From df206371ff157b7a68c5446f96f9595d561935c7 Mon Sep 17 00:00:00 2001 From: reuk Date: Wed, 12 Jan 2022 17:36:18 +0000 Subject: [PATCH] AudioDeviceManager: Ensure device settings are up to date before notifying callbacks --- .../audio_io/juce_AudioDeviceManager.cpp | 127 ++++++++++++++++-- 1 file changed, 115 insertions(+), 12 deletions(-) diff --git a/modules/juce_audio_devices/audio_io/juce_AudioDeviceManager.cpp b/modules/juce_audio_devices/audio_io/juce_AudioDeviceManager.cpp index 3ffbc3ca25..c6b87f32cc 100644 --- a/modules/juce_audio_devices/audio_io/juce_AudioDeviceManager.cpp +++ b/modules/juce_audio_devices/audio_io/juce_AudioDeviceManager.cpp @@ -715,11 +715,7 @@ String AudioDeviceManager::setAudioDeviceSetup (const AudioDeviceSetup& newSetup currentDeviceType = currentAudioDevice->getTypeName(); currentAudioDevice->start (callbackHandler.get()); - - currentSetup.sampleRate = currentAudioDevice->getCurrentSampleRate(); - currentSetup.bufferSize = currentAudioDevice->getCurrentBufferSizeSamples(); - currentSetup.inputChannels = currentAudioDevice->getActiveInputChannels(); - currentSetup.outputChannels = currentAudioDevice->getActiveOutputChannels(); + updateCurrentSetup(); for (int i = 0; i < availableDeviceTypes.size(); ++i) if (availableDeviceTypes.getUnchecked (i)->getTypeName() == currentDeviceType) @@ -965,6 +961,8 @@ void AudioDeviceManager::audioDeviceAboutToStartInt (AudioIODevice* const device loadMeasurer.reset (device->getCurrentSampleRate(), device->getCurrentBufferSizeSamples()); + updateCurrentSetup(); + { const ScopedLock sl (audioCallbackLock); @@ -972,7 +970,6 @@ void AudioDeviceManager::audioDeviceAboutToStartInt (AudioIODevice* const device callbacks.getUnchecked(i)->audioDeviceAboutToStart (device); } - updateCurrentSetup(); sendChangeMessage(); } @@ -1444,6 +1441,48 @@ public: enableInputChannels (manager); closeDeviceByRequestingEmptyNames (manager); } + + beginTest ("AudioDeviceManager updates its current settings before notifying callbacks when device restarts itself"); + { + AudioDeviceManager manager; + auto deviceType = std::make_unique ("foo", + StringArray { "foo in a", "foo in b" }, + StringArray { "foo out a", "foo out b" }); + auto* ptr = deviceType.get(); + manager.addAudioDeviceType (std::move (deviceType)); + + AudioDeviceManager::AudioDeviceSetup setup; + setup.sampleRate = 48000.0; + setup.bufferSize = 256; + setup.inputDeviceName = "foo in a"; + setup.outputDeviceName = "foo out a"; + setup.useDefaultInputChannels = true; + setup.useDefaultOutputChannels = true; + manager.setAudioDeviceSetup (setup, true); + + const auto currentSetup = manager.getAudioDeviceSetup(); + expectEquals (currentSetup.sampleRate, setup.sampleRate); + expectEquals (currentSetup.bufferSize, setup.bufferSize); + + MockCallback callback; + manager.addAudioCallback (&callback); + + constexpr auto newSr = 10000.0; + constexpr auto newBs = 1024; + auto numCalls = 0; + + // Compilers disagree about whether newSr and newBs need to be captured + callback.aboutToStart = [&] + { + ++numCalls; + const auto current = manager.getAudioDeviceSetup(); + expectEquals (current.sampleRate, newSr); + expectEquals (current.bufferSize, newBs); + }; + + ptr->restartDevices (newSr, newBs); + expectEquals (numCalls, 1); + } } private: @@ -1608,11 +1647,26 @@ private: const String mockBName = "mockB"; const String emptyName = "empty"; - class MockDevice : public AudioIODevice + struct Restartable + { + virtual ~Restartable() = default; + virtual void restart (double newSr, int newBs) = 0; + }; + + class MockDevice : public AudioIODevice, + private Restartable { public: - MockDevice (String typeNameIn, String outNameIn, String inNameIn) - : AudioIODevice ("mock", typeNameIn), outName (outNameIn), inName (inNameIn) {} + MockDevice (ListenerList& l, String typeNameIn, String outNameIn, String inNameIn) + : AudioIODevice ("mock", typeNameIn), listeners (l), outName (outNameIn), inName (inNameIn) + { + listeners.add (this); + } + + ~MockDevice() override + { + listeners.remove (this); + } StringArray getOutputChannelNames() override { return { "o1", "o2", "o3" }; } StringArray getInputChannelNames() override { return { "i1", "i2", "i3" }; } @@ -1634,8 +1688,19 @@ private: void close() override { on = false; } bool isOpen() override { return on; } - void start (AudioIODeviceCallback*) override { playing = true; } - void stop() override { playing = false; } + void start (AudioIODeviceCallback* c) override + { + callback = c; + callback->audioDeviceAboutToStart (this); + playing = true; + } + + void stop() override + { + playing = false; + callback->audioDeviceStopped(); + } + bool isPlaying() override { return playing; } String getLastError() override { return {}; } @@ -1650,6 +1715,16 @@ private: int getInputLatencyInSamples() override { return 0; } private: + void restart (double newSr, int newBs) override + { + stop(); + close(); + open (inChannels, outChannels, newSr, newBs); + start (callback); + } + + ListenerList& listeners; + AudioIODeviceCallback* callback = nullptr; String outName, inName; BigInteger outChannels, inChannels; double sampleRate = 0.0; @@ -1668,6 +1743,12 @@ private: inNames (std::move (inputNames)), outNames (std::move (outputNames)) {} + ~MockDeviceType() override + { + // A Device outlived its DeviceType! + jassert (listeners.isEmpty()); + } + void scanForDevices() override {} StringArray getDeviceNames (bool isInput) const override @@ -1687,15 +1768,37 @@ private: AudioIODevice* createDevice (const String& outputName, const String& inputName) override { if (inNames.contains (inputName) || outNames.contains (outputName)) - return new MockDevice (getTypeName(), outputName, inputName); + return new MockDevice (listeners, getTypeName(), outputName, inputName); return nullptr; } + // Call this to emulate the device restarting itself with new settings. + // This might happen e.g. when a user changes the ASIO settings. + void restartDevices (double newSr, int newBs) + { + listeners.call ([&] (auto& l) { return l.restart (newSr, newBs); }); + } + private: const StringArray& getNames (bool isInput) const { return isInput ? inNames : outNames; } const StringArray inNames, outNames; + ListenerList listeners; + }; + + class MockCallback : public AudioIODeviceCallback + { + public: + std::function callback; + std::function aboutToStart; + std::function stopped; + std::function error; + + void audioDeviceIOCallback (const float**, int, float**, int, int) override { NullCheckedInvocation::invoke (callback); } + void audioDeviceAboutToStart (AudioIODevice*) override { NullCheckedInvocation::invoke (aboutToStart); } + void audioDeviceStopped() override { NullCheckedInvocation::invoke (stopped); } + void audioDeviceError (const String&) override { NullCheckedInvocation::invoke (error); } }; void initialiseManager (AudioDeviceManager& manager)