aboutsummaryrefslogtreecommitdiff
path: root/externals/oboe/src/aaudio/AudioStreamAAudio.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/aaudio/AudioStreamAAudio.cpp
parentc0cceff365f754f8859f4c9a0362ebdba7ea388b (diff)
Move dead submodules in-tree
Signed-off-by: swurl <swurl@swurl.xyz>
Diffstat (limited to 'externals/oboe/src/aaudio/AudioStreamAAudio.cpp')
-rw-r--r--externals/oboe/src/aaudio/AudioStreamAAudio.cpp1022
1 files changed, 1022 insertions, 0 deletions
diff --git a/externals/oboe/src/aaudio/AudioStreamAAudio.cpp b/externals/oboe/src/aaudio/AudioStreamAAudio.cpp
new file mode 100644
index 0000000000..9f80047086
--- /dev/null
+++ b/externals/oboe/src/aaudio/AudioStreamAAudio.cpp
@@ -0,0 +1,1022 @@
+/*
+ * Copyright 2016 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 <cassert>
+#include <stdint.h>
+#include <stdlib.h>
+
+#include "aaudio/AAudioLoader.h"
+#include "aaudio/AudioStreamAAudio.h"
+#include "common/OboeDebug.h"
+#include "oboe/AudioClock.h"
+#include "oboe/Utilities.h"
+#include "AAudioExtensions.h"
+
+#ifdef __ANDROID__
+#include <sys/system_properties.h>
+#include <common/QuirksManager.h>
+
+#endif
+
+#ifndef OBOE_FIX_FORCE_STARTING_TO_STARTED
+// Workaround state problems in AAudio
+// TODO Which versions does this occur in? Verify fixed in Q.
+#define OBOE_FIX_FORCE_STARTING_TO_STARTED 1
+#endif // OBOE_FIX_FORCE_STARTING_TO_STARTED
+
+using namespace oboe;
+AAudioLoader *AudioStreamAAudio::mLibLoader = nullptr;
+
+// 'C' wrapper for the data callback method
+static aaudio_data_callback_result_t oboe_aaudio_data_callback_proc(
+ AAudioStream *stream,
+ void *userData,
+ void *audioData,
+ int32_t numFrames) {
+
+ AudioStreamAAudio *oboeStream = reinterpret_cast<AudioStreamAAudio*>(userData);
+ if (oboeStream != nullptr) {
+ return static_cast<aaudio_data_callback_result_t>(
+ oboeStream->callOnAudioReady(stream, audioData, numFrames));
+
+ } else {
+ return static_cast<aaudio_data_callback_result_t>(DataCallbackResult::Stop);
+ }
+}
+
+// This runs in its own thread.
+// Only one of these threads will be launched from internalErrorCallback().
+// It calls app error callbacks from a static function in case the stream gets deleted.
+static void oboe_aaudio_error_thread_proc_common(AudioStreamAAudio *oboeStream,
+ Result error) {
+#if 0
+ LOGE("%s() sleep for 5 seconds", __func__);
+ usleep(5*1000*1000);
+ LOGD("%s() - woke up -------------------------", __func__);
+#endif
+ AudioStreamErrorCallback *errorCallback = oboeStream->getErrorCallback();
+ if (errorCallback == nullptr) return; // should be impossible
+ bool isErrorHandled = errorCallback->onError(oboeStream, error);
+
+ if (!isErrorHandled) {
+ oboeStream->requestStop();
+ errorCallback->onErrorBeforeClose(oboeStream, error);
+ oboeStream->close();
+ // Warning, oboeStream may get deleted by this callback.
+ errorCallback->onErrorAfterClose(oboeStream, error);
+ }
+}
+
+// Callback thread for raw pointers.
+static void oboe_aaudio_error_thread_proc(AudioStreamAAudio *oboeStream,
+ Result error) {
+ LOGD("%s(,%d) - entering >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>", __func__, error);
+ oboe_aaudio_error_thread_proc_common(oboeStream, error);
+ LOGD("%s() - exiting <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<", __func__);
+}
+
+// Callback thread for shared pointers.
+static void oboe_aaudio_error_thread_proc_shared(std::shared_ptr<AudioStream> sharedStream,
+ Result error) {
+ LOGD("%s(,%d) - entering >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>", __func__, error);
+ // Hold the shared pointer while we use the raw pointer.
+ AudioStreamAAudio *oboeStream = reinterpret_cast<AudioStreamAAudio*>(sharedStream.get());
+ oboe_aaudio_error_thread_proc_common(oboeStream, error);
+ LOGD("%s() - exiting <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<", __func__);
+}
+
+static void oboe_aaudio_presentation_thread_proc_common(AudioStreamAAudio *oboeStream) {
+ auto presentationCallback = oboeStream->getPresentationCallback();
+ if (presentationCallback == nullptr) return; // should be impossible
+ presentationCallback->onPresentationEnded(oboeStream);
+}
+
+// Callback thread for raw pointers
+static void oboe_aaudio_presentation_thread_proc(AudioStreamAAudio *oboeStream) {
+ LOGD("%s() - entering >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>", __func__);
+ oboe_aaudio_presentation_thread_proc_common(oboeStream);
+ LOGD("%s() - exiting <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<", __func__);
+}
+
+// Callback thread for shared pointers
+static void oboe_aaudio_presentation_end_thread_proc_shared(
+ std::shared_ptr<AudioStream> sharedStream) {
+ LOGD("%s() - entering >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>", __func__);
+ AudioStreamAAudio *oboeStream = reinterpret_cast<AudioStreamAAudio*>(sharedStream.get());
+ oboe_aaudio_presentation_thread_proc_common(oboeStream);
+ LOGD("%s() - exiting <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<", __func__);
+}
+
+namespace oboe {
+
+/*
+ * Create a stream that uses Oboe Audio API.
+ */
+AudioStreamAAudio::AudioStreamAAudio(const AudioStreamBuilder &builder)
+ : AudioStream(builder)
+ , mAAudioStream(nullptr) {
+ mCallbackThreadEnabled.store(false);
+ mLibLoader = AAudioLoader::getInstance();
+}
+
+bool AudioStreamAAudio::isSupported() {
+ mLibLoader = AAudioLoader::getInstance();
+ int openResult = mLibLoader->open();
+ return openResult == 0;
+}
+
+// Static method for the error callback.
+// We use a method so we can access protected methods on the stream.
+// Launch a thread to handle the error.
+// That other thread can safely stop, close and delete the stream.
+void AudioStreamAAudio::internalErrorCallback(
+ AAudioStream *stream,
+ void *userData,
+ aaudio_result_t error) {
+ oboe::Result oboeResult = static_cast<Result>(error);
+ AudioStreamAAudio *oboeStream = reinterpret_cast<AudioStreamAAudio*>(userData);
+
+ // Coerce the error code if needed to workaround a regression in RQ1A that caused
+ // the wrong code to be passed when headsets plugged in. See b/173928197.
+ if (OboeGlobals::areWorkaroundsEnabled()
+ && getSdkVersion() == __ANDROID_API_R__
+ && oboeResult == oboe::Result::ErrorTimeout) {
+ oboeResult = oboe::Result::ErrorDisconnected;
+ LOGD("%s() ErrorTimeout changed to ErrorDisconnected to fix b/173928197", __func__);
+ }
+
+ oboeStream->mErrorCallbackResult = oboeResult;
+
+ // Prevents deletion of the stream if the app is using AudioStreamBuilder::openStream(shared_ptr)
+ std::shared_ptr<AudioStream> sharedStream = oboeStream->lockWeakThis();
+
+ // These checks should be enough because we assume that the stream close()
+ // will join() any active callback threads and will not allow new callbacks.
+ if (oboeStream->wasErrorCallbackCalled()) { // block extra error callbacks
+ LOGE("%s() multiple error callbacks called!", __func__);
+ } else if (stream != oboeStream->getUnderlyingStream()) {
+ LOGW("%s() stream already closed or closing", __func__); // might happen if there are bugs
+ } else if (sharedStream) {
+ // Handle error on a separate thread using shared pointer.
+ std::thread t(oboe_aaudio_error_thread_proc_shared, sharedStream, oboeResult);
+ t.detach();
+ } else {
+ // Handle error on a separate thread.
+ std::thread t(oboe_aaudio_error_thread_proc, oboeStream, oboeResult);
+ t.detach();
+ }
+}
+
+void AudioStreamAAudio::beginPerformanceHintInCallback() {
+ if (isPerformanceHintEnabled()) {
+ if (!mAdpfOpenAttempted) {
+ int64_t targetDurationNanos = (mFramesPerBurst * 1e9) / getSampleRate();
+ // This has to be called from the callback thread so we get the right TID.
+ int adpfResult = mAdpfWrapper.open(gettid(), targetDurationNanos);
+ if (adpfResult < 0) {
+ LOGW("WARNING ADPF not supported, %d\n", adpfResult);
+ } else {
+ LOGD("ADPF is now active\n");
+ }
+ mAdpfOpenAttempted = true;
+ }
+ mAdpfWrapper.onBeginCallback();
+ } else if (!isPerformanceHintEnabled() && mAdpfOpenAttempted) {
+ LOGD("ADPF closed\n");
+ mAdpfWrapper.close();
+ mAdpfOpenAttempted = false;
+ }
+}
+
+void AudioStreamAAudio::endPerformanceHintInCallback(int32_t numFrames) {
+ if (mAdpfWrapper.isOpen()) {
+ // Scale the measured duration based on numFrames so it is normalized to a full burst.
+ double durationScaler = static_cast<double>(mFramesPerBurst) / numFrames;
+ // Skip this callback if numFrames is very small.
+ // This can happen when buffers wrap around, particularly when doing sample rate conversion.
+ if (durationScaler < 2.0) {
+ mAdpfWrapper.onEndCallback(durationScaler);
+ }
+ }
+}
+
+void AudioStreamAAudio::logUnsupportedAttributes() {
+ int sdkVersion = getSdkVersion();
+
+ // These attributes are not supported pre Android "P"
+ if (sdkVersion < __ANDROID_API_P__) {
+ if (mUsage != Usage::Media) {
+ LOGW("Usage [AudioStreamBuilder::setUsage()] "
+ "is not supported on AAudio streams running on pre-Android P versions.");
+ }
+
+ if (mContentType != ContentType::Music) {
+ LOGW("ContentType [AudioStreamBuilder::setContentType()] "
+ "is not supported on AAudio streams running on pre-Android P versions.");
+ }
+
+ if (mSessionId != SessionId::None) {
+ LOGW("SessionId [AudioStreamBuilder::setSessionId()] "
+ "is not supported on AAudio streams running on pre-Android P versions.");
+ }
+ }
+}
+
+Result AudioStreamAAudio::open() {
+ Result result = Result::OK;
+
+ if (mAAudioStream != nullptr) {
+ return Result::ErrorInvalidState;
+ }
+
+ result = AudioStream::open();
+ if (result != Result::OK) {
+ return result;
+ }
+
+ AAudioStreamBuilder *aaudioBuilder;
+ result = static_cast<Result>(mLibLoader->createStreamBuilder(&aaudioBuilder));
+ if (result != Result::OK) {
+ return result;
+ }
+
+ // Do not set INPUT capacity below 4096 because that prevents us from getting a FAST track
+ // when using the Legacy data path.
+ // If the app requests > 4096 then we allow it but we are less likely to get LowLatency.
+ // See internal bug b/80308183 for more details.
+ // Fixed in Q but let's still clip the capacity because high input capacity
+ // does not increase latency.
+ int32_t capacity = mBufferCapacityInFrames;
+ constexpr int kCapacityRequiredForFastLegacyTrack = 4096; // matches value in AudioFinger
+ if (OboeGlobals::areWorkaroundsEnabled()
+ && mDirection == oboe::Direction::Input
+ && capacity != oboe::Unspecified
+ && capacity < kCapacityRequiredForFastLegacyTrack
+ && mPerformanceMode == oboe::PerformanceMode::LowLatency) {
+ capacity = kCapacityRequiredForFastLegacyTrack;
+ LOGD("AudioStreamAAudio.open() capacity changed from %d to %d for lower latency",
+ static_cast<int>(mBufferCapacityInFrames), capacity);
+ }
+ mLibLoader->builder_setBufferCapacityInFrames(aaudioBuilder, capacity);
+
+ if (mLibLoader->builder_setSessionId != nullptr) {
+ mLibLoader->builder_setSessionId(aaudioBuilder,
+ static_cast<aaudio_session_id_t>(mSessionId));
+ // Output effects do not support PerformanceMode::LowLatency.
+ if (OboeGlobals::areWorkaroundsEnabled()
+ && mSessionId != SessionId::None
+ && mDirection == oboe::Direction::Output
+ && mPerformanceMode == PerformanceMode::LowLatency) {
+ mPerformanceMode = PerformanceMode::None;
+ LOGD("AudioStreamAAudio.open() performance mode changed to None when session "
+ "id is requested");
+ }
+ }
+
+ // Channel mask was added in SC_V2. Given the corresponding channel count of selected channel
+ // mask may be different from selected channel count, the last set value will be respected.
+ // If channel count is set after channel mask, the previously set channel mask will be cleared.
+ // If channel mask is set after channel count, the channel count will be automatically
+ // calculated from selected channel mask. In that case, only set channel mask when the API
+ // is available and the channel mask is specified.
+ if (mLibLoader->builder_setChannelMask != nullptr && mChannelMask != ChannelMask::Unspecified) {
+ mLibLoader->builder_setChannelMask(aaudioBuilder,
+ static_cast<aaudio_channel_mask_t>(mChannelMask));
+ } else {
+ mLibLoader->builder_setChannelCount(aaudioBuilder, mChannelCount);
+ }
+ mLibLoader->builder_setDeviceId(aaudioBuilder, getDeviceId());
+ mLibLoader->builder_setDirection(aaudioBuilder, static_cast<aaudio_direction_t>(mDirection));
+ mLibLoader->builder_setFormat(aaudioBuilder, static_cast<aaudio_format_t>(mFormat));
+ mLibLoader->builder_setSampleRate(aaudioBuilder, mSampleRate);
+ mLibLoader->builder_setSharingMode(aaudioBuilder,
+ static_cast<aaudio_sharing_mode_t>(mSharingMode));
+ mLibLoader->builder_setPerformanceMode(aaudioBuilder,
+ static_cast<aaudio_performance_mode_t>(mPerformanceMode));
+
+ // These were added in P so we have to check for the function pointer.
+ if (mLibLoader->builder_setUsage != nullptr) {
+ mLibLoader->builder_setUsage(aaudioBuilder,
+ static_cast<aaudio_usage_t>(mUsage));
+ }
+
+ if (mLibLoader->builder_setContentType != nullptr) {
+ mLibLoader->builder_setContentType(aaudioBuilder,
+ static_cast<aaudio_content_type_t>(mContentType));
+ }
+
+ if (mLibLoader->builder_setInputPreset != nullptr) {
+ aaudio_input_preset_t inputPreset = mInputPreset;
+ if (getSdkVersion() <= __ANDROID_API_P__ && inputPreset == InputPreset::VoicePerformance) {
+ LOGD("InputPreset::VoicePerformance not supported before Q. Using VoiceRecognition.");
+ inputPreset = InputPreset::VoiceRecognition; // most similar preset
+ }
+ mLibLoader->builder_setInputPreset(aaudioBuilder,
+ static_cast<aaudio_input_preset_t>(inputPreset));
+ }
+
+ // These were added in S so we have to check for the function pointer.
+ if (mLibLoader->builder_setPackageName != nullptr && !mPackageName.empty()) {
+ mLibLoader->builder_setPackageName(aaudioBuilder,
+ mPackageName.c_str());
+ }
+
+ if (mLibLoader->builder_setAttributionTag != nullptr && !mAttributionTag.empty()) {
+ mLibLoader->builder_setAttributionTag(aaudioBuilder,
+ mAttributionTag.c_str());
+ }
+
+ // This was added in Q so we have to check for the function pointer.
+ if (mLibLoader->builder_setAllowedCapturePolicy != nullptr && mDirection == oboe::Direction::Output) {
+ mLibLoader->builder_setAllowedCapturePolicy(aaudioBuilder,
+ static_cast<aaudio_allowed_capture_policy_t>(mAllowedCapturePolicy));
+ }
+
+ if (mLibLoader->builder_setPrivacySensitive != nullptr && mDirection == oboe::Direction::Input
+ && mPrivacySensitiveMode != PrivacySensitiveMode::Unspecified) {
+ mLibLoader->builder_setPrivacySensitive(aaudioBuilder,
+ mPrivacySensitiveMode == PrivacySensitiveMode::Enabled);
+ }
+
+ if (mLibLoader->builder_setIsContentSpatialized != nullptr) {
+ mLibLoader->builder_setIsContentSpatialized(aaudioBuilder, mIsContentSpatialized);
+ }
+
+ if (mLibLoader->builder_setSpatializationBehavior != nullptr) {
+ // Override Unspecified as Never to reduce latency.
+ if (mSpatializationBehavior == SpatializationBehavior::Unspecified) {
+ mSpatializationBehavior = SpatializationBehavior::Never;
+ }
+ mLibLoader->builder_setSpatializationBehavior(aaudioBuilder,
+ static_cast<aaudio_spatialization_behavior_t>(mSpatializationBehavior));
+ } else {
+ mSpatializationBehavior = SpatializationBehavior::Never;
+ }
+
+ if (isDataCallbackSpecified()) {
+ mLibLoader->builder_setDataCallback(aaudioBuilder, oboe_aaudio_data_callback_proc, this);
+ mLibLoader->builder_setFramesPerDataCallback(aaudioBuilder, getFramesPerDataCallback());
+
+ if (!isErrorCallbackSpecified()) {
+ // The app did not specify a callback so we should specify
+ // our own so the stream gets closed and stopped.
+ mErrorCallback = &mDefaultErrorCallback;
+ }
+ mLibLoader->builder_setErrorCallback(aaudioBuilder, internalErrorCallback, this);
+ }
+ // Else if the data callback is not being used then the write method will return an error
+ // and the app can stop and close the stream.
+
+ if (isPresentationCallbackSpecified() &&
+ mLibLoader->builder_setPresentationEndCallback != nullptr) {
+ mLibLoader->builder_setPresentationEndCallback(aaudioBuilder,
+ internalPresentationEndCallback,
+ this);
+ }
+
+ // ============= OPEN THE STREAM ================
+ {
+ AAudioStream *stream = nullptr;
+ result = static_cast<Result>(mLibLoader->builder_openStream(aaudioBuilder, &stream));
+ mAAudioStream.store(stream);
+ }
+ if (result != Result::OK) {
+ // Warn developer because ErrorInternal is not very informative.
+ if (result == Result::ErrorInternal && mDirection == Direction::Input) {
+ LOGW("AudioStreamAAudio.open() may have failed due to lack of "
+ "audio recording permission.");
+ }
+ goto error2;
+ }
+
+ // Query and cache the stream properties
+ mChannelCount = mLibLoader->stream_getChannelCount(mAAudioStream);
+ mSampleRate = mLibLoader->stream_getSampleRate(mAAudioStream);
+ mFormat = static_cast<AudioFormat>(mLibLoader->stream_getFormat(mAAudioStream));
+ mSharingMode = static_cast<SharingMode>(mLibLoader->stream_getSharingMode(mAAudioStream));
+ mPerformanceMode = static_cast<PerformanceMode>(
+ mLibLoader->stream_getPerformanceMode(mAAudioStream));
+ mBufferCapacityInFrames = mLibLoader->stream_getBufferCapacity(mAAudioStream);
+ mBufferSizeInFrames = mLibLoader->stream_getBufferSize(mAAudioStream);
+ mFramesPerBurst = mLibLoader->stream_getFramesPerBurst(mAAudioStream);
+
+ // These were added in P so we have to check for the function pointer.
+ if (mLibLoader->stream_getUsage != nullptr) {
+ mUsage = static_cast<Usage>(mLibLoader->stream_getUsage(mAAudioStream));
+ }
+ if (mLibLoader->stream_getContentType != nullptr) {
+ mContentType = static_cast<ContentType>(mLibLoader->stream_getContentType(mAAudioStream));
+ }
+ if (mLibLoader->stream_getInputPreset != nullptr) {
+ mInputPreset = static_cast<InputPreset>(mLibLoader->stream_getInputPreset(mAAudioStream));
+ }
+ if (mLibLoader->stream_getSessionId != nullptr) {
+ mSessionId = static_cast<SessionId>(mLibLoader->stream_getSessionId(mAAudioStream));
+ } else {
+ mSessionId = SessionId::None;
+ }
+
+ // This was added in Q so we have to check for the function pointer.
+ if (mLibLoader->stream_getAllowedCapturePolicy != nullptr && mDirection == oboe::Direction::Output) {
+ mAllowedCapturePolicy = static_cast<AllowedCapturePolicy>(mLibLoader->stream_getAllowedCapturePolicy(mAAudioStream));
+ } else {
+ mAllowedCapturePolicy = AllowedCapturePolicy::Unspecified;
+ }
+
+ if (mLibLoader->stream_isPrivacySensitive != nullptr && mDirection == oboe::Direction::Input) {
+ bool isPrivacySensitive = mLibLoader->stream_isPrivacySensitive(mAAudioStream);
+ mPrivacySensitiveMode = isPrivacySensitive ? PrivacySensitiveMode::Enabled :
+ PrivacySensitiveMode::Disabled;
+ } else {
+ mPrivacySensitiveMode = PrivacySensitiveMode::Unspecified;
+ }
+
+ if (mLibLoader->stream_getChannelMask != nullptr) {
+ mChannelMask = static_cast<ChannelMask>(mLibLoader->stream_getChannelMask(mAAudioStream));
+ }
+
+ if (mLibLoader->stream_isContentSpatialized != nullptr) {
+ mIsContentSpatialized = mLibLoader->stream_isContentSpatialized(mAAudioStream);
+ }
+
+ if (mLibLoader->stream_getSpatializationBehavior != nullptr) {
+ mSpatializationBehavior = static_cast<SpatializationBehavior>(
+ mLibLoader->stream_getSpatializationBehavior(mAAudioStream));
+ }
+
+ if (mLibLoader->stream_getHardwareChannelCount != nullptr) {
+ mHardwareChannelCount = mLibLoader->stream_getHardwareChannelCount(mAAudioStream);
+ }
+ if (mLibLoader->stream_getHardwareSampleRate != nullptr) {
+ mHardwareSampleRate = mLibLoader->stream_getHardwareSampleRate(mAAudioStream);
+ }
+ if (mLibLoader->stream_getHardwareFormat != nullptr) {
+ mHardwareFormat = static_cast<AudioFormat>(mLibLoader->stream_getHardwareFormat(mAAudioStream));
+ }
+
+ updateDeviceIds();
+
+ LOGD("AudioStreamAAudio.open() format=%d, sampleRate=%d, capacity = %d",
+ static_cast<int>(mFormat), static_cast<int>(mSampleRate),
+ static_cast<int>(mBufferCapacityInFrames));
+
+ calculateDefaultDelayBeforeCloseMillis();
+
+error2:
+ mLibLoader->builder_delete(aaudioBuilder);
+ if (static_cast<int>(result) > 0) {
+ // Possibly due to b/267531411
+ LOGW("AudioStreamAAudio.open: AAudioStream_Open() returned positive error = %d",
+ static_cast<int>(result));
+ if (OboeGlobals::areWorkaroundsEnabled()) {
+ result = Result::ErrorInternal; // Coerce to negative error.
+ }
+ } else {
+ LOGD("AudioStreamAAudio.open: AAudioStream_Open() returned %s = %d",
+ mLibLoader->convertResultToText(static_cast<aaudio_result_t>(result)),
+ static_cast<int>(result));
+ }
+ return result;
+}
+
+Result AudioStreamAAudio::release() {
+ if (getSdkVersion() < __ANDROID_API_R__) {
+ return Result::ErrorUnimplemented;
+ }
+
+ // AAudioStream_release() is buggy on Android R.
+ if (OboeGlobals::areWorkaroundsEnabled() && getSdkVersion() == __ANDROID_API_R__) {
+ LOGW("Skipping release() on Android R");
+ return Result::ErrorUnimplemented;
+ }
+
+ std::lock_guard<std::mutex> lock(mLock);
+ AAudioStream *stream = mAAudioStream.load();
+ if (stream != nullptr) {
+ if (OboeGlobals::areWorkaroundsEnabled()) {
+ // Make sure we are really stopped. Do it under mLock
+ // so another thread cannot call requestStart() right before the close.
+ requestStop_l(stream);
+ }
+ return static_cast<Result>(mLibLoader->stream_release(stream));
+ } else {
+ return Result::ErrorClosed;
+ }
+}
+
+Result AudioStreamAAudio::close() {
+ // Prevent two threads from closing the stream at the same time and crashing.
+ // This could occur, for example, if an application called close() at the same
+ // time that an onError callback was being executed because of a disconnect.
+ std::lock_guard<std::mutex> lock(mLock);
+
+ AudioStream::close();
+
+ AAudioStream *stream = nullptr;
+ {
+ // Wait for any methods using mAAudioStream to finish.
+ std::unique_lock<std::shared_mutex> lock2(mAAudioStreamLock);
+ // Closing will delete *mAAudioStream so we need to null out the pointer atomically.
+ stream = mAAudioStream.exchange(nullptr);
+ }
+ if (stream != nullptr) {
+ if (OboeGlobals::areWorkaroundsEnabled()) {
+ // Make sure we are really stopped. Do it under mLock
+ // so another thread cannot call requestStart() right before the close.
+ requestStop_l(stream);
+ sleepBeforeClose();
+ }
+ return static_cast<Result>(mLibLoader->stream_close(stream));
+ } else {
+ return Result::ErrorClosed;
+ }
+}
+
+static void oboe_stop_thread_proc(AudioStream *oboeStream) {
+ if (oboeStream != nullptr) {
+ oboeStream->requestStop();
+ }
+}
+
+void AudioStreamAAudio::launchStopThread() {
+ // Prevent multiple stop threads from being launched.
+ if (mStopThreadAllowed.exchange(false)) {
+ // Stop this stream on a separate thread
+ std::thread t(oboe_stop_thread_proc, this);
+ t.detach();
+ }
+}
+
+DataCallbackResult AudioStreamAAudio::callOnAudioReady(AAudioStream * /*stream*/,
+ void *audioData,
+ int32_t numFrames) {
+ DataCallbackResult result = fireDataCallback(audioData, numFrames);
+ if (result == DataCallbackResult::Continue) {
+ return result;
+ } else {
+ if (result == DataCallbackResult::Stop) {
+ LOGD("Oboe callback returned DataCallbackResult::Stop");
+ } else {
+ LOGE("Oboe callback returned unexpected value. Error: %d", static_cast<int>(result));
+ }
+
+ // Returning Stop caused various problems before S. See #1230
+ if (OboeGlobals::areWorkaroundsEnabled() && getSdkVersion() <= __ANDROID_API_R__) {
+ launchStopThread();
+ return DataCallbackResult::Continue;
+ } else {
+ return DataCallbackResult::Stop; // OK >= API_S
+ }
+ }
+}
+
+Result AudioStreamAAudio::requestStart() {
+ std::lock_guard<std::mutex> lock(mLock);
+ AAudioStream *stream = mAAudioStream.load();
+ if (stream != nullptr) {
+ // Avoid state machine errors in O_MR1.
+ if (getSdkVersion() <= __ANDROID_API_O_MR1__) {
+ StreamState state = static_cast<StreamState>(mLibLoader->stream_getState(stream));
+ if (state == StreamState::Starting || state == StreamState::Started) {
+ // WARNING: On P, AAudio is returning ErrorInvalidState for Output and OK for Input.
+ return Result::OK;
+ }
+ }
+ if (isDataCallbackSpecified()) {
+ setDataCallbackEnabled(true);
+ }
+ mStopThreadAllowed = true;
+ closePerformanceHint();
+ return static_cast<Result>(mLibLoader->stream_requestStart(stream));
+ } else {
+ return Result::ErrorClosed;
+ }
+}
+
+Result AudioStreamAAudio::requestPause() {
+ std::lock_guard<std::mutex> lock(mLock);
+ AAudioStream *stream = mAAudioStream.load();
+ if (stream != nullptr) {
+ // Avoid state machine errors in O_MR1.
+ if (getSdkVersion() <= __ANDROID_API_O_MR1__) {
+ StreamState state = static_cast<StreamState>(mLibLoader->stream_getState(stream));
+ if (state == StreamState::Pausing || state == StreamState::Paused) {
+ return Result::OK;
+ }
+ }
+ return static_cast<Result>(mLibLoader->stream_requestPause(stream));
+ } else {
+ return Result::ErrorClosed;
+ }
+}
+
+Result AudioStreamAAudio::requestFlush() {
+ std::lock_guard<std::mutex> lock(mLock);
+ AAudioStream *stream = mAAudioStream.load();
+ if (stream != nullptr) {
+ // Avoid state machine errors in O_MR1.
+ if (getSdkVersion() <= __ANDROID_API_O_MR1__) {
+ StreamState state = static_cast<StreamState>(mLibLoader->stream_getState(stream));
+ if (state == StreamState::Flushing || state == StreamState::Flushed) {
+ return Result::OK;
+ }
+ }
+ return static_cast<Result>(mLibLoader->stream_requestFlush(stream));
+ } else {
+ return Result::ErrorClosed;
+ }
+}
+
+Result AudioStreamAAudio::requestStop() {
+ std::lock_guard<std::mutex> lock(mLock);
+ AAudioStream *stream = mAAudioStream.load();
+ if (stream != nullptr) {
+ return requestStop_l(stream);
+ } else {
+ return Result::ErrorClosed;
+ }
+}
+
+// Call under mLock
+Result AudioStreamAAudio::requestStop_l(AAudioStream *stream) {
+ // Avoid state machine errors in O_MR1.
+ if (getSdkVersion() <= __ANDROID_API_O_MR1__) {
+ StreamState state = static_cast<StreamState>(mLibLoader->stream_getState(stream));
+ if (state == StreamState::Stopping || state == StreamState::Stopped) {
+ return Result::OK;
+ }
+ }
+ return static_cast<Result>(mLibLoader->stream_requestStop(stream));
+}
+
+ResultWithValue<int32_t> AudioStreamAAudio::write(const void *buffer,
+ int32_t numFrames,
+ int64_t timeoutNanoseconds) {
+ std::shared_lock<std::shared_mutex> lock(mAAudioStreamLock);
+ AAudioStream *stream = mAAudioStream.load();
+ if (stream != nullptr) {
+ int32_t result = mLibLoader->stream_write(mAAudioStream, buffer,
+ numFrames, timeoutNanoseconds);
+ return ResultWithValue<int32_t>::createBasedOnSign(result);
+ } else {
+ return ResultWithValue<int32_t>(Result::ErrorClosed);
+ }
+}
+
+ResultWithValue<int32_t> AudioStreamAAudio::read(void *buffer,
+ int32_t numFrames,
+ int64_t timeoutNanoseconds) {
+ std::shared_lock<std::shared_mutex> lock(mAAudioStreamLock);
+ AAudioStream *stream = mAAudioStream.load();
+ if (stream != nullptr) {
+ int32_t result = mLibLoader->stream_read(mAAudioStream, buffer,
+ numFrames, timeoutNanoseconds);
+ return ResultWithValue<int32_t>::createBasedOnSign(result);
+ } else {
+ return ResultWithValue<int32_t>(Result::ErrorClosed);
+ }
+}
+
+
+// AAudioStream_waitForStateChange() can crash if it is waiting on a stream and that stream
+// is closed from another thread. We do not want to lock the stream for the duration of the call.
+// So we call AAudioStream_waitForStateChange() with a timeout of zero so that it will not block.
+// Then we can do our own sleep with the lock unlocked.
+Result AudioStreamAAudio::waitForStateChange(StreamState currentState,
+ StreamState *nextState,
+ int64_t timeoutNanoseconds) {
+ Result oboeResult = Result::ErrorTimeout;
+ int64_t sleepTimeNanos = 20 * kNanosPerMillisecond; // arbitrary
+ aaudio_stream_state_t currentAAudioState = static_cast<aaudio_stream_state_t>(currentState);
+
+ aaudio_result_t result = AAUDIO_OK;
+ int64_t timeLeftNanos = timeoutNanoseconds;
+
+ mLock.lock();
+ while (true) {
+ // Do we still have an AAudio stream? If not then stream must have been closed.
+ AAudioStream *stream = mAAudioStream.load();
+ if (stream == nullptr) {
+ if (nextState != nullptr) {
+ *nextState = StreamState::Closed;
+ }
+ oboeResult = Result::ErrorClosed;
+ break;
+ }
+
+ // Update and query state change with no blocking.
+ aaudio_stream_state_t aaudioNextState;
+ result = mLibLoader->stream_waitForStateChange(
+ mAAudioStream,
+ currentAAudioState,
+ &aaudioNextState,
+ 0); // timeout=0 for non-blocking
+ // AAudio will return AAUDIO_ERROR_TIMEOUT if timeout=0 and the state does not change.
+ if (result != AAUDIO_OK && result != AAUDIO_ERROR_TIMEOUT) {
+ oboeResult = static_cast<Result>(result);
+ break;
+ }
+#if OBOE_FIX_FORCE_STARTING_TO_STARTED
+ if (OboeGlobals::areWorkaroundsEnabled()
+ && aaudioNextState == static_cast<aaudio_stream_state_t >(StreamState::Starting)) {
+ aaudioNextState = static_cast<aaudio_stream_state_t >(StreamState::Started);
+ }
+#endif // OBOE_FIX_FORCE_STARTING_TO_STARTED
+ if (nextState != nullptr) {
+ *nextState = static_cast<StreamState>(aaudioNextState);
+ }
+ if (currentAAudioState != aaudioNextState) { // state changed?
+ oboeResult = Result::OK;
+ break;
+ }
+
+ // Did we timeout or did user ask for non-blocking?
+ if (timeLeftNanos <= 0) {
+ break;
+ }
+
+ // No change yet so sleep.
+ mLock.unlock(); // Don't sleep while locked.
+ if (sleepTimeNanos > timeLeftNanos) {
+ sleepTimeNanos = timeLeftNanos; // last little bit
+ }
+ AudioClock::sleepForNanos(sleepTimeNanos);
+ timeLeftNanos -= sleepTimeNanos;
+ mLock.lock();
+ }
+
+ mLock.unlock();
+ return oboeResult;
+}
+
+ResultWithValue<int32_t> AudioStreamAAudio::setBufferSizeInFrames(int32_t requestedFrames) {
+ int32_t adjustedFrames = requestedFrames;
+ if (adjustedFrames > mBufferCapacityInFrames) {
+ adjustedFrames = mBufferCapacityInFrames;
+ }
+ // This calls getBufferSize() so avoid recursive lock.
+ adjustedFrames = QuirksManager::getInstance().clipBufferSize(*this, adjustedFrames);
+
+ std::shared_lock<std::shared_mutex> lock(mAAudioStreamLock);
+ AAudioStream *stream = mAAudioStream.load();
+ if (stream != nullptr) {
+ int32_t newBufferSize = mLibLoader->stream_setBufferSize(mAAudioStream, adjustedFrames);
+ // Cache the result if it's valid
+ if (newBufferSize > 0) mBufferSizeInFrames = newBufferSize;
+ return ResultWithValue<int32_t>::createBasedOnSign(newBufferSize);
+ } else {
+ return ResultWithValue<int32_t>(Result::ErrorClosed);
+ }
+}
+
+StreamState AudioStreamAAudio::getState() {
+ std::shared_lock<std::shared_mutex> lock(mAAudioStreamLock);
+ AAudioStream *stream = mAAudioStream.load();
+ if (stream != nullptr) {
+ aaudio_stream_state_t aaudioState = mLibLoader->stream_getState(stream);
+#if OBOE_FIX_FORCE_STARTING_TO_STARTED
+ if (OboeGlobals::areWorkaroundsEnabled()
+ && aaudioState == AAUDIO_STREAM_STATE_STARTING) {
+ aaudioState = AAUDIO_STREAM_STATE_STARTED;
+ }
+#endif // OBOE_FIX_FORCE_STARTING_TO_STARTED
+ return static_cast<StreamState>(aaudioState);
+ } else {
+ return StreamState::Closed;
+ }
+}
+
+int32_t AudioStreamAAudio::getBufferSizeInFrames() {
+ std::shared_lock<std::shared_mutex> lock(mAAudioStreamLock);
+ AAudioStream *stream = mAAudioStream.load();
+ if (stream != nullptr) {
+ mBufferSizeInFrames = mLibLoader->stream_getBufferSize(stream);
+ }
+ return mBufferSizeInFrames;
+}
+
+void AudioStreamAAudio::updateFramesRead() {
+ std::shared_lock<std::shared_mutex> lock(mAAudioStreamLock);
+ AAudioStream *stream = mAAudioStream.load();
+// Set to 1 for debugging race condition #1180 with mAAudioStream.
+// See also DEBUG_CLOSE_RACE in OboeTester.
+// This was left in the code so that we could test the fix again easily in the future.
+// We could not trigger the race condition without adding these get calls and the sleeps.
+#define DEBUG_CLOSE_RACE 0
+#if DEBUG_CLOSE_RACE
+ // This is used when testing race conditions with close().
+ // See DEBUG_CLOSE_RACE in OboeTester
+ AudioClock::sleepForNanos(400 * kNanosPerMillisecond);
+#endif // DEBUG_CLOSE_RACE
+ if (stream != nullptr) {
+ mFramesRead = mLibLoader->stream_getFramesRead(stream);
+ }
+}
+
+void AudioStreamAAudio::updateFramesWritten() {
+ std::shared_lock<std::shared_mutex> lock(mAAudioStreamLock);
+ AAudioStream *stream = mAAudioStream.load();
+ if (stream != nullptr) {
+ mFramesWritten = mLibLoader->stream_getFramesWritten(stream);
+ }
+}
+
+ResultWithValue<int32_t> AudioStreamAAudio::getXRunCount() {
+ std::shared_lock<std::shared_mutex> lock(mAAudioStreamLock);
+ AAudioStream *stream = mAAudioStream.load();
+ if (stream != nullptr) {
+ return ResultWithValue<int32_t>::createBasedOnSign(mLibLoader->stream_getXRunCount(stream));
+ } else {
+ return ResultWithValue<int32_t>(Result::ErrorNull);
+ }
+}
+
+Result AudioStreamAAudio::getTimestamp(clockid_t clockId,
+ int64_t *framePosition,
+ int64_t *timeNanoseconds) {
+ if (getState() != StreamState::Started) {
+ return Result::ErrorInvalidState;
+ }
+ std::shared_lock<std::shared_mutex> lock(mAAudioStreamLock);
+ AAudioStream *stream = mAAudioStream.load();
+ if (stream != nullptr) {
+ return static_cast<Result>(mLibLoader->stream_getTimestamp(stream, clockId,
+ framePosition, timeNanoseconds));
+ } else {
+ return Result::ErrorNull;
+ }
+}
+
+ResultWithValue<double> AudioStreamAAudio::calculateLatencyMillis() {
+ // Get the time that a known audio frame was presented.
+ int64_t hardwareFrameIndex;
+ int64_t hardwareFrameHardwareTime;
+ auto result = getTimestamp(CLOCK_MONOTONIC,
+ &hardwareFrameIndex,
+ &hardwareFrameHardwareTime);
+ if (result != oboe::Result::OK) {
+ return ResultWithValue<double>(static_cast<Result>(result));
+ }
+
+ // Get counter closest to the app.
+ bool isOutput = (getDirection() == oboe::Direction::Output);
+ int64_t appFrameIndex = isOutput ? getFramesWritten() : getFramesRead();
+
+ // Assume that the next frame will be processed at the current time
+ using namespace std::chrono;
+ int64_t appFrameAppTime =
+ duration_cast<nanoseconds>(steady_clock::now().time_since_epoch()).count();
+
+ // Calculate the number of frames between app and hardware
+ int64_t frameIndexDelta = appFrameIndex - hardwareFrameIndex;
+
+ // Calculate the time which the next frame will be or was presented
+ int64_t frameTimeDelta = (frameIndexDelta * oboe::kNanosPerSecond) / getSampleRate();
+ int64_t appFrameHardwareTime = hardwareFrameHardwareTime + frameTimeDelta;
+
+ // The current latency is the difference in time between when the current frame is at
+ // the app and when it is at the hardware.
+ double latencyNanos = static_cast<double>(isOutput
+ ? (appFrameHardwareTime - appFrameAppTime) // hardware is later
+ : (appFrameAppTime - appFrameHardwareTime)); // hardware is earlier
+ double latencyMillis = latencyNanos / kNanosPerMillisecond;
+
+ return ResultWithValue<double>(latencyMillis);
+}
+
+bool AudioStreamAAudio::isMMapUsed() {
+ std::shared_lock<std::shared_mutex> lock(mAAudioStreamLock);
+ AAudioStream *stream = mAAudioStream.load();
+ if (stream != nullptr) {
+ return AAudioExtensions::getInstance().isMMapUsed(stream);
+ } else {
+ return false;
+ }
+}
+
+// static
+// Static method for the presentation end callback.
+// We use a method so we can access protected methods on the stream.
+// Launch a thread to handle the error.
+// That other thread can safely stop, close and delete the stream.
+void AudioStreamAAudio::internalPresentationEndCallback(AAudioStream *stream, void *userData) {
+ AudioStreamAAudio *oboeStream = reinterpret_cast<AudioStreamAAudio*>(userData);
+
+ // Prevents deletion of the stream if the app is using AudioStreamBuilder::openStream(shared_ptr)
+ std::shared_ptr<AudioStream> sharedStream = oboeStream->lockWeakThis();
+
+ if (stream != oboeStream->getUnderlyingStream()) {
+ LOGW("%s() stream already closed or closing", __func__); // might happen if there are bugs
+ } else if (sharedStream) {
+ // Handle error on a separate thread using shared pointer.
+ std::thread t(oboe_aaudio_presentation_end_thread_proc_shared, sharedStream);
+ t.detach();
+ } else {
+ // Handle error on a separate thread.
+ std::thread t(oboe_aaudio_presentation_thread_proc, oboeStream);
+ t.detach();
+ }
+}
+
+Result AudioStreamAAudio::setOffloadDelayPadding(
+ int32_t delayInFrames, int32_t paddingInFrames) {
+ if (mLibLoader->stream_setOffloadDelayPadding == nullptr) {
+ return Result::ErrorUnimplemented;
+ }
+ std::shared_lock<std::shared_mutex> lock(mAAudioStreamLock);
+ AAudioStream *stream = mAAudioStream.load();
+ if (stream == nullptr) {
+ return Result::ErrorClosed;
+ }
+ return static_cast<Result>(
+ mLibLoader->stream_setOffloadDelayPadding(stream, delayInFrames, paddingInFrames));
+}
+
+ResultWithValue<int32_t> AudioStreamAAudio::getOffloadDelay() {
+ if (mLibLoader->stream_getOffloadDelay == nullptr) {
+ return ResultWithValue<int32_t>(Result::ErrorUnimplemented);
+ }
+ std::shared_lock<std::shared_mutex> lock(mAAudioStreamLock);
+ AAudioStream *stream = mAAudioStream.load();
+ if (stream == nullptr) {
+ return Result::ErrorClosed;
+ }
+ return ResultWithValue<int32_t>::createBasedOnSign(mLibLoader->stream_getOffloadDelay(stream));
+}
+
+ResultWithValue<int32_t> AudioStreamAAudio::getOffloadPadding() {
+ if (mLibLoader->stream_getOffloadPadding == nullptr) {
+ return ResultWithValue<int32_t>(Result::ErrorUnimplemented);
+ }
+ std::shared_lock<std::shared_mutex> lock(mAAudioStreamLock);
+ AAudioStream *stream = mAAudioStream.load();
+ if (stream == nullptr) {
+ return ResultWithValue<int32_t>(Result::ErrorClosed);
+ }
+ return ResultWithValue<int32_t>::createBasedOnSign(
+ mLibLoader->stream_getOffloadPadding(stream));
+}
+
+Result AudioStreamAAudio::setOffloadEndOfStream() {
+ if (mLibLoader->stream_setOffloadEndOfStream == nullptr) {
+ return Result::ErrorUnimplemented;
+ }
+ std::shared_lock<std::shared_mutex> lock(mAAudioStreamLock);
+ AAudioStream *stream = mAAudioStream.load();
+ if (stream == nullptr) {
+ return ResultWithValue<int32_t>(Result::ErrorClosed);
+ }
+ return static_cast<Result>(mLibLoader->stream_setOffloadEndOfStream(stream));
+}
+
+void AudioStreamAAudio::updateDeviceIds() {
+ // If stream_getDeviceIds is not supported, use stream_getDeviceId.
+ if (mLibLoader->stream_getDeviceIds == nullptr) {
+ mDeviceIds.clear();
+ int32_t deviceId = mLibLoader->stream_getDeviceId(mAAudioStream);
+ if (deviceId != kUnspecified) {
+ mDeviceIds.push_back(deviceId);
+ }
+ } else {
+ // Allocate a temp vector with 16 elements. This should be enough to cover all cases.
+ // Please file a bug on Oboe if you discover that this returns AAUDIO_ERROR_OUT_OF_RANGE.
+ // When AAUDIO_ERROR_OUT_OF_RANGE is returned, the actual size will be still returned as the
+ // value of deviceIdSize but deviceIds will be empty.
+
+ static constexpr int kDefaultDeviceIdSize = 16;
+ int deviceIdSize = kDefaultDeviceIdSize;
+ std::vector<int32_t> deviceIds(deviceIdSize);
+ aaudio_result_t getDeviceIdResult =
+ mLibLoader->stream_getDeviceIds(mAAudioStream, deviceIds.data(), &deviceIdSize);
+ if (getDeviceIdResult != AAUDIO_OK) {
+ LOGE("stream_getDeviceIds did not return AAUDIO_OK. Error: %d",
+ static_cast<int>(getDeviceIdResult));
+ return;
+ }
+
+ mDeviceIds.clear();
+ for (int i = 0; i < deviceIdSize; i++) {
+ mDeviceIds.push_back(deviceIds[i]);
+ }
+ }
+
+ // This should not happen in most cases. Please file a bug on Oboe if you see this happening.
+ if (mDeviceIds.empty()) {
+ LOGW("updateDeviceIds() returns an empty array.");
+ }
+}
+
+} // namespace oboe