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