From a99b309d3c8293513ea94569b9516852e13a62b3 Mon Sep 17 00:00:00 2001 From: jules Date: Fri, 15 Feb 2013 16:56:13 +0000 Subject: [PATCH] Added WASAPI device change detection. --- .../native/juce_win32_WASAPI.cpp | 132 ++++++++++++++---- 1 file changed, 105 insertions(+), 27 deletions(-) diff --git a/modules/juce_audio_devices/native/juce_win32_WASAPI.cpp b/modules/juce_audio_devices/native/juce_win32_WASAPI.cpp index 584da65878..082b30a617 100644 --- a/modules/juce_audio_devices/native/juce_win32_WASAPI.cpp +++ b/modules/juce_audio_devices/native/juce_win32_WASAPI.cpp @@ -67,6 +67,10 @@ void logFailure (HRESULT hr) case AUDCLNT_E_EVENTHANDLE_NOT_SET: m = "AUDCLNT_E_EVENTHANDLE_NOT_SET"; break; case AUDCLNT_E_INCORRECT_BUFFER_SIZE: m = "AUDCLNT_E_INCORRECT_BUFFER_SIZE"; break; case AUDCLNT_E_BUFFER_SIZE_ERROR: m = "AUDCLNT_E_BUFFER_SIZE_ERROR"; break; + case AUDCLNT_E_CPUUSAGE_EXCEEDED: m = "AUDCLNT_E_CPUUSAGE_EXCEEDED"; break; + case AUDCLNT_E_BUFFER_ERROR: m = "AUDCLNT_E_BUFFER_ERROR"; break; + case AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED: m = "AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED"; break; + case AUDCLNT_E_INVALID_DEVICE_PERIOD: m = "AUDCLNT_E_INVALID_DEVICE_PERIOD"; break; case AUDCLNT_S_BUFFER_EMPTY: m = "AUDCLNT_S_BUFFER_EMPTY"; break; case AUDCLNT_S_THREAD_ALREADY_REGISTERED: m = "AUDCLNT_S_THREAD_ALREADY_REGISTERED"; break; default: break; @@ -124,7 +128,7 @@ namespace eCommunications = (eMultimedia + 1) }; - struct IMMNotificationClient : public IUnknown + struct __declspec (uuid ("7991EEC9-7E89-4D85-8390-6C703CEC60C0")) IMMNotificationClient : public IUnknown { virtual HRESULT STDMETHODCALLTYPE OnDeviceStateChanged(LPCWSTR, DWORD) = 0; virtual HRESULT STDMETHODCALLTYPE OnDeviceAdded(LPCWSTR) = 0; @@ -184,8 +188,8 @@ void copyWavFormat (WAVEFORMATEXTENSIBLE& dest, const WAVEFORMATEX* const src) n class WASAPIDeviceBase { public: - WASAPIDeviceBase (const ComSmartPtr & device_, const bool useExclusiveMode_) - : device (device_), + WASAPIDeviceBase (const ComSmartPtr & d, const bool exclusiveMode) + : device (d), sampleRate (0), defaultSampleRate (0), numChannels (0), @@ -193,7 +197,7 @@ public: minBufferSize (0), defaultBufferSize (0), latencySamples (0), - useExclusiveMode (useExclusiveMode_), + useExclusiveMode (exclusiveMode), sampleRateHasChanged (false) { clientEvent = CreateEvent (0, false, false, _T("JuceWASAPI")); @@ -221,14 +225,14 @@ public: rates.addUsingDefaultSort (defaultSampleRate); - static const double ratesToTest[] = { 44100.0, 48000.0, 88200.0, 96000.0 }; + static const int ratesToTest[] = { 44100, 48000, 88200, 96000, 176400, 192000 }; for (int i = 0; i < numElementsInArray (ratesToTest); ++i) { if (ratesToTest[i] == defaultSampleRate) continue; - format.Format.nSamplesPerSec = (DWORD) roundDoubleToInt (ratesToTest[i]); + format.Format.nSamplesPerSec = (DWORD) ratesToTest[i]; if (SUCCEEDED (tempClient->IsFormatSupported (useExclusiveMode ? AUDCLNT_SHAREMODE_EXCLUSIVE : AUDCLNT_SHAREMODE_SHARED, (WAVEFORMATEX*) &format, 0))) @@ -375,7 +379,8 @@ private: if (device != nullptr) { - HRESULT hr = device->Activate (__uuidof (IAudioClient), CLSCTX_INPROC_SERVER, 0, (void**) client.resetAndGetPointerAddress()); + HRESULT hr = device->Activate (__uuidof (IAudioClient), CLSCTX_INPROC_SERVER, + nullptr, (void**) client.resetAndGetPointerAddress()); logFailure (hr); } @@ -456,8 +461,8 @@ private: class WASAPIInputDevice : public WASAPIDeviceBase { public: - WASAPIInputDevice (const ComSmartPtr & device_, const bool useExclusiveMode_) - : WASAPIDeviceBase (device_, useExclusiveMode_), + WASAPIInputDevice (const ComSmartPtr & d, const bool exclusiveMode) + : WASAPIDeviceBase (d, exclusiveMode), reservoir (1, 1) { } @@ -574,8 +579,8 @@ private: class WASAPIOutputDevice : public WASAPIDeviceBase { public: - WASAPIOutputDevice (const ComSmartPtr & device_, const bool useExclusiveMode_) - : WASAPIDeviceBase (device_, useExclusiveMode_) + WASAPIOutputDevice (const ComSmartPtr & d, const bool exclusiveMode) + : WASAPIDeviceBase (d, exclusiveMode) { } @@ -659,18 +664,19 @@ private: //============================================================================== class WASAPIAudioIODevice : public AudioIODevice, - public Thread + public Thread, + private AsyncUpdater { public: WASAPIAudioIODevice (const String& deviceName, const String& outputDeviceId_, const String& inputDeviceId_, - const bool useExclusiveMode_) + const bool exclusiveMode) : AudioIODevice (deviceName, "Windows Audio"), Thread ("Juce WASAPI"), outputDeviceId (outputDeviceId_), inputDeviceId (inputDeviceId_), - useExclusiveMode (useExclusiveMode_), + useExclusiveMode (exclusiveMode), isOpen_ (false), isStarted (false), currentBufferSizeSamples (0), @@ -780,8 +786,10 @@ public: return lastError; } - currentBufferSizeSamples = bufferSizeSamples <= 0 ? defaultBufferSize : jmax (bufferSizeSamples, minBufferSize); - currentSampleRate = sampleRate > 0 ? sampleRate : defaultSampleRate; + currentBufferSizeSamples = bufferSizeSamples <= 0 ? defaultBufferSize : jmax (bufferSizeSamples, minBufferSize); + currentSampleRate = sampleRate > 0 ? sampleRate : defaultSampleRate; + lastKnownInputChannels = inputChannels; + lastKnownOutputChannels = outputChannels; if (inputDevice != nullptr && ! inputDevice->open (currentSampleRate, inputChannels)) { @@ -904,10 +912,10 @@ public: { setMMThreadPriority(); - const int bufferSize = currentBufferSizeSamples; - const int numInputBuffers = getActiveInputChannels().countNumberOfSetBits(); - const int numOutputBuffers = getActiveOutputChannels().countNumberOfSetBits(); - bool sampleRateChanged = false; + const int bufferSize = currentBufferSizeSamples; + const int numInputBuffers = getActiveInputChannels().countNumberOfSetBits(); + const int numOutputBuffers = getActiveOutputChannels().countNumberOfSetBits(); + bool sampleRateChanged = false; AudioSampleBuffer ins (jmax (1, numInputBuffers), bufferSize + 32); AudioSampleBuffer outs (jmax (1, numOutputBuffers), bufferSize + 32); @@ -925,7 +933,10 @@ public: break; if (inputDevice->sampleRateHasChanged) + { sampleRateChanged = true; + sampleRateChangedByOutput = false; + } } { @@ -943,13 +954,16 @@ public: outputDevice->copyBuffers (const_cast (outputBuffers), numOutputBuffers, bufferSize, *this); if (outputDevice->sampleRateHasChanged) + { sampleRateChanged = true; + sampleRateChangedByOutput = true; + } } if (sampleRateChanged) { - // xxx one of the devices has had its sample rate changed externally.. not 100% sure how - // to handle this.. + triggerAsyncUpdate(); + break; //Quit the thread... will restart it later! } } } @@ -973,10 +987,13 @@ private: bool isOpen_, isStarted; int currentBufferSizeSamples; double currentSampleRate; + bool sampleRateChangedByOutput; AudioIODeviceCallback* callback; CriticalSection startStopLock; + BigInteger lastKnownInputChannels, lastKnownOutputChannels; + //============================================================================== bool createDevices() { @@ -1014,6 +1031,32 @@ private: && (inputDeviceId.isEmpty() || (inputDevice != nullptr && inputDevice->isOk())); } + //============================================================================== + void handleAsyncUpdate() + { + stop(); + + outputDevice = nullptr; + inputDevice = nullptr; + initialise(); + + open (lastKnownInputChannels, lastKnownOutputChannels, + getChangedSampleRate(), currentBufferSizeSamples); + + start (callback); + } + + double getChangedSampleRate() const + { + if (outputDevice != nullptr && sampleRateChangedByOutput) + return outputDevice->defaultSampleRate; + + if (inputDevice != nullptr && ! sampleRateChangedByOutput) + return inputDevice->defaultSampleRate; + + return 0.0; + } + //============================================================================== JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WASAPIAudioIODevice) }; @@ -1031,6 +1074,12 @@ public: { } + ~WASAPIAudioIODeviceType() + { + if (notifyClient != nullptr) + enumerator->UnregisterEndpointNotificationCallback (notifyClient); + } + //============================================================================== void scanForDevices() { @@ -1101,6 +1150,30 @@ public: private: bool hasScanned; + ComSmartPtr enumerator; + + //============================================================================== + class ChangeNotificationClient : public ComBaseClassHelper + { + public: + ChangeNotificationClient (WASAPIAudioIODeviceType& d) + : ComBaseClassHelper (0), device (d) {} + + HRESULT STDMETHODCALLTYPE OnDeviceAdded (LPCWSTR) { return notify(); } + HRESULT STDMETHODCALLTYPE OnDeviceRemoved (LPCWSTR) { return notify(); } + HRESULT STDMETHODCALLTYPE OnDeviceStateChanged (LPCWSTR, DWORD) { return notify(); } + HRESULT STDMETHODCALLTYPE OnDefaultDeviceChanged (EDataFlow, ERole, LPCWSTR) { return notify(); } + HRESULT STDMETHODCALLTYPE OnPropertyValueChanged (LPCWSTR, const PROPERTYKEY) { return notify(); } + + private: + WASAPIAudioIODeviceType& device; + + HRESULT notify() { device.systemDeviceChanged(); return S_OK; } + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ChangeNotificationClient) + }; + + ComSmartPtr notifyClient; //============================================================================== static String getDefaultEndpoint (IMMDeviceEnumerator* const enumerator, const bool forCapture) @@ -1129,9 +1202,14 @@ private: StringArray& outputDeviceIds, StringArray& inputDeviceIds) { - ComSmartPtr enumerator; - if (! check (enumerator.CoCreateInstance (__uuidof (MMDeviceEnumerator)))) - return; + if (enumerator == nullptr) + { + if (! check (enumerator.CoCreateInstance (__uuidof (MMDeviceEnumerator)))) + return; + + notifyClient = new ChangeNotificationClient (*this); + enumerator->RegisterEndpointNotificationCallback (notifyClient); + } const String defaultRenderer (getDefaultEndpoint (enumerator, false)); const String defaultCapture (getDefaultEndpoint (enumerator, true)); @@ -1205,9 +1283,9 @@ private: inputDeviceNames = newInNames; outputDeviceIds = newOutIds; inputDeviceIds = newInIds; - - callDeviceChangeListeners(); } + + callDeviceChangeListeners(); } //==============================================================================