From 6725162cf83c616b3b663575b906905286b4c2ff Mon Sep 17 00:00:00 2001 From: ed Date: Wed, 15 Apr 2020 18:24:54 +0100 Subject: [PATCH] Android: Reworked default buffer size calculation logic to prefer stream's frames per burst over OUTPUT_FRAMES_PER_BUFFER property --- ...juce_android_HighPerformanceAudioHelpers.h | 37 +++++++++---------- .../native/juce_android_Oboe.cpp | 36 +++++++++++++++--- .../native/juce_android_OpenSL.cpp | 19 +++++++--- 3 files changed, 61 insertions(+), 31 deletions(-) diff --git a/modules/juce_audio_devices/native/juce_android_HighPerformanceAudioHelpers.h b/modules/juce_audio_devices/native/juce_android_HighPerformanceAudioHelpers.h index adc29ca6f4..15b421f9af 100644 --- a/modules/juce_audio_devices/native/juce_android_HighPerformanceAudioHelpers.h +++ b/modules/juce_audio_devices/native/juce_android_HighPerformanceAudioHelpers.h @@ -38,8 +38,9 @@ namespace AndroidHighPerformanceAudioHelpers return audioManagerGetProperty ("android.media.property.OUTPUT_SAMPLE_RATE").getDoubleValue(); } - static int getNativeBufferSize() + static int getNativeBufferSizeHint() { + // This property is a hint of a native buffer size but it does not guarantee the size used. auto deviceBufferSize = audioManagerGetProperty ("android.media.property.OUTPUT_FRAMES_PER_BUFFER").getIntValue(); if (deviceBufferSize == 0) @@ -61,17 +62,17 @@ namespace AndroidHighPerformanceAudioHelpers return androidHasSystemFeature ("android.hardware.audio.low_latency"); } - static bool canUseHighPerformanceAudioPath (int requestedBufferSize, int requestedSampleRate) + static bool canUseHighPerformanceAudioPath (int nativeBufferSize, int requestedBufferSize, int requestedSampleRate) { - return ((requestedBufferSize % getNativeBufferSize()) == 0) + return ((requestedBufferSize % nativeBufferSize) == 0) && (requestedSampleRate == getNativeSampleRate()) && isProAudioDevice(); } //============================================================================== - static int getMinimumBuffersToEnqueue (double requestedSampleRate) + static int getMinimumBuffersToEnqueue (int nativeBufferSize, double requestedSampleRate) { - if (canUseHighPerformanceAudioPath (getNativeBufferSize(), (int) requestedSampleRate)) + if (canUseHighPerformanceAudioPath (nativeBufferSize, nativeBufferSize, (int) requestedSampleRate)) { // see https://developer.android.com/ndk/guides/audio/opensl/opensl-prog-notes.html#sandp // "For Android 4.2 (API level 17) and earlier, a buffer count of two or more is required @@ -84,29 +85,27 @@ namespace AndroidHighPerformanceAudioHelpers return 1; } - static int buffersToQueueForBufferDuration (int bufferDurationInMs, double sampleRate) noexcept + static int buffersToQueueForBufferDuration (int nativeBufferSize, int bufferDurationInMs, double sampleRate) noexcept { auto maxBufferFrames = static_cast (std::ceil (bufferDurationInMs * sampleRate / 1000.0)); auto maxNumBuffers = static_cast (std::ceil (static_cast (maxBufferFrames) - / static_cast (getNativeBufferSize()))); + / static_cast (nativeBufferSize))); - return jmax (getMinimumBuffersToEnqueue (sampleRate), maxNumBuffers); + return jmax (getMinimumBuffersToEnqueue (nativeBufferSize, sampleRate), maxNumBuffers); } - static int getMaximumBuffersToEnqueue (double maximumSampleRate) noexcept + static int getMaximumBuffersToEnqueue (int nativeBufferSize, double maximumSampleRate) noexcept { static constexpr int maxBufferSizeMs = 200; - return jmax (8, buffersToQueueForBufferDuration (maxBufferSizeMs, maximumSampleRate)); + return jmax (8, buffersToQueueForBufferDuration (nativeBufferSize, maxBufferSizeMs, maximumSampleRate)); } - static Array getAvailableBufferSizes (Array availableSampleRates) + static Array getAvailableBufferSizes (int nativeBufferSize, Array availableSampleRates) { - auto nativeBufferSize = getNativeBufferSize(); - - auto minBuffersToQueue = getMinimumBuffersToEnqueue (getNativeSampleRate()); - auto maxBuffersToQueue = getMaximumBuffersToEnqueue (findMaximum (availableSampleRates.getRawDataPointer(), - availableSampleRates.size())); + auto minBuffersToQueue = getMinimumBuffersToEnqueue (nativeBufferSize, getNativeSampleRate()); + auto maxBuffersToQueue = getMaximumBuffersToEnqueue (nativeBufferSize, findMaximum (availableSampleRates.getRawDataPointer(), + availableSampleRates.size())); Array bufferSizes; @@ -116,7 +115,7 @@ namespace AndroidHighPerformanceAudioHelpers return bufferSizes; } - static int getDefaultBufferSize (double currentSampleRate) + static int getDefaultBufferSize (int nativeBufferSize, double currentSampleRate) { static constexpr int defaultBufferSizeForLowLatencyDeviceMs = 40; static constexpr int defaultBufferSizeForStandardLatencyDeviceMs = 100; @@ -124,8 +123,8 @@ namespace AndroidHighPerformanceAudioHelpers auto defaultBufferLength = (hasLowLatencyAudioPath() ? defaultBufferSizeForLowLatencyDeviceMs : defaultBufferSizeForStandardLatencyDeviceMs); - auto defaultBuffersToEnqueue = buffersToQueueForBufferDuration (defaultBufferLength, currentSampleRate); - return defaultBuffersToEnqueue * getNativeBufferSize(); + auto defaultBuffersToEnqueue = buffersToQueueForBufferDuration (nativeBufferSize, defaultBufferLength, currentSampleRate); + return defaultBuffersToEnqueue * nativeBufferSize; } } diff --git a/modules/juce_audio_devices/native/juce_android_Oboe.cpp b/modules/juce_audio_devices/native/juce_android_Oboe.cpp index 3b24acba33..12905d1e67 100644 --- a/modules/juce_audio_devices/native/juce_android_Oboe.cpp +++ b/modules/juce_audio_devices/native/juce_android_Oboe.cpp @@ -194,7 +194,7 @@ public: Array getAvailableBufferSizes() override { - return AndroidHighPerformanceAudioHelpers::getAvailableBufferSizes (getAvailableSampleRates()); + return AndroidHighPerformanceAudioHelpers::getAvailableBufferSizes (getNativeBufferSize(), getAvailableSampleRates()); } String open (const BigInteger& inputChannels, const BigInteger& outputChannels, @@ -262,7 +262,7 @@ public: int getDefaultBufferSize() override { - return AndroidHighPerformanceAudioHelpers::getDefaultBufferSize (getCurrentSampleRate()); + return AndroidHighPerformanceAudioHelpers::getDefaultBufferSize (getNativeBufferSize(), getCurrentSampleRate()); } double getCurrentSampleRate() override @@ -371,6 +371,30 @@ private: return rates; } + static int getNativeBufferSize() + { + auto bufferSizeHint = AndroidHighPerformanceAudioHelpers::getNativeBufferSizeHint(); + + // NB: Exclusive mode could be rejected if a device is already opened in that mode, so to get + // reliable results, only use this function when a device is closed. + // We initially try to open a stream with a buffer size returned from + // android.media.property.OUTPUT_FRAMES_PER_BUFFER property, but then we verify the actual + // size after the stream is open. + OboeAudioIODevice::OboeStream tempStream (-1, + oboe::Direction::Output, + oboe::SharingMode::Exclusive, + 2, + getAndroidSDKVersion() >= 21 ? oboe::AudioFormat::Float : oboe::AudioFormat::I16, + (int) AndroidHighPerformanceAudioHelpers::getNativeSampleRate(), + bufferSizeHint, + nullptr); + + if (auto* nativeStream = tempStream.getNativeStream()) + return nativeStream->getFramesPerBurst(); + + return bufferSizeHint; + } + void setCallback (AudioIODeviceCallback* callbackToUse) { if (! running) @@ -499,7 +523,7 @@ private: int32 newSampleRate, int32 newBufferSize, oboe::AudioStreamCallback* newCallback = nullptr) { - oboe::DefaultStreamValues::FramesPerBurst = AndroidHighPerformanceAudioHelpers::getNativeBufferSize(); + oboe::DefaultStreamValues::FramesPerBurst = AndroidHighPerformanceAudioHelpers::getNativeBufferSizeHint(); oboe::AudioStreamBuilder builder; @@ -1063,7 +1087,7 @@ public: forInput ? 1 : 2, getAndroidSDKVersion() >= 21 ? oboe::AudioFormat::Float : oboe::AudioFormat::I16, (int) AndroidHighPerformanceAudioHelpers::getNativeSampleRate(), - AndroidHighPerformanceAudioHelpers::getNativeBufferSize(), + AndroidHighPerformanceAudioHelpers::getNativeBufferSizeHint(), nullptr); if (auto* nativeStream = tempStream.getNativeStream()) @@ -1329,7 +1353,7 @@ public: 1, oboe::AudioFormat::Float, (int) AndroidHighPerformanceAudioHelpers::getNativeSampleRate(), - AndroidHighPerformanceAudioHelpers::getNativeBufferSize(), + OboeAudioIODevice::getNativeBufferSize(), this)), formatUsed (oboe::AudioFormat::Float) { @@ -1342,7 +1366,7 @@ public: 1, oboe::AudioFormat::I16, (int) AndroidHighPerformanceAudioHelpers::getNativeSampleRate(), - AndroidHighPerformanceAudioHelpers::getNativeBufferSize(), + OboeAudioIODevice::getNativeBufferSize(), this)); formatUsed = oboe::AudioFormat::I16; diff --git a/modules/juce_audio_devices/native/juce_android_OpenSL.cpp b/modules/juce_audio_devices/native/juce_android_OpenSL.cpp index ce2599bc63..9703230876 100644 --- a/modules/juce_audio_devices/native/juce_android_OpenSL.cpp +++ b/modules/juce_audio_devices/native/juce_android_OpenSL.cpp @@ -866,7 +866,8 @@ public: Array getAvailableBufferSizes() override { - return AndroidHighPerformanceAudioHelpers::getAvailableBufferSizes (getAvailableSampleRates()); + return AndroidHighPerformanceAudioHelpers::getAvailableBufferSizes (AndroidHighPerformanceAudioHelpers::getNativeBufferSizeHint(), + getAvailableSampleRates()); } String open (const BigInteger& inputChannels, @@ -883,8 +884,13 @@ public: audioBuffersToEnqueue = [this, preferredBufferSize] { - if (AndroidHighPerformanceAudioHelpers::canUseHighPerformanceAudioPath (preferredBufferSize, sampleRate)) - return preferredBufferSize / AndroidHighPerformanceAudioHelpers::getNativeBufferSize(); + using namespace AndroidHighPerformanceAudioHelpers; + + auto nativeBufferSize = getNativeBufferSizeHint(); + + if (canUseHighPerformanceAudioPath (nativeBufferSize, preferredBufferSize, sampleRate)) + return preferredBufferSize / nativeBufferSize; + return 1; }(); @@ -930,7 +936,7 @@ public: DBG ("OpenSL: numInputChannels = " << numInputChannels << ", numOutputChannels = " << numOutputChannels - << ", nativeBufferSize = " << AndroidHighPerformanceAudioHelpers::getNativeBufferSize() + << ", nativeBufferSize = " << AndroidHighPerformanceAudioHelpers::getNativeBufferSizeHint() << ", nativeSampleRate = " << AndroidHighPerformanceAudioHelpers::getNativeSampleRate() << ", actualBufferSize = " << actualBufferSize << ", audioBuffersToEnqueue = " << audioBuffersToEnqueue @@ -964,7 +970,8 @@ public: int getDefaultBufferSize() override { - return AndroidHighPerformanceAudioHelpers::getDefaultBufferSize (getCurrentSampleRate()); + return AndroidHighPerformanceAudioHelpers::getDefaultBufferSize (AndroidHighPerformanceAudioHelpers::getNativeBufferSizeHint(), + getCurrentSampleRate()); } double getCurrentSampleRate() override @@ -1272,7 +1279,7 @@ private: SlRef player; SlRef queue; - int bufferSize = AndroidHighPerformanceAudioHelpers::getNativeBufferSize(); + int bufferSize = AndroidHighPerformanceAudioHelpers::getNativeBufferSizeHint(); HeapBlock buffer { HeapBlock (static_cast (1 * bufferSize * numBuffers)) }; void* (*threadEntryProc) (void*) = nullptr;