| @@ -67,6 +67,10 @@ void logFailure (HRESULT hr) | |||||
| case AUDCLNT_E_EVENTHANDLE_NOT_SET: m = "AUDCLNT_E_EVENTHANDLE_NOT_SET"; break; | 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_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_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_BUFFER_EMPTY: m = "AUDCLNT_S_BUFFER_EMPTY"; break; | ||||
| case AUDCLNT_S_THREAD_ALREADY_REGISTERED: m = "AUDCLNT_S_THREAD_ALREADY_REGISTERED"; break; | case AUDCLNT_S_THREAD_ALREADY_REGISTERED: m = "AUDCLNT_S_THREAD_ALREADY_REGISTERED"; break; | ||||
| default: break; | default: break; | ||||
| @@ -124,7 +128,7 @@ namespace | |||||
| eCommunications = (eMultimedia + 1) | 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 OnDeviceStateChanged(LPCWSTR, DWORD) = 0; | ||||
| virtual HRESULT STDMETHODCALLTYPE OnDeviceAdded(LPCWSTR) = 0; | virtual HRESULT STDMETHODCALLTYPE OnDeviceAdded(LPCWSTR) = 0; | ||||
| @@ -184,8 +188,8 @@ void copyWavFormat (WAVEFORMATEXTENSIBLE& dest, const WAVEFORMATEX* const src) n | |||||
| class WASAPIDeviceBase | class WASAPIDeviceBase | ||||
| { | { | ||||
| public: | public: | ||||
| WASAPIDeviceBase (const ComSmartPtr <IMMDevice>& device_, const bool useExclusiveMode_) | |||||
| : device (device_), | |||||
| WASAPIDeviceBase (const ComSmartPtr <IMMDevice>& d, const bool exclusiveMode) | |||||
| : device (d), | |||||
| sampleRate (0), | sampleRate (0), | ||||
| defaultSampleRate (0), | defaultSampleRate (0), | ||||
| numChannels (0), | numChannels (0), | ||||
| @@ -193,7 +197,7 @@ public: | |||||
| minBufferSize (0), | minBufferSize (0), | ||||
| defaultBufferSize (0), | defaultBufferSize (0), | ||||
| latencySamples (0), | latencySamples (0), | ||||
| useExclusiveMode (useExclusiveMode_), | |||||
| useExclusiveMode (exclusiveMode), | |||||
| sampleRateHasChanged (false) | sampleRateHasChanged (false) | ||||
| { | { | ||||
| clientEvent = CreateEvent (0, false, false, _T("JuceWASAPI")); | clientEvent = CreateEvent (0, false, false, _T("JuceWASAPI")); | ||||
| @@ -221,14 +225,14 @@ public: | |||||
| rates.addUsingDefaultSort (defaultSampleRate); | 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) | for (int i = 0; i < numElementsInArray (ratesToTest); ++i) | ||||
| { | { | ||||
| if (ratesToTest[i] == defaultSampleRate) | if (ratesToTest[i] == defaultSampleRate) | ||||
| continue; | 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, | if (SUCCEEDED (tempClient->IsFormatSupported (useExclusiveMode ? AUDCLNT_SHAREMODE_EXCLUSIVE : AUDCLNT_SHAREMODE_SHARED, | ||||
| (WAVEFORMATEX*) &format, 0))) | (WAVEFORMATEX*) &format, 0))) | ||||
| @@ -375,7 +379,8 @@ private: | |||||
| if (device != nullptr) | 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); | logFailure (hr); | ||||
| } | } | ||||
| @@ -456,8 +461,8 @@ private: | |||||
| class WASAPIInputDevice : public WASAPIDeviceBase | class WASAPIInputDevice : public WASAPIDeviceBase | ||||
| { | { | ||||
| public: | public: | ||||
| WASAPIInputDevice (const ComSmartPtr <IMMDevice>& device_, const bool useExclusiveMode_) | |||||
| : WASAPIDeviceBase (device_, useExclusiveMode_), | |||||
| WASAPIInputDevice (const ComSmartPtr <IMMDevice>& d, const bool exclusiveMode) | |||||
| : WASAPIDeviceBase (d, exclusiveMode), | |||||
| reservoir (1, 1) | reservoir (1, 1) | ||||
| { | { | ||||
| } | } | ||||
| @@ -574,8 +579,8 @@ private: | |||||
| class WASAPIOutputDevice : public WASAPIDeviceBase | class WASAPIOutputDevice : public WASAPIDeviceBase | ||||
| { | { | ||||
| public: | public: | ||||
| WASAPIOutputDevice (const ComSmartPtr <IMMDevice>& device_, const bool useExclusiveMode_) | |||||
| : WASAPIDeviceBase (device_, useExclusiveMode_) | |||||
| WASAPIOutputDevice (const ComSmartPtr <IMMDevice>& d, const bool exclusiveMode) | |||||
| : WASAPIDeviceBase (d, exclusiveMode) | |||||
| { | { | ||||
| } | } | ||||
| @@ -659,18 +664,19 @@ private: | |||||
| //============================================================================== | //============================================================================== | ||||
| class WASAPIAudioIODevice : public AudioIODevice, | class WASAPIAudioIODevice : public AudioIODevice, | ||||
| public Thread | |||||
| public Thread, | |||||
| private AsyncUpdater | |||||
| { | { | ||||
| public: | public: | ||||
| WASAPIAudioIODevice (const String& deviceName, | WASAPIAudioIODevice (const String& deviceName, | ||||
| const String& outputDeviceId_, | const String& outputDeviceId_, | ||||
| const String& inputDeviceId_, | const String& inputDeviceId_, | ||||
| const bool useExclusiveMode_) | |||||
| const bool exclusiveMode) | |||||
| : AudioIODevice (deviceName, "Windows Audio"), | : AudioIODevice (deviceName, "Windows Audio"), | ||||
| Thread ("Juce WASAPI"), | Thread ("Juce WASAPI"), | ||||
| outputDeviceId (outputDeviceId_), | outputDeviceId (outputDeviceId_), | ||||
| inputDeviceId (inputDeviceId_), | inputDeviceId (inputDeviceId_), | ||||
| useExclusiveMode (useExclusiveMode_), | |||||
| useExclusiveMode (exclusiveMode), | |||||
| isOpen_ (false), | isOpen_ (false), | ||||
| isStarted (false), | isStarted (false), | ||||
| currentBufferSizeSamples (0), | currentBufferSizeSamples (0), | ||||
| @@ -780,8 +786,10 @@ public: | |||||
| return lastError; | 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)) | if (inputDevice != nullptr && ! inputDevice->open (currentSampleRate, inputChannels)) | ||||
| { | { | ||||
| @@ -904,10 +912,10 @@ public: | |||||
| { | { | ||||
| setMMThreadPriority(); | 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 ins (jmax (1, numInputBuffers), bufferSize + 32); | ||||
| AudioSampleBuffer outs (jmax (1, numOutputBuffers), bufferSize + 32); | AudioSampleBuffer outs (jmax (1, numOutputBuffers), bufferSize + 32); | ||||
| @@ -925,7 +933,10 @@ public: | |||||
| break; | break; | ||||
| if (inputDevice->sampleRateHasChanged) | if (inputDevice->sampleRateHasChanged) | ||||
| { | |||||
| sampleRateChanged = true; | sampleRateChanged = true; | ||||
| sampleRateChangedByOutput = false; | |||||
| } | |||||
| } | } | ||||
| { | { | ||||
| @@ -943,13 +954,16 @@ public: | |||||
| outputDevice->copyBuffers (const_cast <const float**> (outputBuffers), numOutputBuffers, bufferSize, *this); | outputDevice->copyBuffers (const_cast <const float**> (outputBuffers), numOutputBuffers, bufferSize, *this); | ||||
| if (outputDevice->sampleRateHasChanged) | if (outputDevice->sampleRateHasChanged) | ||||
| { | |||||
| sampleRateChanged = true; | sampleRateChanged = true; | ||||
| sampleRateChangedByOutput = true; | |||||
| } | |||||
| } | } | ||||
| if (sampleRateChanged) | 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; | bool isOpen_, isStarted; | ||||
| int currentBufferSizeSamples; | int currentBufferSizeSamples; | ||||
| double currentSampleRate; | double currentSampleRate; | ||||
| bool sampleRateChangedByOutput; | |||||
| AudioIODeviceCallback* callback; | AudioIODeviceCallback* callback; | ||||
| CriticalSection startStopLock; | CriticalSection startStopLock; | ||||
| BigInteger lastKnownInputChannels, lastKnownOutputChannels; | |||||
| //============================================================================== | //============================================================================== | ||||
| bool createDevices() | bool createDevices() | ||||
| { | { | ||||
| @@ -1014,6 +1031,32 @@ private: | |||||
| && (inputDeviceId.isEmpty() || (inputDevice != nullptr && inputDevice->isOk())); | && (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) | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WASAPIAudioIODevice) | ||||
| }; | }; | ||||
| @@ -1031,6 +1074,12 @@ public: | |||||
| { | { | ||||
| } | } | ||||
| ~WASAPIAudioIODeviceType() | |||||
| { | |||||
| if (notifyClient != nullptr) | |||||
| enumerator->UnregisterEndpointNotificationCallback (notifyClient); | |||||
| } | |||||
| //============================================================================== | //============================================================================== | ||||
| void scanForDevices() | void scanForDevices() | ||||
| { | { | ||||
| @@ -1101,6 +1150,30 @@ public: | |||||
| private: | private: | ||||
| bool hasScanned; | bool hasScanned; | ||||
| ComSmartPtr<IMMDeviceEnumerator> enumerator; | |||||
| //============================================================================== | |||||
| class ChangeNotificationClient : public ComBaseClassHelper<IMMNotificationClient> | |||||
| { | |||||
| public: | |||||
| ChangeNotificationClient (WASAPIAudioIODeviceType& d) | |||||
| : ComBaseClassHelper <IMMNotificationClient> (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<ChangeNotificationClient> notifyClient; | |||||
| //============================================================================== | //============================================================================== | ||||
| static String getDefaultEndpoint (IMMDeviceEnumerator* const enumerator, const bool forCapture) | static String getDefaultEndpoint (IMMDeviceEnumerator* const enumerator, const bool forCapture) | ||||
| @@ -1129,9 +1202,14 @@ private: | |||||
| StringArray& outputDeviceIds, | StringArray& outputDeviceIds, | ||||
| StringArray& inputDeviceIds) | StringArray& inputDeviceIds) | ||||
| { | { | ||||
| ComSmartPtr <IMMDeviceEnumerator> 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 defaultRenderer (getDefaultEndpoint (enumerator, false)); | ||||
| const String defaultCapture (getDefaultEndpoint (enumerator, true)); | const String defaultCapture (getDefaultEndpoint (enumerator, true)); | ||||
| @@ -1205,9 +1283,9 @@ private: | |||||
| inputDeviceNames = newInNames; | inputDeviceNames = newInNames; | ||||
| outputDeviceIds = newOutIds; | outputDeviceIds = newOutIds; | ||||
| inputDeviceIds = newInIds; | inputDeviceIds = newInIds; | ||||
| callDeviceChangeListeners(); | |||||
| } | } | ||||
| callDeviceChangeListeners(); | |||||
| } | } | ||||
| //============================================================================== | //============================================================================== | ||||