/* * Copyright (c) 2018 The WebRTC project authors. All Rights Reserved. * * Use of this source code is governed by a BSD-style license * that can be found in the LICENSE file in the root of the source * tree. An additional intellectual property rights grant can be found * in the file PATENTS. All contributing project authors may * be found in the AUTHORS file in the root of the source tree. */ #include "modules/audio_device/win/core_audio_utility_win.h" #include "rtc_base/arraysize.h" #include "rtc_base/logging.h" #include "rtc_base/win/scoped_com_initializer.h" #include "rtc_base/win/windows_version.h" #include "test/gtest.h" using Microsoft::WRL::ComPtr; using webrtc::AudioDeviceName; namespace webrtc { namespace webrtc_win { namespace { #define ABORT_TEST_IF_NOT(requirements_satisfied) \ do { \ bool fail = false; \ if (ShouldAbortTest(requirements_satisfied, #requirements_satisfied, \ &fail)) { \ if (fail) \ FAIL(); \ else \ return; \ } \ } while (false) bool ShouldAbortTest(bool requirements_satisfied, const char* requirements_expression, bool* should_fail) { if (!requirements_satisfied) { RTC_LOG(LS_ERROR) << "Requirement(s) not satisfied (" << requirements_expression << ")"; // TODO(henrika): improve hard-coded condition to determine if test should // fail or be ignored. Could use e.g. a command-line argument here to // determine if the test should fail or not. *should_fail = false; return true; } *should_fail = false; return false; } } // namespace // CoreAudioUtilityWinTest test fixture. class CoreAudioUtilityWinTest : public ::testing::Test { protected: CoreAudioUtilityWinTest() : com_init_(ScopedCOMInitializer::kMTA) { // We must initialize the COM library on a thread before we calling any of // the library functions. All COM functions will return CO_E_NOTINITIALIZED // otherwise. EXPECT_TRUE(com_init_.Succeeded()); // Configure logging. rtc::LogMessage::LogToDebug(rtc::LS_INFO); rtc::LogMessage::LogTimestamps(); rtc::LogMessage::LogThreads(); } virtual ~CoreAudioUtilityWinTest() {} bool DevicesAvailable() { return core_audio_utility::IsSupported() && core_audio_utility::NumberOfActiveDevices(eCapture) > 0 && core_audio_utility::NumberOfActiveDevices(eRender) > 0; } private: ScopedCOMInitializer com_init_; }; TEST_F(CoreAudioUtilityWinTest, WaveFormatWrapper) { // Use default constructor for WAVEFORMATEX and verify its size. WAVEFORMATEX format = {}; core_audio_utility::WaveFormatWrapper wave_format(&format); EXPECT_FALSE(wave_format.IsExtensible()); EXPECT_EQ(wave_format.size(), sizeof(WAVEFORMATEX)); EXPECT_EQ(wave_format->cbSize, 0); // Ensure that the stand-alone WAVEFORMATEX structure has a valid format tag // and that all accessors work. format.wFormatTag = WAVE_FORMAT_PCM; EXPECT_FALSE(wave_format.IsExtensible()); EXPECT_EQ(wave_format.size(), sizeof(WAVEFORMATEX)); EXPECT_EQ(wave_format.get()->wFormatTag, WAVE_FORMAT_PCM); EXPECT_EQ(wave_format->wFormatTag, WAVE_FORMAT_PCM); // Next, ensure that the size is valid. Stand-alone is not extended. EXPECT_EQ(wave_format.size(), sizeof(WAVEFORMATEX)); // Verify format types for the stand-alone version. EXPECT_TRUE(wave_format.IsPcm()); EXPECT_FALSE(wave_format.IsFloat()); format.wFormatTag = WAVE_FORMAT_IEEE_FLOAT; EXPECT_TRUE(wave_format.IsFloat()); } TEST_F(CoreAudioUtilityWinTest, WaveFormatWrapperExtended) { // Use default constructor for WAVEFORMATEXTENSIBLE and verify that it // results in same size as for WAVEFORMATEX even if the size of `format_ex` // equals the size of WAVEFORMATEXTENSIBLE. WAVEFORMATEXTENSIBLE format_ex = {}; core_audio_utility::WaveFormatWrapper wave_format_ex(&format_ex); EXPECT_FALSE(wave_format_ex.IsExtensible()); EXPECT_EQ(wave_format_ex.size(), sizeof(WAVEFORMATEX)); EXPECT_EQ(wave_format_ex->cbSize, 0); // Ensure that the extended structure has a valid format tag and that all // accessors work. format_ex.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE; EXPECT_FALSE(wave_format_ex.IsExtensible()); EXPECT_EQ(wave_format_ex.size(), sizeof(WAVEFORMATEX)); EXPECT_EQ(wave_format_ex->wFormatTag, WAVE_FORMAT_EXTENSIBLE); EXPECT_EQ(wave_format_ex.get()->wFormatTag, WAVE_FORMAT_EXTENSIBLE); // Next, ensure that the size is valid (sum of stand-alone and extended). // Now the structure qualifies as extended. format_ex.Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX); EXPECT_TRUE(wave_format_ex.IsExtensible()); EXPECT_EQ(wave_format_ex.size(), sizeof(WAVEFORMATEXTENSIBLE)); EXPECT_TRUE(wave_format_ex.GetExtensible()); EXPECT_EQ(wave_format_ex.GetExtensible()->Format.wFormatTag, WAVE_FORMAT_EXTENSIBLE); // Verify format types for the extended version. EXPECT_FALSE(wave_format_ex.IsPcm()); format_ex.SubFormat = KSDATAFORMAT_SUBTYPE_PCM; EXPECT_TRUE(wave_format_ex.IsPcm()); EXPECT_FALSE(wave_format_ex.IsFloat()); format_ex.SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT; EXPECT_TRUE(wave_format_ex.IsFloat()); } TEST_F(CoreAudioUtilityWinTest, NumberOfActiveDevices) { ABORT_TEST_IF_NOT(DevicesAvailable()); int render_devices = core_audio_utility::NumberOfActiveDevices(eRender); EXPECT_GT(render_devices, 0); int capture_devices = core_audio_utility::NumberOfActiveDevices(eCapture); EXPECT_GT(capture_devices, 0); int total_devices = core_audio_utility::NumberOfActiveDevices(eAll); EXPECT_EQ(total_devices, render_devices + capture_devices); } TEST_F(CoreAudioUtilityWinTest, GetAudioClientVersion) { uint32_t client_version = core_audio_utility::GetAudioClientVersion(); EXPECT_GE(client_version, 1u); EXPECT_LE(client_version, 3u); } TEST_F(CoreAudioUtilityWinTest, CreateDeviceEnumerator) { ABORT_TEST_IF_NOT(DevicesAvailable()); ComPtr enumerator = core_audio_utility::CreateDeviceEnumerator(); EXPECT_TRUE(enumerator.Get()); } TEST_F(CoreAudioUtilityWinTest, GetDefaultInputDeviceID) { ABORT_TEST_IF_NOT(DevicesAvailable()); std::string default_device_id = core_audio_utility::GetDefaultInputDeviceID(); EXPECT_FALSE(default_device_id.empty()); } TEST_F(CoreAudioUtilityWinTest, GetDefaultOutputDeviceID) { ABORT_TEST_IF_NOT(DevicesAvailable()); std::string default_device_id = core_audio_utility::GetDefaultOutputDeviceID(); EXPECT_FALSE(default_device_id.empty()); } TEST_F(CoreAudioUtilityWinTest, GetCommunicationsInputDeviceID) { ABORT_TEST_IF_NOT(DevicesAvailable()); std::string default_device_id = core_audio_utility::GetCommunicationsInputDeviceID(); EXPECT_FALSE(default_device_id.empty()); } TEST_F(CoreAudioUtilityWinTest, GetCommunicationsOutputDeviceID) { ABORT_TEST_IF_NOT(DevicesAvailable()); std::string default_device_id = core_audio_utility::GetCommunicationsOutputDeviceID(); EXPECT_FALSE(default_device_id.empty()); } TEST_F(CoreAudioUtilityWinTest, CreateDefaultDevice) { ABORT_TEST_IF_NOT(DevicesAvailable()); 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. ComPtr audio_device; for (size_t i = 0; i < arraysize(data); ++i) { audio_device = core_audio_utility::CreateDevice( AudioDeviceName::kDefaultDeviceId, data[i].flow, data[i].role); EXPECT_TRUE(audio_device.Get()); EXPECT_EQ(data[i].flow, core_audio_utility::GetDataFlow(audio_device.Get())); } // Only eRender and eCapture are allowed as flow parameter. audio_device = core_audio_utility::CreateDevice( AudioDeviceName::kDefaultDeviceId, eAll, eConsole); EXPECT_FALSE(audio_device.Get()); } TEST_F(CoreAudioUtilityWinTest, CreateDevice) { ABORT_TEST_IF_NOT(DevicesAvailable()); // Get name and ID of default device used for playback. ComPtr default_render_device = core_audio_utility::CreateDevice( AudioDeviceName::kDefaultDeviceId, eRender, eConsole); AudioDeviceName default_render_name = core_audio_utility::GetDeviceName(default_render_device.Get()); EXPECT_TRUE(default_render_name.IsValid()); // Use the unique ID as input to CreateDevice() and create a corresponding // IMMDevice. The data-flow direction and role parameters are ignored for // this scenario. ComPtr audio_device = core_audio_utility::CreateDevice( default_render_name.unique_id, EDataFlow(), ERole()); EXPECT_TRUE(audio_device.Get()); // Verify that the two IMMDevice interfaces represents the same endpoint // by comparing their unique IDs. AudioDeviceName device_name = core_audio_utility::GetDeviceName(audio_device.Get()); EXPECT_EQ(default_render_name.unique_id, device_name.unique_id); } TEST_F(CoreAudioUtilityWinTest, GetDefaultDeviceName) { ABORT_TEST_IF_NOT(DevicesAvailable()); 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. ComPtr audio_device; AudioDeviceName device_name; for (size_t i = 0; i < arraysize(data); ++i) { audio_device = core_audio_utility::CreateDevice( AudioDeviceName::kDefaultDeviceId, data[i].flow, data[i].role); device_name = core_audio_utility::GetDeviceName(audio_device.Get()); EXPECT_TRUE(device_name.IsValid()); } } TEST_F(CoreAudioUtilityWinTest, GetFriendlyName) { ABORT_TEST_IF_NOT(DevicesAvailable()); // Get name and ID of default device used for recording. ComPtr audio_device = core_audio_utility::CreateDevice( AudioDeviceName::kDefaultDeviceId, eCapture, eConsole); AudioDeviceName device_name = core_audio_utility::GetDeviceName(audio_device.Get()); EXPECT_TRUE(device_name.IsValid()); // 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 = core_audio_utility::GetFriendlyName( device_name.unique_id, eCapture, eConsole); EXPECT_EQ(friendly_name, device_name.device_name); // Same test as above but for playback. audio_device = core_audio_utility::CreateDevice( AudioDeviceName::kDefaultDeviceId, eRender, eConsole); device_name = core_audio_utility::GetDeviceName(audio_device.Get()); friendly_name = core_audio_utility::GetFriendlyName(device_name.unique_id, eRender, eConsole); EXPECT_EQ(friendly_name, device_name.device_name); } TEST_F(CoreAudioUtilityWinTest, GetInputDeviceNames) { ABORT_TEST_IF_NOT(DevicesAvailable()); webrtc::AudioDeviceNames device_names; EXPECT_TRUE(core_audio_utility::GetInputDeviceNames(&device_names)); // Number of elements in the list should be two more than the number of // active devices since we always add default and default communication // devices on index 0 and 1. EXPECT_EQ(static_cast(device_names.size()), 2 + core_audio_utility::NumberOfActiveDevices(eCapture)); } TEST_F(CoreAudioUtilityWinTest, GetOutputDeviceNames) { ABORT_TEST_IF_NOT(DevicesAvailable()); webrtc::AudioDeviceNames device_names; EXPECT_TRUE(core_audio_utility::GetOutputDeviceNames(&device_names)); // Number of elements in the list should be two more than the number of // active devices since we always add default and default communication // devices on index 0 and 1. EXPECT_EQ(static_cast(device_names.size()), 2 + core_audio_utility::NumberOfActiveDevices(eRender)); } TEST_F(CoreAudioUtilityWinTest, CreateSessionManager2) { ABORT_TEST_IF_NOT(DevicesAvailable() && rtc::rtc_win::GetVersion() >= rtc::rtc_win::VERSION_WIN7); EDataFlow data_flow[] = {eRender, eCapture}; // Obtain reference to an IAudioSessionManager2 interface for a default audio // endpoint device specified by two different data flows and the `eConsole` // role. for (size_t i = 0; i < arraysize(data_flow); ++i) { ComPtr device(core_audio_utility::CreateDevice( AudioDeviceName::kDefaultDeviceId, data_flow[i], eConsole)); EXPECT_TRUE(device.Get()); ComPtr session_manager = core_audio_utility::CreateSessionManager2(device.Get()); EXPECT_TRUE(session_manager.Get()); } } TEST_F(CoreAudioUtilityWinTest, CreateSessionEnumerator) { ABORT_TEST_IF_NOT(DevicesAvailable() && rtc::rtc_win::GetVersion() >= rtc::rtc_win::VERSION_WIN7); EDataFlow data_flow[] = {eRender, eCapture}; // Obtain reference to an IAudioSessionEnumerator interface for a default // audio endpoint device specified by two different data flows and the // `eConsole` role. for (size_t i = 0; i < arraysize(data_flow); ++i) { ComPtr device(core_audio_utility::CreateDevice( AudioDeviceName::kDefaultDeviceId, data_flow[i], eConsole)); EXPECT_TRUE(device.Get()); ComPtr session_enumerator = core_audio_utility::CreateSessionEnumerator(device.Get()); EXPECT_TRUE(session_enumerator.Get()); // Perform a sanity test of the interface by asking for the total number // of audio sessions that are open on the audio device. Note that, we do // not check if the session is active or not. int session_count = 0; EXPECT_TRUE(SUCCEEDED(session_enumerator->GetCount(&session_count))); EXPECT_GE(session_count, 0); } } TEST_F(CoreAudioUtilityWinTest, NumberOfActiveSessions) { ABORT_TEST_IF_NOT(DevicesAvailable() && rtc::rtc_win::GetVersion() >= rtc::rtc_win::VERSION_WIN7); EDataFlow data_flow[] = {eRender, eCapture}; // Count number of active audio session for a default audio endpoint device // specified by two different data flows and the `eConsole` role. // Ensure that the number of active audio sessions is less than or equal to // the total number of audio sessions on that same device. for (size_t i = 0; i < arraysize(data_flow); ++i) { // Create an audio endpoint device. ComPtr device(core_audio_utility::CreateDevice( AudioDeviceName::kDefaultDeviceId, data_flow[i], eConsole)); EXPECT_TRUE(device.Get()); // Ask for total number of audio sessions on the created device. ComPtr session_enumerator = core_audio_utility::CreateSessionEnumerator(device.Get()); EXPECT_TRUE(session_enumerator.Get()); int total_session_count = 0; EXPECT_TRUE(SUCCEEDED(session_enumerator->GetCount(&total_session_count))); EXPECT_GE(total_session_count, 0); // Use NumberOfActiveSessions and get number of active audio sessions. int active_session_count = core_audio_utility::NumberOfActiveSessions(device.Get()); EXPECT_LE(active_session_count, total_session_count); } } TEST_F(CoreAudioUtilityWinTest, CreateClient) { ABORT_TEST_IF_NOT(DevicesAvailable()); EDataFlow data_flow[] = {eRender, eCapture}; // Obtain reference to an IAudioClient interface for a default audio endpoint // device specified by two different data flows and the `eConsole` role. for (size_t i = 0; i < arraysize(data_flow); ++i) { ComPtr client = core_audio_utility::CreateClient( AudioDeviceName::kDefaultDeviceId, data_flow[i], eConsole); EXPECT_TRUE(client.Get()); } } TEST_F(CoreAudioUtilityWinTest, CreateClient2) { ABORT_TEST_IF_NOT(DevicesAvailable() && core_audio_utility::GetAudioClientVersion() >= 2); EDataFlow data_flow[] = {eRender, eCapture}; // Obtain reference to an IAudioClient2 interface for a default audio endpoint // device specified by two different data flows and the `eConsole` role. for (size_t i = 0; i < arraysize(data_flow); ++i) { ComPtr client2 = core_audio_utility::CreateClient2( AudioDeviceName::kDefaultDeviceId, data_flow[i], eConsole); EXPECT_TRUE(client2.Get()); } } TEST_F(CoreAudioUtilityWinTest, CreateClient3) { ABORT_TEST_IF_NOT(DevicesAvailable() && core_audio_utility::GetAudioClientVersion() >= 3); EDataFlow data_flow[] = {eRender, eCapture}; // Obtain reference to an IAudioClient3 interface for a default audio endpoint // device specified by two different data flows and the `eConsole` role. for (size_t i = 0; i < arraysize(data_flow); ++i) { ComPtr client3 = core_audio_utility::CreateClient3( AudioDeviceName::kDefaultDeviceId, data_flow[i], eConsole); EXPECT_TRUE(client3.Get()); } } TEST_F(CoreAudioUtilityWinTest, SetClientProperties) { ABORT_TEST_IF_NOT(DevicesAvailable() && core_audio_utility::GetAudioClientVersion() >= 2); ComPtr client2 = core_audio_utility::CreateClient2( AudioDeviceName::kDefaultDeviceId, eRender, eConsole); EXPECT_TRUE(client2.Get()); EXPECT_TRUE( SUCCEEDED(core_audio_utility::SetClientProperties(client2.Get()))); ComPtr client3 = core_audio_utility::CreateClient3( AudioDeviceName::kDefaultDeviceId, eRender, eConsole); EXPECT_TRUE(client3.Get()); EXPECT_TRUE( SUCCEEDED(core_audio_utility::SetClientProperties(client3.Get()))); } TEST_F(CoreAudioUtilityWinTest, GetSharedModeEnginePeriod) { ABORT_TEST_IF_NOT(DevicesAvailable() && core_audio_utility::GetAudioClientVersion() >= 3); ComPtr client3 = core_audio_utility::CreateClient3( AudioDeviceName::kDefaultDeviceId, eRender, eConsole); EXPECT_TRUE(client3.Get()); WAVEFORMATPCMEX format; EXPECT_TRUE(SUCCEEDED( core_audio_utility::GetSharedModeMixFormat(client3.Get(), &format))); uint32_t default_period = 0; uint32_t fundamental_period = 0; uint32_t min_period = 0; uint32_t max_period = 0; EXPECT_TRUE(SUCCEEDED(core_audio_utility::GetSharedModeEnginePeriod( client3.Get(), &format, &default_period, &fundamental_period, &min_period, &max_period))); } // TODO(henrika): figure out why usage of this API always reports // AUDCLNT_E_OFFLOAD_MODE_ONLY. TEST_F(CoreAudioUtilityWinTest, DISABLED_GetBufferSizeLimits) { ABORT_TEST_IF_NOT(DevicesAvailable() && core_audio_utility::GetAudioClientVersion() >= 2); ComPtr client2 = core_audio_utility::CreateClient2( AudioDeviceName::kDefaultDeviceId, eRender, eConsole); EXPECT_TRUE(client2.Get()); WAVEFORMATPCMEX format; EXPECT_TRUE(SUCCEEDED( core_audio_utility::GetSharedModeMixFormat(client2.Get(), &format))); REFERENCE_TIME min_buffer_duration = 0; REFERENCE_TIME max_buffer_duration = 0; EXPECT_TRUE(SUCCEEDED(core_audio_utility::GetBufferSizeLimits( client2.Get(), &format, &min_buffer_duration, &max_buffer_duration))); } TEST_F(CoreAudioUtilityWinTest, GetSharedModeMixFormat) { ABORT_TEST_IF_NOT(DevicesAvailable()); ComPtr client = core_audio_utility::CreateClient( AudioDeviceName::kDefaultDeviceId, eRender, eConsole); EXPECT_TRUE(client.Get()); // Perform a simple sanity test of the acquired format structure. WAVEFORMATEXTENSIBLE format; EXPECT_TRUE(SUCCEEDED( core_audio_utility::GetSharedModeMixFormat(client.Get(), &format))); core_audio_utility::WaveFormatWrapper wformat(&format); EXPECT_GE(wformat->nChannels, 1); EXPECT_GE(wformat->nSamplesPerSec, 8000u); EXPECT_GE(wformat->wBitsPerSample, 16); if (wformat.IsExtensible()) { EXPECT_EQ(wformat->wFormatTag, WAVE_FORMAT_EXTENSIBLE); EXPECT_GE(wformat->cbSize, 22); EXPECT_GE(wformat.GetExtensible()->Samples.wValidBitsPerSample, 16); } else { EXPECT_EQ(wformat->cbSize, 0); } } TEST_F(CoreAudioUtilityWinTest, IsFormatSupported) { ABORT_TEST_IF_NOT(DevicesAvailable()); // Create a default render client. ComPtr client = core_audio_utility::CreateClient( AudioDeviceName::kDefaultDeviceId, eRender, eConsole); EXPECT_TRUE(client.Get()); // Get the default, shared mode, mixing format. WAVEFORMATEXTENSIBLE format; EXPECT_TRUE(SUCCEEDED( core_audio_utility::GetSharedModeMixFormat(client.Get(), &format))); // In shared mode, the audio engine always supports the mix format. EXPECT_TRUE(core_audio_utility::IsFormatSupported( client.Get(), AUDCLNT_SHAREMODE_SHARED, &format)); // Use an invalid format and verify that it is not supported. format.Format.nSamplesPerSec += 1; EXPECT_FALSE(core_audio_utility::IsFormatSupported( client.Get(), AUDCLNT_SHAREMODE_SHARED, &format)); } TEST_F(CoreAudioUtilityWinTest, GetDevicePeriod) { ABORT_TEST_IF_NOT(DevicesAvailable()); EDataFlow data_flow[] = {eRender, eCapture}; // Verify that the device periods are valid for the default render and // capture devices. ComPtr client; for (size_t i = 0; i < arraysize(data_flow); ++i) { REFERENCE_TIME shared_time_period = 0; REFERENCE_TIME exclusive_time_period = 0; client = core_audio_utility::CreateClient(AudioDeviceName::kDefaultDeviceId, data_flow[i], eConsole); EXPECT_TRUE(client.Get()); EXPECT_TRUE(SUCCEEDED(core_audio_utility::GetDevicePeriod( client.Get(), AUDCLNT_SHAREMODE_SHARED, &shared_time_period))); EXPECT_GT(shared_time_period, 0); EXPECT_TRUE(SUCCEEDED(core_audio_utility::GetDevicePeriod( client.Get(), AUDCLNT_SHAREMODE_EXCLUSIVE, &exclusive_time_period))); EXPECT_GT(exclusive_time_period, 0); EXPECT_LE(exclusive_time_period, shared_time_period); } } TEST_F(CoreAudioUtilityWinTest, GetPreferredAudioParameters) { ABORT_TEST_IF_NOT(DevicesAvailable()); struct { EDataFlow flow; ERole role; } data[] = {{eRender, eConsole}, {eRender, eCommunications}, {eCapture, eConsole}, {eCapture, eCommunications}}; // Verify that the preferred audio parameters are OK for all flow/role // combinations above. ComPtr client; webrtc::AudioParameters params; for (size_t i = 0; i < arraysize(data); ++i) { client = core_audio_utility::CreateClient(AudioDeviceName::kDefaultDeviceId, data[i].flow, data[i].role); EXPECT_TRUE(client.Get()); EXPECT_TRUE(SUCCEEDED(core_audio_utility::GetPreferredAudioParameters( client.Get(), ¶ms))); EXPECT_TRUE(params.is_valid()); EXPECT_TRUE(params.is_complete()); } } TEST_F(CoreAudioUtilityWinTest, SharedModeInitialize) { ABORT_TEST_IF_NOT(DevicesAvailable()); ComPtr client; client = core_audio_utility::CreateClient(AudioDeviceName::kDefaultDeviceId, eRender, eConsole); EXPECT_TRUE(client.Get()); WAVEFORMATPCMEX format; EXPECT_TRUE(SUCCEEDED( core_audio_utility::GetSharedModeMixFormat(client.Get(), &format))); // Perform a shared-mode initialization without event-driven buffer handling. uint32_t endpoint_buffer_size = 0; HRESULT hr = core_audio_utility::SharedModeInitialize( client.Get(), &format, nullptr, 0, false, &endpoint_buffer_size); EXPECT_TRUE(SUCCEEDED(hr)); EXPECT_GT(endpoint_buffer_size, 0u); // It is only possible to create a client once. hr = core_audio_utility::SharedModeInitialize( client.Get(), &format, nullptr, 0, false, &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 // and then creating a new client. client = core_audio_utility::CreateClient(AudioDeviceName::kDefaultDeviceId, eRender, eConsole); EXPECT_TRUE(client.Get()); hr = core_audio_utility::SharedModeInitialize( client.Get(), &format, nullptr, 0, false, &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 rate. client = core_audio_utility::CreateClient(AudioDeviceName::kDefaultDeviceId, eRender, eConsole); EXPECT_TRUE(client.Get()); format.Format.nSamplesPerSec = format.Format.nSamplesPerSec + 1; EXPECT_FALSE(core_audio_utility::IsFormatSupported( client.Get(), AUDCLNT_SHAREMODE_SHARED, &format)); hr = core_audio_utility::SharedModeInitialize( client.Get(), &format, nullptr, 0, false, &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 non-signaled state. ScopedHandle event_handle(::CreateEvent(nullptr, TRUE, FALSE, nullptr)); client = core_audio_utility::CreateClient(AudioDeviceName::kDefaultDeviceId, eRender, eConsole); EXPECT_TRUE(client.Get()); EXPECT_TRUE(SUCCEEDED( core_audio_utility::GetSharedModeMixFormat(client.Get(), &format))); EXPECT_TRUE(core_audio_utility::IsFormatSupported( client.Get(), AUDCLNT_SHAREMODE_SHARED, &format)); hr = core_audio_utility::SharedModeInitialize( client.Get(), &format, event_handle, 0, false, &endpoint_buffer_size); EXPECT_TRUE(SUCCEEDED(hr)); EXPECT_GT(endpoint_buffer_size, 0u); // TODO(henrika): possibly add test for signature which overrides the default // sample rate. } TEST_F(CoreAudioUtilityWinTest, CreateRenderAndCaptureClients) { ABORT_TEST_IF_NOT(DevicesAvailable()); EDataFlow data_flow[] = {eRender, eCapture}; WAVEFORMATPCMEX format; uint32_t endpoint_buffer_size = 0; for (size_t i = 0; i < arraysize(data_flow); ++i) { ComPtr client; ComPtr render_client; ComPtr capture_client; // Create a default client for the given data-flow direction. client = core_audio_utility::CreateClient(AudioDeviceName::kDefaultDeviceId, data_flow[i], eConsole); EXPECT_TRUE(client.Get()); EXPECT_TRUE(SUCCEEDED( core_audio_utility::GetSharedModeMixFormat(client.Get(), &format))); if (data_flow[i] == eRender) { // It is not possible to create a render client using an unitialized // client interface. render_client = core_audio_utility::CreateRenderClient(client.Get()); EXPECT_FALSE(render_client.Get()); // Do a proper initialization and verify that it works this time. core_audio_utility::SharedModeInitialize(client.Get(), &format, nullptr, 0, false, &endpoint_buffer_size); render_client = core_audio_utility::CreateRenderClient(client.Get()); EXPECT_TRUE(render_client.Get()); EXPECT_GT(endpoint_buffer_size, 0u); } else if (data_flow[i] == eCapture) { // It is not possible to create a capture client using an unitialized // client interface. capture_client = core_audio_utility::CreateCaptureClient(client.Get()); EXPECT_FALSE(capture_client.Get()); // Do a proper initialization and verify that it works this time. core_audio_utility::SharedModeInitialize(client.Get(), &format, nullptr, 0, false, &endpoint_buffer_size); capture_client = core_audio_utility::CreateCaptureClient(client.Get()); EXPECT_TRUE(capture_client.Get()); EXPECT_GT(endpoint_buffer_size, 0u); } } } TEST_F(CoreAudioUtilityWinTest, CreateAudioClock) { ABORT_TEST_IF_NOT(DevicesAvailable()); EDataFlow data_flow[] = {eRender, eCapture}; WAVEFORMATPCMEX format; uint32_t endpoint_buffer_size = 0; for (size_t i = 0; i < arraysize(data_flow); ++i) { ComPtr client; ComPtr audio_clock; // Create a default client for the given data-flow direction. client = core_audio_utility::CreateClient(AudioDeviceName::kDefaultDeviceId, data_flow[i], eConsole); EXPECT_TRUE(client.Get()); EXPECT_TRUE(SUCCEEDED( core_audio_utility::GetSharedModeMixFormat(client.Get(), &format))); // It is not possible to create an audio clock using an unitialized client // interface. audio_clock = core_audio_utility::CreateAudioClock(client.Get()); EXPECT_FALSE(audio_clock.Get()); // Do a proper initialization and verify that it works this time. core_audio_utility::SharedModeInitialize(client.Get(), &format, nullptr, 0, false, &endpoint_buffer_size); audio_clock = core_audio_utility::CreateAudioClock(client.Get()); EXPECT_TRUE(audio_clock.Get()); EXPECT_GT(endpoint_buffer_size, 0u); // Use the audio clock and verify that querying the device frequency works. UINT64 frequency = 0; EXPECT_TRUE(SUCCEEDED(audio_clock->GetFrequency(&frequency))); EXPECT_GT(frequency, 0u); } } TEST_F(CoreAudioUtilityWinTest, CreateAudioSessionControl) { ABORT_TEST_IF_NOT(DevicesAvailable()); EDataFlow data_flow[] = {eRender, eCapture}; WAVEFORMATPCMEX format; uint32_t endpoint_buffer_size = 0; for (size_t i = 0; i < arraysize(data_flow); ++i) { ComPtr client; ComPtr audio_session_control; // Create a default client for the given data-flow direction. client = core_audio_utility::CreateClient(AudioDeviceName::kDefaultDeviceId, data_flow[i], eConsole); EXPECT_TRUE(client.Get()); EXPECT_TRUE(SUCCEEDED( core_audio_utility::GetSharedModeMixFormat(client.Get(), &format))); // It is not possible to create an audio session control using an // unitialized client interface. audio_session_control = core_audio_utility::CreateAudioSessionControl(client.Get()); EXPECT_FALSE(audio_session_control.Get()); // Do a proper initialization and verify that it works this time. core_audio_utility::SharedModeInitialize(client.Get(), &format, nullptr, 0, false, &endpoint_buffer_size); audio_session_control = core_audio_utility::CreateAudioSessionControl(client.Get()); EXPECT_TRUE(audio_session_control.Get()); EXPECT_GT(endpoint_buffer_size, 0u); // Use the audio session control and verify that the session state can be // queried. When a client opens a session by assigning the first stream to // the session (by calling the IAudioClient::Initialize method), the initial // session state is inactive. The session state changes from inactive to // active when a stream in the session begins running (because the client // has called the IAudioClient::Start method). AudioSessionState state; EXPECT_TRUE(SUCCEEDED(audio_session_control->GetState(&state))); EXPECT_EQ(state, AudioSessionStateInactive); } } TEST_F(CoreAudioUtilityWinTest, CreateSimpleAudioVolume) { ABORT_TEST_IF_NOT(DevicesAvailable()); EDataFlow data_flow[] = {eRender, eCapture}; WAVEFORMATPCMEX format; uint32_t endpoint_buffer_size = 0; for (size_t i = 0; i < arraysize(data_flow); ++i) { ComPtr client; ComPtr simple_audio_volume; // Create a default client for the given data-flow direction. client = core_audio_utility::CreateClient(AudioDeviceName::kDefaultDeviceId, data_flow[i], eConsole); EXPECT_TRUE(client.Get()); EXPECT_TRUE(SUCCEEDED( core_audio_utility::GetSharedModeMixFormat(client.Get(), &format))); // It is not possible to create an audio volume using an uninitialized // client interface. simple_audio_volume = core_audio_utility::CreateSimpleAudioVolume(client.Get()); EXPECT_FALSE(simple_audio_volume.Get()); // Do a proper initialization and verify that it works this time. core_audio_utility::SharedModeInitialize(client.Get(), &format, nullptr, 0, false, &endpoint_buffer_size); simple_audio_volume = core_audio_utility::CreateSimpleAudioVolume(client.Get()); EXPECT_TRUE(simple_audio_volume.Get()); EXPECT_GT(endpoint_buffer_size, 0u); // Use the audio volume interface and validate that it works. The volume // level should be value in the range 0.0 to 1.0 at first call. float volume = 0.0; EXPECT_TRUE(SUCCEEDED(simple_audio_volume->GetMasterVolume(&volume))); EXPECT_GE(volume, 0.0); EXPECT_LE(volume, 1.0); // Next, set a new volume and verify that the setter does its job. const float target_volume = 0.5; EXPECT_TRUE(SUCCEEDED( simple_audio_volume->SetMasterVolume(target_volume, nullptr))); EXPECT_TRUE(SUCCEEDED(simple_audio_volume->GetMasterVolume(&volume))); EXPECT_EQ(volume, target_volume); } } TEST_F(CoreAudioUtilityWinTest, FillRenderEndpointBufferWithSilence) { ABORT_TEST_IF_NOT(DevicesAvailable()); // Create default clients using the default mixing format for shared mode. ComPtr client(core_audio_utility::CreateClient( AudioDeviceName::kDefaultDeviceId, eRender, eConsole)); EXPECT_TRUE(client.Get()); WAVEFORMATPCMEX format; uint32_t endpoint_buffer_size = 0; EXPECT_TRUE(SUCCEEDED( core_audio_utility::GetSharedModeMixFormat(client.Get(), &format))); core_audio_utility::SharedModeInitialize(client.Get(), &format, nullptr, 0, false, &endpoint_buffer_size); EXPECT_GT(endpoint_buffer_size, 0u); ComPtr render_client( core_audio_utility::CreateRenderClient(client.Get())); EXPECT_TRUE(render_client.Get()); // 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(core_audio_utility::FillRenderEndpointBufferWithSilence( client.Get(), render_client.Get())); client->GetCurrentPadding(&num_queued_frames); EXPECT_EQ(num_queued_frames, endpoint_buffer_size); } } // namespace webrtc_win } // namespace webrtc