text: /*
   *  Copyright (c) 2012 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.
   */
  
  #pragma warning(disable : 4995)  // name was marked as #pragma deprecated
  
  #if (_MSC_VER >= 1310) && (_MSC_VER < 1400)
  // Reports the major and minor versions of the compiler.
  // For example, 1310 for Microsoft Visual C++ .NET 2003. 1310 represents version
  // 13 and a 1.0 point release. The Visual C++ 2005 compiler version is 1400.
  // Type cl /? at the command line to see the major and minor versions of your
  // compiler along with the build number.
  #pragma message(">> INFO: Windows Core Audio is not supported in VS 2003")
  #endif
  
  #include "modules/audio_device/audio_device_config.h"
  
  #ifdef WEBRTC_WINDOWS_CORE_AUDIO_BUILD
  
  #include "modules/audio_device/win/audio_device_core_win.h"
  
  #include <assert.h>
  #include <string.h>
  
  #include <comdef.h>
  #include <dmo.h>
  #include <functiondiscoverykeys_devpkey.h>
  #include <mmsystem.h>
  #include <strsafe.h>
  #include <uuids.h>
  #include <windows.h>
  
  #include <iomanip>
  
  #include "rtc_base/logging.h"
  #include "rtc_base/platform_thread.h"
  #include "rtc_base/string_utils.h"
  #include "rtc_base/thread_annotations.h"
  #include "system_wrappers/include/sleep.h"
  
  // Macro that calls a COM method returning HRESULT value.
  #define EXIT_ON_ERROR(hres) \
    do {                      \
      if (FAILED(hres))       \
        goto Exit;            \
    } while (0)
  
  // Macro that continues to a COM error.
  #define CONTINUE_ON_ERROR(hres) \
    do {                          \
      if (FAILED(hres))           \
        goto Next;                \
    } while (0)
  
  // Macro that releases a COM object if not NULL.
  #define SAFE_RELEASE(p) \
    do {                  \
      if ((p)) {          \
        (p)->Release();   \
        (p) = NULL;       \
      }                   \
    } while (0)
  
  #define ROUND(x) ((x) >= 0 ? (int)((x) + 0.5) : (int)((x)-0.5))
  
  // REFERENCE_TIME time units per millisecond
  #define REFTIMES_PER_MILLISEC 10000
  
  typedef struct tagTHREADNAME_INFO {
    DWORD dwType;      // must be 0x1000
    LPCSTR szName;     // pointer to name (in user addr space)
    DWORD dwThreadID;  // thread ID (-1=caller thread)
    DWORD dwFlags;     // reserved for future use, must be zero
  } THREADNAME_INFO;
  
  namespace webrtc {
  namespace {
  
  enum { COM_THREADING_MODEL = COINIT_MULTITHREADED };
  
  enum { kAecCaptureStreamIndex = 0, kAecRenderStreamIndex = 1 };
  
  // An implementation of IMediaBuffer, as required for
  // IMediaObject::ProcessOutput(). After consuming data provided by
  // ProcessOutput(), call SetLength() to update the buffer availability.
  //
  // Example implementation:
  // http://msdn.microsoft.com/en-us/library/dd376684(v=vs.85).aspx
  class MediaBufferImpl final : public IMediaBuffer {
   public:
    explicit MediaBufferImpl(DWORD maxLength)
        : _data(new BYTE[maxLength]),
          _length(0),
          _maxLength(maxLength),
          _refCount(0) {}
  
    // IMediaBuffer methods.
    STDMETHOD(GetBufferAndLength(BYTE** ppBuffer, DWORD* pcbLength)) {
      if (!ppBuffer || !pcbLength) {
        return E_POINTER;
      }
  
      *ppBuffer = _data;
      *pcbLength = _length;
  
      return S_OK;
    }
  
    STDMETHOD(GetMaxLength(DWORD* pcbMaxLength)) {
      if (!pcbMaxLength) {
        return E_POINTER;
      }
  
      *pcbMaxLength = _maxLength;
      return S_OK;
    }
  
    STDMETHOD(SetLength(DWORD cbLength)) {
      if (cbLength > _maxLength) {
        return E_INVALIDARG;
      }
  
      _length = cbLength;
      return S_OK;
    }
  
    // IUnknown methods.
    STDMETHOD_(ULONG, AddRef()) { return InterlockedIncrement(&_refCount); }
  
    STDMETHOD(QueryInterface(REFIID riid, void** ppv)) {
      if (!ppv) {
        return E_POINTER;
      } else if (riid != IID_IMediaBuffer && riid != IID_IUnknown) {
        return E_NOINTERFACE;
      }
  
      *ppv = static_cast<IMediaBuffer*>(this);
      AddRef();
      return S_OK;
    }
  
    STDMETHOD_(ULONG, Release()) {
      LONG refCount = InterlockedDecrement(&_refCount);
      if (refCount == 0) {
        delete this;
      }
  
      return refCount;
    }
  
   private:
    ~MediaBufferImpl() { delete[] _data; }
  
    BYTE* _data;
    DWORD _length;
    const DWORD _maxLength;
    LONG _refCount;
  };
  }  // namespace
  
  // ============================================================================
  //                              Static Methods
  // ============================================================================
  
  // ----------------------------------------------------------------------------
  //  CoreAudioIsSupported
  // ----------------------------------------------------------------------------
  
  bool AudioDeviceWindowsCore::CoreAudioIsSupported() {
    RTC_LOG(LS_VERBOSE) << __FUNCTION__;
  
    bool MMDeviceIsAvailable(false);
    bool coreAudioIsSupported(false);
  
    HRESULT hr(S_OK);
    wchar_t buf[MAXERRORLENGTH];
    wchar_t errorText[MAXERRORLENGTH];
  
    // 1) Check if Windows version is Vista SP1 or later.
    //
    // CoreAudio is only available on Vista SP1 and later.
    //
    OSVERSIONINFOEX osvi;
    DWORDLONG dwlConditionMask = 0;
    int op = VER_LESS_EQUAL;
  
    // Initialize the OSVERSIONINFOEX structure.
    ZeroMemory(&osvi, sizeof(OSVERSIONINFOEX));
    osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);
    osvi.dwMajorVersion = 6;
    osvi.dwMinorVersion = 0;
    osvi.wServicePackMajor = 0;
    osvi.wServicePackMinor = 0;
    osvi.wProductType = VER_NT_WORKSTATION;
  
    // Initialize the condition mask.
    VER_SET_CONDITION(dwlConditionMask, VER_MAJORVERSION, op);
    VER_SET_CONDITION(dwlConditionMask, VER_MINORVERSION, op);
    VER_SET_CONDITION(dwlConditionMask, VER_SERVICEPACKMAJOR, op);
    VER_SET_CONDITION(dwlConditionMask, VER_SERVICEPACKMINOR, op);
    VER_SET_CONDITION(dwlConditionMask, VER_PRODUCT_TYPE, VER_EQUAL);
  
    DWORD dwTypeMask = VER_MAJORVERSION | VER_MINORVERSION |
                       VER_SERVICEPACKMAJOR | VER_SERVICEPACKMINOR |
                       VER_PRODUCT_TYPE;
  
    // Perform the test.
    BOOL isVistaRTMorXP = VerifyVersionInfo(&osvi, dwTypeMask, dwlConditionMask);
    if (isVistaRTMorXP != 0) {
      RTC_LOG(LS_VERBOSE)
          << "*** Windows Core Audio is only supported on Vista SP1 or later";
      return false;
    }
  
    // 2) Initializes the COM library for use by the calling thread.
  
    // The COM init wrapper sets the thread's concurrency model to MTA,
    // and creates a new apartment for the thread if one is required. The
    // wrapper also ensures that each call to CoInitializeEx is balanced
    // by a corresponding call to CoUninitialize.
    //
    ScopedCOMInitializer comInit(ScopedCOMInitializer::kMTA);
    if (!comInit.succeeded()) {
      // Things will work even if an STA thread is calling this method but we
      // want to ensure that MTA is used and therefore return false here.
      return false;
    }
  
    // 3) Check if the MMDevice API is available.
    //
    // The Windows Multimedia Device (MMDevice) API enables audio clients to
    // discover audio endpoint devices, determine their capabilities, and create
    // driver instances for those devices.
    // Header file Mmdeviceapi.h defines the interfaces in the MMDevice API.
    // The MMDevice API consists of several interfaces. The first of these is the
    // IMMDeviceEnumerator interface. To access the interfaces in the MMDevice
    // API, a client obtains a reference to the IMMDeviceEnumerator interface of a
    // device-enumerator object by calling the CoCreateInstance function.
    //
    // Through the IMMDeviceEnumerator interface, the client can obtain references
    // to the other interfaces in the MMDevice API. The MMDevice API implements
    // the following interfaces:
    //
    // IMMDevice            Represents an audio device.
    // IMMDeviceCollection  Represents a collection of audio devices.
    // IMMDeviceEnumerator  Provides methods for enumerating audio devices.
    // IMMEndpoint          Represents an audio endpoint device.
    //
    IMMDeviceEnumerator* pIMMD(NULL);
    const CLSID CLSID_MMDeviceEnumerator = __uuidof(MMDeviceEnumerator);
    const IID IID_IMMDeviceEnumerator = __uuidof(IMMDeviceEnumerator);
  
    hr = CoCreateInstance(
        CLSID_MMDeviceEnumerator,  // GUID value of MMDeviceEnumerator coclass
        NULL, CLSCTX_ALL,
        IID_IMMDeviceEnumerator,  // GUID value of the IMMDeviceEnumerator
                                  // interface
        (void**)&pIMMD);
  
    if (FAILED(hr)) {
      RTC_LOG(LS_ERROR) << "AudioDeviceWindowsCore::CoreAudioIsSupported()"
                           " Failed to create the required COM object (hr="
                        << hr << ")";
      RTC_LOG(LS_VERBOSE) << "AudioDeviceWindowsCore::CoreAudioIsSupported()"
                             " CoCreateInstance(MMDeviceEnumerator) failed (hr="
                          << hr << ")";
  
      const DWORD dwFlags =
          FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS;
      const DWORD dwLangID = MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US);
  
      // Gets the system's human readable message string for this HRESULT.
      // All error message in English by default.
      DWORD messageLength = ::FormatMessageW(dwFlags, 0, hr, dwLangID, errorText,
                                             MAXERRORLENGTH, NULL);
  
      assert(messageLength <= MAXERRORLENGTH);
  
      // Trims tailing white space (FormatMessage() leaves a trailing cr-lf.).
      for (; messageLength && ::isspace(errorText[messageLength - 1]);
           --messageLength) {
        errorText[messageLength - 1] = '\0';
      }
  
      StringCchPrintfW(buf, MAXERRORLENGTH, L"Error details: ");
      StringCchCatW(buf, MAXERRORLENGTH, errorText);
      RTC_LOG(LS_VERBOSE) << buf;
    } else {
      MMDeviceIsAvailable = true;
      RTC_LOG(LS_VERBOSE)
          << "AudioDeviceWindowsCore::CoreAudioIsSupported()"
             " CoCreateInstance(MMDeviceEnumerator) succeeded (hr="
          << hr << ")";
      SAFE_RELEASE(pIMMD);
    }
  
    // 4) Verify that we can create and initialize our Core Audio class.
    //
    if (MMDeviceIsAvailable) {
      coreAudioIsSupported = false;
  
      AudioDeviceWindowsCore* p = new (std::nothrow) AudioDeviceWindowsCore();
      if (p == NULL) {
        return false;
      }
  
      int ok(0);
  
      if (p->Init() != InitStatus::OK) {
        ok |= -1;
      }
  
      ok |= p->Terminate();
  
      if (ok == 0) {
        coreAudioIsSupported = true;
      }
  
      delete p;
    }
  
    if (coreAudioIsSupported) {
      RTC_LOG(LS_VERBOSE) << "*** Windows Core Audio is supported ***";
    } else {
      RTC_LOG(LS_VERBOSE) << "*** Windows Core Audio is NOT supported";
    }
  
    return (coreAudioIsSupported);
  }
  
  // ============================================================================
  //                            Construction & Destruction
  // ============================================================================
  
  // ----------------------------------------------------------------------------
  //  AudioDeviceWindowsCore() - ctor
  // ----------------------------------------------------------------------------
  
  AudioDeviceWindowsCore::AudioDeviceWindowsCore()
      : _avrtLibrary(NULL),
        _winSupportAvrt(false),
        _comInit(ScopedCOMInitializer::kMTA),
        _ptrAudioBuffer(NULL),
        _ptrEnumerator(NULL),
        _ptrRenderCollection(NULL),
        _ptrCaptureCollection(NULL),
        _ptrDeviceOut(NULL),
        _ptrDeviceIn(NULL),
        _ptrClientOut(NULL),
        _ptrClientIn(NULL),
        _ptrRenderClient(NULL),
        _ptrCaptureClient(NULL),
        _ptrCaptureVolume(NULL),
        _ptrRenderSimpleVolume(NULL),
        _dmo(NULL),
        _mediaBuffer(NULL),
        _builtInAecEnabled(false),
        _hRenderSamplesReadyEvent(NULL),
        _hPlayThread(NULL),
        _hRenderStartedEvent(NULL),
        _hShutdownRenderEvent(NULL),
        _hCaptureSamplesReadyEvent(NULL),
        _hRecThread(NULL),
        _hCaptureStartedEvent(NULL),
        _hShutdownCaptureEvent(NULL),
        _hMmTask(NULL),
        _playAudioFrameSize(0),
        _playSampleRate(0),
        _playBlockSize(0),
        _playChannels(2),
        _sndCardPlayDelay(0),
        _writtenSamples(0),
        _readSamples(0),
        _recAudioFrameSize(0),
        _recSampleRate(0),
        _recBlockSize(0),
        _recChannels(2),
        _initialized(false),
        _recording(false),
        _playing(false),
        _recIsInitialized(false),
        _playIsInitialized(false),
        _speakerIsInitialized(false),
        _microphoneIsInitialized(false),
        _usingInputDeviceIndex(false),
        _usingOutputDeviceIndex(false),
        _inputDevice(AudioDeviceModule::kDefaultDevice),
        _outputDevice(AudioDeviceModule::kDefaultDevice),
        _inputDeviceIndex(0),
        _outputDeviceIndex(0) {
    RTC_LOG(LS_INFO) << __FUNCTION__ << " created";
    assert(_comInit.succeeded());
  
    // Try to load the Avrt DLL
    if (!_avrtLibrary) {
      // Get handle to the Avrt DLL module.
      _avrtLibrary = LoadLibrary(TEXT("Avrt.dll"));
      if (_avrtLibrary) {
        // Handle is valid (should only happen if OS larger than vista & win7).
        // Try to get the function addresses.
        RTC_LOG(LS_VERBOSE) << "AudioDeviceWindowsCore::AudioDeviceWindowsCore()"
                               " The Avrt DLL module is now loaded";
  
        _PAvRevertMmThreadCharacteristics =
            (PAvRevertMmThreadCharacteristics)GetProcAddress(
                _avrtLibrary, "AvRevertMmThreadCharacteristics");
        _PAvSetMmThreadCharacteristicsA =
            (PAvSetMmThreadCharacteristicsA)GetProcAddress(
                _avrtLibrary, "AvSetMmThreadCharacteristicsA");
        _PAvSetMmThreadPriority = (PAvSetMmThreadPriority)GetProcAddress(
            _avrtLibrary, "AvSetMmThreadPriority");
  
        if (_PAvRevertMmThreadCharacteristics &&
            _PAvSetMmThreadCharacteristicsA && _PAvSetMmThreadPriority) {
          RTC_LOG(LS_VERBOSE)
              << "AudioDeviceWindowsCore::AudioDeviceWindowsCore()"
                 " AvRevertMmThreadCharacteristics() is OK";
          RTC_LOG(LS_VERBOSE)
              << "AudioDeviceWindowsCore::AudioDeviceWindowsCore()"
                 " AvSetMmThreadCharacteristicsA() is OK";
          RTC_LOG(LS_VERBOSE)
              << "AudioDeviceWindowsCore::AudioDeviceWindowsCore()"
                 " AvSetMmThreadPriority() is OK";
          _winSupportAvrt = true;
        }
      }
    }
  
    // Create our samples ready events - we want auto reset events that start in
    // the not-signaled state. The state of an auto-reset event object remains
    // signaled until a single waiting thread is released, at which time the
    // system automatically sets the state to nonsignaled. If no threads are
    // waiting, the event object's state remains signaled. (Except for
    // _hShutdownCaptureEvent, which is used to shutdown multiple threads).
    _hRenderSamplesReadyEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
    _hCaptureSamplesReadyEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
    _hShutdownRenderEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
    _hShutdownCaptureEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
    _hRenderStartedEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
    _hCaptureStartedEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
  
    _perfCounterFreq.QuadPart = 1;
    _perfCounterFactor = 0.0;
  
    // list of number of channels to use on recording side
    _recChannelsPrioList[0] = 2;  // stereo is prio 1
    _recChannelsPrioList[1] = 1;  // mono is prio 2
  
  
    // list of number of channels to use on playout side
    _playChannelsPrioList[0] = 2;  // stereo is prio 1
    _playChannelsPrioList[1] = 1;  // mono is prio 2
  
    HRESULT hr;
  
    // We know that this API will work since it has already been verified in
    // CoreAudioIsSupported, hence no need to check for errors here as well.
  
    // Retrive the IMMDeviceEnumerator API (should load the MMDevAPI.dll)
    // TODO(henrika): we should probably move this allocation to Init() instead
    // and deallocate in Terminate() to make the implementation more symmetric.
    CoCreateInstance(__uuidof(MMDeviceEnumerator), NULL, CLSCTX_ALL,
                     __uuidof(IMMDeviceEnumerator),
                     reinterpret_cast<void**>(&_ptrEnumerator));
    assert(NULL != _ptrEnumerator);
  
    // DMO initialization for built-in WASAPI AEC.
    {
      IMediaObject* ptrDMO = NULL;
      hr = CoCreateInstance(CLSID_CWMAudioAEC, NULL, CLSCTX_INPROC_SERVER,
                            IID_IMediaObject, reinterpret_cast<void**>(&ptrDMO));
      if (FAILED(hr) || ptrDMO == NULL) {
        // Since we check that _dmo is non-NULL in EnableBuiltInAEC(), the
        // feature is prevented from being enabled.
        _builtInAecEnabled = false;
        _TraceCOMError(hr);
      }
      _dmo = ptrDMO;
      SAFE_RELEASE(ptrDMO);
    }
  }
  
  // ----------------------------------------------------------------------------
  //  AudioDeviceWindowsCore() - dtor
  // ----------------------------------------------------------------------------
  
  AudioDeviceWindowsCore::~AudioDeviceWindowsCore() {
    RTC_LOG(LS_INFO) << __FUNCTION__ << " destroyed";
  
    Terminate();
  
    // The IMMDeviceEnumerator is created during construction. Must release
    // it here and not in Terminate() since we don't recreate it in Init().
    SAFE_RELEASE(_ptrEnumerator);
  
    _ptrAudioBuffer = NULL;
  
    if (NULL != _hRenderSamplesReadyEvent) {
      CloseHandle(_hRenderSamplesReadyEvent);
      _hRenderSamplesReadyEvent = NULL;
    }
  
    if (NULL != _hCaptureSamplesReadyEvent) {
      CloseHandle(_hCaptureSamplesReadyEvent);
      _hCaptureSamplesReadyEvent = NULL;
    }
  
    if (NULL != _hRenderStartedEvent) {
      CloseHandle(_hRenderStartedEvent);
      _hRenderStartedEvent = NULL;
    }
  
    if (NULL != _hCaptureStartedEvent) {
      CloseHandle(_hCaptureStartedEvent);
      _hCaptureStartedEvent = NULL;
    }
  
    if (NULL != _hShutdownRenderEvent) {
      CloseHandle(_hShutdownRenderEvent);
      _hShutdownRenderEvent = NULL;
    }
  
    if (NULL != _hShutdownCaptureEvent) {
      CloseHandle(_hShutdownCaptureEvent);
      _hShutdownCaptureEvent = NULL;
    }
  
    if (_avrtLibrary) {
      BOOL freeOK = FreeLibrary(_avrtLibrary);
      if (!freeOK) {
        RTC_LOG(LS_WARNING)
            << "AudioDeviceWindowsCore::~AudioDeviceWindowsCore()"
               " failed to free the loaded Avrt DLL module correctly";
      } else {
        RTC_LOG(LS_WARNING) << "AudioDeviceWindowsCore::~AudioDeviceWindowsCore()"
                               " the Avrt DLL module is now unloaded";
      }
    }
  }
  
  // ============================================================================
  //                                     API
  // ============================================================================
  
  // ----------------------------------------------------------------------------
  //  AttachAudioBuffer
  // ----------------------------------------------------------------------------
  
  void AudioDeviceWindowsCore::AttachAudioBuffer(AudioDeviceBuffer* audioBuffer) {
    _ptrAudioBuffer = audioBuffer;
  
    // Inform the AudioBuffer about default settings for this implementation.
    // Set all values to zero here since the actual settings will be done by
    // InitPlayout and InitRecording later.
    _ptrAudioBuffer->SetRecordingSampleRate(0);
    _ptrAudioBuffer->SetPlayoutSampleRate(0);
    _ptrAudioBuffer->SetRecordingChannels(0);
    _ptrAudioBuffer->SetPlayoutChannels(0);
  }
  
  // ----------------------------------------------------------------------------
  //  ActiveAudioLayer
  // ----------------------------------------------------------------------------
  
  int32_t AudioDeviceWindowsCore::ActiveAudioLayer(
      AudioDeviceModule::AudioLayer& audioLayer) const {
    audioLayer = AudioDeviceModule::kWindowsCoreAudio;
    return 0;
  }
  
  // ----------------------------------------------------------------------------
  //  Init
  // ----------------------------------------------------------------------------
  
  AudioDeviceGeneric::InitStatus AudioDeviceWindowsCore::Init() {
    MutexLock lock(&mutex_);
  
    if (_initialized) {
      return InitStatus::OK;
    }
  
    // Enumerate all audio rendering and capturing endpoint devices.
    // Note that, some of these will not be able to select by the user.
    // The complete collection is for internal use only.
    _EnumerateEndpointDevicesAll(eRender);
    _EnumerateEndpointDevicesAll(eCapture);
  
    _initialized = true;
  
    return InitStatus::OK;
  }
  
  // ----------------------------------------------------------------------------
  //  Terminate
  // ----------------------------------------------------------------------------
  
  int32_t AudioDeviceWindowsCore::Terminate() {
    MutexLock lock(&mutex_);
  
    if (!_initialized) {
      return 0;
    }
  
    _initialized = false;
    _speakerIsInitialized = false;
    _microphoneIsInitialized = false;
    _playing = false;
    _recording = false;
  
    SAFE_RELEASE(_ptrRenderCollection);
    SAFE_RELEASE(_ptrCaptureCollection);
    SAFE_RELEASE(_ptrDeviceOut);
    SAFE_RELEASE(_ptrDeviceIn);
    SAFE_RELEASE(_ptrClientOut);
    SAFE_RELEASE(_ptrClientIn);
    SAFE_RELEASE(_ptrRenderClient);
    SAFE_RELEASE(_ptrCaptureClient);
    SAFE_RELEASE(_ptrCaptureVolume);
    SAFE_RELEASE(_ptrRenderSimpleVolume);
  
    return 0;
  }
  
  // ----------------------------------------------------------------------------
  //  Initialized
  // ----------------------------------------------------------------------------
  
  bool AudioDeviceWindowsCore::Initialized() const {
    return (_initialized);
  }
  
  // ----------------------------------------------------------------------------
  //  InitSpeaker
  // ----------------------------------------------------------------------------
  
  int32_t AudioDeviceWindowsCore::InitSpeaker() {
    MutexLock lock(&mutex_);
    return InitSpeakerLocked();
  }
  
  int32_t AudioDeviceWindowsCore::InitSpeakerLocked() {
    if (_playing) {
      return -1;
    }
  
    if (_ptrDeviceOut == NULL) {
      return -1;
    }
  
    if (_usingOutputDeviceIndex) {
      int16_t nDevices = PlayoutDevicesLocked();
      if (_outputDeviceIndex > (nDevices - 1)) {
        RTC_LOG(LS_ERROR) << "current device selection is invalid => unable to"
                             " initialize";
        return -1;
      }
    }
  
    int32_t ret(0);
  
    SAFE_RELEASE(_ptrDeviceOut);
    if (_usingOutputDeviceIndex) {
      // Refresh the selected rendering endpoint device using current index
      ret = _GetListDevice(eRender, _outputDeviceIndex, &_ptrDeviceOut);
    } else {
      ERole role;
      (_outputDevice == AudioDeviceModule::kDefaultDevice)
          ? role = eConsole
          : role = eCommunications;
      // Refresh the selected rendering endpoint device using role
      ret = _GetDefaultDevice(eRender, role, &_ptrDeviceOut);
    }
  
    if (ret != 0 || (_ptrDeviceOut == NULL)) {
      RTC_LOG(LS_ERROR) << "failed to initialize the rendering enpoint device";
      SAFE_RELEASE(_ptrDeviceOut);
      return -1;
    }
  
    IAudioSessionManager* pManager = NULL;
    ret = _ptrDeviceOut->Activate(__uuidof(IAudioSessionManager), CLSCTX_ALL,
                                  NULL, (void**)&pManager);
    if (ret != 0 || pManager == NULL) {
      RTC_LOG(LS_ERROR) << "failed to initialize the render manager";
      SAFE_RELEASE(pManager);
      return -1;
    }
  
    SAFE_RELEASE(_ptrRenderSimpleVolume);
    ret = pManager->GetSimpleAudioVolume(NULL, FALSE, &_ptrRenderSimpleVolume);
    if (ret != 0 || _ptrRenderSimpleVolume == NULL) {
      RTC_LOG(LS_ERROR) << "failed to initialize the render simple volume";
      SAFE_RELEASE(pManager);
      SAFE_RELEASE(_ptrRenderSimpleVolume);
      return -1;
    }
    SAFE_RELEASE(pManager);
  
    _speakerIsInitialized = true;
  
    return 0;
  }
  
  // ----------------------------------------------------------------------------
  //  InitMicrophone
  // ----------------------------------------------------------------------------
  
  int32_t AudioDeviceWindowsCore::InitMicrophone() {
    MutexLock lock(&mutex_);
    return InitMicrophoneLocked();
  }
  
  int32_t AudioDeviceWindowsCore::InitMicrophoneLocked() {
    if (_recording) {
      return -1;
    }
  
    if (_ptrDeviceIn == NULL) {
      return -1;
    }
  
    if (_usingInputDeviceIndex) {
      int16_t nDevices = RecordingDevicesLocked();
      if (_inputDeviceIndex > (nDevices - 1)) {
        RTC_LOG(LS_ERROR) << "current device selection is invalid => unable to"
                             " initialize";
        return -1;
      }
    }
  
    int32_t ret(0);
  
    SAFE_RELEASE(_ptrDeviceIn);
    if (_usingInputDeviceIndex) {
      // Refresh the selected capture endpoint device using current index
      ret = _GetListDevice(eCapture, _inputDeviceIndex, &_ptrDeviceIn);
    } else {
      ERole role;
      (_inputDevice == AudioDeviceModule::kDefaultDevice)
          ? role = eConsole
          : role = eCommunications;
      // Refresh the selected capture endpoint device using role
      ret = _GetDefaultDevice(eCapture, role, &_ptrDeviceIn);
    }
  
    if (ret != 0 || (_ptrDeviceIn == NULL)) {
      RTC_LOG(LS_ERROR) << "failed to initialize the capturing enpoint device";
      SAFE_RELEASE(_ptrDeviceIn);
      return -1;
    }
  
    SAFE_RELEASE(_ptrCaptureVolume);
    ret = _ptrDeviceIn->Activate(__uuidof(IAudioEndpointVolume), CLSCTX_ALL, NULL,
                                 reinterpret_cast<void**>(&_ptrCaptureVolume));
    if (ret != 0 || _ptrCaptureVolume == NULL) {
      RTC_LOG(LS_ERROR) << "failed to initialize the capture volume";
      SAFE_RELEASE(_ptrCaptureVolume);
      return -1;
    }
  
    _microphoneIsInitialized = true;
  
    return 0;
  }
  
  // ----------------------------------------------------------------------------
  //  SpeakerIsInitialized
  // ----------------------------------------------------------------------------
  
  bool AudioDeviceWindowsCore::SpeakerIsInitialized() const {
    return (_speakerIsInitialized);
  }
  
  // ----------------------------------------------------------------------------
  //  MicrophoneIsInitialized
  // ----------------------------------------------------------------------------
  
  bool AudioDeviceWindowsCore::MicrophoneIsInitialized() const {
    return (_microphoneIsInitialized);
  }
  
  // ----------------------------------------------------------------------------
  //  SpeakerVolumeIsAvailable
  // ----------------------------------------------------------------------------
  
  int32_t AudioDeviceWindowsCore::SpeakerVolumeIsAvailable(bool& available) {
    MutexLock lock(&mutex_);
  
    if (_ptrDeviceOut == NULL) {
      return -1;
    }
  
    HRESULT hr = S_OK;
    IAudioSessionManager* pManager = NULL;
    ISimpleAudioVolume* pVolume = NULL;
  
    hr = _ptrDeviceOut->Activate(__uuidof(IAudioSessionManager), CLSCTX_ALL, NULL,
                                 (void**)&pManager);
    EXIT_ON_ERROR(hr);
  
    hr = pManager->GetSimpleAudioVolume(NULL, FALSE, &pVolume);
    EXIT_ON_ERROR(hr);
  
    float volume(0.0f);
    hr = pVolume->GetMasterVolume(&volume);
    if (FAILED(hr)) {
      available = false;
    }
    available = true;
  
    SAFE_RELEASE(pManager);
    SAFE_RELEASE(pVolume);
  
    return 0;
  
  Exit:
    _TraceCOMError(hr);
    SAFE_RELEASE(pManager);
    SAFE_RELEASE(pVolume);
    return -1;
  }
  
  // ----------------------------------------------------------------------------
  //  SetSpeakerVolume
  // ----------------------------------------------------------------------------
  
  int32_t AudioDeviceWindowsCore::SetSpeakerVolume(uint32_t volume) {
    {
      MutexLock lock(&mutex_);
  
      if (!_speakerIsInitialized) {
        return -1;
      }
  
      if (_ptrDeviceOut == NULL) {
        return -1;
      }
    }
  
    if (volume < (uint32_t)MIN_CORE_SPEAKER_VOLUME ||
        volume > (uint32_t)MAX_CORE_SPEAKER_VOLUME) {
      return -1;
    }
  
    HRESULT hr = S_OK;
  
    // scale input volume to valid range (0.0 to 1.0)
    const float fLevel = (float)volume / MAX_CORE_SPEAKER_VOLUME;
    volume_mutex_.Lock();
    hr = _ptrRenderSimpleVolume->SetMasterVolume(fLevel, NULL);
    volume_mutex_.Unlock();
    EXIT_ON_ERROR(hr);
  
    return 0;
  
  Exit:
    _TraceCOMError(hr);
    return -1;
  }
  
  // ----------------------------------------------------------------------------
  //  SpeakerVolume
  // ----------------------------------------------------------------------------
  
  int32_t AudioDeviceWindowsCore::SpeakerVolume(uint32_t& volume) const {
    {
      MutexLock lock(&mutex_);
  
      if (!_speakerIsInitialized) {
        return -1;
      }
  
      if (_ptrDeviceOut == NULL) {
        return -1;
      }
    }
  
    HRESULT hr = S_OK;
    float fLevel(0.0f);
  
    volume_mutex_.Lock();
    hr = _ptrRenderSimpleVolume->GetMasterVolume(&fLevel);
    volume_mutex_.Unlock();
    EXIT_ON_ERROR(hr);
  
    // scale input volume range [0.0,1.0] to valid output range
    volume = static_cast<uint32_t>(fLevel * MAX_CORE_SPEAKER_VOLUME);
  
    return 0;
  
  Exit:
    _TraceCOMError(hr);
    return -1;
  }
  
  // ----------------------------------------------------------------------------
  //  MaxSpeakerVolume
  //
  //  The internal range for Core Audio is 0.0 to 1.0, where 0.0 indicates
  //  silence and 1.0 indicates full volume (no attenuation).
  //  We add our (webrtc-internal) own max level to match the Wave API and
  //  how it is used today in VoE.
  // ----------------------------------------------------------------------------
  
  int32_t AudioDeviceWindowsCore::MaxSpeakerVolume(uint32_t& maxVolume) const {
    if (!_speakerIsInitialized) {
      return -1;
    }
  
    maxVolume = static_cast<uint32_t>(MAX_CORE_SPEAKER_VOLUME);
  
    return 0;
  }
  
  // ----------------------------------------------------------------------------
  //  MinSpeakerVolume
  // ----------------------------------------------------------------------------
  
  int32_t AudioDeviceWindowsCore::MinSpeakerVolume(uint32_t& minVolume) const {
    if (!_speakerIsInitialized) {
      return -1;
    }
  
    minVolume = static_cast<uint32_t>(MIN_CORE_SPEAKER_VOLUME);
  
    return 0;
  }
  
  // ----------------------------------------------------------------------------
  //  SpeakerMuteIsAvailable
  // ----------------------------------------------------------------------------
  
  int32_t AudioDeviceWindowsCore::SpeakerMuteIsAvailable(bool& available) {
    MutexLock lock(&mutex_);
  
    if (_ptrDeviceOut == NULL) {
      return -1;
    }
  
    HRESULT hr = S_OK;
    IAudioEndpointVolume* pVolume = NULL;
  
    // Query the speaker system mute state.
    hr = _ptrDeviceOut->Activate(__uuidof(IAudioEndpointVolume), CLSCTX_ALL, NULL,
                                 reinterpret_cast<void**>(&pVolume));
    EXIT_ON_ERROR(hr);
  
    BOOL mute;
    hr = pVolume->GetMute(&mute);
    if (FAILED(hr))
      available = false;
    else
      available = true;
  
    SAFE_RELEASE(pVolume);
  
    return 0;
  
  Exit:
    _TraceCOMError(hr);
    SAFE_RELEASE(pVolume);
    return -1;
  }
  
  // ----------------------------------------------------------------------------
  //  SetSpeakerMute
  // ----------------------------------------------------------------------------
  
  int32_t AudioDeviceWindowsCore::SetSpeakerMute(bool enable) {
    MutexLock lock(&mutex_);
  
    if (!_speakerIsInitialized) {
      return -1;
    }
  
    if (_ptrDeviceOut == NULL) {
      return -1;
    }
  
    HRESULT hr = S_OK;
    IAudioEndpointVolume* pVolume = NULL;
  
    // Set the speaker system mute state.
    hr = _ptrDeviceOut->Activate(__uuidof(IAudioEndpointVolume), CLSCTX_ALL, NULL,
                                 reinterpret_cast<void**>(&pVolume));
    EXIT_ON_ERROR(hr);
  
    const BOOL mute(enable);
    hr = pVolume->SetMute(mute, NULL);
    EXIT_ON_ERROR(hr);
  
    SAFE_RELEASE(pVolume);
  
    return 0;
  
  Exit:
    _TraceCOMError(hr);
    SAFE_RELEASE(pVolume);
    return -1;
  }
  
  // ----------------------------------------------------------------------------
  //  SpeakerMute
  // ----------------------------------------------------------------------------
  
  int32_t AudioDeviceWindowsCore::SpeakerMute(bool& enabled) const {
    if (!_speakerIsInitialized) {
      return -1;
    }
  
    if (_ptrDeviceOut == NULL) {
      return -1;
    }
  
    HRESULT hr = S_OK;
    IAudioEndpointVolume* pVolume = NULL;
  
    // Query the speaker system mute state.
    hr = _ptrDeviceOut->Activate(__uuidof(IAudioEndpointVolume), CLSCTX_ALL, NULL,
                                 reinterpret_cast<void**>(&pVolume));
    EXIT_ON_ERROR(hr);
  
    BOOL mute;
    hr = pVolume->GetMute(&mute);
    EXIT_ON_ERROR(hr);
  
    enabled = (mute == TRUE) ? true : false;
  
    SAFE_RELEASE(pVolume);
  
    return 0;
  
  Exit:
    _TraceCOMError(hr);
    SAFE_RELEASE(pVolume);
    return -1;
  }
  
  // ----------------------------------------------------------------------------
  //  MicrophoneMuteIsAvailable
  // ----------------------------------------------------------------------------
  
  int32_t AudioDeviceWindowsCore::MicrophoneMuteIsAvailable(bool& available) {
    MutexLock lock(&mutex_);
  
    if (_ptrDeviceIn == NULL) {
      return -1;
    }
  
    HRESULT hr = S_OK;
    IAudioEndpointVolume* pVolume = NULL;
  
    // Query the microphone system mute state.
    hr = _ptrDeviceIn->Activate(__uuidof(IAudioEndpointVolume), CLSCTX_ALL, NULL,
                                reinterpret_cast<void**>(&pVolume));
    EXIT_ON_ERROR(hr);
  
    BOOL mute;
    hr = pVolume->GetMute(&mute);
    if (FAILED(hr))
      available = false;
    else
      available = true;
  
    SAFE_RELEASE(pVolume);
    return 0;
  
  Exit:
    _TraceCOMError(hr);
    SAFE_RELEASE(pVolume);
    return -1;
  }
  
  // ----------------------------------------------------------------------------
  //  SetMicrophoneMute
  // ----------------------------------------------------------------------------
  
  int32_t AudioDeviceWindowsCore::SetMicrophoneMute(bool enable) {
    if (!_microphoneIsInitialized) {
      return -1;
    }
  
    if (_ptrDeviceIn == NULL) {
      return -1;
    }
  
    HRESULT hr = S_OK;
    IAudioEndpointVolume* pVolume = NULL;
  
    // Set the microphone system mute state.
    hr = _ptrDeviceIn->Activate(__uuidof(IAudioEndpointVolume), CLSCTX_ALL, NULL,
                                reinterpret_cast<void**>(&pVolume));
    EXIT_ON_ERROR(hr);
  
    const BOOL mute(enable);
    hr = pVolume->SetMute(mute, NULL);
    EXIT_ON_ERROR(hr);
  
    SAFE_RELEASE(pVolume);
    return 0;
  
  Exit:
    _TraceCOMError(hr);
    SAFE_RELEASE(pVolume);
    return -1;
  }
  
  // ----------------------------------------------------------------------------
  //  MicrophoneMute
  // ----------------------------------------------------------------------------
  
  int32_t AudioDeviceWindowsCore::MicrophoneMute(bool& enabled) const {
    if (!_microphoneIsInitialized) {
      return -1;
    }
  
    HRESULT hr = S_OK;
    IAudioEndpointVolume* pVolume = NULL;
  
    // Query the microphone system mute state.
    hr = _ptrDeviceIn->Activate(__uuidof(IAudioEndpointVolume), CLSCTX_ALL, NULL,
                                reinterpret_cast<void**>(&pVolume));
    EXIT_ON_ERROR(hr);
  
    BOOL mute;
    hr = pVolume->GetMute(&mute);
    EXIT_ON_ERROR(hr);
  
    enabled = (mute == TRUE) ? true : false;
  
    SAFE_RELEASE(pVolume);
    return 0;
  
  Exit:
    _TraceCOMError(hr);
    SAFE_RELEASE(pVolume);
    return -1;
  }
  
  // ----------------------------------------------------------------------------
  //  StereoRecordingIsAvailable
  // ----------------------------------------------------------------------------
  
  int32_t AudioDeviceWindowsCore::StereoRecordingIsAvailable(bool& available) {
    available = true;
    return 0;
  }
  
  // ----------------------------------------------------------------------------
  //  SetStereoRecording
  // ----------------------------------------------------------------------------
  
  int32_t AudioDeviceWindowsCore::SetStereoRecording(bool enable) {
    MutexLock lock(&mutex_);
  
    if (enable) {
      _recChannelsPrioList[0] = 2;  // try stereo first
      _recChannelsPrioList[1] = 1;
      _recChannels = 2;
    } else {
      _recChannelsPrioList[0] = 1;  // try mono first
      _recChannelsPrioList[1] = 2;
      _recChannels = 1;
    }
  
    return 0;
  }
  
  // ----------------------------------------------------------------------------
  //  StereoRecording
  // ----------------------------------------------------------------------------
  
  int32_t AudioDeviceWindowsCore::StereoRecording(bool& enabled) const {
    if (_recChannels == 2)
      enabled = true;
    else
      enabled = false;
  
    return 0;
  }
  
  // ----------------------------------------------------------------------------
  //  StereoPlayoutIsAvailable
  // ----------------------------------------------------------------------------
  
  int32_t AudioDeviceWindowsCore::StereoPlayoutIsAvailable(bool& available) {
    available = true;
    return 0;
  }
  
  // ----------------------------------------------------------------------------
  //  SetStereoPlayout
  // ----------------------------------------------------------------------------
  
  int32_t AudioDeviceWindowsCore::SetStereoPlayout(bool enable) {
    MutexLock lock(&mutex_);
  
    if (enable) {
      _playChannelsPrioList[0] = 2;  // try stereo first
      _playChannelsPrioList[1] = 1;
      _playChannels = 2;
    } else {
      _playChannelsPrioList[0] = 1;  // try mono first
      _playChannelsPrioList[1] = 2;
      _playChannels = 1;
    }
  
    return 0;
  }
  
  // ----------------------------------------------------------------------------
  //  StereoPlayout
  // ----------------------------------------------------------------------------
  
  int32_t AudioDeviceWindowsCore::StereoPlayout(bool& enabled) const {
    if (_playChannels == 2)
      enabled = true;
    else
      enabled = false;
  
    return 0;
  }
  
  // ----------------------------------------------------------------------------
  //  MicrophoneVolumeIsAvailable
  // ----------------------------------------------------------------------------
  
  int32_t AudioDeviceWindowsCore::MicrophoneVolumeIsAvailable(bool& available) {
    MutexLock lock(&mutex_);
  
    if (_ptrDeviceIn == NULL) {
      return -1;
    }
  
    HRESULT hr = S_OK;
    IAudioEndpointVolume* pVolume = NULL;
  
    hr = _ptrDeviceIn->Activate(__uuidof(IAudioEndpointVolume), CLSCTX_ALL, NULL,
                                reinterpret_cast<void**>(&pVolume));
    EXIT_ON_ERROR(hr);
  
    float volume(0.0f);
    hr = pVolume->GetMasterVolumeLevelScalar(&volume);
    if (FAILED(hr)) {
      available = false;
    }
    available = true;
  
    SAFE_RELEASE(pVolume);
    return 0;
  
  Exit:
    _TraceCOMError(hr);
    SAFE_RELEASE(pVolume);
    return -1;
  }
  
  // ----------------------------------------------------------------------------
  //  SetMicrophoneVolume
  // ----------------------------------------------------------------------------
  
  int32_t AudioDeviceWindowsCore::SetMicrophoneVolume(uint32_t volume) {
    RTC_LOG(LS_VERBOSE) << "AudioDeviceWindowsCore::SetMicrophoneVolume(volume="
                        << volume << ")";
  
    {
      MutexLock lock(&mutex_);
  
      if (!_microphoneIsInitialized) {
        return -1;
      }
  
      if (_ptrDeviceIn == NULL) {
        return -1;
      }
    }
  
    if (volume < static_cast<uint32_t>(MIN_CORE_MICROPHONE_VOLUME) ||
        volume > static_cast<uint32_t>(MAX_CORE_MICROPHONE_VOLUME)) {
      return -1;
    }
  
    HRESULT hr = S_OK;
    // scale input volume to valid range (0.0 to 1.0)
    const float fLevel = static_cast<float>(volume) / MAX_CORE_MICROPHONE_VOLUME;
    volume_mutex_.Lock();
    _ptrCaptureVolume->SetMasterVolumeLevelScalar(fLevel, NULL);
    volume_mutex_.Unlock();
    EXIT_ON_ERROR(hr);
  
    return 0;
  
  Exit:
    _TraceCOMError(hr);
    return -1;
  }
  
  // ----------------------------------------------------------------------------
  //  MicrophoneVolume
  // ----------------------------------------------------------------------------
  
  int32_t AudioDeviceWindowsCore::MicrophoneVolume(uint32_t& volume) const {
    {
      MutexLock lock(&mutex_);
  
      if (!_microphoneIsInitialized) {
        return -1;
      }
  
      if (_ptrDeviceIn == NULL) {
        return -1;
      }
    }
  
    HRESULT hr = S_OK;
    float fLevel(0.0f);
    volume = 0;
    volume_mutex_.Lock();
    hr = _ptrCaptureVolume->GetMasterVolumeLevelScalar(&fLevel);
    volume_mutex_.Unlock();
    EXIT_ON_ERROR(hr);
  
    // scale input volume range [0.0,1.0] to valid output range
    volume = static_cast<uint32_t>(fLevel * MAX_CORE_MICROPHONE_VOLUME);
  
    return 0;
  
  Exit:
    _TraceCOMError(hr);
    return -1;
  }
  
  // ----------------------------------------------------------------------------
  //  MaxMicrophoneVolume
  //
  //  The internal range for Core Audio is 0.0 to 1.0, where 0.0 indicates
  //  silence and 1.0 indicates full volume (no attenuation).
  //  We add our (webrtc-internal) own max level to match the Wave API and
  //  how it is used today in VoE.
  // ----------------------------------------------------------------------------
  
  int32_t AudioDeviceWindowsCore::MaxMicrophoneVolume(uint32_t& maxVolume) const {
    RTC_LOG(LS_VERBOSE) << __FUNCTION__;
  
    if (!_microphoneIsInitialized) {
      return -1;
    }
  
    maxVolume = static_cast<uint32_t>(MAX_CORE_MICROPHONE_VOLUME);
  
    return 0;
  }
  
  // ----------------------------------------------------------------------------
  //  MinMicrophoneVolume
  // ----------------------------------------------------------------------------
  
  int32_t AudioDeviceWindowsCore::MinMicrophoneVolume(uint32_t& minVolume) const {
    if (!_microphoneIsInitialized) {
      return -1;
    }
  
    minVolume = static_cast<uint32_t>(MIN_CORE_MICROPHONE_VOLUME);
  
    return 0;
  }
  
  // ----------------------------------------------------------------------------
  //  PlayoutDevices
  // ----------------------------------------------------------------------------
  int16_t AudioDeviceWindowsCore::PlayoutDevices() {
    MutexLock lock(&mutex_);
    return PlayoutDevicesLocked();
  }
  
  int16_t AudioDeviceWindowsCore::PlayoutDevicesLocked() {
    if (_RefreshDeviceList(eRender) != -1) {
      return (_DeviceListCount(eRender));
    }
  
    return -1;
  }
  
  // ----------------------------------------------------------------------------
  //  SetPlayoutDevice I (II)
  // ----------------------------------------------------------------------------
  
  int32_t AudioDeviceWindowsCore::SetPlayoutDevice(uint16_t index) {
    if (_playIsInitialized) {
      return -1;
    }
  
    // Get current number of available rendering endpoint devices and refresh the
    // rendering collection.
    UINT nDevices = PlayoutDevices();
  
    if (index < 0 || index > (nDevices - 1)) {
      RTC_LOG(LS_ERROR) << "device index is out of range [0," << (nDevices - 1)
                        << "]";
      return -1;
    }
  
    MutexLock lock(&mutex_);
  
    HRESULT hr(S_OK);
  
    assert(_ptrRenderCollection != NULL);
  
    //  Select an endpoint rendering device given the specified index
    SAFE_RELEASE(_ptrDeviceOut);
    hr = _ptrRenderCollection->Item(index, &_ptrDeviceOut);
    if (FAILED(hr)) {
      _TraceCOMError(hr);
      SAFE_RELEASE(_ptrDeviceOut);
      return -1;
    }
  
    WCHAR szDeviceName[MAX_PATH];
    const int bufferLen = sizeof(szDeviceName) / sizeof(szDeviceName)[0];
  
    // Get the endpoint device's friendly-name
    if (_GetDeviceName(_ptrDeviceOut, szDeviceName, bufferLen) == 0) {
      RTC_LOG(LS_VERBOSE) << "friendly name: \"" << szDeviceName << "\"";
    }
  
    _usingOutputDeviceIndex = true;
    _outputDeviceIndex = index;
  
    return 0;
  }
  
  // ----------------------------------------------------------------------------
  //  SetPlayoutDevice II (II)
  // ----------------------------------------------------------------------------
  
  int32_t AudioDeviceWindowsCore::SetPlayoutDevice(
      AudioDeviceModule::WindowsDeviceType device) {
    if (_playIsInitialized) {
      return -1;
    }
  
    ERole role(eCommunications);
  
    if (device == AudioDeviceModule::kDefaultDevice) {
      role = eConsole;
    } else if (device == AudioDeviceModule::kDefaultCommunicationDevice) {
      role = eCommunications;
    }
  
    MutexLock lock(&mutex_);
  
    // Refresh the list of rendering endpoint devices
    _RefreshDeviceList(eRender);
  
    HRESULT hr(S_OK);
  
    assert(_ptrEnumerator != NULL);
  
    //  Select an endpoint rendering device given the specified role
    SAFE_RELEASE(_ptrDeviceOut);
    hr = _ptrEnumerator->GetDefaultAudioEndpoint(eRender, role, &_ptrDeviceOut);
    if (FAILED(hr)) {
      _TraceCOMError(hr);
      SAFE_RELEASE(_ptrDeviceOut);
      return -1;
    }
  
    WCHAR szDeviceName[MAX_PATH];
    const int bufferLen = sizeof(szDeviceName) / sizeof(szDeviceName)[0];
  
    // Get the endpoint device's friendly-name
    if (_GetDeviceName(_ptrDeviceOut, szDeviceName, bufferLen) == 0) {
      RTC_LOG(LS_VERBOSE) << "friendly name: \"" << szDeviceName << "\"";
    }
  
    _usingOutputDeviceIndex = false;
    _outputDevice = device;
  
    return 0;
  }
  
  // ----------------------------------------------------------------------------
  //  PlayoutDeviceName
  // ----------------------------------------------------------------------------
  
  int32_t AudioDeviceWindowsCore::PlayoutDeviceName(
      uint16_t index,
      char name[kAdmMaxDeviceNameSize],
      char guid[kAdmMaxGuidSize]) {
    bool defaultCommunicationDevice(false);
    const int16_t nDevices(PlayoutDevices());  // also updates the list of devices
  
    // Special fix for the case when the user selects '-1' as index (<=> Default
    // Communication Device)
    if (index == (uint16_t)(-1)) {
      defaultCommunicationDevice = true;
      index = 0;
      RTC_LOG(LS_VERBOSE) << "Default Communication endpoint device will be used";
    }
  
    if ((index > (nDevices - 1)) || (name == NULL)) {
      return -1;
    }
  
    memset(name, 0, kAdmMaxDeviceNameSize);
  
    if (guid != NULL) {
      memset(guid, 0, kAdmMaxGuidSize);
    }
  
    MutexLock lock(&mutex_);
  
    int32_t ret(-1);
    WCHAR szDeviceName[MAX_PATH];
    const int bufferLen = sizeof(szDeviceName) / sizeof(szDeviceName)[0];
  
    // Get the endpoint device's friendly-name
    if (defaultCommunicationDevice) {
      ret = _GetDefaultDeviceName(eRender, eCommunications, szDeviceName,
                                  bufferLen);
    } else {
      ret = _GetListDeviceName(eRender, index, szDeviceName, bufferLen);
    }
  
    if (ret == 0) {
      // Convert the endpoint device's friendly-name to UTF-8
      if (WideCharToMultiByte(CP_UTF8, 0, szDeviceName, -1, name,
                              kAdmMaxDeviceNameSize, NULL, NULL) == 0) {
        RTC_LOG(LS_ERROR)
            << "WideCharToMultiByte(CP_UTF8) failed with error code "
            << GetLastError();
      }
    }
  
    // Get the endpoint ID string (uniquely identifies the device among all audio
    // endpoint devices)
    if (defaultCommunicationDevice) {
      ret =
          _GetDefaultDeviceID(eRender, eCommunications, szDeviceName, bufferLen);
    } else {
      ret = _GetListDeviceID(eRender, index, szDeviceName, bufferLen);
    }
  
    if (guid != NULL && ret == 0) {
      // Convert the endpoint device's ID string to UTF-8
      if (WideCharToMultiByte(CP_UTF8, 0, szDeviceName, -1, guid, kAdmMaxGuidSize,
                              NULL, NULL) == 0) {
        RTC_LOG(LS_ERROR)
            << "WideCharToMultiByte(CP_UTF8) failed with error code "
            << GetLastError();
      }
    }
  
    return ret;
  }
  
  // ----------------------------------------------------------------------------
  //  RecordingDeviceName
  // ----------------------------------------------------------------------------
  
  int32_t AudioDeviceWindowsCore::RecordingDeviceName(
      uint16_t index,
      char name[kAdmMaxDeviceNameSize],
      char guid[kAdmMaxGuidSize]) {
    bool defaultCommunicationDevice(false);
    const int16_t nDevices(
        RecordingDevices());  // also updates the list of devices
  
    // Special fix for the case when the user selects '-1' as index (<=> Default
    // Communication Device)
    if (index == (uint16_t)(-1)) {
      defaultCommunicationDevice = true;
      index = 0;
      RTC_LOG(LS_VERBOSE) << "Default Communication endpoint device will be used";
    }
  
    if ((index > (nDevices - 1)) || (name == NULL)) {
      return -1;
    }
  
    memset(name, 0, kAdmMaxDeviceNameSize);
  
    if (guid != NULL) {
      memset(guid, 0, kAdmMaxGuidSize);
    }
  
    MutexLock lock(&mutex_);
  
    int32_t ret(-1);
    WCHAR szDeviceName[MAX_PATH];
    const int bufferLen = sizeof(szDeviceName) / sizeof(szDeviceName)[0];
  
    // Get the endpoint device's friendly-name
    if (defaultCommunicationDevice) {
      ret = _GetDefaultDeviceName(eCapture, eCommunications, szDeviceName,
                                  bufferLen);
    } else {
      ret = _GetListDeviceName(eCapture, index, szDeviceName, bufferLen);
    }
  
    if (ret == 0) {
      // Convert the endpoint device's friendly-name to UTF-8
      if (WideCharToMultiByte(CP_UTF8, 0, szDeviceName, -1, name,
                              kAdmMaxDeviceNameSize, NULL, NULL) == 0) {
        RTC_LOG(LS_ERROR)
            << "WideCharToMultiByte(CP_UTF8) failed with error code "
            << GetLastError();
      }
    }
  
    // Get the endpoint ID string (uniquely identifies the device among all audio
    // endpoint devices)
    if (defaultCommunicationDevice) {
      ret =
          _GetDefaultDeviceID(eCapture, eCommunications, szDeviceName, bufferLen);
    } else {
      ret = _GetListDeviceID(eCapture, index, szDeviceName, bufferLen);
    }
  
    if (guid != NULL && ret == 0) {
      // Convert the endpoint device's ID string to UTF-8
      if (WideCharToMultiByte(CP_UTF8, 0, szDeviceName, -1, guid, kAdmMaxGuidSize,
                              NULL, NULL) == 0) {
        RTC_LOG(LS_ERROR)
            << "WideCharToMultiByte(CP_UTF8) failed with error code "
            << GetLastError();
      }
    }
  
    return ret;
  }
  
  // ----------------------------------------------------------------------------
  //  RecordingDevices
  // ----------------------------------------------------------------------------
  
  int16_t AudioDeviceWindowsCore::RecordingDevices() {
    MutexLock lock(&mutex_);
    return RecordingDevicesLocked();
  }
  
  int16_t AudioDeviceWindowsCore::RecordingDevicesLocked() {
    if (_RefreshDeviceList(eCapture) != -1) {
      return (_DeviceListCount(eCapture));
    }
  
    return -1;
  }
  
  // ----------------------------------------------------------------------------
  //  SetRecordingDevice I (II)
  // ----------------------------------------------------------------------------
  
  int32_t AudioDeviceWindowsCore::SetRecordingDevice(uint16_t index) {
    if (_recIsInitialized) {
      return -1;
    }
  
    // Get current number of available capture endpoint devices and refresh the
    // capture collection.
    UINT nDevices = RecordingDevices();
  
    if (index < 0 || index > (nDevices - 1)) {
      RTC_LOG(LS_ERROR) << "device index is out of range [0," << (nDevices - 1)
                        << "]";
      return -1;
    }
  
    MutexLock lock(&mutex_);
  
    HRESULT hr(S_OK);
  
    assert(_ptrCaptureCollection != NULL);
  
    // Select an endpoint capture device given the specified index
    SAFE_RELEASE(_ptrDeviceIn);
    hr = _ptrCaptureCollection->Item(index, &_ptrDeviceIn);
    if (FAILED(hr)) {
      _TraceCOMError(hr);
      SAFE_RELEASE(_ptrDeviceIn);
      return -1;
    }
  
    WCHAR szDeviceName[MAX_PATH];
    const int bufferLen = sizeof(szDeviceName) / sizeof(szDeviceName)[0];
  
    // Get the endpoint device's friendly-name
    if (_GetDeviceName(_ptrDeviceIn, szDeviceName, bufferLen) == 0) {
      RTC_LOG(LS_VERBOSE) << "friendly name: \"" << szDeviceName << "\"";
    }
  
    _usingInputDeviceIndex = true;
    _inputDeviceIndex = index;
  
    return 0;
  }
  
  // ----------------------------------------------------------------------------
  //  SetRecordingDevice II (II)
  // ----------------------------------------------------------------------------
  
  int32_t AudioDeviceWindowsCore::SetRecordingDevice(
      AudioDeviceModule::WindowsDeviceType device) {
    if (_recIsInitialized) {
      return -1;
    }
  
    ERole role(eCommunications);
  
    if (device == AudioDeviceModule::kDefaultDevice) {
      role = eConsole;
    } else if (device == AudioDeviceModule::kDefaultCommunicationDevice) {
      role = eCommunications;
    }
  
    MutexLock lock(&mutex_);
  
    // Refresh the list of capture endpoint devices
    _RefreshDeviceList(eCapture);
  
    HRESULT hr(S_OK);
  
    assert(_ptrEnumerator != NULL);
  
    //  Select an endpoint capture device given the specified role
    SAFE_RELEASE(_ptrDeviceIn);
    hr = _ptrEnumerator->GetDefaultAudioEndpoint(eCapture, role, &_ptrDeviceIn);
    if (FAILED(hr)) {
      _TraceCOMError(hr);
      SAFE_RELEASE(_ptrDeviceIn);
      return -1;
    }
  
    WCHAR szDeviceName[MAX_PATH];
    const int bufferLen = sizeof(szDeviceName) / sizeof(szDeviceName)[0];
  
    // Get the endpoint device's friendly-name
    if (_GetDeviceName(_ptrDeviceIn, szDeviceName, bufferLen) == 0) {
      RTC_LOG(LS_VERBOSE) << "friendly name: \"" << szDeviceName << "\"";
    }
  
    _usingInputDeviceIndex = false;
    _inputDevice = device;
  
    return 0;
  }
  
  // ----------------------------------------------------------------------------
  //  PlayoutIsAvailable
  // ----------------------------------------------------------------------------
  
  int32_t AudioDeviceWindowsCore::PlayoutIsAvailable(bool& available) {
    available = false;
  
    // Try to initialize the playout side
    int32_t res = InitPlayout();
  
    // Cancel effect of initialization
    StopPlayout();
  
    if (res != -1) {
      available = true;
    }
  
    return 0;
  }
  
  // ----------------------------------------------------------------------------
  //  RecordingIsAvailable
  // ----------------------------------------------------------------------------
  
  int32_t AudioDeviceWindowsCore::RecordingIsAvailable(bool& available) {
    available = false;
  
    // Try to initialize the recording side
    int32_t res = InitRecording();
  
    // Cancel effect of initialization
    StopRecording();
  
    if (res != -1) {
      available = true;
    }
  
    return 0;
  }
  
  // ----------------------------------------------------------------------------
  //  InitPlayout
  // ----------------------------------------------------------------------------
  
  int32_t AudioDeviceWindowsCore::InitPlayout() {
    MutexLock lock(&mutex_);
  
    if (_playing) {
      return -1;
    }
  
    if (_playIsInitialized) {
      return 0;
    }
  
    if (_ptrDeviceOut == NULL) {
      return -1;
    }
  
    // Initialize the speaker (devices might have been added or removed)
    if (InitSpeakerLocked() == -1) {
      RTC_LOG(LS_WARNING) << "InitSpeaker() failed";
    }
  
    // Ensure that the updated rendering endpoint device is valid
    if (_ptrDeviceOut == NULL) {
      return -1;
    }
  
    if (_builtInAecEnabled && _recIsInitialized) {
      // Ensure the correct render device is configured in case
      // InitRecording() was called before InitPlayout().
      if (SetDMOProperties() == -1) {
        return -1;
      }
    }
  
    HRESULT hr = S_OK;
    WAVEFORMATEX* pWfxOut = NULL;
    WAVEFORMATEX Wfx = WAVEFORMATEX();
    WAVEFORMATEX* pWfxClosestMatch = NULL;
  
    // Create COM object with IAudioClient interface.
    SAFE_RELEASE(_ptrClientOut);
    hr = _ptrDeviceOut->Activate(__uuidof(IAudioClient), CLSCTX_ALL, NULL,
                                 (void**)&_ptrClientOut);
    EXIT_ON_ERROR(hr);
  
    // Retrieve the stream format that the audio engine uses for its internal
    // processing (mixing) of shared-mode streams.
    hr = _ptrClientOut->GetMixFormat(&pWfxOut);
    if (SUCCEEDED(hr)) {
      RTC_LOG(LS_VERBOSE) << "Audio Engine's current rendering mix format:";
      // format type
      RTC_LOG(LS_VERBOSE) << "wFormatTag     : 0x"
                          << rtc::ToHex(pWfxOut->wFormatTag) << " ("
                          << pWfxOut->wFormatTag << ")";
      // number of channels (i.e. mono, stereo...)
      RTC_LOG(LS_VERBOSE) << "nChannels      : " << pWfxOut->nChannels;
      // sample rate
      RTC_LOG(LS_VERBOSE) << "nSamplesPerSec : " << pWfxOut->nSamplesPerSec;
      // for buffer estimation
      RTC_LOG(LS_VERBOSE) << "nAvgBytesPerSec: " << pWfxOut->nAvgBytesPerSec;
      // block size of data
      RTC_LOG(LS_VERBOSE) << "nBlockAlign    : " << pWfxOut->nBlockAlign;
      // number of bits per sample of mono data
      RTC_LOG(LS_VERBOSE) << "wBitsPerSample : " << pWfxOut->wBitsPerSample;
      RTC_LOG(LS_VERBOSE) << "cbSize         : " << pWfxOut->cbSize;
    }
  
    // Set wave format
    Wfx.wFormatTag = WAVE_FORMAT_PCM;
    Wfx.wBitsPerSample = 16;
    Wfx.cbSize = 0;
  
    const int freqs[] = {48000, 44100, 16000, 96000, 32000, 8000};
    hr = S_FALSE;
  
    // Iterate over frequencies and channels, in order of priority
    for (unsigned int freq = 0; freq < sizeof(freqs) / sizeof(freqs[0]); freq++) {
      for (unsigned int chan = 0; chan < sizeof(_playChannelsPrioList) /
                                             sizeof(_playChannelsPrioList[0]);
           chan++) {
        Wfx.nChannels = _playChannelsPrioList[chan];
        Wfx.nSamplesPerSec = freqs[freq];
        Wfx.nBlockAlign = Wfx.nChannels * Wfx.wBitsPerSample / 8;
        Wfx.nAvgBytesPerSec = Wfx.nSamplesPerSec * Wfx.nBlockAlign;
        // If the method succeeds and the audio endpoint device supports the
        // specified stream format, it returns S_OK. If the method succeeds and
        // provides a closest match to the specified format, it returns S_FALSE.
        hr = _ptrClientOut->IsFormatSupported(AUDCLNT_SHAREMODE_SHARED, &Wfx,
                                              &pWfxClosestMatch);
        if (hr == S_OK) {
          break;
        } else {
          if (pWfxClosestMatch) {
            RTC_LOG(INFO) << "nChannels=" << Wfx.nChannels
                          << ", nSamplesPerSec=" << Wfx.nSamplesPerSec
                          << " is not supported. Closest match: "
                             "nChannels="
                          << pWfxClosestMatch->nChannels << ", nSamplesPerSec="
                          << pWfxClosestMatch->nSamplesPerSec;
            CoTaskMemFree(pWfxClosestMatch);
            pWfxClosestMatch = NULL;
          } else {
            RTC_LOG(INFO) << "nChannels=" << Wfx.nChannels
                          << ", nSamplesPerSec=" << Wfx.nSamplesPerSec
                          << " is not supported. No closest match.";
          }
        }
      }
      if (hr == S_OK)
        break;
    }
  
    // TODO(andrew): what happens in the event of failure in the above loop?
    //   Is _ptrClientOut->Initialize expected to fail?
    //   Same in InitRecording().
    if (hr == S_OK) {
      _playAudioFrameSize = Wfx.nBlockAlign;
      // Block size is the number of samples each channel in 10ms.
      _playBlockSize = Wfx.nSamplesPerSec / 100;
      _playSampleRate = Wfx.nSamplesPerSec;
      _devicePlaySampleRate =
          Wfx.nSamplesPerSec;  // The device itself continues to run at 44.1 kHz.
      _devicePlayBlockSize = Wfx.nSamplesPerSec / 100;
      _playChannels = Wfx.nChannels;
  
      RTC_LOG(LS_VERBOSE) << "VoE selected this rendering format:";
      RTC_LOG(LS_VERBOSE) << "wFormatTag         : 0x"
                          << rtc::ToHex(Wfx.wFormatTag) << " (" << Wfx.wFormatTag
                          << ")";
      RTC_LOG(LS_VERBOSE) << "nChannels          : " << Wfx.nChannels;
      RTC_LOG(LS_VERBOSE) << "nSamplesPerSec     : " << Wfx.nSamplesPerSec;
      RTC_LOG(LS_VERBOSE) << "nAvgBytesPerSec    : " << Wfx.nAvgBytesPerSec;
      RTC_LOG(LS_VERBOSE) << "nBlockAlign        : " << Wfx.nBlockAlign;
      RTC_LOG(LS_VERBOSE) << "wBitsPerSample     : " << Wfx.wBitsPerSample;
      RTC_LOG(LS_VERBOSE) << "cbSize             : " << Wfx.cbSize;
      RTC_LOG(LS_VERBOSE) << "Additional settings:";
      RTC_LOG(LS_VERBOSE) << "_playAudioFrameSize: " << _playAudioFrameSize;
      RTC_LOG(LS_VERBOSE) << "_playBlockSize     : " << _playBlockSize;
      RTC_LOG(LS_VERBOSE) << "_playChannels      : " << _playChannels;
    }
  
    // Create a rendering stream.
    //
    // ****************************************************************************
    // For a shared-mode stream that uses event-driven buffering, the caller must
    // set both hnsPeriodicity and hnsBufferDuration to 0. The Initialize method
    // determines how large a buffer to allocate based on the scheduling period
    // of the audio engine. Although the client's buffer processing thread is
    // event driven, the basic buffer management process, as described previously,
    // is unaltered.
    // Each time the thread awakens, it should call
    // IAudioClient::GetCurrentPadding to determine how much data to write to a
    // rendering buffer or read from a capture buffer. In contrast to the two
    // buffers that the Initialize method allocates for an exclusive-mode stream
    // that uses event-driven buffering, a shared-mode stream requires a single
    // buffer.
    // ****************************************************************************
    //
    REFERENCE_TIME hnsBufferDuration =
        0;  // ask for minimum buffer size (default)
    if (_devicePlaySampleRate == 44100) {
      // Ask for a larger buffer size (30ms) when using 44.1kHz as render rate.
      // There seems to be a larger risk of underruns for 44.1 compared
      // with the default rate (48kHz). When using default, we set the requested
      // buffer duration to 0, which sets the buffer to the minimum size
      // required by the engine thread. The actual buffer size can then be
      // read by GetBufferSize() and it is 20ms on most machines.
      hnsBufferDuration = 30 * 10000;
    }
    hr = _ptrClientOut->Initialize(
        AUDCLNT_SHAREMODE_SHARED,  // share Audio Engine with other applications
        AUDCLNT_STREAMFLAGS_EVENTCALLBACK,  // processing of the audio buffer by
                                            // the client will be event driven
        hnsBufferDuration,  // requested buffer capacity as a time value (in
                            // 100-nanosecond units)
        0,                  // periodicity
        &Wfx,               // selected wave format
        NULL);              // session GUID
  
    if (FAILED(hr)) {
      RTC_LOG(LS_ERROR) << "IAudioClient::Initialize() failed:";
    }
    EXIT_ON_ERROR(hr);
  
    if (_ptrAudioBuffer) {
      // Update the audio buffer with the selected parameters
      _ptrAudioBuffer->SetPlayoutSampleRate(_playSampleRate);
      _ptrAudioBuffer->SetPlayoutChannels((uint8_t)_playChannels);
    } else {
      // We can enter this state during CoreAudioIsSupported() when no
      // AudioDeviceImplementation has been created, hence the AudioDeviceBuffer
      // does not exist. It is OK to end up here since we don't initiate any media
      // in CoreAudioIsSupported().
      RTC_LOG(LS_VERBOSE)
          << "AudioDeviceBuffer must be attached before streaming can start";
    }
  
    // Get the actual size of the shared (endpoint buffer).
    // Typical value is 960 audio frames <=> 20ms @ 48kHz sample rate.
    UINT bufferFrameCount(0);
    hr = _ptrClientOut->GetBufferSize(&bufferFrameCount);
    if (SUCCEEDED(hr)) {
      RTC_LOG(LS_VERBOSE) << "IAudioClient::GetBufferSize() => "
                          << bufferFrameCount << " (<=> "
                          << bufferFrameCount * _playAudioFrameSize << " bytes)";
    }
  
    // Set the event handle that the system signals when an audio buffer is ready
    // to be processed by the client.
    hr = _ptrClientOut->SetEventHandle(_hRenderSamplesReadyEvent);
    EXIT_ON_ERROR(hr);
  
    // Get an IAudioRenderClient interface.
    SAFE_RELEASE(_ptrRenderClient);
    hr = _ptrClientOut->GetService(__uuidof(IAudioRenderClient),
                                   (void**)&_ptrRenderClient);
    EXIT_ON_ERROR(hr);
  
    // Mark playout side as initialized
    _playIsInitialized = true;
  
    CoTaskMemFree(pWfxOut);
    CoTaskMemFree(pWfxClosestMatch);
  
    RTC_LOG(LS_VERBOSE) << "render side is now initialized";
    return 0;
  
  Exit:
    _TraceCOMError(hr);
    CoTaskMemFree(pWfxOut);
    CoTaskMemFree(pWfxClosestMatch);
    SAFE_RELEASE(_ptrClientOut);
    SAFE_RELEASE(_ptrRenderClient);
    return -1;
  }
  
  // Capture initialization when the built-in AEC DirectX Media Object (DMO) is
  // used. Called from InitRecording(), most of which is skipped over. The DMO
  // handles device initialization itself.
  // Reference: http://msdn.microsoft.com/en-us/library/ff819492(v=vs.85).aspx
  int32_t AudioDeviceWindowsCore::InitRecordingDMO() {
    assert(_builtInAecEnabled);
    assert(_dmo != NULL);
  
    if (SetDMOProperties() == -1) {
      return -1;
    }
  
    DMO_MEDIA_TYPE mt = {};
    HRESULT hr = MoInitMediaType(&mt, sizeof(WAVEFORMATEX));
    if (FAILED(hr)) {
      MoFreeMediaType(&mt);
      _TraceCOMError(hr);
      return -1;
    }
    mt.majortype = MEDIATYPE_Audio;
    mt.subtype = MEDIASUBTYPE_PCM;
    mt.formattype = FORMAT_WaveFormatEx;
  
    // Supported formats
    // nChannels: 1 (in AEC-only mode)
    // nSamplesPerSec: 8000, 11025, 16000, 22050
    // wBitsPerSample: 16
    WAVEFORMATEX* ptrWav = reinterpret_cast<WAVEFORMATEX*>(mt.pbFormat);
    ptrWav->wFormatTag = WAVE_FORMAT_PCM;
    ptrWav->nChannels = 1;
    // 16000 is the highest we can support with our resampler.
    ptrWav->nSamplesPerSec = 16000;
    ptrWav->nAvgBytesPerSec = 32000;
    ptrWav->nBlockAlign = 2;
    ptrWav->wBitsPerSample = 16;
    ptrWav->cbSize = 0;
  
    // Set the VoE format equal to the AEC output format.
    _recAudioFrameSize = ptrWav->nBlockAlign;
    _recSampleRate = ptrWav->nSamplesPerSec;
    _recBlockSize = ptrWav->nSamplesPerSec / 100;
    _recChannels = ptrWav->nChannels;
  
    // Set the DMO output format parameters.
    hr = _dmo->SetOutputType(kAecCaptureStreamIndex, &mt, 0);
    MoFreeMediaType(&mt);
    if (FAILED(hr)) {
      _TraceCOMError(hr);
      return -1;
    }
  
    if (_ptrAudioBuffer) {
      _ptrAudioBuffer->SetRecordingSampleRate(_recSampleRate);
      _ptrAudioBuffer->SetRecordingChannels(_recChannels);
    } else {
      // Refer to InitRecording() for comments.
      RTC_LOG(LS_VERBOSE)
          << "AudioDeviceBuffer must be attached before streaming can start";
    }
  
    _mediaBuffer = new MediaBufferImpl(_recBlockSize * _recAudioFrameSize);
  
    // Optional, but if called, must be after media types are set.
    hr = _dmo->AllocateStreamingResources();
    if (FAILED(hr)) {
      _TraceCOMError(hr);
      return -1;
    }
  
    _recIsInitialized = true;
    RTC_LOG(LS_VERBOSE) << "Capture side is now initialized";
  
    return 0;
  }
  
  // ----------------------------------------------------------------------------
  //  InitRecording
  // ----------------------------------------------------------------------------
  
  int32_t AudioDeviceWindowsCore::InitRecording() {
    MutexLock lock(&mutex_);
  
    if (_recording) {
      return -1;
    }
  
    if (_recIsInitialized) {
      return 0;
    }
  
    if (QueryPerformanceFrequency(&_perfCounterFreq) == 0) {
      return -1;
    }
    _perfCounterFactor = 10000000.0 / (double)_perfCounterFreq.QuadPart;
  
    if (_ptrDeviceIn == NULL) {
      return -1;
    }
  
    // Initialize the microphone (devices might have been added or removed)
    if (InitMicrophoneLocked() == -1) {
      RTC_LOG(LS_WARNING) << "InitMicrophone() failed";
    }
  
    // Ensure that the updated capturing endpoint device is valid
    if (_ptrDeviceIn == NULL) {
      return -1;
    }
  
    if (_builtInAecEnabled) {
      // The DMO will configure the capture device.
      return InitRecordingDMO();
    }
  
    HRESULT hr = S_OK;
    WAVEFORMATEX* pWfxIn = NULL;
    WAVEFORMATEXTENSIBLE Wfx = WAVEFORMATEXTENSIBLE();
    WAVEFORMATEX* pWfxClosestMatch = NULL;
  
    // Create COM object with IAudioClient interface.
    SAFE_RELEASE(_ptrClientIn);
    hr = _ptrDeviceIn->Activate(__uuidof(IAudioClient), CLSCTX_ALL, NULL,
                                (void**)&_ptrClientIn);
    EXIT_ON_ERROR(hr);
  
    // Retrieve the stream format that the audio engine uses for its internal
    // processing (mixing) of shared-mode streams.
    hr = _ptrClientIn->GetMixFormat(&pWfxIn);
    if (SUCCEEDED(hr)) {
      RTC_LOG(LS_VERBOSE) << "Audio Engine's current capturing mix format:";
      // format type
      RTC_LOG(LS_VERBOSE) << "wFormatTag     : 0x"
                          << rtc::ToHex(pWfxIn->wFormatTag) << " ("
                          << pWfxIn->wFormatTag << ")";
      // number of channels (i.e. mono, stereo...)
      RTC_LOG(LS_VERBOSE) << "nChannels      : " << pWfxIn->nChannels;
      // sample rate
      RTC_LOG(LS_VERBOSE) << "nSamplesPerSec : " << pWfxIn->nSamplesPerSec;
      // for buffer estimation
      RTC_LOG(LS_VERBOSE) << "nAvgBytesPerSec: " << pWfxIn->nAvgBytesPerSec;
      // block size of data
      RTC_LOG(LS_VERBOSE) << "nBlockAlign    : " << pWfxIn->nBlockAlign;
      // number of bits per sample of mono data
      RTC_LOG(LS_VERBOSE) << "wBitsPerSample : " << pWfxIn->wBitsPerSample;
      RTC_LOG(LS_VERBOSE) << "cbSize         : " << pWfxIn->cbSize;
    }
  
    // Set wave format
    Wfx.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
    Wfx.Format.wBitsPerSample = 16;
    Wfx.Format.cbSize = 22;
    Wfx.dwChannelMask = 0;
    Wfx.Samples.wValidBitsPerSample = Wfx.Format.wBitsPerSample;
    Wfx.SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
  
    const int freqs[6] = {48000, 44100, 16000, 96000, 32000, 8000};
    hr = S_FALSE;
  
    // Iterate over frequencies and channels, in order of priority
    for (unsigned int freq = 0; freq < sizeof(freqs) / sizeof(freqs[0]); freq++) {
      for (unsigned int chan = 0;
           chan < sizeof(_recChannelsPrioList) / sizeof(_recChannelsPrioList[0]);
           chan++) {
        Wfx.Format.nChannels = _recChannelsPrioList[chan];
        Wfx.Format.nSamplesPerSec = freqs[freq];
        Wfx.Format.nBlockAlign =
            Wfx.Format.nChannels * Wfx.Format.wBitsPerSample / 8;
        Wfx.Format.nAvgBytesPerSec =
            Wfx.Format.nSamplesPerSec * Wfx.Format.nBlockAlign;
        // If the method succeeds and the audio endpoint device supports the
        // specified stream format, it returns S_OK. If the method succeeds and
        // provides a closest match to the specified format, it returns S_FALSE.
        hr = _ptrClientIn->IsFormatSupported(
            AUDCLNT_SHAREMODE_SHARED, (WAVEFORMATEX*)&Wfx, &pWfxClosestMatch);
        if (hr == S_OK) {
          break;
        } else {
          if (pWfxClosestMatch) {
            RTC_LOG(INFO) << "nChannels=" << Wfx.Format.nChannels
                          << ", nSamplesPerSec=" << Wfx.Format.nSamplesPerSec
                          << " is not supported. Closest match: "
                             "nChannels="
                          << pWfxClosestMatch->nChannels << ", nSamplesPerSec="
                          << pWfxClosestMatch->nSamplesPerSec;
            CoTaskMemFree(pWfxClosestMatch);
            pWfxClosestMatch = NULL;
          } else {
            RTC_LOG(INFO) << "nChannels=" << Wfx.Format.nChannels
                          << ", nSamplesPerSec=" << Wfx.Format.nSamplesPerSec
                          << " is not supported. No closest match.";
          }
        }
      }
      if (hr == S_OK)
        break;
    }
  
    if (hr == S_OK) {
      _recAudioFrameSize = Wfx.Format.nBlockAlign;
      _recSampleRate = Wfx.Format.nSamplesPerSec;
      _recBlockSize = Wfx.Format.nSamplesPerSec / 100;
      _recChannels = Wfx.Format.nChannels;
  
      RTC_LOG(LS_VERBOSE) << "VoE selected this capturing format:";
      RTC_LOG(LS_VERBOSE) << "wFormatTag        : 0x"
                          << rtc::ToHex(Wfx.Format.wFormatTag) << " ("
                          << Wfx.Format.wFormatTag << ")";
      RTC_LOG(LS_VERBOSE) << "nChannels         : " << Wfx.Format.nChannels;
      RTC_LOG(LS_VERBOSE) << "nSamplesPerSec    : " << Wfx.Format.nSamplesPerSec;
      RTC_LOG(LS_VERBOSE) << "nAvgBytesPerSec   : " << Wfx.Format.nAvgBytesPerSec;
      RTC_LOG(LS_VERBOSE) << "nBlockAlign       : " << Wfx.Format.nBlockAlign;
      RTC_LOG(LS_VERBOSE) << "wBitsPerSample    : " << Wfx.Format.wBitsPerSample;
      RTC_LOG(LS_VERBOSE) << "cbSize            : " << Wfx.Format.cbSize;
      RTC_LOG(LS_VERBOSE) << "Additional settings:";
      RTC_LOG(LS_VERBOSE) << "_recAudioFrameSize: " << _recAudioFrameSize;
      RTC_LOG(LS_VERBOSE) << "_recBlockSize     : " << _recBlockSize;
      RTC_LOG(LS_VERBOSE) << "_recChannels      : " << _recChannels;
    }
  
    // Create a capturing stream.
    hr = _ptrClientIn->Initialize(
        AUDCLNT_SHAREMODE_SHARED,  // share Audio Engine with other applications
        AUDCLNT_STREAMFLAGS_EVENTCALLBACK |  // processing of the audio buffer by
                                             // the client will be event driven
            AUDCLNT_STREAMFLAGS_NOPERSIST,   // volume and mute settings for an
                                             // audio session will not persist
                                             // across system restarts
        0,                    // required for event-driven shared mode
        0,                    // periodicity
        (WAVEFORMATEX*)&Wfx,  // selected wave format
        NULL);                // session GUID
  
    if (hr != S_OK) {
      RTC_LOG(LS_ERROR) << "IAudioClient::Initialize() failed:";
    }
    EXIT_ON_ERROR(hr);
  
    if (_ptrAudioBuffer) {
      // Update the audio buffer with the selected parameters
      _ptrAudioBuffer->SetRecordingSampleRate(_recSampleRate);
      _ptrAudioBuffer->SetRecordingChannels((uint8_t)_recChannels);
    } else {
      // We can enter this state during CoreAudioIsSupported() when no
      // AudioDeviceImplementation has been created, hence the AudioDeviceBuffer
      // does not exist. It is OK to end up here since we don't initiate any media
      // in CoreAudioIsSupported().
      RTC_LOG(LS_VERBOSE)
          << "AudioDeviceBuffer must be attached before streaming can start";
    }
  
    // Get the actual size of the shared (endpoint buffer).
    // Typical value is 960 audio frames <=> 20ms @ 48kHz sample rate.
    UINT bufferFrameCount(0);
    hr = _ptrClientIn->GetBufferSize(&bufferFrameCount);
    if (SUCCEEDED(hr)) {
      RTC_LOG(LS_VERBOSE) << "IAudioClient::GetBufferSize() => "
                          << bufferFrameCount << " (<=> "
                          << bufferFrameCount * _recAudioFrameSize << " bytes)";
    }
  
    // Set the event handle that the system signals when an audio buffer is ready
    // to be processed by the client.
    hr = _ptrClientIn->SetEventHandle(_hCaptureSamplesReadyEvent);
    EXIT_ON_ERROR(hr);
  
    // Get an IAudioCaptureClient interface.
    SAFE_RELEASE(_ptrCaptureClient);
    hr = _ptrClientIn->GetService(__uuidof(IAudioCaptureClient),
                                  (void**)&_ptrCaptureClient);
    EXIT_ON_ERROR(hr);
  
    // Mark capture side as initialized
    _recIsInitialized = true;
  
    CoTaskMemFree(pWfxIn);
    CoTaskMemFree(pWfxClosestMatch);
  
    RTC_LOG(LS_VERBOSE) << "capture side is now initialized";
    return 0;
  
  Exit:
    _TraceCOMError(hr);
    CoTaskMemFree(pWfxIn);
    CoTaskMemFree(pWfxClosestMatch);
    SAFE_RELEASE(_ptrClientIn);
    SAFE_RELEASE(_ptrCaptureClient);
    return -1;
  }
  
  // ----------------------------------------------------------------------------
  //  StartRecording
  // ----------------------------------------------------------------------------
  
  int32_t AudioDeviceWindowsCore::StartRecording() {
    if (!_recIsInitialized) {
      return -1;
    }
  
    if (_hRecThread != NULL) {
      return 0;
    }
  
    if (_recording) {
      return 0;
    }
  
    {
      MutexLock lockScoped(&mutex_);
  
      // Create thread which will drive the capturing
      LPTHREAD_START_ROUTINE lpStartAddress = WSAPICaptureThread;
      if (_builtInAecEnabled) {
        // Redirect to the DMO polling method.
        lpStartAddress = WSAPICaptureThreadPollDMO;
  
        if (!_playing) {
          // The DMO won't provide us captured output data unless we
          // give it render data to process.
          RTC_LOG(LS_ERROR)
              << "Playout must be started before recording when using"
                 " the built-in AEC";
          return -1;
        }
      }
  
      assert(_hRecThread == NULL);
      _hRecThread = CreateThread(NULL, 0, lpStartAddress, this, 0, NULL);
      if (_hRecThread == NULL) {
        RTC_LOG(LS_ERROR) << "failed to create the recording thread";
        return -1;
      }
  
      // Set thread priority to highest possible
      SetThreadPriority(_hRecThread, THREAD_PRIORITY_TIME_CRITICAL);
  
    }  // critScoped
  
    DWORD ret = WaitForSingleObject(_hCaptureStartedEvent, 1000);
    if (ret != WAIT_OBJECT_0) {
      RTC_LOG(LS_VERBOSE) << "capturing did not start up properly";
      return -1;
    }
    RTC_LOG(LS_VERBOSE) << "capture audio stream has now started...";
  
    _recording = true;
  
    return 0;
  }
  
  // ----------------------------------------------------------------------------
  //  StopRecording
  // ----------------------------------------------------------------------------
  
  int32_t AudioDeviceWindowsCore::StopRecording() {
    int32_t err = 0;
  
    if (!_recIsInitialized) {
      return 0;
    }
  
    _Lock();
  
    if (_hRecThread == NULL) {
      RTC_LOG(LS_VERBOSE)
          << "no capturing stream is active => close down WASAPI only";
      SAFE_RELEASE(_ptrClientIn);
      SAFE_RELEASE(_ptrCaptureClient);
      _recIsInitialized = false;
      _recording = false;
      _UnLock();
      return 0;
    }
  
    // Stop the driving thread...
    RTC_LOG(LS_VERBOSE) << "closing down the webrtc_core_audio_capture_thread...";
    // Manual-reset event; it will remain signalled to stop all capture threads.
    SetEvent(_hShutdownCaptureEvent);
  
    _UnLock();
    DWORD ret = WaitForSingleObject(_hRecThread, 2000);
    if (ret != WAIT_OBJECT_0) {
      RTC_LOG(LS_ERROR)
          << "failed to close down webrtc_core_audio_capture_thread";
      err = -1;
    } else {
      RTC_LOG(LS_VERBOSE) << "webrtc_core_audio_capture_thread is now closed";
    }
    _Lock();
  
    ResetEvent(_hShutdownCaptureEvent);  // Must be manually reset.
    // Ensure that the thread has released these interfaces properly.
    assert(err == -1 || _ptrClientIn == NULL);
    assert(err == -1 || _ptrCaptureClient == NULL);
  
    _recIsInitialized = false;
    _recording = false;
  
    // These will create thread leaks in the result of an error,
    // but we can at least resume the call.
    CloseHandle(_hRecThread);
    _hRecThread = NULL;
  
    if (_builtInAecEnabled) {
      assert(_dmo != NULL);
      // This is necessary. Otherwise the DMO can generate garbage render
      // audio even after rendering has stopped.
      HRESULT hr = _dmo->FreeStreamingResources();
      if (FAILED(hr)) {
        _TraceCOMError(hr);
        err = -1;
      }
    }
  
    _UnLock();
  
    return err;
  }
  
  // ----------------------------------------------------------------------------
  //  RecordingIsInitialized
  // ----------------------------------------------------------------------------
  
  bool AudioDeviceWindowsCore::RecordingIsInitialized() const {
    return (_recIsInitialized);
  }
  
  // ----------------------------------------------------------------------------
  //  Recording
  // ----------------------------------------------------------------------------
  
  bool AudioDeviceWindowsCore::Recording() const {
    return (_recording);
  }
  
  // ----------------------------------------------------------------------------
  //  PlayoutIsInitialized
  // ----------------------------------------------------------------------------
  
  bool AudioDeviceWindowsCore::PlayoutIsInitialized() const {
    return (_playIsInitialized);
  }
  
  // ----------------------------------------------------------------------------
  //  StartPlayout
  // ----------------------------------------------------------------------------
  
  int32_t AudioDeviceWindowsCore::StartPlayout() {
    if (!_playIsInitialized) {
      return -1;
    }
  
    if (_hPlayThread != NULL) {
      return 0;
    }
  
    if (_playing) {
      return 0;
    }
  
    {
      MutexLock lockScoped(&mutex_);
  
      // Create thread which will drive the rendering.
      assert(_hPlayThread == NULL);
      _hPlayThread = CreateThread(NULL, 0, WSAPIRenderThread, this, 0, NULL);
      if (_hPlayThread == NULL) {
        RTC_LOG(LS_ERROR) << "failed to create the playout thread";
        return -1;
      }
  
      // Set thread priority to highest possible.
      SetThreadPriority(_hPlayThread, THREAD_PRIORITY_TIME_CRITICAL);
    }  // critScoped
  
    DWORD ret = WaitForSingleObject(_hRenderStartedEvent, 1000);
    if (ret != WAIT_OBJECT_0) {
      RTC_LOG(LS_VERBOSE) << "rendering did not start up properly";
      return -1;
    }
  
    _playing = true;
    RTC_LOG(LS_VERBOSE) << "rendering audio stream has now started...";
  
    return 0;
  }
  
  // ----------------------------------------------------------------------------
  //  StopPlayout
  // ----------------------------------------------------------------------------
  
  int32_t AudioDeviceWindowsCore::StopPlayout() {
    if (!_playIsInitialized) {
      return 0;
    }
  
    {
      MutexLock lockScoped(&mutex_);
  
      if (_hPlayThread == NULL) {
        RTC_LOG(LS_VERBOSE)
            << "no rendering stream is active => close down WASAPI only";
        SAFE_RELEASE(_ptrClientOut);
        SAFE_RELEASE(_ptrRenderClient);
        _playIsInitialized = false;
        _playing = false;
        return 0;
      }
  
      // stop the driving thread...
      RTC_LOG(LS_VERBOSE)
          << "closing down the webrtc_core_audio_render_thread...";
      SetEvent(_hShutdownRenderEvent);
    }  // critScoped
  
    DWORD ret = WaitForSingleObject(_hPlayThread, 2000);
    if (ret != WAIT_OBJECT_0) {
      // the thread did not stop as it should
      RTC_LOG(LS_ERROR) << "failed to close down webrtc_core_audio_render_thread";
      CloseHandle(_hPlayThread);
      _hPlayThread = NULL;
      _playIsInitialized = false;
      _playing = false;
      return -1;
    }
  
    {
      MutexLock lockScoped(&mutex_);
      RTC_LOG(LS_VERBOSE) << "webrtc_core_audio_render_thread is now closed";
  
      // to reset this event manually at each time we finish with it,
      // in case that the render thread has exited before StopPlayout(),
      // this event might be caught by the new render thread within same VoE
      // instance.
      ResetEvent(_hShutdownRenderEvent);
  
      SAFE_RELEASE(_ptrClientOut);
      SAFE_RELEASE(_ptrRenderClient);
  
      _playIsInitialized = false;
      _playing = false;
  
      CloseHandle(_hPlayThread);
      _hPlayThread = NULL;
  
      if (_builtInAecEnabled && _recording) {
        // The DMO won't provide us captured output data unless we
        // give it render data to process.
        //
        // We still permit the playout to shutdown, and trace a warning.
        // Otherwise, VoE can get into a state which will never permit
        // playout to stop properly.
        RTC_LOG(LS_WARNING)
            << "Recording should be stopped before playout when using the"
               " built-in AEC";
      }
  
      // Reset the playout delay value.
      _sndCardPlayDelay = 0;
    }  // critScoped
  
    return 0;
  }
  
  // ----------------------------------------------------------------------------
  //  PlayoutDelay
  // ----------------------------------------------------------------------------
  
  int32_t AudioDeviceWindowsCore::PlayoutDelay(uint16_t& delayMS) const {
    MutexLock lockScoped(&mutex_);
    delayMS = static_cast<uint16_t>(_sndCardPlayDelay);
    return 0;
  }
  
  bool AudioDeviceWindowsCore::BuiltInAECIsAvailable() const {
    return false; // disable in-built AEC on Windows because this will soon be
    //deprecated, and it also inteferes with one-way audio (ISSUE #6)
  }
  
  // ----------------------------------------------------------------------------
  //  Playing
  // ----------------------------------------------------------------------------
  
  bool AudioDeviceWindowsCore::Playing() const {
    return (_playing);
  }
  
  // ============================================================================
  //                                 Private Methods
  // ============================================================================
  
  // ----------------------------------------------------------------------------
  //  [static] WSAPIRenderThread
  // ----------------------------------------------------------------------------
  
  DWORD WINAPI AudioDeviceWindowsCore::WSAPIRenderThread(LPVOID context) {
    return reinterpret_cast<AudioDeviceWindowsCore*>(context)->DoRenderThread();
  }
  
  // ----------------------------------------------------------------------------
  //  [static] WSAPICaptureThread
  // ----------------------------------------------------------------------------
  
  DWORD WINAPI AudioDeviceWindowsCore::WSAPICaptureThread(LPVOID context) {
    return reinterpret_cast<AudioDeviceWindowsCore*>(context)->DoCaptureThread();
  }
  
  DWORD WINAPI AudioDeviceWindowsCore::WSAPICaptureThreadPollDMO(LPVOID context) {
    return reinterpret_cast<AudioDeviceWindowsCore*>(context)
        ->DoCaptureThreadPollDMO();
  }
  
  // ----------------------------------------------------------------------------
  //  DoRenderThread
  // ----------------------------------------------------------------------------
  
  DWORD AudioDeviceWindowsCore::DoRenderThread() {
    bool keepPlaying = true;
    HANDLE waitArray[2] = {_hShutdownRenderEvent, _hRenderSamplesReadyEvent};
    HRESULT hr = S_OK;
    HANDLE hMmTask = NULL;
  
    // Initialize COM as MTA in this thread.
    ScopedCOMInitializer comInit(ScopedCOMInitializer::kMTA);
    if (!comInit.succeeded()) {
      RTC_LOG(LS_ERROR) << "failed to initialize COM in render thread";
      return 1;
    }
  
    rtc::SetCurrentThreadName("webrtc_core_audio_render_thread");
  
    // Use Multimedia Class Scheduler Service (MMCSS) to boost the thread
    // priority.
    //
    if (_winSupportAvrt) {
      DWORD taskIndex(0);
      hMmTask = _PAvSetMmThreadCharacteristicsA("Pro Audio", &taskIndex);
      if (hMmTask) {
        if (FALSE == _PAvSetMmThreadPriority(hMmTask, AVRT_PRIORITY_CRITICAL)) {
          RTC_LOG(LS_WARNING) << "failed to boost play-thread using MMCSS";
        }
        RTC_LOG(LS_VERBOSE)
            << "render thread is now registered with MMCSS (taskIndex="
            << taskIndex << ")";
      } else {
        RTC_LOG(LS_WARNING) << "failed to enable MMCSS on render thread (err="
                            << GetLastError() << ")";
        _TraceCOMError(GetLastError());
      }
    }
  
    _Lock();
  
    IAudioClock* clock = NULL;
  
    // Get size of rendering buffer (length is expressed as the number of audio
    // frames the buffer can hold). This value is fixed during the rendering
    // session.
    //
    UINT32 bufferLength = 0;
    hr = _ptrClientOut->GetBufferSize(&bufferLength);
    EXIT_ON_ERROR(hr);
    RTC_LOG(LS_VERBOSE) << "[REND] size of buffer       : " << bufferLength;
  
    // Get maximum latency for the current stream (will not change for the
    // lifetime  of the IAudioClient object).
    //
    REFERENCE_TIME latency;
    _ptrClientOut->GetStreamLatency(&latency);
    RTC_LOG(LS_VERBOSE) << "[REND] max stream latency   : " << (DWORD)latency
                        << " (" << (double)(latency / 10000.0) << " ms)";
  
    // Get the length of the periodic interval separating successive processing
    // passes by the audio engine on the data in the endpoint buffer.
    //
    // The period between processing passes by the audio engine is fixed for a
    // particular audio endpoint device and represents the smallest processing
    // quantum for the audio engine. This period plus the stream latency between
    // the buffer and endpoint device represents the minimum possible latency that
    // an audio application can achieve. Typical value: 100000 <=> 0.01 sec =
    // 10ms.
    //
    REFERENCE_TIME devPeriod = 0;
    REFERENCE_TIME devPeriodMin = 0;
    _ptrClientOut->GetDevicePeriod(&devPeriod, &devPeriodMin);
    RTC_LOG(LS_VERBOSE) << "[REND] device period        : " << (DWORD)devPeriod
                        << " (" << (double)(devPeriod / 10000.0) << " ms)";
  
    // Derive initial rendering delay.
    // Example: 10*(960/480) + 15 = 20 + 15 = 35ms
    //
    int playout_delay = 10 * (bufferLength / _playBlockSize) +
                        (int)((latency + devPeriod) / 10000);
    _sndCardPlayDelay = playout_delay;
    _writtenSamples = 0;
    RTC_LOG(LS_VERBOSE) << "[REND] initial delay        : " << playout_delay;
  
    double endpointBufferSizeMS =
        10.0 * ((double)bufferLength / (double)_devicePlayBlockSize);
    RTC_LOG(LS_VERBOSE) << "[REND] endpointBufferSizeMS : "
                        << endpointBufferSizeMS;
  
    // Before starting the stream, fill the rendering buffer with silence.
    //
    BYTE* pData = NULL;
    hr = _ptrRenderClient->GetBuffer(bufferLength, &pData);
    EXIT_ON_ERROR(hr);
  
    hr =
        _ptrRenderClient->ReleaseBuffer(bufferLength, AUDCLNT_BUFFERFLAGS_SILENT);
    EXIT_ON_ERROR(hr);
  
    _writtenSamples += bufferLength;
  
    hr = _ptrClientOut->GetService(__uuidof(IAudioClock), (void**)&clock);
    if (FAILED(hr)) {
      RTC_LOG(LS_WARNING)
          << "failed to get IAudioClock interface from the IAudioClient";
    }
  
    // Start up the rendering audio stream.
    hr = _ptrClientOut->Start();
    EXIT_ON_ERROR(hr);
  
    _UnLock();
  
    // Set event which will ensure that the calling thread modifies the playing
    // state to true.
    //
    SetEvent(_hRenderStartedEvent);
  
    // >> ------------------ THREAD LOOP ------------------
  
    while (keepPlaying) {
      // Wait for a render notification event or a shutdown event
      DWORD waitResult = WaitForMultipleObjects(2, waitArray, FALSE, 500);
      switch (waitResult) {
        case WAIT_OBJECT_0 + 0:  // _hShutdownRenderEvent
          keepPlaying = false;
          break;
        case WAIT_OBJECT_0 + 1:  // _hRenderSamplesReadyEvent
          break;
        case WAIT_TIMEOUT:  // timeout notification
          RTC_LOG(LS_WARNING) << "render event timed out after 0.5 seconds";
          goto Exit;
        default:  // unexpected error
          RTC_LOG(LS_WARNING) << "unknown wait termination on render side";
          goto Exit;
      }
  
      while (keepPlaying) {
        _Lock();
  
        // Sanity check to ensure that essential states are not modified
        // during the unlocked period.
        if (_ptrRenderClient == NULL || _ptrClientOut == NULL) {
          _UnLock();
          RTC_LOG(LS_ERROR)
              << "output state has been modified during unlocked period";
          goto Exit;
        }
  
        // Get the number of frames of padding (queued up to play) in the endpoint
        // buffer.
        UINT32 padding = 0;
        hr = _ptrClientOut->GetCurrentPadding(&padding);
        EXIT_ON_ERROR(hr);
  
        // Derive the amount of available space in the output buffer
        uint32_t framesAvailable = bufferLength - padding;
  
        // Do we have 10 ms available in the render buffer?
        if (framesAvailable < _playBlockSize) {
          // Not enough space in render buffer to store next render packet.
          _UnLock();
          break;
        }
  
        // Write n*10ms buffers to the render buffer
        const uint32_t n10msBuffers = (framesAvailable / _playBlockSize);
        for (uint32_t n = 0; n < n10msBuffers; n++) {
          // Get pointer (i.e., grab the buffer) to next space in the shared
          // render buffer.
          hr = _ptrRenderClient->GetBuffer(_playBlockSize, &pData);
          EXIT_ON_ERROR(hr);
  
          if (_ptrAudioBuffer) {
            // Request data to be played out (#bytes =
            // _playBlockSize*_audioFrameSize)
            _UnLock();
            int32_t nSamples =
                _ptrAudioBuffer->RequestPlayoutData(_playBlockSize);
            _Lock();
  
            if (nSamples == -1) {
              _UnLock();
              RTC_LOG(LS_ERROR) << "failed to read data from render client";
              goto Exit;
            }
  
            // Sanity check to ensure that essential states are not modified
            // during the unlocked period
            if (_ptrRenderClient == NULL || _ptrClientOut == NULL) {
              _UnLock();
              RTC_LOG(LS_ERROR)
                  << "output state has been modified during unlocked"
                     " period";
              goto Exit;
            }
            if (nSamples != static_cast<int32_t>(_playBlockSize)) {
              RTC_LOG(LS_WARNING)
                  << "nSamples(" << nSamples << ") != _playBlockSize"
                  << _playBlockSize << ")";
            }
  
            // Get the actual (stored) data
            nSamples = _ptrAudioBuffer->GetPlayoutData((int8_t*)pData);
          }
  
          DWORD dwFlags(0);
          hr = _ptrRenderClient->ReleaseBuffer(_playBlockSize, dwFlags);
          // See http://msdn.microsoft.com/en-us/library/dd316605(VS.85).aspx
          // for more details regarding AUDCLNT_E_DEVICE_INVALIDATED.
          EXIT_ON_ERROR(hr);
  
          _writtenSamples += _playBlockSize;
        }
  
        // Check the current delay on the playout side.
        if (clock) {
          UINT64 pos = 0;
          UINT64 freq = 1;
          clock->GetPosition(&pos, NULL);
          clock->GetFrequency(&freq);
          playout_delay = ROUND((double(_writtenSamples) / _devicePlaySampleRate -
                                 double(pos) / freq) *
                                1000.0);
          _sndCardPlayDelay = playout_delay;
        }
  
        _UnLock();
      }
    }
  
    // ------------------ THREAD LOOP ------------------ <<
  
    SleepMs(static_cast<DWORD>(endpointBufferSizeMS + 0.5));
    hr = _ptrClientOut->Stop();
  
  Exit:
    SAFE_RELEASE(clock);
  
    if (FAILED(hr)) {
      _ptrClientOut->Stop();
      _UnLock();
      _TraceCOMError(hr);
    }
  
    if (_winSupportAvrt) {
      if (NULL != hMmTask) {
        _PAvRevertMmThreadCharacteristics(hMmTask);
      }
    }
  
    _Lock();
  
    if (keepPlaying) {
      if (_ptrClientOut != NULL) {
        hr = _ptrClientOut->Stop();
        if (FAILED(hr)) {
          _TraceCOMError(hr);
        }
        hr = _ptrClientOut->Reset();
        if (FAILED(hr)) {
          _TraceCOMError(hr);
        }
      }
      RTC_LOG(LS_ERROR)
          << "Playout error: rendering thread has ended pre-maturely";
    } else {
      RTC_LOG(LS_VERBOSE) << "_Rendering thread is now terminated properly";
    }
  
    _UnLock();
  
    return (DWORD)hr;
  }
  
  DWORD AudioDeviceWindowsCore::InitCaptureThreadPriority() {
    _hMmTask = NULL;
  
    rtc::SetCurrentThreadName("webrtc_core_audio_capture_thread");
  
    // Use Multimedia Class Scheduler Service (MMCSS) to boost the thread
    // priority.
    if (_winSupportAvrt) {
      DWORD taskIndex(0);
      _hMmTask = _PAvSetMmThreadCharacteristicsA("Pro Audio", &taskIndex);
      if (_hMmTask) {
        if (!_PAvSetMmThreadPriority(_hMmTask, AVRT_PRIORITY_CRITICAL)) {
          RTC_LOG(LS_WARNING) << "failed to boost rec-thread using MMCSS";
        }
        RTC_LOG(LS_VERBOSE)
            << "capture thread is now registered with MMCSS (taskIndex="
            << taskIndex << ")";
      } else {
        RTC_LOG(LS_WARNING) << "failed to enable MMCSS on capture thread (err="
                            << GetLastError() << ")";
        _TraceCOMError(GetLastError());
      }
    }
  
    return S_OK;
  }
  
  void AudioDeviceWindowsCore::RevertCaptureThreadPriority() {
    if (_winSupportAvrt) {
      if (NULL != _hMmTask) {
        _PAvRevertMmThreadCharacteristics(_hMmTask);
      }
    }
  
    _hMmTask = NULL;
  }
  
  DWORD AudioDeviceWindowsCore::DoCaptureThreadPollDMO() {
    assert(_mediaBuffer != NULL);
    bool keepRecording = true;
  
    // Initialize COM as MTA in this thread.
    ScopedCOMInitializer comInit(ScopedCOMInitializer::kMTA);
    if (!comInit.succeeded()) {
      RTC_LOG(LS_ERROR) << "failed to initialize COM in polling DMO thread";
      return 1;
    }
  
    HRESULT hr = InitCaptureThreadPriority();
    if (FAILED(hr)) {
      return hr;
    }
  
    // Set event which will ensure that the calling thread modifies the
    // recording state to true.
    SetEvent(_hCaptureStartedEvent);
  
    // >> ---------------------------- THREAD LOOP ----------------------------
    while (keepRecording) {
      // Poll the DMO every 5 ms.
      // (The same interval used in the Wave implementation.)
      DWORD waitResult = WaitForSingleObject(_hShutdownCaptureEvent, 5);
      switch (waitResult) {
        case WAIT_OBJECT_0:  // _hShutdownCaptureEvent
          keepRecording = false;
          break;
        case WAIT_TIMEOUT:  // timeout notification
          break;
        default:  // unexpected error
          RTC_LOG(LS_WARNING) << "Unknown wait termination on capture side";
          hr = -1;  // To signal an error callback.
          keepRecording = false;
          break;
      }
  
      while (keepRecording) {
        MutexLock lockScoped(&mutex_);
  
        DWORD dwStatus = 0;
        {
          DMO_OUTPUT_DATA_BUFFER dmoBuffer = {0};
          dmoBuffer.pBuffer = _mediaBuffer;
          dmoBuffer.pBuffer->AddRef();
  
          // Poll the DMO for AEC processed capture data. The DMO will
          // copy available data to |dmoBuffer|, and should only return
          // 10 ms frames. The value of |dwStatus| should be ignored.
          hr = _dmo->ProcessOutput(0, 1, &dmoBuffer, &dwStatus);
          SAFE_RELEASE(dmoBuffer.pBuffer);
          dwStatus = dmoBuffer.dwStatus;
        }
        if (FAILED(hr)) {
          _TraceCOMError(hr);
          keepRecording = false;
          assert(false);
          break;
        }
  
        ULONG bytesProduced = 0;
        BYTE* data;
        // Get a pointer to the data buffer. This should be valid until
        // the next call to ProcessOutput.
        hr = _mediaBuffer->GetBufferAndLength(&data, &bytesProduced);
        if (FAILED(hr)) {
          _TraceCOMError(hr);
          keepRecording = false;
          assert(false);
          break;
        }
  
        if (bytesProduced > 0) {
          const int kSamplesProduced = bytesProduced / _recAudioFrameSize;
          // TODO(andrew): verify that this is always satisfied. It might
          // be that ProcessOutput will try to return more than 10 ms if
          // we fail to call it frequently enough.
          assert(kSamplesProduced == static_cast<int>(_recBlockSize));
          assert(sizeof(BYTE) == sizeof(int8_t));
          _ptrAudioBuffer->SetRecordedBuffer(reinterpret_cast<int8_t*>(data),
                                             kSamplesProduced);
          _ptrAudioBuffer->SetVQEData(0, 0);
  
          _UnLock();  // Release lock while making the callback.
          _ptrAudioBuffer->DeliverRecordedData();
          _Lock();
        }
  
        // Reset length to indicate buffer availability.
        hr = _mediaBuffer->SetLength(0);
        if (FAILED(hr)) {
          _TraceCOMError(hr);
          keepRecording = false;
          assert(false);
          break;
        }
  
        if (!(dwStatus & DMO_OUTPUT_DATA_BUFFERF_INCOMPLETE)) {
          // The DMO cannot currently produce more data. This is the
          // normal case; otherwise it means the DMO had more than 10 ms
          // of data available and ProcessOutput should be called again.
          break;
        }
      }
    }
    // ---------------------------- THREAD LOOP ---------------------------- <<
  
    RevertCaptureThreadPriority();
  
    if (FAILED(hr)) {
      RTC_LOG(LS_ERROR)
          << "Recording error: capturing thread has ended prematurely";
    } else {
      RTC_LOG(LS_VERBOSE) << "Capturing thread is now terminated properly";
    }
  
    return hr;
  }
  
  // ----------------------------------------------------------------------------
  //  DoCaptureThread
  // ----------------------------------------------------------------------------
  
  DWORD AudioDeviceWindowsCore::DoCaptureThread() {
    bool keepRecording = true;
    HANDLE waitArray[2] = {_hShutdownCaptureEvent, _hCaptureSamplesReadyEvent};
    HRESULT hr = S_OK;
  
    LARGE_INTEGER t1;
  
    BYTE* syncBuffer = NULL;
    UINT32 syncBufIndex = 0;
  
    _readSamples = 0;
  
    // Initialize COM as MTA in this thread.
    ScopedCOMInitializer comInit(ScopedCOMInitializer::kMTA);
    if (!comInit.succeeded()) {
      RTC_LOG(LS_ERROR) << "failed to initialize COM in capture thread";
      return 1;
    }
  
    hr = InitCaptureThreadPriority();
    if (FAILED(hr)) {
      return hr;
    }
  
    _Lock();
  
    // Get size of capturing buffer (length is expressed as the number of audio
    // frames the buffer can hold). This value is fixed during the capturing
    // session.
    //
    UINT32 bufferLength = 0;
    if (_ptrClientIn == NULL) {
      RTC_LOG(LS_ERROR)
          << "input state has been modified before capture loop starts.";
      return 1;
    }
    hr = _ptrClientIn->GetBufferSize(&bufferLength);
    EXIT_ON_ERROR(hr);
    RTC_LOG(LS_VERBOSE) << "[CAPT] size of buffer       : " << bufferLength;
  
    // Allocate memory for sync buffer.
    // It is used for compensation between native 44.1 and internal 44.0 and
    // for cases when the capture buffer is larger than 10ms.
    //
    const UINT32 syncBufferSize = 2 * (bufferLength * _recAudioFrameSize);
    syncBuffer = new BYTE[syncBufferSize];
    if (syncBuffer == NULL) {
      return (DWORD)E_POINTER;
    }
    RTC_LOG(LS_VERBOSE) << "[CAPT] size of sync buffer  : " << syncBufferSize
                        << " [bytes]";
  
    // Get maximum latency for the current stream (will not change for the
    // lifetime of the IAudioClient object).
    //
    REFERENCE_TIME latency;
    _ptrClientIn->GetStreamLatency(&latency);
    RTC_LOG(LS_VERBOSE) << "[CAPT] max stream latency   : " << (DWORD)latency
                        << " (" << (double)(latency / 10000.0) << " ms)";
  
    // Get the length of the periodic interval separating successive processing
    // passes by the audio engine on the data in the endpoint buffer.
    //
    REFERENCE_TIME devPeriod = 0;
    REFERENCE_TIME devPeriodMin = 0;
    _ptrClientIn->GetDevicePeriod(&devPeriod, &devPeriodMin);
    RTC_LOG(LS_VERBOSE) << "[CAPT] device period        : " << (DWORD)devPeriod
                        << " (" << (double)(devPeriod / 10000.0) << " ms)";
  
    double extraDelayMS = (double)((latency + devPeriod) / 10000.0);
    RTC_LOG(LS_VERBOSE) << "[CAPT] extraDelayMS         : " << extraDelayMS;
  
    double endpointBufferSizeMS =
        10.0 * ((double)bufferLength / (double)_recBlockSize);
    RTC_LOG(LS_VERBOSE) << "[CAPT] endpointBufferSizeMS : "
                        << endpointBufferSizeMS;
  
    // Start up the capturing stream.
    //
    hr = _ptrClientIn->Start();
    EXIT_ON_ERROR(hr);
  
    _UnLock();
  
    // Set event which will ensure that the calling thread modifies the recording
    // state to true.
    //
    SetEvent(_hCaptureStartedEvent);
  
    // >> ---------------------------- THREAD LOOP ----------------------------
  
    while (keepRecording) {
      // Wait for a capture notification event or a shutdown event
      DWORD waitResult = WaitForMultipleObjects(2, waitArray, FALSE, 500);
      switch (waitResult) {
        case WAIT_OBJECT_0 + 0:  // _hShutdownCaptureEvent
          keepRecording = false;
          break;
        case WAIT_OBJECT_0 + 1:  // _hCaptureSamplesReadyEvent
          break;
        case WAIT_TIMEOUT:  // timeout notification
          RTC_LOG(LS_WARNING) << "capture event timed out after 0.5 seconds";
          goto Exit;
        default:  // unexpected error
          RTC_LOG(LS_WARNING) << "unknown wait termination on capture side";
          goto Exit;
      }
  
      while (keepRecording) {
        BYTE* pData = 0;
        UINT32 framesAvailable = 0;
        DWORD flags = 0;
        UINT64 recTime = 0;
        UINT64 recPos = 0;
  
        _Lock();
  
        // Sanity check to ensure that essential states are not modified
        // during the unlocked period.
        if (_ptrCaptureClient == NULL || _ptrClientIn == NULL) {
          _UnLock();
          RTC_LOG(LS_ERROR)
              << "input state has been modified during unlocked period";
          goto Exit;
        }
  
        //  Find out how much capture data is available
        //
        hr = _ptrCaptureClient->GetBuffer(
            &pData,            // packet which is ready to be read by used
            &framesAvailable,  // #frames in the captured packet (can be zero)
            &flags,            // support flags (check)
            &recPos,    // device position of first audio frame in data packet
            &recTime);  // value of performance counter at the time of recording
                        // the first audio frame
  
        if (SUCCEEDED(hr)) {
          if (AUDCLNT_S_BUFFER_EMPTY == hr) {
            // Buffer was empty => start waiting for a new capture notification
            // event
            _UnLock();
            break;
          }
  
          if (flags & AUDCLNT_BUFFERFLAGS_SILENT) {
            // Treat all of the data in the packet as silence and ignore the
            // actual data values.
            RTC_LOG(LS_WARNING) << "AUDCLNT_BUFFERFLAGS_SILENT";
            pData = NULL;
          }
  
          assert(framesAvailable != 0);
  
          if (pData) {
            CopyMemory(&syncBuffer[syncBufIndex * _recAudioFrameSize], pData,
                       framesAvailable * _recAudioFrameSize);
          } else {
            ZeroMemory(&syncBuffer[syncBufIndex * _recAudioFrameSize],
                       framesAvailable * _recAudioFrameSize);
          }
          assert(syncBufferSize >= (syncBufIndex * _recAudioFrameSize) +
                                       framesAvailable * _recAudioFrameSize);
  
          // Release the capture buffer
          //
          hr = _ptrCaptureClient->ReleaseBuffer(framesAvailable);
          EXIT_ON_ERROR(hr);
  
          _readSamples += framesAvailable;
          syncBufIndex += framesAvailable;
  
          QueryPerformanceCounter(&t1);
  
          // Get the current recording and playout delay.
          uint32_t sndCardRecDelay = (uint32_t)(
              ((((UINT64)t1.QuadPart * _perfCounterFactor) - recTime) / 10000) +
              (10 * syncBufIndex) / _recBlockSize - 10);
          uint32_t sndCardPlayDelay = static_cast<uint32_t>(_sndCardPlayDelay);
  
          while (syncBufIndex >= _recBlockSize) {
            if (_ptrAudioBuffer) {
              _ptrAudioBuffer->SetRecordedBuffer((const int8_t*)syncBuffer,
                                                 _recBlockSize);
              _ptrAudioBuffer->SetVQEData(sndCardPlayDelay, sndCardRecDelay);
  
              _ptrAudioBuffer->SetTypingStatus(KeyPressed());
  
              _UnLock();  // release lock while making the callback
              _ptrAudioBuffer->DeliverRecordedData();
              _Lock();  // restore the lock
  
              // Sanity check to ensure that essential states are not modified
              // during the unlocked period
              if (_ptrCaptureClient == NULL || _ptrClientIn == NULL) {
                _UnLock();
                RTC_LOG(LS_ERROR) << "input state has been modified during"
                                     " unlocked period";
                goto Exit;
              }
            }
  
            // store remaining data which was not able to deliver as 10ms segment
            MoveMemory(&syncBuffer[0],
                       &syncBuffer[_recBlockSize * _recAudioFrameSize],
                       (syncBufIndex - _recBlockSize) * _recAudioFrameSize);
            syncBufIndex -= _recBlockSize;
            sndCardRecDelay -= 10;
          }
        } else {
          // If GetBuffer returns AUDCLNT_E_BUFFER_ERROR, the thread consuming the
          // audio samples must wait for the next processing pass. The client
          // might benefit from keeping a count of the failed GetBuffer calls. If
          // GetBuffer returns this error repeatedly, the client can start a new
          // processing loop after shutting down the current client by calling
          // IAudioClient::Stop, IAudioClient::Reset, and releasing the audio
          // client.
          RTC_LOG(LS_ERROR) << "IAudioCaptureClient::GetBuffer returned"
                               " AUDCLNT_E_BUFFER_ERROR, hr = 0x"
                            << rtc::ToHex(hr);
          goto Exit;
        }
  
        _UnLock();
      }
    }
  
    // ---------------------------- THREAD LOOP ---------------------------- <<
  
    if (_ptrClientIn) {
      hr = _ptrClientIn->Stop();
    }
  
  Exit:
    if (FAILED(hr)) {
      _ptrClientIn->Stop();
      _UnLock();
      _TraceCOMError(hr);
    }
  
    RevertCaptureThreadPriority();
  
    _Lock();
  
    if (keepRecording) {
      if (_ptrClientIn != NULL) {
        hr = _ptrClientIn->Stop();
        if (FAILED(hr)) {
          _TraceCOMError(hr);
        }
        hr = _ptrClientIn->Reset();
        if (FAILED(hr)) {
          _TraceCOMError(hr);
        }
      }
  
      RTC_LOG(LS_ERROR)
          << "Recording error: capturing thread has ended pre-maturely";
    } else {
      RTC_LOG(LS_VERBOSE) << "_Capturing thread is now terminated properly";
    }
  
    SAFE_RELEASE(_ptrClientIn);
    SAFE_RELEASE(_ptrCaptureClient);
  
    _UnLock();
  
    if (syncBuffer) {
      delete[] syncBuffer;
    }
  
    return (DWORD)hr;
  }
  
  int32_t AudioDeviceWindowsCore::EnableBuiltInAEC(bool enable) {
    if (_recIsInitialized) {
      RTC_LOG(LS_ERROR)
          << "Attempt to set Windows AEC with recording already initialized";
      return -1;
    }
  
    if (_dmo == NULL) {
      RTC_LOG(LS_ERROR)
          << "Built-in AEC DMO was not initialized properly at create time";
      return -1;
    }
  
    _builtInAecEnabled = enable;
    return 0;
  }
  
  void AudioDeviceWindowsCore::_Lock() RTC_NO_THREAD_SAFETY_ANALYSIS {
    mutex_.Lock();
  }
  
  void AudioDeviceWindowsCore::_UnLock() RTC_NO_THREAD_SAFETY_ANALYSIS {
    mutex_.Unlock();
  }
  
  int AudioDeviceWindowsCore::SetDMOProperties() {
    HRESULT hr = S_OK;
    assert(_dmo != NULL);
  
    rtc::scoped_refptr<IPropertyStore> ps;
    {
      IPropertyStore* ptrPS = NULL;
      hr = _dmo->QueryInterface(IID_IPropertyStore,
                                reinterpret_cast<void**>(&ptrPS));
      if (FAILED(hr) || ptrPS == NULL) {
        _TraceCOMError(hr);
        return -1;
      }
      ps = ptrPS;
      SAFE_RELEASE(ptrPS);
    }
  
    // Set the AEC system mode.
    // SINGLE_CHANNEL_AEC - AEC processing only.
    if (SetVtI4Property(ps, MFPKEY_WMAAECMA_SYSTEM_MODE, SINGLE_CHANNEL_AEC)) {
      return -1;
    }
  
    // Set the AEC source mode.
    // VARIANT_TRUE - Source mode (we poll the AEC for captured data).
    if (SetBoolProperty(ps, MFPKEY_WMAAECMA_DMO_SOURCE_MODE, VARIANT_TRUE) ==
        -1) {
      return -1;
    }
  
    // Enable the feature mode.
    // This lets us override all the default processing settings below.
    if (SetBoolProperty(ps, MFPKEY_WMAAECMA_FEATURE_MODE, VARIANT_TRUE) == -1) {
      return -1;
    }
  
    // Disable analog AGC (default enabled).
    if (SetBoolProperty(ps, MFPKEY_WMAAECMA_MIC_GAIN_BOUNDER, VARIANT_FALSE) ==
        -1) {
      return -1;
    }
  
    // Disable noise suppression (default enabled).
    // 0 - Disabled, 1 - Enabled
    if (SetVtI4Property(ps, MFPKEY_WMAAECMA_FEATR_NS, 0) == -1) {
      return -1;
    }
  
    // Relevant parameters to leave at default settings:
    // MFPKEY_WMAAECMA_FEATR_AGC - Digital AGC (disabled).
    // MFPKEY_WMAAECMA_FEATR_CENTER_CLIP - AEC center clipping (enabled).
    // MFPKEY_WMAAECMA_FEATR_ECHO_LENGTH - Filter length (256 ms).
    //   TODO(andrew): investigate decresing the length to 128 ms.
    // MFPKEY_WMAAECMA_FEATR_FRAME_SIZE - Frame size (0).
    //   0 is automatic; defaults to 160 samples (or 10 ms frames at the
    //   selected 16 kHz) as long as mic array processing is disabled.
    // MFPKEY_WMAAECMA_FEATR_NOISE_FILL - Comfort noise (enabled).
    // MFPKEY_WMAAECMA_FEATR_VAD - VAD (disabled).
  
    // Set the devices selected by VoE. If using a default device, we need to
    // search for the device index.
    int inDevIndex = _inputDeviceIndex;
    int outDevIndex = _outputDeviceIndex;
    if (!_usingInputDeviceIndex) {
      ERole role = eCommunications;
      if (_inputDevice == AudioDeviceModule::kDefaultDevice) {
        role = eConsole;
      }
  
      if (_GetDefaultDeviceIndex(eCapture, role, &inDevIndex) == -1) {
        return -1;
      }
    }
  
    if (!_usingOutputDeviceIndex) {
      ERole role = eCommunications;
      if (_outputDevice == AudioDeviceModule::kDefaultDevice) {
        role = eConsole;
      }
  
      if (_GetDefaultDeviceIndex(eRender, role, &outDevIndex) == -1) {
        return -1;
      }
    }
  
    DWORD devIndex = static_cast<uint32_t>(outDevIndex << 16) +
                     static_cast<uint32_t>(0x0000ffff & inDevIndex);
    RTC_LOG(LS_VERBOSE) << "Capture device index: " << inDevIndex
                        << ", render device index: " << outDevIndex;
    if (SetVtI4Property(ps, MFPKEY_WMAAECMA_DEVICE_INDEXES, devIndex) == -1) {
      return -1;
    }
  
    return 0;
  }
  
  int AudioDeviceWindowsCore::SetBoolProperty(IPropertyStore* ptrPS,
                                              REFPROPERTYKEY key,
                                              VARIANT_BOOL value) {
    PROPVARIANT pv;
    PropVariantInit(&pv);
    pv.vt = VT_BOOL;
    pv.boolVal = value;
    HRESULT hr = ptrPS->SetValue(key, pv);
    PropVariantClear(&pv);
    if (FAILED(hr)) {
      _TraceCOMError(hr);
      return -1;
    }
    return 0;
  }
  
  int AudioDeviceWindowsCore::SetVtI4Property(IPropertyStore* ptrPS,
                                              REFPROPERTYKEY key,
                                              LONG value) {
    PROPVARIANT pv;
    PropVariantInit(&pv);
    pv.vt = VT_I4;
    pv.lVal = value;
    HRESULT hr = ptrPS->SetValue(key, pv);
    PropVariantClear(&pv);
    if (FAILED(hr)) {
      _TraceCOMError(hr);
      return -1;
    }
    return 0;
  }
  
  // ----------------------------------------------------------------------------
  //  _RefreshDeviceList
  //
  //  Creates a new list of endpoint rendering or capture devices after
  //  deleting any previously created (and possibly out-of-date) list of
  //  such devices.
  // ----------------------------------------------------------------------------
  
  int32_t AudioDeviceWindowsCore::_RefreshDeviceList(EDataFlow dir) {
    RTC_LOG(LS_VERBOSE) << __FUNCTION__;
  
    HRESULT hr = S_OK;
    IMMDeviceCollection* pCollection = NULL;
  
    assert(dir == eRender || dir == eCapture);
    assert(_ptrEnumerator != NULL);
  
    // Create a fresh list of devices using the specified direction
    hr = _ptrEnumerator->EnumAudioEndpoints(dir, DEVICE_STATE_ACTIVE,
                                            &pCollection);
    if (FAILED(hr)) {
      _TraceCOMError(hr);
      SAFE_RELEASE(pCollection);
      return -1;
    }
  
    if (dir == eRender) {
      SAFE_RELEASE(_ptrRenderCollection);
      _ptrRenderCollection = pCollection;
    } else {
      SAFE_RELEASE(_ptrCaptureCollection);
      _ptrCaptureCollection = pCollection;
    }
  
    return 0;
  }
  
  // ----------------------------------------------------------------------------
  //  _DeviceListCount
  //
  //  Gets a count of the endpoint rendering or capture devices in the
  //  current list of such devices.
  // ----------------------------------------------------------------------------
  
  int16_t AudioDeviceWindowsCore::_DeviceListCount(EDataFlow dir) {
    RTC_LOG(LS_VERBOSE) << __FUNCTION__;
  
    HRESULT hr = S_OK;
    UINT count = 0;
  
    assert(eRender == dir || eCapture == dir);
  
    if (eRender == dir && NULL != _ptrRenderCollection) {
      hr = _ptrRenderCollection->GetCount(&count);
    } else if (NULL != _ptrCaptureCollection) {
      hr = _ptrCaptureCollection->GetCount(&count);
    }
  
    if (FAILED(hr)) {
      _TraceCOMError(hr);
      return -1;
    }
  
    return static_cast<int16_t>(count);
  }
  
  // ----------------------------------------------------------------------------
  //  _GetListDeviceName
  //
  //  Gets the friendly name of an endpoint rendering or capture device
  //  from the current list of such devices. The caller uses an index
  //  into the list to identify the device.
  //
  //  Uses: _ptrRenderCollection or _ptrCaptureCollection which is updated
  //  in _RefreshDeviceList().
  // ----------------------------------------------------------------------------
  
  int32_t AudioDeviceWindowsCore::_GetListDeviceName(EDataFlow dir,
                                                     int index,
                                                     LPWSTR szBuffer,
                                                     int bufferLen) {
    RTC_LOG(LS_VERBOSE) << __FUNCTION__;
  
    HRESULT hr = S_OK;
    IMMDevice* pDevice = NULL;
  
    assert(dir == eRender || dir == eCapture);
  
    if (eRender == dir && NULL != _ptrRenderCollection) {
      hr = _ptrRenderCollection->Item(index, &pDevice);
    } else if (NULL != _ptrCaptureCollection) {
      hr = _ptrCaptureCollection->Item(index, &pDevice);
    }
  
    if (FAILED(hr)) {
      _TraceCOMError(hr);
      SAFE_RELEASE(pDevice);
      return -1;
    }
  
    int32_t res = _GetDeviceName(pDevice, szBuffer, bufferLen);
    SAFE_RELEASE(pDevice);
    return res;
  }
  
  // ----------------------------------------------------------------------------
  //  _GetDefaultDeviceName
  //
  //  Gets the friendly name of an endpoint rendering or capture device
  //  given a specified device role.
  //
  //  Uses: _ptrEnumerator
  // ----------------------------------------------------------------------------
  
  int32_t AudioDeviceWindowsCore::_GetDefaultDeviceName(EDataFlow dir,
                                                        ERole role,
                                                        LPWSTR szBuffer,
                                                        int bufferLen) {
    RTC_LOG(LS_VERBOSE) << __FUNCTION__;
  
    HRESULT hr = S_OK;
    IMMDevice* pDevice = NULL;
  
    assert(dir == eRender || dir == eCapture);
    assert(role == eConsole || role == eCommunications);
    assert(_ptrEnumerator != NULL);
  
    hr = _ptrEnumerator->GetDefaultAudioEndpoint(dir, role, &pDevice);
  
    if (FAILED(hr)) {
      _TraceCOMError(hr);
      SAFE_RELEASE(pDevice);
      return -1;
    }
  
    int32_t res = _GetDeviceName(pDevice, szBuffer, bufferLen);
    SAFE_RELEASE(pDevice);
    return res;
  }
  
  // ----------------------------------------------------------------------------
  //  _GetListDeviceID
  //
  //  Gets the unique ID string of an endpoint rendering or capture device
  //  from the current list of such devices. The caller uses an index
  //  into the list to identify the device.
  //
  //  Uses: _ptrRenderCollection or _ptrCaptureCollection which is updated
  //  in _RefreshDeviceList().
  // ----------------------------------------------------------------------------
  
  int32_t AudioDeviceWindowsCore::_GetListDeviceID(EDataFlow dir,
                                                   int index,
                                                   LPWSTR szBuffer,
                                                   int bufferLen) {
    RTC_LOG(LS_VERBOSE) << __FUNCTION__;
  
    HRESULT hr = S_OK;
    IMMDevice* pDevice = NULL;
  
    assert(dir == eRender || dir == eCapture);
  
    if (eRender == dir && NULL != _ptrRenderCollection) {
      hr = _ptrRenderCollection->Item(index, &pDevice);
    } else if (NULL != _ptrCaptureCollection) {
      hr = _ptrCaptureCollection->Item(index, &pDevice);
    }
  
    if (FAILED(hr)) {
      _TraceCOMError(hr);
      SAFE_RELEASE(pDevice);
      return -1;
    }
  
    int32_t res = _GetDeviceID(pDevice, szBuffer, bufferLen);
    SAFE_RELEASE(pDevice);
    return res;
  }
  
  // ----------------------------------------------------------------------------
  //  _GetDefaultDeviceID
  //
  //  Gets the uniqe device ID of an endpoint rendering or capture device
  //  given a specified device role.
  //
  //  Uses: _ptrEnumerator
  // ----------------------------------------------------------------------------
  
  int32_t AudioDeviceWindowsCore::_GetDefaultDeviceID(EDataFlow dir,
                                                      ERole role,
                                                      LPWSTR szBuffer,
                                                      int bufferLen) {
    RTC_LOG(LS_VERBOSE) << __FUNCTION__;
  
    HRESULT hr = S_OK;
    IMMDevice* pDevice = NULL;
  
    assert(dir == eRender || dir == eCapture);
    assert(role == eConsole || role == eCommunications);
    assert(_ptrEnumerator != NULL);
  
    hr = _ptrEnumerator->GetDefaultAudioEndpoint(dir, role, &pDevice);
  
    if (FAILED(hr)) {
      _TraceCOMError(hr);
      SAFE_RELEASE(pDevice);
      return -1;
    }
  
    int32_t res = _GetDeviceID(pDevice, szBuffer, bufferLen);
    SAFE_RELEASE(pDevice);
    return res;
  }
  
  int32_t AudioDeviceWindowsCore::_GetDefaultDeviceIndex(EDataFlow dir,
                                                         ERole role,
                                                         int* index) {
    RTC_LOG(LS_VERBOSE) << __FUNCTION__;
  
    HRESULT hr = S_OK;
    WCHAR szDefaultDeviceID[MAX_PATH] = {0};
    WCHAR szDeviceID[MAX_PATH] = {0};
  
    const size_t kDeviceIDLength = sizeof(szDeviceID) / sizeof(szDeviceID[0]);
    assert(kDeviceIDLength ==
           sizeof(szDefaultDeviceID) / sizeof(szDefaultDeviceID[0]));
  
    if (_GetDefaultDeviceID(dir, role, szDefaultDeviceID, kDeviceIDLength) ==
        -1) {
      return -1;
    }
  
    IMMDeviceCollection* collection = _ptrCaptureCollection;
    if (dir == eRender) {
      collection = _ptrRenderCollection;
    }
  
    if (!collection) {
      RTC_LOG(LS_ERROR) << "Device collection not valid";
      return -1;
    }
  
    UINT count = 0;
    hr = collection->GetCount(&count);
    if (FAILED(hr)) {
      _TraceCOMError(hr);
      return -1;
    }
  
    *index = -1;
    for (UINT i = 0; i < count; i++) {
      memset(szDeviceID, 0, sizeof(szDeviceID));
      rtc::scoped_refptr<IMMDevice> device;
      {
        IMMDevice* ptrDevice = NULL;
        hr = collection->Item(i, &ptrDevice);
        if (FAILED(hr) || ptrDevice == NULL) {
          _TraceCOMError(hr);
          return -1;
        }
        device = ptrDevice;
        SAFE_RELEASE(ptrDevice);
      }
  
      if (_GetDeviceID(device, szDeviceID, kDeviceIDLength) == -1) {
        return -1;
      }
  
      if (wcsncmp(szDefaultDeviceID, szDeviceID, kDeviceIDLength) == 0) {
        // Found a match.
        *index = i;
        break;
      }
    }
  
    if (*index == -1) {
      RTC_LOG(LS_ERROR) << "Unable to find collection index for default device";
      return -1;
    }
  
    return 0;
  }
  
  // ----------------------------------------------------------------------------
  //  _GetDeviceName
  // ----------------------------------------------------------------------------
  
  int32_t AudioDeviceWindowsCore::_GetDeviceName(IMMDevice* pDevice,
                                                 LPWSTR pszBuffer,
                                                 int bufferLen) {
    RTC_LOG(LS_VERBOSE) << __FUNCTION__;
  
    static const WCHAR szDefault[] = L"<Device not available>";
  
    HRESULT hr = E_FAIL;
    IPropertyStore* pProps = NULL;
    PROPVARIANT varName;
  
    assert(pszBuffer != NULL);
    assert(bufferLen > 0);
  
    if (pDevice != NULL) {
      hr = pDevice->OpenPropertyStore(STGM_READ, &pProps);
      if (FAILED(hr)) {
        RTC_LOG(LS_ERROR) << "IMMDevice::OpenPropertyStore failed, hr = 0x"
                          << rtc::ToHex(hr);
      }
    }
  
    // Initialize container for property value.
    PropVariantInit(&varName);
  
    if (SUCCEEDED(hr)) {
      // Get the endpoint device's friendly-name property.
      hr = pProps->GetValue(PKEY_Device_FriendlyName, &varName);
      if (FAILED(hr)) {
        RTC_LOG(LS_ERROR) << "IPropertyStore::GetValue failed, hr = 0x"
                          << rtc::ToHex(hr);
      }
    }
  
    if ((SUCCEEDED(hr)) && (VT_EMPTY == varName.vt)) {
      hr = E_FAIL;
      RTC_LOG(LS_ERROR) << "IPropertyStore::GetValue returned no value,"
                           " hr = 0x"
                        << rtc::ToHex(hr);
    }
  
    if ((SUCCEEDED(hr)) && (VT_LPWSTR != varName.vt)) {
      // The returned value is not a wide null terminated string.
      hr = E_UNEXPECTED;
      RTC_LOG(LS_ERROR) << "IPropertyStore::GetValue returned unexpected"
                           " type, hr = 0x"
                        << rtc::ToHex(hr);
    }
  
    if (SUCCEEDED(hr) && (varName.pwszVal != NULL)) {
      // Copy the valid device name to the provided ouput buffer.
      wcsncpy_s(pszBuffer, bufferLen, varName.pwszVal, _TRUNCATE);
    } else {
      // Failed to find the device name.
      wcsncpy_s(pszBuffer, bufferLen, szDefault, _TRUNCATE);
    }
  
    PropVariantClear(&varName);
    SAFE_RELEASE(pProps);
  
    return 0;
  }
  
  // ----------------------------------------------------------------------------
  //  _GetDeviceID
  // ----------------------------------------------------------------------------
  
  int32_t AudioDeviceWindowsCore::_GetDeviceID(IMMDevice* pDevice,
                                               LPWSTR pszBuffer,
                                               int bufferLen) {
    RTC_LOG(LS_VERBOSE) << __FUNCTION__;
  
    static const WCHAR szDefault[] = L"<Device not available>";
  
    HRESULT hr = E_FAIL;
    LPWSTR pwszID = NULL;
  
    assert(pszBuffer != NULL);
    assert(bufferLen > 0);
  
    if (pDevice != NULL) {
      hr = pDevice->GetId(&pwszID);
    }
  
    if (hr == S_OK) {
      // Found the device ID.
      wcsncpy_s(pszBuffer, bufferLen, pwszID, _TRUNCATE);
    } else {
      // Failed to find the device ID.
      wcsncpy_s(pszBuffer, bufferLen, szDefault, _TRUNCATE);
    }
  
    CoTaskMemFree(pwszID);
    return 0;
  }
  
  // ----------------------------------------------------------------------------
  //  _GetDefaultDevice
  // ----------------------------------------------------------------------------
  
  int32_t AudioDeviceWindowsCore::_GetDefaultDevice(EDataFlow dir,
                                                    ERole role,
                                                    IMMDevice** ppDevice) {
    RTC_LOG(LS_VERBOSE) << __FUNCTION__;
  
    HRESULT hr(S_OK);
  
    assert(_ptrEnumerator != NULL);
  
    hr = _ptrEnumerator->GetDefaultAudioEndpoint(dir, role, ppDevice);
    if (FAILED(hr)) {
      _TraceCOMError(hr);
      return -1;
    }
  
    return 0;
  }
  
  // ----------------------------------------------------------------------------
  //  _GetListDevice
  // ----------------------------------------------------------------------------
  
  int32_t AudioDeviceWindowsCore::_GetListDevice(EDataFlow dir,
                                                 int index,
                                                 IMMDevice** ppDevice) {
    HRESULT hr(S_OK);
  
    assert(_ptrEnumerator != NULL);
  
    IMMDeviceCollection* pCollection = NULL;
  
    hr = _ptrEnumerator->EnumAudioEndpoints(
        dir,
        DEVICE_STATE_ACTIVE,  // only active endpoints are OK
        &pCollection);
    if (FAILED(hr)) {
      _TraceCOMError(hr);
      SAFE_RELEASE(pCollection);
      return -1;
    }
  
    hr = pCollection->Item(index, ppDevice);
    if (FAILED(hr)) {
      _TraceCOMError(hr);
      SAFE_RELEASE(pCollection);
      return -1;
    }
  
    return 0;
  }
  
  // ----------------------------------------------------------------------------
  //  _EnumerateEndpointDevicesAll
  // ----------------------------------------------------------------------------
  
  int32_t AudioDeviceWindowsCore::_EnumerateEndpointDevicesAll(
      EDataFlow dataFlow) const {
    RTC_LOG(LS_VERBOSE) << __FUNCTION__;
  
    assert(_ptrEnumerator != NULL);
  
    HRESULT hr = S_OK;
    IMMDeviceCollection* pCollection = NULL;
    IMMDevice* pEndpoint = NULL;
    IPropertyStore* pProps = NULL;
    IAudioEndpointVolume* pEndpointVolume = NULL;
    LPWSTR pwszID = NULL;
  
    // Generate a collection of audio endpoint devices in the system.
    // Get states for *all* endpoint devices.
    // Output: IMMDeviceCollection interface.
    hr = _ptrEnumerator->EnumAudioEndpoints(
        dataFlow,  // data-flow direction (input parameter)
        DEVICE_STATE_ACTIVE | DEVICE_STATE_DISABLED | DEVICE_STATE_UNPLUGGED,
        &pCollection);  // release interface when done
  
    EXIT_ON_ERROR(hr);
  
    // use the IMMDeviceCollection interface...
  
    UINT count = 0;
  
    // Retrieve a count of the devices in the device collection.
    hr = pCollection->GetCount(&count);
    EXIT_ON_ERROR(hr);
    if (dataFlow == eRender)
      RTC_LOG(LS_VERBOSE) << "#rendering endpoint devices (counting all): "
                          << count;
    else if (dataFlow == eCapture)
      RTC_LOG(LS_VERBOSE) << "#capturing endpoint devices (counting all): "
                          << count;
  
    if (count == 0) {
      return 0;
    }
  
    // Each loop prints the name of an endpoint device.
    for (ULONG i = 0; i < count; i++) {
      RTC_LOG(LS_VERBOSE) << "Endpoint " << i << ":";
  
      // Get pointer to endpoint number i.
      // Output: IMMDevice interface.
      hr = pCollection->Item(i, &pEndpoint);
      CONTINUE_ON_ERROR(hr);
  
      // use the IMMDevice interface of the specified endpoint device...
  
      // Get the endpoint ID string (uniquely identifies the device among all
      // audio endpoint devices)
      hr = pEndpoint->GetId(&pwszID);
      CONTINUE_ON_ERROR(hr);
      RTC_LOG(LS_VERBOSE) << "ID string    : " << pwszID;
  
      // Retrieve an interface to the device's property store.
      // Output: IPropertyStore interface.
      hr = pEndpoint->OpenPropertyStore(STGM_READ, &pProps);
      CONTINUE_ON_ERROR(hr);
  
      // use the IPropertyStore interface...
  
      PROPVARIANT varName;
      // Initialize container for property value.
      PropVariantInit(&varName);
  
      // Get the endpoint's friendly-name property.
      // Example: "Speakers (Realtek High Definition Audio)"
      hr = pProps->GetValue(PKEY_Device_FriendlyName, &varName);
      CONTINUE_ON_ERROR(hr);
      RTC_LOG(LS_VERBOSE) << "friendly name: \"" << varName.pwszVal << "\"";
  
      // Get the endpoint's current device state
      DWORD dwState;
      hr = pEndpoint->GetState(&dwState);
      CONTINUE_ON_ERROR(hr);
      if (dwState & DEVICE_STATE_ACTIVE)
        RTC_LOG(LS_VERBOSE) << "state (0x" << rtc::ToHex(dwState)
                            << ")  : *ACTIVE*";
      if (dwState & DEVICE_STATE_DISABLED)
        RTC_LOG(LS_VERBOSE) << "state (0x" << rtc::ToHex(dwState)
                            << ")  : DISABLED";
      if (dwState & DEVICE_STATE_NOTPRESENT)
        RTC_LOG(LS_VERBOSE) << "state (0x" << rtc::ToHex(dwState)
                            << ")  : NOTPRESENT";
      if (dwState & DEVICE_STATE_UNPLUGGED)
        RTC_LOG(LS_VERBOSE) << "state (0x" << rtc::ToHex(dwState)
                            << ")  : UNPLUGGED";
  
      // Check the hardware volume capabilities.
      DWORD dwHwSupportMask = 0;
      hr = pEndpoint->Activate(__uuidof(IAudioEndpointVolume), CLSCTX_ALL, NULL,
                               (void**)&pEndpointVolume);
      CONTINUE_ON_ERROR(hr);
      hr = pEndpointVolume->QueryHardwareSupport(&dwHwSupportMask);
      CONTINUE_ON_ERROR(hr);
      if (dwHwSupportMask & ENDPOINT_HARDWARE_SUPPORT_VOLUME)
        // The audio endpoint device supports a hardware volume control
        RTC_LOG(LS_VERBOSE) << "hwmask (0x" << rtc::ToHex(dwHwSupportMask)
                            << ") : HARDWARE_SUPPORT_VOLUME";
      if (dwHwSupportMask & ENDPOINT_HARDWARE_SUPPORT_MUTE)
        // The audio endpoint device supports a hardware mute control
        RTC_LOG(LS_VERBOSE) << "hwmask (0x" << rtc::ToHex(dwHwSupportMask)
                            << ") : HARDWARE_SUPPORT_MUTE";
      if (dwHwSupportMask & ENDPOINT_HARDWARE_SUPPORT_METER)
        // The audio endpoint device supports a hardware peak meter
        RTC_LOG(LS_VERBOSE) << "hwmask (0x" << rtc::ToHex(dwHwSupportMask)
                            << ") : HARDWARE_SUPPORT_METER";
  
      // Check the channel count (#channels in the audio stream that enters or
      // leaves the audio endpoint device)
      UINT nChannelCount(0);
      hr = pEndpointVolume->GetChannelCount(&nChannelCount);
      CONTINUE_ON_ERROR(hr);
      RTC_LOG(LS_VERBOSE) << "#channels    : " << nChannelCount;
  
      if (dwHwSupportMask & ENDPOINT_HARDWARE_SUPPORT_VOLUME) {
        // Get the volume range.
        float fLevelMinDB(0.0);
        float fLevelMaxDB(0.0);
        float fVolumeIncrementDB(0.0);
        hr = pEndpointVolume->GetVolumeRange(&fLevelMinDB, &fLevelMaxDB,
                                             &fVolumeIncrementDB);
        CONTINUE_ON_ERROR(hr);
        RTC_LOG(LS_VERBOSE) << "volume range : " << fLevelMinDB << " (min), "
                            << fLevelMaxDB << " (max), " << fVolumeIncrementDB
                            << " (inc) [dB]";
  
        // The volume range from vmin = fLevelMinDB to vmax = fLevelMaxDB is
        // divided into n uniform intervals of size vinc = fVolumeIncrementDB,
        // where n = (vmax ?vmin) / vinc. The values vmin, vmax, and vinc are
        // measured in decibels. The client can set the volume level to one of n +
        // 1 discrete values in the range from vmin to vmax.
        int n = (int)((fLevelMaxDB - fLevelMinDB) / fVolumeIncrementDB);
        RTC_LOG(LS_VERBOSE) << "#intervals   : " << n;
  
        // Get information about the current step in the volume range.
        // This method represents the volume level of the audio stream that enters
        // or leaves the audio endpoint device as an index or "step" in a range of
        // discrete volume levels. Output value nStepCount is the number of steps
        // in the range. Output value nStep is the step index of the current
        // volume level. If the number of steps is n = nStepCount, then step index
        // nStep can assume values from 0 (minimum volume) to n ?1 (maximum
        // volume).
        UINT nStep(0);
        UINT nStepCount(0);
        hr = pEndpointVolume->GetVolumeStepInfo(&nStep, &nStepCount);
        CONTINUE_ON_ERROR(hr);
        RTC_LOG(LS_VERBOSE) << "volume steps : " << nStep << " (nStep), "
                            << nStepCount << " (nStepCount)";
      }
    Next:
      if (FAILED(hr)) {
        RTC_LOG(LS_VERBOSE) << "Error when logging device information";
      }
      CoTaskMemFree(pwszID);
      pwszID = NULL;
      PropVariantClear(&varName);
      SAFE_RELEASE(pProps);
      SAFE_RELEASE(pEndpoint);
      SAFE_RELEASE(pEndpointVolume);
    }
    SAFE_RELEASE(pCollection);
    return 0;
  
  Exit:
    _TraceCOMError(hr);
    CoTaskMemFree(pwszID);
    pwszID = NULL;
    SAFE_RELEASE(pCollection);
    SAFE_RELEASE(pEndpoint);
    SAFE_RELEASE(pEndpointVolume);
    SAFE_RELEASE(pProps);
    return -1;
  }
  
  // ----------------------------------------------------------------------------
  //  _TraceCOMError
  // ----------------------------------------------------------------------------
  
  void AudioDeviceWindowsCore::_TraceCOMError(HRESULT hr) const {
    wchar_t buf[MAXERRORLENGTH];
    wchar_t errorText[MAXERRORLENGTH];
  
    const DWORD dwFlags =
        FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS;
    const DWORD dwLangID = MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US);
  
    // Gets the system's human readable message string for this HRESULT.
    // All error message in English by default.
    DWORD messageLength = ::FormatMessageW(dwFlags, 0, hr, dwLangID, errorText,
                                           MAXERRORLENGTH, NULL);
  
    assert(messageLength <= MAXERRORLENGTH);
  
    // Trims tailing white space (FormatMessage() leaves a trailing cr-lf.).
    for (; messageLength && ::isspace(errorText[messageLength - 1]);
         --messageLength) {
      errorText[messageLength - 1] = '\0';
    }
  
    RTC_LOG(LS_ERROR) << "Core Audio method failed (hr=" << hr << ")";
    StringCchPrintfW(buf, MAXERRORLENGTH, L"Error details: ");
    StringCchCatW(buf, MAXERRORLENGTH, errorText);
    RTC_LOG(LS_ERROR) << rtc::ToUtf8(buf);
  }
  
  bool AudioDeviceWindowsCore::KeyPressed() const {
    int key_down = 0;
    for (int key = VK_SPACE; key < VK_NUMLOCK; key++) {
      short res = GetAsyncKeyState(key);
      key_down |= res & 0x1;  // Get the LSB
    }
    return (key_down > 0);
  }
  }  // namespace webrtc
  
  #endif  // WEBRTC_WINDOWS_CORE_AUDIO_BUILD
  