chromium/media/audio/win/core_audio_util_win_unittest.cc

// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifdef UNSAFE_BUFFERS_BUILD
// TODO(crbug.com/40285824): Remove this and convert code to safer constructs.
#pragma allow_unsafe_buffers
#endif

#include "media/audio/win/core_audio_util_win.h"

#include <stddef.h>
#include <stdint.h>

#include "base/strings/utf_string_conversions.h"
#include "base/synchronization/waitable_event.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/win/scoped_co_mem.h"
#include "base/win/scoped_com_initializer.h"
#include "base/win/scoped_handle.h"
#include "media/audio/audio_device_description.h"
#include "media/audio/audio_unittest_util.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

using Microsoft::WRL::ComPtr;
using base::win::ScopedCOMInitializer;

namespace media {
namespace {
constexpr bool kOffloadModeEnabled = true;
}

class CoreAudioUtilWinTest : public ::testing::Test {
 protected:
  // The tests must run on a COM thread.
  // If we don't initialize the COM library on a thread before using COM,
  // all function calls will return CO_E_NOTINITIALIZED.
  CoreAudioUtilWinTest() {
    DCHECK(com_init_.Succeeded());
  }
  ~CoreAudioUtilWinTest() override {}

  bool DevicesAvailable() {
    return CoreAudioUtil::IsSupported() &&
           CoreAudioUtil::NumberOfActiveDevices(eCapture) > 0 &&
           CoreAudioUtil::NumberOfActiveDevices(eRender) > 0;
  }

