blob: e9ed0c4f597feefab9fd8b4ac233847835402733 [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 "base/memory/scoped_ptr.h"
#include "base/strings/utf_string_conversions.h"
#include "base/synchronization/waitable_event.h"
#include "base/win/scoped_co_mem.h"
#include "base/win/scoped_com_initializer.h"
#include "base/win/scoped_handle.h"
#include "media/audio/win/core_audio_util_win.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
using base::win::ScopedCOMInitializer;
namespace media {
class CoreAudioUtilWinTest : public ::testing::Test {
protected:
// The test runs on a COM thread in the multithreaded apartment (MTA).
// If we don't initialize the COM library on a thread before using COM,
// all function calls will return CO_E_NOTINITIALIZED.
CoreAudioUtilWinTest()
: com_init_(ScopedCOMInitializer::kMTA) {
DCHECK(com_init_.succeeded());
}
virtual ~CoreAudioUtilWinTest() {}
bool CanRunAudioTest() {
bool core_audio = CoreAudioUtil::IsSupported();
if (!core_audio)
return false;
int capture_devices = CoreAudioUtil::NumberOfActiveDevices(eCapture);
int render_devices = CoreAudioUtil::NumberOfActiveDevices(eRender);
return ((capture_devices > 0) && (render_devices > 0));
}
ScopedCOMInitializer com_init_;
};
TEST_F(CoreAudioUtilWinTest, NumberOfActiveDevices) {
if (!CanRunAudioTest())
return;
int render_devices = CoreAudioUtil::NumberOfActiveDevices(eRender);
EXPECT_GT(render_devices, 0);
int capture_devices = CoreAudioUtil::NumberOfActiveDevices(eCapture);
EXPECT_GT(capture_devices, 0);
int total_devices = CoreAudioUtil::NumberOfActiveDevices(eAll);
EXPECT_EQ(total_devices, render_devices + capture_devices);
}
TEST_F(CoreAudioUtilWinTest, CreateDeviceEnumerator) {
if (!CanRunAudioTest())
return;
ScopedComPtr<IMMDeviceEnumerator> enumerator =
CoreAudioUtil::CreateDeviceEnumerator();
EXPECT_TRUE(enumerator);
}
TEST_F(CoreAudioUtilWinTest, CreateDefaultDevice) {
if (!CanRunAudioTest())
return;
struct {
EDataFlow flow;
ERole role;
} data[] = {
{eRender, eConsole},
{eRender, eCommunications},
{eRender, eMultimedia},
{eCapture, eConsole},
{eCapture, eCommunications},
{eCapture, eMultimedia}
};
// Create default devices for all flow/role combinations above.
ScopedComPtr<IMMDevice> audio_device;
for (int i = 0; i < arraysize(data); ++i) {
audio_device =
CoreAudioUtil::CreateDefaultDevice(data[i].flow, data[i].role);
EXPECT_TRUE(audio_device);
EXPECT_EQ(data[i].flow, CoreAudioUtil::GetDataFlow(audio_device));
}
// Only eRender and eCapture are allowed as flow parameter.
audio_device = CoreAudioUtil::CreateDefaultDevice(eAll, eConsole);
EXPECT_FALSE(audio_device);
}
TEST_F(CoreAudioUtilWinTest, CreateDevice) {
if (!CanRunAudioTest())
return;
// Get name and ID of default device used for playback.
ScopedComPtr<IMMDevice> default_render_device =
CoreAudioUtil::CreateDefaultDevice(eRender, eConsole);
AudioDeviceName default_render_name;
EXPECT_TRUE(SUCCEEDED(CoreAudioUtil::GetDeviceName(default_render_device,
&default_render_name)));
// Use the uniqe ID as input to CreateDevice() and create a corresponding
// IMMDevice.
ScopedComPtr<IMMDevice> audio_device =
CoreAudioUtil::CreateDevice(default_render_name.unique_id);
EXPECT_TRUE(audio_device);
// Verify that the two IMMDevice interfaces represents the same endpoint
// by comparing their unique IDs.
AudioDeviceName device_name;
EXPECT_TRUE(SUCCEEDED(CoreAudioUtil::GetDeviceName(audio_device,
&device_name)));
EXPECT_EQ(default_render_name.unique_id, device_name.unique_id);
}
TEST_F(CoreAudioUtilWinTest, GetDefaultDeviceName) {
if (!CanRunAudioTest())
return;
struct {
EDataFlow flow;
ERole role;
} data[] = {
{eRender, eConsole},
{eRender, eCommunications},
{eCapture, eConsole},
{eCapture, eCommunications}
};
// Get name and ID of default devices for all flow/role combinations above.
ScopedComPtr<IMMDevice> audio_device;
AudioDeviceName device_name;
for (int i = 0; i < arraysize(data); ++i) {
audio_device =
CoreAudioUtil::CreateDefaultDevice(data[i].flow, data[i].role);
EXPECT_TRUE(SUCCEEDED(CoreAudioUtil::GetDeviceName(audio_device,
&device_name)));
EXPECT_FALSE(device_name.device_name.empty());
EXPECT_FALSE(device_name.unique_id.empty());
}
}
TEST_F(CoreAudioUtilWinTest, GetAudioControllerID) {
if (!CanRunAudioTest())
return;
ScopedComPtr<IMMDeviceEnumerator> enumerator(
CoreAudioUtil::CreateDeviceEnumerator());
ASSERT_TRUE(enumerator);
// Enumerate all active input and output devices and fetch the ID of
// the associated device.
EDataFlow flows[] = { eRender , eCapture };
for (int i = 0; i < arraysize(flows); ++i) {
ScopedComPtr<IMMDeviceCollection> collection;
ASSERT_TRUE(SUCCEEDED(enumerator->EnumAudioEndpoints(flows[i],
DEVICE_STATE_ACTIVE, collection.Receive())));
UINT count = 0;
collection->GetCount(&count);
for (UINT j = 0; j < count; ++j) {
ScopedComPtr<IMMDevice> device;
collection->Item(j, device.Receive());
std::string controller_id(CoreAudioUtil::GetAudioControllerID(
device, enumerator));
EXPECT_FALSE(controller_id.empty());
}
}
}
TEST_F(CoreAudioUtilWinTest, GetFriendlyName) {
if (!CanRunAudioTest())
return;
// Get name and ID of default device used for recording.
ScopedComPtr<IMMDevice> audio_device =
CoreAudioUtil::CreateDefaultDevice(eCapture, eConsole);
AudioDeviceName device_name;
HRESULT hr = CoreAudioUtil::GetDeviceName(audio_device, &device_name);
EXPECT_TRUE(SUCCEEDED(hr));
// Use unique ID as input to GetFriendlyName() and compare the result
// with the already obtained friendly name for the default capture device.
std::string friendly_name = CoreAudioUtil::GetFriendlyName(
device_name.unique_id);
EXPECT_EQ(friendly_name, device_name.device_name);
// Same test as above but for playback.
audio_device = CoreAudioUtil::CreateDefaultDevice(eRender, eConsole);
hr = CoreAudioUtil::GetDeviceName(audio_device, &device_name);
EXPECT_TRUE(SUCCEEDED(hr));
friendly_name = CoreAudioUtil::GetFriendlyName(device_name.unique_id);
EXPECT_EQ(friendly_name, device_name.device_name);
}
TEST_F(CoreAudioUtilWinTest, DeviceIsDefault) {
if (!CanRunAudioTest())
return;
// Verify that the default render device is correctly identified as a
// default device.
ScopedComPtr<IMMDevice> audio_device =
CoreAudioUtil::CreateDefaultDevice(eRender, eConsole);
AudioDeviceName name;
EXPECT_TRUE(SUCCEEDED(CoreAudioUtil::GetDeviceName(audio_device, &name)));
const std::string id = name.unique_id;
EXPECT_TRUE(CoreAudioUtil::DeviceIsDefault(eRender, eConsole, id));
EXPECT_FALSE(CoreAudioUtil::DeviceIsDefault(eCapture, eConsole, id));
}
TEST_F(CoreAudioUtilWinTest, CreateDefaultClient) {
if (!CanRunAudioTest())
return;
EDataFlow data[] = {eRender, eCapture};
for (int i = 0; i < arraysize(data); ++i) {
ScopedComPtr<IAudioClient> client;
client = CoreAudioUtil::CreateDefaultClient(data[i], eConsole);
EXPECT_TRUE(client);
}
}
TEST_F(CoreAudioUtilWinTest, CreateClient) {
if (!CanRunAudioTest())
return;
EDataFlow data[] = {eRender, eCapture};
for (int i = 0; i < arraysize(data); ++i) {
ScopedComPtr<IMMDevice> device;
ScopedComPtr<IAudioClient> client;
device = CoreAudioUtil::CreateDefaultDevice(data[i], eConsole);
EXPECT_TRUE(device);
EXPECT_EQ(data[i], CoreAudioUtil::GetDataFlow(device));
client = CoreAudioUtil::CreateClient(device);
EXPECT_TRUE(client);
}
}
TEST_F(CoreAudioUtilWinTest, GetSharedModeMixFormat) {
if (!CanRunAudioTest())
return;
ScopedComPtr<IMMDevice> device;
ScopedComPtr<IAudioClient> client;
device = CoreAudioUtil::CreateDefaultDevice(eRender, eConsole);
EXPECT_TRUE(device);
client = CoreAudioUtil::CreateClient(device);
EXPECT_TRUE(client);
// Perform a simple sanity test of the aquired format structure.
WAVEFORMATPCMEX format;
EXPECT_TRUE(SUCCEEDED(CoreAudioUtil::GetSharedModeMixFormat(client,
&format)));
EXPECT_GE(format.Format.nChannels, 1);
EXPECT_GE(format.Format.nSamplesPerSec, 8000u);
EXPECT_GE(format.Format.wBitsPerSample, 16);
EXPECT_GE(format.Samples.wValidBitsPerSample, 16);
EXPECT_EQ(format.Format.wFormatTag, WAVE_FORMAT_EXTENSIBLE);
}
TEST_F(CoreAudioUtilWinTest, IsChannelLayoutSupported) {
if (!CanRunAudioTest())
return;
// The preferred channel layout should always be supported. Being supported
// means that it is possible to initialize a shared mode stream with the
// particular channel layout.
AudioParameters mix_params;
HRESULT hr = CoreAudioUtil::GetPreferredAudioParameters(eRender, eConsole,
&mix_params);
EXPECT_TRUE(SUCCEEDED(hr));
EXPECT_TRUE(mix_params.IsValid());
EXPECT_TRUE(CoreAudioUtil::IsChannelLayoutSupported(
std::string(), eRender, eConsole, mix_params.channel_layout()));
// Check if it is possible to modify the channel layout to stereo for a
// device which reports that it prefers to be openen up in an other
// channel configuration.
if (mix_params.channel_layout() != CHANNEL_LAYOUT_STEREO) {
ChannelLayout channel_layout = CHANNEL_LAYOUT_STEREO;
// TODO(henrika): it might be too pessimistic to assume false as return
// value here.
EXPECT_FALSE(CoreAudioUtil::IsChannelLayoutSupported(
std::string(), eRender, eConsole, channel_layout));
}
}
TEST_F(CoreAudioUtilWinTest, GetDevicePeriod) {
if (!CanRunAudioTest())
return;
EDataFlow data[] = {eRender, eCapture};
// Verify that the device periods are valid for the default render and
// capture devices.
for (int i = 0; i < arraysize(data); ++i) {
ScopedComPtr<IAudioClient> client;
REFERENCE_TIME shared_time_period = 0;
REFERENCE_TIME exclusive_time_period = 0;
client = CoreAudioUtil::CreateDefaultClient(data[i], eConsole);
EXPECT_TRUE(client);
EXPECT_TRUE(SUCCEEDED(CoreAudioUtil::GetDevicePeriod(
client, AUDCLNT_SHAREMODE_SHARED, &shared_time_period)));
EXPECT_GT(shared_time_period, 0);
EXPECT_TRUE(SUCCEEDED(CoreAudioUtil::GetDevicePeriod(
client, AUDCLNT_SHAREMODE_EXCLUSIVE, &exclusive_time_period)));
EXPECT_GT(exclusive_time_period, 0);
EXPECT_LE(exclusive_time_period, shared_time_period);
}
}
TEST_F(CoreAudioUtilWinTest, GetPreferredAudioParameters) {
if (!CanRunAudioTest())
return;
EDataFlow data[] = {eRender, eCapture};
// Verify that the preferred audio parameters are OK for the default render
// and capture devices.
for (int i = 0; i < arraysize(data); ++i) {
ScopedComPtr<IAudioClient> client;
AudioParameters params;
client = CoreAudioUtil::CreateDefaultClient(data[i], eConsole);
EXPECT_TRUE(client);
EXPECT_TRUE(SUCCEEDED(CoreAudioUtil::GetPreferredAudioParameters(client,
&params)));
EXPECT_TRUE(params.IsValid());
}
}
TEST_F(CoreAudioUtilWinTest, SharedModeInitialize) {
if (!CanRunAudioTest())
return;
ScopedComPtr<IAudioClient> client;
client = CoreAudioUtil::CreateDefaultClient(eRender, eConsole);
EXPECT_TRUE(client);
WAVEFORMATPCMEX format;
EXPECT_TRUE(SUCCEEDED(CoreAudioUtil::GetSharedModeMixFormat(client,
&format)));
// Perform a shared-mode initialization without event-driven buffer handling.
uint32 endpoint_buffer_size = 0;
HRESULT hr = CoreAudioUtil::SharedModeInitialize(client, &format, NULL,
&endpoint_buffer_size);
EXPECT_TRUE(SUCCEEDED(hr));
EXPECT_GT(endpoint_buffer_size, 0u);
// It is only possible to create a client once.
hr = CoreAudioUtil::SharedModeInitialize(client, &format, NULL,
&endpoint_buffer_size);
EXPECT_FALSE(SUCCEEDED(hr));
EXPECT_EQ(hr, AUDCLNT_E_ALREADY_INITIALIZED);
// Verify that it is possible to reinitialize the client after releasing it.
client = CoreAudioUtil::CreateDefaultClient(eRender, eConsole);
EXPECT_TRUE(client);
hr = CoreAudioUtil::SharedModeInitialize(client, &format, NULL,
&endpoint_buffer_size);
EXPECT_TRUE(SUCCEEDED(hr));
EXPECT_GT(endpoint_buffer_size, 0u);
// Use a non-supported format and verify that initialization fails.
// A simple way to emulate an invalid format is to use the shared-mode
// mixing format and modify the preferred sample.
client = CoreAudioUtil::CreateDefaultClient(eRender, eConsole);
EXPECT_TRUE(client);
format.Format.nSamplesPerSec = format.Format.nSamplesPerSec + 1;
EXPECT_FALSE(CoreAudioUtil::IsFormatSupported(
client, AUDCLNT_SHAREMODE_SHARED, &format));
hr = CoreAudioUtil::SharedModeInitialize(client, &format, NULL,
&endpoint_buffer_size);
EXPECT_TRUE(FAILED(hr));
EXPECT_EQ(hr, E_INVALIDARG);
// Finally, perform a shared-mode initialization using event-driven buffer
// handling. The event handle will be signaled when an audio buffer is ready
// to be processed by the client (not verified here).
// The event handle should be in the nonsignaled state.
base::win::ScopedHandle event_handle(::CreateEvent(NULL, TRUE, FALSE, NULL));
client = CoreAudioUtil::CreateDefaultClient(eRender, eConsole);
EXPECT_TRUE(client);
EXPECT_TRUE(SUCCEEDED(CoreAudioUtil::GetSharedModeMixFormat(client,
&format)));
EXPECT_TRUE(CoreAudioUtil::IsFormatSupported(
client, AUDCLNT_SHAREMODE_SHARED, &format));
hr = CoreAudioUtil::SharedModeInitialize(client, &format, event_handle.Get(),
&endpoint_buffer_size);
EXPECT_TRUE(SUCCEEDED(hr));
EXPECT_GT(endpoint_buffer_size, 0u);
}
TEST_F(CoreAudioUtilWinTest, CreateRenderAndCaptureClients) {
if (!CanRunAudioTest())
return;
EDataFlow data[] = {eRender, eCapture};
WAVEFORMATPCMEX format;
uint32 endpoint_buffer_size = 0;
for (int i = 0; i < arraysize(data); ++i) {
ScopedComPtr<IAudioClient> client;
ScopedComPtr<IAudioRenderClient> render_client;
ScopedComPtr<IAudioCaptureClient> capture_client;
client = CoreAudioUtil::CreateDefaultClient(data[i], eConsole);
EXPECT_TRUE(client);
EXPECT_TRUE(SUCCEEDED(CoreAudioUtil::GetSharedModeMixFormat(client,
&format)));
if (data[i] == eRender) {
// It is not possible to create a render client using an unitialized
// client interface.
render_client = CoreAudioUtil::CreateRenderClient(client);
EXPECT_FALSE(render_client);
// Do a proper initialization and verify that it works this time.
CoreAudioUtil::SharedModeInitialize(client, &format, NULL,
&endpoint_buffer_size);
render_client = CoreAudioUtil::CreateRenderClient(client);
EXPECT_TRUE(render_client);
EXPECT_GT(endpoint_buffer_size, 0u);
} else if (data[i] == eCapture) {
// It is not possible to create a capture client using an unitialized
// client interface.
capture_client = CoreAudioUtil::CreateCaptureClient(client);
EXPECT_FALSE(capture_client);
// Do a proper initialization and verify that it works this time.
CoreAudioUtil::SharedModeInitialize(client, &format, NULL,
&endpoint_buffer_size);
capture_client = CoreAudioUtil::CreateCaptureClient(client);
EXPECT_TRUE(capture_client);
EXPECT_GT(endpoint_buffer_size, 0u);
}
}
}
TEST_F(CoreAudioUtilWinTest, FillRenderEndpointBufferWithSilence) {
if (!CanRunAudioTest())
return;
// Create default clients using the default mixing format for shared mode.
ScopedComPtr<IAudioClient> client(
CoreAudioUtil::CreateDefaultClient(eRender, eConsole));
EXPECT_TRUE(client);
WAVEFORMATPCMEX format;
uint32 endpoint_buffer_size = 0;
EXPECT_TRUE(SUCCEEDED(CoreAudioUtil::GetSharedModeMixFormat(client,
&format)));
CoreAudioUtil::SharedModeInitialize(client, &format, NULL,
&endpoint_buffer_size);
EXPECT_GT(endpoint_buffer_size, 0u);
ScopedComPtr<IAudioRenderClient> render_client(
CoreAudioUtil::CreateRenderClient(client));
EXPECT_TRUE(render_client);
// The endpoint audio buffer should not be filled up by default after being
// created.
UINT32 num_queued_frames = 0;
client->GetCurrentPadding(&num_queued_frames);
EXPECT_EQ(num_queued_frames, 0u);
// Fill it up with zeros and verify that the buffer is full.
// It is not possible to verify that the actual data consists of zeros
// since we can't access data that has already been sent to the endpoint
// buffer.
EXPECT_TRUE(CoreAudioUtil::FillRenderEndpointBufferWithSilence(
client, render_client));
client->GetCurrentPadding(&num_queued_frames);
EXPECT_EQ(num_queued_frames, endpoint_buffer_size);
}
// This test can only succeed on a machine that has audio hardware
// that has both input and output devices. Currently this is the case
// with our test bots and the CanRunAudioTest() method should make sure
// that the test won't run in unsupported environments, but be warned.
TEST_F(CoreAudioUtilWinTest, GetMatchingOutputDeviceID) {
if (!CanRunAudioTest())
return;
bool found_a_pair = false;
ScopedComPtr<IMMDeviceEnumerator> enumerator(
CoreAudioUtil::CreateDeviceEnumerator());
ASSERT_TRUE(enumerator);
// Enumerate all active input and output devices and fetch the ID of
// the associated device.
ScopedComPtr<IMMDeviceCollection> collection;
ASSERT_TRUE(SUCCEEDED(enumerator->EnumAudioEndpoints(eCapture,
DEVICE_STATE_ACTIVE, collection.Receive())));
UINT count = 0;
collection->GetCount(&count);
for (UINT i = 0; i < count && !found_a_pair; ++i) {
ScopedComPtr<IMMDevice> device;
collection->Item(i, device.Receive());
base::win::ScopedCoMem<WCHAR> wide_id;
device->GetId(&wide_id);
std::string id;
WideToUTF8(wide_id, wcslen(wide_id), &id);
found_a_pair = !CoreAudioUtil::GetMatchingOutputDeviceID(id).empty();
}
EXPECT_TRUE(found_a_pair);
}
TEST_F(CoreAudioUtilWinTest, GetDefaultOutputDeviceID) {
if (!CanRunAudioTest())
return;
std::string default_device_id(CoreAudioUtil::GetDefaultOutputDeviceID());
EXPECT_FALSE(default_device_id.empty());
}
} // namespace media