aboutsummaryrefslogtreecommitdiff
path: root/externals/oboe/src/opensles/AudioStreamOpenSLES.cpp
diff options
context:
space:
mode:
authorswurl <swurl@swurl.xyz>2025-05-31 02:33:02 -0400
committerswurl <swurl@swurl.xyz>2025-05-31 02:33:02 -0400
commit6c655321e6b04b8648802e296bb5ca11d290ae68 (patch)
tree5c906511e4a2910ce8b806bb9d763b524c28619e /externals/oboe/src/opensles/AudioStreamOpenSLES.cpp
parentc0cceff365f754f8859f4c9a0362ebdba7ea388b (diff)
Move dead submodules in-tree
Signed-off-by: swurl <swurl@swurl.xyz>
Diffstat (limited to 'externals/oboe/src/opensles/AudioStreamOpenSLES.cpp')
-rw-r--r--externals/oboe/src/opensles/AudioStreamOpenSLES.cpp523
1 files changed, 523 insertions, 0 deletions
diff --git a/externals/oboe/src/opensles/AudioStreamOpenSLES.cpp b/externals/oboe/src/opensles/AudioStreamOpenSLES.cpp
new file mode 100644
index 0000000000..70f78b6647
--- /dev/null
+++ b/externals/oboe/src/opensles/AudioStreamOpenSLES.cpp
@@ -0,0 +1,523 @@
+/* Copyright 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include <sys/types.h>
+#include <cassert>
+#include <android/log.h>
+
+#include "common/OboeDebug.h"
+#include "oboe/AudioClock.h"
+#include "oboe/AudioStream.h"
+#include "oboe/AudioStreamBuilder.h"
+#include "EngineOpenSLES.h"
+#include "AudioStreamOpenSLES.h"
+#include "OpenSLESUtilities.h"
+
+using namespace oboe;
+
+AudioStreamOpenSLES::AudioStreamOpenSLES(const AudioStreamBuilder &builder)
+ : AudioStreamBuffered(builder) {
+ // OpenSL ES does not support device IDs. So overwrite value from builder.
+ mDeviceIds.clear();
+ // OpenSL ES does not support session IDs. So overwrite value from builder.
+ mSessionId = SessionId::None;
+}
+
+static constexpr int32_t kHighLatencyBufferSizeMillis = 20; // typical Android period
+static constexpr SLuint32 kAudioChannelCountMax = 30; // TODO Why 30?
+static constexpr SLuint32 SL_ANDROID_UNKNOWN_CHANNELMASK = 0; // Matches name used internally.
+
+SLuint32 AudioStreamOpenSLES::channelCountToChannelMaskDefault(int channelCount) const {
+ if (channelCount > kAudioChannelCountMax) {
+ return SL_ANDROID_UNKNOWN_CHANNELMASK;
+ }
+
+ SLuint32 bitfield = (1 << channelCount) - 1;
+
+ // Check for OS at run-time.
+ if(getSdkVersion() >= __ANDROID_API_N__) {
+ return SL_ANDROID_MAKE_INDEXED_CHANNEL_MASK(bitfield);
+ }
+
+ // Indexed channels masks were added in N.
+ // For before N, the best we can do is use a positional channel mask.
+ return bitfield;
+}
+
+static bool s_isLittleEndian() {
+ static uint32_t value = 1;
+ return (*reinterpret_cast<uint8_t *>(&value) == 1); // Does address point to LSB?
+}
+
+SLuint32 AudioStreamOpenSLES::getDefaultByteOrder() {
+ return s_isLittleEndian() ? SL_BYTEORDER_LITTLEENDIAN : SL_BYTEORDER_BIGENDIAN;
+}
+
+Result AudioStreamOpenSLES::open() {
+#ifndef OBOE_SUPPRESS_LOG_SPAM
+ LOGI("AudioStreamOpenSLES::open() chans=%d, rate=%d", mChannelCount, mSampleRate);
+#endif
+
+ // OpenSL ES only supports I16 and Float
+ if (mFormat != AudioFormat::I16 && mFormat != AudioFormat::Float) {
+ LOGW("%s() Android's OpenSL ES implementation only supports I16 and Float. Format: %s",
+ __func__, oboe::convertToText(mFormat));
+ return Result::ErrorInvalidFormat;
+ }
+
+ SLresult result = EngineOpenSLES::getInstance().open();
+ if (SL_RESULT_SUCCESS != result) {
+ return Result::ErrorInternal;
+ }
+
+ Result oboeResult = AudioStreamBuffered::open();
+ if (oboeResult != Result::OK) {
+ EngineOpenSLES::getInstance().close();
+ return oboeResult;
+ }
+ // Convert to defaults if UNSPECIFIED
+ if (mSampleRate == kUnspecified) {
+ mSampleRate = DefaultStreamValues::SampleRate;
+ }
+ if (mChannelCount == kUnspecified) {
+ mChannelCount = DefaultStreamValues::ChannelCount;
+ }
+ if (mContentType == kUnspecified) {
+ mContentType = ContentType::Music;
+ }
+ if (static_cast<const int32_t>(mUsage) == kUnspecified) {
+ mUsage = Usage::Media;
+ }
+
+ mSharingMode = SharingMode::Shared;
+
+ return Result::OK;
+}
+
+
+SLresult AudioStreamOpenSLES::finishCommonOpen(SLAndroidConfigurationItf configItf) {
+ // Setting privacy sensitive mode and allowed capture policy are not supported for OpenSL ES.
+ mPrivacySensitiveMode = PrivacySensitiveMode::Unspecified;
+ mAllowedCapturePolicy = AllowedCapturePolicy::Unspecified;
+
+ // Spatialization Behavior is not supported for OpenSL ES.
+ mSpatializationBehavior = SpatializationBehavior::Never;
+
+ SLresult result = registerBufferQueueCallback();
+ if (SL_RESULT_SUCCESS != result) {
+ return result;
+ }
+
+ result = updateStreamParameters(configItf);
+ if (SL_RESULT_SUCCESS != result) {
+ return result;
+ }
+
+ Result oboeResult = configureBufferSizes(mSampleRate);
+ if (Result::OK != oboeResult) {
+ return (SLresult) oboeResult;
+ }
+
+ allocateFifo();
+
+ calculateDefaultDelayBeforeCloseMillis();
+
+ return SL_RESULT_SUCCESS;
+}
+
+static int32_t roundUpDivideByN(int32_t x, int32_t n) {
+ return (x + n - 1) / n;
+}
+
+int32_t AudioStreamOpenSLES::calculateOptimalBufferQueueLength() {
+ int32_t queueLength = kBufferQueueLengthDefault;
+ int32_t likelyFramesPerBurst = estimateNativeFramesPerBurst();
+ int32_t minCapacity = mBufferCapacityInFrames; // specified by app or zero
+ // The buffer capacity needs to be at least twice the size of the requested callbackSize
+ // so that we can have double buffering.
+ minCapacity = std::max(minCapacity, kDoubleBufferCount * mFramesPerCallback);
+ if (minCapacity > 0) {
+ int32_t queueLengthFromCapacity = roundUpDivideByN(minCapacity, likelyFramesPerBurst);
+ queueLength = std::max(queueLength, queueLengthFromCapacity);
+ }
+ queueLength = std::min(queueLength, kBufferQueueLengthMax); // clip to max
+ // TODO Investigate the effect of queueLength on latency for normal streams. (not low latency)
+ return queueLength;
+}
+
+/**
+ * The best information we have is if DefaultStreamValues::FramesPerBurst
+ * was set by the app based on AudioManager.PROPERTY_OUTPUT_FRAMES_PER_BUFFER.
+ * Without that we just have to guess.
+ * @return
+ */
+int32_t AudioStreamOpenSLES::estimateNativeFramesPerBurst() {
+ int32_t framesPerBurst = DefaultStreamValues::FramesPerBurst;
+ LOGD("AudioStreamOpenSLES:%s() DefaultStreamValues::FramesPerBurst = %d",
+ __func__, DefaultStreamValues::FramesPerBurst);
+ framesPerBurst = std::max(framesPerBurst, 16);
+ // Calculate the size of a fixed duration high latency buffer based on sample rate.
+ // Estimate sample based on default options in order of priority.
+ int32_t sampleRate = 48000;
+ sampleRate = (DefaultStreamValues::SampleRate > 0)
+ ? DefaultStreamValues::SampleRate : sampleRate;
+ sampleRate = (mSampleRate > 0) ? mSampleRate : sampleRate;
+ int32_t framesPerHighLatencyBuffer =
+ (kHighLatencyBufferSizeMillis * sampleRate) / kMillisPerSecond;
+ // For high latency streams, use a larger buffer size.
+ // Performance Mode support was added in N_MR1 (7.1)
+ if (getSdkVersion() >= __ANDROID_API_N_MR1__
+ && mPerformanceMode != PerformanceMode::LowLatency
+ && framesPerBurst < framesPerHighLatencyBuffer) {
+ // Find a multiple of framesPerBurst >= framesPerHighLatencyBuffer.
+ int32_t numBursts = roundUpDivideByN(framesPerHighLatencyBuffer, framesPerBurst);
+ framesPerBurst *= numBursts;
+ LOGD("AudioStreamOpenSLES:%s() NOT low latency, numBursts = %d, mSampleRate = %d, set framesPerBurst = %d",
+ __func__, numBursts, mSampleRate, framesPerBurst);
+ }
+ return framesPerBurst;
+}
+
+Result AudioStreamOpenSLES::configureBufferSizes(int32_t sampleRate) {
+ LOGD("AudioStreamOpenSLES:%s(%d) initial mFramesPerBurst = %d, mFramesPerCallback = %d",
+ __func__, mSampleRate, mFramesPerBurst, mFramesPerCallback);
+ mFramesPerBurst = estimateNativeFramesPerBurst();
+ mFramesPerCallback = (mFramesPerCallback > 0) ? mFramesPerCallback : mFramesPerBurst;
+ LOGD("AudioStreamOpenSLES:%s(%d) final mFramesPerBurst = %d, mFramesPerCallback = %d",
+ __func__, mSampleRate, mFramesPerBurst, mFramesPerCallback);
+
+ mBytesPerCallback = mFramesPerCallback * getBytesPerFrame();
+ if (mBytesPerCallback <= 0) {
+ LOGE("AudioStreamOpenSLES::open() bytesPerCallback < 0 = %d, bad format?",
+ mBytesPerCallback);
+ return Result::ErrorInvalidFormat; // causing bytesPerFrame == 0
+ }
+
+ for (int i = 0; i < mBufferQueueLength; ++i) {
+ mCallbackBuffer[i] = std::make_unique<uint8_t[]>(mBytesPerCallback);
+ }
+
+ if (!usingFIFO()) {
+ mBufferCapacityInFrames = mFramesPerBurst * mBufferQueueLength;
+ // Check for overflow.
+ if (mBufferCapacityInFrames <= 0) {
+ mBufferCapacityInFrames = 0;
+ LOGE("AudioStreamOpenSLES::open() numeric overflow because mFramesPerBurst = %d",
+ mFramesPerBurst);
+ return Result::ErrorOutOfRange;
+ }
+ mBufferSizeInFrames = mBufferCapacityInFrames;
+ }
+
+ return Result::OK;
+}
+
+SLuint32 AudioStreamOpenSLES::convertPerformanceMode(PerformanceMode oboeMode) const {
+ SLuint32 openslMode = SL_ANDROID_PERFORMANCE_NONE;
+ switch(oboeMode) {
+ case PerformanceMode::None:
+ openslMode = SL_ANDROID_PERFORMANCE_NONE;
+ break;
+ case PerformanceMode::LowLatency:
+ openslMode = (getSessionId() == SessionId::None) ? SL_ANDROID_PERFORMANCE_LATENCY : SL_ANDROID_PERFORMANCE_LATENCY_EFFECTS;
+ break;
+ case PerformanceMode::PowerSaving:
+ openslMode = SL_ANDROID_PERFORMANCE_POWER_SAVING;
+ break;
+ default:
+ break;
+ }
+ return openslMode;
+}
+
+PerformanceMode AudioStreamOpenSLES::convertPerformanceMode(SLuint32 openslMode) const {
+ PerformanceMode oboeMode = PerformanceMode::None;
+ switch(openslMode) {
+ case SL_ANDROID_PERFORMANCE_NONE:
+ oboeMode = PerformanceMode::None;
+ break;
+ case SL_ANDROID_PERFORMANCE_LATENCY:
+ case SL_ANDROID_PERFORMANCE_LATENCY_EFFECTS:
+ oboeMode = PerformanceMode::LowLatency;
+ break;
+ case SL_ANDROID_PERFORMANCE_POWER_SAVING:
+ oboeMode = PerformanceMode::PowerSaving;
+ break;
+ default:
+ break;
+ }
+ return oboeMode;
+}
+
+void AudioStreamOpenSLES::logUnsupportedAttributes() {
+ // Log unsupported attributes
+ // only report if changed from the default
+
+ // Device ID
+ if (!mDeviceIds.empty()) {
+ LOGW("Device ID [AudioStreamBuilder::setDeviceId()] "
+ "is not supported on OpenSLES streams.");
+ }
+ // Sharing Mode
+ if (mSharingMode != SharingMode::Shared) {
+ LOGW("SharingMode [AudioStreamBuilder::setSharingMode()] "
+ "is not supported on OpenSLES streams.");
+ }
+ // Performance Mode
+ int sdkVersion = getSdkVersion();
+ if (mPerformanceMode != PerformanceMode::None && sdkVersion < __ANDROID_API_N_MR1__) {
+ LOGW("PerformanceMode [AudioStreamBuilder::setPerformanceMode()] "
+ "is not supported on OpenSLES streams running on pre-Android N-MR1 versions.");
+ }
+ // Content Type
+ if (static_cast<const int32_t>(mContentType) != kUnspecified) {
+ LOGW("ContentType [AudioStreamBuilder::setContentType()] "
+ "is not supported on OpenSLES streams.");
+ }
+
+ // Session Id
+ if (mSessionId != SessionId::None) {
+ LOGW("SessionId [AudioStreamBuilder::setSessionId()] "
+ "is not supported on OpenSLES streams.");
+ }
+
+ // Privacy Sensitive Mode
+ if (mPrivacySensitiveMode != PrivacySensitiveMode::Unspecified) {
+ LOGW("PrivacySensitiveMode [AudioStreamBuilder::setPrivacySensitiveMode()] "
+ "is not supported on OpenSLES streams.");
+ }
+
+ // Spatialization Behavior
+ if (mSpatializationBehavior != SpatializationBehavior::Unspecified) {
+ LOGW("SpatializationBehavior [AudioStreamBuilder::setSpatializationBehavior()] "
+ "is not supported on OpenSLES streams.");
+ }
+
+ if (mIsContentSpatialized) {
+ LOGW("Boolean [AudioStreamBuilder::setIsContentSpatialized()] "
+ "is not supported on OpenSLES streams.");
+ }
+
+ // Allowed Capture Policy
+ if (mAllowedCapturePolicy != AllowedCapturePolicy::Unspecified) {
+ LOGW("AllowedCapturePolicy [AudioStreamBuilder::setAllowedCapturePolicy()] "
+ "is not supported on OpenSLES streams.");
+ }
+
+ // Package Name
+ if (!mPackageName.empty()) {
+ LOGW("PackageName [AudioStreamBuilder::setPackageName()] "
+ "is not supported on OpenSLES streams.");
+ }
+
+ // Attribution Tag
+ if (!mAttributionTag.empty()) {
+ LOGW("AttributionTag [AudioStreamBuilder::setAttributionTag()] "
+ "is not supported on OpenSLES streams.");
+ }
+}
+
+SLresult AudioStreamOpenSLES::configurePerformanceMode(SLAndroidConfigurationItf configItf) {
+
+ if (configItf == nullptr) {
+ LOGW("%s() called with NULL configuration", __func__);
+ mPerformanceMode = PerformanceMode::None;
+ return SL_RESULT_INTERNAL_ERROR;
+ }
+ if (getSdkVersion() < __ANDROID_API_N_MR1__) {
+ LOGW("%s() not supported until N_MR1", __func__);
+ mPerformanceMode = PerformanceMode::None;
+ return SL_RESULT_SUCCESS;
+ }
+
+ SLresult result = SL_RESULT_SUCCESS;
+ SLuint32 performanceMode = convertPerformanceMode(getPerformanceMode());
+ result = (*configItf)->SetConfiguration(configItf, SL_ANDROID_KEY_PERFORMANCE_MODE,
+ &performanceMode, sizeof(performanceMode));
+ if (SL_RESULT_SUCCESS != result) {
+ LOGW("SetConfiguration(PERFORMANCE_MODE, SL %u) returned %s",
+ performanceMode, getSLErrStr(result));
+ mPerformanceMode = PerformanceMode::None;
+ }
+
+ return result;
+}
+
+SLresult AudioStreamOpenSLES::updateStreamParameters(SLAndroidConfigurationItf configItf) {
+ SLresult result = SL_RESULT_SUCCESS;
+ if(getSdkVersion() >= __ANDROID_API_N_MR1__ && configItf != nullptr) {
+ SLuint32 performanceMode = 0;
+ SLuint32 performanceModeSize = sizeof(performanceMode);
+ result = (*configItf)->GetConfiguration(configItf, SL_ANDROID_KEY_PERFORMANCE_MODE,
+ &performanceModeSize, &performanceMode);
+ // A bug in GetConfiguration() before P caused a wrong result code to be returned.
+ if (getSdkVersion() <= __ANDROID_API_O_MR1__) {
+ result = SL_RESULT_SUCCESS; // Ignore actual result before P.
+ }
+
+ if (SL_RESULT_SUCCESS != result) {
+ LOGW("GetConfiguration(SL_ANDROID_KEY_PERFORMANCE_MODE) returned %d", result);
+ mPerformanceMode = PerformanceMode::None; // If we can't query it then assume None.
+ } else {
+ mPerformanceMode = convertPerformanceMode(performanceMode); // convert SL to Oboe mode
+ }
+ } else {
+ mPerformanceMode = PerformanceMode::None; // If we can't query it then assume None.
+ }
+ return result;
+}
+
+// This is called under mLock.
+Result AudioStreamOpenSLES::close_l() {
+ LOGD("AudioOutputStreamOpenSLES::%s() called", __func__);
+ if (mState == StreamState::Closed) {
+ return Result::ErrorClosed;
+ }
+
+ AudioStreamBuffered::close();
+
+ onBeforeDestroy();
+
+ // Mark as CLOSED before we unlock for the join.
+ // This will prevent other threads from trying to close().
+ setState(StreamState::Closed);
+
+ SLObjectItf tempObjectInterface = mObjectInterface;
+ mObjectInterface = nullptr;
+ if (tempObjectInterface != nullptr) {
+ // Temporarily unlock so we can join() the callback thread.
+ mLock.unlock();
+ (*tempObjectInterface)->Destroy(tempObjectInterface); // Will join the callback!
+ mLock.lock();
+ }
+
+ onAfterDestroy();
+
+ mSimpleBufferQueueInterface = nullptr;
+ EngineOpenSLES::getInstance().close();
+
+ return Result::OK;
+}
+
+SLresult AudioStreamOpenSLES::enqueueCallbackBuffer(SLAndroidSimpleBufferQueueItf bq) {
+ SLresult result = (*bq)->Enqueue(
+ bq, mCallbackBuffer[mCallbackBufferIndex].get(), mBytesPerCallback);
+ mCallbackBufferIndex = (mCallbackBufferIndex + 1) % mBufferQueueLength;
+ return result;
+}
+
+int32_t AudioStreamOpenSLES::getBufferDepth(SLAndroidSimpleBufferQueueItf bq) {
+ SLAndroidSimpleBufferQueueState queueState;
+ SLresult result = (*bq)->GetState(bq, &queueState);
+ return (result == SL_RESULT_SUCCESS) ? queueState.count : -1;
+}
+
+bool AudioStreamOpenSLES::processBufferCallback(SLAndroidSimpleBufferQueueItf bq) {
+ bool shouldStopStream = false;
+ // Ask the app callback to process the buffer.
+ DataCallbackResult result =
+ fireDataCallback(mCallbackBuffer[mCallbackBufferIndex].get(), mFramesPerCallback);
+ if (result == DataCallbackResult::Continue) {
+ // Pass the buffer to OpenSLES.
+ SLresult enqueueResult = enqueueCallbackBuffer(bq);
+ if (enqueueResult != SL_RESULT_SUCCESS) {
+ LOGE("%s() returned %d", __func__, enqueueResult);
+ shouldStopStream = true;
+ }
+ // Update Oboe client position with frames handled by the callback.
+ if (getDirection() == Direction::Input) {
+ mFramesRead += mFramesPerCallback;
+ } else {
+ mFramesWritten += mFramesPerCallback;
+ }
+ } else if (result == DataCallbackResult::Stop) {
+ LOGD("Oboe callback returned Stop");
+ shouldStopStream = true;
+ } else {
+ LOGW("Oboe callback returned unexpected value = %d", static_cast<int>(result));
+ shouldStopStream = true;
+ }
+ if (shouldStopStream) {
+ mCallbackBufferIndex = 0;
+ }
+ return shouldStopStream;
+}
+
+// This callback handler is called every time a buffer has been processed by OpenSL ES.
+static void bqCallbackGlue(SLAndroidSimpleBufferQueueItf bq, void *context) {
+ bool shouldStopStream = (reinterpret_cast<AudioStreamOpenSLES *>(context))
+ ->processBufferCallback(bq);
+ if (shouldStopStream) {
+ (reinterpret_cast<AudioStreamOpenSLES *>(context))->requestStop();
+ }
+}
+
+SLresult AudioStreamOpenSLES::registerBufferQueueCallback() {
+ // The BufferQueue
+ SLresult result = (*mObjectInterface)->GetInterface(mObjectInterface,
+ EngineOpenSLES::getInstance().getIidAndroidSimpleBufferQueue(),
+ &mSimpleBufferQueueInterface);
+ if (SL_RESULT_SUCCESS != result) {
+ LOGE("get buffer queue interface:%p result:%s",
+ mSimpleBufferQueueInterface,
+ getSLErrStr(result));
+ } else {
+ // Register the BufferQueue callback
+ result = (*mSimpleBufferQueueInterface)->RegisterCallback(mSimpleBufferQueueInterface,
+ bqCallbackGlue, this);
+ if (SL_RESULT_SUCCESS != result) {
+ LOGE("RegisterCallback result:%s", getSLErrStr(result));
+ }
+ }
+ return result;
+}
+
+int64_t AudioStreamOpenSLES::getFramesProcessedByServer() {
+ updateServiceFrameCounter();
+ int64_t millis64 = mPositionMillis.get();
+ int64_t framesProcessed = millis64 * getSampleRate() / kMillisPerSecond;
+ return framesProcessed;
+}
+
+Result AudioStreamOpenSLES::waitForStateChange(StreamState currentState,
+ StreamState *nextState,
+ int64_t timeoutNanoseconds) {
+ Result oboeResult = Result::ErrorTimeout;
+ int64_t sleepTimeNanos = 20 * kNanosPerMillisecond; // arbitrary
+ int64_t timeLeftNanos = timeoutNanoseconds;
+
+ while (true) {
+ const StreamState state = getState(); // this does not require a lock
+ if (nextState != nullptr) {
+ *nextState = state;
+ }
+ if (currentState != state) { // state changed?
+ oboeResult = Result::OK;
+ break;
+ }
+
+ // Did we timeout or did user ask for non-blocking?
+ if (timeLeftNanos <= 0) {
+ break;
+ }
+
+ if (sleepTimeNanos > timeLeftNanos){
+ sleepTimeNanos = timeLeftNanos;
+ }
+ AudioClock::sleepForNanos(sleepTimeNanos);
+ timeLeftNanos -= sleepTimeNanos;
+ }
+
+ return oboeResult;
+}