/*
 *  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<IMMDeviceEnumerator> 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<IMMDevice> 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<IMMDevice> 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<IMMDevice> 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<IMMDevice> 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<IMMDevice> 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<int>(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<int>(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<IMMDevice> device(core_audio_utility::CreateDevice(
        AudioDeviceName::kDefaultDeviceId, data_flow[i], eConsole));
    EXPECT_TRUE(device.Get());
    ComPtr<IAudioSessionManager2> 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<IMMDevice> device(core_audio_utility::CreateDevice(
        AudioDeviceName::kDefaultDeviceId, data_flow[i], eConsole));
    EXPECT_TRUE(device.Get());
    ComPtr<IAudioSessionEnumerator> 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<IMMDevice> 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<IAudioSessionEnumerator> 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<IAudioClient> 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<IAudioClient2> 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<IAudioClient3> 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<IAudioClient2> client2 = core_audio_utility::CreateClient2(
      AudioDeviceName::kDefaultDeviceId, eRender, eConsole);
  EXPECT_TRUE(client2.Get());
  EXPECT_TRUE(
      SUCCEEDED(core_audio_utility::SetClientProperties(client2.Get())));

  ComPtr<IAudioClient3> 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<IAudioClient3> 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<IAudioClient2> 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<IAudioClient> 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<IAudioClient> 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<IAudioClient> 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<IAudioClient> 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(), &params)));
    EXPECT_TRUE(params.is_valid());
    EXPECT_TRUE(params.is_complete());
  }
}

TEST_F(CoreAudioUtilityWinTest, SharedModeInitialize) {
  ABORT_TEST_IF_NOT(DevicesAvailable());

  ComPtr<IAudioClient> 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<IAudioClient> client;
    ComPtr<IAudioRenderClient> render_client;
    ComPtr<IAudioCaptureClient> 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<IAudioClient> client;
    ComPtr<IAudioClock> 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<IAudioClient> client;
    ComPtr<IAudioSessionControl> 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<IAudioClient> client;
    ComPtr<ISimpleAudioVolume> 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<IAudioClient> 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<IAudioRenderClient> 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
