diff --git a/modules/juce_audio_devices/audio_io/juce_AudioDeviceManager.cpp b/modules/juce_audio_devices/audio_io/juce_AudioDeviceManager.cpp index c3c7fa3600..429ba6fe66 100644 --- a/modules/juce_audio_devices/audio_io/juce_AudioDeviceManager.cpp +++ b/modules/juce_audio_devices/audio_io/juce_AudioDeviceManager.cpp @@ -73,12 +73,101 @@ bool AudioDeviceManager::AudioDeviceSetup::operator!= (const AudioDeviceManager: } //============================================================================== -class AudioDeviceManager::CallbackHandler final : public AudioIODeviceCallback, - public MidiInputCallback, - public AudioIODeviceType::Listener +/* This class is used to ensure that audio callbacks use buffers with a + predictable maximum size. + + On some platforms (such as iOS 10), the expected buffer size reported in + audioDeviceAboutToStart may be smaller than the blocks passed to + audioDeviceIOCallbackWithContext. This can lead to out-of-bounds reads if the render + callback depends on additional buffers which were initialised using the + smaller size. + + As a workaround, this class will ensure that the render callback will + only ever be called with a block with a length less than or equal to the + expected block size. +*/ +class CallbackMaxSizeEnforcer : public AudioIODeviceCallback { public: - CallbackHandler (AudioDeviceManager& adm) noexcept : owner (adm) {} + explicit CallbackMaxSizeEnforcer (AudioIODeviceCallback& callbackIn) + : inner (callbackIn) {} + + void audioDeviceAboutToStart (AudioIODevice* device) override + { + maximumSize = device->getCurrentBufferSizeSamples(); + storedInputChannels .resize ((size_t) device->getActiveInputChannels() .countNumberOfSetBits()); + storedOutputChannels.resize ((size_t) device->getActiveOutputChannels().countNumberOfSetBits()); + + inner.audioDeviceAboutToStart (device); + } + + void audioDeviceIOCallbackWithContext (const float* const* inputChannelData, + [[maybe_unused]] int numInputChannels, + float* const* outputChannelData, + [[maybe_unused]] int numOutputChannels, + int numSamples, + const AudioIODeviceCallbackContext& context) override + { + jassert ((int) storedInputChannels.size() == numInputChannels); + jassert ((int) storedOutputChannels.size() == numOutputChannels); + + int position = 0; + + while (position < numSamples) + { + const auto blockLength = jmin (maximumSize, numSamples - position); + + initChannelPointers (inputChannelData, storedInputChannels, position); + initChannelPointers (outputChannelData, storedOutputChannels, position); + + inner.audioDeviceIOCallbackWithContext (storedInputChannels.data(), + (int) storedInputChannels.size(), + storedOutputChannels.data(), + (int) storedOutputChannels.size(), + blockLength, + context); + + position += blockLength; + } + } + + void audioDeviceStopped() override + { + inner.audioDeviceStopped(); + } + +private: + struct GetChannelWithOffset + { + int offset; + + template + auto operator() (Ptr ptr) const noexcept -> Ptr { return ptr + offset; } + }; + + template + void initChannelPointers (Ptr&& source, Vector&& target, int offset) + { + std::transform (source, source + target.size(), target.begin(), GetChannelWithOffset { offset }); + } + + AudioIODeviceCallback& inner; + int maximumSize = 0; + std::vector storedInputChannels; + std::vector storedOutputChannels; +}; + +//============================================================================== +class AudioDeviceManager::CallbackHandler final : private MidiInputCallback, + private AudioIODeviceType::Listener, + private AudioIODeviceCallback +{ +public: + explicit CallbackHandler (AudioDeviceManager& adm) noexcept : owner (adm) {} + + MidiInputCallback* getMidiInputCallback() { return this; } + AudioIODeviceType::Listener* getAudioIODeviceTypeListener() { return this; } + AudioIODeviceCallback* getAudioIODeviceCallback() { return &enforcer; } private: void audioDeviceIOCallbackWithContext (const float* const* ins, @@ -117,6 +206,7 @@ private: } AudioDeviceManager& owner; + CallbackMaxSizeEnforcer enforcer { *this }; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (CallbackHandler) }; @@ -271,7 +361,7 @@ void AudioDeviceManager::addAudioDeviceType (std::unique_ptr availableDeviceTypes.add (newDeviceType.release()); lastDeviceTypeConfigs.add (new AudioDeviceSetup()); - availableDeviceTypes.getLast()->addListener (callbackHandler.get()); + availableDeviceTypes.getLast()->addListener (callbackHandler->getAudioIODeviceTypeListener()); } } @@ -285,7 +375,7 @@ void AudioDeviceManager::removeAudioDeviceType (AudioIODeviceType* deviceTypeToR if (auto removed = std::unique_ptr (availableDeviceTypes.removeAndReturn (index))) { - removed->removeListener (callbackHandler.get()); + removed->removeListener (callbackHandler->getAudioIODeviceTypeListener()); lastDeviceTypeConfigs.remove (index, true); } } @@ -818,7 +908,7 @@ String AudioDeviceManager::setAudioDeviceSetup (const AudioDeviceSetup& newSetup { currentDeviceType = currentAudioDevice->getTypeName(); - currentAudioDevice->start (callbackHandler.get()); + currentAudioDevice->start (callbackHandler->getAudioIODeviceCallback()); error = currentAudioDevice->getLastError(); } @@ -1122,7 +1212,7 @@ void AudioDeviceManager::setMidiInputDeviceEnabled (const String& identifier, bo { if (enabled) { - if (auto midiIn = MidiInput::openDevice (identifier, callbackHandler.get())) + if (auto midiIn = MidiInput::openDevice (identifier, callbackHandler->getMidiInputCallback())) { enabledMidiInputs.push_back (std::move (midiIn)); enabledMidiInputs.back()->start(); diff --git a/modules/juce_audio_plugin_client/Standalone/juce_StandaloneFilterWindow.h b/modules/juce_audio_plugin_client/Standalone/juce_StandaloneFilterWindow.h index 328105bcc1..50149a2c5b 100644 --- a/modules/juce_audio_plugin_client/Standalone/juce_StandaloneFilterWindow.h +++ b/modules/juce_audio_plugin_client/Standalone/juce_StandaloneFilterWindow.h @@ -459,93 +459,6 @@ private: processor = nullptr; } - //============================================================================== - /* This class can be used to ensure that audio callbacks use buffers with a - predictable maximum size. - - On some platforms (such as iOS 10), the expected buffer size reported in - audioDeviceAboutToStart may be smaller than the blocks passed to - audioDeviceIOCallbackWithContext. This can lead to out-of-bounds reads if the render - callback depends on additional buffers which were initialised using the - smaller size. - - As a workaround, this class will ensure that the render callback will - only ever be called with a block with a length less than or equal to the - expected block size. - */ - class CallbackMaxSizeEnforcer : public AudioIODeviceCallback - { - public: - explicit CallbackMaxSizeEnforcer (AudioIODeviceCallback& callbackIn) - : inner (callbackIn) {} - - void audioDeviceAboutToStart (AudioIODevice* device) override - { - maximumSize = device->getCurrentBufferSizeSamples(); - storedInputChannels .resize ((size_t) device->getActiveInputChannels() .countNumberOfSetBits()); - storedOutputChannels.resize ((size_t) device->getActiveOutputChannels().countNumberOfSetBits()); - - inner.audioDeviceAboutToStart (device); - } - - void audioDeviceIOCallbackWithContext (const float* const* inputChannelData, - [[maybe_unused]] int numInputChannels, - float* const* outputChannelData, - [[maybe_unused]] int numOutputChannels, - int numSamples, - const AudioIODeviceCallbackContext& context) override - { - jassert ((int) storedInputChannels.size() == numInputChannels); - jassert ((int) storedOutputChannels.size() == numOutputChannels); - - int position = 0; - - while (position < numSamples) - { - const auto blockLength = jmin (maximumSize, numSamples - position); - - initChannelPointers (inputChannelData, storedInputChannels, position); - initChannelPointers (outputChannelData, storedOutputChannels, position); - - inner.audioDeviceIOCallbackWithContext (storedInputChannels.data(), - (int) storedInputChannels.size(), - storedOutputChannels.data(), - (int) storedOutputChannels.size(), - blockLength, - context); - - position += blockLength; - } - } - - void audioDeviceStopped() override - { - inner.audioDeviceStopped(); - } - - private: - struct GetChannelWithOffset - { - int offset; - - template - auto operator() (Ptr ptr) const noexcept -> Ptr { return ptr + offset; } - }; - - template - void initChannelPointers (Ptr&& source, Vector&& target, int offset) - { - std::transform (source, source + target.size(), target.begin(), GetChannelWithOffset { offset }); - } - - AudioIODeviceCallback& inner; - int maximumSize = 0; - std::vector storedInputChannels; - std::vector storedOutputChannels; - }; - - CallbackMaxSizeEnforcer maxSizeEnforcer { *this }; - //============================================================================== class SettingsComponent : public Component { @@ -682,7 +595,7 @@ private: const String& preferredDefaultDeviceName, const AudioDeviceManager::AudioDeviceSetup* preferredSetupOptions) { - deviceManager.addAudioCallback (&maxSizeEnforcer); + deviceManager.addAudioCallback (this); deviceManager.addMidiInputDeviceCallback ({}, &player); reloadAudioDeviceState (enableAudioInput, preferredDefaultDeviceName, preferredSetupOptions); @@ -693,7 +606,7 @@ private: saveAudioDeviceState(); deviceManager.removeMidiInputDeviceCallback ({}, &player); - deviceManager.removeAudioCallback (&maxSizeEnforcer); + deviceManager.removeAudioCallback (this); } void timerCallback() override