1
0
Fork 0
mirror of https://github.com/juce-framework/JUCE.git synced 2026-02-02 03:20:06 +00:00

Android: Reworked default buffer size calculation logic to prefer stream's frames per burst over OUTPUT_FRAMES_PER_BUFFER property

This commit is contained in:
ed 2020-04-15 18:24:54 +01:00
parent 6a48f66cd4
commit 6725162cf8
3 changed files with 61 additions and 31 deletions

View file

@ -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<int> (std::ceil (bufferDurationInMs * sampleRate / 1000.0));
auto maxNumBuffers = static_cast<int> (std::ceil (static_cast<double> (maxBufferFrames)
/ static_cast<double> (getNativeBufferSize())));
/ static_cast<double> (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<int> getAvailableBufferSizes (Array<double> availableSampleRates)
static Array<int> getAvailableBufferSizes (int nativeBufferSize, Array<double> 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<int> 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;
}
}

View file

@ -194,7 +194,7 @@ public:
Array<int> 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;

View file

@ -866,7 +866,8 @@ public:
Array<int> 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<SLPlayItf_> player;
SlRef<SLAndroidSimpleBufferQueueItf_> queue;
int bufferSize = AndroidHighPerformanceAudioHelpers::getNativeBufferSize();
int bufferSize = AndroidHighPerformanceAudioHelpers::getNativeBufferSizeHint();
HeapBlock<int16> buffer { HeapBlock<int16> (static_cast<size_t> (1 * bufferSize * numBuffers)) };
void* (*threadEntryProc) (void*) = nullptr;