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/win/core_audio_output_win.h"
12
13 #include <memory>
14
15 #include "modules/audio_device/audio_device_buffer.h"
16 #include "modules/audio_device/fine_audio_buffer.h"
17 #include "rtc_base/bind.h"
18 #include "rtc_base/checks.h"
19 #include "rtc_base/logging.h"
20 #include "rtc_base/time_utils.h"
21
22 using Microsoft::WRL::ComPtr;
23
24 namespace webrtc {
25 namespace webrtc_win {
26
CoreAudioOutput(bool automatic_restart)27 CoreAudioOutput::CoreAudioOutput(bool automatic_restart)
28 : CoreAudioBase(
29 CoreAudioBase::Direction::kOutput,
30 automatic_restart,
31 [this](uint64_t freq) { return OnDataCallback(freq); },
__anon3b1fe9ae0202(ErrorType err) 32 [this](ErrorType err) { return OnErrorCallback(err); }) {
33 RTC_DLOG(INFO) << __FUNCTION__;
34 RTC_DCHECK_RUN_ON(&thread_checker_);
35 thread_checker_audio_.Detach();
36 }
37
~CoreAudioOutput()38 CoreAudioOutput::~CoreAudioOutput() {
39 RTC_DLOG(INFO) << __FUNCTION__;
40 RTC_DCHECK_RUN_ON(&thread_checker_);
41 Terminate();
42 }
43
Init()44 int CoreAudioOutput::Init() {
45 RTC_DLOG(INFO) << __FUNCTION__;
46 RTC_DCHECK_RUN_ON(&thread_checker_);
47 return 0;
48 }
49
Terminate()50 int CoreAudioOutput::Terminate() {
51 RTC_DLOG(INFO) << __FUNCTION__;
52 RTC_DCHECK_RUN_ON(&thread_checker_);
53 StopPlayout();
54 return 0;
55 }
56
NumDevices() const57 int CoreAudioOutput::NumDevices() const {
58 RTC_DCHECK_RUN_ON(&thread_checker_);
59 return core_audio_utility::NumberOfActiveDevices(eRender);
60 }
61
SetDevice(int index)62 int CoreAudioOutput::SetDevice(int index) {
63 RTC_DLOG(INFO) << __FUNCTION__ << ": " << index;
64 RTC_DCHECK_GE(index, 0);
65 RTC_DCHECK_RUN_ON(&thread_checker_);
66 return CoreAudioBase::SetDevice(index);
67 }
68
SetDevice(AudioDeviceModule::WindowsDeviceType device)69 int CoreAudioOutput::SetDevice(AudioDeviceModule::WindowsDeviceType device) {
70 RTC_DLOG(INFO) << __FUNCTION__ << ": "
71 << ((device == AudioDeviceModule::kDefaultDevice)
72 ? "Default"
73 : "DefaultCommunication");
74 RTC_DCHECK_RUN_ON(&thread_checker_);
75 return SetDevice((device == AudioDeviceModule::kDefaultDevice) ? 0 : 1);
76 }
77
DeviceName(int index,std::string * name,std::string * guid)78 int CoreAudioOutput::DeviceName(int index,
79 std::string* name,
80 std::string* guid) {
81 RTC_DLOG(INFO) << __FUNCTION__ << ": " << index;
82 RTC_DCHECK_RUN_ON(&thread_checker_);
83 RTC_DCHECK(name);
84 return CoreAudioBase::DeviceName(index, name, guid);
85 }
86
AttachAudioBuffer(AudioDeviceBuffer * audio_buffer)87 void CoreAudioOutput::AttachAudioBuffer(AudioDeviceBuffer* audio_buffer) {
88 RTC_DLOG(INFO) << __FUNCTION__;
89 RTC_DCHECK_RUN_ON(&thread_checker_);
90 audio_device_buffer_ = audio_buffer;
91 }
92
PlayoutIsInitialized() const93 bool CoreAudioOutput::PlayoutIsInitialized() const {
94 RTC_DLOG(INFO) << __FUNCTION__;
95 RTC_DCHECK_RUN_ON(&thread_checker_);
96 return initialized_;
97 }
98
InitPlayout()99 int CoreAudioOutput::InitPlayout() {
100 RTC_DLOG(INFO) << __FUNCTION__ << ": " << IsRestarting();
101 RTC_DCHECK(!initialized_);
102 RTC_DCHECK(!Playing());
103 RTC_DCHECK(!audio_render_client_);
104
105 // Creates an IAudioClient instance and stores the valid interface pointer in
106 // |audio_client3_|, |audio_client2_|, or |audio_client_| depending on
107 // platform support. The base class will use optimal output parameters and do
108 // an event driven shared mode initialization. The utilized format will be
109 // stored in |format_| and can be used for configuration and allocation of
110 // audio buffers.
111 if (!CoreAudioBase::Init()) {
112 return -1;
113 }
114 RTC_DCHECK(audio_client_);
115
116 // Configure the playout side of the audio device buffer using |format_|
117 // after a trivial sanity check of the format structure.
118 RTC_DCHECK(audio_device_buffer_);
119 WAVEFORMATEX* format = &format_.Format;
120 RTC_DCHECK_EQ(format->wFormatTag, WAVE_FORMAT_EXTENSIBLE);
121 audio_device_buffer_->SetPlayoutSampleRate(format->nSamplesPerSec);
122 audio_device_buffer_->SetPlayoutChannels(format->nChannels);
123
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
126 // buffer size per callback used by Core Audio.
127 // TODO(henrika): can we share one FineAudioBuffer with the input side?
128 fine_audio_buffer_ = std::make_unique<FineAudioBuffer>(audio_device_buffer_);
129
130 // Create an IAudioRenderClient for an initialized IAudioClient.
131 // The IAudioRenderClient interface enables us to write output data to
132 // a rendering endpoint buffer.
133 ComPtr<IAudioRenderClient> audio_render_client =
134 core_audio_utility::CreateRenderClient(audio_client_.Get());
135 if (!audio_render_client.Get()) {
136 return -1;
137 }
138
139 ComPtr<IAudioClock> audio_clock =
140 core_audio_utility::CreateAudioClock(audio_client_.Get());
141 if (!audio_clock.Get()) {
142 return -1;
143 }
144
145 // Store valid COM interfaces.
146 audio_render_client_ = audio_render_client;
147 audio_clock_ = audio_clock;
148
149 initialized_ = true;
150 return 0;
151 }
152
StartPlayout()153 int CoreAudioOutput::StartPlayout() {
154 RTC_DLOG(INFO) << __FUNCTION__ << ": " << IsRestarting();
155 RTC_DCHECK(!Playing());
156 RTC_DCHECK(fine_audio_buffer_);
157 RTC_DCHECK(audio_device_buffer_);
158 if (!initialized_) {
159 RTC_DLOG(LS_WARNING)
160 << "Playout can not start since InitPlayout must succeed first";
161 }
162
163 fine_audio_buffer_->ResetPlayout();
164 if (!IsRestarting()) {
165 audio_device_buffer_->StartPlayout();
166 }
167
168 if (!core_audio_utility::FillRenderEndpointBufferWithSilence(
169 audio_client_.Get(), audio_render_client_.Get())) {
170 RTC_LOG(LS_WARNING) << "Failed to prepare output endpoint with silence";
171 }
172
173 num_frames_written_ = endpoint_buffer_size_frames_;
174
175 if (!Start()) {
176 return -1;
177 }
178
179 is_active_ = true;
180 return 0;
181 }
182
StopPlayout()183 int CoreAudioOutput::StopPlayout() {
184 RTC_DLOG(INFO) << __FUNCTION__ << ": " << IsRestarting();
185 if (!initialized_) {
186 return 0;
187 }
188
189 // Release resources allocated in InitPlayout() and then return if this
190 // method is called without any active output audio.
191 if (!Playing()) {
192 RTC_DLOG(WARNING) << "No output stream is active";
193 ReleaseCOMObjects();
194 initialized_ = false;
195 return 0;
196 }
197
198 if (!Stop()) {
199 RTC_LOG(LS_ERROR) << "StopPlayout failed";
200 return -1;
201 }
202
203 if (!IsRestarting()) {
204 RTC_DCHECK(audio_device_buffer_);
205 audio_device_buffer_->StopPlayout();
206 }
207
208 // Release all allocated resources to allow for a restart without
209 // intermediate destruction.
210 ReleaseCOMObjects();
211
212 initialized_ = false;
213 is_active_ = false;
214 return 0;
215 }
216
Playing()217 bool CoreAudioOutput::Playing() {
218 RTC_DLOG(INFO) << __FUNCTION__ << ": " << is_active_;
219 return is_active_;
220 }
221
222 // TODO(henrika): finalize support of audio session volume control. As is, we
223 // are not compatible with the old ADM implementation since it allows accessing
224 // the volume control with any active audio output stream.
VolumeIsAvailable(bool * available)225 int CoreAudioOutput::VolumeIsAvailable(bool* available) {
226 RTC_DLOG(INFO) << __FUNCTION__;
227 RTC_DCHECK_RUN_ON(&thread_checker_);
228 return IsVolumeControlAvailable(available) ? 0 : -1;
229 }
230
231 // Triggers the restart sequence. Only used for testing purposes to emulate
232 // a real event where e.g. an active output device is removed.
RestartPlayout()233 int CoreAudioOutput::RestartPlayout() {
234 RTC_DLOG(INFO) << __FUNCTION__;
235 RTC_DCHECK_RUN_ON(&thread_checker_);
236 if (!Playing()) {
237 return 0;
238 }
239 if (!Restart()) {
240 RTC_LOG(LS_ERROR) << "RestartPlayout failed";
241 return -1;
242 }
243 return 0;
244 }
245
Restarting() const246 bool CoreAudioOutput::Restarting() const {
247 RTC_DLOG(INFO) << __FUNCTION__;
248 RTC_DCHECK_RUN_ON(&thread_checker_);
249 return IsRestarting();
250 }
251
SetSampleRate(uint32_t sample_rate)252 int CoreAudioOutput::SetSampleRate(uint32_t sample_rate) {
253 RTC_DLOG(INFO) << __FUNCTION__;
254 RTC_DCHECK_RUN_ON(&thread_checker_);
255 sample_rate_ = sample_rate;
256 return 0;
257 }
258
ReleaseCOMObjects()259 void CoreAudioOutput::ReleaseCOMObjects() {
260 RTC_DLOG(INFO) << __FUNCTION__;
261 CoreAudioBase::ReleaseCOMObjects();
262 if (audio_render_client_.Get()) {
263 audio_render_client_.Reset();
264 }
265 }
266
OnErrorCallback(ErrorType error)267 bool CoreAudioOutput::OnErrorCallback(ErrorType error) {
268 RTC_DLOG(INFO) << __FUNCTION__ << ": " << as_integer(error);
269 RTC_DCHECK_RUN_ON(&thread_checker_audio_);
270 if (!initialized_ || !Playing()) {
271 return true;
272 }
273
274 if (error == CoreAudioBase::ErrorType::kStreamDisconnected) {
275 HandleStreamDisconnected();
276 } else {
277 RTC_DLOG(WARNING) << "Unsupported error type";
278 }
279 return true;
280 }
281
OnDataCallback(uint64_t device_frequency)282 bool CoreAudioOutput::OnDataCallback(uint64_t device_frequency) {
283 RTC_DCHECK_RUN_ON(&thread_checker_audio_);
284 if (num_data_callbacks_ == 0) {
285 RTC_LOG(INFO) << "--- Output audio stream is alive ---";
286 }
287 // Get the padding value which indicates the amount of valid unread data that
288 // the endpoint buffer currently contains.
289 UINT32 num_unread_frames = 0;
290 _com_error error = audio_client_->GetCurrentPadding(&num_unread_frames);
291 if (error.Error() == AUDCLNT_E_DEVICE_INVALIDATED) {
292 // Avoid breaking the thread loop implicitly by returning false and return
293 // true instead for AUDCLNT_E_DEVICE_INVALIDATED even it is a valid error
294 // message. We will use notifications about device changes instead to stop
295 // data callbacks and attempt to restart streaming .
296 RTC_DLOG(LS_ERROR) << "AUDCLNT_E_DEVICE_INVALIDATED";
297 return true;
298 }
299 if (FAILED(error.Error())) {
300 RTC_LOG(LS_ERROR) << "IAudioClient::GetCurrentPadding failed: "
301 << core_audio_utility::ErrorToString(error);
302 return false;
303 }
304
305 // Contains how much new data we can write to the buffer without the risk of
306 // overwriting previously written data that the audio engine has not yet read
307 // from the buffer. I.e., it is the maximum buffer size we can request when
308 // calling IAudioRenderClient::GetBuffer().
309 UINT32 num_requested_frames =
310 endpoint_buffer_size_frames_ - num_unread_frames;
311 if (num_requested_frames == 0) {
312 RTC_DLOG(LS_WARNING)
313 << "Audio thread is signaled but no new audio samples are needed";
314 return true;
315 }
316
317 // Request all available space in the rendering endpoint buffer into which the
318 // client can later write an audio packet.
319 uint8_t* audio_data;
320 error = audio_render_client_->GetBuffer(num_requested_frames, &audio_data);
321 if (FAILED(error.Error())) {
322 RTC_LOG(LS_ERROR) << "IAudioRenderClient::GetBuffer failed: "
323 << core_audio_utility::ErrorToString(error);
324 return false;
325 }
326
327 // Update output delay estimate but only about once per second to save
328 // resources. The estimate is usually stable.
329 if (num_data_callbacks_ % 100 == 0) {
330 // TODO(henrika): note that FineAudioBuffer adds latency as well.
331 latency_ms_ = EstimateOutputLatencyMillis(device_frequency);
332 if (num_data_callbacks_ % 500 == 0) {
333 RTC_DLOG(INFO) << "latency: " << latency_ms_;
334 }
335 }
336
337 // Get audio data from WebRTC and write it to the allocated buffer in
338 // |audio_data|. The playout latency is not updated for each callback.
339 fine_audio_buffer_->GetPlayoutData(
340 rtc::MakeArrayView(reinterpret_cast<int16_t*>(audio_data),
341 num_requested_frames * format_.Format.nChannels),
342 latency_ms_);
343
344 // Release the buffer space acquired in IAudioRenderClient::GetBuffer.
345 error = audio_render_client_->ReleaseBuffer(num_requested_frames, 0);
346 if (FAILED(error.Error())) {
347 RTC_LOG(LS_ERROR) << "IAudioRenderClient::ReleaseBuffer failed: "
348 << core_audio_utility::ErrorToString(error);
349 return false;
350 }
351
352 num_frames_written_ += num_requested_frames;
353 ++num_data_callbacks_;
354
355 return true;
356 }
357
358 // TODO(henrika): IAudioClock2::GetDevicePosition could perhaps be used here
359 // instead. Tried it once, but it crashed for capture devices.
EstimateOutputLatencyMillis(uint64_t device_frequency)360 int CoreAudioOutput::EstimateOutputLatencyMillis(uint64_t device_frequency) {
361 UINT64 position = 0;
362 UINT64 qpc_position = 0;
363 int delay_ms = 0;
364 // Get the device position through output parameter |position|. This is the
365 // stream position of the sample that is currently playing through the
366 // speakers.
367 _com_error error = audio_clock_->GetPosition(&position, &qpc_position);
368 if (error.Error() == S_OK) {
369 // Number of frames already played out through the speaker.
370 const uint64_t num_played_out_frames =
371 format_.Format.nSamplesPerSec * position / device_frequency;
372
373 // Number of frames that have been written to the buffer but not yet
374 // played out corresponding to the estimated latency measured in number
375 // of audio frames.
376 const uint64_t delay_frames = num_frames_written_ - num_played_out_frames;
377
378 // Convert latency in number of frames into milliseconds.
379 webrtc::TimeDelta delay =
380 webrtc::TimeDelta::Micros(delay_frames * rtc::kNumMicrosecsPerSec /
381 format_.Format.nSamplesPerSec);
382 delay_ms = delay.ms();
383 }
384 return delay_ms;
385 }
386
387 // Called from OnErrorCallback() when error type is kStreamDisconnected.
388 // Note that this method is called on the audio thread and the internal restart
389 // sequence is also executed on that same thread. The audio thread is therefore
390 // not stopped during restart. Such a scheme also makes the restart process less
391 // complex.
392 // Note that, none of the called methods are thread checked since they can also
393 // be called on the main thread. Thread checkers are instead added on one layer
394 // above (in audio_device_module.cc) which ensures that the public API is thread
395 // safe.
396 // TODO(henrika): add more details.
HandleStreamDisconnected()397 bool CoreAudioOutput::HandleStreamDisconnected() {
398 RTC_DLOG(INFO) << "<<<--- " << __FUNCTION__;
399 RTC_DCHECK_RUN_ON(&thread_checker_audio_);
400 RTC_DCHECK(automatic_restart());
401
402 if (StopPlayout() != 0) {
403 return false;
404 }
405
406 if (!SwitchDeviceIfNeeded()) {
407 return false;
408 }
409
410 if (InitPlayout() != 0) {
411 return false;
412 }
413 if (StartPlayout() != 0) {
414 return false;
415 }
416
417 RTC_DLOG(INFO) << __FUNCTION__ << " --->>>";
418 return true;
419 }
420
421 } // namespace webrtc_win
422
423 } // namespace webrtc
424