blob: b501cbfa98c6400c326ef26301419ce645eb6514 [file] [log] [blame]
// 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 <Audioclient.h>
#include <Functiondiscoverykeys_devpkey.h>
#include "base/command_line.h"
#include "base/logging.h"
#include "base/stringprintf.h"
#include "base/utf_string_conversions.h"
#include "base/win/scoped_co_mem.h"
#include "base/win/scoped_handle.h"
#include "base/win/windows_version.h"
#include "media/base/media_switches.h"
using base::win::ScopedCoMem;
using base::win::ScopedHandle;
namespace media {
typedef uint32 ChannelConfig;
// 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 layout: " << config;
return CHANNEL_LAYOUT_UNSUPPORTED;
}
}
// Scoped PROPVARIANT class for automatically freeing a COM PROPVARIANT
// structure at the end of a scope.
class ScopedPropertyVariant {
public:
ScopedPropertyVariant() {
PropVariantInit(&propvar_);
}
~ScopedPropertyVariant() {
PropVariantClear(&propvar_);
}
// Retrieves the pointer address.
// Used to receive a PROPVARIANT as an out argument (and take ownership).
PROPVARIANT* Receive() {
DCHECK_EQ(propvar_.vt, VT_EMPTY);
return &propvar_;
}
VARTYPE type() const {
return propvar_.vt;
}
LPWSTR as_wide_string() const {
DCHECK_EQ(type(), VT_LPWSTR);
return propvar_.pwszVal;
}
private:
PROPVARIANT propvar_;
DISALLOW_COPY_AND_ASSIGN(ScopedPropertyVariant);
};
bool CoreAudioUtil::IsSupported() {
// 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.
return (base::win::GetVersion() >= base::win::VERSION_VISTA);
}
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(CoreAudioUtil::IsSupported());
// Create the IMMDeviceEnumerator interface.
ScopedComPtr<IMMDeviceEnumerator> 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<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(CoreAudioUtil::IsSupported());
ScopedComPtr<IMMDeviceEnumerator> device_enumerator;
HRESULT hr = CoCreateInstance(__uuidof(MMDeviceEnumerator),
NULL,
CLSCTX_INPROC_SERVER,
__uuidof(IMMDeviceEnumerator),
device_enumerator.ReceiveVoid());
// CO_E_NOTINITIALIZED is the most likely reason for failure and if that
// happens we might as well die here.
CHECK(SUCCEEDED(hr));
return device_enumerator;
}
ScopedComPtr<IMMDevice> CoreAudioUtil::CreateDefaultDevice(EDataFlow data_flow,
ERole role) {
DCHECK(CoreAudioUtil::IsSupported());
ScopedComPtr<IMMDevice> endpoint_device;
// Create the IMMDeviceEnumerator interface.
ScopedComPtr<IMMDeviceEnumerator> 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;
}
ScopedComPtr<IMMDevice> CoreAudioUtil::CreateDevice(
const std::string& device_id) {
DCHECK(CoreAudioUtil::IsSupported());
ScopedComPtr<IMMDevice> endpoint_device;
// Create the IMMDeviceEnumerator interface.
ScopedComPtr<IMMDeviceEnumerator> 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(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(CoreAudioUtil::IsSupported());
// Retrieve unique name of endpoint device.
// Example: "{0.0.1.00000000}.{8db6020f-18e3-4f25-b6f5-7726c9122574}".
AudioDeviceName device_name;
ScopedCoMem<WCHAR> endpoint_device_id;
HRESULT hr = device->GetId(&endpoint_device_id);
if (FAILED(hr))
return hr;
WideToUTF8(endpoint_device_id, wcslen(endpoint_device_id),
&device_name.unique_id);
// Retrieve user-friendly name of endpoint device.
// Example: "Microphone (Realtek High Definition Audio)".
ScopedComPtr<IPropertyStore> properties;
hr = device->OpenPropertyStore(STGM_READ, properties.Receive());
if (FAILED(hr))
return hr;
ScopedPropertyVariant friendly_name;
hr = properties->GetValue(PKEY_Device_FriendlyName, friendly_name.Receive());
if (FAILED(hr))
return hr;
if (friendly_name.as_wide_string()) {
WideToUTF8(friendly_name.as_wide_string(),
wcslen(friendly_name.as_wide_string()),
&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::GetFriendlyName(const std::string& device_id) {
DCHECK(CoreAudioUtil::IsSupported());
ScopedComPtr<IMMDevice> 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,
std::string device_id) {
DCHECK(CoreAudioUtil::IsSupported());
ScopedComPtr<IMMDevice> device = CreateDefaultDevice(flow, role);
if (!device)
return false;
ScopedCoMem<WCHAR> default_device_id;
HRESULT hr = device->GetId(&default_device_id);
if (FAILED(hr))
return false;
std::string str_default;
WideToUTF8(default_device_id, wcslen(default_device_id), &str_default);
if (device_id.compare(str_default) != 0)
return false;
return true;
}
EDataFlow CoreAudioUtil::GetDataFlow(IMMDevice* device) {
DCHECK(CoreAudioUtil::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(CoreAudioUtil::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(CoreAudioUtil::IsSupported());
ScopedComPtr<IMMDevice> default_device(CreateDefaultDevice(data_flow, role));
return (default_device ? CreateClient(default_device) :
ScopedComPtr<IAudioClient>());
}
HRESULT CoreAudioUtil::GetSharedModeMixFormat(
IAudioClient* client, WAVEFORMATPCMEX* format) {
DCHECK(CoreAudioUtil::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) << "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 hr;
}
bool CoreAudioUtil::IsFormatSupported(IAudioClient* client,
AUDCLNT_SHAREMODE share_mode,
const WAVEFORMATPCMEX* format) {
DCHECK(CoreAudioUtil::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) << "wFormatTag: " << closest_match->Format.wFormatTag
<< ", nChannels: " << closest_match->Format.nChannels
<< ", nSamplesPerSec: " << closest_match->Format.nSamplesPerSec
<< ", wBitsPerSample: " << closest_match->Format.wBitsPerSample;
}
return (hr == S_OK);
}
HRESULT CoreAudioUtil::GetDevicePeriod(IAudioClient* client,
AUDCLNT_SHAREMODE share_mode,
REFERENCE_TIME* device_period) {
DCHECK(CoreAudioUtil::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(CoreAudioUtil::IsSupported());
WAVEFORMATPCMEX format;
HRESULT hr = GetSharedModeMixFormat(client, &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 = format.dwChannelMask;
// Convert Microsoft's channel configuration to genric ChannelLayout.
ChannelLayout channel_layout = ChannelConfigToChannelLayout(channel_config);
// Store preferred sample rate and buffer size.
int sample_rate = format.Format.nSamplesPerSec;
int frames_per_buffer = static_cast<int>(sample_rate *
RefererenceTimeToTimeDelta(default_period).InSecondsF() + 0.5);
// 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;
DVLOG(2) << "channel_layout : " << channel_layout;
DVLOG(2) << "sample_rate : " << sample_rate;
DVLOG(2) << "bits_per_sample : " << bits_per_sample;
DVLOG(2) << "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(CoreAudioUtil::IsSupported());
ScopedComPtr<IAudioClient> 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;
}
return GetPreferredAudioParameters(client, params);
}
HRESULT CoreAudioUtil::SharedModeInitialize(IAudioClient* client,
const WAVEFORMATPCMEX* format,
HANDLE event_handle,
size_t* endpoint_buffer_size) {
DCHECK(CoreAudioUtil::IsSupported());
DWORD stream_flags = AUDCLNT_STREAMFLAGS_NOPERSIST;
// Enable event-driven streaming if a valid event handle is provided.
// After the stream starts, the audio engine will signal the event handle
// to notify the client each time a buffer becomes ready to process.
// Event-driven buffering is supported for both rendering and capturing.
// Both shared-mode and exclusive-mode streams can use event-driven buffering.
bool use_event = (event_handle != 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),
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 = static_cast<size_t>(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(CoreAudioUtil::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>();
}
// TODO(henrika): verify that this scheme is the same for shared mode and
// exclusive mode streams.
// Avoid start-up glitches by filling up the endpoint buffer with "silence"
// before starting the stream.
UINT32 endpoint_buffer_size = 0;
hr = client->GetBufferSize(&endpoint_buffer_size);
DVLOG_IF(1, FAILED(hr)) << "IAudioClient::GetBufferSize: " << std::hex << hr;
BYTE* data = NULL;
hr = audio_render_client->GetBuffer(endpoint_buffer_size, &data);
DVLOG_IF(1, FAILED(hr)) << "IAudioRenderClient::GetBuffer: "
<< std::hex << hr;
if (SUCCEEDED(hr)) {
// Using the AUDCLNT_BUFFERFLAGS_SILENT flag eliminates the need to
// explicitly write silence data to the rendering buffer.
hr = audio_render_client->ReleaseBuffer(endpoint_buffer_size,
AUDCLNT_BUFFERFLAGS_SILENT);
DVLOG_IF(1, FAILED(hr)) << "IAudioRenderClient::ReleaseBuffer: "
<< std::hex << hr;
}
// Sanity check: verify that the endpoint buffer is filled with silence.
UINT32 num_queued_frames = 0;
client->GetCurrentPadding(&num_queued_frames);
DCHECK(num_queued_frames == endpoint_buffer_size);
return audio_render_client;
}
ScopedComPtr<IAudioCaptureClient> CoreAudioUtil::CreateCaptureClient(
IAudioClient* client) {
DCHECK(CoreAudioUtil::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;
}
} // namespace media