| /* | |
| * Copyright (c) 2018 The WebRTC project authors. All Rights Reserved. | |
| * | |
| * Use of this source code is governed by a BSD-style license | |
| * that can be found in the LICENSE file in the root of the source | |
| * tree. An additional intellectual property rights grant can be found | |
| * in the file PATENTS. All contributing project authors may | |
| * be found in the AUTHORS file in the root of the source tree. | |
| */ | |
| #include "modules/audio_device/android/aaudio_wrapper.h" | |
| #include "modules/audio_device/android/audio_manager.h" | |
| #include "rtc_base/logging.h" | |
| #include "rtc_base/strings/string_builder.h" | |
| #include "rtc_base/timeutils.h" | |
| #define LOG_ON_ERROR(op) \ | |
| do { \ | |
| aaudio_result_t result = (op); \ | |
| if (result != AAUDIO_OK) { \ | |
| RTC_LOG(LS_ERROR) << #op << ": " << AAudio_convertResultToText(result); \ | |
| } \ | |
| } while (0) | |
| #define RETURN_ON_ERROR(op, ...) \ | |
| do { \ | |
| aaudio_result_t result = (op); \ | |
| if (result != AAUDIO_OK) { \ | |
| RTC_LOG(LS_ERROR) << #op << ": " << AAudio_convertResultToText(result); \ | |
| return __VA_ARGS__; \ | |
| } \ | |
| } while (0) | |
| namespace webrtc { | |
| namespace { | |
| const char* DirectionToString(aaudio_direction_t direction) { | |
| switch (direction) { | |
| case AAUDIO_DIRECTION_OUTPUT: | |
| return "OUTPUT"; | |
| case AAUDIO_DIRECTION_INPUT: | |
| return "INPUT"; | |
| default: | |
| return "UNKNOWN"; | |
| } | |
| } | |
| const char* SharingModeToString(aaudio_sharing_mode_t mode) { | |
| switch (mode) { | |
| case AAUDIO_SHARING_MODE_EXCLUSIVE: | |
| return "EXCLUSIVE"; | |
| case AAUDIO_SHARING_MODE_SHARED: | |
| return "SHARED"; | |
| default: | |
| return "UNKNOWN"; | |
| } | |
| } | |
| const char* PerformanceModeToString(aaudio_performance_mode_t mode) { | |
| switch (mode) { | |
| case AAUDIO_PERFORMANCE_MODE_NONE: | |
| return "NONE"; | |
| case AAUDIO_PERFORMANCE_MODE_POWER_SAVING: | |
| return "POWER_SAVING"; | |
| case AAUDIO_PERFORMANCE_MODE_LOW_LATENCY: | |
| return "LOW_LATENCY"; | |
| default: | |
| return "UNKNOWN"; | |
| } | |
| } | |
| const char* FormatToString(int32_t id) { | |
| switch (id) { | |
| case AAUDIO_FORMAT_INVALID: | |
| return "INVALID"; | |
| case AAUDIO_FORMAT_UNSPECIFIED: | |
| return "UNSPECIFIED"; | |
| case AAUDIO_FORMAT_PCM_I16: | |
| return "PCM_I16"; | |
| case AAUDIO_FORMAT_PCM_FLOAT: | |
| return "FLOAT"; | |
| default: | |
| return "UNKNOWN"; | |
| } | |
| } | |
| void ErrorCallback(AAudioStream* stream, | |
| void* user_data, | |
| aaudio_result_t error) { | |
| RTC_DCHECK(user_data); | |
| AAudioWrapper* aaudio_wrapper = reinterpret_cast<AAudioWrapper*>(user_data); | |
| RTC_LOG(WARNING) << "ErrorCallback: " | |
| << DirectionToString(aaudio_wrapper->direction()); | |
| RTC_DCHECK(aaudio_wrapper->observer()); | |
| aaudio_wrapper->observer()->OnErrorCallback(error); | |
| } | |
| aaudio_data_callback_result_t DataCallback(AAudioStream* stream, | |
| void* user_data, | |
| void* audio_data, | |
| int32_t num_frames) { | |
| RTC_DCHECK(user_data); | |
| RTC_DCHECK(audio_data); | |
| AAudioWrapper* aaudio_wrapper = reinterpret_cast<AAudioWrapper*>(user_data); | |
| RTC_DCHECK(aaudio_wrapper->observer()); | |
| return aaudio_wrapper->observer()->OnDataCallback(audio_data, num_frames); | |
| } | |
| // Wraps the stream builder object to ensure that it is released properly when | |
| // the stream builder goes out of scope. | |
| class ScopedStreamBuilder { | |
| public: | |
| ScopedStreamBuilder() { | |
| LOG_ON_ERROR(AAudio_createStreamBuilder(&builder_)); | |
| RTC_DCHECK(builder_); | |
| } | |
| ~ScopedStreamBuilder() { | |
| if (builder_) { | |
| LOG_ON_ERROR(AAudioStreamBuilder_delete(builder_)); | |
| } | |
| } | |
| AAudioStreamBuilder* get() const { return builder_; } | |
| private: | |
| AAudioStreamBuilder* builder_ = nullptr; | |
| }; | |
| } // namespace | |
| AAudioWrapper::AAudioWrapper(AudioManager* audio_manager, | |
| aaudio_direction_t direction, | |
| AAudioObserverInterface* observer) | |
| : direction_(direction), observer_(observer) { | |
| RTC_LOG(INFO) << "ctor"; | |
| RTC_DCHECK(observer_); | |
| direction_ == AAUDIO_DIRECTION_OUTPUT | |
| ? audio_parameters_ = audio_manager->GetPlayoutAudioParameters() | |
| : audio_parameters_ = audio_manager->GetRecordAudioParameters(); | |
| aaudio_thread_checker_.DetachFromThread(); | |
| RTC_LOG(INFO) << audio_parameters_.ToString(); | |
| } | |
| AAudioWrapper::~AAudioWrapper() { | |
| RTC_LOG(INFO) << "dtor"; | |
| RTC_DCHECK(thread_checker_.CalledOnValidThread()); | |
| RTC_DCHECK(!stream_); | |
| } | |
| bool AAudioWrapper::Init() { | |
| RTC_LOG(INFO) << "Init"; | |
| RTC_DCHECK(thread_checker_.CalledOnValidThread()); | |
| // Creates a stream builder which can be used to open an audio stream. | |
| ScopedStreamBuilder builder; | |
| // Configures the stream builder using audio parameters given at construction. | |
| SetStreamConfiguration(builder.get()); | |
| // Opens a stream based on options in the stream builder. | |
| if (!OpenStream(builder.get())) { | |
| return false; | |
| } | |
| // Ensures that the opened stream could activate the requested settings. | |
| if (!VerifyStreamConfiguration()) { | |
| return false; | |
| } | |
| // Optimizes the buffer scheme for lowest possible latency and creates | |
| // additional buffer logic to match the 10ms buffer size used in WebRTC. | |
| if (!OptimizeBuffers()) { | |
| return false; | |
| } | |
| LogStreamState(); | |
| return true; | |
| } | |
| bool AAudioWrapper::Start() { | |
| RTC_LOG(INFO) << "Start"; | |
| RTC_DCHECK(thread_checker_.CalledOnValidThread()); | |
| // TODO(henrika): this state check might not be needed. | |
| aaudio_stream_state_t current_state = AAudioStream_getState(stream_); | |
| if (current_state != AAUDIO_STREAM_STATE_OPEN) { | |
| RTC_LOG(LS_ERROR) << "Invalid state: " | |
| << AAudio_convertStreamStateToText(current_state); | |
| return false; | |
| } | |
| // Asynchronous request for the stream to start. | |
| RETURN_ON_ERROR(AAudioStream_requestStart(stream_), false); | |
| LogStreamState(); | |
| return true; | |
| } | |
| bool AAudioWrapper::Stop() { | |
| RTC_LOG(INFO) << "Stop: " << DirectionToString(direction()); | |
| RTC_DCHECK(thread_checker_.CalledOnValidThread()); | |
| // Asynchronous request for the stream to stop. | |
| RETURN_ON_ERROR(AAudioStream_requestStop(stream_), false); | |
| CloseStream(); | |
| aaudio_thread_checker_.DetachFromThread(); | |
| return true; | |
| } | |
| double AAudioWrapper::EstimateLatencyMillis() const { | |
| RTC_DCHECK(stream_); | |
| double latency_millis = 0.0; | |
| if (direction() == AAUDIO_DIRECTION_INPUT) { | |
| // For input streams. Best guess we can do is to use the current burst size | |
| // as delay estimate. | |
| latency_millis = static_cast<double>(frames_per_burst()) / sample_rate() * | |
| rtc::kNumMillisecsPerSec; | |
| } else { | |
| int64_t existing_frame_index; | |
| int64_t existing_frame_presentation_time; | |
| // Get the time at which a particular frame was presented to audio hardware. | |
| aaudio_result_t result = AAudioStream_getTimestamp( | |
| stream_, CLOCK_MONOTONIC, &existing_frame_index, | |
| &existing_frame_presentation_time); | |
| // Results are only valid when the stream is in AAUDIO_STREAM_STATE_STARTED. | |
| if (result == AAUDIO_OK) { | |
| // Get write index for next audio frame. | |
| int64_t next_frame_index = frames_written(); | |
| // Number of frames between next frame and the existing frame. | |
| int64_t frame_index_delta = next_frame_index - existing_frame_index; | |
| // Assume the next frame will be written now. | |
| int64_t next_frame_write_time = rtc::TimeNanos(); | |
| // Calculate time when next frame will be presented to the hardware taking | |
| // sample rate into account. | |
| int64_t frame_time_delta = | |
| (frame_index_delta * rtc::kNumNanosecsPerSec) / sample_rate(); | |
| int64_t next_frame_presentation_time = | |
| existing_frame_presentation_time + frame_time_delta; | |
| // Derive a latency estimate given results above. | |
| latency_millis = static_cast<double>(next_frame_presentation_time - | |
| next_frame_write_time) / | |
| rtc::kNumNanosecsPerMillisec; | |
| } | |
| } | |
| return latency_millis; | |
| } | |
| // Returns new buffer size or a negative error value if buffer size could not | |
| // be increased. | |
| bool AAudioWrapper::IncreaseOutputBufferSize() { | |
| RTC_LOG(INFO) << "IncreaseBufferSize"; | |
| RTC_DCHECK(stream_); | |
| RTC_DCHECK(aaudio_thread_checker_.CalledOnValidThread()); | |
| RTC_DCHECK_EQ(direction(), AAUDIO_DIRECTION_OUTPUT); | |
| aaudio_result_t buffer_size = AAudioStream_getBufferSizeInFrames(stream_); | |
| // Try to increase size of buffer with one burst to reduce risk of underrun. | |
| buffer_size += frames_per_burst(); | |
| // Verify that the new buffer size is not larger than max capacity. | |
| // TODO(henrika): keep track of case when we reach the capacity limit. | |
| const int32_t max_buffer_size = buffer_capacity_in_frames(); | |
| if (buffer_size > max_buffer_size) { | |
| RTC_LOG(LS_ERROR) << "Required buffer size (" << buffer_size | |
| << ") is higher than max: " << max_buffer_size; | |
| return false; | |
| } | |
| RTC_LOG(INFO) << "Updating buffer size to: " << buffer_size | |
| << " (max=" << max_buffer_size << ")"; | |
| buffer_size = AAudioStream_setBufferSizeInFrames(stream_, buffer_size); | |
| if (buffer_size < 0) { | |
| RTC_LOG(LS_ERROR) << "Failed to change buffer size: " | |
| << AAudio_convertResultToText(buffer_size); | |
| return false; | |
| } | |
| RTC_LOG(INFO) << "Buffer size changed to: " << buffer_size; | |
| return true; | |
| } | |
| void AAudioWrapper::ClearInputStream(void* audio_data, int32_t num_frames) { | |
| RTC_LOG(INFO) << "ClearInputStream"; | |
| RTC_DCHECK(stream_); | |
| RTC_DCHECK(aaudio_thread_checker_.CalledOnValidThread()); | |
| RTC_DCHECK_EQ(direction(), AAUDIO_DIRECTION_INPUT); | |
| aaudio_result_t cleared_frames = 0; | |
| do { | |
| cleared_frames = AAudioStream_read(stream_, audio_data, num_frames, 0); | |
| } while (cleared_frames > 0); | |
| } | |
| AAudioObserverInterface* AAudioWrapper::observer() const { | |
| return observer_; | |
| } | |
| AudioParameters AAudioWrapper::audio_parameters() const { | |
| return audio_parameters_; | |
| } | |
| int32_t AAudioWrapper::samples_per_frame() const { | |
| RTC_DCHECK(stream_); | |
| return AAudioStream_getSamplesPerFrame(stream_); | |
| } | |
| int32_t AAudioWrapper::buffer_size_in_frames() const { | |
| RTC_DCHECK(stream_); | |
| return AAudioStream_getBufferSizeInFrames(stream_); | |
| } | |
| int32_t AAudioWrapper::buffer_capacity_in_frames() const { | |
| RTC_DCHECK(stream_); | |
| return AAudioStream_getBufferCapacityInFrames(stream_); | |
| } | |
| int32_t AAudioWrapper::device_id() const { | |
| RTC_DCHECK(stream_); | |
| return AAudioStream_getDeviceId(stream_); | |
| } | |
| int32_t AAudioWrapper::xrun_count() const { | |
| RTC_DCHECK(stream_); | |
| return AAudioStream_getXRunCount(stream_); | |
| } | |
| int32_t AAudioWrapper::format() const { | |
| RTC_DCHECK(stream_); | |
| return AAudioStream_getFormat(stream_); | |
| } | |
| int32_t AAudioWrapper::sample_rate() const { | |
| RTC_DCHECK(stream_); | |
| return AAudioStream_getSampleRate(stream_); | |
| } | |
| int32_t AAudioWrapper::channel_count() const { | |
| RTC_DCHECK(stream_); | |
| return AAudioStream_getChannelCount(stream_); | |
| } | |
| int32_t AAudioWrapper::frames_per_callback() const { | |
| RTC_DCHECK(stream_); | |
| return AAudioStream_getFramesPerDataCallback(stream_); | |
| } | |
| aaudio_sharing_mode_t AAudioWrapper::sharing_mode() const { | |
| RTC_DCHECK(stream_); | |
| return AAudioStream_getSharingMode(stream_); | |
| } | |
| aaudio_performance_mode_t AAudioWrapper::performance_mode() const { | |
| RTC_DCHECK(stream_); | |
| return AAudioStream_getPerformanceMode(stream_); | |
| } | |
| aaudio_stream_state_t AAudioWrapper::stream_state() const { | |
| RTC_DCHECK(stream_); | |
| return AAudioStream_getState(stream_); | |
| } | |
| int64_t AAudioWrapper::frames_written() const { | |
| RTC_DCHECK(stream_); | |
| return AAudioStream_getFramesWritten(stream_); | |
| } | |
| int64_t AAudioWrapper::frames_read() const { | |
| RTC_DCHECK(stream_); | |
| return AAudioStream_getFramesRead(stream_); | |
| } | |
| void AAudioWrapper::SetStreamConfiguration(AAudioStreamBuilder* builder) { | |
| RTC_LOG(INFO) << "SetStreamConfiguration"; | |
| RTC_DCHECK(builder); | |
| RTC_DCHECK(thread_checker_.CalledOnValidThread()); | |
| // Request usage of default primary output/input device. | |
| // TODO(henrika): verify that default device follows Java APIs. | |
| // https://developer.android.com/reference/android/media/AudioDeviceInfo.html. | |
| AAudioStreamBuilder_setDeviceId(builder, AAUDIO_UNSPECIFIED); | |
| // Use preferred sample rate given by the audio parameters. | |
| AAudioStreamBuilder_setSampleRate(builder, audio_parameters().sample_rate()); | |
| // Use preferred channel configuration given by the audio parameters. | |
| AAudioStreamBuilder_setChannelCount(builder, audio_parameters().channels()); | |
| // Always use 16-bit PCM audio sample format. | |
| AAudioStreamBuilder_setFormat(builder, AAUDIO_FORMAT_PCM_I16); | |
| // TODO(henrika): investigate effect of using AAUDIO_SHARING_MODE_EXCLUSIVE. | |
| // Ask for exclusive mode since this will give us the lowest possible latency. | |
| // If exclusive mode isn't available, shared mode will be used instead. | |
| AAudioStreamBuilder_setSharingMode(builder, AAUDIO_SHARING_MODE_SHARED); | |
| // Use the direction that was given at construction. | |
| AAudioStreamBuilder_setDirection(builder, direction_); | |
| // TODO(henrika): investigate performance using different performance modes. | |
| AAudioStreamBuilder_setPerformanceMode(builder, | |
| AAUDIO_PERFORMANCE_MODE_LOW_LATENCY); | |
| // Given that WebRTC applications require low latency, our audio stream uses | |
| // an asynchronous callback function to transfer data to and from the | |
| // application. AAudio executes the callback in a higher-priority thread that | |
| // has better performance. | |
| AAudioStreamBuilder_setDataCallback(builder, DataCallback, this); | |
| // Request that AAudio calls this functions if any error occurs on a callback | |
| // thread. | |
| AAudioStreamBuilder_setErrorCallback(builder, ErrorCallback, this); | |
| } | |
| bool AAudioWrapper::OpenStream(AAudioStreamBuilder* builder) { | |
| RTC_LOG(INFO) << "OpenStream"; | |
| RTC_DCHECK(builder); | |
| AAudioStream* stream = nullptr; | |
| RETURN_ON_ERROR(AAudioStreamBuilder_openStream(builder, &stream), false); | |
| stream_ = stream; | |
| LogStreamConfiguration(); | |
| return true; | |
| } | |
| void AAudioWrapper::CloseStream() { | |
| RTC_LOG(INFO) << "CloseStream"; | |
| RTC_DCHECK(stream_); | |
| LOG_ON_ERROR(AAudioStream_close(stream_)); | |
| stream_ = nullptr; | |
| } | |
| void AAudioWrapper::LogStreamConfiguration() { | |
| RTC_DCHECK(stream_); | |
| char ss_buf[1024]; | |
| rtc::SimpleStringBuilder ss(ss_buf); | |
| ss << "Stream Configuration: "; | |
| ss << "sample rate=" << sample_rate() << ", channels=" << channel_count(); | |
| ss << ", samples per frame=" << samples_per_frame(); | |
| ss << ", format=" << FormatToString(format()); | |
| ss << ", sharing mode=" << SharingModeToString(sharing_mode()); | |
| ss << ", performance mode=" << PerformanceModeToString(performance_mode()); | |
| ss << ", direction=" << DirectionToString(direction()); | |
| ss << ", device id=" << AAudioStream_getDeviceId(stream_); | |
| ss << ", frames per callback=" << frames_per_callback(); | |
| RTC_LOG(INFO) << ss.str(); | |
| } | |
| void AAudioWrapper::LogStreamState() { | |
| RTC_LOG(INFO) << "AAudio stream state: " | |
| << AAudio_convertStreamStateToText(stream_state()); | |
| } | |
| bool AAudioWrapper::VerifyStreamConfiguration() { | |
| RTC_LOG(INFO) << "VerifyStreamConfiguration"; | |
| RTC_DCHECK(stream_); | |
| // TODO(henrika): should we verify device ID as well? | |
| if (AAudioStream_getSampleRate(stream_) != audio_parameters().sample_rate()) { | |
| RTC_LOG(LS_ERROR) << "Stream unable to use requested sample rate"; | |
| return false; | |
| } | |
| if (AAudioStream_getChannelCount(stream_) != | |
| static_cast<int32_t>(audio_parameters().channels())) { | |
| RTC_LOG(LS_ERROR) << "Stream unable to use requested channel count"; | |
| return false; | |
| } | |
| if (AAudioStream_getFormat(stream_) != AAUDIO_FORMAT_PCM_I16) { | |
| RTC_LOG(LS_ERROR) << "Stream unable to use requested format"; | |
| return false; | |
| } | |
| if (AAudioStream_getSharingMode(stream_) != AAUDIO_SHARING_MODE_SHARED) { | |
| RTC_LOG(LS_ERROR) << "Stream unable to use requested sharing mode"; | |
| return false; | |
| } | |
| if (AAudioStream_getPerformanceMode(stream_) != | |
| AAUDIO_PERFORMANCE_MODE_LOW_LATENCY) { | |
| RTC_LOG(LS_ERROR) << "Stream unable to use requested performance mode"; | |
| return false; | |
| } | |
| if (AAudioStream_getDirection(stream_) != direction()) { | |
| RTC_LOG(LS_ERROR) << "Stream direction could not be set"; | |
| return false; | |
| } | |
| if (AAudioStream_getSamplesPerFrame(stream_) != | |
| static_cast<int32_t>(audio_parameters().channels())) { | |
| RTC_LOG(LS_ERROR) << "Invalid number of samples per frame"; | |
| return false; | |
| } | |
| return true; | |
| } | |
| bool AAudioWrapper::OptimizeBuffers() { | |
| RTC_LOG(INFO) << "OptimizeBuffers"; | |
| RTC_DCHECK(stream_); | |
| // Maximum number of frames that can be filled without blocking. | |
| RTC_LOG(INFO) << "max buffer capacity in frames: " | |
| << buffer_capacity_in_frames(); | |
| // Query the number of frames that the application should read or write at | |
| // one time for optimal performance. | |
| int32_t frames_per_burst = AAudioStream_getFramesPerBurst(stream_); | |
| RTC_LOG(INFO) << "frames per burst for optimal performance: " | |
| << frames_per_burst; | |
| frames_per_burst_ = frames_per_burst; | |
| if (direction() == AAUDIO_DIRECTION_INPUT) { | |
| // There is no point in calling setBufferSizeInFrames() for input streams | |
| // since it has no effect on the performance (latency in this case). | |
| return true; | |
| } | |
| // Set buffer size to same as burst size to guarantee lowest possible latency. | |
| // This size might change for output streams if underruns are detected and | |
| // automatic buffer adjustment is enabled. | |
| AAudioStream_setBufferSizeInFrames(stream_, frames_per_burst); | |
| int32_t buffer_size = AAudioStream_getBufferSizeInFrames(stream_); | |
| if (buffer_size != frames_per_burst) { | |
| RTC_LOG(LS_ERROR) << "Failed to use optimal buffer burst size"; | |
| return false; | |
| } | |
| // Maximum number of frames that can be filled without blocking. | |
| RTC_LOG(INFO) << "buffer burst size in frames: " << buffer_size; | |
| return true; | |
| } | |
| } // namespace webrtc |