From 0fae9341c8762a4f2b3003fc4fc94ee42402f84c Mon Sep 17 00:00:00 2001 From: hogliux Date: Tue, 6 Feb 2018 15:41:39 +0000 Subject: [PATCH] Android: Ensured that JUCE will always use the high-performance audio path if the device supports it and that increasing the buffer size will more effectively reduce glitchess --- .../audio_io/juce_AudioIODevice.h | 8 +- .../native/juce_android_OpenSL.cpp | 104 ++++++++++++++---- 2 files changed, 86 insertions(+), 26 deletions(-) diff --git a/modules/juce_audio_devices/audio_io/juce_AudioIODevice.h b/modules/juce_audio_devices/audio_io/juce_AudioIODevice.h index a1599f4d2b..33c3cfdb32 100644 --- a/modules/juce_audio_devices/audio_io/juce_AudioIODevice.h +++ b/modules/juce_audio_devices/audio_io/juce_AudioIODevice.h @@ -75,9 +75,11 @@ public: @param numSamples the number of samples in each channel of the input and output arrays. The number of samples will depend on the audio device's buffer size and will usually remain constant, - although this isn't guaranteed, so make sure your code can - cope with reasonable changes in the buffer size from one - callback to the next. + although this isn't guaranteed. For example, on Android, + on devices which support it, Android will chop up your audio + processing into several smaller callbacks to ensure higher audio + performance. So make sure your code can cope with reasonable + changes in the buffer size from one callback to the next. */ virtual void audioDeviceIOCallback (const float** inputChannelData, int numInputChannels, diff --git a/modules/juce_audio_devices/native/juce_android_OpenSL.cpp b/modules/juce_audio_devices/native/juce_android_OpenSL.cpp index 93187872d1..23441cf8ea 100644 --- a/modules/juce_audio_devices/native/juce_android_OpenSL.cpp +++ b/modules/juce_audio_devices/native/juce_android_OpenSL.cpp @@ -764,7 +764,7 @@ public: //============================================================================== OpenSLAudioIODevice (const String& deviceName) : AudioIODevice (deviceName, openSLTypeName), - actualBufferSize (0), sampleRate (0), + actualBufferSize (0), sampleRate (0), audioBuffersToEnqueue (0), audioProcessingEnabled (true), callback (nullptr) { @@ -831,11 +831,12 @@ public: { // we need to offer the lowest possible buffer size which // is the native buffer size - const int defaultNumMultiples = 8; - const int nativeBufferSize = getNativeBufferSize(); + auto nativeBufferSize = getNativeBufferSize(); + auto minBuffersToQueue = getMinimumBuffersToEnqueue(); + auto maxBuffersToQueue = getMaximumBuffersToEnqueue(); Array retval; - for (int i = 1; i < defaultNumMultiples; ++i) + for (int i = minBuffersToQueue; i <= maxBuffersToQueue; ++i) retval.add (i * nativeBufferSize); return retval; @@ -851,7 +852,13 @@ public: lastError.clear(); sampleRate = (int) requestedSampleRate; - int preferredBufferSize = (bufferSize <= 0) ? getDefaultBufferSize() : bufferSize; + auto totalPreferredBufferSize = (bufferSize <= 0) ? getDefaultBufferSize() : bufferSize; + auto nativeBufferSize = getNativeBufferSize(); + bool useHighPerformanceAudioPath = canUseHighPerformanceAudioPath (totalPreferredBufferSize, sampleRate); + + audioBuffersToEnqueue = useHighPerformanceAudioPath ? (totalPreferredBufferSize / nativeBufferSize) : 1; + actualBufferSize = totalPreferredBufferSize / audioBuffersToEnqueue; + jassert ((actualBufferSize * audioBuffersToEnqueue) == totalPreferredBufferSize); activeOutputChans = outputChannels; activeOutputChans.setRange (2, activeOutputChans.getHighestBit(), false); @@ -861,11 +868,6 @@ public: activeInputChans.setRange (1, activeInputChans.getHighestBit(), false); int numInputChannels = activeInputChans.countNumberOfSetBits(); - actualBufferSize = preferredBufferSize; - - const int audioBuffersToEnqueue = hasLowLatencyAudioPath() ? buffersToEnqueueForLowLatency - : buffersToEnqueueSlowAudio; - if (numInputChannels > 0 && (! RuntimePermissions::isGranted (RuntimePermissions::recordAudio))) { // If you hit this assert, you probably forgot to get RuntimePermissions::recordAudio @@ -917,7 +919,7 @@ public: int getOutputLatencyInSamples() override { return outputLatency; } int getInputLatencyInSamples() override { return inputLatency; } bool isOpen() override { return deviceOpen; } - int getCurrentBufferSizeSamples() override { return actualBufferSize; } + int getCurrentBufferSizeSamples() override { return actualBufferSize * audioBuffersToEnqueue; } int getCurrentBitDepth() override { return (session != nullptr && session->supportsFloatingPoint() ? 32 : 16); } BigInteger getActiveOutputChannels() const override { return activeOutputChans; } BigInteger getActiveInputChannels() const override { return activeInputChans; } @@ -927,11 +929,11 @@ public: int getDefaultBufferSize() override { - // Only on a Pro-Audio device will we set the lowest possible buffer size - // by default. We need to be more conservative on other devices - // as they may be low-latency, but still have a crappy CPU. - return (isProAudioDevice() ? 1 : 6) - * defaultBufferSizeIsMultipleOfNative * getNativeBufferSize(); + auto defaultBufferLength = (hasLowLatencyAudioPath() ? defaultBufferSizeForLowLatencyDeviceMs + : defaultBufferSizeForStandardLatencyDeviceMs); + + auto defaultBuffersToEnqueue = buffersToQueueForBufferDuration (defaultBufferLength, getCurrentSampleRate()); + return defaultBuffersToEnqueue * getNativeBufferSize(); } double getCurrentSampleRate() override @@ -999,7 +1001,7 @@ private: //============================================================================== DynamicLibrary slLibrary; - int actualBufferSize, sampleRate; + int actualBufferSize, sampleRate, audioBuffersToEnqueue; int inputLatency, outputLatency; bool deviceOpen, audioProcessingEnabled; String lastError; @@ -1010,13 +1012,48 @@ private: enum { - // The number of buffers to enqueue needs to be at least two for the audio to use the low-latency - // audio path (see "Performance" section in ndk/docs/Additional_library_docs/opensles/index.html) - buffersToEnqueueForLowLatency = 4, - buffersToEnqueueSlowAudio = 8, - defaultBufferSizeIsMultipleOfNative = 1 + defaultBufferSizeForLowLatencyDeviceMs = 40, + defaultBufferSizeForStandardLatencyDeviceMs = 100 }; + static int getMinimumBuffersToEnqueue (double sampleRateToCheck = getNativeSampleRate()) + { + if (canUseHighPerformanceAudioPath (getNativeBufferSize(), (int) sampleRateToCheck)) + { + // 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 + // for lower latency. Beginning with Android 4.3 (API level 18), a buffer count of one + // is sufficient for lower latency." + + auto sdkVersion = getEnv()->GetStaticIntField (AndroidBuildVersion, AndroidBuildVersion.SDK_INT); + return (sdkVersion >= 18 ? 1 : 2); + } + + // we will not use the low-latency path so we can use the absolute minimum number of buffers + // to queue + return 1; + } + + int getMaximumBuffersToEnqueue() noexcept + { + constexpr auto maxBufferSizeMs = 200; + + auto availableSampleRates = getAvailableSampleRates(); + auto maximumSampleRate = findMaximum(availableSampleRates.getRawDataPointer(), availableSampleRates.size()); + + // ensure we don't return something crazy small + return jmax (8, buffersToQueueForBufferDuration (maxBufferSizeMs, maximumSampleRate)); + } + + static int buffersToQueueForBufferDuration (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()))); + + return jmax (getMinimumBuffersToEnqueue (sampleRate), maxNumBuffers); + } + //============================================================================== static String audioManagerGetProperty (const String& property) { @@ -1048,7 +1085,7 @@ private: static bool isProAudioDevice() { - return androidHasSystemFeature ("android.hardware.audio.pro"); + return androidHasSystemFeature ("android.hardware.audio.pro") || isSapaSupported(); } static bool hasLowLatencyAudioPath() @@ -1056,6 +1093,27 @@ private: return androidHasSystemFeature ("android.hardware.audio.low_latency"); } + static bool canUseHighPerformanceAudioPath (int requestedBufferSize, int requestedSampleRate) + { + return ((requestedBufferSize % getNativeBufferSize()) == 0) + && (requestedSampleRate == getNativeSampleRate()) + && isProAudioDevice(); + } + + //============================================================================== + // Some minimum Sapa support to check if this device supports pro audio + static bool isSamsungDevice() + { + return SystemStats::getDeviceManufacturer().containsIgnoreCase ("SAMSUNG"); + } + + static bool isSapaSupported() + { + static bool supported = isSamsungDevice() && DynamicLibrary().open ("libapa_jni.so"); + + return supported; + } + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (OpenSLAudioIODevice) };