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_base_win.h"
12
13 #include <memory>
14 #include <string>
15
16 #include "absl/strings/string_view.h"
17 #include "modules/audio_device/audio_device_buffer.h"
18 #include "rtc_base/arraysize.h"
19 #include "rtc_base/checks.h"
20 #include "rtc_base/logging.h"
21 #include "rtc_base/numerics/safe_conversions.h"
22 #include "rtc_base/platform_thread.h"
23 #include "rtc_base/time_utils.h"
24 #include "rtc_base/win/scoped_com_initializer.h"
25 #include "rtc_base/win/windows_version.h"
26
27 using Microsoft::WRL::ComPtr;
28
29 namespace webrtc {
30 namespace webrtc_win {
31 namespace {
32
33 // Even if the device supports low latency and even if IAudioClient3 can be
34 // used (requires Win10 or higher), we currently disable any attempts to
35 // initialize the client for low-latency.
36 // TODO(henrika): more research is needed before we can enable low-latency.
37 const bool kEnableLowLatencyIfSupported = false;
38
39 // Each unit of reference time is 100 nanoseconds, hence `kReftimesPerSec`
40 // corresponds to one second.
41 // TODO(henrika): possibly add usage in Init().
42 // const REFERENCE_TIME kReferenceTimesPerSecond = 10000000;
43
44 enum DefaultDeviceType {
45 kUndefined = -1,
46 kDefault = 0,
47 kDefaultCommunications = 1,
48 kDefaultDeviceTypeMaxCount = kDefaultCommunications + 1,
49 };
50
DirectionToString(CoreAudioBase::Direction direction)51 const char* DirectionToString(CoreAudioBase::Direction direction) {
52 switch (direction) {
53 case CoreAudioBase::Direction::kOutput:
54 return "Output";
55 case CoreAudioBase::Direction::kInput:
56 return "Input";
57 default:
58 return "Unkown";
59 }
60 }
61
RoleToString(const ERole role)62 const char* RoleToString(const ERole role) {
63 switch (role) {
64 case eConsole:
65 return "Console";
66 case eMultimedia:
67 return "Multimedia";
68 case eCommunications:
69 return "Communications";
70 default:
71 return "Unsupported";
72 }
73 }
74
IndexToString(int index)75 std::string IndexToString(int index) {
76 std::string ss = std::to_string(index);
77 switch (index) {
78 case kDefault:
79 ss += " (Default)";
80 break;
81 case kDefaultCommunications:
82 ss += " (Communications)";
83 break;
84 default:
85 break;
86 }
87 return ss;
88 }
89
SessionStateToString(AudioSessionState state)90 const char* SessionStateToString(AudioSessionState state) {
91 switch (state) {
92 case AudioSessionStateActive:
93 return "Active";
94 case AudioSessionStateInactive:
95 return "Inactive";
96 case AudioSessionStateExpired:
97 return "Expired";
98 default:
99 return "Invalid";
100 }
101 }
102
SessionDisconnectReasonToString(AudioSessionDisconnectReason reason)103 const char* SessionDisconnectReasonToString(
104 AudioSessionDisconnectReason reason) {
105 switch (reason) {
106 case DisconnectReasonDeviceRemoval:
107 return "DeviceRemoval";
108 case DisconnectReasonServerShutdown:
109 return "ServerShutdown";
110 case DisconnectReasonFormatChanged:
111 return "FormatChanged";
112 case DisconnectReasonSessionLogoff:
113 return "SessionLogoff";
114 case DisconnectReasonSessionDisconnected:
115 return "Disconnected";
116 case DisconnectReasonExclusiveModeOverride:
117 return "ExclusiveModeOverride";
118 default:
119 return "Invalid";
120 }
121 }
122
123 // Returns true if the selected audio device supports low latency, i.e, if it
124 // is possible to initialize the engine using periods less than the default
125 // period (10ms).
IsLowLatencySupported(IAudioClient3 * client3,const WAVEFORMATEXTENSIBLE * format,uint32_t * min_period_in_frames)126 bool IsLowLatencySupported(IAudioClient3* client3,
127 const WAVEFORMATEXTENSIBLE* format,
128 uint32_t* min_period_in_frames) {
129 RTC_DLOG(LS_INFO) << __FUNCTION__;
130
131 // Get the range of periodicities supported by the engine for the specified
132 // stream format.
133 uint32_t default_period = 0;
134 uint32_t fundamental_period = 0;
135 uint32_t min_period = 0;
136 uint32_t max_period = 0;
137 if (FAILED(core_audio_utility::GetSharedModeEnginePeriod(
138 client3, format, &default_period, &fundamental_period, &min_period,
139 &max_period))) {
140 return false;
141 }
142
143 // Low latency is supported if the shortest allowed period is less than the
144 // default engine period.
145 // TODO(henrika): verify that this assumption is correct.
146 const bool low_latency = min_period < default_period;
147 RTC_LOG(LS_INFO) << "low_latency: " << low_latency;
148 *min_period_in_frames = low_latency ? min_period : 0;
149 return low_latency;
150 }
151
152 } // namespace
153
CoreAudioBase(Direction direction,bool automatic_restart,OnDataCallback data_callback,OnErrorCallback error_callback)154 CoreAudioBase::CoreAudioBase(Direction direction,
155 bool automatic_restart,
156 OnDataCallback data_callback,
157 OnErrorCallback error_callback)
158 : format_(),
159 direction_(direction),
160 automatic_restart_(automatic_restart),
161 on_data_callback_(data_callback),
162 on_error_callback_(error_callback),
163 device_index_(kUndefined),
164 is_restarting_(false) {
165 RTC_DLOG(LS_INFO) << __FUNCTION__ << "[" << DirectionToString(direction)
166 << "]";
167 RTC_DLOG(LS_INFO) << "Automatic restart: " << automatic_restart;
168 RTC_DLOG(LS_INFO) << "Windows version: " << rtc::rtc_win::GetVersion();
169
170 // Create the event which the audio engine will signal each time a buffer
171 // becomes ready to be processed by the client.
172 audio_samples_event_.Set(CreateEvent(nullptr, false, false, nullptr));
173 RTC_DCHECK(audio_samples_event_.IsValid());
174
175 // Event to be set in Stop() when rendering/capturing shall stop.
176 stop_event_.Set(CreateEvent(nullptr, false, false, nullptr));
177 RTC_DCHECK(stop_event_.IsValid());
178
179 // Event to be set when it has been detected that an active device has been
180 // invalidated or the stream format has changed.
181 restart_event_.Set(CreateEvent(nullptr, false, false, nullptr));
182 RTC_DCHECK(restart_event_.IsValid());
183 }
184
~CoreAudioBase()185 CoreAudioBase::~CoreAudioBase() {
186 RTC_DLOG(LS_INFO) << __FUNCTION__;
187 RTC_DCHECK_EQ(ref_count_, 1);
188 }
189
GetDataFlow() const190 EDataFlow CoreAudioBase::GetDataFlow() const {
191 return direction_ == CoreAudioBase::Direction::kOutput ? eRender : eCapture;
192 }
193
IsRestarting() const194 bool CoreAudioBase::IsRestarting() const {
195 return is_restarting_;
196 }
197
TimeSinceStart() const198 int64_t CoreAudioBase::TimeSinceStart() const {
199 return rtc::TimeSince(start_time_);
200 }
201
NumberOfActiveDevices() const202 int CoreAudioBase::NumberOfActiveDevices() const {
203 return core_audio_utility::NumberOfActiveDevices(GetDataFlow());
204 }
205
NumberOfEnumeratedDevices() const206 int CoreAudioBase::NumberOfEnumeratedDevices() const {
207 const int num_active = NumberOfActiveDevices();
208 return num_active > 0 ? num_active + kDefaultDeviceTypeMaxCount : 0;
209 }
210
ReleaseCOMObjects()211 void CoreAudioBase::ReleaseCOMObjects() {
212 RTC_DLOG(LS_INFO) << __FUNCTION__;
213 // ComPtr::Reset() sets the ComPtr to nullptr releasing any previous
214 // reference.
215 if (audio_client_) {
216 audio_client_.Reset();
217 }
218 if (audio_clock_.Get()) {
219 audio_clock_.Reset();
220 }
221 if (audio_session_control_.Get()) {
222 audio_session_control_.Reset();
223 }
224 }
225
IsDefaultDevice(int index) const226 bool CoreAudioBase::IsDefaultDevice(int index) const {
227 return index == kDefault;
228 }
229
IsDefaultCommunicationsDevice(int index) const230 bool CoreAudioBase::IsDefaultCommunicationsDevice(int index) const {
231 return index == kDefaultCommunications;
232 }
233
IsDefaultDeviceId(absl::string_view device_id) const234 bool CoreAudioBase::IsDefaultDeviceId(absl::string_view device_id) const {
235 // Returns true if `device_id` corresponds to the id of the default
236 // device. Note that, if only one device is available (or if the user has not
237 // explicitly set a default device), `device_id` will also math
238 // IsDefaultCommunicationsDeviceId().
239 return (IsInput() &&
240 (device_id == core_audio_utility::GetDefaultInputDeviceID())) ||
241 (IsOutput() &&
242 (device_id == core_audio_utility::GetDefaultOutputDeviceID()));
243 }
244
IsDefaultCommunicationsDeviceId(absl::string_view device_id) const245 bool CoreAudioBase::IsDefaultCommunicationsDeviceId(
246 absl::string_view device_id) const {
247 // Returns true if `device_id` corresponds to the id of the default
248 // communication device. Note that, if only one device is available (or if
249 // the user has not explicitly set a communication device), `device_id` will
250 // also math IsDefaultDeviceId().
251 return (IsInput() &&
252 (device_id ==
253 core_audio_utility::GetCommunicationsInputDeviceID())) ||
254 (IsOutput() &&
255 (device_id == core_audio_utility::GetCommunicationsOutputDeviceID()));
256 }
257
IsInput() const258 bool CoreAudioBase::IsInput() const {
259 return direction_ == CoreAudioBase::Direction::kInput;
260 }
261
IsOutput() const262 bool CoreAudioBase::IsOutput() const {
263 return direction_ == CoreAudioBase::Direction::kOutput;
264 }
265
GetDeviceID(int index) const266 std::string CoreAudioBase::GetDeviceID(int index) const {
267 if (index >= NumberOfEnumeratedDevices()) {
268 RTC_LOG(LS_ERROR) << "Invalid device index";
269 return std::string();
270 }
271
272 std::string device_id;
273 if (IsDefaultDevice(index)) {
274 device_id = IsInput() ? core_audio_utility::GetDefaultInputDeviceID()
275 : core_audio_utility::GetDefaultOutputDeviceID();
276 } else if (IsDefaultCommunicationsDevice(index)) {
277 device_id = IsInput()
278 ? core_audio_utility::GetCommunicationsInputDeviceID()
279 : core_audio_utility::GetCommunicationsOutputDeviceID();
280 } else {
281 AudioDeviceNames device_names;
282 bool ok = IsInput()
283 ? core_audio_utility::GetInputDeviceNames(&device_names)
284 : core_audio_utility::GetOutputDeviceNames(&device_names);
285 if (ok) {
286 device_id = device_names[index].unique_id;
287 }
288 }
289 return device_id;
290 }
291
SetDevice(int index)292 int CoreAudioBase::SetDevice(int index) {
293 RTC_DLOG(LS_INFO) << __FUNCTION__ << "[" << DirectionToString(direction())
294 << "]: index=" << IndexToString(index);
295 if (initialized_) {
296 return -1;
297 }
298
299 std::string device_id = GetDeviceID(index);
300 RTC_DLOG(LS_INFO) << "index=" << IndexToString(index)
301 << " => device_id: " << device_id;
302 device_index_ = index;
303 device_id_ = device_id;
304
305 return device_id_.empty() ? -1 : 0;
306 }
307
DeviceName(int index,std::string * name,std::string * guid) const308 int CoreAudioBase::DeviceName(int index,
309 std::string* name,
310 std::string* guid) const {
311 RTC_DLOG(LS_INFO) << __FUNCTION__ << "[" << DirectionToString(direction())
312 << "]: index=" << IndexToString(index);
313 if (index > NumberOfEnumeratedDevices() - 1) {
314 RTC_LOG(LS_ERROR) << "Invalid device index";
315 return -1;
316 }
317
318 AudioDeviceNames device_names;
319 bool ok = IsInput() ? core_audio_utility::GetInputDeviceNames(&device_names)
320 : core_audio_utility::GetOutputDeviceNames(&device_names);
321 // Validate the index one extra time in-case the size of the generated list
322 // did not match NumberOfEnumeratedDevices().
323 if (!ok || static_cast<int>(device_names.size()) <= index) {
324 RTC_LOG(LS_ERROR) << "Failed to get the device name";
325 return -1;
326 }
327
328 *name = device_names[index].device_name;
329 RTC_DLOG(LS_INFO) << "name: " << *name;
330 if (guid != nullptr) {
331 *guid = device_names[index].unique_id;
332 RTC_DLOG(LS_INFO) << "guid: " << *guid;
333 }
334 return 0;
335 }
336
Init()337 bool CoreAudioBase::Init() {
338 RTC_DLOG(LS_INFO) << __FUNCTION__ << "[" << DirectionToString(direction())
339 << "]";
340 RTC_DCHECK_GE(device_index_, 0);
341 RTC_DCHECK(!device_id_.empty());
342 RTC_DCHECK(audio_device_buffer_);
343 RTC_DCHECK(!audio_client_);
344 RTC_DCHECK(!audio_session_control_.Get());
345
346 // Use an existing combination of `device_index_` and `device_id_` to set
347 // parameters which are required to create an audio client. It is up to the
348 // parent class to set `device_index_` and `device_id_`.
349 std::string device_id = AudioDeviceName::kDefaultDeviceId;
350 ERole role = ERole();
351 if (IsDefaultDevice(device_index_)) {
352 role = eConsole;
353 } else if (IsDefaultCommunicationsDevice(device_index_)) {
354 role = eCommunications;
355 } else {
356 device_id = device_id_;
357 }
358 RTC_LOG(LS_INFO) << "Unique device identifier: device_id=" << device_id
359 << ", role=" << RoleToString(role);
360
361 // Create an IAudioClient interface which enables us to create and initialize
362 // an audio stream between an audio application and the audio engine.
363 ComPtr<IAudioClient> audio_client;
364 if (core_audio_utility::GetAudioClientVersion() == 3) {
365 RTC_DLOG(LS_INFO) << "Using IAudioClient3";
366 audio_client =
367 core_audio_utility::CreateClient3(device_id, GetDataFlow(), role);
368 } else if (core_audio_utility::GetAudioClientVersion() == 2) {
369 RTC_DLOG(LS_INFO) << "Using IAudioClient2";
370 audio_client =
371 core_audio_utility::CreateClient2(device_id, GetDataFlow(), role);
372 } else {
373 RTC_DLOG(LS_INFO) << "Using IAudioClient";
374 audio_client =
375 core_audio_utility::CreateClient(device_id, GetDataFlow(), role);
376 }
377 if (!audio_client) {
378 return false;
379 }
380
381 // Set extra client properties before initialization if the audio client
382 // supports it.
383 // TODO(henrika): evaluate effect(s) of making these changes. Also, perhaps
384 // these types of settings belongs to the client and not the utility parts.
385 if (core_audio_utility::GetAudioClientVersion() >= 2) {
386 if (FAILED(core_audio_utility::SetClientProperties(
387 static_cast<IAudioClient2*>(audio_client.Get())))) {
388 return false;
389 }
390 }
391
392 // Retrieve preferred audio input or output parameters for the given client
393 // and the specified client properties. Override the preferred rate if sample
394 // rate has been defined by the user. Rate conversion will be performed by
395 // the audio engine to match the client if needed.
396 AudioParameters params;
397 HRESULT res = sample_rate_ ? core_audio_utility::GetPreferredAudioParameters(
398 audio_client.Get(), ¶ms, *sample_rate_)
399 : core_audio_utility::GetPreferredAudioParameters(
400 audio_client.Get(), ¶ms);
401 if (FAILED(res)) {
402 return false;
403 }
404
405 // Define the output WAVEFORMATEXTENSIBLE format in `format_`.
406 WAVEFORMATEX* format = &format_.Format;
407 format->wFormatTag = WAVE_FORMAT_EXTENSIBLE;
408 // Check the preferred channel configuration and request implicit channel
409 // upmixing (audio engine extends from 2 to N channels internally) if the
410 // preferred number of channels is larger than two; i.e., initialize the
411 // stream in stereo even if the preferred configuration is multi-channel.
412 if (params.channels() <= 2) {
413 format->nChannels = rtc::dchecked_cast<WORD>(params.channels());
414 } else {
415 // TODO(henrika): ensure that this approach works on different multi-channel
416 // devices. Verified on:
417 // - Corsair VOID PRO Surround USB Adapter (supports 7.1)
418 RTC_LOG(LS_WARNING)
419 << "Using channel upmixing in WASAPI audio engine (2 => "
420 << params.channels() << ")";
421 format->nChannels = 2;
422 }
423 format->nSamplesPerSec = params.sample_rate();
424 format->wBitsPerSample = rtc::dchecked_cast<WORD>(params.bits_per_sample());
425 format->nBlockAlign = (format->wBitsPerSample / 8) * format->nChannels;
426 format->nAvgBytesPerSec = format->nSamplesPerSec * format->nBlockAlign;
427 format->cbSize = sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX);
428 // Add the parts which are unique for the WAVE_FORMAT_EXTENSIBLE structure.
429 format_.Samples.wValidBitsPerSample =
430 rtc::dchecked_cast<WORD>(params.bits_per_sample());
431 format_.dwChannelMask =
432 format->nChannels == 1 ? KSAUDIO_SPEAKER_MONO : KSAUDIO_SPEAKER_STEREO;
433 format_.SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
434 RTC_DLOG(LS_INFO) << core_audio_utility::WaveFormatToString(&format_);
435
436 // Verify that the format is supported but exclude the test if the default
437 // sample rate has been overridden. If so, the WASAPI audio engine will do
438 // any necessary conversions between the client format we have given it and
439 // the playback mix format or recording split format.
440 if (!sample_rate_) {
441 if (!core_audio_utility::IsFormatSupported(
442 audio_client.Get(), AUDCLNT_SHAREMODE_SHARED, &format_)) {
443 return false;
444 }
445 }
446
447 // Check if low-latency is supported and use special initialization if it is.
448 // Low-latency initialization requires these things:
449 // - IAudioClient3 (>= Win10)
450 // - HDAudio driver
451 // - kEnableLowLatencyIfSupported changed from false (default) to true.
452 // TODO(henrika): IsLowLatencySupported() returns AUDCLNT_E_UNSUPPORTED_FORMAT
453 // when `sample_rate_.has_value()` returns true if rate conversion is
454 // actually required (i.e., client asks for other than the default rate).
455 bool low_latency_support = false;
456 uint32_t min_period_in_frames = 0;
457 if (kEnableLowLatencyIfSupported &&
458 core_audio_utility::GetAudioClientVersion() >= 3) {
459 low_latency_support =
460 IsLowLatencySupported(static_cast<IAudioClient3*>(audio_client.Get()),
461 &format_, &min_period_in_frames);
462 }
463
464 if (low_latency_support) {
465 RTC_DCHECK_GE(core_audio_utility::GetAudioClientVersion(), 3);
466 // Use IAudioClient3::InitializeSharedAudioStream() API to initialize a
467 // low-latency event-driven client. Request the smallest possible
468 // periodicity.
469 // TODO(henrika): evaluate this scheme in terms of CPU etc.
470 if (FAILED(core_audio_utility::SharedModeInitializeLowLatency(
471 static_cast<IAudioClient3*>(audio_client.Get()), &format_,
472 audio_samples_event_, min_period_in_frames,
473 sample_rate_.has_value(), &endpoint_buffer_size_frames_))) {
474 return false;
475 }
476 } else {
477 // Initialize the audio stream between the client and the device in shared
478 // mode using event-driven buffer handling. Also, using 0 as requested
479 // buffer size results in a default (minimum) endpoint buffer size.
480 // TODO(henrika): possibly increase `requested_buffer_size` to add
481 // robustness.
482 const REFERENCE_TIME requested_buffer_size = 0;
483 if (FAILED(core_audio_utility::SharedModeInitialize(
484 audio_client.Get(), &format_, audio_samples_event_,
485 requested_buffer_size, sample_rate_.has_value(),
486 &endpoint_buffer_size_frames_))) {
487 return false;
488 }
489 }
490
491 // Check device period and the preferred buffer size and log a warning if
492 // WebRTC's buffer size is not an even divisor of the preferred buffer size
493 // in Core Audio.
494 // TODO(henrika): sort out if a non-perfect match really is an issue.
495 // TODO(henrika): compare with IAudioClient3::GetSharedModeEnginePeriod().
496 REFERENCE_TIME device_period;
497 if (FAILED(core_audio_utility::GetDevicePeriod(
498 audio_client.Get(), AUDCLNT_SHAREMODE_SHARED, &device_period))) {
499 return false;
500 }
501 const double device_period_in_seconds =
502 static_cast<double>(
503 core_audio_utility::ReferenceTimeToTimeDelta(device_period).ms()) /
504 1000.0L;
505 const int preferred_frames_per_buffer =
506 static_cast<int>(params.sample_rate() * device_period_in_seconds + 0.5);
507 RTC_DLOG(LS_INFO) << "preferred_frames_per_buffer: "
508 << preferred_frames_per_buffer;
509 if (preferred_frames_per_buffer % params.frames_per_buffer()) {
510 RTC_LOG(LS_WARNING) << "Buffer size of " << params.frames_per_buffer()
511 << " is not an even divisor of "
512 << preferred_frames_per_buffer;
513 }
514
515 // Create an AudioSessionControl interface given the initialized client.
516 // The IAudioControl interface enables a client to configure the control
517 // parameters for an audio session and to monitor events in the session.
518 ComPtr<IAudioSessionControl> audio_session_control =
519 core_audio_utility::CreateAudioSessionControl(audio_client.Get());
520 if (!audio_session_control.Get()) {
521 return false;
522 }
523
524 // The Sndvol program displays volume and mute controls for sessions that
525 // are in the active and inactive states.
526 AudioSessionState state;
527 if (FAILED(audio_session_control->GetState(&state))) {
528 return false;
529 }
530 RTC_DLOG(LS_INFO) << "audio session state: " << SessionStateToString(state);
531 RTC_DCHECK_EQ(state, AudioSessionStateInactive);
532
533 // Register the client to receive notifications of session events, including
534 // changes in the stream state.
535 if (FAILED(audio_session_control->RegisterAudioSessionNotification(this))) {
536 return false;
537 }
538
539 // Store valid COM interfaces.
540 audio_client_ = audio_client;
541 audio_session_control_ = audio_session_control;
542
543 return true;
544 }
545
Start()546 bool CoreAudioBase::Start() {
547 RTC_DLOG(LS_INFO) << __FUNCTION__ << "[" << DirectionToString(direction())
548 << "]";
549 if (IsRestarting()) {
550 // Audio thread should be alive during internal restart since the restart
551 // callback is triggered on that thread and it also makes the restart
552 // sequence less complex.
553 RTC_DCHECK(!audio_thread_.empty());
554 }
555
556 // Start an audio thread but only if one does not already exist (which is the
557 // case during restart).
558 if (audio_thread_.empty()) {
559 const absl::string_view name =
560 IsInput() ? "wasapi_capture_thread" : "wasapi_render_thread";
561 audio_thread_ = rtc::PlatformThread::SpawnJoinable(
562 [this] { ThreadRun(); }, name,
563 rtc::ThreadAttributes().SetPriority(rtc::ThreadPriority::kRealtime));
564 RTC_DLOG(LS_INFO) << "Started thread with name: " << name
565 << " and handle: " << *audio_thread_.GetHandle();
566 }
567
568 // Start streaming data between the endpoint buffer and the audio engine.
569 _com_error error = audio_client_->Start();
570 if (FAILED(error.Error())) {
571 StopThread();
572 RTC_LOG(LS_ERROR) << "IAudioClient::Start failed: "
573 << core_audio_utility::ErrorToString(error);
574 return false;
575 }
576
577 start_time_ = rtc::TimeMillis();
578 num_data_callbacks_ = 0;
579
580 return true;
581 }
582
Stop()583 bool CoreAudioBase::Stop() {
584 RTC_DLOG(LS_INFO) << __FUNCTION__ << "[" << DirectionToString(direction())
585 << "]";
586 RTC_DLOG(LS_INFO) << "total activity time: " << TimeSinceStart();
587
588 // Stop audio streaming.
589 _com_error error = audio_client_->Stop();
590 if (FAILED(error.Error())) {
591 RTC_LOG(LS_ERROR) << "IAudioClient::Stop failed: "
592 << core_audio_utility::ErrorToString(error);
593 }
594 // Stop and destroy the audio thread but only when a restart attempt is not
595 // ongoing.
596 if (!IsRestarting()) {
597 StopThread();
598 }
599
600 // Flush all pending data and reset the audio clock stream position to 0.
601 error = audio_client_->Reset();
602 if (FAILED(error.Error())) {
603 RTC_LOG(LS_ERROR) << "IAudioClient::Reset failed: "
604 << core_audio_utility::ErrorToString(error);
605 }
606
607 if (IsOutput()) {
608 // Extra safety check to ensure that the buffers are cleared.
609 // If the buffers are not cleared correctly, the next call to Start()
610 // would fail with AUDCLNT_E_BUFFER_ERROR at
611 // IAudioRenderClient::GetBuffer().
612 UINT32 num_queued_frames = 0;
613 audio_client_->GetCurrentPadding(&num_queued_frames);
614 RTC_DCHECK_EQ(0u, num_queued_frames);
615 }
616
617 // Delete the previous registration by the client to receive notifications
618 // about audio session events.
619 RTC_DLOG(LS_INFO) << "audio session state: "
620 << SessionStateToString(GetAudioSessionState());
621 error = audio_session_control_->UnregisterAudioSessionNotification(this);
622 if (FAILED(error.Error())) {
623 RTC_LOG(LS_ERROR)
624 << "IAudioSessionControl::UnregisterAudioSessionNotification failed: "
625 << core_audio_utility::ErrorToString(error);
626 }
627
628 // To ensure that the restart process is as simple as possible, the audio
629 // thread is not destroyed during restart attempts triggered by internal
630 // error callbacks.
631 if (!IsRestarting()) {
632 thread_checker_audio_.Detach();
633 }
634
635 // Release all allocated COM interfaces to allow for a restart without
636 // intermediate destruction.
637 ReleaseCOMObjects();
638
639 return true;
640 }
641
IsVolumeControlAvailable(bool * available) const642 bool CoreAudioBase::IsVolumeControlAvailable(bool* available) const {
643 // A valid IAudioClient is required to access the ISimpleAudioVolume interface
644 // properly. It is possible to use IAudioSessionManager::GetSimpleAudioVolume
645 // as well but we use the audio client here to ensure that the initialized
646 // audio session is visible under group box labeled "Applications" in
647 // Sndvol.exe.
648 if (!audio_client_) {
649 return false;
650 }
651
652 // Try to create an ISimpleAudioVolume instance.
653 ComPtr<ISimpleAudioVolume> audio_volume =
654 core_audio_utility::CreateSimpleAudioVolume(audio_client_.Get());
655 if (!audio_volume.Get()) {
656 RTC_DLOG(LS_ERROR) << "Volume control is not supported";
657 return false;
658 }
659
660 // Try to use the valid volume control.
661 float volume = 0.0;
662 _com_error error = audio_volume->GetMasterVolume(&volume);
663 if (error.Error() != S_OK) {
664 RTC_LOG(LS_ERROR) << "ISimpleAudioVolume::GetMasterVolume failed: "
665 << core_audio_utility::ErrorToString(error);
666 *available = false;
667 }
668 RTC_DLOG(LS_INFO) << "master volume for output audio session: " << volume;
669
670 *available = true;
671 return false;
672 }
673
674 // Internal test method which can be used in tests to emulate a restart signal.
675 // It simply sets the same event which is normally triggered by session and
676 // device notifications. Hence, the emulated restart sequence covers most parts
677 // of a real sequence expect the actual device switch.
Restart()678 bool CoreAudioBase::Restart() {
679 RTC_DLOG(LS_INFO) << __FUNCTION__ << "[" << DirectionToString(direction())
680 << "]";
681 if (!automatic_restart()) {
682 return false;
683 }
684 is_restarting_ = true;
685 SetEvent(restart_event_.Get());
686 return true;
687 }
688
StopThread()689 void CoreAudioBase::StopThread() {
690 RTC_DLOG(LS_INFO) << __FUNCTION__;
691 RTC_DCHECK(!IsRestarting());
692 if (!audio_thread_.empty()) {
693 RTC_DLOG(LS_INFO) << "Sets stop_event...";
694 SetEvent(stop_event_.Get());
695 RTC_DLOG(LS_INFO) << "PlatformThread::Finalize...";
696 audio_thread_.Finalize();
697
698 // Ensure that we don't quit the main thread loop immediately next
699 // time Start() is called.
700 ResetEvent(stop_event_.Get());
701 ResetEvent(restart_event_.Get());
702 }
703 }
704
HandleRestartEvent()705 bool CoreAudioBase::HandleRestartEvent() {
706 RTC_DLOG(LS_INFO) << __FUNCTION__ << "[" << DirectionToString(direction())
707 << "]";
708 RTC_DCHECK_RUN_ON(&thread_checker_audio_);
709 RTC_DCHECK(!audio_thread_.empty());
710 RTC_DCHECK(IsRestarting());
711 // Let each client (input and/or output) take care of its own restart
712 // sequence since each side might need unique actions.
713 // TODO(henrika): revisit and investigate if one common base implementation
714 // is possible
715 bool restart_ok = on_error_callback_(ErrorType::kStreamDisconnected);
716 is_restarting_ = false;
717 return restart_ok;
718 }
719
SwitchDeviceIfNeeded()720 bool CoreAudioBase::SwitchDeviceIfNeeded() {
721 RTC_DLOG(LS_INFO) << __FUNCTION__ << "[" << DirectionToString(direction())
722 << "]";
723 RTC_DCHECK_RUN_ON(&thread_checker_audio_);
724 RTC_DCHECK(IsRestarting());
725
726 RTC_DLOG(LS_INFO) << "device_index=" << device_index_
727 << " => device_id: " << device_id_;
728
729 // Ensure that at least one device exists and can be utilized. The most
730 // probable cause for ending up here is that a device has been removed.
731 if (core_audio_utility::NumberOfActiveDevices(IsInput() ? eCapture
732 : eRender) < 1) {
733 RTC_DLOG(LS_ERROR) << "All devices are disabled or removed";
734 return false;
735 }
736
737 // Get the unique device ID for the index which is currently used. It seems
738 // safe to assume that if the ID is the same as the existing device ID, then
739 // the device configuration is the same as before.
740 std::string device_id = GetDeviceID(device_index_);
741 if (device_id != device_id_) {
742 RTC_LOG(LS_WARNING)
743 << "Device configuration has changed => changing device selection...";
744 // TODO(henrika): depending on the current state and how we got here, we
745 // must select a new device here.
746 if (SetDevice(kDefault) == -1) {
747 RTC_LOG(LS_WARNING) << "Failed to set new audio device";
748 return false;
749 }
750 } else {
751 RTC_LOG(LS_INFO)
752 << "Device configuration has not changed => keeping selected device";
753 }
754 return true;
755 }
756
GetAudioSessionState() const757 AudioSessionState CoreAudioBase::GetAudioSessionState() const {
758 AudioSessionState state = AudioSessionStateInactive;
759 RTC_DCHECK(audio_session_control_.Get());
760 _com_error error = audio_session_control_->GetState(&state);
761 if (FAILED(error.Error())) {
762 RTC_DLOG(LS_ERROR) << "IAudioSessionControl::GetState failed: "
763 << core_audio_utility::ErrorToString(error);
764 }
765 return state;
766 }
767
768 // TODO(henrika): only used for debugging purposes currently.
AddRef()769 ULONG CoreAudioBase::AddRef() {
770 ULONG new_ref = InterlockedIncrement(&ref_count_);
771 // RTC_DLOG(LS_INFO) << "__AddRef => " << new_ref;
772 return new_ref;
773 }
774
775 // TODO(henrika): does not call delete this.
Release()776 ULONG CoreAudioBase::Release() {
777 ULONG new_ref = InterlockedDecrement(&ref_count_);
778 // RTC_DLOG(LS_INFO) << "__Release => " << new_ref;
779 return new_ref;
780 }
781
782 // TODO(henrika): can probably be replaced by "return S_OK" only.
QueryInterface(REFIID iid,void ** object)783 HRESULT CoreAudioBase::QueryInterface(REFIID iid, void** object) {
784 if (object == nullptr) {
785 return E_POINTER;
786 }
787 if (iid == IID_IUnknown || iid == __uuidof(IAudioSessionEvents)) {
788 *object = static_cast<IAudioSessionEvents*>(this);
789 return S_OK;
790 }
791 *object = nullptr;
792 return E_NOINTERFACE;
793 }
794
795 // IAudioSessionEvents::OnStateChanged.
OnStateChanged(AudioSessionState new_state)796 HRESULT CoreAudioBase::OnStateChanged(AudioSessionState new_state) {
797 RTC_DLOG(LS_INFO) << "___" << __FUNCTION__ << "["
798 << DirectionToString(direction())
799 << "] new_state: " << SessionStateToString(new_state);
800 return S_OK;
801 }
802
803 // When a session is disconnected because of a device removal or format change
804 // event, we want to inform the audio thread about the lost audio session and
805 // trigger an attempt to restart audio using a new (default) device.
806 // This method is called on separate threads owned by the session manager and
807 // it can happen that the same type of callback is called more than once for the
808 // same event.
OnSessionDisconnected(AudioSessionDisconnectReason disconnect_reason)809 HRESULT CoreAudioBase::OnSessionDisconnected(
810 AudioSessionDisconnectReason disconnect_reason) {
811 RTC_DLOG(LS_INFO) << "___" << __FUNCTION__ << "["
812 << DirectionToString(direction()) << "] reason: "
813 << SessionDisconnectReasonToString(disconnect_reason);
814 // Ignore changes in the audio session (don't try to restart) if the user
815 // has explicitly asked for this type of ADM during construction.
816 if (!automatic_restart()) {
817 RTC_DLOG(LS_WARNING) << "___Automatic restart is disabled";
818 return S_OK;
819 }
820
821 if (IsRestarting()) {
822 RTC_DLOG(LS_WARNING) << "___Ignoring since restart is already active";
823 return S_OK;
824 }
825
826 // By default, automatic restart is enabled and the restart event will be set
827 // below if the device was removed or the format was changed.
828 if (disconnect_reason == DisconnectReasonDeviceRemoval ||
829 disconnect_reason == DisconnectReasonFormatChanged) {
830 is_restarting_ = true;
831 SetEvent(restart_event_.Get());
832 }
833 return S_OK;
834 }
835
836 // IAudioSessionEvents::OnDisplayNameChanged
OnDisplayNameChanged(LPCWSTR new_display_name,LPCGUID event_context)837 HRESULT CoreAudioBase::OnDisplayNameChanged(LPCWSTR new_display_name,
838 LPCGUID event_context) {
839 return S_OK;
840 }
841
842 // IAudioSessionEvents::OnIconPathChanged
OnIconPathChanged(LPCWSTR new_icon_path,LPCGUID event_context)843 HRESULT CoreAudioBase::OnIconPathChanged(LPCWSTR new_icon_path,
844 LPCGUID event_context) {
845 return S_OK;
846 }
847
848 // IAudioSessionEvents::OnSimpleVolumeChanged
OnSimpleVolumeChanged(float new_simple_volume,BOOL new_mute,LPCGUID event_context)849 HRESULT CoreAudioBase::OnSimpleVolumeChanged(float new_simple_volume,
850 BOOL new_mute,
851 LPCGUID event_context) {
852 return S_OK;
853 }
854
855 // IAudioSessionEvents::OnChannelVolumeChanged
OnChannelVolumeChanged(DWORD channel_count,float new_channel_volumes[],DWORD changed_channel,LPCGUID event_context)856 HRESULT CoreAudioBase::OnChannelVolumeChanged(DWORD channel_count,
857 float new_channel_volumes[],
858 DWORD changed_channel,
859 LPCGUID event_context) {
860 return S_OK;
861 }
862
863 // IAudioSessionEvents::OnGroupingParamChanged
OnGroupingParamChanged(LPCGUID new_grouping_param,LPCGUID event_context)864 HRESULT CoreAudioBase::OnGroupingParamChanged(LPCGUID new_grouping_param,
865 LPCGUID event_context) {
866 return S_OK;
867 }
868
ThreadRun()869 void CoreAudioBase::ThreadRun() {
870 if (!core_audio_utility::IsMMCSSSupported()) {
871 RTC_LOG(LS_ERROR) << "MMCSS is not supported";
872 return;
873 }
874 RTC_DLOG(LS_INFO) << "[" << DirectionToString(direction())
875 << "] ThreadRun starts...";
876 // TODO(henrika): difference between "Pro Audio" and "Audio"?
877 ScopedMMCSSRegistration mmcss_registration(L"Pro Audio");
878 ScopedCOMInitializer com_initializer(ScopedCOMInitializer::kMTA);
879 RTC_DCHECK(mmcss_registration.Succeeded());
880 RTC_DCHECK(com_initializer.Succeeded());
881 RTC_DCHECK(stop_event_.IsValid());
882 RTC_DCHECK(audio_samples_event_.IsValid());
883
884 bool streaming = true;
885 bool error = false;
886 HANDLE wait_array[] = {stop_event_.Get(), restart_event_.Get(),
887 audio_samples_event_.Get()};
888
889 // The device frequency is the frequency generated by the hardware clock in
890 // the audio device. The GetFrequency() method reports a constant frequency.
891 UINT64 device_frequency = 0;
892 _com_error result(S_FALSE);
893 if (audio_clock_) {
894 RTC_DCHECK(IsOutput());
895 result = audio_clock_->GetFrequency(&device_frequency);
896 if (FAILED(result.Error())) {
897 RTC_LOG(LS_ERROR) << "IAudioClock::GetFrequency failed: "
898 << core_audio_utility::ErrorToString(result);
899 }
900 }
901
902 // Keep streaming audio until the stop event or the stream-switch event
903 // is signaled. An error event can also break the main thread loop.
904 while (streaming && !error) {
905 // Wait for a close-down event, stream-switch event or a new render event.
906 DWORD wait_result = WaitForMultipleObjects(arraysize(wait_array),
907 wait_array, false, INFINITE);
908 switch (wait_result) {
909 case WAIT_OBJECT_0 + 0:
910 // `stop_event_` has been set.
911 streaming = false;
912 break;
913 case WAIT_OBJECT_0 + 1:
914 // `restart_event_` has been set.
915 error = !HandleRestartEvent();
916 break;
917 case WAIT_OBJECT_0 + 2:
918 // `audio_samples_event_` has been set.
919 error = !on_data_callback_(device_frequency);
920 break;
921 default:
922 error = true;
923 break;
924 }
925 }
926
927 if (streaming && error) {
928 RTC_LOG(LS_ERROR) << "[" << DirectionToString(direction())
929 << "] WASAPI streaming failed.";
930 // Stop audio streaming since something has gone wrong in our main thread
931 // loop. Note that, we are still in a "started" state, hence a Stop() call
932 // is required to join the thread properly.
933 result = audio_client_->Stop();
934 if (FAILED(result.Error())) {
935 RTC_LOG(LS_ERROR) << "IAudioClient::Stop failed: "
936 << core_audio_utility::ErrorToString(result);
937 }
938
939 // TODO(henrika): notify clients that something has gone wrong and that
940 // this stream should be destroyed instead of reused in the future.
941 }
942
943 RTC_DLOG(LS_INFO) << "[" << DirectionToString(direction())
944 << "] ...ThreadRun stops";
945 }
946
947 } // namespace webrtc_win
948 } // namespace webrtc
949