// 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 #include #include #include "base/command_line.h" #include "base/logging.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/windows_version.h" #include "media/base/media_switches.h" using base::win::ScopedCoMem; using base::win::ScopedHandle; namespace media { 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 bool CanCreateDeviceEnumerator() { ScopedComPtr device_enumerator; HRESULT hr = device_enumerator.CreateInstance(__uuidof(MMDeviceEnumerator), NULL, CLSCTX_INPROC_SERVER); // If we hit CO_E_NOTINITIALIZED, CoInitialize has not been called and it // must be called at least once for each thread that uses the COM library. CHECK_NE(hr, CO_E_NOTINITIALIZED); return SUCCEEDED(hr); } static std::string GetDeviceID(IMMDevice* device) { ScopedCoMem 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; } bool CoreAudioUtil::IsSupported() { // It is possible to force usage of WaveXxx APIs by using a command line flag. const CommandLine* cmd_line = 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. static bool g_audioses_dll_available = LoadAudiosesDll(); if (!g_audioses_dll_available) 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. static bool g_can_create_device_enumerator = CanCreateDeviceEnumerator(); LOG_IF(ERROR, !g_can_create_device_enumerator) << "Failed to create Core Audio device enumerator on thread with ID " << GetCurrentThreadId(); return g_can_create_device_enumerator; } 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 CommandLine* cmd_line = 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 device_enumerator = CreateDeviceEnumerator(); if (!device_enumerator) 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 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(number_of_active_devices); } ScopedComPtr CoreAudioUtil::CreateDeviceEnumerator() { DCHECK(IsSupported()); ScopedComPtr device_enumerator; HRESULT hr = device_enumerator.CreateInstance(__uuidof(MMDeviceEnumerator), NULL, CLSCTX_INPROC_SERVER); CHECK(SUCCEEDED(hr)); return device_enumerator; } ScopedComPtr CoreAudioUtil::CreateDefaultDevice(EDataFlow data_flow, ERole role) { DCHECK(IsSupported()); ScopedComPtr endpoint_device; // Create the IMMDeviceEnumerator interface. ScopedComPtr device_enumerator = CreateDeviceEnumerator(); if (!device_enumerator) 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. DWORD state = DEVICE_STATE_DISABLED; hr = endpoint_device->GetState(&state); if (SUCCEEDED(hr)) { if (!(state & DEVICE_STATE_ACTIVE)) { DVLOG(1) << "Selected endpoint device is not active"; endpoint_device.Release(); } } return endpoint_device; } std::string CoreAudioUtil::GetDefaultOutputDeviceID() { DCHECK(IsSupported()); ScopedComPtr device(CreateDefaultDevice(eRender, eConsole)); return device ? GetDeviceID(device) : std::string(); } ScopedComPtr CoreAudioUtil::CreateDevice( const std::string& device_id) { DCHECK(IsSupported()); ScopedComPtr endpoint_device; // Create the IMMDeviceEnumerator interface. ScopedComPtr device_enumerator = CreateDeviceEnumerator(); if (!device_enumerator) 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; 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; // Retrieve user-friendly name of endpoint device. // Example: "Microphone (Realtek High Definition Audio)". ScopedComPtr properties; HRESULT hr = device->OpenPropertyStore(STGM_READ, properties.Receive()); if (FAILED(hr)) return hr; base::win::ScopedPropVariant friendly_name; hr = properties->GetValue(PKEY_Device_FriendlyName, friendly_name.Receive()); if (FAILED(hr)) return hr; if (friendly_name.get().vt == VT_LPWSTR && friendly_name.get().pwszVal) { base::WideToUTF8(friendly_name.get().pwszVal, wcslen(friendly_name.get().pwszVal), &device_name.device_name); } *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 topology; ScopedComPtr connector; ScopedCoMem 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 device_node; ScopedComPtr 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) { ScopedComPtr input_device(CreateDevice(input_device_id)); if (!input_device) return std::string(); // See if we can get id of the associated controller. ScopedComPtr enumerator(CreateDeviceEnumerator()); std::string controller_id(GetAudioControllerID(input_device, enumerator)); 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 collection; enumerator->EnumAudioEndpoints(eRender, DEVICE_STATE_ACTIVE, collection.Receive()); if (!collection) return std::string(); UINT count = 0; collection->GetCount(&count); ScopedComPtr output_device; for (UINT i = 0; i < count; ++i) { collection->Item(i, output_device.Receive()); std::string output_controller_id(GetAudioControllerID( output_device, enumerator)); if (output_controller_id == controller_id) break; output_device = NULL; } return output_device ? GetDeviceID(output_device) : std::string(); } std::string CoreAudioUtil::GetFriendlyName(const std::string& device_id) { DCHECK(IsSupported()); ScopedComPtr audio_device = CreateDevice(device_id); if (!audio_device) return std::string(); AudioDeviceName device_name; HRESULT hr = GetDeviceName(audio_device, &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 device = CreateDefaultDevice(flow, role); if (!device) return false; std::string str_default(GetDeviceID(device)); return device_id.compare(str_default) == 0; } EDataFlow CoreAudioUtil::GetDataFlow(IMMDevice* device) { DCHECK(IsSupported()); ScopedComPtr 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 CoreAudioUtil::CreateClient( IMMDevice* audio_device) { DCHECK(IsSupported()); // Creates and activates an IAudioClient COM object given the selected // endpoint device. ScopedComPtr 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 CoreAudioUtil::CreateDefaultClient( EDataFlow data_flow, ERole role) { DCHECK(IsSupported()); ScopedComPtr default_device(CreateDefaultDevice(data_flow, role)); return (default_device ? CreateClient(default_device) : ScopedComPtr()); } ScopedComPtr CoreAudioUtil::CreateClient( const std::string& device_id, EDataFlow data_flow, ERole role) { if (device_id.empty()) return CreateDefaultClient(data_flow, role); ScopedComPtr device(CreateDevice(device_id)); if (!device) return ScopedComPtr(); return CreateClient(device); } HRESULT CoreAudioUtil::GetSharedModeMixFormat( IAudioClient* client, WAVEFORMATPCMEX* format) { DCHECK(IsSupported()); ScopedCoMem format_pcmex; HRESULT hr = client->GetMixFormat( reinterpret_cast(&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 closest_match; HRESULT hr = client->IsFormatSupported( share_mode, reinterpret_cast(format), reinterpret_cast(&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 client(CreateClient(device_id, data_flow, role)); if (!client) return false; WAVEFORMATPCMEX format; HRESULT hr = GetSharedModeMixFormat(client, &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, 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) { VLOG(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(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( EDataFlow data_flow, ERole role, AudioParameters* params) { DCHECK(IsSupported()); ScopedComPtr client(CreateDefaultClient(data_flow, role)); if (!client) { // 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, params); if (FAILED(hr)) return hr; if (role == eCommunications) { // Raise the 'DUCKING' flag for default communication devices. *params = AudioParameters(params->format(), params->channel_layout(), params->channels(), params->input_channels(), params->sample_rate(), params->bits_per_sample(), params->frames_per_buffer(), params->effects() | AudioParameters::DUCKING); } return hr; } HRESULT CoreAudioUtil::GetPreferredAudioParameters( const std::string& device_id, AudioParameters* params) { DCHECK(IsSupported()); ScopedComPtr device(CreateDevice(device_id)); if (!device) { // 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 client(CreateClient(device)); if (!client) { // 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; } return GetPreferredAudioParameters(client, params); } ChannelConfig CoreAudioUtil::GetChannelConfig(const std::string& device_id, EDataFlow data_flow) { ScopedComPtr client( CreateClient(device_id, data_flow, eConsole)); WAVEFORMATPCMEX format = {0}; if (!client || FAILED(GetSharedModeMixFormat(client, &format))) return 0; return static_cast(format.dwChannelMask); } HRESULT CoreAudioUtil::SharedModeInitialize(IAudioClient* client, const WAVEFORMATPCMEX* format, HANDLE event_handle, uint32* endpoint_buffer_size) { 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(format), NULL); 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 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 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(); } return audio_render_client; } ScopedComPtr 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 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(); } 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)); } } // namespace media