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