/* * 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 #include #include #include #include #include #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 CreateDeviceEnumeratorInternal( bool allow_reinitialize) { ComPtr 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 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 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 audio_endpoint_device; // Create the IMMDeviceEnumerator interface. ComPtr 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 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 CreateSessionManager2Internal( IMMDevice* audio_device) { if (!audio_device) return ComPtr(); ComPtr 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 CreateSessionEnumeratorInternal( IMMDevice* audio_device) { if (!audio_device) { return ComPtr(); } ComPtr audio_session_enumerator; ComPtr 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(); } return audio_session_enumerator; } // Creates and activates an IAudioClient COM object given the selected // endpoint device. ComPtr CreateClientInternal(IMMDevice* audio_device) { if (!audio_device) return ComPtr(); ComPtr 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 CreateClient2Internal(IMMDevice* audio_device) { if (!audio_device) return ComPtr(); ComPtr 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 CreateClient3Internal(IMMDevice* audio_device) { if (!audio_device) return ComPtr(); ComPtr 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 CreateCollectionInternal(EDataFlow data_flow) { ComPtr device_enumerator( CreateDeviceEnumeratorInternal(true)); if (!device_enumerator) { return ComPtr(); } // 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 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 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 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 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( core_audio_utility::ReferenceTimeToTimeDelta(default_period).ms()) / 1000.0L; const size_t frames_per_buffer = static_cast(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(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 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(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 CreateDeviceEnumerator() { RTC_DLOG(LS_INFO) << "CreateDeviceEnumerator"; return CreateDeviceEnumeratorInternal(true); } std::string GetDefaultInputDeviceID() { RTC_DLOG(LS_INFO) << "GetDefaultInputDeviceID"; ComPtr device( CreateDevice(AudioDeviceName::kDefaultDeviceId, eCapture, eConsole)); return device.Get() ? GetDeviceIdInternal(device.Get()) : std::string(); } std::string GetDefaultOutputDeviceID() { RTC_DLOG(LS_INFO) << "GetDefaultOutputDeviceID"; ComPtr device( CreateDevice(AudioDeviceName::kDefaultDeviceId, eRender, eConsole)); return device.Get() ? GetDeviceIdInternal(device.Get()) : std::string(); } std::string GetCommunicationsInputDeviceID() { RTC_DLOG(LS_INFO) << "GetCommunicationsInputDeviceID"; ComPtr device(CreateDevice(AudioDeviceName::kDefaultDeviceId, eCapture, eCommunications)); return device.Get() ? GetDeviceIdInternal(device.Get()) : std::string(); } std::string GetCommunicationsOutputDeviceID() { RTC_DLOG(LS_INFO) << "GetCommunicationsOutputDeviceID"; ComPtr device(CreateDevice(AudioDeviceName::kDefaultDeviceId, eRender, eCommunications)); return device.Get() ? GetDeviceIdInternal(device.Get()) : std::string(); } ComPtr 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 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 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 CreateSessionManager2(IMMDevice* device) { RTC_DLOG(LS_INFO) << "CreateSessionManager2"; return CreateSessionManager2Internal(device); } Microsoft::WRL::ComPtr CreateSessionEnumerator( IMMDevice* device) { RTC_DLOG(LS_INFO) << "CreateSessionEnumerator"; return CreateSessionEnumeratorInternal(device); } int NumberOfActiveSessions(IMMDevice* device) { RTC_DLOG(LS_INFO) << "NumberOfActiveSessions"; ComPtr 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 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 CreateClient(absl::string_view device_id, EDataFlow data_flow, ERole role) { RTC_DLOG(LS_INFO) << "CreateClient"; ComPtr device(CreateDevice(device_id, data_flow, role)); return CreateClientInternal(device.Get()); } ComPtr CreateClient2(absl::string_view device_id, EDataFlow data_flow, ERole role) { RTC_DLOG(LS_INFO) << "CreateClient2"; ComPtr device(CreateDevice(device_id, data_flow, role)); return CreateClient2Internal(device.Get()); } ComPtr CreateClient3(absl::string_view device_id, EDataFlow data_flow, ERole role) { RTC_DLOG(LS_INFO) << "CreateClient3"; ComPtr 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(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 mix_format; _com_error error = client->GetMixFormat(reinterpret_cast(&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 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(format), &closest_match); RTC_LOG(LS_INFO) << WaveFormatToString( const_cast(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(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(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(buffer_size_in_frames) / (format->Format.nSamplesPerSec / 1000.0); RTC_DLOG(LS_INFO) << "endpoint buffer size: " << static_cast(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(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(buffer_size_in_frames) / (format->Format.nSamplesPerSec / 1000.0); RTC_DLOG(LS_INFO) << "endpoint buffer size: " << static_cast(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 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 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(); } return audio_render_client; } ComPtr 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 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(); } return audio_capture_client; } ComPtr 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 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(); } return audio_clock; } ComPtr CreateAudioSessionControl(IAudioClient* client) { RTC_DLOG(LS_INFO) << "CreateAudioSessionControl"; RTC_DCHECK(client); ComPtr 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(); } return audio_session_control; } ComPtr 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 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(); } 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(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