summaryrefslogtreecommitdiffstats
path: root/third_party/libwebrtc/modules/audio_device/win/core_audio_utility_win.cc
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /third_party/libwebrtc/modules/audio_device/win/core_audio_utility_win.cc
parentInitial commit. (diff)
downloadfirefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz
firefox-26a029d407be480d791972afb5975cf62c9360a6.zip
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'third_party/libwebrtc/modules/audio_device/win/core_audio_utility_win.cc')
-rw-r--r--third_party/libwebrtc/modules/audio_device/win/core_audio_utility_win.cc1529
1 files changed, 1529 insertions, 0 deletions
diff --git a/third_party/libwebrtc/modules/audio_device/win/core_audio_utility_win.cc b/third_party/libwebrtc/modules/audio_device/win/core_audio_utility_win.cc
new file mode 100644
index 0000000000..e4e2864db5
--- /dev/null
+++ b/third_party/libwebrtc/modules/audio_device/win/core_audio_utility_win.cc
@@ -0,0 +1,1529 @@
+/*
+ * Copyright (c) 2018 The WebRTC project authors. All Rights Reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include "modules/audio_device/win/core_audio_utility_win.h"
+
+#include <functiondiscoverykeys_devpkey.h>
+#include <stdio.h>
+#include <tchar.h>
+
+#include <iomanip>
+#include <string>
+#include <utility>
+
+#include "absl/strings/string_view.h"
+#include "rtc_base/arraysize.h"
+#include "rtc_base/logging.h"
+#include "rtc_base/platform_thread_types.h"
+#include "rtc_base/string_utils.h"
+#include "rtc_base/strings/string_builder.h"
+#include "rtc_base/win/windows_version.h"
+
+using Microsoft::WRL::ComPtr;
+using webrtc::AudioDeviceName;
+using webrtc::AudioParameters;
+
+namespace webrtc {
+namespace webrtc_win {
+namespace {
+
+using core_audio_utility::ErrorToString;
+
+// Converts from channel mask to list of included channels.
+// Each audio data format contains channels for one or more of the positions
+// listed below. The number of channels simply equals the number of nonzero
+// flag bits in the `channel_mask`. The relative positions of the channels
+// within each block of audio data always follow the same relative ordering
+// as the flag bits in the table below. For example, if `channel_mask` contains
+// the value 0x00000033, the format defines four audio channels that are
+// assigned for playback to the front-left, front-right, back-left,
+// and back-right speakers, respectively. The channel data should be interleaved
+// in that order within each block.
+std::string ChannelMaskToString(DWORD channel_mask) {
+ std::string ss;
+ int n = 0;
+ if (channel_mask & SPEAKER_FRONT_LEFT) {
+ ss += "FRONT_LEFT | ";
+ ++n;
+ }
+ if (channel_mask & SPEAKER_FRONT_RIGHT) {
+ ss += "FRONT_RIGHT | ";
+ ++n;
+ }
+ if (channel_mask & SPEAKER_FRONT_CENTER) {
+ ss += "FRONT_CENTER | ";
+ ++n;
+ }
+ if (channel_mask & SPEAKER_LOW_FREQUENCY) {
+ ss += "LOW_FREQUENCY | ";
+ ++n;
+ }
+ if (channel_mask & SPEAKER_BACK_LEFT) {
+ ss += "BACK_LEFT | ";
+ ++n;
+ }
+ if (channel_mask & SPEAKER_BACK_RIGHT) {
+ ss += "BACK_RIGHT | ";
+ ++n;
+ }
+ if (channel_mask & SPEAKER_FRONT_LEFT_OF_CENTER) {
+ ss += "FRONT_LEFT_OF_CENTER | ";
+ ++n;
+ }
+ if (channel_mask & SPEAKER_FRONT_RIGHT_OF_CENTER) {
+ ss += "RIGHT_OF_CENTER | ";
+ ++n;
+ }
+ if (channel_mask & SPEAKER_BACK_CENTER) {
+ ss += "BACK_CENTER | ";
+ ++n;
+ }
+ if (channel_mask & SPEAKER_SIDE_LEFT) {
+ ss += "SIDE_LEFT | ";
+ ++n;
+ }
+ if (channel_mask & SPEAKER_SIDE_RIGHT) {
+ ss += "SIDE_RIGHT | ";
+ ++n;
+ }
+ if (channel_mask & SPEAKER_TOP_CENTER) {
+ ss += "TOP_CENTER | ";
+ ++n;
+ }
+ if (channel_mask & SPEAKER_TOP_FRONT_LEFT) {
+ ss += "TOP_FRONT_LEFT | ";
+ ++n;
+ }
+ if (channel_mask & SPEAKER_TOP_FRONT_CENTER) {
+ ss += "TOP_FRONT_CENTER | ";
+ ++n;
+ }
+ if (channel_mask & SPEAKER_TOP_FRONT_RIGHT) {
+ ss += "TOP_FRONT_RIGHT | ";
+ ++n;
+ }
+ if (channel_mask & SPEAKER_TOP_BACK_LEFT) {
+ ss += "TOP_BACK_LEFT | ";
+ ++n;
+ }
+ if (channel_mask & SPEAKER_TOP_BACK_CENTER) {
+ ss += "TOP_BACK_CENTER | ";
+ ++n;
+ }
+ if (channel_mask & SPEAKER_TOP_BACK_RIGHT) {
+ ss += "TOP_BACK_RIGHT | ";
+ ++n;
+ }
+
+ if (!ss.empty()) {
+ // Delete last appended " | " substring.
+ ss.erase(ss.end() - 3, ss.end());
+ }
+ ss += " (";
+ ss += std::to_string(n);
+ ss += ")";
+ return ss;
+}
+
+#if !defined(KSAUDIO_SPEAKER_1POINT1)
+// These values are only defined in ksmedia.h after a certain version, to build
+// cleanly for older windows versions this just defines the ones that are
+// missing.
+#define KSAUDIO_SPEAKER_1POINT1 (SPEAKER_FRONT_CENTER | SPEAKER_LOW_FREQUENCY)
+#define KSAUDIO_SPEAKER_2POINT1 \
+ (SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_LOW_FREQUENCY)
+#define KSAUDIO_SPEAKER_3POINT0 \
+ (SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER)
+#define KSAUDIO_SPEAKER_3POINT1 \
+ (SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER | \
+ SPEAKER_LOW_FREQUENCY)
+#define KSAUDIO_SPEAKER_5POINT0 \
+ (SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER | \
+ SPEAKER_SIDE_LEFT | SPEAKER_SIDE_RIGHT)
+#define KSAUDIO_SPEAKER_7POINT0 \
+ (SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER | \
+ SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT | SPEAKER_SIDE_LEFT | \
+ SPEAKER_SIDE_RIGHT)
+#endif
+
+#if !defined(AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY)
+#define AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY 0x08000000
+#define AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM 0x80000000
+#endif
+
+// Converts the most common format tags defined in mmreg.h into string
+// equivalents. Mainly intended for log messages.
+const char* WaveFormatTagToString(WORD format_tag) {
+ switch (format_tag) {
+ case WAVE_FORMAT_UNKNOWN:
+ return "WAVE_FORMAT_UNKNOWN";
+ case WAVE_FORMAT_PCM:
+ return "WAVE_FORMAT_PCM";
+ case WAVE_FORMAT_IEEE_FLOAT:
+ return "WAVE_FORMAT_IEEE_FLOAT";
+ case WAVE_FORMAT_EXTENSIBLE:
+ return "WAVE_FORMAT_EXTENSIBLE";
+ default:
+ return "UNKNOWN";
+ }
+}
+
+const char* RoleToString(const ERole role) {
+ switch (role) {
+ case eConsole:
+ return "Console";
+ case eMultimedia:
+ return "Multimedia";
+ case eCommunications:
+ return "Communications";
+ default:
+ return "Unsupported";
+ }
+}
+
+const char* FlowToString(const EDataFlow flow) {
+ switch (flow) {
+ case eRender:
+ return "Render";
+ case eCapture:
+ return "Capture";
+ case eAll:
+ return "Render or Capture";
+ default:
+ return "Unsupported";
+ }
+}
+
+bool LoadAudiosesDll() {
+ static const wchar_t* const kAudiosesDLL =
+ L"%WINDIR%\\system32\\audioses.dll";
+ wchar_t path[MAX_PATH] = {0};
+ ExpandEnvironmentStringsW(kAudiosesDLL, path, arraysize(path));
+ RTC_DLOG(LS_INFO) << rtc::ToUtf8(path);
+ return (LoadLibraryExW(path, nullptr, LOAD_WITH_ALTERED_SEARCH_PATH) !=
+ nullptr);
+}
+
+bool LoadAvrtDll() {
+ static const wchar_t* const kAvrtDLL = L"%WINDIR%\\system32\\Avrt.dll";
+ wchar_t path[MAX_PATH] = {0};
+ ExpandEnvironmentStringsW(kAvrtDLL, path, arraysize(path));
+ RTC_DLOG(LS_INFO) << rtc::ToUtf8(path);
+ return (LoadLibraryExW(path, nullptr, LOAD_WITH_ALTERED_SEARCH_PATH) !=
+ nullptr);
+}
+
+ComPtr<IMMDeviceEnumerator> CreateDeviceEnumeratorInternal(
+ bool allow_reinitialize) {
+ ComPtr<IMMDeviceEnumerator> device_enumerator;
+ _com_error error =
+ ::CoCreateInstance(__uuidof(MMDeviceEnumerator), nullptr, CLSCTX_ALL,
+ IID_PPV_ARGS(&device_enumerator));
+ if (FAILED(error.Error())) {
+ RTC_LOG(LS_ERROR) << "CoCreateInstance failed: " << ErrorToString(error);
+ }
+
+ if (error.Error() == CO_E_NOTINITIALIZED && allow_reinitialize) {
+ RTC_LOG(LS_ERROR) << "CoCreateInstance failed with CO_E_NOTINITIALIZED";
+ // We have seen crashes which indicates that this method can in fact
+ // fail with CO_E_NOTINITIALIZED in combination with certain 3rd party
+ // modules. Calling CoInitializeEx() is an attempt to resolve the reported
+ // issues. See http://crbug.com/378465 for details.
+ error = CoInitializeEx(nullptr, COINIT_MULTITHREADED);
+ if (FAILED(error.Error())) {
+ error = ::CoCreateInstance(__uuidof(MMDeviceEnumerator), nullptr,
+ CLSCTX_ALL, IID_PPV_ARGS(&device_enumerator));
+ if (FAILED(error.Error())) {
+ RTC_LOG(LS_ERROR) << "CoCreateInstance failed: "
+ << ErrorToString(error);
+ }
+ }
+ }
+ return device_enumerator;
+}
+
+bool IsSupportedInternal() {
+ // The Core Audio APIs are implemented in the user-mode system components
+ // Audioses.dll and Mmdevapi.dll. Dependency Walker shows that it is
+ // enough to verify possibility to load the Audioses DLL since it depends
+ // on Mmdevapi.dll. See http://crbug.com/166397 why this extra step is
+ // required to guarantee Core Audio support.
+ if (!LoadAudiosesDll())
+ return false;
+
+ // Being able to load the Audioses.dll does not seem to be sufficient for
+ // all devices to guarantee Core Audio support. To be 100%, we also verify
+ // that it is possible to a create the IMMDeviceEnumerator interface. If
+ // this works as well we should be home free.
+ ComPtr<IMMDeviceEnumerator> device_enumerator =
+ CreateDeviceEnumeratorInternal(false);
+ if (!device_enumerator) {
+ RTC_LOG(LS_ERROR)
+ << "Failed to create Core Audio device enumerator on thread with ID "
+ << rtc::CurrentThreadId();
+ return false;
+ }
+
+ return true;
+}
+
+bool IsDeviceActive(IMMDevice* device) {
+ DWORD state = DEVICE_STATE_DISABLED;
+ return SUCCEEDED(device->GetState(&state)) && (state & DEVICE_STATE_ACTIVE);
+}
+
+// Retrieve an audio device specified by `device_id` or a default device
+// specified by data-flow direction and role if `device_id` is default.
+ComPtr<IMMDevice> CreateDeviceInternal(absl::string_view device_id,
+ EDataFlow data_flow,
+ ERole role) {
+ RTC_DLOG(LS_INFO) << "CreateDeviceInternal: "
+ "id="
+ << device_id << ", flow=" << FlowToString(data_flow)
+ << ", role=" << RoleToString(role);
+ ComPtr<IMMDevice> audio_endpoint_device;
+
+ // Create the IMMDeviceEnumerator interface.
+ ComPtr<IMMDeviceEnumerator> device_enum(CreateDeviceEnumeratorInternal(true));
+ if (!device_enum.Get())
+ return audio_endpoint_device;
+
+ _com_error error(S_FALSE);
+ if (device_id == AudioDeviceName::kDefaultDeviceId) {
+ // Get the default audio endpoint for the specified data-flow direction and
+ // role. Note that, if only a single rendering or capture device is
+ // available, the system always assigns all three rendering or capture roles
+ // to that device. If the method fails to find a rendering or capture device
+ // for the specified role, this means that no rendering or capture device is
+ // available at all. If no device is available, the method sets the output
+ // pointer to NULL and returns ERROR_NOT_FOUND.
+ error = device_enum->GetDefaultAudioEndpoint(
+ data_flow, role, audio_endpoint_device.GetAddressOf());
+ if (FAILED(error.Error())) {
+ RTC_LOG(LS_ERROR)
+ << "IMMDeviceEnumerator::GetDefaultAudioEndpoint failed: "
+ << ErrorToString(error);
+ }
+ } else {
+ // Ask for an audio endpoint device that is identified by an endpoint ID
+ // string.
+ error = device_enum->GetDevice(rtc::ToUtf16(device_id).c_str(),
+ audio_endpoint_device.GetAddressOf());
+ if (FAILED(error.Error())) {
+ RTC_LOG(LS_ERROR) << "IMMDeviceEnumerator::GetDevice failed: "
+ << ErrorToString(error);
+ }
+ }
+
+ // Verify that the audio endpoint device is active, i.e., that the audio
+ // adapter that connects to the endpoint device is present and enabled.
+ if (SUCCEEDED(error.Error()) && audio_endpoint_device.Get() &&
+ !IsDeviceActive(audio_endpoint_device.Get())) {
+ RTC_LOG(LS_WARNING) << "Selected endpoint device is not active";
+ audio_endpoint_device.Reset();
+ }
+
+ return audio_endpoint_device;
+}
+
+std::string GetDeviceIdInternal(IMMDevice* device) {
+ // Retrieve unique name of endpoint device.
+ // Example: "{0.0.1.00000000}.{8db6020f-18e3-4f25-b6f5-7726c9122574}".
+ LPWSTR device_id;
+ if (SUCCEEDED(device->GetId(&device_id))) {
+ std::string device_id_utf8 = rtc::ToUtf8(device_id, wcslen(device_id));
+ CoTaskMemFree(device_id);
+ return device_id_utf8;
+ } else {
+ return std::string();
+ }
+}
+
+std::string GetDeviceFriendlyNameInternal(IMMDevice* device) {
+ // Retrieve user-friendly name of endpoint device.
+ // Example: "Microphone (Realtek High Definition Audio)".
+ ComPtr<IPropertyStore> properties;
+ HRESULT hr = device->OpenPropertyStore(STGM_READ, properties.GetAddressOf());
+ if (FAILED(hr))
+ return std::string();
+
+ ScopedPropVariant friendly_name_pv;
+ hr = properties->GetValue(PKEY_Device_FriendlyName,
+ friendly_name_pv.Receive());
+ if (FAILED(hr))
+ return std::string();
+
+ if (friendly_name_pv.get().vt == VT_LPWSTR &&
+ friendly_name_pv.get().pwszVal) {
+ return rtc::ToUtf8(friendly_name_pv.get().pwszVal,
+ wcslen(friendly_name_pv.get().pwszVal));
+ } else {
+ return std::string();
+ }
+}
+
+ComPtr<IAudioSessionManager2> CreateSessionManager2Internal(
+ IMMDevice* audio_device) {
+ if (!audio_device)
+ return ComPtr<IAudioSessionManager2>();
+
+ ComPtr<IAudioSessionManager2> audio_session_manager;
+ _com_error error =
+ audio_device->Activate(__uuidof(IAudioSessionManager2), CLSCTX_ALL,
+ nullptr, &audio_session_manager);
+ if (FAILED(error.Error())) {
+ RTC_LOG(LS_ERROR) << "IMMDevice::Activate(IAudioSessionManager2) failed: "
+ << ErrorToString(error);
+ }
+ return audio_session_manager;
+}
+
+ComPtr<IAudioSessionEnumerator> CreateSessionEnumeratorInternal(
+ IMMDevice* audio_device) {
+ if (!audio_device) {
+ return ComPtr<IAudioSessionEnumerator>();
+ }
+
+ ComPtr<IAudioSessionEnumerator> audio_session_enumerator;
+ ComPtr<IAudioSessionManager2> audio_session_manager =
+ CreateSessionManager2Internal(audio_device);
+ if (!audio_session_manager.Get()) {
+ return audio_session_enumerator;
+ }
+ _com_error error =
+ audio_session_manager->GetSessionEnumerator(&audio_session_enumerator);
+ if (FAILED(error.Error())) {
+ RTC_LOG(LS_ERROR)
+ << "IAudioSessionEnumerator::IAudioSessionEnumerator failed: "
+ << ErrorToString(error);
+ return ComPtr<IAudioSessionEnumerator>();
+ }
+ return audio_session_enumerator;
+}
+
+// Creates and activates an IAudioClient COM object given the selected
+// endpoint device.
+ComPtr<IAudioClient> CreateClientInternal(IMMDevice* audio_device) {
+ if (!audio_device)
+ return ComPtr<IAudioClient>();
+
+ ComPtr<IAudioClient> audio_client;
+ _com_error error = audio_device->Activate(__uuidof(IAudioClient), CLSCTX_ALL,
+ nullptr, &audio_client);
+ if (FAILED(error.Error())) {
+ RTC_LOG(LS_ERROR) << "IMMDevice::Activate(IAudioClient) failed: "
+ << ErrorToString(error);
+ }
+ return audio_client;
+}
+
+ComPtr<IAudioClient2> CreateClient2Internal(IMMDevice* audio_device) {
+ if (!audio_device)
+ return ComPtr<IAudioClient2>();
+
+ ComPtr<IAudioClient2> audio_client;
+ _com_error error = audio_device->Activate(__uuidof(IAudioClient2), CLSCTX_ALL,
+ nullptr, &audio_client);
+ if (FAILED(error.Error())) {
+ RTC_LOG(LS_ERROR) << "IMMDevice::Activate(IAudioClient2) failed: "
+ << ErrorToString(error);
+ }
+ return audio_client;
+}
+
+ComPtr<IAudioClient3> CreateClient3Internal(IMMDevice* audio_device) {
+ if (!audio_device)
+ return ComPtr<IAudioClient3>();
+
+ ComPtr<IAudioClient3> audio_client;
+ _com_error error = audio_device->Activate(__uuidof(IAudioClient3), CLSCTX_ALL,
+ nullptr, &audio_client);
+ if (FAILED(error.Error())) {
+ RTC_LOG(LS_ERROR) << "IMMDevice::Activate(IAudioClient3) failed: "
+ << ErrorToString(error);
+ }
+ return audio_client;
+}
+
+ComPtr<IMMDeviceCollection> CreateCollectionInternal(EDataFlow data_flow) {
+ ComPtr<IMMDeviceEnumerator> device_enumerator(
+ CreateDeviceEnumeratorInternal(true));
+ if (!device_enumerator) {
+ return ComPtr<IMMDeviceCollection>();
+ }
+
+ // Generate a collection of active (present and not disabled) audio endpoint
+ // devices for the specified data-flow direction.
+ // This method will succeed even if all devices are disabled.
+ ComPtr<IMMDeviceCollection> collection;
+ _com_error error = device_enumerator->EnumAudioEndpoints(
+ data_flow, DEVICE_STATE_ACTIVE, collection.GetAddressOf());
+ if (FAILED(error.Error())) {
+ RTC_LOG(LS_ERROR) << "IMMDeviceCollection::EnumAudioEndpoints failed: "
+ << ErrorToString(error);
+ }
+ return collection;
+}
+
+bool GetDeviceNamesInternal(EDataFlow data_flow,
+ webrtc::AudioDeviceNames* device_names) {
+ RTC_DLOG(LS_INFO) << "GetDeviceNamesInternal: flow="
+ << FlowToString(data_flow);
+
+ // Generate a collection of active audio endpoint devices for the specified
+ // direction.
+ ComPtr<IMMDeviceCollection> collection = CreateCollectionInternal(data_flow);
+ if (!collection.Get()) {
+ RTC_LOG(LS_ERROR) << "Failed to create a collection of active devices";
+ return false;
+ }
+
+ // Retrieve the number of active (present, not disabled and plugged in) audio
+ // devices for the specified direction.
+ UINT number_of_active_devices = 0;
+ _com_error error = collection->GetCount(&number_of_active_devices);
+ if (FAILED(error.Error())) {
+ RTC_LOG(LS_ERROR) << "IMMDeviceCollection::GetCount failed: "
+ << ErrorToString(error);
+ return false;
+ }
+
+ if (number_of_active_devices == 0) {
+ RTC_DLOG(LS_WARNING) << "Found no active devices";
+ return false;
+ }
+
+ // Loop over all active devices and add friendly name and unique id to the
+ // `device_names` queue. For now, devices are added at indexes 0, 1, ..., N-1
+ // but they will be moved to 2,3,..., N+1 at the next stage when default and
+ // default communication devices are added at index 0 and 1.
+ ComPtr<IMMDevice> audio_device;
+ for (UINT i = 0; i < number_of_active_devices; ++i) {
+ // Retrieve a pointer to the specified item in the device collection.
+ error = collection->Item(i, audio_device.GetAddressOf());
+ if (FAILED(error.Error())) {
+ // Skip this item and try to get the next item instead; will result in an
+ // incomplete list of devices.
+ RTC_LOG(LS_WARNING) << "IMMDeviceCollection::Item failed: "
+ << ErrorToString(error);
+ continue;
+ }
+ if (!audio_device.Get()) {
+ RTC_LOG(LS_WARNING) << "Invalid audio device";
+ continue;
+ }
+
+ // Retrieve the complete device name for the given audio device endpoint.
+ AudioDeviceName device_name(
+ GetDeviceFriendlyNameInternal(audio_device.Get()),
+ GetDeviceIdInternal(audio_device.Get()));
+ // Add combination of user-friendly and unique name to the output list.
+ device_names->push_back(device_name);
+ }
+
+ // Log a warning of the list of device is not complete but let's keep on
+ // trying to add default and default communications device at the front.
+ if (device_names->size() != number_of_active_devices) {
+ RTC_DLOG(LS_WARNING)
+ << "List of device names does not contain all active devices";
+ }
+
+ // Avoid adding default and default communication devices if no active device
+ // could be added to the queue. We might as well break here and return false
+ // since no active devices were identified.
+ if (device_names->empty()) {
+ RTC_DLOG(LS_ERROR) << "List of active devices is empty";
+ return false;
+ }
+
+ // Prepend the queue with two more elements: one for the default device and
+ // one for the default communication device (can correspond to the same unique
+ // id if only one active device exists). The first element (index 0) is the
+ // default device and the second element (index 1) is the default
+ // communication device.
+ ERole role[] = {eCommunications, eConsole};
+ ComPtr<IMMDevice> default_device;
+ AudioDeviceName default_device_name;
+ for (size_t i = 0; i < arraysize(role); ++i) {
+ default_device = CreateDeviceInternal(AudioDeviceName::kDefaultDeviceId,
+ data_flow, role[i]);
+ if (!default_device.Get()) {
+ // Add empty strings to device name if the device could not be created.
+ RTC_DLOG(LS_WARNING) << "Failed to add device with role: "
+ << RoleToString(role[i]);
+ default_device_name.device_name = std::string();
+ default_device_name.unique_id = std::string();
+ } else {
+ // Populate the device name with friendly name and unique id.
+ std::string device_name;
+ device_name += (role[i] == eConsole ? "Default - " : "Communication - ");
+ device_name += GetDeviceFriendlyNameInternal(default_device.Get());
+ std::string unique_id = GetDeviceIdInternal(default_device.Get());
+ default_device_name.device_name = std::move(device_name);
+ default_device_name.unique_id = std::move(unique_id);
+ }
+
+ // Add combination of user-friendly and unique name to the output queue.
+ // The last element (<=> eConsole) will be at the front of the queue, hence
+ // at index 0. Empty strings will be added for cases where no default
+ // devices were found.
+ device_names->push_front(default_device_name);
+ }
+
+ // Example of log output when only one device is active. Note that the queue
+ // contains two extra elements at index 0 (Default) and 1 (Communication) to
+ // allow selection of device by role instead of id. All elements corresponds
+ // the same unique id.
+ // [0] friendly name: Default - Headset Microphone (2- Arctis 7 Chat)
+ // [0] unique id : {0.0.1.00000000}.{ff9eed76-196e-467a-b295-26986e69451c}
+ // [1] friendly name: Communication - Headset Microphone (2- Arctis 7 Chat)
+ // [1] unique id : {0.0.1.00000000}.{ff9eed76-196e-467a-b295-26986e69451c}
+ // [2] friendly name: Headset Microphone (2- Arctis 7 Chat)
+ // [2] unique id : {0.0.1.00000000}.{ff9eed76-196e-467a-b295-26986e69451c}
+ for (size_t i = 0; i < device_names->size(); ++i) {
+ RTC_DLOG(LS_INFO) << "[" << i
+ << "] friendly name: " << (*device_names)[i].device_name;
+ RTC_DLOG(LS_INFO) << "[" << i
+ << "] unique id : " << (*device_names)[i].unique_id;
+ }
+
+ return true;
+}
+
+HRESULT GetPreferredAudioParametersInternal(IAudioClient* client,
+ AudioParameters* params,
+ int fixed_sample_rate) {
+ WAVEFORMATPCMEX mix_format;
+ HRESULT hr = core_audio_utility::GetSharedModeMixFormat(client, &mix_format);
+ if (FAILED(hr))
+ return hr;
+
+ REFERENCE_TIME default_period = 0;
+ hr = core_audio_utility::GetDevicePeriod(client, AUDCLNT_SHAREMODE_SHARED,
+ &default_period);
+ if (FAILED(hr))
+ return hr;
+
+ int sample_rate = mix_format.Format.nSamplesPerSec;
+ // Override default sample rate if `fixed_sample_rate` is set and different
+ // from the default rate.
+ if (fixed_sample_rate > 0 && fixed_sample_rate != sample_rate) {
+ RTC_DLOG(LS_INFO) << "Using fixed sample rate instead of the preferred: "
+ << sample_rate << " is replaced by " << fixed_sample_rate;
+ sample_rate = fixed_sample_rate;
+ }
+ // TODO(henrika): utilize full mix_format.Format.wBitsPerSample.
+ // const size_t bits_per_sample = AudioParameters::kBitsPerSample;
+ // TODO(henrika): improve channel layout support.
+ const size_t channels = mix_format.Format.nChannels;
+
+ // Use the native device period to derive the smallest possible buffer size
+ // in shared mode.
+ double device_period_in_seconds =
+ static_cast<double>(
+ core_audio_utility::ReferenceTimeToTimeDelta(default_period).ms()) /
+ 1000.0L;
+ const size_t frames_per_buffer =
+ static_cast<size_t>(sample_rate * device_period_in_seconds + 0.5);
+
+ AudioParameters audio_params(sample_rate, channels, frames_per_buffer);
+ *params = audio_params;
+ RTC_DLOG(LS_INFO) << audio_params.ToString();
+
+ return hr;
+}
+
+} // namespace
+
+namespace core_audio_utility {
+
+// core_audio_utility::WaveFormatWrapper implementation.
+WAVEFORMATEXTENSIBLE* WaveFormatWrapper::GetExtensible() const {
+ RTC_CHECK(IsExtensible());
+ return reinterpret_cast<WAVEFORMATEXTENSIBLE*>(ptr_);
+}
+
+bool WaveFormatWrapper::IsExtensible() const {
+ return ptr_->wFormatTag == WAVE_FORMAT_EXTENSIBLE && ptr_->cbSize >= 22;
+}
+
+bool WaveFormatWrapper::IsPcm() const {
+ return IsExtensible() ? GetExtensible()->SubFormat == KSDATAFORMAT_SUBTYPE_PCM
+ : ptr_->wFormatTag == WAVE_FORMAT_PCM;
+}
+
+bool WaveFormatWrapper::IsFloat() const {
+ return IsExtensible()
+ ? GetExtensible()->SubFormat == KSDATAFORMAT_SUBTYPE_IEEE_FLOAT
+ : ptr_->wFormatTag == WAVE_FORMAT_IEEE_FLOAT;
+}
+
+size_t WaveFormatWrapper::size() const {
+ return sizeof(*ptr_) + ptr_->cbSize;
+}
+
+bool IsSupported() {
+ RTC_DLOG(LS_INFO) << "IsSupported";
+ static bool g_is_supported = IsSupportedInternal();
+ return g_is_supported;
+}
+
+bool IsMMCSSSupported() {
+ RTC_DLOG(LS_INFO) << "IsMMCSSSupported";
+ return LoadAvrtDll();
+}
+
+int NumberOfActiveDevices(EDataFlow data_flow) {
+ // Generate a collection of active audio endpoint devices for the specified
+ // data-flow direction.
+ ComPtr<IMMDeviceCollection> collection = CreateCollectionInternal(data_flow);
+ if (!collection.Get()) {
+ return 0;
+ }
+
+ // Retrieve the number of active audio devices for the specified direction.
+ UINT number_of_active_devices = 0;
+ collection->GetCount(&number_of_active_devices);
+ std::string str;
+ if (data_flow == eCapture) {
+ str = "Number of capture devices: ";
+ } else if (data_flow == eRender) {
+ str = "Number of render devices: ";
+ } else if (data_flow == eAll) {
+ str = "Total number of devices: ";
+ }
+ RTC_DLOG(LS_INFO) << str << number_of_active_devices;
+ return static_cast<int>(number_of_active_devices);
+}
+
+uint32_t GetAudioClientVersion() {
+ uint32_t version = 1;
+ if (rtc::rtc_win::GetVersion() >= rtc::rtc_win::VERSION_WIN10) {
+ version = 3;
+ } else if (rtc::rtc_win::GetVersion() >= rtc::rtc_win::VERSION_WIN8) {
+ version = 2;
+ }
+ return version;
+}
+
+ComPtr<IMMDeviceEnumerator> CreateDeviceEnumerator() {
+ RTC_DLOG(LS_INFO) << "CreateDeviceEnumerator";
+ return CreateDeviceEnumeratorInternal(true);
+}
+
+std::string GetDefaultInputDeviceID() {
+ RTC_DLOG(LS_INFO) << "GetDefaultInputDeviceID";
+ ComPtr<IMMDevice> device(
+ CreateDevice(AudioDeviceName::kDefaultDeviceId, eCapture, eConsole));
+ return device.Get() ? GetDeviceIdInternal(device.Get()) : std::string();
+}
+
+std::string GetDefaultOutputDeviceID() {
+ RTC_DLOG(LS_INFO) << "GetDefaultOutputDeviceID";
+ ComPtr<IMMDevice> device(
+ CreateDevice(AudioDeviceName::kDefaultDeviceId, eRender, eConsole));
+ return device.Get() ? GetDeviceIdInternal(device.Get()) : std::string();
+}
+
+std::string GetCommunicationsInputDeviceID() {
+ RTC_DLOG(LS_INFO) << "GetCommunicationsInputDeviceID";
+ ComPtr<IMMDevice> device(CreateDevice(AudioDeviceName::kDefaultDeviceId,
+ eCapture, eCommunications));
+ return device.Get() ? GetDeviceIdInternal(device.Get()) : std::string();
+}
+
+std::string GetCommunicationsOutputDeviceID() {
+ RTC_DLOG(LS_INFO) << "GetCommunicationsOutputDeviceID";
+ ComPtr<IMMDevice> device(CreateDevice(AudioDeviceName::kDefaultDeviceId,
+ eRender, eCommunications));
+ return device.Get() ? GetDeviceIdInternal(device.Get()) : std::string();
+}
+
+ComPtr<IMMDevice> CreateDevice(absl::string_view device_id,
+ EDataFlow data_flow,
+ ERole role) {
+ RTC_DLOG(LS_INFO) << "CreateDevice";
+ return CreateDeviceInternal(device_id, data_flow, role);
+}
+
+AudioDeviceName GetDeviceName(IMMDevice* device) {
+ RTC_DLOG(LS_INFO) << "GetDeviceName";
+ RTC_DCHECK(device);
+ AudioDeviceName device_name(GetDeviceFriendlyNameInternal(device),
+ GetDeviceIdInternal(device));
+ RTC_DLOG(LS_INFO) << "friendly name: " << device_name.device_name;
+ RTC_DLOG(LS_INFO) << "unique id : " << device_name.unique_id;
+ return device_name;
+}
+
+std::string GetFriendlyName(absl::string_view device_id,
+ EDataFlow data_flow,
+ ERole role) {
+ RTC_DLOG(LS_INFO) << "GetFriendlyName";
+ ComPtr<IMMDevice> audio_device = CreateDevice(device_id, data_flow, role);
+ if (!audio_device.Get())
+ return std::string();
+
+ AudioDeviceName device_name = GetDeviceName(audio_device.Get());
+ return device_name.device_name;
+}
+
+EDataFlow GetDataFlow(IMMDevice* device) {
+ RTC_DLOG(LS_INFO) << "GetDataFlow";
+ RTC_DCHECK(device);
+ ComPtr<IMMEndpoint> endpoint;
+ _com_error error = device->QueryInterface(endpoint.GetAddressOf());
+ if (FAILED(error.Error())) {
+ RTC_LOG(LS_ERROR) << "IMMDevice::QueryInterface failed: "
+ << ErrorToString(error);
+ return eAll;
+ }
+
+ EDataFlow data_flow;
+ error = endpoint->GetDataFlow(&data_flow);
+ if (FAILED(error.Error())) {
+ RTC_LOG(LS_ERROR) << "IMMEndpoint::GetDataFlow failed: "
+ << ErrorToString(error);
+ return eAll;
+ }
+ return data_flow;
+}
+
+bool GetInputDeviceNames(webrtc::AudioDeviceNames* device_names) {
+ RTC_DLOG(LS_INFO) << "GetInputDeviceNames";
+ RTC_DCHECK(device_names);
+ RTC_DCHECK(device_names->empty());
+ return GetDeviceNamesInternal(eCapture, device_names);
+}
+
+bool GetOutputDeviceNames(webrtc::AudioDeviceNames* device_names) {
+ RTC_DLOG(LS_INFO) << "GetOutputDeviceNames";
+ RTC_DCHECK(device_names);
+ RTC_DCHECK(device_names->empty());
+ return GetDeviceNamesInternal(eRender, device_names);
+}
+
+ComPtr<IAudioSessionManager2> CreateSessionManager2(IMMDevice* device) {
+ RTC_DLOG(LS_INFO) << "CreateSessionManager2";
+ return CreateSessionManager2Internal(device);
+}
+
+Microsoft::WRL::ComPtr<IAudioSessionEnumerator> CreateSessionEnumerator(
+ IMMDevice* device) {
+ RTC_DLOG(LS_INFO) << "CreateSessionEnumerator";
+ return CreateSessionEnumeratorInternal(device);
+}
+
+int NumberOfActiveSessions(IMMDevice* device) {
+ RTC_DLOG(LS_INFO) << "NumberOfActiveSessions";
+ ComPtr<IAudioSessionEnumerator> session_enumerator =
+ CreateSessionEnumerator(device);
+
+ // Iterate over all audio sessions for the given device.
+ int session_count = 0;
+ _com_error error = session_enumerator->GetCount(&session_count);
+ if (FAILED(error.Error())) {
+ RTC_LOG(LS_ERROR) << "IAudioSessionEnumerator::GetCount failed: "
+ << ErrorToString(error);
+ return 0;
+ }
+ RTC_DLOG(LS_INFO) << "Total number of audio sessions: " << session_count;
+
+ int num_active = 0;
+ for (int session = 0; session < session_count; session++) {
+ // Acquire the session control interface.
+ ComPtr<IAudioSessionControl> session_control;
+ error = session_enumerator->GetSession(session, &session_control);
+ if (FAILED(error.Error())) {
+ RTC_LOG(LS_ERROR) << "IAudioSessionEnumerator::GetSession failed: "
+ << ErrorToString(error);
+ return 0;
+ }
+
+ // Log the display name of the audio session for debugging purposes.
+ LPWSTR display_name;
+ if (SUCCEEDED(session_control->GetDisplayName(&display_name))) {
+ RTC_DLOG(LS_INFO) << "display name: "
+ << rtc::ToUtf8(display_name, wcslen(display_name));
+ CoTaskMemFree(display_name);
+ }
+
+ // Get the current state and check if the state is active or not.
+ AudioSessionState state;
+ error = session_control->GetState(&state);
+ if (FAILED(error.Error())) {
+ RTC_LOG(LS_ERROR) << "IAudioSessionControl::GetState failed: "
+ << ErrorToString(error);
+ return 0;
+ }
+ if (state == AudioSessionStateActive) {
+ ++num_active;
+ }
+ }
+
+ RTC_DLOG(LS_INFO) << "Number of active audio sessions: " << num_active;
+ return num_active;
+}
+
+ComPtr<IAudioClient> CreateClient(absl::string_view device_id,
+ EDataFlow data_flow,
+ ERole role) {
+ RTC_DLOG(LS_INFO) << "CreateClient";
+ ComPtr<IMMDevice> device(CreateDevice(device_id, data_flow, role));
+ return CreateClientInternal(device.Get());
+}
+
+ComPtr<IAudioClient2> CreateClient2(absl::string_view device_id,
+ EDataFlow data_flow,
+ ERole role) {
+ RTC_DLOG(LS_INFO) << "CreateClient2";
+ ComPtr<IMMDevice> device(CreateDevice(device_id, data_flow, role));
+ return CreateClient2Internal(device.Get());
+}
+
+ComPtr<IAudioClient3> CreateClient3(absl::string_view device_id,
+ EDataFlow data_flow,
+ ERole role) {
+ RTC_DLOG(LS_INFO) << "CreateClient3";
+ ComPtr<IMMDevice> device(CreateDevice(device_id, data_flow, role));
+ return CreateClient3Internal(device.Get());
+}
+
+HRESULT SetClientProperties(IAudioClient2* client) {
+ RTC_DLOG(LS_INFO) << "SetClientProperties";
+ RTC_DCHECK(client);
+ if (GetAudioClientVersion() < 2) {
+ RTC_LOG(LS_WARNING) << "Requires IAudioClient2 or higher";
+ return AUDCLNT_E_UNSUPPORTED_FORMAT;
+ }
+ AudioClientProperties props = {0};
+ props.cbSize = sizeof(AudioClientProperties);
+ // Real-time VoIP communication.
+ // TODO(henrika): other categories?
+ props.eCategory = AudioCategory_Communications;
+ // Hardware-offloaded audio processing allows the main audio processing tasks
+ // to be performed outside the computer's main CPU. Check support and log the
+ // result but hard-code `bIsOffload` to FALSE for now.
+ // TODO(henrika): evaluate hardware-offloading. Might complicate usage of
+ // IAudioClient::GetMixFormat().
+ BOOL supports_offload = FALSE;
+ _com_error error =
+ client->IsOffloadCapable(props.eCategory, &supports_offload);
+ if (FAILED(error.Error())) {
+ RTC_LOG(LS_ERROR) << "IAudioClient2::IsOffloadCapable failed: "
+ << ErrorToString(error);
+ }
+ RTC_DLOG(LS_INFO) << "supports_offload: " << supports_offload;
+ props.bIsOffload = false;
+#if (NTDDI_VERSION < NTDDI_WINBLUE)
+ RTC_DLOG(LS_INFO) << "options: Not supported in this build";
+#else
+ // TODO(henrika): pros and cons compared with AUDCLNT_STREAMOPTIONS_NONE?
+ props.Options |= AUDCLNT_STREAMOPTIONS_NONE;
+ // Requires System.Devices.AudioDevice.RawProcessingSupported.
+ // The application can choose to *always ignore* the OEM AEC/AGC by setting
+ // the AUDCLNT_STREAMOPTIONS_RAW flag in the call to SetClientProperties.
+ // This flag will preserve the user experience aspect of Communications
+ // streams, but will not insert any OEM provided communications specific
+ // processing in the audio signal path.
+ // props.Options |= AUDCLNT_STREAMOPTIONS_RAW;
+
+ // If it is important to avoid resampling in the audio engine, set this flag.
+ // AUDCLNT_STREAMOPTIONS_MATCH_FORMAT (or anything in IAudioClient3) is not
+ // an appropriate interface to use for communications scenarios.
+ // This interface is mainly meant for pro audio scenarios.
+ // props.Options |= AUDCLNT_STREAMOPTIONS_MATCH_FORMAT;
+ RTC_DLOG(LS_INFO) << "options: 0x" << rtc::ToHex(props.Options);
+#endif
+ error = client->SetClientProperties(&props);
+ if (FAILED(error.Error())) {
+ RTC_LOG(LS_ERROR) << "IAudioClient2::SetClientProperties failed: "
+ << ErrorToString(error);
+ }
+ return error.Error();
+}
+
+HRESULT GetBufferSizeLimits(IAudioClient2* client,
+ const WAVEFORMATEXTENSIBLE* format,
+ REFERENCE_TIME* min_buffer_duration,
+ REFERENCE_TIME* max_buffer_duration) {
+ RTC_DLOG(LS_INFO) << "GetBufferSizeLimits";
+ RTC_DCHECK(client);
+ if (GetAudioClientVersion() < 2) {
+ RTC_LOG(LS_WARNING) << "Requires IAudioClient2 or higher";
+ return AUDCLNT_E_UNSUPPORTED_FORMAT;
+ }
+ REFERENCE_TIME min_duration = 0;
+ REFERENCE_TIME max_duration = 0;
+ _com_error error =
+ client->GetBufferSizeLimits(reinterpret_cast<const WAVEFORMATEX*>(format),
+ TRUE, &min_duration, &max_duration);
+ if (error.Error() == AUDCLNT_E_OFFLOAD_MODE_ONLY) {
+ // This API seems to be supported in off-load mode only but it is not
+ // documented as a valid error code. Making a special note about it here.
+ RTC_LOG(LS_ERROR) << "IAudioClient2::GetBufferSizeLimits failed: "
+ "AUDCLNT_E_OFFLOAD_MODE_ONLY";
+ } else if (FAILED(error.Error())) {
+ RTC_LOG(LS_ERROR) << "IAudioClient2::GetBufferSizeLimits failed: "
+ << ErrorToString(error);
+ } else {
+ *min_buffer_duration = min_duration;
+ *max_buffer_duration = max_duration;
+ RTC_DLOG(LS_INFO) << "min_buffer_duration: " << min_buffer_duration;
+ RTC_DLOG(LS_INFO) << "max_buffer_duration: " << max_buffer_duration;
+ }
+ return error.Error();
+}
+
+HRESULT GetSharedModeMixFormat(IAudioClient* client,
+ WAVEFORMATEXTENSIBLE* format) {
+ RTC_DLOG(LS_INFO) << "GetSharedModeMixFormat";
+ RTC_DCHECK(client);
+
+ // The GetMixFormat method retrieves the stream format that the audio engine
+ // uses for its internal processing of shared-mode streams. The method
+ // allocates the storage for the structure and this memory will be released
+ // when `mix_format` goes out of scope. The GetMixFormat method retrieves a
+ // format descriptor that is in the form of a WAVEFORMATEXTENSIBLE structure
+ // instead of a standalone WAVEFORMATEX structure. The method outputs a
+ // pointer to the WAVEFORMATEX structure that is embedded at the start of
+ // this WAVEFORMATEXTENSIBLE structure.
+ // Note that, crbug/803056 indicates that some devices can return a format
+ // where only the WAVEFORMATEX parts is initialized and we must be able to
+ // account for that.
+ ScopedCoMem<WAVEFORMATEXTENSIBLE> mix_format;
+ _com_error error =
+ client->GetMixFormat(reinterpret_cast<WAVEFORMATEX**>(&mix_format));
+ if (FAILED(error.Error())) {
+ RTC_LOG(LS_ERROR) << "IAudioClient::GetMixFormat failed: "
+ << ErrorToString(error);
+ return error.Error();
+ }
+
+ // Use a wave format wrapper to make things simpler.
+ WaveFormatWrapper wrapped_format(mix_format.Get());
+
+ // Verify that the reported format can be mixed by the audio engine in
+ // shared mode.
+ if (!wrapped_format.IsPcm() && !wrapped_format.IsFloat()) {
+ RTC_DLOG(LS_ERROR)
+ << "Only pure PCM or float audio streams can be mixed in shared mode";
+ return AUDCLNT_E_UNSUPPORTED_FORMAT;
+ }
+
+ // Log a warning for the rare case where `mix_format` only contains a
+ // stand-alone WAVEFORMATEX structure but don't return.
+ if (!wrapped_format.IsExtensible()) {
+ RTC_DLOG(LS_WARNING)
+ << "The returned format contains no extended information. "
+ "The size is "
+ << wrapped_format.size() << " bytes.";
+ }
+
+ // Copy the correct number of bytes into |*format| taking into account if
+ // the returned structure is correctly extended or not.
+ RTC_CHECK_LE(wrapped_format.size(), sizeof(WAVEFORMATEXTENSIBLE));
+ memcpy(format, wrapped_format.get(), wrapped_format.size());
+ RTC_DLOG(LS_INFO) << WaveFormatToString(format);
+
+ return error.Error();
+}
+
+bool IsFormatSupported(IAudioClient* client,
+ AUDCLNT_SHAREMODE share_mode,
+ const WAVEFORMATEXTENSIBLE* format) {
+ RTC_DLOG(LS_INFO) << "IsFormatSupported";
+ RTC_DCHECK(client);
+ ScopedCoMem<WAVEFORMATEX> closest_match;
+ // This method provides a way for a client to determine, before calling
+ // IAudioClient::Initialize, whether the audio engine supports a particular
+ // stream format or not. In shared mode, the audio engine always supports
+ // the mix format (see GetSharedModeMixFormat).
+ // TODO(henrika): verify support for exclusive mode as well?
+ _com_error error = client->IsFormatSupported(
+ share_mode, reinterpret_cast<const WAVEFORMATEX*>(format),
+ &closest_match);
+ RTC_LOG(LS_INFO) << WaveFormatToString(
+ const_cast<WAVEFORMATEXTENSIBLE*>(format));
+ if ((error.Error() == S_OK) && (closest_match == nullptr)) {
+ RTC_DLOG(LS_INFO)
+ << "The audio endpoint device supports the specified stream format";
+ } else if ((error.Error() == S_FALSE) && (closest_match != nullptr)) {
+ // Call succeeded with a closest match to the specified format. This log can
+ // only be triggered for shared mode.
+ RTC_LOG(LS_WARNING)
+ << "Exact format is not supported, but a closest match exists";
+ RTC_LOG(LS_INFO) << WaveFormatToString(closest_match.Get());
+ } else if ((error.Error() == AUDCLNT_E_UNSUPPORTED_FORMAT) &&
+ (closest_match == nullptr)) {
+ // The audio engine does not support the caller-specified format or any
+ // similar format.
+ RTC_DLOG(LS_INFO) << "The audio endpoint device does not support the "
+ "specified stream format";
+ } else {
+ RTC_LOG(LS_ERROR) << "IAudioClient::IsFormatSupported failed: "
+ << ErrorToString(error);
+ }
+
+ return (error.Error() == S_OK);
+}
+
+HRESULT GetDevicePeriod(IAudioClient* client,
+ AUDCLNT_SHAREMODE share_mode,
+ REFERENCE_TIME* device_period) {
+ RTC_DLOG(LS_INFO) << "GetDevicePeriod";
+ RTC_DCHECK(client);
+ // The `default_period` parameter specifies the default scheduling period
+ // for a shared-mode stream. The `minimum_period` parameter specifies the
+ // minimum scheduling period for an exclusive-mode stream.
+ // The time is expressed in 100-nanosecond units.
+ REFERENCE_TIME default_period = 0;
+ REFERENCE_TIME minimum_period = 0;
+ _com_error error = client->GetDevicePeriod(&default_period, &minimum_period);
+ if (FAILED(error.Error())) {
+ RTC_LOG(LS_ERROR) << "IAudioClient::GetDevicePeriod failed: "
+ << ErrorToString(error);
+ return error.Error();
+ }
+
+ *device_period = (share_mode == AUDCLNT_SHAREMODE_SHARED) ? default_period
+ : minimum_period;
+ RTC_LOG(LS_INFO) << "device_period: "
+ << ReferenceTimeToTimeDelta(*device_period).ms() << " [ms]";
+ RTC_LOG(LS_INFO) << "minimum_period: "
+ << ReferenceTimeToTimeDelta(minimum_period).ms() << " [ms]";
+ return error.Error();
+}
+
+HRESULT GetSharedModeEnginePeriod(IAudioClient3* client3,
+ const WAVEFORMATEXTENSIBLE* format,
+ uint32_t* default_period_in_frames,
+ uint32_t* fundamental_period_in_frames,
+ uint32_t* min_period_in_frames,
+ uint32_t* max_period_in_frames) {
+ RTC_DLOG(LS_INFO) << "GetSharedModeEnginePeriod";
+ RTC_DCHECK(client3);
+
+ UINT32 default_period = 0;
+ UINT32 fundamental_period = 0;
+ UINT32 min_period = 0;
+ UINT32 max_period = 0;
+ _com_error error = client3->GetSharedModeEnginePeriod(
+ reinterpret_cast<const WAVEFORMATEX*>(format), &default_period,
+ &fundamental_period, &min_period, &max_period);
+ if (FAILED(error.Error())) {
+ RTC_LOG(LS_ERROR) << "IAudioClient3::GetSharedModeEnginePeriod failed: "
+ << ErrorToString(error);
+ return error.Error();
+ }
+
+ WAVEFORMATEX format_ex = format->Format;
+ const WORD sample_rate = format_ex.nSamplesPerSec;
+ RTC_LOG(LS_INFO) << "default_period_in_frames: " << default_period << " ("
+ << FramesToMilliseconds(default_period, sample_rate)
+ << " ms)";
+ RTC_LOG(LS_INFO) << "fundamental_period_in_frames: " << fundamental_period
+ << " ("
+ << FramesToMilliseconds(fundamental_period, sample_rate)
+ << " ms)";
+ RTC_LOG(LS_INFO) << "min_period_in_frames: " << min_period << " ("
+ << FramesToMilliseconds(min_period, sample_rate) << " ms)";
+ RTC_LOG(LS_INFO) << "max_period_in_frames: " << max_period << " ("
+ << FramesToMilliseconds(max_period, sample_rate) << " ms)";
+ *default_period_in_frames = default_period;
+ *fundamental_period_in_frames = fundamental_period;
+ *min_period_in_frames = min_period;
+ *max_period_in_frames = max_period;
+ return error.Error();
+}
+
+HRESULT GetPreferredAudioParameters(IAudioClient* client,
+ AudioParameters* params) {
+ RTC_DLOG(LS_INFO) << "GetPreferredAudioParameters";
+ RTC_DCHECK(client);
+ return GetPreferredAudioParametersInternal(client, params, -1);
+}
+
+HRESULT GetPreferredAudioParameters(IAudioClient* client,
+ webrtc::AudioParameters* params,
+ uint32_t sample_rate) {
+ RTC_DLOG(LS_INFO) << "GetPreferredAudioParameters: " << sample_rate;
+ RTC_DCHECK(client);
+ return GetPreferredAudioParametersInternal(client, params, sample_rate);
+}
+
+HRESULT SharedModeInitialize(IAudioClient* client,
+ const WAVEFORMATEXTENSIBLE* format,
+ HANDLE event_handle,
+ REFERENCE_TIME buffer_duration,
+ bool auto_convert_pcm,
+ uint32_t* endpoint_buffer_size) {
+ RTC_DLOG(LS_INFO) << "SharedModeInitialize: buffer_duration="
+ << buffer_duration
+ << ", auto_convert_pcm=" << auto_convert_pcm;
+ RTC_DCHECK(client);
+ RTC_DCHECK_GE(buffer_duration, 0);
+ if (buffer_duration != 0) {
+ RTC_DLOG(LS_WARNING) << "Non-default buffer size is used";
+ }
+ if (auto_convert_pcm) {
+ RTC_DLOG(LS_WARNING) << "Sample rate converter can be utilized";
+ }
+ // The AUDCLNT_STREAMFLAGS_NOPERSIST flag disables persistence of the volume
+ // and mute settings for a session that contains rendering streams.
+ // By default, the volume level and muting state for a rendering session are
+ // persistent across system restarts. The volume level and muting state for a
+ // capture session are never persistent.
+ DWORD stream_flags = AUDCLNT_STREAMFLAGS_NOPERSIST;
+
+ // Enable event-driven streaming if a valid event handle is provided.
+ // After the stream starts, the audio engine will signal the event handle
+ // to notify the client each time a buffer becomes ready to process.
+ // Event-driven buffering is supported for both rendering and capturing.
+ // Both shared-mode and exclusive-mode streams can use event-driven buffering.
+ bool use_event =
+ (event_handle != nullptr && event_handle != INVALID_HANDLE_VALUE);
+ if (use_event) {
+ stream_flags |= AUDCLNT_STREAMFLAGS_EVENTCALLBACK;
+ RTC_DLOG(LS_INFO) << "The stream is initialized to be event driven";
+ }
+
+ // Check if sample-rate conversion is requested.
+ if (auto_convert_pcm) {
+ // Add channel matrixer (not utilized here) and rate converter to convert
+ // from our (the client's) format to the audio engine mix format.
+ // Currently only supported for testing, i.e., not possible to enable using
+ // public APIs.
+ RTC_DLOG(LS_INFO) << "The stream is initialized to support rate conversion";
+ stream_flags |= AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM;
+ stream_flags |= AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY;
+ }
+ RTC_DLOG(LS_INFO) << "stream_flags: 0x" << rtc::ToHex(stream_flags);
+
+ // Initialize the shared mode client for minimal delay if `buffer_duration`
+ // is 0 or possibly a higher delay (more robust) if `buffer_duration` is
+ // larger than 0. The actual size is given by IAudioClient::GetBufferSize().
+ _com_error error = client->Initialize(
+ AUDCLNT_SHAREMODE_SHARED, stream_flags, buffer_duration, 0,
+ reinterpret_cast<const WAVEFORMATEX*>(format), nullptr);
+ if (FAILED(error.Error())) {
+ RTC_LOG(LS_ERROR) << "IAudioClient::Initialize failed: "
+ << ErrorToString(error);
+ return error.Error();
+ }
+
+ // If a stream is initialized to be event driven and in shared mode, the
+ // associated application must also obtain a handle by making a call to
+ // IAudioClient::SetEventHandle.
+ if (use_event) {
+ error = client->SetEventHandle(event_handle);
+ if (FAILED(error.Error())) {
+ RTC_LOG(LS_ERROR) << "IAudioClient::SetEventHandle failed: "
+ << ErrorToString(error);
+ return error.Error();
+ }
+ }
+
+ UINT32 buffer_size_in_frames = 0;
+ // Retrieves the size (maximum capacity) of the endpoint buffer. The size is
+ // expressed as the number of audio frames the buffer can hold.
+ // For rendering clients, the buffer length determines the maximum amount of
+ // rendering data that the application can write to the endpoint buffer
+ // during a single processing pass. For capture clients, the buffer length
+ // determines the maximum amount of capture data that the audio engine can
+ // read from the endpoint buffer during a single processing pass.
+ error = client->GetBufferSize(&buffer_size_in_frames);
+ if (FAILED(error.Error())) {
+ RTC_LOG(LS_ERROR) << "IAudioClient::GetBufferSize failed: "
+ << ErrorToString(error);
+ return error.Error();
+ }
+
+ *endpoint_buffer_size = buffer_size_in_frames;
+ RTC_DLOG(LS_INFO) << "endpoint buffer size: " << buffer_size_in_frames
+ << " [audio frames]";
+ const double size_in_ms = static_cast<double>(buffer_size_in_frames) /
+ (format->Format.nSamplesPerSec / 1000.0);
+ RTC_DLOG(LS_INFO) << "endpoint buffer size: "
+ << static_cast<int>(size_in_ms + 0.5) << " [ms]";
+ RTC_DLOG(LS_INFO) << "bytes per audio frame: " << format->Format.nBlockAlign;
+ RTC_DLOG(LS_INFO) << "endpoint buffer size: "
+ << buffer_size_in_frames * format->Format.nChannels *
+ (format->Format.wBitsPerSample / 8)
+ << " [bytes]";
+
+ // TODO(henrika): utilize when delay measurements are added.
+ REFERENCE_TIME latency = 0;
+ error = client->GetStreamLatency(&latency);
+ RTC_DLOG(LS_INFO) << "stream latency: "
+ << ReferenceTimeToTimeDelta(latency).ms() << " [ms]";
+ return error.Error();
+}
+
+HRESULT SharedModeInitializeLowLatency(IAudioClient3* client,
+ const WAVEFORMATEXTENSIBLE* format,
+ HANDLE event_handle,
+ uint32_t period_in_frames,
+ bool auto_convert_pcm,
+ uint32_t* endpoint_buffer_size) {
+ RTC_DLOG(LS_INFO) << "SharedModeInitializeLowLatency: period_in_frames="
+ << period_in_frames
+ << ", auto_convert_pcm=" << auto_convert_pcm;
+ RTC_DCHECK(client);
+ RTC_DCHECK_GT(period_in_frames, 0);
+ if (auto_convert_pcm) {
+ RTC_DLOG(LS_WARNING) << "Sample rate converter is enabled";
+ }
+
+ // Define stream flags.
+ DWORD stream_flags = AUDCLNT_STREAMFLAGS_NOPERSIST;
+ bool use_event =
+ (event_handle != nullptr && event_handle != INVALID_HANDLE_VALUE);
+ if (use_event) {
+ stream_flags |= AUDCLNT_STREAMFLAGS_EVENTCALLBACK;
+ RTC_DLOG(LS_INFO) << "The stream is initialized to be event driven";
+ }
+ if (auto_convert_pcm) {
+ stream_flags |= AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM;
+ stream_flags |= AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY;
+ }
+ RTC_DLOG(LS_INFO) << "stream_flags: 0x" << rtc::ToHex(stream_flags);
+
+ // Initialize the shared mode client for lowest possible latency.
+ // It is assumed that GetSharedModeEnginePeriod() has been used to query the
+ // smallest possible engine period and that it is given by `period_in_frames`.
+ _com_error error = client->InitializeSharedAudioStream(
+ stream_flags, period_in_frames,
+ reinterpret_cast<const WAVEFORMATEX*>(format), nullptr);
+ if (FAILED(error.Error())) {
+ RTC_LOG(LS_ERROR) << "IAudioClient3::InitializeSharedAudioStream failed: "
+ << ErrorToString(error);
+ return error.Error();
+ }
+
+ // Set the event handle.
+ if (use_event) {
+ error = client->SetEventHandle(event_handle);
+ if (FAILED(error.Error())) {
+ RTC_LOG(LS_ERROR) << "IAudioClient::SetEventHandle failed: "
+ << ErrorToString(error);
+ return error.Error();
+ }
+ }
+
+ UINT32 buffer_size_in_frames = 0;
+ // Retrieve the size (maximum capacity) of the endpoint buffer.
+ error = client->GetBufferSize(&buffer_size_in_frames);
+ if (FAILED(error.Error())) {
+ RTC_LOG(LS_ERROR) << "IAudioClient::GetBufferSize failed: "
+ << ErrorToString(error);
+ return error.Error();
+ }
+
+ *endpoint_buffer_size = buffer_size_in_frames;
+ RTC_DLOG(LS_INFO) << "endpoint buffer size: " << buffer_size_in_frames
+ << " [audio frames]";
+ const double size_in_ms = static_cast<double>(buffer_size_in_frames) /
+ (format->Format.nSamplesPerSec / 1000.0);
+ RTC_DLOG(LS_INFO) << "endpoint buffer size: "
+ << static_cast<int>(size_in_ms + 0.5) << " [ms]";
+ RTC_DLOG(LS_INFO) << "bytes per audio frame: " << format->Format.nBlockAlign;
+ RTC_DLOG(LS_INFO) << "endpoint buffer size: "
+ << buffer_size_in_frames * format->Format.nChannels *
+ (format->Format.wBitsPerSample / 8)
+ << " [bytes]";
+
+ // TODO(henrika): utilize when delay measurements are added.
+ REFERENCE_TIME latency = 0;
+ error = client->GetStreamLatency(&latency);
+ if (FAILED(error.Error())) {
+ RTC_LOG(LS_WARNING) << "IAudioClient::GetStreamLatency failed: "
+ << ErrorToString(error);
+ } else {
+ RTC_DLOG(LS_INFO) << "stream latency: "
+ << ReferenceTimeToTimeDelta(latency).ms() << " [ms]";
+ }
+ return error.Error();
+}
+
+ComPtr<IAudioRenderClient> CreateRenderClient(IAudioClient* client) {
+ RTC_DLOG(LS_INFO) << "CreateRenderClient";
+ RTC_DCHECK(client);
+ // Get access to the IAudioRenderClient interface. This interface
+ // enables us to write output data to a rendering endpoint buffer.
+ ComPtr<IAudioRenderClient> audio_render_client;
+ _com_error error = client->GetService(IID_PPV_ARGS(&audio_render_client));
+ if (FAILED(error.Error())) {
+ RTC_LOG(LS_ERROR)
+ << "IAudioClient::GetService(IID_IAudioRenderClient) failed: "
+ << ErrorToString(error);
+ return ComPtr<IAudioRenderClient>();
+ }
+ return audio_render_client;
+}
+
+ComPtr<IAudioCaptureClient> CreateCaptureClient(IAudioClient* client) {
+ RTC_DLOG(LS_INFO) << "CreateCaptureClient";
+ RTC_DCHECK(client);
+ // Get access to the IAudioCaptureClient interface. This interface
+ // enables us to read input data from a capturing endpoint buffer.
+ ComPtr<IAudioCaptureClient> audio_capture_client;
+ _com_error error = client->GetService(IID_PPV_ARGS(&audio_capture_client));
+ if (FAILED(error.Error())) {
+ RTC_LOG(LS_ERROR)
+ << "IAudioClient::GetService(IID_IAudioCaptureClient) failed: "
+ << ErrorToString(error);
+ return ComPtr<IAudioCaptureClient>();
+ }
+ return audio_capture_client;
+}
+
+ComPtr<IAudioClock> CreateAudioClock(IAudioClient* client) {
+ RTC_DLOG(LS_INFO) << "CreateAudioClock";
+ RTC_DCHECK(client);
+ // Get access to the IAudioClock interface. This interface enables us to
+ // monitor a stream's data rate and the current position in the stream.
+ ComPtr<IAudioClock> audio_clock;
+ _com_error error = client->GetService(IID_PPV_ARGS(&audio_clock));
+ if (FAILED(error.Error())) {
+ RTC_LOG(LS_ERROR) << "IAudioClient::GetService(IID_IAudioClock) failed: "
+ << ErrorToString(error);
+ return ComPtr<IAudioClock>();
+ }
+ return audio_clock;
+}
+
+ComPtr<IAudioSessionControl> CreateAudioSessionControl(IAudioClient* client) {
+ RTC_DLOG(LS_INFO) << "CreateAudioSessionControl";
+ RTC_DCHECK(client);
+ ComPtr<IAudioSessionControl> audio_session_control;
+ _com_error error = client->GetService(IID_PPV_ARGS(&audio_session_control));
+ if (FAILED(error.Error())) {
+ RTC_LOG(LS_ERROR) << "IAudioClient::GetService(IID_IAudioControl) failed: "
+ << ErrorToString(error);
+ return ComPtr<IAudioSessionControl>();
+ }
+ return audio_session_control;
+}
+
+ComPtr<ISimpleAudioVolume> CreateSimpleAudioVolume(IAudioClient* client) {
+ RTC_DLOG(LS_INFO) << "CreateSimpleAudioVolume";
+ RTC_DCHECK(client);
+ // Get access to the ISimpleAudioVolume interface. This interface enables a
+ // client to control the master volume level of an audio session.
+ ComPtr<ISimpleAudioVolume> simple_audio_volume;
+ _com_error error = client->GetService(IID_PPV_ARGS(&simple_audio_volume));
+ if (FAILED(error.Error())) {
+ RTC_LOG(LS_ERROR)
+ << "IAudioClient::GetService(IID_ISimpleAudioVolume) failed: "
+ << ErrorToString(error);
+ return ComPtr<ISimpleAudioVolume>();
+ }
+ return simple_audio_volume;
+}
+
+bool FillRenderEndpointBufferWithSilence(IAudioClient* client,
+ IAudioRenderClient* render_client) {
+ RTC_DLOG(LS_INFO) << "FillRenderEndpointBufferWithSilence";
+ RTC_DCHECK(client);
+ RTC_DCHECK(render_client);
+ UINT32 endpoint_buffer_size = 0;
+ _com_error error = client->GetBufferSize(&endpoint_buffer_size);
+ if (FAILED(error.Error())) {
+ RTC_LOG(LS_ERROR) << "IAudioClient::GetBufferSize failed: "
+ << ErrorToString(error);
+ return false;
+ }
+
+ UINT32 num_queued_frames = 0;
+ // Get number of audio frames that are queued up to play in the endpoint
+ // buffer.
+ error = client->GetCurrentPadding(&num_queued_frames);
+ if (FAILED(error.Error())) {
+ RTC_LOG(LS_ERROR) << "IAudioClient::GetCurrentPadding failed: "
+ << ErrorToString(error);
+ return false;
+ }
+ RTC_DLOG(LS_INFO) << "num_queued_frames: " << num_queued_frames;
+
+ BYTE* data = nullptr;
+ int num_frames_to_fill = endpoint_buffer_size - num_queued_frames;
+ RTC_DLOG(LS_INFO) << "num_frames_to_fill: " << num_frames_to_fill;
+ error = render_client->GetBuffer(num_frames_to_fill, &data);
+ if (FAILED(error.Error())) {
+ RTC_LOG(LS_ERROR) << "IAudioRenderClient::GetBuffer failed: "
+ << ErrorToString(error);
+ return false;
+ }
+
+ // Using the AUDCLNT_BUFFERFLAGS_SILENT flag eliminates the need to
+ // explicitly write silence data to the rendering buffer.
+ error = render_client->ReleaseBuffer(num_frames_to_fill,
+ AUDCLNT_BUFFERFLAGS_SILENT);
+ if (FAILED(error.Error())) {
+ RTC_LOG(LS_ERROR) << "IAudioRenderClient::ReleaseBuffer failed: "
+ << ErrorToString(error);
+ return false;
+ }
+
+ return true;
+}
+
+std::string WaveFormatToString(const WaveFormatWrapper format) {
+ char ss_buf[1024];
+ rtc::SimpleStringBuilder ss(ss_buf);
+ // Start with the WAVEFORMATEX part (which always exists).
+ ss.AppendFormat("wFormatTag: %s (0x%X)",
+ WaveFormatTagToString(format->wFormatTag),
+ format->wFormatTag);
+ ss.AppendFormat(", nChannels: %d", format->nChannels);
+ ss.AppendFormat(", nSamplesPerSec: %d", format->nSamplesPerSec);
+ ss.AppendFormat(", nAvgBytesPerSec: %d", format->nAvgBytesPerSec);
+ ss.AppendFormat(", nBlockAlign: %d", format->nBlockAlign);
+ ss.AppendFormat(", wBitsPerSample: %d", format->wBitsPerSample);
+ ss.AppendFormat(", cbSize: %d", format->cbSize);
+ if (!format.IsExtensible())
+ return ss.str();
+
+ // Append the WAVEFORMATEXTENSIBLE part (which we know exists).
+ ss.AppendFormat(
+ " [+] wValidBitsPerSample: %d, dwChannelMask: %s",
+ format.GetExtensible()->Samples.wValidBitsPerSample,
+ ChannelMaskToString(format.GetExtensible()->dwChannelMask).c_str());
+ if (format.IsPcm()) {
+ ss.AppendFormat("%s", ", SubFormat: KSDATAFORMAT_SUBTYPE_PCM");
+ } else if (format.IsFloat()) {
+ ss.AppendFormat("%s", ", SubFormat: KSDATAFORMAT_SUBTYPE_IEEE_FLOAT");
+ } else {
+ ss.AppendFormat("%s", ", SubFormat: NOT_SUPPORTED");
+ }
+ return ss.str();
+}
+
+webrtc::TimeDelta ReferenceTimeToTimeDelta(REFERENCE_TIME time) {
+ // Each unit of reference time is 100 nanoseconds <=> 0.1 microsecond.
+ return webrtc::TimeDelta::Micros(0.1 * time + 0.5);
+}
+
+double FramesToMilliseconds(uint32_t num_frames, uint16_t sample_rate) {
+ // Convert the current period in frames into milliseconds.
+ return static_cast<double>(num_frames) / (sample_rate / 1000.0);
+}
+
+std::string ErrorToString(const _com_error& error) {
+ char ss_buf[1024];
+ rtc::SimpleStringBuilder ss(ss_buf);
+ ss.AppendFormat("(HRESULT: 0x%08X)", error.Error());
+ return ss.str();
+}
+
+} // namespace core_audio_utility
+} // namespace webrtc_win
+} // namespace webrtc