/* * 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 #include #include #include "oboe_aaudio_AAudioLoader_android.h" #include "oboe_aaudio_AudioStreamAAudio_android.h" #include "oboe_common_AudioClock_android.h" #include "oboe_common_OboeDebug_android.h" #include "oboe_oboe_Utilities_android.h" #ifdef __ANDROID__ #include #include "oboe_common_QuirksManager_android.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(userData); if (oboeStream != nullptr) { return static_cast( oboeStream->callOnAudioReady(stream, audioData, numFrames)); } else { return static_cast(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(AudioStreamAAudio *oboeStream, Result error) { LOGD("%s() - entering >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>", __func__); 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); } LOGD("%s() - exiting <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<", __func__); } // This runs in its own thread. // Only one of these threads will be launched from internalErrorCallback(). // Prevents deletion of the stream if the app is using AudioStreamBuilder::openSharedStream() static void oboe_aaudio_error_thread_proc_shared(std::shared_ptr sharedStream, Result error) { AudioStreamAAudio *oboeStream = reinterpret_cast(sharedStream.get()); oboe_aaudio_error_thread_proc(oboeStream, error); } 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(error); AudioStreamAAudio *oboeStream = reinterpret_cast(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 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::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(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(mBufferCapacityInFrames), capacity); } mLibLoader->builder_setBufferCapacityInFrames(aaudioBuilder, capacity); mLibLoader->builder_setChannelCount(aaudioBuilder, mChannelCount); mLibLoader->builder_setDeviceId(aaudioBuilder, mDeviceId); mLibLoader->builder_setDirection(aaudioBuilder, static_cast(mDirection)); mLibLoader->builder_setFormat(aaudioBuilder, static_cast(mFormat)); mLibLoader->builder_setSampleRate(aaudioBuilder, mSampleRate); mLibLoader->builder_setSharingMode(aaudioBuilder, static_cast(mSharingMode)); mLibLoader->builder_setPerformanceMode(aaudioBuilder, static_cast(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(mUsage)); } if (mLibLoader->builder_setContentType != nullptr) { mLibLoader->builder_setContentType(aaudioBuilder, static_cast(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(inputPreset)); } if (mLibLoader->builder_setSessionId != nullptr) { mLibLoader->builder_setSessionId(aaudioBuilder, static_cast(mSessionId)); } // TODO get more parameters from the builder? 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. // ============= OPEN THE STREAM ================ { AAudioStream *stream = nullptr; result = static_cast(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 mDeviceId = mLibLoader->stream_getDeviceId(mAAudioStream); mChannelCount = mLibLoader->stream_getChannelCount(mAAudioStream); mSampleRate = mLibLoader->stream_getSampleRate(mAAudioStream); mFormat = static_cast(mLibLoader->stream_getFormat(mAAudioStream)); mSharingMode = static_cast(mLibLoader->stream_getSharingMode(mAAudioStream)); mPerformanceMode = static_cast( mLibLoader->stream_getPerformanceMode(mAAudioStream)); mBufferCapacityInFrames = mLibLoader->stream_getBufferCapacity(mAAudioStream); mBufferSizeInFrames = mLibLoader->stream_getBufferSize(mAAudioStream); // These were added in P so we have to check for the function pointer. if (mLibLoader->stream_getUsage != nullptr) { mUsage = static_cast(mLibLoader->stream_getUsage(mAAudioStream)); } if (mLibLoader->stream_getContentType != nullptr) { mContentType = static_cast(mLibLoader->stream_getContentType(mAAudioStream)); } if (mLibLoader->stream_getInputPreset != nullptr) { mInputPreset = static_cast(mLibLoader->stream_getInputPreset(mAAudioStream)); } if (mLibLoader->stream_getSessionId != nullptr) { mSessionId = static_cast(mLibLoader->stream_getSessionId(mAAudioStream)); } else { mSessionId = SessionId::None; } LOGD("AudioStreamAAudio.open() format=%d, sampleRate=%d, capacity = %d", static_cast(mFormat), static_cast(mSampleRate), static_cast(mBufferCapacityInFrames)); error2: mLibLoader->builder_delete(aaudioBuilder); LOGD("AudioStreamAAudio.open: AAudioStream_Open() returned %s", mLibLoader->convertResultToText(static_cast(result))); return result; } 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 lock(mLock); AudioStream::close(); // This will delete the AAudio stream object so we need to null out the pointer. AAudioStream *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); // Sometimes a callback can occur shortly after a stream has been stopped and // even after a close! If the stream has been closed then the callback // can access memory that has been freed. That causes a crash. // This seems to be more likely in Android P or earlier. // But it can also occur in later versions. usleep(kDelayBeforeCloseMillis * 1000); } return static_cast(mLibLoader->stream_close(stream)); } else { return Result::ErrorClosed; } } 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 = %d", result); } if (getSdkVersion() <= __ANDROID_API_P__) { launchStopThread(); if (isMMapUsed()) { return DataCallbackResult::Stop; } else { // Legacy stream <= API_P cannot be restarted after returning Stop. return DataCallbackResult::Continue; } } else { return DataCallbackResult::Stop; // OK >= API_Q } } } Result AudioStreamAAudio::requestStart() { std::lock_guard 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(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); } return static_cast(mLibLoader->stream_requestStart(stream)); } else { return Result::ErrorClosed; } } Result AudioStreamAAudio::requestPause() { std::lock_guard 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(mLibLoader->stream_getState(stream)); if (state == StreamState::Pausing || state == StreamState::Paused) { return Result::OK; } } return static_cast(mLibLoader->stream_requestPause(stream)); } else { return Result::ErrorClosed; } } Result AudioStreamAAudio::requestFlush() { std::lock_guard 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(mLibLoader->stream_getState(stream)); if (state == StreamState::Flushing || state == StreamState::Flushed) { return Result::OK; } } return static_cast(mLibLoader->stream_requestFlush(stream)); } else { return Result::ErrorClosed; } } Result AudioStreamAAudio::requestStop() { std::lock_guard 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(mLibLoader->stream_getState(stream)); if (state == StreamState::Stopping || state == StreamState::Stopped) { return Result::OK; } } return static_cast(mLibLoader->stream_requestStop(stream)); } ResultWithValue AudioStreamAAudio::write(const void *buffer, int32_t numFrames, int64_t timeoutNanoseconds) { AAudioStream *stream = mAAudioStream.load(); if (stream != nullptr) { int32_t result = mLibLoader->stream_write(mAAudioStream, buffer, numFrames, timeoutNanoseconds); return ResultWithValue::createBasedOnSign(result); } else { return ResultWithValue(Result::ErrorClosed); } } ResultWithValue AudioStreamAAudio::read(void *buffer, int32_t numFrames, int64_t timeoutNanoseconds) { AAudioStream *stream = mAAudioStream.load(); if (stream != nullptr) { int32_t result = mLibLoader->stream_read(mAAudioStream, buffer, numFrames, timeoutNanoseconds); return ResultWithValue::createBasedOnSign(result); } else { return ResultWithValue(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(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); break; } #if OBOE_FIX_FORCE_STARTING_TO_STARTED if (OboeGlobals::areWorkaroundsEnabled() && aaudioNextState == static_cast(StreamState::Starting)) { aaudioNextState = static_cast(StreamState::Started); } #endif // OBOE_FIX_FORCE_STARTING_TO_STARTED if (nextState != nullptr) { *nextState = static_cast(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 AudioStreamAAudio::setBufferSizeInFrames(int32_t requestedFrames) { AAudioStream *stream = mAAudioStream.load(); if (stream != nullptr) { int32_t adjustedFrames = requestedFrames; if (adjustedFrames > mBufferCapacityInFrames) { adjustedFrames = mBufferCapacityInFrames; } adjustedFrames = QuirksManager::getInstance().clipBufferSize(*this, adjustedFrames); int32_t newBufferSize = mLibLoader->stream_setBufferSize(mAAudioStream, adjustedFrames); // Cache the result if it's valid if (newBufferSize > 0) mBufferSizeInFrames = newBufferSize; return ResultWithValue::createBasedOnSign(newBufferSize); } else { return ResultWithValue(Result::ErrorClosed); } } StreamState AudioStreamAAudio::getState() const { 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(aaudioState); } else { return StreamState::Closed; } } int32_t AudioStreamAAudio::getBufferSizeInFrames() { AAudioStream *stream = mAAudioStream.load(); if (stream != nullptr) { mBufferSizeInFrames = mLibLoader->stream_getBufferSize(stream); } return mBufferSizeInFrames; } int32_t AudioStreamAAudio::getFramesPerBurst() { AAudioStream *stream = mAAudioStream.load(); if (stream != nullptr) { mFramesPerBurst = mLibLoader->stream_getFramesPerBurst(stream); } return mFramesPerBurst; } void AudioStreamAAudio::updateFramesRead() { AAudioStream *stream = mAAudioStream.load(); if (stream != nullptr) { mFramesRead = mLibLoader->stream_getFramesRead(stream); } } void AudioStreamAAudio::updateFramesWritten() { AAudioStream *stream = mAAudioStream.load(); if (stream != nullptr) { mFramesWritten = mLibLoader->stream_getFramesWritten(stream); } } ResultWithValue AudioStreamAAudio::getXRunCount() const { AAudioStream *stream = mAAudioStream.load(); if (stream != nullptr) { return ResultWithValue::createBasedOnSign(mLibLoader->stream_getXRunCount(stream)); } else { return ResultWithValue(Result::ErrorNull); } } Result AudioStreamAAudio::getTimestamp(clockid_t clockId, int64_t *framePosition, int64_t *timeNanoseconds) { AAudioStream *stream = mAAudioStream.load(); if (stream != nullptr) { if (getState() != StreamState::Started) { return Result::ErrorInvalidState; } return static_cast(mLibLoader->stream_getTimestamp(stream, clockId, framePosition, timeNanoseconds)); } else { return Result::ErrorNull; } } ResultWithValue AudioStreamAAudio::calculateLatencyMillis() { AAudioStream *stream = mAAudioStream.load(); if (stream == nullptr) { return ResultWithValue(Result::ErrorClosed); } // 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(static_cast(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(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(isOutput ? (appFrameHardwareTime - appFrameAppTime) // hardware is later : (appFrameAppTime - appFrameHardwareTime)); // hardware is earlier double latencyMillis = latencyNanos / kNanosPerMillisecond; return ResultWithValue(latencyMillis); } bool AudioStreamAAudio::isMMapUsed() { AAudioStream *stream = mAAudioStream.load(); if (stream != nullptr) { return mLibLoader->stream_isMMapUsed(stream); } else { return false; } } } // namespace oboe