229 lines
7.6 KiB
C++
229 lines
7.6 KiB
C++
/*
|
|
* 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_player.h"
|
|
|
|
#include <memory>
|
|
|
|
#include "api/array_view.h"
|
|
#include "modules/audio_device/android/audio_manager.h"
|
|
#include "modules/audio_device/fine_audio_buffer.h"
|
|
#include "rtc_base/checks.h"
|
|
#include "rtc_base/logging.h"
|
|
|
|
namespace webrtc {
|
|
|
|
enum AudioDeviceMessageType : uint32_t {
|
|
kMessageOutputStreamDisconnected,
|
|
};
|
|
|
|
AAudioPlayer::AAudioPlayer(AudioManager* audio_manager)
|
|
: main_thread_(rtc::Thread::Current()),
|
|
aaudio_(audio_manager, AAUDIO_DIRECTION_OUTPUT, this) {
|
|
RTC_LOG(INFO) << "ctor";
|
|
thread_checker_aaudio_.Detach();
|
|
}
|
|
|
|
AAudioPlayer::~AAudioPlayer() {
|
|
RTC_LOG(INFO) << "dtor";
|
|
RTC_DCHECK_RUN_ON(&main_thread_checker_);
|
|
Terminate();
|
|
RTC_LOG(INFO) << "#detected underruns: " << underrun_count_;
|
|
}
|
|
|
|
int AAudioPlayer::Init() {
|
|
RTC_LOG(INFO) << "Init";
|
|
RTC_DCHECK_RUN_ON(&main_thread_checker_);
|
|
if (aaudio_.audio_parameters().channels() == 2) {
|
|
RTC_DLOG(LS_WARNING) << "Stereo mode is enabled";
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int AAudioPlayer::Terminate() {
|
|
RTC_LOG(INFO) << "Terminate";
|
|
RTC_DCHECK_RUN_ON(&main_thread_checker_);
|
|
StopPlayout();
|
|
return 0;
|
|
}
|
|
|
|
int AAudioPlayer::InitPlayout() {
|
|
RTC_LOG(INFO) << "InitPlayout";
|
|
RTC_DCHECK_RUN_ON(&main_thread_checker_);
|
|
RTC_DCHECK(!initialized_);
|
|
RTC_DCHECK(!playing_);
|
|
if (!aaudio_.Init()) {
|
|
return -1;
|
|
}
|
|
initialized_ = true;
|
|
return 0;
|
|
}
|
|
|
|
bool AAudioPlayer::PlayoutIsInitialized() const {
|
|
RTC_DCHECK_RUN_ON(&main_thread_checker_);
|
|
return initialized_;
|
|
}
|
|
|
|
int AAudioPlayer::StartPlayout() {
|
|
RTC_LOG(INFO) << "StartPlayout";
|
|
RTC_DCHECK_RUN_ON(&main_thread_checker_);
|
|
RTC_DCHECK(!playing_);
|
|
if (!initialized_) {
|
|
RTC_DLOG(LS_WARNING)
|
|
<< "Playout can not start since InitPlayout must succeed first";
|
|
return 0;
|
|
}
|
|
if (fine_audio_buffer_) {
|
|
fine_audio_buffer_->ResetPlayout();
|
|
}
|
|
if (!aaudio_.Start()) {
|
|
return -1;
|
|
}
|
|
underrun_count_ = aaudio_.xrun_count();
|
|
first_data_callback_ = true;
|
|
playing_ = true;
|
|
return 0;
|
|
}
|
|
|
|
int AAudioPlayer::StopPlayout() {
|
|
RTC_LOG(INFO) << "StopPlayout";
|
|
RTC_DCHECK_RUN_ON(&main_thread_checker_);
|
|
if (!initialized_ || !playing_) {
|
|
return 0;
|
|
}
|
|
if (!aaudio_.Stop()) {
|
|
RTC_LOG(LS_ERROR) << "StopPlayout failed";
|
|
return -1;
|
|
}
|
|
thread_checker_aaudio_.Detach();
|
|
initialized_ = false;
|
|
playing_ = false;
|
|
return 0;
|
|
}
|
|
|
|
bool AAudioPlayer::Playing() const {
|
|
RTC_DCHECK_RUN_ON(&main_thread_checker_);
|
|
return playing_;
|
|
}
|
|
|
|
void AAudioPlayer::AttachAudioBuffer(AudioDeviceBuffer* audioBuffer) {
|
|
RTC_DLOG(INFO) << "AttachAudioBuffer";
|
|
RTC_DCHECK_RUN_ON(&main_thread_checker_);
|
|
audio_device_buffer_ = audioBuffer;
|
|
const AudioParameters audio_parameters = aaudio_.audio_parameters();
|
|
audio_device_buffer_->SetPlayoutSampleRate(audio_parameters.sample_rate());
|
|
audio_device_buffer_->SetPlayoutChannels(audio_parameters.channels());
|
|
RTC_CHECK(audio_device_buffer_);
|
|
// Create a modified audio buffer class which allows us to ask for any number
|
|
// of samples (and not only multiple of 10ms) to match the optimal buffer
|
|
// size per callback used by AAudio.
|
|
fine_audio_buffer_ = std::make_unique<FineAudioBuffer>(audio_device_buffer_);
|
|
}
|
|
|
|
int AAudioPlayer::SpeakerVolumeIsAvailable(bool& available) {
|
|
available = false;
|
|
return 0;
|
|
}
|
|
|
|
void AAudioPlayer::OnErrorCallback(aaudio_result_t error) {
|
|
RTC_LOG(LS_ERROR) << "OnErrorCallback: " << AAudio_convertResultToText(error);
|
|
// TODO(henrika): investigate if we can use a thread checker here. Initial
|
|
// tests shows that this callback can sometimes be called on a unique thread
|
|
// but according to the documentation it should be on the same thread as the
|
|
// data callback.
|
|
// RTC_DCHECK_RUN_ON(&thread_checker_aaudio_);
|
|
if (aaudio_.stream_state() == AAUDIO_STREAM_STATE_DISCONNECTED) {
|
|
// The stream is disconnected and any attempt to use it will return
|
|
// AAUDIO_ERROR_DISCONNECTED.
|
|
RTC_LOG(WARNING) << "Output stream disconnected";
|
|
// AAudio documentation states: "You should not close or reopen the stream
|
|
// from the callback, use another thread instead". A message is therefore
|
|
// sent to the main thread to do the restart operation.
|
|
RTC_DCHECK(main_thread_);
|
|
main_thread_->Post(RTC_FROM_HERE, this, kMessageOutputStreamDisconnected);
|
|
}
|
|
}
|
|
|
|
aaudio_data_callback_result_t AAudioPlayer::OnDataCallback(void* audio_data,
|
|
int32_t num_frames) {
|
|
RTC_DCHECK_RUN_ON(&thread_checker_aaudio_);
|
|
// Log device id in first data callback to ensure that a valid device is
|
|
// utilized.
|
|
if (first_data_callback_) {
|
|
RTC_LOG(INFO) << "--- First output data callback: "
|
|
"device id="
|
|
<< aaudio_.device_id();
|
|
first_data_callback_ = false;
|
|
}
|
|
|
|
// Check if the underrun count has increased. If it has, increase the buffer
|
|
// size by adding the size of a burst. It will reduce the risk of underruns
|
|
// at the expense of an increased latency.
|
|
// TODO(henrika): enable possibility to disable and/or tune the algorithm.
|
|
const int32_t underrun_count = aaudio_.xrun_count();
|
|
if (underrun_count > underrun_count_) {
|
|
RTC_LOG(LS_ERROR) << "Underrun detected: " << underrun_count;
|
|
underrun_count_ = underrun_count;
|
|
aaudio_.IncreaseOutputBufferSize();
|
|
}
|
|
|
|
// Estimate latency between writing an audio frame to the output stream and
|
|
// the time that same frame is played out on the output audio device.
|
|
latency_millis_ = aaudio_.EstimateLatencyMillis();
|
|
// TODO(henrika): use for development only.
|
|
if (aaudio_.frames_written() % (1000 * aaudio_.frames_per_burst()) == 0) {
|
|
RTC_DLOG(INFO) << "output latency: " << latency_millis_
|
|
<< ", num_frames: " << num_frames;
|
|
}
|
|
|
|
// Read audio data from the WebRTC source using the FineAudioBuffer object
|
|
// and write that data into |audio_data| to be played out by AAudio.
|
|
// Prime output with zeros during a short initial phase to avoid distortion.
|
|
// TODO(henrika): do more work to figure out of if the initial forced silence
|
|
// period is really needed.
|
|
if (aaudio_.frames_written() < 50 * aaudio_.frames_per_burst()) {
|
|
const size_t num_bytes =
|
|
sizeof(int16_t) * aaudio_.samples_per_frame() * num_frames;
|
|
memset(audio_data, 0, num_bytes);
|
|
} else {
|
|
fine_audio_buffer_->GetPlayoutData(
|
|
rtc::MakeArrayView(static_cast<int16_t*>(audio_data),
|
|
aaudio_.samples_per_frame() * num_frames),
|
|
static_cast<int>(latency_millis_ + 0.5));
|
|
}
|
|
|
|
// TODO(henrika): possibly add trace here to be included in systrace.
|
|
// See https://developer.android.com/studio/profile/systrace-commandline.html.
|
|
return AAUDIO_CALLBACK_RESULT_CONTINUE;
|
|
}
|
|
|
|
void AAudioPlayer::OnMessage(rtc::Message* msg) {
|
|
RTC_DCHECK_RUN_ON(&main_thread_checker_);
|
|
switch (msg->message_id) {
|
|
case kMessageOutputStreamDisconnected:
|
|
HandleStreamDisconnected();
|
|
break;
|
|
}
|
|
}
|
|
|
|
void AAudioPlayer::HandleStreamDisconnected() {
|
|
RTC_DCHECK_RUN_ON(&main_thread_checker_);
|
|
RTC_DLOG(INFO) << "HandleStreamDisconnected";
|
|
if (!initialized_ || !playing_) {
|
|
return;
|
|
}
|
|
// Perform a restart by first closing the disconnected stream and then start
|
|
// a new stream; this time using the new (preferred) audio output device.
|
|
StopPlayout();
|
|
InitPlayout();
|
|
StartPlayout();
|
|
}
|
|
} // namespace webrtc
|