diff --git a/modules/juce_audio_devices/native/juce_WASAPI_windows.cpp b/modules/juce_audio_devices/native/juce_WASAPI_windows.cpp index 5bdf112e77..6c85b989ff 100644 --- a/modules/juce_audio_devices/native/juce_WASAPI_windows.cpp +++ b/modules/juce_audio_devices/native/juce_WASAPI_windows.cpp @@ -412,12 +412,25 @@ static bool supportsSampleRateConversion (WASAPIDeviceMode deviceMode) noexcept } //============================================================================== +struct WASAPIDeviceBaseDelegate +{ + virtual ~WASAPIDeviceBaseDelegate() = default; + virtual void deviceOpened (IMMDevice*) = 0; + virtual void deviceSampleRateChanged (IMMDevice*) = 0; + virtual void deviceSessionBecameInactive (IMMDevice*) = 0; + virtual void deviceSessionExpired (IMMDevice*) = 0; + virtual void deviceSessionBecameActive (IMMDevice*) = 0; +}; + class WASAPIDeviceBase { public: - WASAPIDeviceBase (const ComSmartPtr& d, WASAPIDeviceMode mode) + WASAPIDeviceBase (const ComSmartPtr& d, + WASAPIDeviceMode mode, + WASAPIDeviceBaseDelegate& delegateIn) : device (d), - deviceMode (mode) + deviceMode (mode), + delegate (delegateIn) { clientEvent = CreateEvent (nullptr, false, false, nullptr); @@ -468,8 +481,7 @@ public: if (client == nullptr || ! tryInitialisingWithBufferSize (bufferSizeSamples)) return false; - sampleRateHasChanged = false; - shouldShutdown = false; + delegate.deviceOpened (device); channelMaps.clear(); @@ -503,22 +515,22 @@ public: void deviceSampleRateChanged() { - sampleRateHasChanged = true; + delegate.deviceSampleRateChanged (device); } void deviceSessionBecameInactive() { - isActive = false; + delegate.deviceSessionBecameInactive (device); } void deviceSessionExpired() { - shouldShutdown = true; + delegate.deviceSessionExpired (device); } void deviceSessionBecameActive() { - isActive = true; + delegate.deviceSessionBecameActive (device); } std::optional getDefaultLayout() const @@ -537,6 +549,8 @@ public: WASAPIDeviceMode deviceMode; + WASAPIDeviceBaseDelegate& delegate; + double sampleRate = 0, defaultSampleRate = 0; int numChannels = 0, actualNumChannels = 0, maxNumChannels = 0, defaultNumChannels = 0; int minBufferSize = 0, defaultBufferSize = 0, latencySamples = 0; @@ -548,7 +562,6 @@ public: Array channelMaps; UINT32 actualBufferSize = 0; int bytesPerSample = 0, bytesPerFrame = 0; - std::atomic sampleRateHasChanged { false }, shouldShutdown { false }, isActive { true }; virtual void updateFormat (bool isFloat) = 0; @@ -919,10 +932,7 @@ private: class WASAPIInputDevice final : public WASAPIDeviceBase { public: - WASAPIInputDevice (const ComSmartPtr& d, WASAPIDeviceMode mode) - : WASAPIDeviceBase (d, mode) - { - } + using WASAPIDeviceBase::WASAPIDeviceBase; ~WASAPIInputDevice() override { @@ -971,7 +981,7 @@ public: return false; purgeInputBuffers(); - isActive = true; + delegate.deviceSessionBecameActive (device); return true; } @@ -1067,10 +1077,7 @@ private: class WASAPIOutputDevice final : public WASAPIDeviceBase { public: - WASAPIOutputDevice (const ComSmartPtr& d, WASAPIDeviceMode mode) - : WASAPIDeviceBase (d, mode) - { - } + using WASAPIDeviceBase::WASAPIDeviceBase; ~WASAPIOutputDevice() override { @@ -1116,7 +1123,7 @@ public: if (! check (client->Start())) return false; - isActive = true; + delegate.deviceSessionBecameActive (device); return true; } @@ -1193,6 +1200,7 @@ private: //============================================================================== class WASAPIAudioIODevice final : public AudioIODevice, public Thread, + private WASAPIDeviceBaseDelegate, private AsyncUpdater { public: @@ -1402,8 +1410,8 @@ public: if (inputDevice != nullptr) ResetEvent (inputDevice->clientEvent); if (outputDevice != nullptr) ResetEvent (outputDevice->clientEvent); - // No lock here; background thread is not running - flags &= ~(flagShutdown | flagSampleRateChanged); + flags.fetch_and (~(flagShutdown | flagInputSampleRateDidChange | flagOutputSampleRateDidChange), + std::memory_order_acq_rel); startThread (Priority::high); Thread::sleep (5); @@ -1432,7 +1440,7 @@ public: } } - flags |= flagOpen; + flags.fetch_or (flagOpen, std::memory_order_acq_rel); return lastError; } @@ -1451,17 +1459,17 @@ public: if (outputDevice != nullptr) outputDevice->close(); // Background thread has stopped at this point - flags &= ~flagOpen; + flags.fetch_and (~flagOpen, std::memory_order_acq_rel); } bool isOpen() override { - return ((flags & flagOpen) != 0) && isThreadRunning(); + return ((flags.load (std::memory_order_acquire) & flagOpen) != 0) && isThreadRunning(); } bool isPlaying() override { - return ((flags & (flagOpen | flagStarted)) == (flagOpen | flagStarted)) && isThreadRunning(); + return ((flags.load (std::memory_order_acquire) & (flagOpen | flagStarted)) == (flagOpen | flagStarted)) && isThreadRunning(); } void start (AudioIODeviceCallback* call) override @@ -1469,13 +1477,13 @@ public: { const ScopedLock sl (startStopLock); - if ((flags & (flagOpen | flagStarted)) != flagOpen || call == nullptr) + if ((flags.load (std::memory_order_acquire) & (flagOpen | flagStarted)) != flagOpen || call == nullptr) return; if (! isThreadRunning()) { // something's gone wrong and the thread's stopped - flags &= ~flagOpen; + flags.fetch_and (~flagOpen, std::memory_order_acq_rel); return; } } @@ -1486,7 +1494,7 @@ public: const ScopedLock sl (startStopLock); callback = call; - flags |= flagStarted; + flags.fetch_or (flagStarted, std::memory_order_acq_rel); } } @@ -1496,7 +1504,7 @@ public: { const ScopedLock sl (startStopLock); - const auto wasStarted = (flags.fetch_and (~flagStarted) & flagStarted) != 0; + const auto wasStarted = (flags.fetch_and (~flagStarted, std::memory_order_acq_rel) & flagStarted) != 0; return wasStarted ? callback : nullptr; }); @@ -1534,17 +1542,16 @@ public: while (! threadShouldExit()) { - if ((outputDevice != nullptr && outputDevice->shouldShutdown) - || (inputDevice != nullptr && inputDevice->shouldShutdown)) - { - flags |= flagShutdown; - triggerAsyncUpdate(); + const auto loadedFlags = flags.load (std::memory_order_acquire); + if ((loadedFlags & (flagShutdown | flagOutputSampleRateDidChange | flagInputSampleRateDidChange)) != 0) + { + triggerAsyncUpdate(); break; } - auto inputDeviceActive = (inputDevice != nullptr && inputDevice->isActive); - auto outputDeviceActive = (outputDevice != nullptr && outputDevice->isActive); + const auto inputDeviceActive = (loadedFlags & flagInputIsActive) != 0; + const auto outputDeviceActive = (loadedFlags & flagOutputIsActive) != 0; if (! inputDeviceActive && ! outputDeviceActive) continue; @@ -1568,20 +1575,12 @@ public: } inputDevice->copyBuffersFromReservoir (ins.getArrayOfWritePointers(), numInputBuffers, bufferSize); - - if (inputDevice->sampleRateHasChanged) - { - flags |= flagSampleRateChanged; - triggerAsyncUpdate(); - - break; - } } { const ScopedTryLock sl (startStopLock); - if (sl.isLocked() && (flags & flagStarted) != 0) + if (sl.isLocked() && (loadedFlags & flagStarted) != 0) { callback->audioDeviceIOCallbackWithContext (ins.getArrayOfReadPointers(), numInputBuffers, @@ -1601,14 +1600,6 @@ public: // 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 outputDevice->copyBuffers (outs.getArrayOfReadPointers(), numOutputBuffers, bufferSize, inputDevice.get(), *this); - - if (outputDevice->sampleRateHasChanged) - { - flags |= flagSampleRateChanged; - triggerAsyncUpdate(); - - break; - } } } } @@ -1618,6 +1609,45 @@ public: String lastError; private: + void deviceOpened (IMMDevice* d) override + { + const auto newFlags = inputDevice == nullptr || inputDevice->device != d + ? ~flagOutputSampleRateDidChange + : ~flagInputSampleRateDidChange; + flags.fetch_and (newFlags, std::memory_order_acq_rel); + } + + void deviceSampleRateChanged (IMMDevice* d) override + { + const auto newFlags = inputDevice == nullptr || inputDevice->device != d + ? flagOutputSampleRateDidChange + : flagInputSampleRateDidChange; + flags.fetch_or (newFlags, std::memory_order_acq_rel); + } + + void deviceSessionBecameInactive (IMMDevice* d) override + { + const auto newFlags = inputDevice == nullptr || inputDevice->device != d + ? flagOutputIsActive + : flagInputIsActive; + flags.fetch_and (~newFlags, std::memory_order_acq_rel); + } + + void deviceSessionExpired (IMMDevice*) override + { + flags.fetch_or (flagShutdown, std::memory_order_acq_rel); + } + + void deviceSessionBecameActive (IMMDevice* d) override + { + const auto newFlags = inputDevice == nullptr || inputDevice->device != d + ? flagOutputIsActive + : flagInputIsActive; + flags.fetch_or (newFlags, std::memory_order_acq_rel); + } + + std::atomic flags { 0 }; + // Device stats... std::unique_ptr inputDevice; std::unique_ptr outputDevice; @@ -1629,15 +1659,17 @@ private: Array sampleRates; Array bufferSizes; - // Active state... - std::atomic flags { 0 }; - enum Flags { - flagOpen = 1 << 0, - flagStarted = 1 << 1, - flagShutdown = 1 << 2, - flagSampleRateChanged = 1 << 3, + flagOpen = 1 << 0, + flagStarted = 1 << 1, + flagShutdown = 1 << 2, + + flagInputSampleRateDidChange = 1 << 3, + flagInputIsActive = 1 << 4, + + flagOutputSampleRateDidChange = 1 << 5, + flagOutputIsActive = 1 << 6, }; int currentBufferSizeSamples = 0; @@ -1680,9 +1712,9 @@ private: auto flow = getDataFlow (device); if (deviceId == inputDeviceId && flow == eCapture) - inputDevice.reset (new WASAPIInputDevice (device, deviceMode)); + inputDevice.reset (new WASAPIInputDevice (device, deviceMode, *this)); else if (deviceId == outputDeviceId && flow == eRender) - outputDevice.reset (new WASAPIOutputDevice (device, deviceMode)); + outputDevice.reset (new WASAPIOutputDevice (device, deviceMode, *this)); } return (outputDeviceId.isEmpty() || (outputDevice != nullptr && outputDevice->isOk())) @@ -1700,7 +1732,8 @@ private: inputDevice = nullptr; }; - const auto prevFlags = flags.fetch_and (~(flagShutdown | flagSampleRateChanged)); + const auto prevFlags = flags.fetch_and (~(flagShutdown | flagInputSampleRateDidChange | flagOutputSampleRateDidChange), + std::memory_order_acq_rel); if ((prevFlags & flagShutdown) != 0) { @@ -1708,10 +1741,10 @@ private: return; } - if ((prevFlags & flagSampleRateChanged) == 0) + if ((prevFlags & (flagInputSampleRateDidChange | flagOutputSampleRateDidChange)) == 0) return; - auto sampleRateChangedByInput = (inputDevice != nullptr && inputDevice->sampleRateHasChanged); + const auto sampleRateChangedByInput = (prevFlags & flagInputSampleRateDidChange) != 0; closeDevices(); initialise();