• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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