Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(821)

Unified Diff: media/audio/win/core_audio_util_win.cc

Issue 2144333002: MuteSource Audio During Full Screen Cast (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Run Git Cl Format Created 4 years, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: media/audio/win/core_audio_util_win.cc
diff --git a/media/audio/win/core_audio_util_win.cc b/media/audio/win/core_audio_util_win.cc
index d00d3608339d6497862905a013ff9542eb01ef8a..91064644f4148706788e72e0214f7df113d31898 100644
--- a/media/audio/win/core_audio_util_win.cc
+++ b/media/audio/win/core_audio_util_win.cc
@@ -1,973 +1,973 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "media/audio/win/core_audio_util_win.h"
-
-#include <devicetopology.h>
-#include <dxdiag.h>
-#include <functiondiscoverykeys_devpkey.h>
-#include <stddef.h>
-
-#include "base/command_line.h"
-#include "base/logging.h"
-#include "base/macros.h"
-#include "base/strings/stringprintf.h"
-#include "base/strings/utf_string_conversions.h"
-#include "base/win/scoped_co_mem.h"
-#include "base/win/scoped_handle.h"
-#include "base/win/scoped_propvariant.h"
-#include "base/win/scoped_variant.h"
-#include "base/win/windows_version.h"
-#include "media/audio/audio_device_description.h"
-#include "media/base/media_switches.h"
-
-using base::win::ScopedCoMem;
-using base::win::ScopedHandle;
-
-namespace media {
-
-// See header file for documentation.
-// {BE39AF4F-087C-423F-9303-234EC1E5B8EE}
-const GUID kCommunicationsSessionId = {
- 0xbe39af4f, 0x87c, 0x423f, { 0x93, 0x3, 0x23, 0x4e, 0xc1, 0xe5, 0xb8, 0xee }
-};
-
-enum { KSAUDIO_SPEAKER_UNSUPPORTED = 0 };
-
-// Converts Microsoft's channel configuration to ChannelLayout.
-// This mapping is not perfect but the best we can do given the current
-// ChannelLayout enumerator and the Windows-specific speaker configurations
-// defined in ksmedia.h. Don't assume that the channel ordering in
-// ChannelLayout is exactly the same as the Windows specific configuration.
-// As an example: KSAUDIO_SPEAKER_7POINT1_SURROUND is mapped to
-// CHANNEL_LAYOUT_7_1 but the positions of Back L, Back R and Side L, Side R
-// speakers are different in these two definitions.
-static ChannelLayout ChannelConfigToChannelLayout(ChannelConfig config) {
- switch (config) {
- case KSAUDIO_SPEAKER_DIRECTOUT:
- DVLOG(2) << "KSAUDIO_SPEAKER_DIRECTOUT=>CHANNEL_LAYOUT_NONE";
- return CHANNEL_LAYOUT_NONE;
- case KSAUDIO_SPEAKER_MONO:
- DVLOG(2) << "KSAUDIO_SPEAKER_MONO=>CHANNEL_LAYOUT_MONO";
- return CHANNEL_LAYOUT_MONO;
- case KSAUDIO_SPEAKER_STEREO:
- DVLOG(2) << "KSAUDIO_SPEAKER_STEREO=>CHANNEL_LAYOUT_STEREO";
- return CHANNEL_LAYOUT_STEREO;
- case KSAUDIO_SPEAKER_QUAD:
- DVLOG(2) << "KSAUDIO_SPEAKER_QUAD=>CHANNEL_LAYOUT_QUAD";
- return CHANNEL_LAYOUT_QUAD;
- case KSAUDIO_SPEAKER_SURROUND:
- DVLOG(2) << "KSAUDIO_SPEAKER_SURROUND=>CHANNEL_LAYOUT_4_0";
- return CHANNEL_LAYOUT_4_0;
- case KSAUDIO_SPEAKER_5POINT1:
- DVLOG(2) << "KSAUDIO_SPEAKER_5POINT1=>CHANNEL_LAYOUT_5_1_BACK";
- return CHANNEL_LAYOUT_5_1_BACK;
- case KSAUDIO_SPEAKER_5POINT1_SURROUND:
- DVLOG(2) << "KSAUDIO_SPEAKER_5POINT1_SURROUND=>CHANNEL_LAYOUT_5_1";
- return CHANNEL_LAYOUT_5_1;
- case KSAUDIO_SPEAKER_7POINT1:
- DVLOG(2) << "KSAUDIO_SPEAKER_7POINT1=>CHANNEL_LAYOUT_7_1_WIDE";
- return CHANNEL_LAYOUT_7_1_WIDE;
- case KSAUDIO_SPEAKER_7POINT1_SURROUND:
- DVLOG(2) << "KSAUDIO_SPEAKER_7POINT1_SURROUND=>CHANNEL_LAYOUT_7_1";
- return CHANNEL_LAYOUT_7_1;
- default:
- DVLOG(2) << "Unsupported channel configuration: " << config;
- return CHANNEL_LAYOUT_UNSUPPORTED;
- }
-}
-
-// TODO(henrika): add mapping for all types in the ChannelLayout enumerator.
-static ChannelConfig ChannelLayoutToChannelConfig(ChannelLayout layout) {
- switch (layout) {
- case CHANNEL_LAYOUT_NONE:
- DVLOG(2) << "CHANNEL_LAYOUT_NONE=>KSAUDIO_SPEAKER_UNSUPPORTED";
- return KSAUDIO_SPEAKER_UNSUPPORTED;
- case CHANNEL_LAYOUT_UNSUPPORTED:
- DVLOG(2) << "CHANNEL_LAYOUT_UNSUPPORTED=>KSAUDIO_SPEAKER_UNSUPPORTED";
- return KSAUDIO_SPEAKER_UNSUPPORTED;
- case CHANNEL_LAYOUT_MONO:
- DVLOG(2) << "CHANNEL_LAYOUT_MONO=>KSAUDIO_SPEAKER_MONO";
- return KSAUDIO_SPEAKER_MONO;
- case CHANNEL_LAYOUT_STEREO:
- DVLOG(2) << "CHANNEL_LAYOUT_STEREO=>KSAUDIO_SPEAKER_STEREO";
- return KSAUDIO_SPEAKER_STEREO;
- case CHANNEL_LAYOUT_QUAD:
- DVLOG(2) << "CHANNEL_LAYOUT_QUAD=>KSAUDIO_SPEAKER_QUAD";
- return KSAUDIO_SPEAKER_QUAD;
- case CHANNEL_LAYOUT_4_0:
- DVLOG(2) << "CHANNEL_LAYOUT_4_0=>KSAUDIO_SPEAKER_SURROUND";
- return KSAUDIO_SPEAKER_SURROUND;
- case CHANNEL_LAYOUT_5_1_BACK:
- DVLOG(2) << "CHANNEL_LAYOUT_5_1_BACK=>KSAUDIO_SPEAKER_5POINT1";
- return KSAUDIO_SPEAKER_5POINT1;
- case CHANNEL_LAYOUT_5_1:
- DVLOG(2) << "CHANNEL_LAYOUT_5_1=>KSAUDIO_SPEAKER_5POINT1_SURROUND";
- return KSAUDIO_SPEAKER_5POINT1_SURROUND;
- case CHANNEL_LAYOUT_7_1_WIDE:
- DVLOG(2) << "CHANNEL_LAYOUT_7_1_WIDE=>KSAUDIO_SPEAKER_7POINT1";
- return KSAUDIO_SPEAKER_7POINT1;
- case CHANNEL_LAYOUT_7_1:
- DVLOG(2) << "CHANNEL_LAYOUT_7_1=>KSAUDIO_SPEAKER_7POINT1_SURROUND";
- return KSAUDIO_SPEAKER_7POINT1_SURROUND;
- default:
- DVLOG(2) << "Unsupported channel layout: " << layout;
- return KSAUDIO_SPEAKER_UNSUPPORTED;
- }
-}
-
-static std::ostream& operator<<(std::ostream& os,
- const WAVEFORMATPCMEX& format) {
- os << "wFormatTag: 0x" << std::hex << format.Format.wFormatTag
- << ", nChannels: " << std::dec << format.Format.nChannels
- << ", nSamplesPerSec: " << format.Format.nSamplesPerSec
- << ", nAvgBytesPerSec: " << format.Format.nAvgBytesPerSec
- << ", nBlockAlign: " << format.Format.nBlockAlign
- << ", wBitsPerSample: " << format.Format.wBitsPerSample
- << ", cbSize: " << format.Format.cbSize
- << ", wValidBitsPerSample: " << format.Samples.wValidBitsPerSample
- << ", dwChannelMask: 0x" << std::hex << format.dwChannelMask;
- return os;
-}
-
-static bool LoadAudiosesDll() {
- static const wchar_t* const kAudiosesDLL =
- L"%WINDIR%\\system32\\audioses.dll";
-
- wchar_t path[MAX_PATH] = {0};
- ExpandEnvironmentStringsW(kAudiosesDLL, path, arraysize(path));
- return (LoadLibraryExW(path, NULL, LOAD_WITH_ALTERED_SEARCH_PATH) != NULL);
-}
-
-static std::string GetDeviceID(IMMDevice* device) {
- ScopedCoMem<WCHAR> device_id_com;
- std::string device_id;
- if (SUCCEEDED(device->GetId(&device_id_com)))
- base::WideToUTF8(device_id_com, wcslen(device_id_com), &device_id);
- return device_id;
-}
-
-static bool IsDefaultDeviceId(const std::string& device_id) {
- return device_id.empty() ||
- device_id == AudioDeviceDescription::kDefaultDeviceId;
-}
-
-static bool IsDeviceActive(IMMDevice* device) {
- DWORD state = DEVICE_STATE_DISABLED;
- return SUCCEEDED(device->GetState(&state)) && (state & DEVICE_STATE_ACTIVE);
-}
-
-static HRESULT GetDeviceFriendlyNameInternal(IMMDevice* device,
- std::string* friendly_name) {
- // Retrieve user-friendly name of endpoint device.
- // Example: "Microphone (Realtek High Definition Audio)".
- ScopedComPtr<IPropertyStore> properties;
- HRESULT hr = device->OpenPropertyStore(STGM_READ, properties.Receive());
- if (FAILED(hr))
- return hr;
-
- base::win::ScopedPropVariant friendly_name_pv;
- hr = properties->GetValue(PKEY_Device_FriendlyName,
- friendly_name_pv.Receive());
- if (FAILED(hr))
- return hr;
-
- if (friendly_name_pv.get().vt == VT_LPWSTR &&
- friendly_name_pv.get().pwszVal) {
- base::WideToUTF8(friendly_name_pv.get().pwszVal,
- wcslen(friendly_name_pv.get().pwszVal), friendly_name);
- }
-
- return hr;
-}
-
-static ScopedComPtr<IMMDeviceEnumerator> CreateDeviceEnumeratorInternal(
- bool allow_reinitialize) {
- ScopedComPtr<IMMDeviceEnumerator> device_enumerator;
- HRESULT hr = device_enumerator.CreateInstance(__uuidof(MMDeviceEnumerator),
- NULL, CLSCTX_INPROC_SERVER);
- if (hr == CO_E_NOTINITIALIZED && allow_reinitialize) {
- LOG(ERROR) << "CoCreateInstance fails 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.
- hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);
- if (SUCCEEDED(hr)) {
- hr = device_enumerator.CreateInstance(__uuidof(MMDeviceEnumerator),
- NULL, CLSCTX_INPROC_SERVER);
- }
- }
- return device_enumerator;
-}
-
-static bool IsSupportedInternal() {
- // It is possible to force usage of WaveXxx APIs by using a command line flag.
- const base::CommandLine* cmd_line = base::CommandLine::ForCurrentProcess();
- if (cmd_line->HasSwitch(switches::kForceWaveAudio)) {
- DVLOG(1) << "Forcing usage of Windows WaveXxx APIs";
- return false;
- }
-
- // Microsoft does not plan to make the Core Audio APIs available for use
- // with earlier versions of Windows, including Microsoft Windows Server 2003,
- // Windows XP, Windows Millennium Edition, Windows 2000, and Windows 98.
- if (base::win::GetVersion() < base::win::VERSION_VISTA)
- return false;
-
- // The audio core APIs are implemented in the Mmdevapi.dll and Audioses.dll
- // system components.
- // 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.
- ScopedComPtr<IMMDeviceEnumerator> device_enumerator =
- CreateDeviceEnumeratorInternal(false);
- if (!device_enumerator) {
- LOG(ERROR)
- << "Failed to create Core Audio device enumerator on thread with ID "
- << GetCurrentThreadId();
- return false;
- }
-
- return true;
-}
-
-bool CoreAudioUtil::IsSupported() {
- static bool g_is_supported = IsSupportedInternal();
- return g_is_supported;
-}
-
-base::TimeDelta CoreAudioUtil::RefererenceTimeToTimeDelta(REFERENCE_TIME time) {
- // Each unit of reference time is 100 nanoseconds <=> 0.1 microsecond.
- return base::TimeDelta::FromMicroseconds(0.1 * time + 0.5);
-}
-
-AUDCLNT_SHAREMODE CoreAudioUtil::GetShareMode() {
- const base::CommandLine* cmd_line = base::CommandLine::ForCurrentProcess();
- if (cmd_line->HasSwitch(switches::kEnableExclusiveAudio))
- return AUDCLNT_SHAREMODE_EXCLUSIVE;
- return AUDCLNT_SHAREMODE_SHARED;
-}
-
-int CoreAudioUtil::NumberOfActiveDevices(EDataFlow data_flow) {
- DCHECK(IsSupported());
- // Create the IMMDeviceEnumerator interface.
- ScopedComPtr<IMMDeviceEnumerator> device_enumerator =
- CreateDeviceEnumerator();
- if (!device_enumerator.get())
- return 0;
-
- // 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.
- ScopedComPtr<IMMDeviceCollection> collection;
- HRESULT hr = device_enumerator->EnumAudioEndpoints(data_flow,
- DEVICE_STATE_ACTIVE,
- collection.Receive());
- if (FAILED(hr)) {
- LOG(ERROR) << "IMMDeviceCollection::EnumAudioEndpoints: " << std::hex << hr;
- 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);
- DVLOG(2) << ((data_flow == eCapture) ? "[in ] " : "[out] ")
- << "number of devices: " << number_of_active_devices;
- return static_cast<int>(number_of_active_devices);
-}
-
-ScopedComPtr<IMMDeviceEnumerator> CoreAudioUtil::CreateDeviceEnumerator() {
- DCHECK(IsSupported());
- ScopedComPtr<IMMDeviceEnumerator> device_enumerator =
- CreateDeviceEnumeratorInternal(true);
- CHECK(device_enumerator);
- return device_enumerator;
-}
-
-ScopedComPtr<IMMDevice> CoreAudioUtil::CreateDefaultDevice(EDataFlow data_flow,
- ERole role) {
- DCHECK(IsSupported());
- ScopedComPtr<IMMDevice> endpoint_device;
-
- // Create the IMMDeviceEnumerator interface.
- ScopedComPtr<IMMDeviceEnumerator> device_enumerator =
- CreateDeviceEnumerator();
- if (!device_enumerator.get())
- return endpoint_device;
-
- // Retrieve the default audio endpoint for the specified data-flow
- // direction and role.
- HRESULT hr = device_enumerator->GetDefaultAudioEndpoint(
- data_flow, role, endpoint_device.Receive());
-
- if (FAILED(hr)) {
- DVLOG(1) << "IMMDeviceEnumerator::GetDefaultAudioEndpoint: "
- << std::hex << hr;
- return endpoint_device;
- }
-
- // 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 (!IsDeviceActive(endpoint_device.get())) {
- DVLOG(1) << "Selected endpoint device is not active";
- endpoint_device.Release();
- }
- return endpoint_device;
-}
-
-std::string CoreAudioUtil::GetDefaultOutputDeviceID() {
- DCHECK(IsSupported());
- ScopedComPtr<IMMDevice> device(CreateDefaultDevice(eRender, eConsole));
- return device.get() ? GetDeviceID(device.get()) : std::string();
-}
-
-ScopedComPtr<IMMDevice> CoreAudioUtil::CreateDevice(
- const std::string& device_id) {
- DCHECK(IsSupported());
- ScopedComPtr<IMMDevice> endpoint_device;
-
- // Create the IMMDeviceEnumerator interface.
- ScopedComPtr<IMMDeviceEnumerator> device_enumerator =
- CreateDeviceEnumerator();
- if (!device_enumerator.get())
- return endpoint_device;
-
- // Retrieve an audio device specified by an endpoint device-identification
- // string.
- HRESULT hr = device_enumerator->GetDevice(
- base::UTF8ToUTF16(device_id).c_str(), endpoint_device.Receive());
- DVLOG_IF(1, FAILED(hr)) << "IMMDeviceEnumerator::GetDevice: "
- << std::hex << hr;
-
- if (FAILED(hr)) {
- DVLOG(1) << "IMMDeviceEnumerator::GetDevice: " << std::hex << hr;
- return endpoint_device;
- }
-
- // 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 (!IsDeviceActive(endpoint_device.get())) {
- DVLOG(1) << "Selected endpoint device is not active";
- endpoint_device.Release();
- }
- return endpoint_device;
-}
-
-HRESULT CoreAudioUtil::GetDeviceName(IMMDevice* device, AudioDeviceName* name) {
- DCHECK(IsSupported());
-
- // Retrieve unique name of endpoint device.
- // Example: "{0.0.1.00000000}.{8db6020f-18e3-4f25-b6f5-7726c9122574}".
- AudioDeviceName device_name;
- device_name.unique_id = GetDeviceID(device);
- if (device_name.unique_id.empty())
- return E_FAIL;
-
- HRESULT hr = GetDeviceFriendlyNameInternal(device, &device_name.device_name);
- if (FAILED(hr))
- return hr;
-
- *name = device_name;
- DVLOG(2) << "friendly name: " << device_name.device_name;
- DVLOG(2) << "unique id : " << device_name.unique_id;
- return hr;
-}
-
-std::string CoreAudioUtil::GetAudioControllerID(IMMDevice* device,
- IMMDeviceEnumerator* enumerator) {
- DCHECK(IsSupported());
-
- // Fetching the controller device id could be as simple as fetching the value
- // of the "{B3F8FA53-0004-438E-9003-51A46E139BFC},2" property in the property
- // store of the |device|, but that key isn't defined in any header and
- // according to MS should not be relied upon.
- // So, instead, we go deeper, look at the device topology and fetch the
- // PKEY_Device_InstanceId of the associated physical audio device.
- ScopedComPtr<IDeviceTopology> topology;
- ScopedComPtr<IConnector> connector;
- ScopedCoMem<WCHAR> filter_id;
- if (FAILED(device->Activate(__uuidof(IDeviceTopology), CLSCTX_ALL, NULL,
- topology.ReceiveVoid())) ||
- // For our purposes checking the first connected device should be enough
- // and if there are cases where there are more than one device connected
- // we're not sure how to handle that anyway. So we pass 0.
- FAILED(topology->GetConnector(0, connector.Receive())) ||
- FAILED(connector->GetDeviceIdConnectedTo(&filter_id))) {
- DLOG(ERROR) << "Failed to get the device identifier of the audio device";
- return std::string();
- }
-
- // Now look at the properties of the connected device node and fetch the
- // instance id (PKEY_Device_InstanceId) of the device node that uniquely
- // identifies the controller.
- ScopedComPtr<IMMDevice> device_node;
- ScopedComPtr<IPropertyStore> properties;
- base::win::ScopedPropVariant instance_id;
- if (FAILED(enumerator->GetDevice(filter_id, device_node.Receive())) ||
- FAILED(device_node->OpenPropertyStore(STGM_READ, properties.Receive())) ||
- FAILED(properties->GetValue(PKEY_Device_InstanceId,
- instance_id.Receive())) ||
- instance_id.get().vt != VT_LPWSTR) {
- DLOG(ERROR) << "Failed to get instance id of the audio device node";
- return std::string();
- }
-
- std::string controller_id;
- base::WideToUTF8(instance_id.get().pwszVal,
- wcslen(instance_id.get().pwszVal),
- &controller_id);
-
- return controller_id;
-}
-
-std::string CoreAudioUtil::GetMatchingOutputDeviceID(
- const std::string& input_device_id) {
- // Special handling for the default communications device.
- // We always treat the configured communications devices, as a pair.
- // If we didn't do that and the user has e.g. configured a mic of a headset
- // as the default comms input device and a different device (not the speakers
- // of the headset) as the default comms output device, then we would otherwise
- // here pick the headset as the matched output device. That's technically
- // correct, but the user experience would be that any audio played out to
- // the matched device, would get ducked since it's not the default comms
- // device. So here, we go with the user's configuration.
- if (input_device_id == AudioDeviceDescription::kCommunicationsDeviceId)
- return AudioDeviceDescription::kCommunicationsDeviceId;
-
- ScopedComPtr<IMMDevice> input_device;
- if (IsDefaultDeviceId(input_device_id)) {
- input_device = CreateDefaultDevice(eCapture, eConsole);
- } else {
- input_device = CreateDevice(input_device_id);
- }
-
- if (!input_device.get())
- return std::string();
-
- // See if we can get id of the associated controller.
- ScopedComPtr<IMMDeviceEnumerator> enumerator(CreateDeviceEnumerator());
- std::string controller_id(
- GetAudioControllerID(input_device.get(), enumerator.get()));
- if (controller_id.empty())
- return std::string();
-
- // Now enumerate the available (and active) output devices and see if any of
- // them is associated with the same controller.
- ScopedComPtr<IMMDeviceCollection> collection;
- enumerator->EnumAudioEndpoints(eRender, DEVICE_STATE_ACTIVE,
- collection.Receive());
- if (!collection.get())
- return std::string();
-
- UINT count = 0;
- collection->GetCount(&count);
- ScopedComPtr<IMMDevice> output_device;
- for (UINT i = 0; i < count; ++i) {
- collection->Item(i, output_device.Receive());
- std::string output_controller_id(
- GetAudioControllerID(output_device.get(), enumerator.get()));
- if (output_controller_id == controller_id)
- break;
- output_device = NULL;
- }
-
- return output_device.get() ? GetDeviceID(output_device.get()) : std::string();
-}
-
-std::string CoreAudioUtil::GetFriendlyName(const std::string& device_id) {
- DCHECK(IsSupported());
- ScopedComPtr<IMMDevice> audio_device = CreateDevice(device_id);
- if (!audio_device.get())
- return std::string();
-
- AudioDeviceName device_name;
- HRESULT hr = GetDeviceName(audio_device.get(), &device_name);
- if (FAILED(hr))
- return std::string();
-
- return device_name.device_name;
-}
-
-bool CoreAudioUtil::DeviceIsDefault(EDataFlow flow,
- ERole role,
- const std::string& device_id) {
- DCHECK(IsSupported());
- ScopedComPtr<IMMDevice> device = CreateDefaultDevice(flow, role);
- if (!device.get())
- return false;
-
- std::string str_default(GetDeviceID(device.get()));
- return device_id.compare(str_default) == 0;
-}
-
-EDataFlow CoreAudioUtil::GetDataFlow(IMMDevice* device) {
- DCHECK(IsSupported());
- ScopedComPtr<IMMEndpoint> endpoint;
- HRESULT hr = device->QueryInterface(endpoint.Receive());
- if (FAILED(hr)) {
- DVLOG(1) << "IMMDevice::QueryInterface: " << std::hex << hr;
- return eAll;
- }
-
- EDataFlow data_flow;
- hr = endpoint->GetDataFlow(&data_flow);
- if (FAILED(hr)) {
- DVLOG(1) << "IMMEndpoint::GetDataFlow: " << std::hex << hr;
- return eAll;
- }
- return data_flow;
-}
-
-ScopedComPtr<IAudioClient> CoreAudioUtil::CreateClient(
- IMMDevice* audio_device) {
- DCHECK(IsSupported());
-
- // Creates and activates an IAudioClient COM object given the selected
- // endpoint device.
- ScopedComPtr<IAudioClient> audio_client;
- HRESULT hr = audio_device->Activate(__uuidof(IAudioClient),
- CLSCTX_INPROC_SERVER,
- NULL,
- audio_client.ReceiveVoid());
- DVLOG_IF(1, FAILED(hr)) << "IMMDevice::Activate: " << std::hex << hr;
- return audio_client;
-}
-
-ScopedComPtr<IAudioClient> CoreAudioUtil::CreateDefaultClient(
- EDataFlow data_flow, ERole role) {
- DCHECK(IsSupported());
- ScopedComPtr<IMMDevice> default_device(CreateDefaultDevice(data_flow, role));
- return (default_device.get() ? CreateClient(default_device.get())
- : ScopedComPtr<IAudioClient>());
-}
-
-ScopedComPtr<IAudioClient> CoreAudioUtil::CreateClient(
- const std::string& device_id, EDataFlow data_flow, ERole role) {
- if (IsDefaultDeviceId(device_id))
- return CreateDefaultClient(data_flow, role);
-
- ScopedComPtr<IMMDevice> device(CreateDevice(device_id));
- if (!device.get())
- return ScopedComPtr<IAudioClient>();
-
- return CreateClient(device.get());
-}
-
-HRESULT CoreAudioUtil::GetSharedModeMixFormat(
- IAudioClient* client, WAVEFORMATPCMEX* format) {
- DCHECK(IsSupported());
- ScopedCoMem<WAVEFORMATPCMEX> format_pcmex;
- HRESULT hr = client->GetMixFormat(
- reinterpret_cast<WAVEFORMATEX**>(&format_pcmex));
- if (FAILED(hr))
- return hr;
-
- size_t bytes = sizeof(WAVEFORMATEX) + format_pcmex->Format.cbSize;
- DCHECK_EQ(bytes, sizeof(WAVEFORMATPCMEX));
-
- memcpy(format, format_pcmex, bytes);
- DVLOG(2) << *format;
-
- return hr;
-}
-
-bool CoreAudioUtil::IsFormatSupported(IAudioClient* client,
- AUDCLNT_SHAREMODE share_mode,
- const WAVEFORMATPCMEX* format) {
- DCHECK(IsSupported());
- ScopedCoMem<WAVEFORMATEXTENSIBLE> closest_match;
- HRESULT hr = client->IsFormatSupported(
- share_mode, reinterpret_cast<const WAVEFORMATEX*>(format),
- reinterpret_cast<WAVEFORMATEX**>(&closest_match));
-
- // This log can only be triggered for shared mode.
- DLOG_IF(ERROR, hr == S_FALSE) << "Format is not supported "
- << "but a closest match exists.";
- // This log can be triggered both for shared and exclusive modes.
- DLOG_IF(ERROR, hr == AUDCLNT_E_UNSUPPORTED_FORMAT) << "Unsupported format.";
- if (hr == S_FALSE) {
- DVLOG(2) << *closest_match;
- }
-
- return (hr == S_OK);
-}
-
-bool CoreAudioUtil::IsChannelLayoutSupported(const std::string& device_id,
- EDataFlow data_flow,
- ERole role,
- ChannelLayout channel_layout) {
- DCHECK(IsSupported());
-
- // First, get the preferred mixing format for shared mode streams.
-
- ScopedComPtr<IAudioClient> client(CreateClient(device_id, data_flow, role));
- if (!client.get())
- return false;
-
- WAVEFORMATPCMEX format;
- HRESULT hr = GetSharedModeMixFormat(client.get(), &format);
- if (FAILED(hr))
- return false;
-
- // Next, check if it is possible to use an alternative format where the
- // channel layout (and possibly number of channels) is modified.
-
- // Convert generic channel layout into Windows-specific channel configuration.
- ChannelConfig new_config = ChannelLayoutToChannelConfig(channel_layout);
- if (new_config == KSAUDIO_SPEAKER_UNSUPPORTED) {
- return false;
- }
- format.dwChannelMask = new_config;
-
- // Modify the format if the new channel layout has changed the number of
- // utilized channels.
- const int channels = ChannelLayoutToChannelCount(channel_layout);
- if (channels != format.Format.nChannels) {
- format.Format.nChannels = channels;
- format.Format.nBlockAlign = (format.Format.wBitsPerSample / 8) * channels;
- format.Format.nAvgBytesPerSec = format.Format.nSamplesPerSec *
- format.Format.nBlockAlign;
- }
- DVLOG(2) << format;
-
- // Some devices can initialize a shared-mode stream with a format that is
- // not identical to the mix format obtained from the GetMixFormat() method.
- // However, chances of succeeding increases if we use the same number of
- // channels and the same sample rate as the mix format. I.e, this call will
- // return true only in those cases where the audio engine is able to support
- // an even wider range of shared-mode formats where the installation package
- // for the audio device includes a local effects (LFX) audio processing
- // object (APO) that can handle format conversions.
- return CoreAudioUtil::IsFormatSupported(client.get(),
- AUDCLNT_SHAREMODE_SHARED, &format);
-}
-
-HRESULT CoreAudioUtil::GetDevicePeriod(IAudioClient* client,
- AUDCLNT_SHAREMODE share_mode,
- REFERENCE_TIME* device_period) {
- DCHECK(IsSupported());
-
- // Get the period of the engine thread.
- REFERENCE_TIME default_period = 0;
- REFERENCE_TIME minimum_period = 0;
- HRESULT hr = client->GetDevicePeriod(&default_period, &minimum_period);
- if (FAILED(hr))
- return hr;
-
- *device_period = (share_mode == AUDCLNT_SHAREMODE_SHARED) ? default_period :
- minimum_period;
- DVLOG(2) << "device_period: "
- << RefererenceTimeToTimeDelta(*device_period).InMillisecondsF()
- << " [ms]";
- return hr;
-}
-
-HRESULT CoreAudioUtil::GetPreferredAudioParameters(
- IAudioClient* client, AudioParameters* params) {
- DCHECK(IsSupported());
- WAVEFORMATPCMEX mix_format;
- HRESULT hr = GetSharedModeMixFormat(client, &mix_format);
- if (FAILED(hr))
- return hr;
-
- REFERENCE_TIME default_period = 0;
- hr = GetDevicePeriod(client, AUDCLNT_SHAREMODE_SHARED, &default_period);
- if (FAILED(hr))
- return hr;
-
- // Get the integer mask which corresponds to the channel layout the
- // audio engine uses for its internal processing/mixing of shared-mode
- // streams. This mask indicates which channels are present in the multi-
- // channel stream. The least significant bit corresponds with the Front Left
- // speaker, the next least significant bit corresponds to the Front Right
- // speaker, and so on, continuing in the order defined in KsMedia.h.
- // See http://msdn.microsoft.com/en-us/library/windows/hardware/ff537083.aspx
- // for more details.
- ChannelConfig channel_config = mix_format.dwChannelMask;
-
- // Convert Microsoft's channel configuration to genric ChannelLayout.
- ChannelLayout channel_layout = ChannelConfigToChannelLayout(channel_config);
-
- // Some devices don't appear to set a valid channel layout, so guess based on
- // the number of channels. See http://crbug.com/311906.
- if (channel_layout == CHANNEL_LAYOUT_UNSUPPORTED) {
- DVLOG(1) << "Unsupported channel config: "
- << std::hex << channel_config
- << ". Guessing layout by channel count: "
- << std::dec << mix_format.Format.nChannels;
- channel_layout = GuessChannelLayout(mix_format.Format.nChannels);
- }
-
- // Preferred sample rate.
- int sample_rate = mix_format.Format.nSamplesPerSec;
-
- // TODO(henrika): possibly use format.Format.wBitsPerSample here instead.
- // We use a hard-coded value of 16 bits per sample today even if most audio
- // engines does the actual mixing in 32 bits per sample.
- int bits_per_sample = 16;
-
- // We are using the native device period to derive the smallest possible
- // buffer size in shared mode. Note that the actual endpoint buffer will be
- // larger than this size but it will be possible to fill it up in two calls.
- // TODO(henrika): ensure that this scheme works for capturing as well.
- int frames_per_buffer = static_cast<int>(sample_rate *
- RefererenceTimeToTimeDelta(default_period).InSecondsF() + 0.5);
-
- DVLOG(1) << "channel_layout : " << channel_layout;
- DVLOG(1) << "sample_rate : " << sample_rate;
- DVLOG(1) << "bits_per_sample : " << bits_per_sample;
- DVLOG(1) << "frames_per_buffer: " << frames_per_buffer;
-
- AudioParameters audio_params(AudioParameters::AUDIO_PCM_LOW_LATENCY,
- channel_layout,
- sample_rate,
- bits_per_sample,
- frames_per_buffer);
-
- *params = audio_params;
- return hr;
-}
-
-HRESULT CoreAudioUtil::GetPreferredAudioParameters(const std::string& device_id,
- bool is_output_device,
- AudioParameters* params) {
- DCHECK(IsSupported());
-
- ScopedComPtr<IMMDevice> device;
- if (device_id == AudioDeviceDescription::kDefaultDeviceId) {
- device = CoreAudioUtil::CreateDefaultDevice(
- is_output_device ? eRender : eCapture, eConsole);
- } else if (device_id == AudioDeviceDescription::kLoopbackInputDeviceId) {
- DCHECK(!is_output_device);
- device = CoreAudioUtil::CreateDefaultDevice(eRender, eConsole);
- } else if (device_id == AudioDeviceDescription::kCommunicationsDeviceId) {
- device = CoreAudioUtil::CreateDefaultDevice(
- is_output_device ? eRender : eCapture, eCommunications);
- } else {
- device = CreateDevice(device_id);
- }
-
- if (!device.get()) {
- // Map NULL-pointer to new error code which can be different from the
- // actual error code. The exact value is not important here.
- return AUDCLNT_E_DEVICE_INVALIDATED;
- }
-
- ScopedComPtr<IAudioClient> client(CreateClient(device.get()));
- if (!client.get()) {
- // Map NULL-pointer to new error code which can be different from the
- // actual error code. The exact value is not important here.
- return AUDCLNT_E_ENDPOINT_CREATE_FAILED;
- }
-
- HRESULT hr = GetPreferredAudioParameters(client.get(), params);
- if (FAILED(hr) || is_output_device || !params->IsValid())
- return hr;
-
- // The following functionality is only for input devices.
- DCHECK(!is_output_device);
-
- // TODO(dalecurtis): Old code rewrote != 1 channels to stereo, do we still
- // need to do the same thing?
- if (params->channels() != 1) {
- params->Reset(params->format(), CHANNEL_LAYOUT_STEREO,
- params->sample_rate(), params->bits_per_sample(),
- params->frames_per_buffer());
- }
-
- return hr;
-}
-
-ChannelConfig CoreAudioUtil::GetChannelConfig(const std::string& device_id,
- EDataFlow data_flow) {
- ScopedComPtr<IAudioClient> client(
- CreateClient(device_id, data_flow, eConsole));
-
- WAVEFORMATPCMEX format = {};
- if (!client.get() || FAILED(GetSharedModeMixFormat(client.get(), &format)))
- return 0;
-
- return static_cast<ChannelConfig>(format.dwChannelMask);
-}
-
-HRESULT CoreAudioUtil::SharedModeInitialize(IAudioClient* client,
- const WAVEFORMATPCMEX* format,
- HANDLE event_handle,
- uint32_t* endpoint_buffer_size,
- const GUID* session_guid) {
- DCHECK(IsSupported());
-
- // Use default flags (i.e, dont set AUDCLNT_STREAMFLAGS_NOPERSIST) to
- // ensure that 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 = 0;
-
- // 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 != NULL &&
- event_handle != INVALID_HANDLE_VALUE);
- if (use_event)
- stream_flags |= AUDCLNT_STREAMFLAGS_EVENTCALLBACK;
- DVLOG(2) << "stream_flags: 0x" << std::hex << stream_flags;
-
- // Initialize the shared mode client for minimal delay.
- HRESULT hr = client->Initialize(AUDCLNT_SHAREMODE_SHARED,
- stream_flags,
- 0,
- 0,
- reinterpret_cast<const WAVEFORMATEX*>(format),
- session_guid);
- if (FAILED(hr)) {
- DVLOG(1) << "IAudioClient::Initialize: " << std::hex << hr;
- return hr;
- }
-
- if (use_event) {
- hr = client->SetEventHandle(event_handle);
- if (FAILED(hr)) {
- DVLOG(1) << "IAudioClient::SetEventHandle: " << std::hex << hr;
- return hr;
- }
- }
-
- UINT32 buffer_size_in_frames = 0;
- hr = client->GetBufferSize(&buffer_size_in_frames);
- if (FAILED(hr)) {
- DVLOG(1) << "IAudioClient::GetBufferSize: " << std::hex << hr;
- return hr;
- }
-
- *endpoint_buffer_size = buffer_size_in_frames;
- DVLOG(2) << "endpoint buffer size: " << buffer_size_in_frames;
-
- // TODO(henrika): utilize when delay measurements are added.
- REFERENCE_TIME latency = 0;
- hr = client->GetStreamLatency(&latency);
- DVLOG(2) << "stream latency: "
- << RefererenceTimeToTimeDelta(latency).InMillisecondsF() << " [ms]";
- return hr;
-}
-
-ScopedComPtr<IAudioRenderClient> CoreAudioUtil::CreateRenderClient(
- IAudioClient* client) {
- DCHECK(IsSupported());
-
- // Get access to the IAudioRenderClient interface. This interface
- // enables us to write output data to a rendering endpoint buffer.
- ScopedComPtr<IAudioRenderClient> audio_render_client;
- HRESULT hr = client->GetService(__uuidof(IAudioRenderClient),
- audio_render_client.ReceiveVoid());
- if (FAILED(hr)) {
- DVLOG(1) << "IAudioClient::GetService: " << std::hex << hr;
- return ScopedComPtr<IAudioRenderClient>();
- }
- return audio_render_client;
-}
-
-ScopedComPtr<IAudioCaptureClient> CoreAudioUtil::CreateCaptureClient(
- IAudioClient* client) {
- DCHECK(IsSupported());
-
- // Get access to the IAudioCaptureClient interface. This interface
- // enables us to read input data from a capturing endpoint buffer.
- ScopedComPtr<IAudioCaptureClient> audio_capture_client;
- HRESULT hr = client->GetService(__uuidof(IAudioCaptureClient),
- audio_capture_client.ReceiveVoid());
- if (FAILED(hr)) {
- DVLOG(1) << "IAudioClient::GetService: " << std::hex << hr;
- return ScopedComPtr<IAudioCaptureClient>();
- }
- return audio_capture_client;
-}
-
-bool CoreAudioUtil::FillRenderEndpointBufferWithSilence(
- IAudioClient* client, IAudioRenderClient* render_client) {
- DCHECK(IsSupported());
-
- UINT32 endpoint_buffer_size = 0;
- if (FAILED(client->GetBufferSize(&endpoint_buffer_size)))
- return false;
-
- UINT32 num_queued_frames = 0;
- if (FAILED(client->GetCurrentPadding(&num_queued_frames)))
- return false;
-
- BYTE* data = NULL;
- int num_frames_to_fill = endpoint_buffer_size - num_queued_frames;
- if (FAILED(render_client->GetBuffer(num_frames_to_fill, &data)))
- return false;
-
- // Using the AUDCLNT_BUFFERFLAGS_SILENT flag eliminates the need to
- // explicitly write silence data to the rendering buffer.
- DVLOG(2) << "filling up " << num_frames_to_fill << " frames with silence";
- return SUCCEEDED(render_client->ReleaseBuffer(num_frames_to_fill,
- AUDCLNT_BUFFERFLAGS_SILENT));
-}
-
-bool CoreAudioUtil::GetDxDiagDetails(std::string* driver_name,
- std::string* driver_version) {
- ScopedComPtr<IDxDiagProvider, &IID_IDxDiagProvider> provider;
- HRESULT hr =
- provider.CreateInstance(CLSID_DxDiagProvider, NULL, CLSCTX_INPROC_SERVER);
- if (FAILED(hr))
- return false;
-
- DXDIAG_INIT_PARAMS params = {sizeof(params)};
- params.dwDxDiagHeaderVersion = DXDIAG_DX9_SDK_VERSION;
- params.bAllowWHQLChecks = FALSE;
- params.pReserved = NULL;
- hr = provider->Initialize(&params);
- if (FAILED(hr))
- return false;
-
- ScopedComPtr<IDxDiagContainer, &IID_IDxDiagContainer> root;
- hr = provider->GetRootContainer(root.Receive());
- if (FAILED(hr))
- return false;
-
- // Limit to the SoundDevices subtree. The tree in its entirity is
- // enormous and only this branch contains useful information.
- ScopedComPtr<IDxDiagContainer, &IID_IDxDiagContainer> sound_devices;
- hr = root->GetChildContainer(L"DxDiag_DirectSound.DxDiag_SoundDevices.0",
- sound_devices.Receive());
- if (FAILED(hr))
- return false;
-
- base::win::ScopedVariant variant;
- hr = sound_devices->GetProp(L"szDriverName", variant.Receive());
- if (FAILED(hr))
- return false;
-
- if (variant.type() == VT_BSTR && variant.ptr()->bstrVal) {
- base::WideToUTF8(variant.ptr()->bstrVal, wcslen(variant.ptr()->bstrVal),
- driver_name);
- }
-
- variant.Reset();
- hr = sound_devices->GetProp(L"szDriverVersion", variant.Receive());
- if (FAILED(hr))
- return false;
-
- if (variant.type() == VT_BSTR && variant.ptr()->bstrVal) {
- base::WideToUTF8(variant.ptr()->bstrVal, wcslen(variant.ptr()->bstrVal),
- driver_version);
- }
-
- return true;
-}
-
-} // namespace media
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "media/audio/win/core_audio_util_win.h"
+
+#include <devicetopology.h>
+#include <dxdiag.h>
+#include <functiondiscoverykeys_devpkey.h>
+#include <stddef.h>
+
+#include "base/command_line.h"
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/strings/stringprintf.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/win/scoped_co_mem.h"
+#include "base/win/scoped_handle.h"
+#include "base/win/scoped_propvariant.h"
+#include "base/win/scoped_variant.h"
+#include "base/win/windows_version.h"
+#include "media/audio/audio_device_description.h"
+#include "media/base/media_switches.h"
+
+using base::win::ScopedCoMem;
+using base::win::ScopedHandle;
+
+namespace media {
+
+// See header file for documentation.
+// {BE39AF4F-087C-423F-9303-234EC1E5B8EE}
+const GUID kCommunicationsSessionId = {
+ 0xbe39af4f,
+ 0x87c,
+ 0x423f,
+ {0x93, 0x3, 0x23, 0x4e, 0xc1, 0xe5, 0xb8, 0xee}};
+
+enum { KSAUDIO_SPEAKER_UNSUPPORTED = 0 };
+
+// Converts Microsoft's channel configuration to ChannelLayout.
+// This mapping is not perfect but the best we can do given the current
+// ChannelLayout enumerator and the Windows-specific speaker configurations
+// defined in ksmedia.h. Don't assume that the channel ordering in
+// ChannelLayout is exactly the same as the Windows specific configuration.
+// As an example: KSAUDIO_SPEAKER_7POINT1_SURROUND is mapped to
+// CHANNEL_LAYOUT_7_1 but the positions of Back L, Back R and Side L, Side R
+// speakers are different in these two definitions.
+static ChannelLayout ChannelConfigToChannelLayout(ChannelConfig config) {
+ switch (config) {
+ case KSAUDIO_SPEAKER_DIRECTOUT:
+ DVLOG(2) << "KSAUDIO_SPEAKER_DIRECTOUT=>CHANNEL_LAYOUT_NONE";
+ return CHANNEL_LAYOUT_NONE;
+ case KSAUDIO_SPEAKER_MONO:
+ DVLOG(2) << "KSAUDIO_SPEAKER_MONO=>CHANNEL_LAYOUT_MONO";
+ return CHANNEL_LAYOUT_MONO;
+ case KSAUDIO_SPEAKER_STEREO:
+ DVLOG(2) << "KSAUDIO_SPEAKER_STEREO=>CHANNEL_LAYOUT_STEREO";
+ return CHANNEL_LAYOUT_STEREO;
+ case KSAUDIO_SPEAKER_QUAD:
+ DVLOG(2) << "KSAUDIO_SPEAKER_QUAD=>CHANNEL_LAYOUT_QUAD";
+ return CHANNEL_LAYOUT_QUAD;
+ case KSAUDIO_SPEAKER_SURROUND:
+ DVLOG(2) << "KSAUDIO_SPEAKER_SURROUND=>CHANNEL_LAYOUT_4_0";
+ return CHANNEL_LAYOUT_4_0;
+ case KSAUDIO_SPEAKER_5POINT1:
+ DVLOG(2) << "KSAUDIO_SPEAKER_5POINT1=>CHANNEL_LAYOUT_5_1_BACK";
+ return CHANNEL_LAYOUT_5_1_BACK;
+ case KSAUDIO_SPEAKER_5POINT1_SURROUND:
+ DVLOG(2) << "KSAUDIO_SPEAKER_5POINT1_SURROUND=>CHANNEL_LAYOUT_5_1";
+ return CHANNEL_LAYOUT_5_1;
+ case KSAUDIO_SPEAKER_7POINT1:
+ DVLOG(2) << "KSAUDIO_SPEAKER_7POINT1=>CHANNEL_LAYOUT_7_1_WIDE";
+ return CHANNEL_LAYOUT_7_1_WIDE;
+ case KSAUDIO_SPEAKER_7POINT1_SURROUND:
+ DVLOG(2) << "KSAUDIO_SPEAKER_7POINT1_SURROUND=>CHANNEL_LAYOUT_7_1";
+ return CHANNEL_LAYOUT_7_1;
+ default:
+ DVLOG(2) << "Unsupported channel configuration: " << config;
+ return CHANNEL_LAYOUT_UNSUPPORTED;
+ }
+}
+
+// TODO(henrika): add mapping for all types in the ChannelLayout enumerator.
+static ChannelConfig ChannelLayoutToChannelConfig(ChannelLayout layout) {
+ switch (layout) {
+ case CHANNEL_LAYOUT_NONE:
+ DVLOG(2) << "CHANNEL_LAYOUT_NONE=>KSAUDIO_SPEAKER_UNSUPPORTED";
+ return KSAUDIO_SPEAKER_UNSUPPORTED;
+ case CHANNEL_LAYOUT_UNSUPPORTED:
+ DVLOG(2) << "CHANNEL_LAYOUT_UNSUPPORTED=>KSAUDIO_SPEAKER_UNSUPPORTED";
+ return KSAUDIO_SPEAKER_UNSUPPORTED;
+ case CHANNEL_LAYOUT_MONO:
+ DVLOG(2) << "CHANNEL_LAYOUT_MONO=>KSAUDIO_SPEAKER_MONO";
+ return KSAUDIO_SPEAKER_MONO;
+ case CHANNEL_LAYOUT_STEREO:
+ DVLOG(2) << "CHANNEL_LAYOUT_STEREO=>KSAUDIO_SPEAKER_STEREO";
+ return KSAUDIO_SPEAKER_STEREO;
+ case CHANNEL_LAYOUT_QUAD:
+ DVLOG(2) << "CHANNEL_LAYOUT_QUAD=>KSAUDIO_SPEAKER_QUAD";
+ return KSAUDIO_SPEAKER_QUAD;
+ case CHANNEL_LAYOUT_4_0:
+ DVLOG(2) << "CHANNEL_LAYOUT_4_0=>KSAUDIO_SPEAKER_SURROUND";
+ return KSAUDIO_SPEAKER_SURROUND;
+ case CHANNEL_LAYOUT_5_1_BACK:
+ DVLOG(2) << "CHANNEL_LAYOUT_5_1_BACK=>KSAUDIO_SPEAKER_5POINT1";
+ return KSAUDIO_SPEAKER_5POINT1;
+ case CHANNEL_LAYOUT_5_1:
+ DVLOG(2) << "CHANNEL_LAYOUT_5_1=>KSAUDIO_SPEAKER_5POINT1_SURROUND";
+ return KSAUDIO_SPEAKER_5POINT1_SURROUND;
+ case CHANNEL_LAYOUT_7_1_WIDE:
+ DVLOG(2) << "CHANNEL_LAYOUT_7_1_WIDE=>KSAUDIO_SPEAKER_7POINT1";
+ return KSAUDIO_SPEAKER_7POINT1;
+ case CHANNEL_LAYOUT_7_1:
+ DVLOG(2) << "CHANNEL_LAYOUT_7_1=>KSAUDIO_SPEAKER_7POINT1_SURROUND";
+ return KSAUDIO_SPEAKER_7POINT1_SURROUND;
+ default:
+ DVLOG(2) << "Unsupported channel layout: " << layout;
+ return KSAUDIO_SPEAKER_UNSUPPORTED;
+ }
+}
+
+static std::ostream& operator<<(std::ostream& os,
+ const WAVEFORMATPCMEX& format) {
+ os << "wFormatTag: 0x" << std::hex << format.Format.wFormatTag
+ << ", nChannels: " << std::dec << format.Format.nChannels
+ << ", nSamplesPerSec: " << format.Format.nSamplesPerSec
+ << ", nAvgBytesPerSec: " << format.Format.nAvgBytesPerSec
+ << ", nBlockAlign: " << format.Format.nBlockAlign
+ << ", wBitsPerSample: " << format.Format.wBitsPerSample
+ << ", cbSize: " << format.Format.cbSize
+ << ", wValidBitsPerSample: " << format.Samples.wValidBitsPerSample
+ << ", dwChannelMask: 0x" << std::hex << format.dwChannelMask;
+ return os;
+}
+
+static bool LoadAudiosesDll() {
+ static const wchar_t* const kAudiosesDLL =
+ L"%WINDIR%\\system32\\audioses.dll";
+
+ wchar_t path[MAX_PATH] = {0};
+ ExpandEnvironmentStringsW(kAudiosesDLL, path, arraysize(path));
+ return (LoadLibraryExW(path, NULL, LOAD_WITH_ALTERED_SEARCH_PATH) != NULL);
+}
+
+static std::string GetDeviceID(IMMDevice* device) {
+ ScopedCoMem<WCHAR> device_id_com;
+ std::string device_id;
+ if (SUCCEEDED(device->GetId(&device_id_com)))
+ base::WideToUTF8(device_id_com, wcslen(device_id_com), &device_id);
+ return device_id;
+}
+
+static bool IsDefaultDeviceId(const std::string& device_id) {
+ return device_id.empty() ||
+ device_id == AudioDeviceDescription::kDefaultDeviceId;
+}
+
+static bool IsDeviceActive(IMMDevice* device) {
+ DWORD state = DEVICE_STATE_DISABLED;
+ return SUCCEEDED(device->GetState(&state)) && (state & DEVICE_STATE_ACTIVE);
+}
+
+static HRESULT GetDeviceFriendlyNameInternal(IMMDevice* device,
+ std::string* friendly_name) {
+ // Retrieve user-friendly name of endpoint device.
+ // Example: "Microphone (Realtek High Definition Audio)".
+ ScopedComPtr<IPropertyStore> properties;
+ HRESULT hr = device->OpenPropertyStore(STGM_READ, properties.Receive());
+ if (FAILED(hr))
+ return hr;
+
+ base::win::ScopedPropVariant friendly_name_pv;
+ hr = properties->GetValue(PKEY_Device_FriendlyName,
+ friendly_name_pv.Receive());
+ if (FAILED(hr))
+ return hr;
+
+ if (friendly_name_pv.get().vt == VT_LPWSTR &&
+ friendly_name_pv.get().pwszVal) {
+ base::WideToUTF8(friendly_name_pv.get().pwszVal,
+ wcslen(friendly_name_pv.get().pwszVal), friendly_name);
+ }
+
+ return hr;
+}
+
+static ScopedComPtr<IMMDeviceEnumerator> CreateDeviceEnumeratorInternal(
+ bool allow_reinitialize) {
+ ScopedComPtr<IMMDeviceEnumerator> device_enumerator;
+ HRESULT hr = device_enumerator.CreateInstance(__uuidof(MMDeviceEnumerator),
+ NULL, CLSCTX_INPROC_SERVER);
+ if (hr == CO_E_NOTINITIALIZED && allow_reinitialize) {
+ LOG(ERROR) << "CoCreateInstance fails 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.
+ hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);
+ if (SUCCEEDED(hr)) {
+ hr = device_enumerator.CreateInstance(__uuidof(MMDeviceEnumerator), NULL,
+ CLSCTX_INPROC_SERVER);
+ }
+ }
+ return device_enumerator;
+}
+
+static bool IsSupportedInternal() {
+ // It is possible to force usage of WaveXxx APIs by using a command line flag.
+ const base::CommandLine* cmd_line = base::CommandLine::ForCurrentProcess();
+ if (cmd_line->HasSwitch(switches::kForceWaveAudio)) {
+ DVLOG(1) << "Forcing usage of Windows WaveXxx APIs";
+ return false;
+ }
+
+ // Microsoft does not plan to make the Core Audio APIs available for use
+ // with earlier versions of Windows, including Microsoft Windows Server 2003,
+ // Windows XP, Windows Millennium Edition, Windows 2000, and Windows 98.
+ if (base::win::GetVersion() < base::win::VERSION_VISTA)
+ return false;
+
+ // The audio core APIs are implemented in the Mmdevapi.dll and Audioses.dll
+ // system components.
+ // 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.
+ ScopedComPtr<IMMDeviceEnumerator> device_enumerator =
+ CreateDeviceEnumeratorInternal(false);
+ if (!device_enumerator) {
+ LOG(ERROR)
+ << "Failed to create Core Audio device enumerator on thread with ID "
+ << GetCurrentThreadId();
+ return false;
+ }
+
+ return true;
+}
+
+bool CoreAudioUtil::IsSupported() {
+ static bool g_is_supported = IsSupportedInternal();
+ return g_is_supported;
+}
+
+base::TimeDelta CoreAudioUtil::RefererenceTimeToTimeDelta(REFERENCE_TIME time) {
+ // Each unit of reference time is 100 nanoseconds <=> 0.1 microsecond.
+ return base::TimeDelta::FromMicroseconds(0.1 * time + 0.5);
+}
+
+AUDCLNT_SHAREMODE CoreAudioUtil::GetShareMode() {
+ const base::CommandLine* cmd_line = base::CommandLine::ForCurrentProcess();
+ if (cmd_line->HasSwitch(switches::kEnableExclusiveAudio))
+ return AUDCLNT_SHAREMODE_EXCLUSIVE;
+ return AUDCLNT_SHAREMODE_SHARED;
+}
+
+int CoreAudioUtil::NumberOfActiveDevices(EDataFlow data_flow) {
+ DCHECK(IsSupported());
+ // Create the IMMDeviceEnumerator interface.
+ ScopedComPtr<IMMDeviceEnumerator> device_enumerator =
+ CreateDeviceEnumerator();
+ if (!device_enumerator.get())
+ return 0;
+
+ // 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.
+ ScopedComPtr<IMMDeviceCollection> collection;
+ HRESULT hr = device_enumerator->EnumAudioEndpoints(
+ data_flow, DEVICE_STATE_ACTIVE, collection.Receive());
+ if (FAILED(hr)) {
+ LOG(ERROR) << "IMMDeviceCollection::EnumAudioEndpoints: " << std::hex << hr;
+ 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);
+ DVLOG(2) << ((data_flow == eCapture) ? "[in ] " : "[out] ")
+ << "number of devices: " << number_of_active_devices;
+ return static_cast<int>(number_of_active_devices);
+}
+
+ScopedComPtr<IMMDeviceEnumerator> CoreAudioUtil::CreateDeviceEnumerator() {
+ DCHECK(IsSupported());
+ ScopedComPtr<IMMDeviceEnumerator> device_enumerator =
+ CreateDeviceEnumeratorInternal(true);
+ CHECK(device_enumerator);
+ return device_enumerator;
+}
+
+ScopedComPtr<IMMDevice> CoreAudioUtil::CreateDefaultDevice(EDataFlow data_flow,
+ ERole role) {
+ DCHECK(IsSupported());
+ ScopedComPtr<IMMDevice> endpoint_device;
+
+ // Create the IMMDeviceEnumerator interface.
+ ScopedComPtr<IMMDeviceEnumerator> device_enumerator =
+ CreateDeviceEnumerator();
+ if (!device_enumerator.get())
+ return endpoint_device;
+
+ // Retrieve the default audio endpoint for the specified data-flow
+ // direction and role.
+ HRESULT hr = device_enumerator->GetDefaultAudioEndpoint(
+ data_flow, role, endpoint_device.Receive());
+
+ if (FAILED(hr)) {
+ DVLOG(1) << "IMMDeviceEnumerator::GetDefaultAudioEndpoint: " << std::hex
+ << hr;
+ return endpoint_device;
+ }
+
+ // 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 (!IsDeviceActive(endpoint_device.get())) {
+ DVLOG(1) << "Selected endpoint device is not active";
+ endpoint_device.Release();
+ }
+ return endpoint_device;
+}
+
+std::string CoreAudioUtil::GetDefaultOutputDeviceID() {
+ DCHECK(IsSupported());
+ ScopedComPtr<IMMDevice> device(CreateDefaultDevice(eRender, eConsole));
+ return device.get() ? GetDeviceID(device.get()) : std::string();
+}
+
+ScopedComPtr<IMMDevice> CoreAudioUtil::CreateDevice(
+ const std::string& device_id) {
+ DCHECK(IsSupported());
+ ScopedComPtr<IMMDevice> endpoint_device;
+
+ // Create the IMMDeviceEnumerator interface.
+ ScopedComPtr<IMMDeviceEnumerator> device_enumerator =
+ CreateDeviceEnumerator();
+ if (!device_enumerator.get())
+ return endpoint_device;
+
+ // Retrieve an audio device specified by an endpoint device-identification
+ // string.
+ HRESULT hr = device_enumerator->GetDevice(
+ base::UTF8ToUTF16(device_id).c_str(), endpoint_device.Receive());
+ DVLOG_IF(1, FAILED(hr)) << "IMMDeviceEnumerator::GetDevice: " << std::hex
+ << hr;
+
+ if (FAILED(hr)) {
+ DVLOG(1) << "IMMDeviceEnumerator::GetDevice: " << std::hex << hr;
+ return endpoint_device;
+ }
+
+ // 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 (!IsDeviceActive(endpoint_device.get())) {
+ DVLOG(1) << "Selected endpoint device is not active";
+ endpoint_device.Release();
+ }
+ return endpoint_device;
+}
+
+HRESULT CoreAudioUtil::GetDeviceName(IMMDevice* device, AudioDeviceName* name) {
+ DCHECK(IsSupported());
+
+ // Retrieve unique name of endpoint device.
+ // Example: "{0.0.1.00000000}.{8db6020f-18e3-4f25-b6f5-7726c9122574}".
+ AudioDeviceName device_name;
+ device_name.unique_id = GetDeviceID(device);
+ if (device_name.unique_id.empty())
+ return E_FAIL;
+
+ HRESULT hr = GetDeviceFriendlyNameInternal(device, &device_name.device_name);
+ if (FAILED(hr))
+ return hr;
+
+ *name = device_name;
+ DVLOG(2) << "friendly name: " << device_name.device_name;
+ DVLOG(2) << "unique id : " << device_name.unique_id;
+ return hr;
+}
+
+std::string CoreAudioUtil::GetAudioControllerID(
+ IMMDevice* device,
+ IMMDeviceEnumerator* enumerator) {
+ DCHECK(IsSupported());
+
+ // Fetching the controller device id could be as simple as fetching the value
+ // of the "{B3F8FA53-0004-438E-9003-51A46E139BFC},2" property in the property
+ // store of the |device|, but that key isn't defined in any header and
+ // according to MS should not be relied upon.
+ // So, instead, we go deeper, look at the device topology and fetch the
+ // PKEY_Device_InstanceId of the associated physical audio device.
+ ScopedComPtr<IDeviceTopology> topology;
+ ScopedComPtr<IConnector> connector;
+ ScopedCoMem<WCHAR> filter_id;
+ if (FAILED(device->Activate(__uuidof(IDeviceTopology), CLSCTX_ALL, NULL,
+ topology.ReceiveVoid())) ||
+ // For our purposes checking the first connected device should be enough
+ // and if there are cases where there are more than one device connected
+ // we're not sure how to handle that anyway. So we pass 0.
+ FAILED(topology->GetConnector(0, connector.Receive())) ||
+ FAILED(connector->GetDeviceIdConnectedTo(&filter_id))) {
+ DLOG(ERROR) << "Failed to get the device identifier of the audio device";
+ return std::string();
+ }
+
+ // Now look at the properties of the connected device node and fetch the
+ // instance id (PKEY_Device_InstanceId) of the device node that uniquely
+ // identifies the controller.
+ ScopedComPtr<IMMDevice> device_node;
+ ScopedComPtr<IPropertyStore> properties;
+ base::win::ScopedPropVariant instance_id;
+ if (FAILED(enumerator->GetDevice(filter_id, device_node.Receive())) ||
+ FAILED(device_node->OpenPropertyStore(STGM_READ, properties.Receive())) ||
+ FAILED(properties->GetValue(PKEY_Device_InstanceId,
+ instance_id.Receive())) ||
+ instance_id.get().vt != VT_LPWSTR) {
+ DLOG(ERROR) << "Failed to get instance id of the audio device node";
+ return std::string();
+ }
+
+ std::string controller_id;
+ base::WideToUTF8(instance_id.get().pwszVal, wcslen(instance_id.get().pwszVal),
+ &controller_id);
+
+ return controller_id;
+}
+
+std::string CoreAudioUtil::GetMatchingOutputDeviceID(
+ const std::string& input_device_id) {
+ // Special handling for the default communications device.
+ // We always treat the configured communications devices, as a pair.
+ // If we didn't do that and the user has e.g. configured a mic of a headset
+ // as the default comms input device and a different device (not the speakers
+ // of the headset) as the default comms output device, then we would otherwise
+ // here pick the headset as the matched output device. That's technically
+ // correct, but the user experience would be that any audio played out to
+ // the matched device, would get ducked since it's not the default comms
+ // device. So here, we go with the user's configuration.
+ if (input_device_id == AudioDeviceDescription::kCommunicationsDeviceId)
+ return AudioDeviceDescription::kCommunicationsDeviceId;
+
+ ScopedComPtr<IMMDevice> input_device;
+ if (IsDefaultDeviceId(input_device_id)) {
+ input_device = CreateDefaultDevice(eCapture, eConsole);
+ } else {
+ input_device = CreateDevice(input_device_id);
+ }
+
+ if (!input_device.get())
+ return std::string();
+
+ // See if we can get id of the associated controller.
+ ScopedComPtr<IMMDeviceEnumerator> enumerator(CreateDeviceEnumerator());
+ std::string controller_id(
+ GetAudioControllerID(input_device.get(), enumerator.get()));
+ if (controller_id.empty())
+ return std::string();
+
+ // Now enumerate the available (and active) output devices and see if any of
+ // them is associated with the same controller.
+ ScopedComPtr<IMMDeviceCollection> collection;
+ enumerator->EnumAudioEndpoints(eRender, DEVICE_STATE_ACTIVE,
+ collection.Receive());
+ if (!collection.get())
+ return std::string();
+
+ UINT count = 0;
+ collection->GetCount(&count);
+ ScopedComPtr<IMMDevice> output_device;
+ for (UINT i = 0; i < count; ++i) {
+ collection->Item(i, output_device.Receive());
+ std::string output_controller_id(
+ GetAudioControllerID(output_device.get(), enumerator.get()));
+ if (output_controller_id == controller_id)
+ break;
+ output_device = NULL;
+ }
+
+ return output_device.get() ? GetDeviceID(output_device.get()) : std::string();
+}
+
+std::string CoreAudioUtil::GetFriendlyName(const std::string& device_id) {
+ DCHECK(IsSupported());
+ ScopedComPtr<IMMDevice> audio_device = CreateDevice(device_id);
+ if (!audio_device.get())
+ return std::string();
+
+ AudioDeviceName device_name;
+ HRESULT hr = GetDeviceName(audio_device.get(), &device_name);
+ if (FAILED(hr))
+ return std::string();
+
+ return device_name.device_name;
+}
+
+bool CoreAudioUtil::DeviceIsDefault(EDataFlow flow,
+ ERole role,
+ const std::string& device_id) {
+ DCHECK(IsSupported());
+ ScopedComPtr<IMMDevice> device = CreateDefaultDevice(flow, role);
+ if (!device.get())
+ return false;
+
+ std::string str_default(GetDeviceID(device.get()));
+ return device_id.compare(str_default) == 0;
+}
+
+EDataFlow CoreAudioUtil::GetDataFlow(IMMDevice* device) {
+ DCHECK(IsSupported());
+ ScopedComPtr<IMMEndpoint> endpoint;
+ HRESULT hr = device->QueryInterface(endpoint.Receive());
+ if (FAILED(hr)) {
+ DVLOG(1) << "IMMDevice::QueryInterface: " << std::hex << hr;
+ return eAll;
+ }
+
+ EDataFlow data_flow;
+ hr = endpoint->GetDataFlow(&data_flow);
+ if (FAILED(hr)) {
+ DVLOG(1) << "IMMEndpoint::GetDataFlow: " << std::hex << hr;
+ return eAll;
+ }
+ return data_flow;
+}
+
+ScopedComPtr<IAudioClient> CoreAudioUtil::CreateClient(
+ IMMDevice* audio_device) {
+ DCHECK(IsSupported());
+
+ // Creates and activates an IAudioClient COM object given the selected
+ // endpoint device.
+ ScopedComPtr<IAudioClient> audio_client;
+ HRESULT hr =
+ audio_device->Activate(__uuidof(IAudioClient), CLSCTX_INPROC_SERVER, NULL,
+ audio_client.ReceiveVoid());
+ DVLOG_IF(1, FAILED(hr)) << "IMMDevice::Activate: " << std::hex << hr;
+ return audio_client;
+}
+
+ScopedComPtr<IAudioClient> CoreAudioUtil::CreateDefaultClient(
+ EDataFlow data_flow,
+ ERole role) {
+ DCHECK(IsSupported());
+ ScopedComPtr<IMMDevice> default_device(CreateDefaultDevice(data_flow, role));
+ return (default_device.get() ? CreateClient(default_device.get())
+ : ScopedComPtr<IAudioClient>());
+}
+
+ScopedComPtr<IAudioClient> CoreAudioUtil::CreateClient(
+ const std::string& device_id,
+ EDataFlow data_flow,
+ ERole role) {
+ if (IsDefaultDeviceId(device_id))
+ return CreateDefaultClient(data_flow, role);
+
+ ScopedComPtr<IMMDevice> device(CreateDevice(device_id));
+ if (!device.get())
+ return ScopedComPtr<IAudioClient>();
+
+ return CreateClient(device.get());
+}
+
+HRESULT CoreAudioUtil::GetSharedModeMixFormat(IAudioClient* client,
+ WAVEFORMATPCMEX* format) {
+ DCHECK(IsSupported());
+ ScopedCoMem<WAVEFORMATPCMEX> format_pcmex;
+ HRESULT hr =
+ client->GetMixFormat(reinterpret_cast<WAVEFORMATEX**>(&format_pcmex));
+ if (FAILED(hr))
+ return hr;
+
+ size_t bytes = sizeof(WAVEFORMATEX) + format_pcmex->Format.cbSize;
+ DCHECK_EQ(bytes, sizeof(WAVEFORMATPCMEX));
+
+ memcpy(format, format_pcmex, bytes);
+ DVLOG(2) << *format;
+
+ return hr;
+}
+
+bool CoreAudioUtil::IsFormatSupported(IAudioClient* client,
+ AUDCLNT_SHAREMODE share_mode,
+ const WAVEFORMATPCMEX* format) {
+ DCHECK(IsSupported());
+ ScopedCoMem<WAVEFORMATEXTENSIBLE> closest_match;
+ HRESULT hr = client->IsFormatSupported(
+ share_mode, reinterpret_cast<const WAVEFORMATEX*>(format),
+ reinterpret_cast<WAVEFORMATEX**>(&closest_match));
+
+ // This log can only be triggered for shared mode.
+ DLOG_IF(ERROR, hr == S_FALSE) << "Format is not supported "
+ << "but a closest match exists.";
+ // This log can be triggered both for shared and exclusive modes.
+ DLOG_IF(ERROR, hr == AUDCLNT_E_UNSUPPORTED_FORMAT) << "Unsupported format.";
+ if (hr == S_FALSE) {
+ DVLOG(2) << *closest_match;
+ }
+
+ return (hr == S_OK);
+}
+
+bool CoreAudioUtil::IsChannelLayoutSupported(const std::string& device_id,
+ EDataFlow data_flow,
+ ERole role,
+ ChannelLayout channel_layout) {
+ DCHECK(IsSupported());
+
+ // First, get the preferred mixing format for shared mode streams.
+
+ ScopedComPtr<IAudioClient> client(CreateClient(device_id, data_flow, role));
+ if (!client.get())
+ return false;
+
+ WAVEFORMATPCMEX format;
+ HRESULT hr = GetSharedModeMixFormat(client.get(), &format);
+ if (FAILED(hr))
+ return false;
+
+ // Next, check if it is possible to use an alternative format where the
+ // channel layout (and possibly number of channels) is modified.
+
+ // Convert generic channel layout into Windows-specific channel configuration.
+ ChannelConfig new_config = ChannelLayoutToChannelConfig(channel_layout);
+ if (new_config == KSAUDIO_SPEAKER_UNSUPPORTED) {
+ return false;
+ }
+ format.dwChannelMask = new_config;
+
+ // Modify the format if the new channel layout has changed the number of
+ // utilized channels.
+ const int channels = ChannelLayoutToChannelCount(channel_layout);
+ if (channels != format.Format.nChannels) {
+ format.Format.nChannels = channels;
+ format.Format.nBlockAlign = (format.Format.wBitsPerSample / 8) * channels;
+ format.Format.nAvgBytesPerSec =
+ format.Format.nSamplesPerSec * format.Format.nBlockAlign;
+ }
+ DVLOG(2) << format;
+
+ // Some devices can initialize a shared-mode stream with a format that is
+ // not identical to the mix format obtained from the GetMixFormat() method.
+ // However, chances of succeeding increases if we use the same number of
+ // channels and the same sample rate as the mix format. I.e, this call will
+ // return true only in those cases where the audio engine is able to support
+ // an even wider range of shared-mode formats where the installation package
+ // for the audio device includes a local effects (LFX) audio processing
+ // object (APO) that can handle format conversions.
+ return CoreAudioUtil::IsFormatSupported(client.get(),
+ AUDCLNT_SHAREMODE_SHARED, &format);
+}
+
+HRESULT CoreAudioUtil::GetDevicePeriod(IAudioClient* client,
+ AUDCLNT_SHAREMODE share_mode,
+ REFERENCE_TIME* device_period) {
+ DCHECK(IsSupported());
+
+ // Get the period of the engine thread.
+ REFERENCE_TIME default_period = 0;
+ REFERENCE_TIME minimum_period = 0;
+ HRESULT hr = client->GetDevicePeriod(&default_period, &minimum_period);
+ if (FAILED(hr))
+ return hr;
+
+ *device_period = (share_mode == AUDCLNT_SHAREMODE_SHARED) ? default_period
+ : minimum_period;
+ DVLOG(2) << "device_period: "
+ << RefererenceTimeToTimeDelta(*device_period).InMillisecondsF()
+ << " [ms]";
+ return hr;
+}
+
+HRESULT CoreAudioUtil::GetPreferredAudioParameters(IAudioClient* client,
+ AudioParameters* params) {
+ DCHECK(IsSupported());
+ WAVEFORMATPCMEX mix_format;
+ HRESULT hr = GetSharedModeMixFormat(client, &mix_format);
+ if (FAILED(hr))
+ return hr;
+
+ REFERENCE_TIME default_period = 0;
+ hr = GetDevicePeriod(client, AUDCLNT_SHAREMODE_SHARED, &default_period);
+ if (FAILED(hr))
+ return hr;
+
+ // Get the integer mask which corresponds to the channel layout the
+ // audio engine uses for its internal processing/mixing of shared-mode
+ // streams. This mask indicates which channels are present in the multi-
+ // channel stream. The least significant bit corresponds with the Front Left
+ // speaker, the next least significant bit corresponds to the Front Right
+ // speaker, and so on, continuing in the order defined in KsMedia.h.
+ // See http://msdn.microsoft.com/en-us/library/windows/hardware/ff537083.aspx
+ // for more details.
+ ChannelConfig channel_config = mix_format.dwChannelMask;
+
+ // Convert Microsoft's channel configuration to genric ChannelLayout.
+ ChannelLayout channel_layout = ChannelConfigToChannelLayout(channel_config);
+
+ // Some devices don't appear to set a valid channel layout, so guess based on
+ // the number of channels. See http://crbug.com/311906.
+ if (channel_layout == CHANNEL_LAYOUT_UNSUPPORTED) {
+ DVLOG(1) << "Unsupported channel config: " << std::hex << channel_config
+ << ". Guessing layout by channel count: " << std::dec
+ << mix_format.Format.nChannels;
+ channel_layout = GuessChannelLayout(mix_format.Format.nChannels);
+ }
+
+ // Preferred sample rate.
+ int sample_rate = mix_format.Format.nSamplesPerSec;
+
+ // TODO(henrika): possibly use format.Format.wBitsPerSample here instead.
+ // We use a hard-coded value of 16 bits per sample today even if most audio
+ // engines does the actual mixing in 32 bits per sample.
+ int bits_per_sample = 16;
+
+ // We are using the native device period to derive the smallest possible
+ // buffer size in shared mode. Note that the actual endpoint buffer will be
+ // larger than this size but it will be possible to fill it up in two calls.
+ // TODO(henrika): ensure that this scheme works for capturing as well.
+ int frames_per_buffer = static_cast<int>(
+ sample_rate * RefererenceTimeToTimeDelta(default_period).InSecondsF() +
+ 0.5);
+
+ DVLOG(1) << "channel_layout : " << channel_layout;
+ DVLOG(1) << "sample_rate : " << sample_rate;
+ DVLOG(1) << "bits_per_sample : " << bits_per_sample;
+ DVLOG(1) << "frames_per_buffer: " << frames_per_buffer;
+
+ AudioParameters audio_params(AudioParameters::AUDIO_PCM_LOW_LATENCY,
+ channel_layout, sample_rate, bits_per_sample,
+ frames_per_buffer);
+
+ *params = audio_params;
+ return hr;
+}
+
+HRESULT CoreAudioUtil::GetPreferredAudioParameters(const std::string& device_id,
+ bool is_output_device,
+ AudioParameters* params) {
+ DCHECK(IsSupported());
+
+ ScopedComPtr<IMMDevice> device;
+ if (device_id == AudioDeviceDescription::kDefaultDeviceId) {
+ device = CoreAudioUtil::CreateDefaultDevice(
+ is_output_device ? eRender : eCapture, eConsole);
+ } else if (device_id == AudioDeviceDescription::kLoopbackInputDeviceId ||
+ device_id == AudioDeviceDescription::kLoopbackWithMuteDeviceId) {
+ DCHECK(!is_output_device);
+ device = CoreAudioUtil::CreateDefaultDevice(eRender, eConsole);
+ } else if (device_id == AudioDeviceDescription::kCommunicationsDeviceId) {
+ device = CoreAudioUtil::CreateDefaultDevice(
+ is_output_device ? eRender : eCapture, eCommunications);
+ } else {
+ device = CreateDevice(device_id);
+ }
+
+ if (!device.get()) {
+ // Map NULL-pointer to new error code which can be different from the
+ // actual error code. The exact value is not important here.
+ return AUDCLNT_E_DEVICE_INVALIDATED;
+ }
+
+ ScopedComPtr<IAudioClient> client(CreateClient(device.get()));
+ if (!client.get()) {
+ // Map NULL-pointer to new error code which can be different from the
+ // actual error code. The exact value is not important here.
+ return AUDCLNT_E_ENDPOINT_CREATE_FAILED;
+ }
+
+ HRESULT hr = GetPreferredAudioParameters(client.get(), params);
+ if (FAILED(hr) || is_output_device || !params->IsValid())
+ return hr;
+
+ // The following functionality is only for input devices.
+ DCHECK(!is_output_device);
+
+ // TODO(dalecurtis): Old code rewrote != 1 channels to stereo, do we still
+ // need to do the same thing?
+ if (params->channels() != 1) {
+ params->Reset(params->format(), CHANNEL_LAYOUT_STEREO,
+ params->sample_rate(), params->bits_per_sample(),
+ params->frames_per_buffer());
+ }
+
+ return hr;
+}
+
+ChannelConfig CoreAudioUtil::GetChannelConfig(const std::string& device_id,
+ EDataFlow data_flow) {
+ ScopedComPtr<IAudioClient> client(
+ CreateClient(device_id, data_flow, eConsole));
+
+ WAVEFORMATPCMEX format = {};
+ if (!client.get() || FAILED(GetSharedModeMixFormat(client.get(), &format)))
+ return 0;
+
+ return static_cast<ChannelConfig>(format.dwChannelMask);
+}
+
+HRESULT CoreAudioUtil::SharedModeInitialize(IAudioClient* client,
+ const WAVEFORMATPCMEX* format,
+ HANDLE event_handle,
+ uint32_t* endpoint_buffer_size,
+ const GUID* session_guid) {
+ DCHECK(IsSupported());
+
+ // Use default flags (i.e, dont set AUDCLNT_STREAMFLAGS_NOPERSIST) to
+ // ensure that 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 = 0;
+
+ // 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 != NULL && event_handle != INVALID_HANDLE_VALUE);
+ if (use_event)
+ stream_flags |= AUDCLNT_STREAMFLAGS_EVENTCALLBACK;
+ DVLOG(2) << "stream_flags: 0x" << std::hex << stream_flags;
+
+ // Initialize the shared mode client for minimal delay.
+ HRESULT hr = client->Initialize(AUDCLNT_SHAREMODE_SHARED, stream_flags, 0, 0,
+ reinterpret_cast<const WAVEFORMATEX*>(format),
+ session_guid);
+ if (FAILED(hr)) {
+ DVLOG(1) << "IAudioClient::Initialize: " << std::hex << hr;
+ return hr;
+ }
+
+ if (use_event) {
+ hr = client->SetEventHandle(event_handle);
+ if (FAILED(hr)) {
+ DVLOG(1) << "IAudioClient::SetEventHandle: " << std::hex << hr;
+ return hr;
+ }
+ }
+
+ UINT32 buffer_size_in_frames = 0;
+ hr = client->GetBufferSize(&buffer_size_in_frames);
+ if (FAILED(hr)) {
+ DVLOG(1) << "IAudioClient::GetBufferSize: " << std::hex << hr;
+ return hr;
+ }
+
+ *endpoint_buffer_size = buffer_size_in_frames;
+ DVLOG(2) << "endpoint buffer size: " << buffer_size_in_frames;
+
+ // TODO(henrika): utilize when delay measurements are added.
+ REFERENCE_TIME latency = 0;
+ hr = client->GetStreamLatency(&latency);
+ DVLOG(2) << "stream latency: "
+ << RefererenceTimeToTimeDelta(latency).InMillisecondsF() << " [ms]";
+ return hr;
+}
+
+ScopedComPtr<IAudioRenderClient> CoreAudioUtil::CreateRenderClient(
+ IAudioClient* client) {
+ DCHECK(IsSupported());
+
+ // Get access to the IAudioRenderClient interface. This interface
+ // enables us to write output data to a rendering endpoint buffer.
+ ScopedComPtr<IAudioRenderClient> audio_render_client;
+ HRESULT hr = client->GetService(__uuidof(IAudioRenderClient),
+ audio_render_client.ReceiveVoid());
+ if (FAILED(hr)) {
+ DVLOG(1) << "IAudioClient::GetService: " << std::hex << hr;
+ return ScopedComPtr<IAudioRenderClient>();
+ }
+ return audio_render_client;
+}
+
+ScopedComPtr<IAudioCaptureClient> CoreAudioUtil::CreateCaptureClient(
+ IAudioClient* client) {
+ DCHECK(IsSupported());
+
+ // Get access to the IAudioCaptureClient interface. This interface
+ // enables us to read input data from a capturing endpoint buffer.
+ ScopedComPtr<IAudioCaptureClient> audio_capture_client;
+ HRESULT hr = client->GetService(__uuidof(IAudioCaptureClient),
+ audio_capture_client.ReceiveVoid());
+ if (FAILED(hr)) {
+ DVLOG(1) << "IAudioClient::GetService: " << std::hex << hr;
+ return ScopedComPtr<IAudioCaptureClient>();
+ }
+ return audio_capture_client;
+}
+
+bool CoreAudioUtil::FillRenderEndpointBufferWithSilence(
+ IAudioClient* client,
+ IAudioRenderClient* render_client) {
+ DCHECK(IsSupported());
+
+ UINT32 endpoint_buffer_size = 0;
+ if (FAILED(client->GetBufferSize(&endpoint_buffer_size)))
+ return false;
+
+ UINT32 num_queued_frames = 0;
+ if (FAILED(client->GetCurrentPadding(&num_queued_frames)))
+ return false;
+
+ BYTE* data = NULL;
+ int num_frames_to_fill = endpoint_buffer_size - num_queued_frames;
+ if (FAILED(render_client->GetBuffer(num_frames_to_fill, &data)))
+ return false;
+
+ // Using the AUDCLNT_BUFFERFLAGS_SILENT flag eliminates the need to
+ // explicitly write silence data to the rendering buffer.
+ DVLOG(2) << "filling up " << num_frames_to_fill << " frames with silence";
+ return SUCCEEDED(render_client->ReleaseBuffer(num_frames_to_fill,
+ AUDCLNT_BUFFERFLAGS_SILENT));
+}
+
+bool CoreAudioUtil::GetDxDiagDetails(std::string* driver_name,
+ std::string* driver_version) {
+ ScopedComPtr<IDxDiagProvider, &IID_IDxDiagProvider> provider;
+ HRESULT hr =
+ provider.CreateInstance(CLSID_DxDiagProvider, NULL, CLSCTX_INPROC_SERVER);
+ if (FAILED(hr))
+ return false;
+
+ DXDIAG_INIT_PARAMS params = {sizeof(params)};
+ params.dwDxDiagHeaderVersion = DXDIAG_DX9_SDK_VERSION;
+ params.bAllowWHQLChecks = FALSE;
+ params.pReserved = NULL;
+ hr = provider->Initialize(&params);
+ if (FAILED(hr))
+ return false;
+
+ ScopedComPtr<IDxDiagContainer, &IID_IDxDiagContainer> root;
+ hr = provider->GetRootContainer(root.Receive());
+ if (FAILED(hr))
+ return false;
+
+ // Limit to the SoundDevices subtree. The tree in its entirity is
+ // enormous and only this branch contains useful information.
+ ScopedComPtr<IDxDiagContainer, &IID_IDxDiagContainer> sound_devices;
+ hr = root->GetChildContainer(L"DxDiag_DirectSound.DxDiag_SoundDevices.0",
+ sound_devices.Receive());
+ if (FAILED(hr))
+ return false;
+
+ base::win::ScopedVariant variant;
+ hr = sound_devices->GetProp(L"szDriverName", variant.Receive());
+ if (FAILED(hr))
+ return false;
+
+ if (variant.type() == VT_BSTR && variant.ptr()->bstrVal) {
+ base::WideToUTF8(variant.ptr()->bstrVal, wcslen(variant.ptr()->bstrVal),
+ driver_name);
+ }
+
+ variant.Reset();
+ hr = sound_devices->GetProp(L"szDriverVersion", variant.Receive());
+ if (FAILED(hr))
+ return false;
+
+ if (variant.type() == VT_BSTR && variant.ptr()->bstrVal) {
+ base::WideToUTF8(variant.ptr()->bstrVal, wcslen(variant.ptr()->bstrVal),
+ driver_version);
+ }
+
+ return true;
+}
+
+} // namespace media

Powered by Google App Engine
This is Rietveld 408576698