mirror of
https://github.com/hajimehoshi/ebiten.git
synced 2024-11-14 06:57:27 +01:00
687 lines
27 KiB
C++
687 lines
27 KiB
C++
|
/*
|
||
|
* 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 "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 <sys/system_properties.h>
|
||
|
#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<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(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<AudioStream> sharedStream,
|
||
|
Result error) {
|
||
|
AudioStreamAAudio *oboeStream = reinterpret_cast<AudioStreamAAudio*>(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<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::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);
|
||
|
|
||
|
mLibLoader->builder_setChannelCount(aaudioBuilder, mChannelCount);
|
||
|
mLibLoader->builder_setDeviceId(aaudioBuilder, mDeviceId);
|
||
|
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));
|
||
|
}
|
||
|
|
||
|
if (mLibLoader->builder_setSessionId != nullptr) {
|
||
|
mLibLoader->builder_setSessionId(aaudioBuilder,
|
||
|
static_cast<aaudio_session_id_t>(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<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
|
||
|
mDeviceId = mLibLoader->stream_getDeviceId(mAAudioStream);
|
||
|
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);
|
||
|
|
||
|
// 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;
|
||
|
}
|
||
|
|
||
|
LOGD("AudioStreamAAudio.open() format=%d, sampleRate=%d, capacity = %d",
|
||
|
static_cast<int>(mFormat), static_cast<int>(mSampleRate),
|
||
|
static_cast<int>(mBufferCapacityInFrames));
|
||
|
|
||
|
error2:
|
||
|
mLibLoader->builder_delete(aaudioBuilder);
|
||
|
LOGD("AudioStreamAAudio.open: AAudioStream_Open() returned %s",
|
||
|
mLibLoader->convertResultToText(static_cast<aaudio_result_t>(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<std::mutex> 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<Result>(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<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);
|
||
|
}
|
||
|
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) {
|
||
|
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) {
|
||
|
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) {
|
||
|
|
||
|
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<int32_t>::createBasedOnSign(newBufferSize);
|
||
|
|
||
|
} else {
|
||
|
return ResultWithValue<int32_t>(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<StreamState>(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<int32_t> AudioStreamAAudio::getXRunCount() const {
|
||
|
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) {
|
||
|
AAudioStream *stream = mAAudioStream.load();
|
||
|
if (stream != nullptr) {
|
||
|
if (getState() != StreamState::Started) {
|
||
|
return Result::ErrorInvalidState;
|
||
|
}
|
||
|
return static_cast<Result>(mLibLoader->stream_getTimestamp(stream, clockId,
|
||
|
framePosition, timeNanoseconds));
|
||
|
} else {
|
||
|
return Result::ErrorNull;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
ResultWithValue<double> AudioStreamAAudio::calculateLatencyMillis() {
|
||
|
AAudioStream *stream = mAAudioStream.load();
|
||
|
if (stream == nullptr) {
|
||
|
return ResultWithValue<double>(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<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() {
|
||
|
AAudioStream *stream = mAAudioStream.load();
|
||
|
if (stream != nullptr) {
|
||
|
return mLibLoader->stream_isMMapUsed(stream);
|
||
|
} else {
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
} // namespace oboe
|