  ScopedCOMInitializer com_init_;
};

TEST_F(CoreAudioUtilWinTest, WaveFormatWrapper) {
  // Use default constructor for WAVEFORMATEX and verify its size.
  WAVEFORMATEX format = {};
  CoreAudioUtil::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(CoreAudioUtilWinTest, 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 = {};
  CoreAudioUtil::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(CoreAudioUtilWinTest, NumberOfActiveDevices) {
  ABORT_AUDIO_TEST_IF_NOT(DevicesAvailable());

  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) {
  ABORT_AUDIO_TEST_IF_NOT(DevicesAvailable());

  ComPtr<IMMDeviceEnumerator> enumerator =
      CoreAudioUtil::CreateDeviceEnumerator();
  EXPECT_TRUE(enumerator.Get());
}

TEST_F(CoreAudioUtilWinTest, GetDefaultDeviceIDs) {
  ABORT_AUDIO_TEST_IF_NOT(DevicesAvailable());
  std::string default_device_id = CoreAudioUtil::GetDefaultInputDeviceID();
  EXPECT_FALSE(default_device_id.empty());
  default_device_id = CoreAudioUtil::GetDefaultOutputDeviceID();
  EXPECT_FALSE(default_device_id.empty());
  default_device_id = CoreAudioUtil::GetCommunicationsInputDeviceID();
  EXPECT_FALSE(default_device_id.empty());
  default_device_id = CoreAudioUtil::GetCommunicationsOutputDeviceID();
  EXPECT_FALSE(default_device_id.empty());
}

TEST_F(CoreAudioUtilWinTest, CreateDefaultDevice) {
  ABORT_AUDIO_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<IMMDevice> audio_device;
  for (size_t i = 0; i < std::size(data); ++i) {
    audio_device = CoreAudioUtil::CreateDevice(
        AudioDeviceDescription::kDefaultDeviceId, data[i].flow, data[i].role);
    EXPECT_TRUE(audio_device.Get());
    EXPECT_EQ(data[i].flow, CoreAudioUtil::GetDataFlow(audio_device.Get()));
  }

  // Only eRender and eCapture are allowed as flow parameter.
  audio_device = CoreAudioUtil::CreateDevice(
      AudioDeviceDescription::kDefaultDeviceId, eAll, eConsole);
  EXPECT_FALSE(audio_device.Get());
}

TEST_F(CoreAudioUtilWinTest, CreateDevice) {
  ABORT_AUDIO_TEST_IF_NOT(DevicesAvailable());

  // Get name and ID of default device used for playback.
  ComPtr<IMMDevice> default_render_device = CoreAudioUtil::CreateDevice(
      AudioDeviceDescription::kDefaultDeviceId, eRender, eConsole);
  AudioDeviceName default_render_name;
  EXPECT_TRUE(SUCCEEDED(CoreAudioUtil::GetDeviceName(
      default_render_device.Get(), &default_render_name)));

  // Use the unique ID as input to CreateDevice() and create a corresponding
  // IMMDevice.
  ComPtr<IMMDevice> audio_device = CoreAudioUtil::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;
  EXPECT_TRUE(SUCCEEDED(
      CoreAudioUtil::GetDeviceName(audio_device.Get(), &device_name)));
  EXPECT_EQ(default_render_name.unique_id, device_name.unique_id);
}

TEST_F(CoreAudioUtilWinTest, GetDefaultDeviceName) {
  ABORT_AUDIO_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<IMMDevice> audio_device;
  AudioDeviceName device_name;
  for (size_t i = 0; i < std::size(data); ++i) {
    audio_device = CoreAudioUtil::CreateDevice(
        AudioDeviceDescription::kDefaultDeviceId, data[i].flow, data[i].role);
    EXPECT_TRUE(SUCCEEDED(
        CoreAudioUtil::GetDeviceName(audio_device.Get(), &device_name)));
    EXPECT_FALSE(device_name.device_name.empty());
    EXPECT_FALSE(device_name.unique_id.empty());
  }
}

TEST_F(CoreAudioUtilWinTest, GetAudioControllerID) {
  ABORT_AUDIO_TEST_IF_NOT(DevicesAvailable());

  ComPtr<IMMDeviceEnumerator> enumerator(
      CoreAudioUtil::CreateDeviceEnumerator());
  ASSERT_TRUE(enumerator.Get());

  // Enumerate all active input and output devices and fetch the ID of
  // the associated device.
  EDataFlow flows[] = { eRender , eCapture };
  for (size_t i = 0; i < std::size(flows); ++i) {
    ComPtr<IMMDeviceCollection> collection;
    ASSERT_TRUE(SUCCEEDED(enumerator->EnumAudioEndpoints(
        flows[i], DEVICE_STATE_ACTIVE, &collection)));
    UINT count = 0;
    collection->GetCount(&count);
    for (UINT j = 0; j < count; ++j) {
      ComPtr<IMMDevice> device;
      collection->Item(j, &device);
      std::string controller_id(
          CoreAudioUtil::GetAudioControllerID(device.Get(), enumerator.Get()));
      EXPECT_FALSE(controller_id.empty());
    }
  }
}

TEST_F(CoreAudioUtilWinTest, GetFriendlyName) {
  ABORT_AUDIO_TEST_IF_NOT(DevicesAvailable());

  // Get name and ID of default device used for recording.
  ComPtr<IMMDevice> audio_device = CoreAudioUtil::CreateDevice(
      AudioDeviceDescription::kDefaultDeviceId, eCapture, eConsole);
  AudioDeviceName device_name;
  HRESULT hr = CoreAudioUtil::GetDeviceName(audio_device.Get(), &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, eCapture, eConsole);
  EXPECT_EQ(friendly_name, device_name.device_name);

  // Same test as above but for playback.
  audio_device = CoreAudioUtil::CreateDevice(
      AudioDeviceDescription::kDefaultDeviceId, eRender, eConsole);
  hr = CoreAudioUtil::GetDeviceName(audio_device.Get(), &device_name);
  EXPECT_TRUE(SUCCEEDED(hr));
  friendly_name =
      CoreAudioUtil::GetFriendlyName(device_name.unique_id, eRender, eConsole);
  EXPECT_EQ(friendly_name, device_name.device_name);
}

TEST_F(CoreAudioUtilWinTest, CreateClient) {
  ABORT_AUDIO_TEST_IF_NOT(DevicesAvailable());

  EDataFlow data[] = {eRender, eCapture};

  for (size_t i = 0; i < std::size(data); ++i) {
    ComPtr<IAudioClient> client = CoreAudioUtil::CreateClient(
        AudioDeviceDescription::kDefaultDeviceId, data[i], eConsole);
    EXPECT_TRUE(client.Get());
  }
}

TEST_F(CoreAudioUtilWinTest, CreateClient3) {
  ABORT_AUDIO_TEST_IF_NOT(DevicesAvailable());

  EDataFlow data[] = {eRender, eCapture};

  for (size_t i = 0; i < std::size(data); ++i) {
    ComPtr<IAudioClient3> client3 = CoreAudioUtil::CreateClient3(
        AudioDeviceDescription::kDefaultDeviceId, data[i], eConsole);
    EXPECT_TRUE(client3.Get());
  }

  // Use ComPtr notation to achieve the same thing as above. ComPtr::As wraps
  // QueryInterface calls on existing COM objects. In this case we use an
  // existing IAudioClient to obtain the IAudioClient3 interface.
  for (size_t i = 0; i < std::size(data); ++i) {
    ComPtr<IAudioClient> client = CoreAudioUtil::CreateClient(
        AudioDeviceDescription::kDefaultDeviceId, data[i], eConsole);
    EXPECT_TRUE(client.Get());
    ComPtr<IAudioClient3> client3;
    EXPECT_TRUE(SUCCEEDED(client.As(&client3)));
    EXPECT_TRUE(client3.Get());
  }
}

TEST_F(CoreAudioUtilWinTest, GetSharedModeMixFormat) {
  ABORT_AUDIO_TEST_IF_NOT(DevicesAvailable());

  ComPtr<IAudioClient> client = CoreAudioUtil::CreateClient(
      AudioDeviceDescription::kDefaultDeviceId, eRender, eConsole);
  EXPECT_TRUE(client.Get());

  // Perform a simple sanity test of the acquired format structure.
  WAVEFORMATEXTENSIBLE format;
  EXPECT_TRUE(
      SUCCEEDED(CoreAudioUtil::GetSharedModeMixFormat(client.Get(), &format)));
  CoreAudioUtil::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(CoreAudioUtilWinTest, IsChannelLayoutSupported) {
  ABORT_AUDIO_TEST_IF_NOT(DevicesAvailable());

  // 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(
      AudioDeviceDescription::kDefaultDeviceId, true, &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 opened 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) {
  ABORT_AUDIO_TEST_IF_NOT(DevicesAvailable());

  EDataFlow data[] = {eRender, eCapture};

  // Verify that the device periods are valid for the default render and
  // capture devices.
  for (size_t i = 0; i < std::size(data); ++i) {
    ComPtr<IAudioClient> client;
    REFERENCE_TIME shared_time_period = 0;
    REFERENCE_TIME exclusive_time_period = 0;
    client = CoreAudioUtil::CreateClient(
        AudioDeviceDescription::kDefaultDeviceId, data[i], eConsole);
    EXPECT_TRUE(client.Get());
    EXPECT_TRUE(SUCCEEDED(CoreAudioUtil::GetDevicePeriod(
        client.Get(), AUDCLNT_SHAREMODE_SHARED, &shared_time_period)));
    EXPECT_GT(shared_time_period, 0);
    EXPECT_TRUE(SUCCEEDED(CoreAudioUtil::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(CoreAudioUtilWinTest, GetPreferredAudioParameters) {
  ABORT_AUDIO_TEST_IF_NOT(DevicesAvailable());

  EDataFlow data[] = {eRender, eCapture};

  // Verify that the preferred audio parameters are OK for the default render
  // and capture devices.
  for (size_t i = 0; i < std::size(data); ++i) {
    AudioParameters params;
    const bool is_output_device = (data[i] == eRender);
    EXPECT_TRUE(SUCCEEDED(CoreAudioUtil::GetPreferredAudioParameters(
        AudioDeviceDescription::kDefaultDeviceId, is_output_device, &params)));
    EXPECT_TRUE(params.IsValid());
    if (!is_output_device) {
      // Loopack devices are supported for input streams.
      EXPECT_TRUE(SUCCEEDED(CoreAudioUtil::GetPreferredAudioParameters(
          AudioDeviceDescription::kLoopbackInputDeviceId, is_output_device,
          &params)));
      EXPECT_TRUE(params.IsValid());
    }
  }
}

TEST_F(CoreAudioUtilWinTest, GetChannelConfig) {
  ABORT_AUDIO_TEST_IF_NOT(DevicesAvailable());

  EDataFlow data_flows[] = {eRender, eCapture};

  for (auto data_flow : data_flows) {
    ChannelConfig config1 =
        CoreAudioUtil::GetChannelConfig(std::string(), data_flow);
    EXPECT_NE(config1, CHANNEL_LAYOUT_NONE);
    EXPECT_NE(config1, CHANNEL_LAYOUT_UNSUPPORTED);
    ChannelConfig config2 = CoreAudioUtil::GetChannelConfig(
        AudioDeviceDescription::kDefaultDeviceId, data_flow);
    EXPECT_EQ(config1, config2);
    // For loopback input devices, verify that the channel configuration is
    // same as for the default output device.
    if (data_flow == eCapture) {
      config1 = CoreAudioUtil::GetChannelConfig(
          AudioDeviceDescription::kLoopbackInputDeviceId, data_flow);
      config2 = CoreAudioUtil::GetChannelConfig(
          AudioDeviceDescription::kDefaultDeviceId, eRender);
      EXPECT_EQ(config1, config2);
    }
  }
}

TEST_F(CoreAudioUtilWinTest, SharedModeInitializeWithoutOffload) {
  ABORT_AUDIO_TEST_IF_NOT(DevicesAvailable());

  ComPtr<IAudioClient> client;
  client = CoreAudioUtil::CreateClient(AudioDeviceDescription::kDefaultDeviceId,
                                       eRender, eConsole);
  EXPECT_TRUE(client.Get());

  WAVEFORMATEXTENSIBLE format;
  EXPECT_TRUE(
      SUCCEEDED(CoreAudioUtil::GetSharedModeMixFormat(client.Get(), &format)));

  // Perform a shared-mode initialization without event-driven buffer handling.
  uint32_t endpoint_buffer_size = 0;
  HRESULT hr = CoreAudioUtil::SharedModeInitialize(
      client.Get(), &format, NULL, 0, &endpoint_buffer_size, NULL);
  EXPECT_TRUE(SUCCEEDED(hr));
  EXPECT_GT(endpoint_buffer_size, 0u);

  // It is only possible to create a client once.
  hr = CoreAudioUtil::SharedModeInitialize(client.Get(), &format, NULL, 0,
                                           &endpoint_buffer_size, NULL);
  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::CreateClient(AudioDeviceDescription::kDefaultDeviceId,
                                       eRender, eConsole);
  EXPECT_TRUE(client.Get());
  hr = CoreAudioUtil::SharedModeInitialize(client.Get(), &format, NULL, 0,
                                           &endpoint_buffer_size, NULL);
  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::CreateClient(AudioDeviceDescription::kDefaultDeviceId,
                                       eRender, eConsole);
  EXPECT_TRUE(client.Get());
  format.Format.nSamplesPerSec = format.Format.nSamplesPerSec + 1;
  EXPECT_FALSE(CoreAudioUtil::IsFormatSupported(
      client.Get(), AUDCLNT_SHAREMODE_SHARED, &format));
  hr = CoreAudioUtil::SharedModeInitialize(client.Get(), &format, NULL, 0,
                                           &endpoint_buffer_size, NULL);
  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::CreateClient(AudioDeviceDescription::kDefaultDeviceId,
                                       eRender, eConsole);
  EXPECT_TRUE(client.Get());
  EXPECT_TRUE(
      SUCCEEDED(CoreAudioUtil::GetSharedModeMixFormat(client.Get(), &format)));
  EXPECT_TRUE(CoreAudioUtil::IsFormatSupported(
      client.Get(), AUDCLNT_SHAREMODE_SHARED, &format));
  hr = CoreAudioUtil::SharedModeInitialize(client.Get(), &format,
                                           event_handle.Get(), 0,
                                           &endpoint_buffer_size, NULL);
  EXPECT_TRUE(SUCCEEDED(hr));
  EXPECT_GT(endpoint_buffer_size, 0u);
}

TEST_F(CoreAudioUtilWinTest, SharedModeInitializeWithOffload) {
  ABORT_AUDIO_TEST_IF_NOT(DevicesAvailable());

  ComPtr<IAudioClient> client;
  client = CoreAudioUtil::CreateClient(AudioDeviceDescription::kDefaultDeviceId,
                                       eRender, eConsole);
  EXPECT_TRUE(client.Get());

  WAVEFORMATEXTENSIBLE format;
  EXPECT_TRUE(
      SUCCEEDED(CoreAudioUtil::GetSharedModeMixFormat(client.Get(), &format)));

  // Perform a shared-mode initialization without event-driven buffer handling,
  // with audio offload enabled.
  uint32_t endpoint_buffer_size = 0;
  HRESULT hr = CoreAudioUtil::SharedModeInitialize(client.Get(), &format, NULL,
                                                   0, &endpoint_buffer_size,
                                                   NULL, kOffloadModeEnabled);
  EXPECT_TRUE(SUCCEEDED(hr));
  EXPECT_GT(endpoint_buffer_size, 0u);

  // It is only possible to create a client once.
  hr = CoreAudioUtil::SharedModeInitialize(client.Get(), &format, NULL, 0,
                                           &endpoint_buffer_size, NULL,
                                           kOffloadModeEnabled);
  EXPECT_FALSE(SUCCEEDED(hr));

  // Verify that it is possible to reinitialize the client after releasing it.
  client = CoreAudioUtil::CreateClient(AudioDeviceDescription::kDefaultDeviceId,
                                       eRender, eConsole);
  EXPECT_TRUE(client.Get());
  hr = CoreAudioUtil::SharedModeInitialize(client.Get(), &format, NULL, 0,
                                           &endpoint_buffer_size, NULL,
                                           kOffloadModeEnabled);
  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::CreateClient(AudioDeviceDescription::kDefaultDeviceId,
                                       eRender, eConsole);
  EXPECT_TRUE(client.Get());
  format.Format.nSamplesPerSec = format.Format.nSamplesPerSec + 1;
  EXPECT_FALSE(CoreAudioUtil::IsFormatSupported(
      client.Get(), AUDCLNT_SHAREMODE_SHARED, &format));
  hr = CoreAudioUtil::SharedModeInitialize(client.Get(), &format, NULL, 0,
                                           &endpoint_buffer_size, NULL,
                                           kOffloadModeEnabled);
  EXPECT_TRUE(FAILED(hr));

  // 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::CreateClient(AudioDeviceDescription::kDefaultDeviceId,
                                       eRender, eConsole);
  EXPECT_TRUE(client.Get());
  EXPECT_TRUE(
      SUCCEEDED(CoreAudioUtil::GetSharedModeMixFormat(client.Get(), &format)));
  EXPECT_TRUE(CoreAudioUtil::IsFormatSupported(
      client.Get(), AUDCLNT_SHAREMODE_SHARED, &format));
  hr = CoreAudioUtil::SharedModeInitialize(
      client.Get(), &format, event_handle.Get(), 0, &endpoint_buffer_size, NULL,
      kOffloadModeEnabled);
  EXPECT_TRUE(SUCCEEDED(hr));
  EXPECT_GT(endpoint_buffer_size, 0u);
}

TEST_F(CoreAudioUtilWinTest, CreateRenderAndCaptureClients) {
  ABORT_AUDIO_TEST_IF_NOT(DevicesAvailable());

  EDataFlow data[] = {eRender, eCapture};

  WAVEFORMATEXTENSIBLE format;
  uint32_t endpoint_buffer_size = 0;

  for (size_t i = 0; i < std::size(data); ++i) {
    ComPtr<IAudioClient> client;
    ComPtr<IAudioRenderClient> render_client;
    ComPtr<IAudioCaptureClient> capture_client;

    client = CoreAudioUtil::CreateClient(
        AudioDeviceDescription::kDefaultDeviceId, data[i], eConsole);
    EXPECT_TRUE(client.Get());
    EXPECT_TRUE(SUCCEEDED(
        CoreAudioUtil::GetSharedModeMixFormat(client.Get(), &format)));
    if (data[i] == eRender) {
      // It is not possible to create a render client using an uninitialized
      // client interface.
      render_client = CoreAudioUtil::CreateRenderClient(client.Get());
      EXPECT_FALSE(render_client.Get());

      // Do a proper initialization and verify that it works this time.
      CoreAudioUtil::SharedModeInitialize(client.Get(), &format, NULL, 0,
                                          &endpoint_buffer_size, NULL);
      render_client = CoreAudioUtil::CreateRenderClient(client.Get());
      EXPECT_TRUE(render_client.Get());
      EXPECT_GT(endpoint_buffer_size, 0u);
    } else if (data[i] == eCapture) {
      // It is not possible to create a capture client using an uninitialized
      // client interface.
      capture_client = CoreAudioUtil::CreateCaptureClient(client.Get());
      EXPECT_FALSE(capture_client.Get());

      // Do a proper initialization and verify that it works this time.
      CoreAudioUtil::SharedModeInitialize(client.Get(), &format, NULL, 0,
                                          &endpoint_buffer_size, NULL);
      capture_client = CoreAudioUtil::CreateCaptureClient(client.Get());
      EXPECT_TRUE(capture_client.Get());
      EXPECT_GT(endpoint_buffer_size, 0u);
    }
  }
}

TEST_F(CoreAudioUtilWinTest, FillRenderEndpointBufferWithSilence) {
  ABORT_AUDIO_TEST_IF_NOT(DevicesAvailable());

  // Create default clients using the default mixing format for shared mode.
  ComPtr<IAudioClient> client(CoreAudioUtil::CreateClient(
      AudioDeviceDescription::kDefaultDeviceId, eRender, eConsole));
  EXPECT_TRUE(client.Get());

  WAVEFORMATEXTENSIBLE format;
  uint32_t endpoint_buffer_size = 0;
  EXPECT_TRUE(
      SUCCEEDED(CoreAudioUtil::GetSharedModeMixFormat(client.Get(), &format)));
  CoreAudioUtil::SharedModeInitialize(client.Get(), &format, NULL, 0,
                                      &endpoint_buffer_size, NULL);
  EXPECT_GT(endpoint_buffer_size, 0u);

  ComPtr<IAudioRenderClient> render_client(
      CoreAudioUtil::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(CoreAudioUtil::FillRenderEndpointBufferWithSilence(
      client.Get(), render_client.Get()));
  client->GetCurrentPadding(&num_queued_frames);
  EXPECT_EQ(num_queued_frames, endpoint_buffer_size);
}

// This test can only run on a machine that has audio hardware
// that has both input and output devices.
TEST_F(CoreAudioUtilWinTest, GetMatchingOutputDeviceID) {
  ABORT_AUDIO_TEST_IF_NOT(DevicesAvailable());

  bool found_a_pair = false;

  ComPtr<IMMDeviceEnumerator> enumerator(
      CoreAudioUtil::CreateDeviceEnumerator());
  ASSERT_TRUE(enumerator.Get());

  // Enumerate all active input and output devices and fetch the ID of
  // the associated device.
  ComPtr<IMMDeviceCollection> collection;
  ASSERT_TRUE(SUCCEEDED(enumerator->EnumAudioEndpoints(
      eCapture, DEVICE_STATE_ACTIVE, &collection)));
  UINT count = 0;
  collection->GetCount(&count);
  for (UINT i = 0; i < count && !found_a_pair; ++i) {
    ComPtr<IMMDevice> device;
    collection->Item(i, &device);
    base::win::ScopedCoMem<WCHAR> wide_id;
    device->GetId(&wide_id);
    std::string id;
    base::WideToUTF8(wide_id, wcslen(wide_id), &id);
    found_a_pair = !CoreAudioUtil::GetMatchingOutputDeviceID(id).empty();
  }

  EXPECT_TRUE(found_a_pair);
}

TEST_F(CoreAudioUtilWinTest, SharedModeLowerBufferSize) {
  ABORT_AUDIO_TEST_IF_NOT(DevicesAvailable());

  AudioParameters params;
  EXPECT_TRUE(SUCCEEDED(CoreAudioUtil::GetPreferredAudioParameters(
      AudioDeviceDescription::kDefaultDeviceId, true, &params)));

  AudioParameters::HardwareCapabilities hardware_capabilities =
      params.hardware_capabilities().value_or(
          AudioParameters::HardwareCapabilities());

  // If min_frames_per_buffer is 0 then we don't support the IAudioClient3
  // low-latency API.
  ABORT_AUDIO_TEST_IF_NOT(hardware_capabilities.min_frames_per_buffer > 0);

  // Nothing to test if the default is already the minimum.
  ABORT_AUDIO_TEST_IF_NOT(hardware_capabilities.min_frames_per_buffer <
                          params.frames_per_buffer());

  ComPtr<IAudioClient> default_client;
  default_client = CoreAudioUtil::CreateClient(
      AudioDeviceDescription::kDefaultDeviceId, eRender, eConsole);
  EXPECT_TRUE(default_client.Get());

  WAVEFORMATEXTENSIBLE format;
  EXPECT_TRUE(SUCCEEDED(
      CoreAudioUtil::GetSharedModeMixFormat(default_client.Get(), &format)));

  uint32_t default_endpoint_buffer_size = 0;
  HRESULT hr = CoreAudioUtil::SharedModeInitialize(
      default_client.Get(), &format, NULL, 0, &default_endpoint_buffer_size,
      NULL);
  EXPECT_TRUE(SUCCEEDED(hr));
  EXPECT_GT(default_endpoint_buffer_size, 0u);

  ComPtr<IAudioClient> low_latency_client;
  low_latency_client = CoreAudioUtil::CreateClient(
      AudioDeviceDescription::kDefaultDeviceId, eRender, eConsole);
  EXPECT_TRUE(low_latency_client.Get());

  uint32_t low_latency_endpoint_buffer_size = 0;
  hr = CoreAudioUtil::SharedModeInitialize(
      low_latency_client.Get(), &format, NULL,
      hardware_capabilities.min_frames_per_buffer,
      &low_latency_endpoint_buffer_size, NULL);
  EXPECT_TRUE(SUCCEEDED(hr));
  EXPECT_GT(low_latency_endpoint_buffer_size, 0u);

  EXPECT_LT(low_latency_endpoint_buffer_size, default_endpoint_buffer_size);
}

}  // namespace media