| @@ -352,8 +352,8 @@ void copyWavFormat (WAVEFORMATEXTENSIBLE& dest, const WAVEFORMATEX* src) noexcep | |||||
| class WASAPIDeviceBase | class WASAPIDeviceBase | ||||
| { | { | ||||
| public: | public: | ||||
| WASAPIDeviceBase (const ComSmartPtr<IMMDevice>& d, bool exclusiveMode, std::function<void()>&& cb) | |||||
| : device (d), useExclusiveMode (exclusiveMode), reopenCallback (cb) | |||||
| WASAPIDeviceBase (const ComSmartPtr<IMMDevice>& d, bool exclusiveMode) | |||||
| : device (d), useExclusiveMode (exclusiveMode) | |||||
| { | { | ||||
| clientEvent = CreateEvent (nullptr, false, false, nullptr); | clientEvent = CreateEvent (nullptr, false, false, nullptr); | ||||
| @@ -429,7 +429,8 @@ public: | |||||
| && tryInitialisingWithBufferSize (bufferSizeSamples)) | && tryInitialisingWithBufferSize (bufferSizeSamples)) | ||||
| { | { | ||||
| sampleRateHasChanged = false; | sampleRateHasChanged = false; | ||||
| shouldClose = false; | |||||
| shouldShutdown = false; | |||||
| channelMaps.clear(); | channelMaps.clear(); | ||||
| for (int i = 0; i <= channels.getHighestBit(); ++i) | for (int i = 0; i <= channels.getHighestBit(); ++i) | ||||
| @@ -468,9 +469,19 @@ public: | |||||
| sampleRateHasChanged = true; | sampleRateHasChanged = true; | ||||
| } | } | ||||
| void deviceBecameInactive() | |||||
| void deviceSessionBecameInactive() | |||||
| { | |||||
| isActive = false; | |||||
| } | |||||
| void deviceSessionExpired() | |||||
| { | |||||
| shouldShutdown = true; | |||||
| } | |||||
| void deviceSessionBecameActive() | |||||
| { | { | ||||
| shouldClose = true; | |||||
| isActive = true; | |||||
| } | } | ||||
| //============================================================================== | //============================================================================== | ||||
| @@ -487,8 +498,7 @@ public: | |||||
| Array<int> channelMaps; | Array<int> channelMaps; | ||||
| UINT32 actualBufferSize = 0; | UINT32 actualBufferSize = 0; | ||||
| int bytesPerSample = 0, bytesPerFrame = 0; | int bytesPerSample = 0, bytesPerFrame = 0; | ||||
| bool sampleRateHasChanged = false, shouldClose = false; | |||||
| std::function<void()> reopenCallback; | |||||
| std::atomic<bool> sampleRateHasChanged { false }, shouldShutdown { false }, isActive { true }; | |||||
| virtual void updateFormat (bool isFloat) = 0; | virtual void updateFormat (bool isFloat) = 0; | ||||
| @@ -504,13 +514,20 @@ private: | |||||
| JUCE_COMRESULT OnChannelVolumeChanged (DWORD, float*, DWORD, LPCGUID) { return S_OK; } | JUCE_COMRESULT OnChannelVolumeChanged (DWORD, float*, DWORD, LPCGUID) { return S_OK; } | ||||
| JUCE_COMRESULT OnGroupingParamChanged (LPCGUID, LPCGUID) { return S_OK; } | JUCE_COMRESULT OnGroupingParamChanged (LPCGUID, LPCGUID) { return S_OK; } | ||||
| JUCE_COMRESULT OnStateChanged(AudioSessionState state) | |||||
| JUCE_COMRESULT OnStateChanged (AudioSessionState state) | |||||
| { | { | ||||
| if (state == AudioSessionStateActive) | |||||
| owner.reopenCallback(); | |||||
| if (state == AudioSessionStateInactive || state == AudioSessionStateExpired) | |||||
| owner.deviceBecameInactive(); | |||||
| switch (state) | |||||
| { | |||||
| case AudioSessionStateInactive: | |||||
| owner.deviceSessionBecameInactive(); | |||||
| break; | |||||
| case AudioSessionStateExpired: | |||||
| owner.deviceSessionExpired(); | |||||
| break; | |||||
| case AudioSessionStateActive: | |||||
| owner.deviceSessionBecameActive(); | |||||
| break; | |||||
| } | |||||
| return S_OK; | return S_OK; | ||||
| } | } | ||||
| @@ -692,8 +709,8 @@ private: | |||||
| class WASAPIInputDevice : public WASAPIDeviceBase | class WASAPIInputDevice : public WASAPIDeviceBase | ||||
| { | { | ||||
| public: | public: | ||||
| WASAPIInputDevice (const ComSmartPtr<IMMDevice>& d, bool exclusiveMode, std::function<void()>&& reopenCallback) | |||||
| : WASAPIDeviceBase (d, exclusiveMode, std::move (reopenCallback)) | |||||
| WASAPIInputDevice (const ComSmartPtr<IMMDevice>& d, bool exclusiveMode) | |||||
| : WASAPIDeviceBase (d, exclusiveMode) | |||||
| { | { | ||||
| } | } | ||||
| @@ -746,6 +763,8 @@ public: | |||||
| return false; | return false; | ||||
| purgeInputBuffers(); | purgeInputBuffers(); | ||||
| isActive = true; | |||||
| return true; | return true; | ||||
| } | } | ||||
| @@ -853,8 +872,8 @@ private: | |||||
| class WASAPIOutputDevice : public WASAPIDeviceBase | class WASAPIOutputDevice : public WASAPIDeviceBase | ||||
| { | { | ||||
| public: | public: | ||||
| WASAPIOutputDevice (const ComSmartPtr<IMMDevice>& d, bool exclusiveMode, std::function<void()>&& reopenCallback) | |||||
| : WASAPIDeviceBase (d, exclusiveMode, std::move (reopenCallback)) | |||||
| WASAPIOutputDevice (const ComSmartPtr<IMMDevice>& d, bool exclusiveMode) | |||||
| : WASAPIDeviceBase (d, exclusiveMode) | |||||
| { | { | ||||
| } | } | ||||
| @@ -899,7 +918,12 @@ public: | |||||
| if (check (renderClient->GetBuffer (samplesToDo, &outputData))) | if (check (renderClient->GetBuffer (samplesToDo, &outputData))) | ||||
| renderClient->ReleaseBuffer (samplesToDo, AUDCLNT_BUFFERFLAGS_SILENT); | renderClient->ReleaseBuffer (samplesToDo, AUDCLNT_BUFFERFLAGS_SILENT); | ||||
| return check (client->Start()); | |||||
| if (! check (client->Start())) | |||||
| return false; | |||||
| isActive = true; | |||||
| return true; | |||||
| } | } | ||||
| int getNumSamplesAvailableToCopy() const | int getNumSamplesAvailableToCopy() const | ||||
| @@ -1124,7 +1148,8 @@ public: | |||||
| if (inputDevice != nullptr) ResetEvent (inputDevice->clientEvent); | if (inputDevice != nullptr) ResetEvent (inputDevice->clientEvent); | ||||
| if (outputDevice != nullptr) ResetEvent (outputDevice->clientEvent); | if (outputDevice != nullptr) ResetEvent (outputDevice->clientEvent); | ||||
| deviceBecameInactive = false; | |||||
| shouldShutdown = false; | |||||
| deviceSampleRateChanged = false; | |||||
| startThread (8); | startThread (8); | ||||
| Thread::sleep (5); | Thread::sleep (5); | ||||
| @@ -1233,7 +1258,6 @@ public: | |||||
| auto bufferSize = currentBufferSizeSamples; | auto bufferSize = currentBufferSizeSamples; | ||||
| auto numInputBuffers = getActiveInputChannels().countNumberOfSetBits(); | auto numInputBuffers = getActiveInputChannels().countNumberOfSetBits(); | ||||
| auto numOutputBuffers = getActiveOutputChannels().countNumberOfSetBits(); | auto numOutputBuffers = getActiveOutputChannels().countNumberOfSetBits(); | ||||
| bool sampleRateHasChanged = false; | |||||
| AudioBuffer<float> ins (jmax (1, numInputBuffers), bufferSize + 32); | AudioBuffer<float> ins (jmax (1, numInputBuffers), bufferSize + 32); | ||||
| AudioBuffer<float> outs (jmax (1, numOutputBuffers), bufferSize + 32); | AudioBuffer<float> outs (jmax (1, numOutputBuffers), bufferSize + 32); | ||||
| @@ -1244,13 +1268,22 @@ public: | |||||
| while (! threadShouldExit()) | while (! threadShouldExit()) | ||||
| { | { | ||||
| if ((outputDevice != nullptr && outputDevice->shouldClose) | |||||
| || (inputDevice != nullptr && inputDevice->shouldClose)) | |||||
| if ((outputDevice != nullptr && outputDevice->shouldShutdown) | |||||
| || (inputDevice != nullptr && inputDevice->shouldShutdown)) | |||||
| { | { | ||||
| deviceBecameInactive = true; | |||||
| shouldShutdown = true; | |||||
| triggerAsyncUpdate(); | |||||
| break; | |||||
| } | } | ||||
| if (inputDevice != nullptr && ! deviceBecameInactive) | |||||
| auto inputDeviceActive = (inputDevice != nullptr && inputDevice->isActive); | |||||
| auto outputDeviceActive = (outputDevice != nullptr && outputDevice->isActive); | |||||
| if (! inputDeviceActive && ! outputDeviceActive) | |||||
| continue; | |||||
| if (inputDeviceActive) | |||||
| { | { | ||||
| if (outputDevice == nullptr) | if (outputDevice == nullptr) | ||||
| { | { | ||||
| @@ -1272,12 +1305,13 @@ public: | |||||
| if (inputDevice->sampleRateHasChanged) | if (inputDevice->sampleRateHasChanged) | ||||
| { | { | ||||
| sampleRateHasChanged = true; | |||||
| sampleRateChangedByOutput = false; | |||||
| deviceSampleRateChanged = true; | |||||
| triggerAsyncUpdate(); | |||||
| break; | |||||
| } | } | ||||
| } | } | ||||
| if (! deviceBecameInactive) | |||||
| { | { | ||||
| const ScopedTryLock sl (startStopLock); | const ScopedTryLock sl (startStopLock); | ||||
| @@ -1288,7 +1322,7 @@ public: | |||||
| outs.clear(); | outs.clear(); | ||||
| } | } | ||||
| if (outputDevice != nullptr && ! deviceBecameInactive) | |||||
| if (outputDeviceActive) | |||||
| { | { | ||||
| // Note that this function is handed the input device so it can check for the event and make sure | // Note that this function is handed the input device so it can check for the event and make sure | ||||
| // the input reservoir is filled up correctly even when bufferSize > device actualBufferSize | // the input reservoir is filled up correctly even when bufferSize > device actualBufferSize | ||||
| @@ -1296,15 +1330,11 @@ public: | |||||
| if (outputDevice->sampleRateHasChanged) | if (outputDevice->sampleRateHasChanged) | ||||
| { | { | ||||
| sampleRateHasChanged = true; | |||||
| sampleRateChangedByOutput = true; | |||||
| } | |||||
| } | |||||
| deviceSampleRateChanged = true; | |||||
| triggerAsyncUpdate(); | |||||
| if (sampleRateHasChanged || deviceBecameInactive) | |||||
| { | |||||
| triggerAsyncUpdate(); | |||||
| break; // Quit the thread... will restart it later! | |||||
| break; | |||||
| } | |||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| @@ -1332,7 +1362,7 @@ private: | |||||
| AudioIODeviceCallback* callback = {}; | AudioIODeviceCallback* callback = {}; | ||||
| CriticalSection startStopLock; | CriticalSection startStopLock; | ||||
| bool sampleRateChangedByOutput = false, deviceBecameInactive = false; | |||||
| std::atomic<bool> shouldShutdown { false }, deviceSampleRateChanged { false }; | |||||
| BigInteger lastKnownInputChannels, lastKnownOutputChannels; | BigInteger lastKnownInputChannels, lastKnownOutputChannels; | ||||
| @@ -1368,60 +1398,54 @@ private: | |||||
| auto flow = getDataFlow (device); | auto flow = getDataFlow (device); | ||||
| auto deviceReopenCallback = [this] | |||||
| { | |||||
| if (deviceBecameInactive) | |||||
| { | |||||
| MessageManager::callAsync ([this] | |||||
| { | |||||
| close(); | |||||
| reopenDevices(); | |||||
| }); | |||||
| } | |||||
| }; | |||||
| if (deviceId == inputDeviceId && flow == eCapture) | if (deviceId == inputDeviceId && flow == eCapture) | ||||
| inputDevice.reset (new WASAPIInputDevice (device, useExclusiveMode, deviceReopenCallback)); | |||||
| inputDevice.reset (new WASAPIInputDevice (device, useExclusiveMode)); | |||||
| else if (deviceId == outputDeviceId && flow == eRender) | else if (deviceId == outputDeviceId && flow == eRender) | ||||
| outputDevice.reset (new WASAPIOutputDevice (device, useExclusiveMode, deviceReopenCallback)); | |||||
| outputDevice.reset (new WASAPIOutputDevice (device, useExclusiveMode)); | |||||
| } | } | ||||
| return (outputDeviceId.isEmpty() || (outputDevice != nullptr && outputDevice->isOk())) | return (outputDeviceId.isEmpty() || (outputDevice != nullptr && outputDevice->isOk())) | ||||
| && (inputDeviceId.isEmpty() || (inputDevice != nullptr && inputDevice->isOk())); | && (inputDeviceId.isEmpty() || (inputDevice != nullptr && inputDevice->isOk())); | ||||
| } | } | ||||
| void reopenDevices() | |||||
| //============================================================================== | |||||
| void handleAsyncUpdate() override | |||||
| { | { | ||||
| outputDevice = nullptr; | |||||
| inputDevice = nullptr; | |||||
| auto closeDevices = [this] | |||||
| { | |||||
| close(); | |||||
| initialise(); | |||||
| outputDevice = nullptr; | |||||
| inputDevice = nullptr; | |||||
| }; | |||||
| open (lastKnownInputChannels, lastKnownOutputChannels, | |||||
| getChangedSampleRate(), currentBufferSizeSamples); | |||||
| if (shouldShutdown) | |||||
| { | |||||
| closeDevices(); | |||||
| } | |||||
| else if (deviceSampleRateChanged) | |||||
| { | |||||
| auto sampleRateChangedByInput = (inputDevice != nullptr && inputDevice->sampleRateHasChanged); | |||||
| start (callback); | |||||
| } | |||||
| closeDevices(); | |||||
| initialise(); | |||||
| //============================================================================== | |||||
| void handleAsyncUpdate() override | |||||
| { | |||||
| stop(); | |||||
| auto changedSampleRate = [this, sampleRateChangedByInput] () | |||||
| { | |||||
| if (inputDevice != nullptr && sampleRateChangedByInput) | |||||
| return inputDevice->defaultSampleRate; | |||||
| // sample rate change | |||||
| if (! deviceBecameInactive) | |||||
| reopenDevices(); | |||||
| } | |||||
| if (outputDevice != nullptr && ! sampleRateChangedByInput) | |||||
| return outputDevice->defaultSampleRate; | |||||
| double getChangedSampleRate() const | |||||
| { | |||||
| if (outputDevice != nullptr && sampleRateChangedByOutput) | |||||
| return outputDevice->defaultSampleRate; | |||||
| return 0.0; | |||||
| }(); | |||||
| if (inputDevice != nullptr && ! sampleRateChangedByOutput) | |||||
| return inputDevice->defaultSampleRate; | |||||
| open (lastKnownInputChannels, lastKnownOutputChannels, | |||||
| changedSampleRate, currentBufferSizeSamples); | |||||
| return 0.0; | |||||
| start (callback); | |||||
| } | |||||
| } | } | ||||
| //============================================================================== | //============================================================================== | ||||
| @@ -1542,7 +1566,7 @@ private: | |||||
| HRESULT notify() | HRESULT notify() | ||||
| { | { | ||||
| if (device != nullptr) | if (device != nullptr) | ||||
| device->triggerAsyncDeviceChangeCallback(); | |||||
| device->triggerAsyncDeviceChangeCallback(); | |||||
| return S_OK; | return S_OK; | ||||
| } | } | ||||