xref: /aosp_15_r20/external/webrtc/modules/audio_device/win/core_audio_base_win.cc (revision d9f758449e529ab9291ac668be2861e7a55c2422)
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(), &params, *sample_rate_)
399                              : core_audio_utility::GetPreferredAudioParameters(
400                                    audio_client.Get(), &params);
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