| /* |
| * 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 "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; |
| } |
| |
| // Converts from channel mask to DirectSound speaker configuration. |
| // The values below are copied from ksmedia.h. |
| // Example: KSAUDIO_SPEAKER_STEREO = (SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT). |
| const char* DirectSoundConfigToString(DWORD channel_mask) { |
| switch (channel_mask) { |
| case KSAUDIO_SPEAKER_DIRECTOUT: |
| return "KSAUDIO_DIRECTOUT"; |
| case KSAUDIO_SPEAKER_MONO: |
| // Front center (C) |
| return "KSAUDIO_MONO"; |
| case KSAUDIO_SPEAKER_1POINT1: |
| return "KSAUDIO_1POINT1"; |
| case KSAUDIO_SPEAKER_STEREO: |
| // Front left (L), front right (R). |
| return "KSAUDIO_STEREO"; |
| case KSAUDIO_SPEAKER_2POINT1: |
| return "KSAUDIO_2POINT1"; |
| case KSAUDIO_SPEAKER_3POINT0: |
| return "KSAUDIO_3POINT0"; |
| case KSAUDIO_SPEAKER_3POINT1: |
| return "KSAUDIO_3POINT1"; |
| case KSAUDIO_SPEAKER_QUAD: |
| // L, R, back left (Lb), back right (Rb). |
| return "KSAUDIO_QUAD"; |
| case KSAUDIO_SPEAKER_SURROUND: |
| // L, R, front center (C), back center (Cb). |
| return "KSAUDIO_SURROUND"; |
| case KSAUDIO_SPEAKER_5POINT0: |
| return "KSAUDIO_5POINT0"; |
| case KSAUDIO_SPEAKER_5POINT1: |
| return "KSAUDIO_5POINT1"; |
| case KSAUDIO_SPEAKER_7POINT0: |
| return "KSAUDIO_7POINT0"; |
| case KSAUDIO_SPEAKER_7POINT1: |
| // L, R, C, Lb, Rb, front left-of-center, front right-of-center, LFE. |
| return "KSAUDIO_7POINT1"; |
| case KSAUDIO_SPEAKER_5POINT1_SURROUND: |
| // L, R, C, side left (Ls), side right (Rs), LFE. |
| return "KSAUDIO_5POINT1_SURROUND"; |
| case KSAUDIO_SPEAKER_7POINT1_SURROUND: |
| // L, R, C, Lb, Rb, Ls, Rs, LFE. |
| return "KSAUDIO_7POINT1_SURROUND"; |
| default: |
| return "KSAUDIO_INVALID"; |
| } |
| } |
| |
| 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(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(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(const std::string& device_id, |
| EDataFlow data_flow, |
| ERole role) { |
| RTC_DLOG(INFO) << "CreateDeviceInternal: " << 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) { |
| 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 { |
| 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()) && |
| !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) { |
| // Always add the default device in index 0 and the default communication |
| // device as index 1 in the vector. The name of the default device starts |
| // with "Default - " and the default communication device starts with |
| // "Communication - ". |
| // Example of friendly name: "Default - Headset (SB Arena Headset)" |
| ERole role[] = {eConsole, eCommunications}; |
| 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]); |
| 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); |
| RTC_DLOG(INFO) << "friendly name: " << default_device_name.device_name; |
| RTC_DLOG(INFO) << "unique id : " << default_device_name.unique_id; |
| // Add combination of user-friendly and unique name to the output list. |
| device_names->emplace_back(default_device_name); |
| } |
| |
| // Next, add all active input devices on index 2 and above. Note that, |
| // one device can have more than one role. Hence, if only one input device |
| // is present, the output vector will contain three elements all with the |
| // same unique ID but with different names. |
| // Example (one capture device but three elements in device_names): |
| // 0: friendly name: Default - Headset (SB Arena Headset) |
| // 0: unique id : {0.0.1.00000000}.{822d99bb-d9b0-4f6f-b2a5-cd1be220d338} |
| // 1: friendly name: Communication - Headset (SB Arena Headset) |
| // 1: unique id : {0.0.1.00000000}.{822d99bb-d9b0-4f6f-b2a5-cd1be220d338} |
| // 2: friendly name: Headset (SB Arena Headset) |
| // 2: unique id : {0.0.1.00000000}.{822d99bb-d9b0-4f6f-b2a5-cd1be220d338} |
| |
| // Generate a collection of active audio endpoint devices for the specified |
| // direction. |
| ComPtr<IMMDeviceCollection> collection = CreateCollectionInternal(data_flow); |
| if (!collection.Get()) { |
| return false; |
| } |
| |
| // Retrieve the number of active audio devices for the specified direction. |
| UINT number_of_active_devices = 0; |
| collection->GetCount(&number_of_active_devices); |
| if (number_of_active_devices == 0) { |
| return true; |
| } |
| |
| // Loop over all active devices and add friendly name and unique ID to the |
| // |device_names| list which already contains two elements |
| RTC_DCHECK_EQ(device_names->size(), 2); |
| for (UINT i = 0; i < number_of_active_devices; ++i) { |
| // Retrieve a pointer to the specified item in the device collection. |
| ComPtr<IMMDevice> audio_device; |
| _com_error error = collection->Item(i, audio_device.GetAddressOf()); |
| if (FAILED(error.Error())) |
| continue; |
| // Retrieve the complete device name for the given audio device endpoint. |
| AudioDeviceName device_name( |
| GetDeviceFriendlyNameInternal(audio_device.Get()), |
| GetDeviceIdInternal(audio_device.Get())); |
| RTC_DLOG(INFO) << "friendly name: " << device_name.device_name; |
| RTC_DLOG(INFO) << "unique id : " << device_name.unique_id; |
| // Add combination of user-friendly and unique name to the output list. |
| device_names->emplace_back(device_name); |
| } |
| |
| 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(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(INFO) << audio_params.ToString(); |
| |
| return hr; |
| } |
| |
| } // namespace |
| |
| namespace core_audio_utility { |
| |
| bool IsSupported() { |
| RTC_DLOG(INFO) << "IsSupported"; |
| static bool g_is_supported = IsSupportedInternal(); |
| return g_is_supported; |
| } |
| |
| bool IsMMCSSSupported() { |
| RTC_DLOG(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(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(INFO) << "CreateDeviceEnumerator"; |
| return CreateDeviceEnumeratorInternal(true); |
| } |
| |
| std::string GetDefaultInputDeviceID() { |
| RTC_DLOG(INFO) << "GetDefaultInputDeviceID"; |
| ComPtr<IMMDevice> device( |
| CreateDevice(AudioDeviceName::kDefaultDeviceId, eCapture, eConsole)); |
| return device.Get() ? GetDeviceIdInternal(device.Get()) : std::string(); |
| } |
| |
| std::string GetDefaultOutputDeviceID() { |
| RTC_DLOG(INFO) << "GetDefaultOutputDeviceID"; |
| ComPtr<IMMDevice> device( |
| CreateDevice(AudioDeviceName::kDefaultDeviceId, eRender, eConsole)); |
| return device.Get() ? GetDeviceIdInternal(device.Get()) : std::string(); |
| } |
| |
| std::string GetCommunicationsInputDeviceID() { |
| RTC_DLOG(INFO) << "GetCommunicationsInputDeviceID"; |
| ComPtr<IMMDevice> device(CreateDevice(AudioDeviceName::kDefaultDeviceId, |
| eCapture, eCommunications)); |
| return device.Get() ? GetDeviceIdInternal(device.Get()) : std::string(); |
| } |
| |
| std::string GetCommunicationsOutputDeviceID() { |
| RTC_DLOG(INFO) << "GetCommunicationsOutputDeviceID"; |
| ComPtr<IMMDevice> device(CreateDevice(AudioDeviceName::kDefaultDeviceId, |
| eRender, eCommunications)); |
| return device.Get() ? GetDeviceIdInternal(device.Get()) : std::string(); |
| } |
| |
| ComPtr<IMMDevice> CreateDevice(const std::string& device_id, |
| EDataFlow data_flow, |
| ERole role) { |
| RTC_DLOG(INFO) << "CreateDevice"; |
| return CreateDeviceInternal(device_id, data_flow, role); |
| } |
| |
| AudioDeviceName GetDeviceName(IMMDevice* device) { |
| RTC_DLOG(INFO) << "GetDeviceName"; |
| RTC_DCHECK(device); |
| AudioDeviceName device_name(GetDeviceFriendlyNameInternal(device), |
| GetDeviceIdInternal(device)); |
| RTC_DLOG(INFO) << "friendly name: " << device_name.device_name; |
| RTC_DLOG(INFO) << "unique id : " << device_name.unique_id; |
| return device_name; |
| } |
| |
| std::string GetFriendlyName(const std::string& device_id, |
| EDataFlow data_flow, |
| ERole role) { |
| RTC_DLOG(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(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(INFO) << "GetInputDeviceNames"; |
| RTC_DCHECK(device_names); |
| return GetDeviceNamesInternal(eCapture, device_names); |
| } |
| |
| bool GetOutputDeviceNames(webrtc::AudioDeviceNames* device_names) { |
| RTC_DLOG(INFO) << "GetOutputDeviceNames"; |
| RTC_DCHECK(device_names); |
| return GetDeviceNamesInternal(eRender, device_names); |
| } |
| |
| ComPtr<IAudioSessionManager2> CreateSessionManager2(IMMDevice* device) { |
| RTC_DLOG(INFO) << "CreateSessionManager2"; |
| return CreateSessionManager2Internal(device); |
| } |
| |
| Microsoft::WRL::ComPtr<IAudioSessionEnumerator> CreateSessionEnumerator( |
| IMMDevice* device) { |
| RTC_DLOG(INFO) << "CreateSessionEnumerator"; |
| return CreateSessionEnumeratorInternal(device); |
| } |
| |
| int NumberOfActiveSessions(IMMDevice* device) { |
| RTC_DLOG(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(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(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(INFO) << "Number of active audio sessions: " << num_active; |
| return num_active; |
| } |
| |
| ComPtr<IAudioClient> CreateClient(const std::string& device_id, |
| EDataFlow data_flow, |
| ERole role) { |
| RTC_DLOG(INFO) << "CreateClient"; |
| ComPtr<IMMDevice> device(CreateDevice(device_id, data_flow, role)); |
| return CreateClientInternal(device.Get()); |
| } |
| |
| ComPtr<IAudioClient2> CreateClient2(const std::string& device_id, |
| EDataFlow data_flow, |
| ERole role) { |
| RTC_DLOG(INFO) << "CreateClient2"; |
| ComPtr<IMMDevice> device(CreateDevice(device_id, data_flow, role)); |
| return CreateClient2Internal(device.Get()); |
| } |
| |
| ComPtr<IAudioClient3> CreateClient3(const std::string& device_id, |
| EDataFlow data_flow, |
| ERole role) { |
| RTC_DLOG(INFO) << "CreateClient3"; |
| ComPtr<IMMDevice> device(CreateDevice(device_id, data_flow, role)); |
| return CreateClient3Internal(device.Get()); |
| } |
| |
| HRESULT SetClientProperties(IAudioClient2* client) { |
| RTC_DLOG(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(INFO) << "supports_offload: " << supports_offload; |
| props.bIsOffload = false; |
| // 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(INFO) << "options: 0x" << rtc::ToHex(props.Options); |
| 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(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(INFO) << "min_buffer_duration: " << min_buffer_duration; |
| RTC_DLOG(INFO) << "max_buffer_duration: " << max_buffer_duration; |
| } |
| return error.Error(); |
| } |
| |
| HRESULT GetSharedModeMixFormat(IAudioClient* client, |
| WAVEFORMATEXTENSIBLE* format) { |
| RTC_DLOG(INFO) << "GetSharedModeMixFormat"; |
| RTC_DCHECK(client); |
| ScopedCoMem<WAVEFORMATEXTENSIBLE> format_ex; |
| _com_error error = |
| client->GetMixFormat(reinterpret_cast<WAVEFORMATEX**>(&format_ex)); |
| if (FAILED(error.Error())) { |
| RTC_LOG(LS_ERROR) << "IAudioClient::GetMixFormat failed: " |
| << ErrorToString(error); |
| return error.Error(); |
| } |
| |
| size_t bytes = sizeof(WAVEFORMATEX) + format_ex->Format.cbSize; |
| RTC_DCHECK_EQ(bytes, sizeof(WAVEFORMATEXTENSIBLE)); |
| memcpy(format, format_ex, bytes); |
| RTC_DLOG(INFO) << WaveFormatExToString(format); |
| |
| return error.Error(); |
| } |
| |
| bool IsFormatSupported(IAudioClient* client, |
| AUDCLNT_SHAREMODE share_mode, |
| const WAVEFORMATEXTENSIBLE* format) { |
| RTC_DLOG(INFO) << "IsFormatSupported"; |
| RTC_DCHECK(client); |
| ScopedCoMem<WAVEFORMATEXTENSIBLE> 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), |
| reinterpret_cast<WAVEFORMATEX**>(&closest_match)); |
| if ((error.Error() == S_OK) && (closest_match == nullptr)) { |
| RTC_DLOG(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(INFO) << WaveFormatExToString(closest_match); |
| } 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(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(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(INFO) << "device_period: " |
| << ReferenceTimeToTimeDelta(*device_period).ms() << " [ms]"; |
| RTC_LOG(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(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(INFO) << "default_period_in_frames: " << default_period << " (" |
| << FramesToMilliseconds(default_period, sample_rate) << " ms)"; |
| RTC_LOG(INFO) << "fundamental_period_in_frames: " << fundamental_period |
| << " (" << FramesToMilliseconds(fundamental_period, sample_rate) |
| << " ms)"; |
| RTC_LOG(INFO) << "min_period_in_frames: " << min_period << " (" |
| << FramesToMilliseconds(min_period, sample_rate) << " ms)"; |
| RTC_LOG(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(const std::string& device_id, |
| bool is_output_device, |
| AudioParameters* params) { |
| RTC_DLOG(INFO) << "GetPreferredAudioParameters: " << is_output_device; |
| EDataFlow data_flow = is_output_device ? eRender : eCapture; |
| ComPtr<IMMDevice> device; |
| if (device_id == AudioDeviceName::kDefaultCommunicationsDeviceId) { |
| device = CreateDeviceInternal(AudioDeviceName::kDefaultDeviceId, data_flow, |
| eCommunications); |
| } else { |
| // If |device_id| equals AudioDeviceName::kDefaultDeviceId, a default |
| // device will be created. |
| device = CreateDeviceInternal(device_id, data_flow, eConsole); |
| } |
| if (!device.Get()) { |
| return E_FAIL; |
| } |
| |
| ComPtr<IAudioClient> client(CreateClientInternal(device.Get())); |
| if (!client.Get()) |
| return E_FAIL; |
| |
| return GetPreferredAudioParametersInternal(client.Get(), params, -1); |
| } |
| |
| HRESULT GetPreferredAudioParameters(IAudioClient* client, |
| AudioParameters* params) { |
| RTC_DLOG(INFO) << "GetPreferredAudioParameters"; |
| RTC_DCHECK(client); |
| return GetPreferredAudioParametersInternal(client, params, -1); |
| } |
| |
| HRESULT GetPreferredAudioParameters(IAudioClient* client, |
| webrtc::AudioParameters* params, |
| uint32_t sample_rate) { |
| RTC_DLOG(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(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(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(INFO) << "The stream is initialized to support rate conversion"; |
| stream_flags |= AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM; |
| stream_flags |= AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY; |
| } |
| RTC_DLOG(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(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(INFO) << "endpoint buffer size: " |
| << static_cast<int>(size_in_ms + 0.5) << " [ms]"; |
| RTC_DLOG(INFO) << "bytes per audio frame: " << format->Format.nBlockAlign; |
| RTC_DLOG(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(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(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(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(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(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(INFO) << "endpoint buffer size: " |
| << static_cast<int>(size_in_ms + 0.5) << " [ms]"; |
| RTC_DLOG(INFO) << "bytes per audio frame: " << format->Format.nBlockAlign; |
| RTC_DLOG(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(INFO) << "stream latency: " |
| << ReferenceTimeToTimeDelta(latency).ms() << " [ms]"; |
| } |
| return error.Error(); |
| } |
| |
| ComPtr<IAudioRenderClient> CreateRenderClient(IAudioClient* client) { |
| RTC_DLOG(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(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(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(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(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(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(INFO) << "num_queued_frames: " << num_queued_frames; |
| |
| BYTE* data = nullptr; |
| int num_frames_to_fill = endpoint_buffer_size - num_queued_frames; |
| RTC_DLOG(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 WaveFormatExToString(const WAVEFORMATEXTENSIBLE* format) { |
| RTC_DCHECK_EQ(format->Format.wFormatTag, WAVE_FORMAT_EXTENSIBLE); |
| char ss_buf[1024]; |
| rtc::SimpleStringBuilder ss(ss_buf); |
| ss.AppendFormat("wFormatTag: WAVE_FORMAT_EXTENSIBLE"); |
| ss.AppendFormat(", nChannels: %d", format->Format.nChannels); |
| ss.AppendFormat(", nSamplesPerSec: %d", format->Format.nSamplesPerSec); |
| ss.AppendFormat(", nAvgBytesPerSec: %d", format->Format.nAvgBytesPerSec); |
| ss.AppendFormat(", nBlockAlign: %d", format->Format.nBlockAlign); |
| ss.AppendFormat(", wBitsPerSample: %d", format->Format.wBitsPerSample); |
| ss.AppendFormat(", cbSize: %d", format->Format.cbSize); |
| ss.AppendFormat(", wValidBitsPerSample: %d", |
| format->Samples.wValidBitsPerSample); |
| ss.AppendFormat(", dwChannelMask: 0x%X", format->dwChannelMask); |
| if (format->SubFormat == KSDATAFORMAT_SUBTYPE_PCM) { |
| ss << ", SubFormat: KSDATAFORMAT_SUBTYPE_PCM"; |
| } else if (format->SubFormat == KSDATAFORMAT_SUBTYPE_IEEE_FLOAT) { |
| ss << ", SubFormat: KSDATAFORMAT_SUBTYPE_IEEE_FLOAT"; |
| } else { |
| ss << ", SubFormat: NOT_SUPPORTED"; |
| } |
| ss.AppendFormat("\nChannel configuration: %s", |
| ChannelMaskToString(format->dwChannelMask).c_str()); |
| ss.AppendFormat("\nDirectSound configuration : %s", |
| DirectSoundConfigToString(format->dwChannelMask)); |
| return ss.str(); |
| } |
| |
| webrtc::TimeDelta ReferenceTimeToTimeDelta(REFERENCE_TIME time) { |
| // Each unit of reference time is 100 nanoseconds <=> 0.1 microsecond. |
| return webrtc::TimeDelta::us(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 |