henrika | 883d00f | 2018-03-16 10:09:49 +0100 | [diff] [blame] | 1 | /* |
| 2 | * Copyright (c) 2018 The WebRTC project authors. All Rights Reserved. |
| 3 | * |
| 4 | * Use of this source code is governed by a BSD-style license |
| 5 | * that can be found in the LICENSE file in the root of the source |
| 6 | * tree. An additional intellectual property rights grant can be found |
| 7 | * in the file PATENTS. All contributing project authors may |
| 8 | * be found in the AUTHORS file in the root of the source tree. |
| 9 | */ |
| 10 | |
| 11 | #include "modules/audio_device/android/aaudio_player.h" |
| 12 | |
Mirko Bonadei | 317a1f0 | 2019-09-17 17:06:18 +0200 | [diff] [blame^] | 13 | #include <memory> |
| 14 | |
henrika | 883d00f | 2018-03-16 10:09:49 +0100 | [diff] [blame] | 15 | #include "api/array_view.h" |
| 16 | #include "modules/audio_device/android/audio_manager.h" |
| 17 | #include "modules/audio_device/fine_audio_buffer.h" |
| 18 | #include "rtc_base/checks.h" |
| 19 | #include "rtc_base/logging.h" |
| 20 | |
| 21 | namespace webrtc { |
| 22 | |
| 23 | enum AudioDeviceMessageType : uint32_t { |
| 24 | kMessageOutputStreamDisconnected, |
| 25 | }; |
| 26 | |
| 27 | AAudioPlayer::AAudioPlayer(AudioManager* audio_manager) |
| 28 | : main_thread_(rtc::Thread::Current()), |
| 29 | aaudio_(audio_manager, AAUDIO_DIRECTION_OUTPUT, this) { |
| 30 | RTC_LOG(INFO) << "ctor"; |
Sebastian Jansson | c01367d | 2019-04-08 15:20:44 +0200 | [diff] [blame] | 31 | thread_checker_aaudio_.Detach(); |
henrika | 883d00f | 2018-03-16 10:09:49 +0100 | [diff] [blame] | 32 | } |
| 33 | |
| 34 | AAudioPlayer::~AAudioPlayer() { |
| 35 | RTC_LOG(INFO) << "dtor"; |
| 36 | RTC_DCHECK_RUN_ON(&main_thread_checker_); |
| 37 | Terminate(); |
| 38 | RTC_LOG(INFO) << "#detected underruns: " << underrun_count_; |
| 39 | } |
| 40 | |
| 41 | int AAudioPlayer::Init() { |
| 42 | RTC_LOG(INFO) << "Init"; |
| 43 | RTC_DCHECK_RUN_ON(&main_thread_checker_); |
henrika | 29e865a | 2018-04-24 13:22:31 +0200 | [diff] [blame] | 44 | if (aaudio_.audio_parameters().channels() == 2) { |
| 45 | RTC_DLOG(LS_WARNING) << "Stereo mode is enabled"; |
| 46 | } |
henrika | 883d00f | 2018-03-16 10:09:49 +0100 | [diff] [blame] | 47 | return 0; |
| 48 | } |
| 49 | |
| 50 | int AAudioPlayer::Terminate() { |
| 51 | RTC_LOG(INFO) << "Terminate"; |
| 52 | RTC_DCHECK_RUN_ON(&main_thread_checker_); |
| 53 | StopPlayout(); |
| 54 | return 0; |
| 55 | } |
| 56 | |
| 57 | int AAudioPlayer::InitPlayout() { |
| 58 | RTC_LOG(INFO) << "InitPlayout"; |
| 59 | RTC_DCHECK_RUN_ON(&main_thread_checker_); |
| 60 | RTC_DCHECK(!initialized_); |
| 61 | RTC_DCHECK(!playing_); |
| 62 | if (!aaudio_.Init()) { |
| 63 | return -1; |
| 64 | } |
| 65 | initialized_ = true; |
| 66 | return 0; |
| 67 | } |
| 68 | |
| 69 | bool AAudioPlayer::PlayoutIsInitialized() const { |
| 70 | RTC_DCHECK_RUN_ON(&main_thread_checker_); |
| 71 | return initialized_; |
| 72 | } |
| 73 | |
| 74 | int AAudioPlayer::StartPlayout() { |
| 75 | RTC_LOG(INFO) << "StartPlayout"; |
| 76 | RTC_DCHECK_RUN_ON(&main_thread_checker_); |
| 77 | RTC_DCHECK(!playing_); |
| 78 | if (!initialized_) { |
| 79 | RTC_DLOG(LS_WARNING) |
| 80 | << "Playout can not start since InitPlayout must succeed first"; |
| 81 | return 0; |
| 82 | } |
| 83 | if (fine_audio_buffer_) { |
| 84 | fine_audio_buffer_->ResetPlayout(); |
| 85 | } |
| 86 | if (!aaudio_.Start()) { |
| 87 | return -1; |
| 88 | } |
| 89 | underrun_count_ = aaudio_.xrun_count(); |
| 90 | first_data_callback_ = true; |
| 91 | playing_ = true; |
| 92 | return 0; |
| 93 | } |
| 94 | |
| 95 | int AAudioPlayer::StopPlayout() { |
| 96 | RTC_LOG(INFO) << "StopPlayout"; |
| 97 | RTC_DCHECK_RUN_ON(&main_thread_checker_); |
| 98 | if (!initialized_ || !playing_) { |
| 99 | return 0; |
| 100 | } |
| 101 | if (!aaudio_.Stop()) { |
| 102 | RTC_LOG(LS_ERROR) << "StopPlayout failed"; |
| 103 | return -1; |
| 104 | } |
Sebastian Jansson | c01367d | 2019-04-08 15:20:44 +0200 | [diff] [blame] | 105 | thread_checker_aaudio_.Detach(); |
henrika | 883d00f | 2018-03-16 10:09:49 +0100 | [diff] [blame] | 106 | initialized_ = false; |
| 107 | playing_ = false; |
| 108 | return 0; |
| 109 | } |
| 110 | |
| 111 | bool AAudioPlayer::Playing() const { |
| 112 | RTC_DCHECK_RUN_ON(&main_thread_checker_); |
| 113 | return playing_; |
| 114 | } |
| 115 | |
| 116 | void AAudioPlayer::AttachAudioBuffer(AudioDeviceBuffer* audioBuffer) { |
| 117 | RTC_DLOG(INFO) << "AttachAudioBuffer"; |
| 118 | RTC_DCHECK_RUN_ON(&main_thread_checker_); |
| 119 | audio_device_buffer_ = audioBuffer; |
| 120 | const AudioParameters audio_parameters = aaudio_.audio_parameters(); |
| 121 | audio_device_buffer_->SetPlayoutSampleRate(audio_parameters.sample_rate()); |
| 122 | audio_device_buffer_->SetPlayoutChannels(audio_parameters.channels()); |
| 123 | RTC_CHECK(audio_device_buffer_); |
| 124 | // Create a modified audio buffer class which allows us to ask for any number |
| 125 | // of samples (and not only multiple of 10ms) to match the optimal buffer |
henrika | 29e865a | 2018-04-24 13:22:31 +0200 | [diff] [blame] | 126 | // size per callback used by AAudio. |
Mirko Bonadei | 317a1f0 | 2019-09-17 17:06:18 +0200 | [diff] [blame^] | 127 | fine_audio_buffer_ = std::make_unique<FineAudioBuffer>(audio_device_buffer_); |
henrika | 883d00f | 2018-03-16 10:09:49 +0100 | [diff] [blame] | 128 | } |
| 129 | |
| 130 | int AAudioPlayer::SpeakerVolumeIsAvailable(bool& available) { |
| 131 | available = false; |
| 132 | return 0; |
| 133 | } |
| 134 | |
| 135 | void AAudioPlayer::OnErrorCallback(aaudio_result_t error) { |
| 136 | RTC_LOG(LS_ERROR) << "OnErrorCallback: " << AAudio_convertResultToText(error); |
| 137 | // TODO(henrika): investigate if we can use a thread checker here. Initial |
| 138 | // tests shows that this callback can sometimes be called on a unique thread |
| 139 | // but according to the documentation it should be on the same thread as the |
| 140 | // data callback. |
| 141 | // RTC_DCHECK_RUN_ON(&thread_checker_aaudio_); |
| 142 | if (aaudio_.stream_state() == AAUDIO_STREAM_STATE_DISCONNECTED) { |
| 143 | // The stream is disconnected and any attempt to use it will return |
| 144 | // AAUDIO_ERROR_DISCONNECTED. |
| 145 | RTC_LOG(WARNING) << "Output stream disconnected"; |
| 146 | // AAudio documentation states: "You should not close or reopen the stream |
| 147 | // from the callback, use another thread instead". A message is therefore |
| 148 | // sent to the main thread to do the restart operation. |
| 149 | RTC_DCHECK(main_thread_); |
| 150 | main_thread_->Post(RTC_FROM_HERE, this, kMessageOutputStreamDisconnected); |
| 151 | } |
| 152 | } |
| 153 | |
| 154 | aaudio_data_callback_result_t AAudioPlayer::OnDataCallback(void* audio_data, |
| 155 | int32_t num_frames) { |
| 156 | RTC_DCHECK_RUN_ON(&thread_checker_aaudio_); |
| 157 | // Log device id in first data callback to ensure that a valid device is |
| 158 | // utilized. |
| 159 | if (first_data_callback_) { |
| 160 | RTC_LOG(INFO) << "--- First output data callback: " |
| 161 | << "device id=" << aaudio_.device_id(); |
| 162 | first_data_callback_ = false; |
| 163 | } |
| 164 | |
| 165 | // Check if the underrun count has increased. If it has, increase the buffer |
| 166 | // size by adding the size of a burst. It will reduce the risk of underruns |
| 167 | // at the expense of an increased latency. |
| 168 | // TODO(henrika): enable possibility to disable and/or tune the algorithm. |
| 169 | const int32_t underrun_count = aaudio_.xrun_count(); |
| 170 | if (underrun_count > underrun_count_) { |
| 171 | RTC_LOG(LS_ERROR) << "Underrun detected: " << underrun_count; |
| 172 | underrun_count_ = underrun_count; |
| 173 | aaudio_.IncreaseOutputBufferSize(); |
| 174 | } |
| 175 | |
| 176 | // Estimate latency between writing an audio frame to the output stream and |
| 177 | // the time that same frame is played out on the output audio device. |
| 178 | latency_millis_ = aaudio_.EstimateLatencyMillis(); |
| 179 | // TODO(henrika): use for development only. |
| 180 | if (aaudio_.frames_written() % (1000 * aaudio_.frames_per_burst()) == 0) { |
| 181 | RTC_DLOG(INFO) << "output latency: " << latency_millis_ |
| 182 | << ", num_frames: " << num_frames; |
| 183 | } |
| 184 | |
| 185 | // Read audio data from the WebRTC source using the FineAudioBuffer object |
| 186 | // and write that data into |audio_data| to be played out by AAudio. |
henrika | 883d00f | 2018-03-16 10:09:49 +0100 | [diff] [blame] | 187 | // Prime output with zeros during a short initial phase to avoid distortion. |
| 188 | // TODO(henrika): do more work to figure out of if the initial forced silence |
| 189 | // period is really needed. |
| 190 | if (aaudio_.frames_written() < 50 * aaudio_.frames_per_burst()) { |
henrika | 8d7393b | 2018-04-19 13:40:15 +0200 | [diff] [blame] | 191 | const size_t num_bytes = |
| 192 | sizeof(int16_t) * aaudio_.samples_per_frame() * num_frames; |
henrika | 883d00f | 2018-03-16 10:09:49 +0100 | [diff] [blame] | 193 | memset(audio_data, 0, num_bytes); |
| 194 | } else { |
| 195 | fine_audio_buffer_->GetPlayoutData( |
henrika | 29e865a | 2018-04-24 13:22:31 +0200 | [diff] [blame] | 196 | rtc::MakeArrayView(static_cast<int16_t*>(audio_data), |
| 197 | aaudio_.samples_per_frame() * num_frames), |
henrika | 883d00f | 2018-03-16 10:09:49 +0100 | [diff] [blame] | 198 | static_cast<int>(latency_millis_ + 0.5)); |
| 199 | } |
| 200 | |
| 201 | // TODO(henrika): possibly add trace here to be included in systrace. |
| 202 | // See https://developer.android.com/studio/profile/systrace-commandline.html. |
| 203 | return AAUDIO_CALLBACK_RESULT_CONTINUE; |
| 204 | } |
| 205 | |
| 206 | void AAudioPlayer::OnMessage(rtc::Message* msg) { |
| 207 | RTC_DCHECK_RUN_ON(&main_thread_checker_); |
| 208 | switch (msg->message_id) { |
| 209 | case kMessageOutputStreamDisconnected: |
| 210 | HandleStreamDisconnected(); |
| 211 | break; |
| 212 | } |
| 213 | } |
| 214 | |
| 215 | void AAudioPlayer::HandleStreamDisconnected() { |
| 216 | RTC_DCHECK_RUN_ON(&main_thread_checker_); |
| 217 | RTC_DLOG(INFO) << "HandleStreamDisconnected"; |
| 218 | if (!initialized_ || !playing_) { |
| 219 | return; |
| 220 | } |
| 221 | // Perform a restart by first closing the disconnected stream and then start |
| 222 | // a new stream; this time using the new (preferred) audio output device. |
henrika | 883d00f | 2018-03-16 10:09:49 +0100 | [diff] [blame] | 223 | StopPlayout(); |
| 224 | InitPlayout(); |
| 225 | StartPlayout(); |
| 226 | } |
| 227 | } // namespace webrtc |