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