// 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.

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

#include <objbase.h>

#include <initguid.h>  // It should be before `devpkey.h`

#include <comdef.h>
#include <devpkey.h>
#include <functiondiscoverykeys_devpkey.h>
#include <stddef.h>
#include <stdint.h>

#include <bit>

#include "base/command_line.h"
#include "base/compiler_specific.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/logging.h"
#include "base/metrics/histogram_functions.h"
#include "base/metrics/histogram_macros.h"
#include "base/no_destructor.h"
#include "base/strings/strcat.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/strings/sys_string_conversions.h"
#include "base/strings/utf_string_conversions.h"
#include "base/trace_event/trace_event.h"
#include "base/win/scoped_co_mem.h"
#include "base/win/scoped_handle.h"
#include "base/win/scoped_propvariant.h"
#include "base/win/scoped_variant.h"
#include "base/win/win_util.h"
#include "base/win/windows_version.h"
#include "media/audio/audio_device_description.h"
#include "media/base/audio_timestamp_helper.h"
#include "media/base/channel_layout.h"
#include "media/base/media_switches.h"
#include "media/base/win/mf_helpers.h"

using Microsoft::WRL::ComPtr;
using base::win::ScopedCoMem;
using base::win::ScopedHandle;

namespace media {

// See header file for documentation.
// {BE39AF4F-087C-423F-9303-234EC1E5B8EE}
const GUID kCommunicationsSessionId = {
  0xbe39af4f, 0x87c, 0x423f, { 0x93, 0x3, 0x23, 0x4e, 0xc1, 0xe5, 0xb8, 0xee }
};

namespace {

constexpr uint32_t KSAUDIO_SPEAKER_UNSUPPORTED = 0xFFFFFFFF;

REFERENCE_TIME BufferSizeInFramesToTimeDelta(uint32_t frames,
                                             DWORD bytes_per_sec,
                                             WORD bytes_per_frame) {
  constexpr double ref_time_ns = 10000000;  // 10ms
  return (REFERENCE_TIME)((double)(frames)*ref_time_ns /
                              ((double)(bytes_per_sec / bytes_per_frame)) +
                          0.5f);
}

// Return the requested offload buffer time in 100ns units.
double GetOffloadBufferTimeIn100Ns() {
  if (base::FeatureList::IsEnabled(kAudioOffload)) {
    return media::kAudioOffloadBufferTimeMs.Get() * 10000;
  }

  return 500000;  // 50ms
}

void LogVoiceProcessingEffectsHistogram(int value) {
  base::UmaHistogramSparse("Media.Audio.Capture.Win.VoiceProcessingEffects",
                           value);
}

// TODO(henrika): add mapping for all types in the ChannelLayout enumerator.
ChannelConfig ChannelLayoutToChannelConfig(ChannelLayout layout) {
  switch (layout) {
    case CHANNEL_LAYOUT_DISCRETE:
      DVLOG(2) << "CHANNEL_LAYOUT_DISCRETE=>KSAUDIO_SPEAKER_DIRECTOUT";
      return KSAUDIO_SPEAKER_DIRECTOUT;
    case CHANNEL_LAYOUT_MONO:
      DVLOG(2) << "CHANNEL_LAYOUT_MONO=>KSAUDIO_SPEAKER_MONO";
      return KSAUDIO_SPEAKER_MONO;
    case CHANNEL_LAYOUT_STEREO:
      DVLOG(2) << "CHANNEL_LAYOUT_STEREO=>KSAUDIO_SPEAKER_STEREO";
      return KSAUDIO_SPEAKER_STEREO;
    case CHANNEL_LAYOUT_2POINT1:
      DVLOG(2) << "CHANNEL_LAYOUT_2POINT1=>KSAUDIO_SPEAKER_2POINT1";
      return KSAUDIO_SPEAKER_2POINT1;
    case CHANNEL_LAYOUT_SURROUND:
      DVLOG(2) << "CHANNEL_LAYOUT_SURROUND=>KSAUDIO_SPEAKER_3POINT0";
      return KSAUDIO_SPEAKER_3POINT0;
    case CHANNEL_LAYOUT_3_1:
      DVLOG(2) << "CHANNEL_LAYOUT_3_1=>KSAUDIO_SPEAKER_3POINT1";
      return KSAUDIO_SPEAKER_3POINT1;
    case CHANNEL_LAYOUT_QUAD:
      DVLOG(2) << "CHANNEL_LAYOUT_QUAD=>KSAUDIO_SPEAKER_QUAD";
      return KSAUDIO_SPEAKER_QUAD;
    case CHANNEL_LAYOUT_4_0:
      DVLOG(2) << "CHANNEL_LAYOUT_4_0=>KSAUDIO_SPEAKER_SURROUND";
      return KSAUDIO_SPEAKER_SURROUND;
    case CHANNEL_LAYOUT_5_0:
      DVLOG(2) << "CHANNEL_LAYOUT_5_0=>KSAUDIO_SPEAKER_5POINT0";
      return KSAUDIO_SPEAKER_5POINT0;
    case CHANNEL_LAYOUT_5_1_BACK:
      DVLOG(2) << "CHANNEL_LAYOUT_5_1_BACK=>KSAUDIO_SPEAKER_5POINT1";
      return KSAUDIO_SPEAKER_5POINT1;
    case CHANNEL_LAYOUT_5_1:
      DVLOG(2) << "CHANNEL_LAYOUT_5_1=>KSAUDIO_SPEAKER_5POINT1_SURROUND";
      return KSAUDIO_SPEAKER_5POINT1_SURROUND;
    case CHANNEL_LAYOUT_7_0:
      DVLOG(2) << "CHANNEL_LAYOUT_7_0=>KSAUDIO_SPEAKER_7POINT0";
      return KSAUDIO_SPEAKER_7POINT0;
    case CHANNEL_LAYOUT_7_1_WIDE_BACK:
      DVLOG(2) << "CHANNEL_LAYOUT_7_1_WIDE_BACK=>KSAUDIO_SPEAKER_7POINT1";
      return KSAUDIO_SPEAKER_7POINT1;
    case CHANNEL_LAYOUT_7_1:
      DVLOG(2) << "CHANNEL_LAYOUT_7_1=>KSAUDIO_SPEAKER_7POINT1_SURROUND";
      return KSAUDIO_SPEAKER_7POINT1_SURROUND;
    default:
      DVLOG(2) << "Unsupported channel layout: " << layout;
      return KSAUDIO_SPEAKER_UNSUPPORTED;
  }
}

// Converts the most common format tags defined in mmreg.h into string
// equivalents. Mainly intended for log messages.
const char* WaveFormatTagToString(WORD format_tag) {
  switch (format_tag) {
    case WAVE_FORMAT_UNKNOWN:
      return "WAVE_FORMAT_UNKNOWN";
    case WAVE_FORMAT_PCM:
      return "WAVE_FORMAT_PCM";
    case WAVE_FORMAT_IEEE_FLOAT:
      return "WAVE_FORMAT_IEEE_FLOAT";
    case WAVE_FORMAT_EXTENSIBLE:
      return "WAVE_FORMAT_EXTENSIBLE";
    default:
      return "UNKNOWN";
  }
}

constexpr struct {
  DWORD mask;
  const char* name;
} kSpeakerMappings[] = {
    {SPEAKER_FRONT_LEFT, "FRONT_LEFT"},
    {SPEAKER_FRONT_RIGHT, "FRONT_RIGHT"},
    {SPEAKER_FRONT_CENTER, "FRONT_CENTER"},
    {SPEAKER_LOW_FREQUENCY, "LOW_FREQUENCY"},
    {SPEAKER_BACK_LEFT, "BACK_LEFT"},
    {SPEAKER_BACK_RIGHT, "BACK_RIGHT"},
    {SPEAKER_FRONT_LEFT_OF_CENTER, "FRONT_LEFT_OF_CENTER"},
    {SPEAKER_FRONT_RIGHT_OF_CENTER, "RIGHT_OF_CENTER"},
    {SPEAKER_BACK_CENTER, "BACK_CENTER"},
    {SPEAKER_SIDE_LEFT, "SIDE_LEFT"},
    {SPEAKER_SIDE_RIGHT, "SIDE_RIGHT"},
    {SPEAKER_TOP_CENTER, "TOP_CENTER"},
    {SPEAKER_TOP_FRONT_LEFT, "TOP_FRONT_LEFT"},
    {SPEAKER_TOP_FRONT_CENTER, "TOP_FRONT_CENTER"},
    {SPEAKER_TOP_FRONT_RIGHT, "TOP_FRONT_RIGHT"},
    {SPEAKER_TOP_BACK_LEFT, "TOP_BACK_LEFT"},
    {SPEAKER_TOP_BACK_CENTER, "TOP_BACK_CENTER"},
    {SPEAKER_TOP_BACK_RIGHT, "TOP_BACK_RIGHT"},
};

// Converts from channel mask to list of included channels.
// Each audio data format contains channels for one or more of the positions
// listed below. The number of channels simply equals the number of nonzero
// flag bits in the |channel_mask|. The relative positions of the channels
// within each block of audio data always follow the same relative ordering
// as the flag bits in the table below. For example, if |channel_mask| contains
// the value 0x00000033, the format defines four audio channels that are
// assigned for playback to the front-left, front-right, back-left,
// and back-right speakers, respectively. The channel data should be interleaved
// in that order within each block.
std::string ChannelMaskToString(DWORD channel_mask) {
  if (channel_mask == KSAUDIO_SPEAKER_DIRECTOUT) {
    // A very rare channel mask where speaker orientation is "hard coded".
    // In direct-out mode, the audio device renders the first channel to the
    // first output connector on the device, the second channel to the second
    // output on the device, and so on.
    return "DIRECT_OUT";
  }

  std::vector<std::string_view> pieces;
  for (const auto& mapping : kSpeakerMappings) {
    if (channel_mask & mapping.mask) {
      pieces.push_back(mapping.name);
    }
  }

  const size_t total_bits = std::popcount(channel_mask);
  if (pieces.size() < total_bits) {
    pieces.push_back("UNKNOWN_BITS");
  }

  std::string result = base::JoinString(pieces, " | ");
  // Append (KnownCount/TotalCount)
  base::StrAppend(&result, {" (", base::NumberToString(pieces.size()), "/",
                            base::NumberToString(total_bits), ")"});

  return result;
}

// Converts a channel count into a channel configuration.
ChannelConfig GuessChannelConfig(WORD channels) {
  switch (channels) {
    case 0:
      DVLOG(2) << "KSAUDIO_SPEAKER_DIRECTOUT";
      return KSAUDIO_SPEAKER_DIRECTOUT;
    case 1:
      DVLOG(2) << "KSAUDIO_SPEAKER_MONO";
      return KSAUDIO_SPEAKER_MONO;
    case 2:
      DVLOG(2) << "KSAUDIO_SPEAKER_STEREO";
      return KSAUDIO_SPEAKER_STEREO;
    case 3:
      DVLOG(2) << "KSAUDIO_SPEAKER_2POINT1";
      return KSAUDIO_SPEAKER_2POINT1;
    case 4:
      DVLOG(2) << "KSAUDIO_SPEAKER_QUAD";
      return KSAUDIO_SPEAKER_QUAD;
    case 5:
      DVLOG(2) << "KSAUDIO_SPEAKER_5POINT0";
      return KSAUDIO_SPEAKER_5POINT0;
    case 6:
      DVLOG(2) << "KSAUDIO_SPEAKER_5POINT1";
      return KSAUDIO_SPEAKER_5POINT1;
    case 7:
      DVLOG(2) << "KSAUDIO_SPEAKER_7POINT0";
      return KSAUDIO_SPEAKER_7POINT0;
    case 8:
      DVLOG(2) << "KSAUDIO_SPEAKER_7POINT1";
      return KSAUDIO_SPEAKER_7POINT1;
    default:
      DVLOG(1) << "Unsupported channel count: " << channels;
  }
  return KSAUDIO_SPEAKER_STEREO;
}

std::string GetDeviceID(IMMDevice* device) {
  base::win::ScopedCoMem<WCHAR> device_id_com;
  if (FAILED(device->GetId(&device_id_com))) {
    return std::string();
  }
  return base::WideToUTF8(device_id_com.get());
}

bool IsDeviceActive(IMMDevice* device) {
  DWORD state = DEVICE_STATE_DISABLED;
  return SUCCEEDED(device->GetState(&state)) && (state & DEVICE_STATE_ACTIVE);
}

HRESULT GetDeviceFriendlyNameInternal(IMMDevice* device,
                                      std::string* friendly_name) {
  // Retrieve user-friendly name of endpoint device.
  // Example: "Microphone (Realtek High Definition Audio)".
  ComPtr<IPropertyStore> properties;
  HRESULT hr = device->OpenPropertyStore(STGM_READ, &properties);
  if (FAILED(hr))
    return hr;

  base::win::ScopedPropVariant friendly_name_pv;
  hr = properties->GetValue(PKEY_Device_FriendlyName,
                            friendly_name_pv.Receive());
  if (FAILED(hr))
    return hr;

  const PROPVARIANT& pv = friendly_name_pv.get();
  if (pv.vt == VT_LPWSTR && pv.pwszVal) {
    *friendly_name = base::WideToUTF8(std::wstring_view(pv.pwszVal));
  }

  return hr;
}

ComPtr<IMMDeviceEnumerator> CreateDeviceEnumeratorInternal(
    bool allow_reinitialize) {
  // Windows AudioSrv and AudioEndpointBuilder create single global
  // IMMDeviceEnumerator instance and uses that everywhere for the
  // life of the process, there's no caching concerns.
  static base::NoDestructor<ComPtr<IMMDeviceEnumerator>> device_enumerator(
      [allow_reinitialize] {
        ComPtr<IMMDeviceEnumerator> local_device_enumerator;
        HRESULT hr = ::CoCreateInstance(__uuidof(MMDeviceEnumerator), nullptr,
                                        CLSCTX_INPROC_SERVER,
                                        IID_PPV_ARGS(&local_device_enumerator));
        if (hr == CO_E_NOTINITIALIZED && allow_reinitialize) {
          LOG(ERROR) << "CoCreateInstance fails with CO_E_NOTINITIALIZED";
          // Buggy third-party DLLs can uninitialize COM out from under us.
          // Attempt to re-initialize it.  See http://crbug.com/378465 for more
          // details.
          ::CoInitializeEx(nullptr,
                           COINIT_MULTITHREADED | COINIT_DISABLE_OLE1DDE);
          hr = ::CoCreateInstance(__uuidof(MMDeviceEnumerator), nullptr,
                                  CLSCTX_INPROC_SERVER,
                                  IID_PPV_ARGS(&local_device_enumerator));
        }
        if (FAILED(hr)) {
          LOG(ERROR) << "CoCreateInstance(MMDeviceEnumerator) failed: "
                     << std::hex << hr;
        }

        return local_device_enumerator;
      }());

  return *device_enumerator;
}

ChannelLayout GetChannelLayout(
    const CoreAudioUtil::WaveFormatWrapper mix_format) {
  if (!mix_format.IsExtensible()) {
    DVLOG(1) << "Format does not contain any channel mask."
             << " Guessing layout by channel count: " << std::dec
             << mix_format->nChannels;
    return GuessChannelLayout(mix_format->nChannels);
  }

  // Get the integer mask which corresponds to the channel layout the
  // audio engine uses for its internal processing/mixing of shared-mode
  // streams. This mask indicates which channels are present in the multi-
  // channel stream. The least significant bit corresponds with the Front
  // Left speaker, the next least significant bit corresponds to the Front
  // Right speaker, and so on, continuing in the order defined in KsMedia.h.
  // See
  // http://msdn.microsoft.com/en-us/library/windows/hardware/ff537083.aspx
  // for more details.
  ChannelConfig channel_config = mix_format.GetExtensible()->dwChannelMask;

  // Convert Microsoft's channel configuration to generic ChannelLayout.
  ChannelLayout channel_layout = ChannelConfigToChannelLayout(channel_config);

  // Some devices don't appear to set a valid channel layout, so guess based
  // on the number of channels.  See http://crbug.com/311906.
  if (channel_layout == CHANNEL_LAYOUT_UNSUPPORTED) {
    DVLOG(1) << "Unsupported channel config: " << std::hex << channel_config
             << ".  Guessing layout by channel count: " << std::dec
             << mix_format->nChannels;
    channel_layout = GuessChannelLayout(mix_format->nChannels);
  }

  return channel_layout;
}

bool IsSupportedInternal() {
  // It is possible to force usage of WaveXxx APIs by using a command line
  // flag.
  const base::CommandLine* cmd_line = base::CommandLine::ForCurrentProcess();
  if (cmd_line->HasSwitch(switches::kForceWaveAudio)) {
    DVLOG(1) << "Forcing usage of Windows WaveXxx APIs";
    return false;
  }

  // Verify that it is possible to a create the IMMDeviceEnumerator interface.
  ComPtr<IMMDeviceEnumerator> device_enumerator =
      CreateDeviceEnumeratorInternal(false);
  if (!device_enumerator) {
    LOG(ERROR)
        << "Failed to create Core Audio device enumerator on thread with ID "
        << GetCurrentThreadId();
    return false;
  }

  return true;
}

// Retrieve an audio device specified by |device_id| or a default device
// specified by data-flow direction and role if |device_id| is default.
ComPtr<IMMDevice> CreateDeviceInternal(const std::string& device_id,
                                       EDataFlow data_flow,
                                       ERole role,
                                       HRESULT* hr_out = nullptr) {
  ComPtr<IMMDevice> endpoint_device;
  // In loopback mode, a client of WASAPI can capture the audio stream that
  // is being played by a rendering endpoint device.
  // See https://crbug.com/956526 for why we use both a DCHECK and then deal
  // with the error here and below. Also, see comments in CreateDeviceByID() for
  // more details.
  DCHECK(!(AudioDeviceDescription::IsLoopbackDevice(device_id) &&
           data_flow != eCapture));
  if (AudioDeviceDescription::IsLoopbackDevice(device_id) &&
      data_flow != eCapture) {
    LOG(WARNING) << "Loopback device must be an input device";
    if (hr_out) {
      *hr_out = E_INVALIDARG;
    }
    return endpoint_device;
  }

  // Usage of AudioDeviceDescription::kCommunicationsDeviceId as |device_id|
  // is not allowed. Instead, set |device_id| to kDefaultDeviceId and select
  // between default device and default communication device by using different
  // |role| values (eConsole or eCommunications).
  DCHECK(!AudioDeviceDescription::IsCommunicationsDevice(device_id));
  if (AudioDeviceDescription::IsCommunicationsDevice(device_id)) {
    LOG(WARNING) << "Invalid device identifier";
    if (hr_out) {
      *hr_out = E_INVALIDARG;
    }
    return endpoint_device;
  }

  // Create the IMMDeviceEnumerator interface.
  ComPtr<IMMDeviceEnumerator> device_enum(CreateDeviceEnumeratorInternal(true));
  if (!device_enum.Get()) {
    if (hr_out) {
      *hr_out = E_POINTER;
    }
    return endpoint_device;
  }

  HRESULT hr;
  if (AudioDeviceDescription::IsDefaultDevice(device_id)) {
    hr =
        device_enum->GetDefaultAudioEndpoint(data_flow, role, &endpoint_device);
  } else if (AudioDeviceDescription::IsLoopbackDevice(device_id)) {
    // To open a stream in loopback mode, the client must obtain an IMMDevice
    // interface for the *rendering* endpoint device.
    hr = device_enum->GetDefaultAudioEndpoint(eRender, role, &endpoint_device);
  } else {
    hr = device_enum->GetDevice(base::UTF8ToWide(device_id).c_str(),
                                &endpoint_device);
  }
  if (hr_out) {
    *hr_out = hr;
  }
  DVLOG_IF(1, FAILED(hr)) << "Create Device failed: " << std::hex << hr;

  // Verify that the audio endpoint device is active, i.e., that the audio
  // adapter that connects to the endpoint device is present and enabled.
  if (SUCCEEDED(hr) && !IsDeviceActive(endpoint_device.Get())) {
    DVLOG(1) << "Selected endpoint device is not active";
    endpoint_device.Reset();
    if (hr_out) {
      *hr_out = E_FAIL;
    }
  }

  return endpoint_device;
}

// Decide on data_flow and role based on |device_id|, and return the
// corresponding audio device.
ComPtr<IMMDevice> CreateDeviceByID(const std::string& device_id,
                                   bool is_output_device,
                                   HRESULT* hr_out = nullptr) {
  // Loopback devices are only supported for capture streams. If a loopback
  // device is requested for a render stream, the default render device will be
  // used instead.
  // See https://crbug.com/956526 for more details.
  if (AudioDeviceDescription::IsLoopbackDevice(device_id)) {
    DCHECK(!is_output_device);
    return CreateDeviceInternal(AudioDeviceDescription::kDefaultDeviceId,
                                eRender, eConsole, hr_out);
  }

  EDataFlow data_flow = is_output_device ? eRender : eCapture;
  if (device_id == AudioDeviceDescription::kCommunicationsDeviceId)
    return CreateDeviceInternal(AudioDeviceDescription::kDefaultDeviceId,
                                data_flow, eCommunications, hr_out);

  // If AudioDeviceDescription::IsDefaultDevice(device_id), a default device
  // will be created
  return CreateDeviceInternal(device_id, data_flow, eConsole, hr_out);
}

// Creates and activates an IAudioClient COM object given the selected
// endpoint device.
ComPtr<IAudioClient> CreateClientInternal(IMMDevice* audio_device,
                                          HRESULT* hr_out = nullptr) {
  if (!audio_device) {
    if (hr_out) {
      *hr_out = E_POINTER;
    }
    return ComPtr<IAudioClient>();
  }

  ComPtr<IAudioClient> audio_client;
  HRESULT hr = audio_device->Activate(
      __uuidof(IAudioClient), CLSCTX_INPROC_SERVER, NULL, &audio_client);
  if (hr_out) {
    *hr_out = hr;
  }
  DVLOG_IF(1, FAILED(hr)) << "IMMDevice::Activate: " << std::hex << hr;
  return audio_client;
}

// Creates and activates an IAudioClient3 COM object given the selected
// endpoint device.
ComPtr<IAudioClient3> CreateClientInternal3(IMMDevice* audio_device,
                                            HRESULT* hr_out = nullptr) {
  if (!audio_device) {
    if (hr_out) {
      *hr_out = E_POINTER;
    }
    return ComPtr<IAudioClient3>();
  }

  ComPtr<IAudioClient3> audio_client;
  HRESULT hr = audio_device->Activate(
      __uuidof(IAudioClient3), CLSCTX_INPROC_SERVER, NULL, &audio_client);
  if (hr_out) {
    *hr_out = hr;
  }
  DVLOG_IF(1, FAILED(hr)) << "IMMDevice::Activate: " << std::hex << hr;
  return audio_client;
}

HRESULT GetPreferredAudioParametersInternal(IAudioClient* client,
                                            bool is_output_device,
                                            AudioParameters* params,
                                            bool is_offload_stream) {
  WAVEFORMATEXTENSIBLE mix_format;
  HRESULT hr = CoreAudioUtil::GetSharedModeMixFormat(client, &mix_format);
  if (FAILED(hr))
    return hr;
  CoreAudioUtil::WaveFormatWrapper format(&mix_format);

  int min_frames_per_buffer = 0;
  int max_frames_per_buffer = 0;
  int default_frames_per_buffer = 0;
  int frames_per_buffer = 0;

  const int sample_rate = format->nSamplesPerSec;
  if (is_offload_stream) {
    frames_per_buffer = AudioTimestampHelper::TimeToFrames(
        CoreAudioUtil::ReferenceTimeToTimeDelta(GetOffloadBufferTimeIn100Ns()),
        sample_rate);
    ComPtr<IAudioClient> audio_client(client);
    ComPtr<IAudioClient2> audio_client_2;
    hr = audio_client.As(&audio_client_2);
    if (SUCCEEDED(hr)) {
      REFERENCE_TIME min_buffer_duration = 0;
      REFERENCE_TIME max_buffer_duration = 0;
      audio_client_2->GetBufferSizeLimits(
          &mix_format.Format, true, &min_buffer_duration, &max_buffer_duration);

      min_frames_per_buffer = AudioTimestampHelper::TimeToFrames(
          CoreAudioUtil::ReferenceTimeToTimeDelta(min_buffer_duration),
          sample_rate);
      max_frames_per_buffer = AudioTimestampHelper::TimeToFrames(
          CoreAudioUtil::ReferenceTimeToTimeDelta(max_buffer_duration),
          sample_rate);
    }
  } else {
    // Try to obtain an IAudioClient3 interface from the IAudioClient object.
    // Use ComPtr::As for doing QueryInterface calls on COM objects.
    ComPtr<IAudioClient> audio_client(client);
    ComPtr<IAudioClient3> audio_client_3;
    hr = audio_client.As(&audio_client_3);
    if (SUCCEEDED(hr)) {
      UINT32 default_period_frames = 0;
      UINT32 fundamental_period_frames = 0;
      UINT32 min_period_frames = 0;
      UINT32 max_period_frames = 0;
      hr = audio_client_3->GetSharedModeEnginePeriod(
          format.get(), &default_period_frames, &fundamental_period_frames,
          &min_period_frames, &max_period_frames);

      if (SUCCEEDED(hr)) {
        min_frames_per_buffer = min_period_frames;
        max_frames_per_buffer = max_period_frames;
        default_frames_per_buffer = default_period_frames;
        frames_per_buffer = default_period_frames;
      }
    }

    // If the call to GetSharedModeEnginePeriod() fails we fall back to
    // GetDevicePeriod().
    if (FAILED(hr)) {
      REFERENCE_TIME default_period = 0;
      hr = CoreAudioUtil::GetDevicePeriod(client, AUDCLNT_SHAREMODE_SHARED,
                                          &default_period);
      if (FAILED(hr)) {
        return hr;
      }

      // We are using the native device period to derive the smallest possible
      // buffer size in shared mode. Note that the actual endpoint buffer will
      // be larger than this size but it will be possible to fill it up in two
      // calls.
      frames_per_buffer = static_cast<int>(
          sample_rate * CoreAudioUtil::ReferenceTimeToTimeDelta(default_period)
                            .InSecondsF() +
          0.5);
      DVLOG(1) << "IAudioClient => frames_per_buffer: " << frames_per_buffer;
    }
  }

  // Retrieve the current channel configuration (e.g. CHANNEL_LAYOUT_STEREO).
  ChannelLayout channel_layout = GetChannelLayout(format);
  int channels = ChannelLayoutToChannelCount(channel_layout);
  if (channel_layout == CHANNEL_LAYOUT_DISCRETE) {
    if (!is_output_device) {
      // Set the number of channels explicitly to two for input devices if
      // the channel layout is discrete to ensure that the parameters are valid
      // and that clients does not have to support multi-channel input cases.
      // Any required down-mixing from N (N > 2) to 2 must be performed by the
      // input stream implementation instead.
      // See crbug.com/868026 for examples where this approach is needed.
      DVLOG(1) << "Forcing number of channels to 2 for CHANNEL_LAYOUT_DISCRETE";
      channels = 2;
    } else {
      // Some output devices return CHANNEL_LAYOUT_DISCRETE. Keep this channel
      // format but update the number of channels with the correct value. The
      // number of channels will be zero otherwise.
      // See crbug.com/957886 for more details.
      DVLOG(1) << "Setting number of channels to " << format->nChannels
               << " for CHANNEL_LAYOUT_DISCRETE";
      channels = format->nChannels;
    }
  }

  AudioParameters audio_params(
      AudioParameters::AUDIO_PCM_LOW_LATENCY, {channel_layout, channels},
      sample_rate, frames_per_buffer,
      AudioParameters::HardwareCapabilities(
          min_frames_per_buffer, max_frames_per_buffer,
          default_frames_per_buffer, is_offload_stream));

  DVLOG(1) << audio_params.AsHumanReadableString();
  DCHECK(audio_params.IsValid());
  *params = audio_params;

  return hr;
}

// Internal utility method used by CoreAudioUtil::GetPreferredAudioParameters
// for input devices. Initializes the client in communications mode using the
// default shared mode mixing format based on IAudioClient::GetMixFormat().
bool InitializeCommunicationsCaptureClient(IAudioClient* client) {
  if (CoreAudioUtil::IsClientInitialized(client)) {
    return true;
  }

  // Set the category as communications. This also ensures that audio effects
  // are enabled by default.
  if (!CoreAudioUtil::EnableCommunicationsAudioCategoryForClient(client)) {
    return false;
  }

  // Initialize the audio client using a basic format and 100 msec buffer
  // capacity. Exact format is not essential for our purposes so keeping it as
  // simple as possible.
  ScopedCoMem<WAVEFORMATEX> mix_format;
  if (FAILED(client->GetMixFormat(&mix_format))) {
    return false;
  }
  if (FAILED(client->Initialize(AUDCLNT_SHAREMODE_SHARED,
                                AUDCLNT_STREAMFLAGS_EVENTCALLBACK, 1000000, 0,
                                mix_format.get(), NULL))) {
    return false;
  }

  return true;
}

}  // namespace

// CoreAudioUtil::WaveFormatWrapper implementation.
WAVEFORMATEXTENSIBLE* CoreAudioUtil::WaveFormatWrapper::GetExtensible() const {
  CHECK(IsExtensible());
  return reinterpret_cast<WAVEFORMATEXTENSIBLE*>(ptr_.get());
}

bool CoreAudioUtil::WaveFormatWrapper::IsExtensible() const {
  return ptr_->wFormatTag == WAVE_FORMAT_EXTENSIBLE && ptr_->cbSize >= 22;
}

bool CoreAudioUtil::WaveFormatWrapper::IsPcm() const {
  return IsExtensible() ? GetExtensible()->SubFormat == KSDATAFORMAT_SUBTYPE_PCM
                        : ptr_->wFormatTag == WAVE_FORMAT_PCM;
}

bool CoreAudioUtil::WaveFormatWrapper::IsFloat() const {
  return IsExtensible()
             ? GetExtensible()->SubFormat == KSDATAFORMAT_SUBTYPE_IEEE_FLOAT
             : ptr_->wFormatTag == WAVE_FORMAT_IEEE_FLOAT;
}

size_t CoreAudioUtil::WaveFormatWrapper::size() const {
  return sizeof(*ptr_) + ptr_->cbSize;
}

bool CoreAudioUtil::IsSupported() {
  static bool g_is_supported = IsSupportedInternal();
  return g_is_supported;
}

std::string CoreAudioUtil::ErrorToString(HRESULT hresult) {
  const _com_error error(hresult);
  // If the HRESULT is within the range 0x80040200 to 0x8004FFFF, the WCode()
  // method returns the HRESULT minus 0x80040200; otherwise, it returns zero.
  return base::StringPrintf("HRESULT: 0x%08lX, WCode: %u, message: \"%s\"",
                            error.Error(), error.WCode(),
                            base::WideToUTF8(error.ErrorMessage()).c_str());
}

std::string CoreAudioUtil::WaveFormatToString(const WaveFormatWrapper format) {
  // Start with the WAVEFORMATEX part.
  std::string wave_format = base::StringPrintf(
      "wFormatTag: %s (0x%X), nChannels: %d, nSamplesPerSec: %lu"
      ", nAvgBytesPerSec: %lu, nBlockAlign: %d, wBitsPerSample: %d, cbSize: %d",
      WaveFormatTagToString(format->wFormatTag), format->wFormatTag,
      format->nChannels, format->nSamplesPerSec, format->nAvgBytesPerSec,
      format->nBlockAlign, format->wBitsPerSample, format->cbSize);
  if (!format.IsExtensible())
    return wave_format;

  // Append the WAVEFORMATEXTENSIBLE part (which we know exists).
  base::StringAppendF(
      &wave_format, " [+] wValidBitsPerSample: %d, dwChannelMask: %s",
      format.GetExtensible()->Samples.wValidBitsPerSample,
      ChannelMaskToString(format.GetExtensible()->dwChannelMask).c_str());
  if (format.IsPcm()) {
    base::StringAppendF(&wave_format, "%s",
                        ", SubFormat: KSDATAFORMAT_SUBTYPE_PCM");
  } else if (format.IsFloat()) {
    base::StringAppendF(&wave_format, "%s",
                        ", SubFormat: KSDATAFORMAT_SUBTYPE_IEEE_FLOAT");
  } else {
    base::StringAppendF(&wave_format, "%s", ", SubFormat: NOT_SUPPORTED");
  }
  return wave_format;
}

base::TimeDelta CoreAudioUtil::ReferenceTimeToTimeDelta(REFERENCE_TIME time) {
  // Each unit of reference time is 100 nanoseconds <=> 0.1 microsecond.
  return base::Microseconds(0.1 * time + 0.5);
}

AUDCLNT_SHAREMODE CoreAudioUtil::GetShareMode() {
  const base::CommandLine* cmd_line = base::CommandLine::ForCurrentProcess();
  if (cmd_line->HasSwitch(switches::kEnableExclusiveAudio))
    return AUDCLNT_SHAREMODE_EXCLUSIVE;
  return AUDCLNT_SHAREMODE_SHARED;
}

int CoreAudioUtil::NumberOfActiveDevices(EDataFlow data_flow) {
  // Create the IMMDeviceEnumerator interface.
  ComPtr<IMMDeviceEnumerator> device_enumerator = CreateDeviceEnumerator();
  if (!device_enumerator.Get())
    return 0;

  // Generate a collection of active (present and not disabled) audio endpoint
  // devices for the specified data-flow direction.
  // This method will succeed even if all devices are disabled.
  ComPtr<IMMDeviceCollection> collection;
  HRESULT hr = device_enumerator->EnumAudioEndpoints(
      data_flow, DEVICE_STATE_ACTIVE, &collection);
  if (FAILED(hr)) {
    LOG(ERROR) << "IMMDeviceCollection::EnumAudioEndpoints: " << std::hex << hr;
    return 0;
  }

  // Retrieve the number of active audio devices for the specified direction
  UINT number_of_active_devices = 0;
  collection->GetCount(&number_of_active_devices);
  DVLOG(2) << ((data_flow == eCapture) ? "[in ] " : "[out] ")
           << "number of devices: " << number_of_active_devices;
  return static_cast<int>(number_of_active_devices);
}

ComPtr<IMMDeviceEnumerator> CoreAudioUtil::CreateDeviceEnumerator() {
  return CreateDeviceEnumeratorInternal(true);
}

std::string CoreAudioUtil::GetDefaultInputDeviceID() {
  ComPtr<IMMDevice> device(CreateDevice(
      AudioDeviceDescription::kDefaultDeviceId, eCapture, eConsole));
  return device.Get() ? GetDeviceID(device.Get()) : std::string();
}

std::string CoreAudioUtil::GetDefaultOutputDeviceID() {
  ComPtr<IMMDevice> device(CreateDevice(
      AudioDeviceDescription::kDefaultDeviceId, eRender, eConsole));
  return device.Get() ? GetDeviceID(device.Get()) : std::string();
}

std::string CoreAudioUtil::GetCommunicationsInputDeviceID() {
  ComPtr<IMMDevice> device(
      CreateDevice(std::string(), eCapture, eCommunications));
  return device.Get() ? GetDeviceID(device.Get()) : std::string();
}

std::string CoreAudioUtil::GetCommunicationsOutputDeviceID() {
  ComPtr<IMMDevice> device(
      CreateDevice(std::string(), eRender, eCommunications));
  return device.Get() ? GetDeviceID(device.Get()) : std::string();
}

HRESULT CoreAudioUtil::GetDeviceName(IMMDevice* device, AudioDeviceName* name) {
  // Retrieve unique name of endpoint device.
  // Example: "{0.0.1.00000000}.{8db6020f-18e3-4f25-b6f5-7726c9122574}".
  AudioDeviceName device_name;
  device_name.unique_id = GetDeviceID(device);
  if (device_name.unique_id.empty())
    return E_FAIL;

  HRESULT hr = GetDeviceFriendlyNameInternal(device, &device_name.device_name);
  if (FAILED(hr))
    return hr;

  *name = device_name;
  DVLOG(2) << "friendly name: " << device_name.device_name;
  DVLOG(2) << "unique id    : " << device_name.unique_id;
  return hr;
}

std::string CoreAudioUtil::GetAudioControllerID(IMMDevice* device) {
  // Windows uses device container to represent the same physical device
  // regardless of the number of devices nodes. The container id is a GUID
  // and can be retrieved by using the `DEVPKEY_Device_ContainerId`.
  // https://learn.microsoft.com/en-us/windows-hardware/drivers/install/devpkey-device-containerid.
  ComPtr<IPropertyStore> properties;
  base::win::ScopedPropVariant instance_id;
  if (FAILED(device->OpenPropertyStore(STGM_READ, &properties)) ||
      FAILED(properties->GetValue((REFPROPERTYKEY)DEVPKEY_Device_ContainerId,
                                  instance_id.Receive())) ||
      instance_id.get().vt != VT_CLSID) {
    DLOG(ERROR) << "Failed to get instance id of the audio device node";
    return std::string();
  }

  return base::SysWideToUTF8(
      base::win::WStringFromGUID(*instance_id.get().puuid));
}

std::string CoreAudioUtil::GetMatchingOutputDeviceID(
    const std::string& input_device_id) {
  // Special handling for the default communications device.
  // We always treat the configured communications devices, as a pair.
  // If we didn't do that and the user has e.g. configured a mic of a headset
  // as the default comms input device and a different device (not the speakers
  // of the headset) as the default comms output device, then we would otherwise
  // here pick the headset as the matched output device.  That's technically
  // correct, but the user experience would be that any audio played out to
  // the matched device, would get ducked since it's not the default comms
  // device.  So here, we go with the user's configuration.
  if (input_device_id == AudioDeviceDescription::kCommunicationsDeviceId)
    return AudioDeviceDescription::kCommunicationsDeviceId;

  ComPtr<IMMDevice> input_device(
      CreateDevice(input_device_id, eCapture, eConsole));

  if (!input_device.Get())
    return std::string();

  // See if we can get id of the associated controller.
  std::string controller_id(GetAudioControllerID(input_device.Get()));
  if (controller_id.empty())
    return std::string();

  // Now enumerate the available (and active) output devices and see if any of
  // them is associated with the same controller.
  ComPtr<IMMDeviceEnumerator> enumerator(CreateDeviceEnumerator());
  ComPtr<IMMDeviceCollection> collection;
  enumerator->EnumAudioEndpoints(eRender, DEVICE_STATE_ACTIVE, &collection);
  if (!collection.Get())
    return std::string();

  UINT count = 0;
  collection->GetCount(&count);
  ComPtr<IMMDevice> output_device;
  for (UINT i = 0; i < count; ++i) {
    collection->Item(i, &output_device);
    std::string output_controller_id(GetAudioControllerID(output_device.Get()));
    if (output_controller_id == controller_id)
      break;
    output_device = nullptr;
  }

  return output_device.Get() ? GetDeviceID(output_device.Get()) : std::string();
}

std::string CoreAudioUtil::GetFriendlyName(const std::string& device_id,
                                           EDataFlow data_flow,
                                           ERole role) {
  ComPtr<IMMDevice> audio_device = CreateDevice(device_id, data_flow, role);
  if (!audio_device.Get())
    return std::string();

  AudioDeviceName device_name;
  HRESULT hr = GetDeviceName(audio_device.Get(), &device_name);
  if (FAILED(hr))
    return std::string();

  return device_name.device_name;
}

EDataFlow CoreAudioUtil::GetDataFlow(IMMDevice* device) {
  ComPtr<IMMEndpoint> endpoint;
  HRESULT hr = device->QueryInterface(IID_PPV_ARGS(&endpoint));
  if (FAILED(hr)) {
    DVLOG(1) << "IMMDevice::QueryInterface: " << std::hex << hr;
    return eAll;
  }

  EDataFlow data_flow;
  hr = endpoint->GetDataFlow(&data_flow);
  if (FAILED(hr)) {
    DVLOG(1) << "IMMEndpoint::GetDataFlow: " << std::hex << hr;
    return eAll;
  }
  return data_flow;
}

ComPtr<IMMDevice> CoreAudioUtil::CreateDevice(const std::string& device_id,
                                              EDataFlow data_flow,
                                              ERole role) {
  HRESULT hr = S_OK;
  return CreateDevice(device_id, data_flow, role, hr);
}

ComPtr<IMMDevice> CoreAudioUtil::CreateDevice(const std::string& device_id,
                                              EDataFlow data_flow,
                                              ERole role,
                                              HRESULT& hr_out) {
  return CreateDeviceInternal(device_id, data_flow, role, &hr_out);
}

ComPtr<IAudioClient> CoreAudioUtil::CreateClient(const std::string& device_id,
                                                 EDataFlow data_flow,
                                                 ERole role) {
  HRESULT hr = S_OK;
  return CreateClient(device_id, data_flow, role, hr);
}

ComPtr<IAudioClient> CoreAudioUtil::CreateClient(const std::string& device_id,
                                                 EDataFlow data_flow,
                                                 ERole role,
                                                 HRESULT& hr_out) {
  TRACE_EVENT0("audio", "CoreAudioUtil::CreateClient");
  ComPtr<IMMDevice> device(CreateDevice(device_id, data_flow, role, hr_out));
  if (!device) {
    return ComPtr<IAudioClient>();
  }
  return CreateClientInternal(device.Get(), &hr_out);
}

ComPtr<IAudioClient3> CoreAudioUtil::CreateClient3(const std::string& device_id,
                                                   EDataFlow data_flow,
                                                   ERole role) {
  HRESULT hr = S_OK;
  return CreateClient3(device_id, data_flow, role, hr);
}

ComPtr<IAudioClient3> CoreAudioUtil::CreateClient3(const std::string& device_id,
                                                   EDataFlow data_flow,
                                                   ERole role,
                                                   HRESULT& hr_out) {
  ComPtr<IMMDevice> device(CreateDevice(device_id, data_flow, role, hr_out));
  if (!device) {
    return ComPtr<IAudioClient3>();
  }
  return CreateClientInternal3(device.Get(), &hr_out);
}

HRESULT CoreAudioUtil::GetSharedModeMixFormat(IAudioClient* client,
                                              WAVEFORMATEXTENSIBLE* format) {
  // The GetMixFormat method retrieves the stream format that the audio engine
  // uses for its internal processing of shared-mode streams. The method
  // allocates the storage for the structure and this memory will be released
  // when |mix_format| goes out of scope. The GetMixFormat method retrieves a
  // format descriptor that is in the form of a WAVEFORMATEXTENSIBLE structure
  // instead of a standalone WAVEFORMATEX structure. The method outputs a
  // pointer to the WAVEFORMATEX structure that is embedded at the start of
  // this WAVEFORMATEXTENSIBLE structure.
  // Note that, crbug/803056 indicates that some devices can return a format
  // where only the WAVEFORMATEX parts is initialized and we must be able to
  // account for that.
  ScopedCoMem<WAVEFORMATEXTENSIBLE> mix_format;
  HRESULT hr =
      client->GetMixFormat(reinterpret_cast<WAVEFORMATEX**>(&mix_format));
  if (FAILED(hr))
    return hr;

  // Use a wave format wrapper to make things simpler.
  WaveFormatWrapper wrapped_format(mix_format.get());

  // Verify that the reported format can be mixed by the audio engine in
  // shared mode.
  if (!wrapped_format.IsPcm() && !wrapped_format.IsFloat()) {
    DLOG(ERROR)
        << "Only pure PCM or float audio streams can be mixed in shared mode";
    return AUDCLNT_E_UNSUPPORTED_FORMAT;
  }
  // Log a warning for the rare case where |mix_format| only contains a
  // stand-alone WAVEFORMATEX structure but don't return.
  if (!wrapped_format.IsExtensible()) {
    DLOG(WARNING) << "The returned format contains no extended information. "
                     "The size is "
                  << wrapped_format.size() << " bytes.";
  }

  // Copy the correct number of bytes into |*format| taking into account if
  // the returned structure is correctly extended or not.
  const size_t format_size = wrapped_format.size();
  CHECK_LE(format_size, sizeof(WAVEFORMATEXTENSIBLE))
      << "Format tag: 0x" << std::hex << wrapped_format->wFormatTag;
  auto dest_span = base::byte_span_from_ref(*format);
  // SAFETY: Trust that |wrapped_format.size()| returned the right size.
  auto source_span = UNSAFE_BUFFERS(base::span(
      reinterpret_cast<const uint8_t*>(wrapped_format.get()), format_size));
  dest_span.first(format_size).copy_from(source_span);
  DVLOG(2) << CoreAudioUtil::WaveFormatToString(format);

  return hr;
}
bool CoreAudioUtil::IsFormatSupported(IAudioClient* client,
                                      AUDCLNT_SHAREMODE share_mode,
                                      const WaveFormatWrapper format) {
  HRESULT hr = S_OK;
  return IsFormatSupported(client, share_mode, format, hr);
}

bool CoreAudioUtil::IsFormatSupported(IAudioClient* client,
                                      AUDCLNT_SHAREMODE share_mode,
                                      const WaveFormatWrapper format,
                                      HRESULT& hr_out) {
  ScopedCoMem<WAVEFORMATEX> closest_match;
  HRESULT hr = client->IsFormatSupported(share_mode, format, &closest_match);
  hr_out = hr;

  // This log can only be triggered for shared mode.
  DLOG_IF(ERROR, hr == S_FALSE) << "Format is not supported "
                                << "but a closest match exists.";
  // This log can be triggered both for shared and exclusive modes.
  DLOG_IF(ERROR, hr == AUDCLNT_E_UNSUPPORTED_FORMAT) << "Unsupported format.";
  DVLOG(2) << CoreAudioUtil::WaveFormatToString(format);
  if (hr == S_FALSE) {
    DVLOG(2) << CoreAudioUtil::WaveFormatToString(closest_match.get());
  }

  return (hr == S_OK);
}

bool CoreAudioUtil::IsChannelLayoutSupported(const std::string& device_id,
                                             EDataFlow data_flow,
                                             ERole role,
                                             ChannelLayout channel_layout) {
  // First, get the preferred mixing format for shared mode streams.
  ComPtr<IAudioClient> client(CreateClient(device_id, data_flow, role));
  if (!client.Get())
    return false;

  WAVEFORMATEXTENSIBLE mix_format;
  HRESULT hr = GetSharedModeMixFormat(client.Get(), &mix_format);
  if (FAILED(hr))
    return false;

  // Next, check if it is possible to use an alternative format where the
  // channel layout (and possibly number of channels) is modified.

  // Convert generic channel layout into Windows-specific channel configuration
  // but only if the wave format is extended (can contain a channel mask).
  WaveFormatWrapper format(&mix_format);
  if (format.IsExtensible()) {
    ChannelConfig new_config = ChannelLayoutToChannelConfig(channel_layout);
    if (new_config == KSAUDIO_SPEAKER_UNSUPPORTED) {
      return false;
    }
    format.GetExtensible()->dwChannelMask = new_config;
  }

  // Modify the format if the new channel layout has changed the number of
  // utilized channels.
  const int channels = ChannelLayoutToChannelCount(channel_layout);
  if (channels != format->nChannels) {
    format->nChannels = channels;
    format->nBlockAlign = (format->wBitsPerSample / 8) * channels;
    format->nAvgBytesPerSec = format->nSamplesPerSec * format->nBlockAlign;
  }
  DVLOG(2) << CoreAudioUtil::WaveFormatToString(format);

  // Some devices can initialize a shared-mode stream with a format that is
  // not identical to the mix format obtained from the GetMixFormat() method.
  // However, chances of succeeding increases if we use the same number of
  // channels and the same sample rate as the mix format. I.e, this call will
  // return true only in those cases where the audio engine is able to support
  // an even wider range of shared-mode formats where the installation package
  // for the audio device includes a local effects (LFX) audio processing
  // object (APO) that can handle format conversions.
  return CoreAudioUtil::IsFormatSupported(client.Get(),
                                          AUDCLNT_SHAREMODE_SHARED, format);
}

HRESULT CoreAudioUtil::GetDevicePeriod(IAudioClient* client,
                                       AUDCLNT_SHAREMODE share_mode,
                                       REFERENCE_TIME* device_period) {
  // Get the period of the engine thread.
  REFERENCE_TIME default_period = 0;
  REFERENCE_TIME minimum_period = 0;
  HRESULT hr = client->GetDevicePeriod(&default_period, &minimum_period);
  if (FAILED(hr))
    return hr;

  *device_period = (share_mode == AUDCLNT_SHAREMODE_SHARED) ? default_period
                                                            : minimum_period;
  DVLOG(2) << "device_period: "
           << ReferenceTimeToTimeDelta(*device_period).InMillisecondsF()
           << " [ms]";
  return hr;
}

HRESULT CoreAudioUtil::GetPreferredAudioParameters(const std::string& device_id,
                                                   bool is_output_device,
                                                   AudioParameters* params,
                                                   bool is_offload_stream) {
  // Loopback capture audio streams must be input streams. If an output device
  // is requested for a loopback device, the default output device will be used
  // instead. See https://crbug.com/956526 for more details.
  // TODO(crbug.com/40947205): figure out which parameters to use for process
  // loopback capture.
  DCHECK(!(is_output_device &&
           (AudioDeviceDescription::IsLoopbackDevice(device_id))));
  if (AudioDeviceDescription::IsLoopbackDevice(device_id) && is_output_device) {
    LOG(WARNING) << "Loopback device must be an input device";
    return E_FAIL;
  }

  HRESULT hr = S_OK;
  ComPtr<IMMDevice> device(CreateDeviceByID(device_id, is_output_device, &hr));
  if (!device.Get()) {
    return hr;
  }

  ComPtr<IAudioClient> client(CreateClientInternal(device.Get(), &hr));
  if (!client.Get()) {
    return hr;
  }

  bool attempt_audio_offload =
      is_offload_stream && EnableOffloadForClient(client.Get());

  hr = GetPreferredAudioParametersInternal(client.Get(), is_output_device,
                                           params, attempt_audio_offload);
  if (FAILED(hr) || is_output_device || !params->IsValid()) {
    return hr;
  }

  // The following functionality is only for input devices.
  CHECK(!is_output_device);

  // TODO(dalecurtis): Old code rewrote != 1 channels to stereo, do we still
  // need to do the same thing?
  if (params->channels() != 1 &&
      params->channel_layout() != CHANNEL_LAYOUT_DISCRETE) {
    DLOG(WARNING)
        << "Replacing existing audio parameter with predefined version";
    params->Reset(params->format(), media::ChannelLayoutConfig::Stereo(),
                  params->sample_rate(), params->frames_per_buffer());
  }

  // The EnforceSystemEchoCancellation flag must be set to support audio
  // effects.
  if (!media::IsSystemEchoCancellationEnforced()) {
    return hr;
  }

  // Modify the effect mask if the device supports the echo canceller audio
  // effect. The mask can contain any combination of ECHO_CANCELLER and
  // NOISE_SUPPRESSION or AUTOMATIC_GAIN_CONTROL. Note that some devices
  // support only NOISE_SUPPRESSION and/or AUTOMATIC_GAIN_CONTROL and not
  // ECHO_CANCELLER. The effect mask in `params` will be set to NO_EFFECTS in
  // this case to reflect that the AEC effect is not supported.
  if (!AudioDeviceDescription::IsLoopbackDevice(device_id)) {
    if (InitializeCommunicationsCaptureClient(client.Get())) {
      auto [effects, aec_is_supported] =
          GetVoiceProcessingEffectsAndCheckForAEC(client.Get());
      if (aec_is_supported) {
        DVLOG(1) << "ECHO_CANCELLER is supported by the device";
        params->set_effects(params->effects() | effects);
      }
      DVLOG(1) << params->AsHumanReadableString();
    } else {
      // An initialized audio client is required to check for effect support.
      LOG(ERROR) << "Failed to enumerate system effects";
    }
  }

  return hr;
}

ChannelConfig CoreAudioUtil::GetChannelConfig(const std::string& device_id,
                                              EDataFlow data_flow) {
  const ERole role = AudioDeviceDescription::IsCommunicationsDevice(device_id)
                         ? eCommunications
                         : eConsole;
  ComPtr<IAudioClient> client(CreateClient(device_id, data_flow, role));

  WAVEFORMATEXTENSIBLE mix_format;
  if (!client.Get() ||
      FAILED(GetSharedModeMixFormat(client.Get(), &mix_format)))
    return 0;
  WaveFormatWrapper format(&mix_format);
  if (!format.IsExtensible()) {
    // A format descriptor from WAVEFORMATEX only supports mono and stereo.
    DCHECK_LE(format->nChannels, 2);
    DVLOG(1) << "Format does not contain any channel mask."
             << " Guessing layout by channel count: " << std::dec
             << format->nChannels;
    return GuessChannelConfig(format->nChannels);
  }

  return static_cast<ChannelConfig>(format.GetExtensible()->dwChannelMask);
}

HRESULT CoreAudioUtil::SharedModeInitialize(IAudioClient* client,
                                            const WaveFormatWrapper format,
                                            HANDLE event_handle,
                                            uint32_t requested_buffer_size,
                                            uint32_t* endpoint_buffer_size,
                                            const GUID* session_guid,
                                            bool is_offload_stream) {
  SCOPED_UMA_HISTOGRAM_TIMER(
      "Media.Audio.Win.CoreAudioUtil.SharedModeInitializeTime");
  TRACE_EVENT0("audio", "CoreAudioUtil::SharedModeInitialize");
  // Use default flags (i.e, dont set AUDCLNT_STREAMFLAGS_NOPERSIST) to
  // ensure that the volume level and muting state for a rendering session
  // are persistent across system restarts. The volume level and muting
  // state for a capture session are never persistent.
  DWORD stream_flags = 0;

  // Enable event-driven streaming if a valid event handle is provided.
  // After the stream starts, the audio engine will signal the event handle
  // to notify the client each time a buffer becomes ready to process.
  // Event-driven buffering is supported for both rendering and capturing.
  // Both shared-mode and exclusive-mode streams can use event-driven
  // buffering.
  bool use_event =
      (event_handle != NULL && event_handle != INVALID_HANDLE_VALUE);
  if (use_event)
    stream_flags |= AUDCLNT_STREAMFLAGS_EVENTCALLBACK;
  DVLOG(2) << "stream_flags: 0x" << std::hex << stream_flags;

  HRESULT hr;

  if (is_offload_stream) {
    {
      TRACE_EVENT0("audio", "IAudioClient::Initialize_Offload");
      hr = client->Initialize(AUDCLNT_SHAREMODE_SHARED, stream_flags,
                              GetOffloadBufferTimeIn100Ns(), 0, format,
                              session_guid);
    }
    // Typically GetBufferSize() must be called after successfully
    // initialization. AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED is the only case we
    // allow with an initialization failure.
    if (hr == AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED) {
      uint32_t buffer_size_in_frames = 0;
      {
        TRACE_EVENT0("audio", "IAudioClient::GetBufferSize_Offload");
        hr = client->GetBufferSize(&buffer_size_in_frames);
      }
      if (SUCCEEDED(hr)) {
        REFERENCE_TIME buffer_duration_in_ns = BufferSizeInFramesToTimeDelta(
            buffer_size_in_frames, format->nAvgBytesPerSec,
            format->nBlockAlign);
        {
          TRACE_EVENT0("audio", "IAudioClient::Initialize_OffloadAligned");
          hr = client->Initialize(AUDCLNT_SHAREMODE_SHARED, stream_flags,
                                  buffer_duration_in_ns, 0, format,
                                  session_guid);
        }
      }
    }
  } else if (requested_buffer_size > 0) {
    // Try to obtain an IAudioClient3 interface from the IAudioClient object.
    // Use ComPtr::As for doing QueryInterface calls on COM objects.
    ComPtr<IAudioClient> audio_client(client);
    ComPtr<IAudioClient3> audio_client_3;
    {
      TRACE_EVENT0("audio", "IAudioClient::QueryInterface_IAudioClient3");
      hr = audio_client.As(&audio_client_3);
    }
    if (FAILED(hr)) {
      DVLOG(1) << "Failed to obtain IAudioClient3 interface: " << std::hex
               << hr;
      return hr;
    }
    // Initialize a low-latency client using IAudioClient3.
    {
      TRACE_EVENT0("audio", "IAudioClient3::InitializeSharedAudioStream");
      hr = audio_client_3->InitializeSharedAudioStream(
          stream_flags, requested_buffer_size, format, session_guid);
    }
    if (FAILED(hr)) {
      DVLOG(1) << "IAudioClient3::InitializeSharedAudioStream: " << std::hex
               << hr;
      return hr;
    }
  } else {
    // Initialize the shared mode client for minimal delay.
    {
      TRACE_EVENT0("audio", "IAudioClient::Initialize");
      hr = client->Initialize(AUDCLNT_SHAREMODE_SHARED, stream_flags, 0, 0,
                              format, session_guid);
    }
    if (FAILED(hr)) {
      DVLOG(1) << "IAudioClient::Initialize: " << std::hex << hr;
      return hr;
    }
  }

  if (use_event) {
    {
      TRACE_EVENT0("audio", "IAudioClient::SetEventHandle");
      hr = client->SetEventHandle(event_handle);
    }
    if (FAILED(hr)) {
      DVLOG(1) << "IAudioClient::SetEventHandle: " << std::hex << hr;
      return hr;
    }
  }

  UINT32 buffer_size_in_frames = 0;
  {
    TRACE_EVENT0("audio", "IAudioClient::GetBufferSize");
    hr = client->GetBufferSize(&buffer_size_in_frames);
  }
  if (FAILED(hr)) {
    DVLOG(1) << "IAudioClient::GetBufferSize: " << std::hex << hr;
    return hr;
  }

  *endpoint_buffer_size = buffer_size_in_frames;
  DVLOG(2) << "endpoint buffer size: " << buffer_size_in_frames;

  // TODO(henrika): utilize when delay measurements are added.
  REFERENCE_TIME latency = 0;
  {
    TRACE_EVENT0("audio", "IAudioClient::GetStreamLatency");
    hr = client->GetStreamLatency(&latency);
  }
  DVLOG(2) << "stream latency: "
           << ReferenceTimeToTimeDelta(latency).InMillisecondsF() << " [ms]";
  return hr;
}

bool CoreAudioUtil::IsClientInitialized(IAudioClient* client) {
  if (!client) {
    return false;
  }

  // All calls to this method will fail with the error AUDCLNT_E_NOT_INITIALIZED
  // until the client initializes the audio stream by successfully calling the
  // IAudioClient::Initialize method. Hence, this is a cheap trick to check if
  // an audio client is initialized or not.
  REFERENCE_TIME latency;
  HRESULT hr = client->GetStreamLatency(&latency);

  return SUCCEEDED(hr);
}

ComPtr<IAudioRenderClient> CoreAudioUtil::CreateRenderClient(
    IAudioClient* client) {
  HRESULT hr = S_OK;
  return CreateRenderClient(client, hr);
}

ComPtr<IAudioRenderClient> CoreAudioUtil::CreateRenderClient(
    IAudioClient* client,
    HRESULT& hr_out) {
  TRACE_EVENT0("audio", "CoreAudioUtil::CreateRenderClient");
  // Get access to the IAudioRenderClient interface. This interface
  // enables us to write output data to a rendering endpoint buffer.
  ComPtr<IAudioRenderClient> audio_render_client;
  HRESULT hr = client->GetService(IID_PPV_ARGS(&audio_render_client));
  hr_out = hr;
  if (FAILED(hr)) {
    DVLOG(1) << "IAudioClient::GetService: " << std::hex << hr;
    return ComPtr<IAudioRenderClient>();
  }
  return audio_render_client;
}

ComPtr<IAudioCaptureClient> CoreAudioUtil::CreateCaptureClient(
    IAudioClient* client) {
  HRESULT hr = S_OK;
  return CreateCaptureClient(client, hr);
}

ComPtr<IAudioCaptureClient> CoreAudioUtil::CreateCaptureClient(
    IAudioClient* client,
    HRESULT& hr_out) {
  // Get access to the IAudioCaptureClient interface. This interface
  // enables us to read input data from a capturing endpoint buffer.
  ComPtr<IAudioCaptureClient> audio_capture_client;
  HRESULT hr = client->GetService(IID_PPV_ARGS(&audio_capture_client));
  hr_out = hr;
  if (FAILED(hr)) {
    DVLOG(1) << "IAudioClient::GetService: " << std::hex << hr;
    return ComPtr<IAudioCaptureClient>();
  }
  return audio_capture_client;
}

HRESULT CoreAudioUtil::FillRenderEndpointBufferWithSilence(
    IAudioClient* client,
    IAudioRenderClient* render_client) {
  UINT32 endpoint_buffer_size = 0;
  HRESULT hr = client->GetBufferSize(&endpoint_buffer_size);
  if (FAILED(hr)) {
    PLOG(ERROR) << "Failed IAudioClient::GetBufferSize()";
    return hr;
  }

  UINT32 num_queued_frames = 0;
  hr = client->GetCurrentPadding(&num_queued_frames);
  if (FAILED(hr)) {
    PLOG(ERROR) << "Failed IAudioClient::GetCurrentPadding()";
    return hr;
  }

  BYTE* data = NULL;
  int num_frames_to_fill = endpoint_buffer_size - num_queued_frames;
  hr = render_client->GetBuffer(num_frames_to_fill, &data);
  if (FAILED(hr)) {
    PLOG(ERROR) << "Failed IAudioRenderClient::GetBuffer()";
    return hr;
  }

  // Using the AUDCLNT_BUFFERFLAGS_SILENT flag eliminates the need to
  // explicitly write silence data to the rendering buffer.
  hr = render_client->ReleaseBuffer(num_frames_to_fill,
                                    AUDCLNT_BUFFERFLAGS_SILENT);
  if (FAILED(hr)) {
    PLOG(ERROR) << "Failed IAudioRenderClient::ReleaseBuffer()";
    return hr;
  }

  return S_OK;
}

// static
bool CoreAudioUtil::EnableOffloadForClient(IAudioClient* client) {
  ComPtr<IAudioClient> audio_client(client);
  ComPtr<IAudioClient2> audio_client2;

  if (!CoreAudioUtil::IsAudioOffloadSupported(audio_client.Get())) {
    return false;
  }
  HRESULT hr = audio_client.As(&audio_client2);
  if (SUCCEEDED(hr)) {
    AudioClientProperties client_properties = {0};
    client_properties.cbSize = sizeof(AudioClientProperties);
    client_properties.bIsOffload = true;
    client_properties.eCategory = AudioCategory_Media;

    hr = audio_client2->SetClientProperties(&client_properties);
    if (SUCCEEDED(hr)) {
      DVLOG(1) << "Enabled audio offload on the client.";
      return true;
    }
  }

  return false;
}

// static
bool CoreAudioUtil::IsAudioOffloadSupported(IAudioClient* client) {
  if (!base::FeatureList::IsEnabled(kAudioOffload)) {
    return false;
  } else if (!client) {
    // If no client is specified, we can't determine if offload is supported,
    // thus allow audio offload to be attempted, the real capability will be
    // checked when the audio client is created.
    return true;
  }

  ComPtr<IAudioClient> audio_client(client);
  ComPtr<IAudioClient2> audio_client2;
  BOOL is_offloadable = FALSE;

  HRESULT hr = audio_client.As(&audio_client2);
  if (SUCCEEDED(hr)) {
    hr = audio_client2->IsOffloadCapable(AudioCategory_Media, &is_offloadable);
    return (hr == S_OK && (is_offloadable == TRUE));
  }

  return false;
}

// static
bool CoreAudioUtil::EnableCommunicationsAudioCategoryForClient(
    IAudioClient* client) {
  ComPtr<IAudioClient> audio_client(client);
  ComPtr<IAudioClient2> audio_client2;

  HRESULT hr = audio_client.As(&audio_client2);
  if (SUCCEEDED(hr)) {
    AudioClientProperties client_properties = {};
    client_properties.cbSize = sizeof(AudioClientProperties);
    client_properties.eCategory = AudioCategory_Communications;

    hr = audio_client2->SetClientProperties(&client_properties);
    if (SUCCEEDED(hr)) {
      DVLOG(1) << "Enabled eCommunications audio category on the client ";
      return true;
    }
  }

  return false;
}

// Uses the IAudioEffectsManager::GetAudioEffects() API to enumerate all
// supported audio effects and at the same time checks if the AEC effect is
// available. Requires an initialized client in communications mode which
// means that IAudioClient2::SetClientProperties() has been called with the
// category set to AudioCategory_Communications.
std::pair<int, bool> CoreAudioUtil::GetVoiceProcessingEffectsAndCheckForAEC(
    IAudioClient* client) {
  TRACE_EVENT("audio",
              "CoreAudioUtil::GetVoiceProcessingEffectsAndCheckForAEC");

  auto false_and_log_no_effect = [&]() -> std::pair<int, bool> {
    LogVoiceProcessingEffectsHistogram(AudioParameters::NO_EFFECTS);
    return {{}, false};
  };

  // This check guarantees that the client has been initialized since that is
  // required by the IAudioEffectsManager to function. The effects manager will
  // only be able to report supported effects if the client is in communications
  // mode since that is the only mode where effects are enabled. There is
  // however no means to check this in advance and all we can do is to trust
  // that the caller has done these preparation. If not, no effects will be
  // found even if the device in fact does support them.
  if (!CoreAudioUtil::IsClientInitialized(client)) {
    return false_and_log_no_effect();
  }

  // IAudioEffectsManager requires build 22000 or higher and an initialized
  // audio client.
  ComPtr<IAudioEffectsManager> audio_effects_manager;
  if (FAILED(client->GetService(IID_PPV_ARGS(&audio_effects_manager)))) {
    return false_and_log_no_effect();
  }

  // Get the current list of audio effects for the associated audio stream.
  ScopedCoMem<AUDIO_EFFECT> audio_effects;
  UINT32 num_effects = 0;
  if (FAILED(audio_effects_manager->GetAudioEffects(&audio_effects,
                                                    &num_effects))) {
    return false_and_log_no_effect();
  }

  if (!audio_effects || num_effects == 0) {
    return false_and_log_no_effect();
  }

  // Check for AEC support among the supported effects and build up the effect
  // mask for supported voice processing effects (AEC, NS, AGC, and DNS (Deep
  // NS)). Note that other audio effects such as beamforming, equalizer etc. are
  // all excluded here since they are not part of the supported effects in
  // AudioParameters::PlatformEffectsMask.
  int effects = AudioParameters::NO_EFFECTS;
  bool echo_cancellation_is_available = false;
  for (size_t i = 0; i < num_effects; i++) {
    int effect = AudioParameters::NO_EFFECTS;
    // SAFETY: This operation is safe since we check both the raw pointer and
    // the number of elements above. The structure of GetAudioEffects forces us
    // to use unsafe buffers here.
    AUDIO_EFFECT audio_effect = UNSAFE_BUFFERS(audio_effects[i]);
    if (audio_effect.id == AUDIO_EFFECT_TYPE_ACOUSTIC_ECHO_CANCELLATION) {
      echo_cancellation_is_available =
          (audio_effect.state == AUDIO_EFFECT_STATE_ON);
      effect = AudioParameters::ECHO_CANCELLER;
    } else if (audio_effect.id == AUDIO_EFFECT_TYPE_NOISE_SUPPRESSION &&
               audio_effect.state == AUDIO_EFFECT_STATE_ON) {
      effect = AudioParameters::NOISE_SUPPRESSION;
    } else if (audio_effect.id == AUDIO_EFFECT_TYPE_DEEP_NOISE_SUPPRESSION &&
               audio_effect.state == AUDIO_EFFECT_STATE_ON) {
      effect = AudioParameters::DEEP_NOISE_SUPPRESSION;
    } else if (audio_effect.id == AUDIO_EFFECT_TYPE_AUTOMATIC_GAIN_CONTROL &&
               audio_effect.state == AUDIO_EFFECT_STATE_ON) {
      effect = AudioParameters::AUTOMATIC_GAIN_CONTROL;
    }
    if (effect) {
      LogVoiceProcessingEffectsHistogram(effect);
      effects |= effect;
    }
  }

  return std::make_pair(effects, echo_cancellation_is_available);
}

}  // namespace media
