tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

cubeb_wasapi.cpp (115400B)


      1 /*
      2 * Copyright © 2013 Mozilla Foundation
      3 *
      4 * This program is made available under an ISC-style license.  See the
      5 * accompanying file LICENSE for details.
      6 */
      7 #ifndef _WIN32_WINNT
      8 #define _WIN32_WINNT 0x0603
      9 #endif // !_WIN32_WINNT
     10 #ifndef NOMINMAX
     11 #define NOMINMAX
     12 #endif // !NOMINMAX
     13 
     14 #include <algorithm>
     15 #include <atomic>
     16 #include <audioclient.h>
     17 #include <avrt.h>
     18 #include <cmath>
     19 #include <devicetopology.h>
     20 #include <initguid.h>
     21 #include <limits>
     22 #include <memory>
     23 #include <mmdeviceapi.h>
     24 #include <process.h>
     25 #include <stdint.h>
     26 #include <stdio.h>
     27 #include <stdlib.h>
     28 #include <vector>
     29 #include <windef.h>
     30 #include <windows.h>
     31 /* clang-format off */
     32 /* These need to be included after windows.h */
     33 #include <mmsystem.h>
     34 /* clang-format on */
     35 
     36 #include "cubeb-internal.h"
     37 #include "cubeb/cubeb.h"
     38 #include "cubeb_mixer.h"
     39 #include "cubeb_resampler.h"
     40 #include "cubeb_strings.h"
     41 #include "cubeb_tracing.h"
     42 #include "cubeb_utils.h"
     43 
     44 // Windows 10 exposes the IAudioClient3 interface to create low-latency streams.
     45 // Copy the interface definition from audioclient.h here to make the code
     46 // simpler and so that we can still access IAudioClient3 via COM if cubeb was
     47 // compiled against an older SDK.
     48 #ifndef __IAudioClient3_INTERFACE_DEFINED__
     49 #define __IAudioClient3_INTERFACE_DEFINED__
     50 MIDL_INTERFACE("7ED4EE07-8E67-4CD4-8C1A-2B7A5987AD42")
     51 IAudioClient3 : public IAudioClient
     52 {
     53 public:
     54  virtual HRESULT STDMETHODCALLTYPE GetSharedModeEnginePeriod(
     55      /* [annotation][in] */
     56      _In_ const WAVEFORMATEX * pFormat,
     57      /* [annotation][out] */
     58      _Out_ UINT32 * pDefaultPeriodInFrames,
     59      /* [annotation][out] */
     60      _Out_ UINT32 * pFundamentalPeriodInFrames,
     61      /* [annotation][out] */
     62      _Out_ UINT32 * pMinPeriodInFrames,
     63      /* [annotation][out] */
     64      _Out_ UINT32 * pMaxPeriodInFrames) = 0;
     65 
     66  virtual HRESULT STDMETHODCALLTYPE GetCurrentSharedModeEnginePeriod(
     67      /* [unique][annotation][out] */
     68      _Out_ WAVEFORMATEX * *ppFormat,
     69      /* [annotation][out] */
     70      _Out_ UINT32 * pCurrentPeriodInFrames) = 0;
     71 
     72  virtual HRESULT STDMETHODCALLTYPE InitializeSharedAudioStream(
     73      /* [annotation][in] */
     74      _In_ DWORD StreamFlags,
     75      /* [annotation][in] */
     76      _In_ UINT32 PeriodInFrames,
     77      /* [annotation][in] */
     78      _In_ const WAVEFORMATEX * pFormat,
     79      /* [annotation][in] */
     80      _In_opt_ LPCGUID AudioSessionGuid) = 0;
     81 };
     82 #ifdef __CRT_UUID_DECL
     83 // Required for MinGW
     84 __CRT_UUID_DECL(IAudioClient3, 0x7ED4EE07, 0x8E67, 0x4CD4, 0x8C, 0x1A, 0x2B,
     85                0x7A, 0x59, 0x87, 0xAD, 0x42)
     86 #endif
     87 #endif
     88 // Copied from audioclient.h in the Windows 10 SDK
     89 #ifndef AUDCLNT_E_ENGINE_PERIODICITY_LOCKED
     90 #define AUDCLNT_E_ENGINE_PERIODICITY_LOCKED AUDCLNT_ERR(0x028)
     91 #endif
     92 
     93 #ifndef PKEY_Device_FriendlyName
     94 DEFINE_PROPERTYKEY(PKEY_Device_FriendlyName, 0xa45c254e, 0xdf1c, 0x4efd, 0x80,
     95                   0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0,
     96                   14); // DEVPROP_TYPE_STRING
     97 #endif
     98 #ifndef PKEY_Device_InstanceId
     99 DEFINE_PROPERTYKEY(PKEY_Device_InstanceId, 0x78c34fc8, 0x104a, 0x4aca, 0x9e,
    100                   0xa4, 0x52, 0x4d, 0x52, 0x99, 0x6e, 0x57,
    101                   0x00000100); //    VT_LPWSTR
    102 #endif
    103 
    104 namespace {
    105 
    106 const int64_t LATENCY_NOT_AVAILABLE_YET = -1;
    107 
    108 const DWORD DEVICE_CHANGE_DEBOUNCE_MS = 250;
    109 
    110 struct com_heap_ptr_deleter {
    111  void operator()(void * ptr) const noexcept { CoTaskMemFree(ptr); }
    112 };
    113 
    114 template <typename T>
    115 using com_heap_ptr = std::unique_ptr<T, com_heap_ptr_deleter>;
    116 
    117 template <typename T, size_t N>
    118 constexpr size_t
    119 ARRAY_LENGTH(T (&)[N])
    120 {
    121  return N;
    122 }
    123 
    124 template <typename T> class no_addref_release : public T {
    125  ULONG STDMETHODCALLTYPE AddRef() = 0;
    126  ULONG STDMETHODCALLTYPE Release() = 0;
    127 };
    128 
    129 template <typename T> class com_ptr {
    130 public:
    131  com_ptr() noexcept = default;
    132 
    133  com_ptr(com_ptr const & other) noexcept = delete;
    134  com_ptr & operator=(com_ptr const & other) noexcept = delete;
    135  T ** operator&() const noexcept = delete;
    136 
    137  ~com_ptr() noexcept { release(); }
    138 
    139  com_ptr(com_ptr && other) noexcept : ptr(other.ptr) { other.ptr = nullptr; }
    140 
    141  com_ptr & operator=(com_ptr && other) noexcept
    142  {
    143    if (ptr != other.ptr) {
    144      release();
    145      ptr = other.ptr;
    146      other.ptr = nullptr;
    147    }
    148    return *this;
    149  }
    150 
    151  explicit operator bool() const noexcept { return nullptr != ptr; }
    152 
    153  no_addref_release<T> * operator->() const noexcept
    154  {
    155    return static_cast<no_addref_release<T> *>(ptr);
    156  }
    157 
    158  T * get() const noexcept { return ptr; }
    159 
    160  T ** receive() noexcept
    161  {
    162    XASSERT(ptr == nullptr);
    163    return &ptr;
    164  }
    165 
    166  void ** receive_vpp() noexcept
    167  {
    168    return reinterpret_cast<void **>(receive());
    169  }
    170 
    171  com_ptr & operator=(std::nullptr_t) noexcept
    172  {
    173    release();
    174    return *this;
    175  }
    176 
    177  void reset(T * p = nullptr) noexcept
    178  {
    179    release();
    180    ptr = p;
    181  }
    182 
    183 private:
    184  void release() noexcept
    185  {
    186    T * temp = ptr;
    187 
    188    if (temp) {
    189      ptr = nullptr;
    190      temp->Release();
    191    }
    192  }
    193 
    194  T * ptr = nullptr;
    195 };
    196 
    197 LONG
    198 wasapi_stream_add_ref(cubeb_stream * stm);
    199 LONG
    200 wasapi_stream_release(cubeb_stream * stm);
    201 
    202 struct auto_stream_ref {
    203  auto_stream_ref(cubeb_stream * stm_) : stm(stm_)
    204  {
    205    wasapi_stream_add_ref(stm);
    206  }
    207  ~auto_stream_ref() { wasapi_stream_release(stm); }
    208  cubeb_stream * stm;
    209 };
    210 
    211 extern cubeb_ops const wasapi_ops;
    212 
    213 static com_heap_ptr<wchar_t>
    214 wasapi_get_default_device_id(EDataFlow flow, ERole role,
    215                             IMMDeviceEnumerator * enumerator);
    216 
    217 struct wasapi_default_devices {
    218  wasapi_default_devices(IMMDeviceEnumerator * enumerator)
    219      : render_console_id(
    220            wasapi_get_default_device_id(eRender, eConsole, enumerator)),
    221        render_comms_id(
    222            wasapi_get_default_device_id(eRender, eCommunications, enumerator)),
    223        capture_console_id(
    224            wasapi_get_default_device_id(eCapture, eConsole, enumerator)),
    225        capture_comms_id(
    226            wasapi_get_default_device_id(eCapture, eCommunications, enumerator))
    227  {
    228  }
    229 
    230  bool is_default(EDataFlow flow, ERole role, wchar_t const * id)
    231  {
    232    wchar_t const * default_id = nullptr;
    233    if (flow == eRender && role == eConsole) {
    234      default_id = this->render_console_id.get();
    235    } else if (flow == eRender && role == eCommunications) {
    236      default_id = this->render_comms_id.get();
    237    } else if (flow == eCapture && role == eConsole) {
    238      default_id = this->capture_console_id.get();
    239    } else if (flow == eCapture && role == eCommunications) {
    240      default_id = this->capture_comms_id.get();
    241    }
    242 
    243    return default_id && wcscmp(id, default_id) == 0;
    244  }
    245 
    246 private:
    247  com_heap_ptr<wchar_t> render_console_id;
    248  com_heap_ptr<wchar_t> render_comms_id;
    249  com_heap_ptr<wchar_t> capture_console_id;
    250  com_heap_ptr<wchar_t> capture_comms_id;
    251 };
    252 
    253 struct AutoRegisterThread {
    254  AutoRegisterThread(const char * name) { CUBEB_REGISTER_THREAD(name); }
    255  ~AutoRegisterThread() { CUBEB_UNREGISTER_THREAD(); }
    256 };
    257 
    258 int
    259 wasapi_stream_stop(cubeb_stream * stm);
    260 int
    261 wasapi_stream_start(cubeb_stream * stm);
    262 void
    263 close_wasapi_stream(cubeb_stream * stm);
    264 int
    265 setup_wasapi_stream(cubeb_stream * stm);
    266 ERole
    267 pref_to_role(cubeb_stream_prefs param);
    268 int
    269 wasapi_create_device(cubeb * ctx, cubeb_device_info & ret,
    270                     IMMDeviceEnumerator * enumerator, IMMDevice * dev,
    271                     wasapi_default_devices * defaults);
    272 void
    273 wasapi_destroy_device(cubeb_device_info * device_info);
    274 static int
    275 wasapi_enumerate_devices_internal(cubeb * context, cubeb_device_type type,
    276                                  cubeb_device_collection * out,
    277                                  DWORD state_mask);
    278 static int
    279 wasapi_device_collection_destroy(cubeb * ctx,
    280                                 cubeb_device_collection * collection);
    281 static std::unique_ptr<char const[]>
    282 wstr_to_utf8(LPCWSTR str);
    283 static std::unique_ptr<wchar_t const[]>
    284 utf8_to_wstr(char const * str);
    285 
    286 } // namespace
    287 
    288 class wasapi_collection_notification_client;
    289 class monitor_device_notifications;
    290 
    291 typedef enum {
    292  /* Clear options */
    293  CUBEB_AUDIO_CLIENT2_NONE,
    294  /* Use AUDCLNT_STREAMOPTIONS_RAW  */
    295  CUBEB_AUDIO_CLIENT2_RAW,
    296  /* Use CUBEB_STREAM_PREF_COMMUNICATIONS */
    297  CUBEB_AUDIO_CLIENT2_VOICE
    298 } AudioClient2Option;
    299 
    300 struct cubeb {
    301  cubeb_ops const * ops = &wasapi_ops;
    302  owned_critical_section lock;
    303  cubeb_strings * device_ids;
    304  /* Device enumerator to get notifications when the
    305     device collection change. */
    306  com_ptr<IMMDeviceEnumerator> device_collection_enumerator;
    307  com_ptr<wasapi_collection_notification_client> collection_notification_client;
    308  /* Collection changed for input (capture) devices. */
    309  cubeb_device_collection_changed_callback input_collection_changed_callback =
    310      nullptr;
    311  void * input_collection_changed_user_ptr = nullptr;
    312  /* Collection changed for output (render) devices. */
    313  cubeb_device_collection_changed_callback output_collection_changed_callback =
    314      nullptr;
    315  void * output_collection_changed_user_ptr = nullptr;
    316  UINT64 performance_counter_frequency;
    317 };
    318 
    319 class wasapi_endpoint_notification_client;
    320 
    321 /* We have three possible callbacks we can use with a stream:
    322 * - input only
    323 * - output only
    324 * - synchronized input and output
    325 *
    326 * Returns true when we should continue to play, false otherwise.
    327 */
    328 typedef bool (*wasapi_refill_callback)(cubeb_stream * stm);
    329 
    330 struct cubeb_stream {
    331  /* Note: Must match cubeb_stream layout in cubeb.c. */
    332  cubeb * context = nullptr;
    333  void * user_ptr = nullptr;
    334  /**/
    335 
    336  /* Mixer pameters. We need to convert the input stream to this
    337     samplerate/channel layout, as WASAPI does not resample nor upmix
    338     itself. */
    339  cubeb_stream_params input_mix_params = {CUBEB_SAMPLE_FLOAT32NE,
    340                                          0,
    341                                          0,
    342                                          CUBEB_LAYOUT_UNDEFINED,
    343                                          CUBEB_STREAM_PREF_NONE,
    344                                          CUBEB_INPUT_PROCESSING_PARAM_NONE};
    345  cubeb_stream_params output_mix_params = {CUBEB_SAMPLE_FLOAT32NE,
    346                                           0,
    347                                           0,
    348                                           CUBEB_LAYOUT_UNDEFINED,
    349                                           CUBEB_STREAM_PREF_NONE,
    350                                           CUBEB_INPUT_PROCESSING_PARAM_NONE};
    351  /* Stream parameters. This is what the client requested,
    352   * and what will be presented in the callback. */
    353  cubeb_stream_params input_stream_params = {CUBEB_SAMPLE_FLOAT32NE,
    354                                             0,
    355                                             0,
    356                                             CUBEB_LAYOUT_UNDEFINED,
    357                                             CUBEB_STREAM_PREF_NONE,
    358                                             CUBEB_INPUT_PROCESSING_PARAM_NONE};
    359  cubeb_stream_params output_stream_params = {
    360      CUBEB_SAMPLE_FLOAT32NE,
    361      0,
    362      0,
    363      CUBEB_LAYOUT_UNDEFINED,
    364      CUBEB_STREAM_PREF_NONE,
    365      CUBEB_INPUT_PROCESSING_PARAM_NONE};
    366  /* A MMDevice role for this stream: either communication or console here. */
    367  ERole role;
    368  /* True if this stream will transport voice-data. */
    369  bool voice;
    370  /* True if the input device of this stream is using bluetooth handsfree. */
    371  bool input_bluetooth_handsfree;
    372  /* The input and output device, or NULL for default. */
    373  std::unique_ptr<const wchar_t[]> input_device_id;
    374  std::unique_ptr<const wchar_t[]> output_device_id;
    375  com_ptr<IMMDevice> input_device;
    376  com_ptr<IMMDevice> output_device;
    377  /* The latency initially requested for this stream, in frames. */
    378  unsigned latency = 0;
    379  cubeb_state_callback state_callback = nullptr;
    380  cubeb_data_callback data_callback = nullptr;
    381  wasapi_refill_callback refill_callback = nullptr;
    382  /* True when a loopback device is requested with no output device. In this
    383     case a dummy output device is opened to drive the loopback, but should not
    384     be exposed. */
    385  bool has_dummy_output = false;
    386  /* Lifetime considerations:
    387     - client, render_client, audio_clock and audio_stream_volume are interface
    388       pointer to the IAudioClient.
    389     - The lifetime for device_enumerator and notification_client, resampler,
    390       mix_buffer are the same as the cubeb_stream instance. */
    391 
    392  /* Main handle on the WASAPI stream. */
    393  com_ptr<IAudioClient> output_client;
    394  /* Interface pointer to use the event-driven interface. */
    395  com_ptr<IAudioRenderClient> render_client;
    396 #ifdef CUBEB_WASAPI_USE_IAUDIOSTREAMVOLUME
    397  /* Interface pointer to use the volume facilities. */
    398  com_ptr<IAudioStreamVolume> audio_stream_volume;
    399 #endif
    400  /* Interface pointer to use the stream audio clock. */
    401  com_ptr<IAudioClock> audio_clock;
    402  /* Frames written to the stream since it was opened. Reset on device
    403     change. Uses mix_params.rate. */
    404  UINT64 frames_written = 0;
    405  /* Frames written to the (logical) stream since it was first
    406     created. Updated on device change. Uses stream_params.rate. */
    407  UINT64 total_frames_written = 0;
    408  /* Last valid reported stream position.  Used to ensure the position
    409     reported by stream_get_position increases monotonically. */
    410  UINT64 prev_position = 0;
    411  /* Device enumerator to be able to be notified when the default
    412     device change. */
    413  com_ptr<IMMDeviceEnumerator> device_enumerator;
    414  /* Device notification client, to be able to be notified when the default
    415     audio device changes and route the audio to the new default audio output
    416     device */
    417  com_ptr<wasapi_endpoint_notification_client> notification_client;
    418  /* Main andle to the WASAPI capture stream. */
    419  com_ptr<IAudioClient> input_client;
    420  /* Interface to use the event driven capture interface */
    421  com_ptr<IAudioCaptureClient> capture_client;
    422  /* This event is set by the stream_destroy function, so the render loop can
    423     exit properly. */
    424  HANDLE shutdown_event = 0;
    425  /* Set by OnDefaultDeviceChanged when a stream reconfiguration is required.
    426     The reconfiguration is handled by the render loop thread. */
    427  HANDLE reconfigure_event = 0;
    428  /* This is set by WASAPI when we should refill the stream. */
    429  HANDLE refill_event = 0;
    430  /* This is set by WASAPI when we should read from the input stream. In
    431   * practice, we read from the input stream in the output callback, so
    432   * this is not used, but it is necessary to start getting input data. */
    433  HANDLE input_available_event = 0;
    434  /* Each cubeb_stream has its own thread. */
    435  HANDLE thread = 0;
    436  /* The lock protects all members that are touched by the render thread or
    437     change during a device reset, including: audio_clock, audio_stream_volume,
    438     client, frames_written, mix_params, total_frames_written, prev_position. */
    439  owned_critical_section stream_reset_lock;
    440  /* Maximum number of frames that can be passed down in a callback. */
    441  uint32_t input_buffer_frame_count = 0;
    442  /* Maximum number of frames that can be requested in a callback. */
    443  uint32_t output_buffer_frame_count = 0;
    444  /* Resampler instance. Resampling will only happen if necessary. */
    445  std::unique_ptr<cubeb_resampler, decltype(&cubeb_resampler_destroy)>
    446      resampler = {nullptr, cubeb_resampler_destroy};
    447  /* Mixer interfaces */
    448  std::unique_ptr<cubeb_mixer, decltype(&cubeb_mixer_destroy)> output_mixer = {
    449      nullptr, cubeb_mixer_destroy};
    450  std::unique_ptr<cubeb_mixer, decltype(&cubeb_mixer_destroy)> input_mixer = {
    451      nullptr, cubeb_mixer_destroy};
    452  /* A buffer for up/down mixing multi-channel audio output. */
    453  std::vector<BYTE> mix_buffer;
    454  /* WASAPI input works in "packets". We re-linearize the audio packets
    455   * into this buffer before handing it to the resampler. */
    456  std::unique_ptr<auto_array_wrapper> linear_input_buffer;
    457  /* Bytes per sample. This multiplied by the number of channels is the number
    458   * of bytes per frame. */
    459  size_t bytes_per_sample = 0;
    460  /* WAVEFORMATEXTENSIBLE sub-format: either PCM or float. */
    461  GUID waveformatextensible_sub_format = GUID_NULL;
    462  /* Stream volume.  Set via stream_set_volume and used to reset volume on
    463     device changes. */
    464  float volume = 1.0;
    465  /* True if the stream is draining. */
    466  bool draining = false;
    467  /* This needs an active audio input stream to be known, and is updated in the
    468   * first audio input callback. */
    469  std::atomic<int64_t> input_latency_hns{LATENCY_NOT_AVAILABLE_YET};
    470  /* Those attributes count the number of frames requested (resp. received) by
    471  the OS, to be able to detect drifts. This is only used for logging for now. */
    472  size_t total_input_frames = 0;
    473  size_t total_output_frames = 0;
    474  /* This is set by the render loop thread once it has obtained a reference to
    475   * COM and this stream object. */
    476  HANDLE thread_ready_event = 0;
    477  /* Keep a ref count on this stream object. After both stream_destroy has been
    478   * called and the render loop thread has exited, destroy this stream object.
    479   */
    480  LONG ref_count = 0;
    481 
    482  /* True if the stream is active, false if inactive. */
    483  bool active = false;
    484 };
    485 
    486 class monitor_device_notifications {
    487 public:
    488  monitor_device_notifications(cubeb * context) : cubeb_context(context)
    489  {
    490    create_thread();
    491  }
    492 
    493  ~monitor_device_notifications()
    494  {
    495    SetEvent(begin_shutdown);
    496    WaitForSingleObject(shutdown_complete, INFINITE);
    497    CloseHandle(thread);
    498 
    499    CloseHandle(input_changed);
    500    CloseHandle(output_changed);
    501    CloseHandle(begin_shutdown);
    502    CloseHandle(shutdown_complete);
    503  }
    504 
    505  void notify(EDataFlow flow)
    506  {
    507    XASSERT(cubeb_context);
    508    if (flow == eCapture && cubeb_context->input_collection_changed_callback) {
    509      bool res = SetEvent(input_changed);
    510      if (!res) {
    511        LOG("Failed to set input changed event");
    512      }
    513      return;
    514    }
    515    if (flow == eRender && cubeb_context->output_collection_changed_callback) {
    516      bool res = SetEvent(output_changed);
    517      if (!res) {
    518        LOG("Failed to set output changed event");
    519      }
    520    }
    521  }
    522 
    523 private:
    524  static unsigned int __stdcall thread_proc(LPVOID args)
    525  {
    526    AutoRegisterThread raii("WASAPI device notification thread");
    527    XASSERT(args);
    528    auto mdn = static_cast<monitor_device_notifications *>(args);
    529    mdn->notification_thread_loop();
    530    SetEvent(mdn->shutdown_complete);
    531    return 0;
    532  }
    533 
    534  void notification_thread_loop()
    535  {
    536    struct auto_com {
    537      auto_com()
    538      {
    539        HRESULT hr = CoInitializeEx(nullptr, COINIT_MULTITHREADED);
    540        XASSERT(SUCCEEDED(hr));
    541      }
    542      ~auto_com() { CoUninitialize(); }
    543    } com;
    544 
    545    HANDLE wait_array[3] = {
    546        input_changed,
    547        output_changed,
    548        begin_shutdown,
    549    };
    550 
    551    while (true) {
    552      Sleep(200);
    553 
    554      DWORD wait_result = WaitForMultipleObjects(ARRAY_LENGTH(wait_array),
    555                                                 wait_array, FALSE, INFINITE);
    556      if (wait_result == WAIT_OBJECT_0) { // input changed
    557        cubeb_context->input_collection_changed_callback(
    558            cubeb_context, cubeb_context->input_collection_changed_user_ptr);
    559      } else if (wait_result == WAIT_OBJECT_0 + 1) { // output changed
    560        cubeb_context->output_collection_changed_callback(
    561            cubeb_context, cubeb_context->output_collection_changed_user_ptr);
    562      } else if (wait_result == WAIT_OBJECT_0 + 2) { // shutdown
    563        break;
    564      } else {
    565        LOG("Unexpected result %lu", wait_result);
    566      }
    567    } // loop
    568  }
    569 
    570  void create_thread()
    571  {
    572    output_changed = CreateEvent(nullptr, 0, 0, nullptr);
    573    if (!output_changed) {
    574      LOG("Failed to create output changed event.");
    575      return;
    576    }
    577 
    578    input_changed = CreateEvent(nullptr, 0, 0, nullptr);
    579    if (!input_changed) {
    580      LOG("Failed to create input changed event.");
    581      return;
    582    }
    583 
    584    begin_shutdown = CreateEvent(nullptr, 0, 0, nullptr);
    585    if (!begin_shutdown) {
    586      LOG("Failed to create begin_shutdown event.");
    587      return;
    588    }
    589 
    590    shutdown_complete = CreateEvent(nullptr, 0, 0, nullptr);
    591    if (!shutdown_complete) {
    592      LOG("Failed to create shutdown_complete event.");
    593      return;
    594    }
    595 
    596    thread = (HANDLE)_beginthreadex(nullptr, 256 * 1024, thread_proc, this,
    597                                    STACK_SIZE_PARAM_IS_A_RESERVATION, nullptr);
    598    if (!thread) {
    599      LOG("Failed to create thread.");
    600      return;
    601    }
    602  }
    603 
    604  HANDLE thread = INVALID_HANDLE_VALUE;
    605  HANDLE output_changed = INVALID_HANDLE_VALUE;
    606  HANDLE input_changed = INVALID_HANDLE_VALUE;
    607  HANDLE begin_shutdown = INVALID_HANDLE_VALUE;
    608  HANDLE shutdown_complete = INVALID_HANDLE_VALUE;
    609 
    610  cubeb * cubeb_context = nullptr;
    611 };
    612 
    613 class wasapi_collection_notification_client : public IMMNotificationClient {
    614 public:
    615  /* The implementation of MSCOM was copied from MSDN. */
    616  ULONG STDMETHODCALLTYPE AddRef() { return InterlockedIncrement(&ref_count); }
    617 
    618  ULONG STDMETHODCALLTYPE Release()
    619  {
    620    ULONG ulRef = InterlockedDecrement(&ref_count);
    621    if (0 == ulRef) {
    622      delete this;
    623    }
    624    return ulRef;
    625  }
    626 
    627  HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, VOID ** ppvInterface)
    628  {
    629    if (__uuidof(IUnknown) == riid) {
    630      AddRef();
    631      *ppvInterface = (IUnknown *)this;
    632    } else if (__uuidof(IMMNotificationClient) == riid) {
    633      AddRef();
    634      *ppvInterface = (IMMNotificationClient *)this;
    635    } else {
    636      *ppvInterface = NULL;
    637      return E_NOINTERFACE;
    638    }
    639    return S_OK;
    640  }
    641 
    642  wasapi_collection_notification_client(cubeb * context)
    643      : ref_count(1), cubeb_context(context), monitor_notifications(context)
    644  {
    645    XASSERT(cubeb_context);
    646  }
    647 
    648  virtual ~wasapi_collection_notification_client() {}
    649 
    650  HRESULT STDMETHODCALLTYPE OnDefaultDeviceChanged(EDataFlow flow, ERole role,
    651                                                   LPCWSTR device_id)
    652  {
    653    LOG("collection: Audio device default changed, id = %S.", device_id);
    654 
    655    /* Default device changes count as device collection changes */
    656    monitor_notifications.notify(flow);
    657 
    658    return S_OK;
    659  }
    660 
    661  /* The remaining methods are not implemented, they simply log when called (if
    662     log is enabled), for debugging. */
    663  HRESULT STDMETHODCALLTYPE OnDeviceAdded(LPCWSTR device_id)
    664  {
    665    LOG("collection: Audio device added.");
    666    return S_OK;
    667  };
    668 
    669  HRESULT STDMETHODCALLTYPE OnDeviceRemoved(LPCWSTR device_id)
    670  {
    671    LOG("collection: Audio device removed.");
    672    return S_OK;
    673  }
    674 
    675  HRESULT STDMETHODCALLTYPE OnDeviceStateChanged(LPCWSTR device_id,
    676                                                 DWORD new_state)
    677  {
    678    XASSERT(cubeb_context->output_collection_changed_callback ||
    679            cubeb_context->input_collection_changed_callback);
    680    LOG("collection: Audio device state changed, id = %S, state = %lu.",
    681        device_id, new_state);
    682    EDataFlow flow;
    683    HRESULT hr = GetDataFlow(device_id, &flow);
    684    if (FAILED(hr)) {
    685      return hr;
    686    }
    687    monitor_notifications.notify(flow);
    688    return S_OK;
    689  }
    690 
    691  HRESULT STDMETHODCALLTYPE OnPropertyValueChanged(LPCWSTR device_id,
    692                                                   const PROPERTYKEY key)
    693  {
    694    // Audio device property value changed.
    695    return S_OK;
    696  }
    697 
    698 private:
    699  HRESULT GetDataFlow(LPCWSTR device_id, EDataFlow * flow)
    700  {
    701    com_ptr<IMMDevice> device;
    702    com_ptr<IMMEndpoint> endpoint;
    703 
    704    HRESULT hr = cubeb_context->device_collection_enumerator->GetDevice(
    705        device_id, device.receive());
    706    if (FAILED(hr)) {
    707      LOG("collection: Could not get device: %lx", hr);
    708      return hr;
    709    }
    710 
    711    hr = device->QueryInterface(IID_PPV_ARGS(endpoint.receive()));
    712    if (FAILED(hr)) {
    713      LOG("collection: Could not get endpoint: %lx", hr);
    714      return hr;
    715    }
    716 
    717    return endpoint->GetDataFlow(flow);
    718  }
    719 
    720  /* refcount for this instance, necessary to implement MSCOM semantics. */
    721  LONG ref_count;
    722 
    723  cubeb * cubeb_context = nullptr;
    724  monitor_device_notifications monitor_notifications;
    725 };
    726 
    727 class wasapi_endpoint_notification_client : public IMMNotificationClient {
    728 public:
    729  /* The implementation of MSCOM was copied from MSDN. */
    730  ULONG STDMETHODCALLTYPE AddRef() { return InterlockedIncrement(&ref_count); }
    731 
    732  ULONG STDMETHODCALLTYPE Release()
    733  {
    734    ULONG ulRef = InterlockedDecrement(&ref_count);
    735    if (0 == ulRef) {
    736      delete this;
    737    }
    738    return ulRef;
    739  }
    740 
    741  HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, VOID ** ppvInterface)
    742  {
    743    if (__uuidof(IUnknown) == riid) {
    744      AddRef();
    745      *ppvInterface = (IUnknown *)this;
    746    } else if (__uuidof(IMMNotificationClient) == riid) {
    747      AddRef();
    748      *ppvInterface = (IMMNotificationClient *)this;
    749    } else {
    750      *ppvInterface = NULL;
    751      return E_NOINTERFACE;
    752    }
    753    return S_OK;
    754  }
    755 
    756  wasapi_endpoint_notification_client(HANDLE event, ERole role)
    757      : ref_count(1), reconfigure_event(event), role(role),
    758        last_device_change(timeGetTime())
    759  {
    760  }
    761 
    762  virtual ~wasapi_endpoint_notification_client() {}
    763 
    764  HRESULT STDMETHODCALLTYPE OnDefaultDeviceChanged(EDataFlow flow, ERole role,
    765                                                   LPCWSTR device_id)
    766  {
    767    LOG("endpoint: Audio device default changed flow=%d role=%d "
    768        "new_device_id=%S.",
    769        flow, role, device_id);
    770 
    771    /* we only support a single stream type for now. */
    772    if (flow != eRender || role != this->role) {
    773      return S_OK;
    774    }
    775 
    776    DWORD last_change_ms = timeGetTime() - last_device_change;
    777    bool same_device = default_device_id && device_id &&
    778                       wcscmp(default_device_id.get(), device_id) == 0;
    779    LOG("endpoint: Audio device default changed last_change=%lu same_device=%d",
    780        last_change_ms, same_device);
    781    if (last_change_ms > DEVICE_CHANGE_DEBOUNCE_MS || !same_device) {
    782      if (device_id) {
    783        wchar_t * new_device_id = new wchar_t[wcslen(device_id) + 1];
    784        wcscpy(new_device_id, device_id);
    785        default_device_id.reset(new_device_id);
    786      } else {
    787        default_device_id.reset();
    788      }
    789      BOOL ok = SetEvent(reconfigure_event);
    790      LOG("endpoint: Audio device default changed: trigger reconfig");
    791      if (!ok) {
    792        LOG("endpoint: SetEvent on reconfigure_event failed: %lx",
    793            GetLastError());
    794      }
    795    }
    796 
    797    return S_OK;
    798  }
    799 
    800  /* The remaining methods are not implemented, they simply log when called (if
    801     log is enabled), for debugging. */
    802  HRESULT STDMETHODCALLTYPE OnDeviceAdded(LPCWSTR device_id)
    803  {
    804    LOG("endpoint: Audio device added.");
    805    return S_OK;
    806  };
    807 
    808  HRESULT STDMETHODCALLTYPE OnDeviceRemoved(LPCWSTR device_id)
    809  {
    810    LOG("endpoint: Audio device removed.");
    811    return S_OK;
    812  }
    813 
    814  HRESULT STDMETHODCALLTYPE OnDeviceStateChanged(LPCWSTR device_id,
    815                                                 DWORD new_state)
    816  {
    817    LOG("endpoint: Audio device state changed.");
    818    return S_OK;
    819  }
    820 
    821  HRESULT STDMETHODCALLTYPE OnPropertyValueChanged(LPCWSTR device_id,
    822                                                   const PROPERTYKEY key)
    823  {
    824    // Audio device property value changed.
    825    return S_OK;
    826  }
    827 
    828 private:
    829  /* refcount for this instance, necessary to implement MSCOM semantics. */
    830  LONG ref_count;
    831  HANDLE reconfigure_event;
    832  ERole role;
    833  std::unique_ptr<const wchar_t[]> default_device_id;
    834  DWORD last_device_change;
    835 };
    836 
    837 namespace {
    838 
    839 long
    840 wasapi_data_callback(cubeb_stream * stm, void * user_ptr,
    841                     void const * input_buffer, void * output_buffer,
    842                     long nframes)
    843 {
    844  return stm->data_callback(stm, user_ptr, input_buffer, output_buffer,
    845                            nframes);
    846 }
    847 
    848 void
    849 wasapi_state_callback(cubeb_stream * stm, void * user_ptr, cubeb_state state)
    850 {
    851  return stm->state_callback(stm, user_ptr, state);
    852 }
    853 
    854 char const *
    855 intern_device_id(cubeb * ctx, wchar_t const * id)
    856 {
    857  XASSERT(id);
    858 
    859  auto_lock lock(ctx->lock);
    860 
    861  std::unique_ptr<char const[]> tmp = wstr_to_utf8(id);
    862  if (!tmp) {
    863    return nullptr;
    864  }
    865 
    866  return cubeb_strings_intern(ctx->device_ids, tmp.get());
    867 }
    868 
    869 bool
    870 has_input(cubeb_stream * stm)
    871 {
    872  return stm->input_stream_params.rate != 0;
    873 }
    874 
    875 bool
    876 has_output(cubeb_stream * stm)
    877 {
    878  return stm->output_stream_params.rate != 0;
    879 }
    880 
    881 double
    882 stream_to_mix_samplerate_ratio(cubeb_stream_params & stream,
    883                               cubeb_stream_params & mixer)
    884 {
    885  return double(stream.rate) / mixer.rate;
    886 }
    887 
    888 /* Convert the channel layout into the corresponding KSAUDIO_CHANNEL_CONFIG.
    889   See more:
    890   https://msdn.microsoft.com/en-us/library/windows/hardware/ff537083(v=vs.85).aspx
    891 */
    892 
    893 cubeb_channel_layout
    894 mask_to_channel_layout(WAVEFORMATEX const * fmt)
    895 {
    896  cubeb_channel_layout mask = 0;
    897 
    898  if (fmt->wFormatTag == WAVE_FORMAT_EXTENSIBLE) {
    899    WAVEFORMATEXTENSIBLE const * ext =
    900        reinterpret_cast<WAVEFORMATEXTENSIBLE const *>(fmt);
    901    mask = ext->dwChannelMask;
    902  } else if (fmt->wFormatTag == WAVE_FORMAT_PCM ||
    903             fmt->wFormatTag == WAVE_FORMAT_IEEE_FLOAT) {
    904    if (fmt->nChannels == 1) {
    905      mask = CHANNEL_FRONT_CENTER;
    906    } else if (fmt->nChannels == 2) {
    907      mask = CHANNEL_FRONT_LEFT | CHANNEL_FRONT_RIGHT;
    908    }
    909  }
    910  return mask;
    911 }
    912 
    913 uint32_t
    914 get_rate(cubeb_stream * stm)
    915 {
    916  return has_input(stm) ? stm->input_stream_params.rate
    917                        : stm->output_stream_params.rate;
    918 }
    919 
    920 uint32_t
    921 hns_to_frames(uint32_t rate, REFERENCE_TIME hns)
    922 {
    923  return std::ceil((hns - 1) / 10000000.0 * rate);
    924 }
    925 
    926 uint32_t
    927 hns_to_frames(cubeb_stream * stm, REFERENCE_TIME hns)
    928 {
    929  return hns_to_frames(get_rate(stm), hns);
    930 }
    931 
    932 REFERENCE_TIME
    933 frames_to_hns(uint32_t rate, uint32_t frames)
    934 {
    935  return std::ceil(frames * 10000000.0 / rate);
    936 }
    937 
    938 /* This returns the size of a frame in the stream, before the eventual upmix
    939   occurs. */
    940 static size_t
    941 frames_to_bytes_before_mix(cubeb_stream * stm, size_t frames)
    942 {
    943  // This is called only when we has a output client.
    944  XASSERT(has_output(stm));
    945  return stm->output_stream_params.channels * stm->bytes_per_sample * frames;
    946 }
    947 
    948 /* This function handles the processing of the input and output audio,
    949 * converting it to rate and channel layout specified at initialization.
    950 * It then calls the data callback, via the resampler. */
    951 long
    952 refill(cubeb_stream * stm, void * input_buffer, long input_frames_count,
    953       void * output_buffer, long output_frames_needed)
    954 {
    955  XASSERT(!stm->draining);
    956  /* If we need to upmix after resampling, resample into the mix buffer to
    957     avoid a copy. Avoid exposing output if it is a dummy stream. */
    958  void * dest = nullptr;
    959  if (has_output(stm) && !stm->has_dummy_output) {
    960    if (stm->output_mixer) {
    961      dest = stm->mix_buffer.data();
    962    } else {
    963      dest = output_buffer;
    964    }
    965  }
    966 
    967  long out_frames =
    968      cubeb_resampler_fill(stm->resampler.get(), input_buffer,
    969                           &input_frames_count, dest, output_frames_needed);
    970  if (out_frames < 0) {
    971    ALOGV("Callback refill error: %ld", out_frames);
    972    wasapi_state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR);
    973    return out_frames;
    974  }
    975 
    976  float volume = 1.0;
    977  {
    978    auto_lock lock(stm->stream_reset_lock);
    979    stm->frames_written += out_frames;
    980    volume = stm->volume;
    981  }
    982 
    983  /* Go in draining mode if we got fewer frames than requested. If the stream
    984     has no output we still expect the callback to return number of frames read
    985     from input, otherwise we stop. */
    986  if ((out_frames < output_frames_needed) ||
    987      (!has_output(stm) && out_frames < input_frames_count)) {
    988    LOG("start draining.");
    989    stm->draining = true;
    990  }
    991 
    992  /* If this is not true, there will be glitches.
    993     It is alright to have produced less frames if we are draining, though. */
    994  XASSERT(out_frames == output_frames_needed || stm->draining ||
    995          !has_output(stm) || stm->has_dummy_output);
    996 
    997 #ifndef CUBEB_WASAPI_USE_IAUDIOSTREAMVOLUME
    998  if (has_output(stm) && !stm->has_dummy_output && volume != 1.0) {
    999    // Adjust the output volume.
   1000    // Note: This could be integrated with the remixing below.
   1001    long out_samples = out_frames * stm->output_stream_params.channels;
   1002    if (volume == 0.0) {
   1003      memset(dest, 0, out_samples * stm->bytes_per_sample);
   1004    } else {
   1005      switch (stm->output_stream_params.format) {
   1006      case CUBEB_SAMPLE_FLOAT32NE: {
   1007        float * buf = static_cast<float *>(dest);
   1008        for (long i = 0; i < out_samples; ++i) {
   1009          buf[i] *= volume;
   1010        }
   1011        break;
   1012      }
   1013      case CUBEB_SAMPLE_S16NE: {
   1014        short * buf = static_cast<short *>(dest);
   1015        for (long i = 0; i < out_samples; ++i) {
   1016          buf[i] = static_cast<short>(static_cast<float>(buf[i]) * volume);
   1017        }
   1018        break;
   1019      }
   1020      default:
   1021        XASSERT(false);
   1022      }
   1023    }
   1024  }
   1025 #endif
   1026 
   1027  // We don't bother mixing dummy output as it will be silenced, otherwise mix
   1028  // output if needed
   1029  if (!stm->has_dummy_output && has_output(stm) && stm->output_mixer) {
   1030    XASSERT(dest == stm->mix_buffer.data());
   1031    size_t dest_size =
   1032        out_frames * stm->output_stream_params.channels * stm->bytes_per_sample;
   1033    XASSERT(dest_size <= stm->mix_buffer.size());
   1034    size_t output_buffer_size =
   1035        out_frames * stm->output_mix_params.channels * stm->bytes_per_sample;
   1036    int ret = cubeb_mixer_mix(stm->output_mixer.get(), out_frames, dest,
   1037                              dest_size, output_buffer, output_buffer_size);
   1038    if (ret < 0) {
   1039      LOG("Error remixing content (%d)", ret);
   1040    }
   1041  }
   1042 
   1043  return out_frames;
   1044 }
   1045 
   1046 bool
   1047 trigger_async_reconfigure(cubeb_stream * stm)
   1048 {
   1049  XASSERT(stm && stm->reconfigure_event);
   1050  LOG("Try reconfiguring the stream");
   1051  BOOL ok = SetEvent(stm->reconfigure_event);
   1052  if (!ok) {
   1053    LOG("SetEvent on reconfigure_event failed: %lx", GetLastError());
   1054  }
   1055  return static_cast<bool>(ok);
   1056 }
   1057 
   1058 /* This helper grabs all the frames available from a capture client, put them in
   1059 * the linear_input_buffer.  This helper does not work with exclusive mode
   1060 * streams. */
   1061 bool
   1062 get_input_buffer(cubeb_stream * stm)
   1063 {
   1064  XASSERT(has_input(stm));
   1065 
   1066  HRESULT hr;
   1067  BYTE * input_packet = NULL;
   1068  DWORD flags;
   1069  UINT64 dev_pos;
   1070  UINT64 pc_position;
   1071  UINT32 next;
   1072  /* Get input packets until we have captured enough frames, and put them in a
   1073   * contiguous buffer. */
   1074  uint32_t offset = 0;
   1075  // If the input stream is event driven we should only ever expect to read a
   1076  // single packet each time. However, if we're pulling from the stream we may
   1077  // need to grab multiple packets worth of frames that have accumulated (so
   1078  // need a loop).
   1079  for (hr = stm->capture_client->GetNextPacketSize(&next); next > 0;
   1080       hr = stm->capture_client->GetNextPacketSize(&next)) {
   1081    if (hr == AUDCLNT_E_DEVICE_INVALIDATED) {
   1082      // Application can recover from this error. More info
   1083      // https://msdn.microsoft.com/en-us/library/windows/desktop/dd316605(v=vs.85).aspx
   1084      LOG("Input device invalidated error");
   1085      // No need to reset device if user asks to use particular device, or
   1086      // switching is disabled.
   1087      if (stm->input_device_id ||
   1088          (stm->input_stream_params.prefs &
   1089           CUBEB_STREAM_PREF_DISABLE_DEVICE_SWITCHING) ||
   1090          !trigger_async_reconfigure(stm)) {
   1091        wasapi_state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR);
   1092        return false;
   1093      }
   1094      return true;
   1095    }
   1096 
   1097    if (FAILED(hr)) {
   1098      LOG("cannot get next packet size: %lx", hr);
   1099      return false;
   1100    }
   1101 
   1102    UINT32 frames;
   1103    hr = stm->capture_client->GetBuffer(&input_packet, &frames, &flags,
   1104                                        &dev_pos, &pc_position);
   1105 
   1106    if (FAILED(hr)) {
   1107      LOG("GetBuffer failed for capture: %lx", hr);
   1108      return false;
   1109    }
   1110    XASSERT(frames == next);
   1111 
   1112    if (stm->context->performance_counter_frequency) {
   1113      LARGE_INTEGER now;
   1114      UINT64 now_hns;
   1115      // See
   1116      // https://docs.microsoft.com/en-us/windows/win32/api/audioclient/nf-audioclient-iaudiocaptureclient-getbuffer,
   1117      // section "Remarks".
   1118      QueryPerformanceCounter(&now);
   1119      now_hns =
   1120          10000000 * now.QuadPart / stm->context->performance_counter_frequency;
   1121      if (now_hns >= pc_position) {
   1122        stm->input_latency_hns = now_hns - pc_position;
   1123      }
   1124    }
   1125 
   1126    stm->total_input_frames += frames;
   1127 
   1128    UINT32 input_stream_samples = frames * stm->input_stream_params.channels;
   1129    // We do not explicitly handle the AUDCLNT_BUFFERFLAGS_DATA_DISCONTINUITY
   1130    // flag. There a two primary (non exhaustive) scenarios we anticipate this
   1131    // flag being set in:
   1132    //   - The first GetBuffer after Start has this flag undefined. In this
   1133    //     case the flag may be set but is meaningless and can be ignored.
   1134    //   - If a glitch is introduced into the input. This should not happen
   1135    //     for event based inputs, and should be mitigated by using a dummy
   1136    //     stream to drive input in the case of input only loopback. Without
   1137    //     a dummy output, input only loopback would glitch on silence. However,
   1138    //     the dummy input should push silence to the loopback and prevent
   1139    //     discontinuities. See
   1140    //     https://blogs.msdn.microsoft.com/matthew_van_eerde/2008/12/16/sample-wasapi-loopback-capture-record-what-you-hear/
   1141    // As the first scenario can be ignored, and we anticipate the second
   1142    // scenario is mitigated, we ignore the flag.
   1143    // For more info:
   1144    // https://msdn.microsoft.com/en-us/library/windows/desktop/dd370859(v=vs.85).aspx,
   1145    // https://msdn.microsoft.com/en-us/library/windows/desktop/dd371458(v=vs.85).aspx
   1146    if (flags & AUDCLNT_BUFFERFLAGS_SILENT) {
   1147      LOG("insert silence: ps=%u", frames);
   1148      stm->linear_input_buffer->push_silence(input_stream_samples);
   1149    } else {
   1150      if (stm->input_mixer) {
   1151        bool ok = stm->linear_input_buffer->reserve(
   1152            stm->linear_input_buffer->length() + input_stream_samples);
   1153        XASSERT(ok);
   1154        size_t input_packet_size =
   1155            frames * stm->input_mix_params.channels *
   1156            cubeb_sample_size(stm->input_mix_params.format);
   1157        size_t linear_input_buffer_size =
   1158            input_stream_samples *
   1159            cubeb_sample_size(stm->input_stream_params.format);
   1160        cubeb_mixer_mix(stm->input_mixer.get(), frames, input_packet,
   1161                        input_packet_size, stm->linear_input_buffer->end(),
   1162                        linear_input_buffer_size);
   1163        stm->linear_input_buffer->set_length(
   1164            stm->linear_input_buffer->length() + input_stream_samples);
   1165      } else {
   1166        stm->linear_input_buffer->push(input_packet, input_stream_samples);
   1167      }
   1168    }
   1169    hr = stm->capture_client->ReleaseBuffer(frames);
   1170    if (FAILED(hr)) {
   1171      LOG("FAILED to release intput buffer");
   1172      return false;
   1173    }
   1174    offset += input_stream_samples;
   1175  }
   1176 
   1177  ALOGV("get_input_buffer: got %d frames", offset);
   1178 
   1179  XASSERT(stm->linear_input_buffer->length() >= offset);
   1180 
   1181  return true;
   1182 }
   1183 
   1184 /* Get an output buffer from the render_client. It has to be released before
   1185 * exiting the callback. */
   1186 bool
   1187 get_output_buffer(cubeb_stream * stm, void *& buffer, size_t & frame_count)
   1188 {
   1189  UINT32 padding_out;
   1190  HRESULT hr;
   1191 
   1192  XASSERT(has_output(stm));
   1193 
   1194  hr = stm->output_client->GetCurrentPadding(&padding_out);
   1195  if (hr == AUDCLNT_E_DEVICE_INVALIDATED) {
   1196    // Application can recover from this error. More info
   1197    // https://msdn.microsoft.com/en-us/library/windows/desktop/dd316605(v=vs.85).aspx
   1198    LOG("Output device invalidated error");
   1199    // No need to reset device if user asks to use particular device, or
   1200    // switching is disabled.
   1201    if (stm->output_device_id ||
   1202        (stm->output_stream_params.prefs &
   1203         CUBEB_STREAM_PREF_DISABLE_DEVICE_SWITCHING) ||
   1204        !trigger_async_reconfigure(stm)) {
   1205      wasapi_state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR);
   1206      return false;
   1207    }
   1208    return true;
   1209  }
   1210 
   1211  if (FAILED(hr)) {
   1212    LOG("Failed to get padding: %lx", hr);
   1213    return false;
   1214  }
   1215 
   1216  XASSERT(padding_out <= stm->output_buffer_frame_count);
   1217 
   1218  if (stm->draining) {
   1219    if (padding_out == 0) {
   1220      LOG("Draining finished.");
   1221      wasapi_state_callback(stm, stm->user_ptr, CUBEB_STATE_DRAINED);
   1222      return false;
   1223    }
   1224    LOG("Draining.");
   1225    return true;
   1226  }
   1227 
   1228  frame_count = stm->output_buffer_frame_count - padding_out;
   1229  BYTE * output_buffer;
   1230 
   1231  hr = stm->render_client->GetBuffer(frame_count, &output_buffer);
   1232  if (FAILED(hr)) {
   1233    LOG("cannot get render buffer");
   1234    return false;
   1235  }
   1236 
   1237  buffer = output_buffer;
   1238 
   1239  return true;
   1240 }
   1241 
   1242 /**
   1243 * This function gets input data from a input device, and pass it along with an
   1244 * output buffer to the resamplers.  */
   1245 bool
   1246 refill_callback_duplex(cubeb_stream * stm)
   1247 {
   1248  HRESULT hr;
   1249  void * output_buffer = nullptr;
   1250  size_t output_frames = 0;
   1251  size_t input_frames;
   1252  bool rv;
   1253 
   1254  XASSERT(has_input(stm) && has_output(stm));
   1255 
   1256  if (stm->input_stream_params.prefs & CUBEB_STREAM_PREF_LOOPBACK) {
   1257    rv = get_input_buffer(stm);
   1258    if (!rv) {
   1259      return rv;
   1260    }
   1261  }
   1262 
   1263  input_frames =
   1264      stm->linear_input_buffer->length() / stm->input_stream_params.channels;
   1265 
   1266  rv = get_output_buffer(stm, output_buffer, output_frames);
   1267  if (!rv) {
   1268    return rv;
   1269  }
   1270 
   1271  /* This can only happen when debugging, and having breakpoints set in the
   1272   * callback in a way that it makes the stream underrun. */
   1273  if (output_frames == 0) {
   1274    return true;
   1275  }
   1276 
   1277  /* Wait for draining is not important on duplex. */
   1278  if (stm->draining) {
   1279    return false;
   1280  }
   1281 
   1282  stm->total_output_frames += output_frames;
   1283 
   1284  ALOGV("in: %llu, out: %llu, missing: %ld, ratio: %f",
   1285        (unsigned long long)stm->total_input_frames,
   1286        (unsigned long long)stm->total_output_frames,
   1287        static_cast<long long>(stm->total_output_frames) -
   1288            static_cast<long long>(stm->total_input_frames),
   1289        static_cast<float>(stm->total_output_frames) / stm->total_input_frames);
   1290 
   1291  long got;
   1292  if (stm->has_dummy_output) {
   1293    ALOGV(
   1294        "Duplex callback (dummy output): input frames: %Iu, output frames: %Iu",
   1295        input_frames, output_frames);
   1296 
   1297    // We don't want to expose the dummy output to the callback so don't pass
   1298    // the output buffer (it will be released later with silence in it)
   1299    got =
   1300        refill(stm, stm->linear_input_buffer->data(), input_frames, nullptr, 0);
   1301 
   1302  } else {
   1303    ALOGV("Duplex callback: input frames: %Iu, output frames: %Iu",
   1304          input_frames, output_frames);
   1305 
   1306    got = refill(stm, stm->linear_input_buffer->data(), input_frames,
   1307                 output_buffer, output_frames);
   1308  }
   1309 
   1310  stm->linear_input_buffer->clear();
   1311 
   1312  if (stm->has_dummy_output) {
   1313    // If output is a dummy output, make sure it's silent
   1314    hr = stm->render_client->ReleaseBuffer(output_frames,
   1315                                           AUDCLNT_BUFFERFLAGS_SILENT);
   1316  } else {
   1317    hr = stm->render_client->ReleaseBuffer(output_frames, 0);
   1318  }
   1319  if (FAILED(hr)) {
   1320    LOG("failed to release buffer: %lx", hr);
   1321    return false;
   1322  }
   1323  if (got < 0) {
   1324    return false;
   1325  }
   1326  return true;
   1327 }
   1328 
   1329 bool
   1330 refill_callback_input(cubeb_stream * stm)
   1331 {
   1332  bool rv;
   1333  size_t input_frames;
   1334 
   1335  XASSERT(has_input(stm) && !has_output(stm));
   1336 
   1337  rv = get_input_buffer(stm);
   1338  if (!rv) {
   1339    return rv;
   1340  }
   1341 
   1342  input_frames =
   1343      stm->linear_input_buffer->length() / stm->input_stream_params.channels;
   1344  if (!input_frames) {
   1345    return true;
   1346  }
   1347 
   1348  ALOGV("Input callback: input frames: %Iu", input_frames);
   1349 
   1350  long read =
   1351      refill(stm, stm->linear_input_buffer->data(), input_frames, nullptr, 0);
   1352  if (read < 0) {
   1353    return false;
   1354  }
   1355 
   1356  stm->linear_input_buffer->clear();
   1357 
   1358  return !stm->draining;
   1359 }
   1360 
   1361 bool
   1362 refill_callback_output(cubeb_stream * stm)
   1363 {
   1364  bool rv;
   1365  HRESULT hr;
   1366  void * output_buffer = nullptr;
   1367  size_t output_frames = 0;
   1368 
   1369  XASSERT(!has_input(stm) && has_output(stm));
   1370 
   1371  rv = get_output_buffer(stm, output_buffer, output_frames);
   1372  if (!rv) {
   1373    return rv;
   1374  }
   1375 
   1376  if (stm->draining || output_frames == 0) {
   1377    return true;
   1378  }
   1379 
   1380  long got = refill(stm, nullptr, 0, output_buffer, output_frames);
   1381 
   1382  ALOGV("Output callback: output frames requested: %Iu, got %ld", output_frames,
   1383        got);
   1384  if (got < 0) {
   1385    return false;
   1386  }
   1387  XASSERT(size_t(got) == output_frames || stm->draining);
   1388 
   1389  hr = stm->render_client->ReleaseBuffer(got, 0);
   1390  if (FAILED(hr)) {
   1391    LOG("failed to release buffer: %lx", hr);
   1392    return false;
   1393  }
   1394 
   1395  return size_t(got) == output_frames || stm->draining;
   1396 }
   1397 
   1398 void
   1399 wasapi_stream_destroy(cubeb_stream * stm);
   1400 
   1401 static unsigned int __stdcall wasapi_stream_render_loop(LPVOID stream)
   1402 {
   1403  AutoRegisterThread raii("cubeb rendering thread");
   1404  cubeb_stream * stm = static_cast<cubeb_stream *>(stream);
   1405 
   1406  auto_stream_ref stream_ref(stm);
   1407  struct auto_com {
   1408    auto_com()
   1409    {
   1410      HRESULT hr = CoInitializeEx(nullptr, COINIT_MULTITHREADED);
   1411      XASSERT(SUCCEEDED(hr));
   1412    }
   1413    ~auto_com() { CoUninitialize(); }
   1414  } com;
   1415 
   1416  bool is_playing = true;
   1417  HANDLE wait_array[4] = {stm->shutdown_event, stm->reconfigure_event,
   1418                          stm->refill_event, stm->input_available_event};
   1419  HANDLE mmcss_handle = NULL;
   1420  HRESULT hr = 0;
   1421  DWORD mmcss_task_index = 0;
   1422 
   1423  // Signal wasapi_stream_start that we've initialized COM and incremented
   1424  // the stream's ref_count.
   1425  BOOL ok = SetEvent(stm->thread_ready_event);
   1426  if (!ok) {
   1427    LOG("thread_ready SetEvent failed: %lx", GetLastError());
   1428    return 0;
   1429  }
   1430 
   1431  /* We could consider using "Pro Audio" here for WebAudio and
   1432     maybe WebRTC. */
   1433  mmcss_handle = AvSetMmThreadCharacteristicsA("Audio", &mmcss_task_index);
   1434  if (!mmcss_handle) {
   1435    /* This is not fatal, but we might glitch under heavy load. */
   1436    LOG("Unable to use mmcss to bump the render thread priority: %lx",
   1437        GetLastError());
   1438  }
   1439 
   1440  while (is_playing) {
   1441    DWORD waitResult = WaitForMultipleObjects(ARRAY_LENGTH(wait_array),
   1442                                              wait_array, FALSE, INFINITE);
   1443    switch (waitResult) {
   1444    case WAIT_OBJECT_0: { /* shutdown */
   1445      is_playing = false;
   1446      /* We don't check if the drain is actually finished here, we just want to
   1447         shutdown. */
   1448      if (stm->draining) {
   1449        wasapi_state_callback(stm, stm->user_ptr, CUBEB_STATE_DRAINED);
   1450      }
   1451      continue;
   1452    }
   1453    case WAIT_OBJECT_0 + 1: { /* reconfigure */
   1454      auto_lock lock(stm->stream_reset_lock);
   1455      if (!stm->active) {
   1456        /* Avoid reconfiguring, stream start will handle it. */
   1457        LOG("Stream is not active, ignoring reconfigure.");
   1458        continue;
   1459      }
   1460      XASSERT(stm->output_client || stm->input_client);
   1461      LOG("Reconfiguring the stream");
   1462      /* Close the stream */
   1463      bool was_running = false;
   1464      if (stm->output_client) {
   1465        was_running = stm->output_client->Stop() == S_OK;
   1466        LOG("Output stopped.");
   1467      }
   1468      if (stm->input_client) {
   1469        was_running = stm->input_client->Stop() == S_OK;
   1470        LOG("Input stopped.");
   1471      }
   1472      close_wasapi_stream(stm);
   1473      LOG("Stream closed.");
   1474      /* Reopen a stream and start it immediately. This will automatically
   1475          pick the new default device for this role. */
   1476      int r = setup_wasapi_stream(stm);
   1477      if (r != CUBEB_OK) {
   1478        LOG("Error setting up the stream during reconfigure.");
   1479        /* Don't destroy the stream here, since we expect the caller to do
   1480            so after the error has propagated via the state callback. */
   1481        is_playing = false;
   1482        hr = E_FAIL;
   1483        continue;
   1484      }
   1485      LOG("Stream setup successfuly.");
   1486      XASSERT(stm->output_client || stm->input_client);
   1487      if (was_running && stm->output_client) {
   1488        hr = stm->output_client->Start();
   1489        if (FAILED(hr)) {
   1490          LOG("Error starting output after reconfigure, error: %lx", hr);
   1491          is_playing = false;
   1492          continue;
   1493        }
   1494        LOG("Output started after reconfigure.");
   1495      }
   1496      if (was_running && stm->input_client) {
   1497        hr = stm->input_client->Start();
   1498        if (FAILED(hr)) {
   1499          LOG("Error starting input after reconfiguring, error: %lx", hr);
   1500          is_playing = false;
   1501          continue;
   1502        }
   1503        LOG("Input started after reconfigure.");
   1504      }
   1505      break;
   1506    }
   1507    case WAIT_OBJECT_0 + 2: /* refill */
   1508      XASSERT((has_input(stm) && has_output(stm)) ||
   1509              (!has_input(stm) && has_output(stm)));
   1510      is_playing = stm->refill_callback(stm);
   1511      break;
   1512    case WAIT_OBJECT_0 + 3: { /* input available */
   1513      bool rv = get_input_buffer(stm);
   1514      if (!rv) {
   1515        is_playing = false;
   1516        continue;
   1517      }
   1518 
   1519      if (!has_output(stm)) {
   1520        is_playing = stm->refill_callback(stm);
   1521      }
   1522 
   1523      break;
   1524    }
   1525    default:
   1526      LOG("render_loop: waitResult=%lu (lastError=%lu) unhandled, exiting",
   1527          waitResult, GetLastError());
   1528      is_playing = false;
   1529      hr = E_FAIL;
   1530      continue;
   1531    }
   1532  }
   1533 
   1534  // Stop audio clients since this thread will no longer service
   1535  // the events.
   1536  if (stm->output_client) {
   1537    stm->output_client->Stop();
   1538  }
   1539  if (stm->input_client) {
   1540    stm->input_client->Stop();
   1541  }
   1542 
   1543  if (mmcss_handle) {
   1544    AvRevertMmThreadCharacteristics(mmcss_handle);
   1545  }
   1546 
   1547  if (FAILED(hr)) {
   1548    wasapi_state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR);
   1549  }
   1550 
   1551  return 0;
   1552 }
   1553 
   1554 void
   1555 wasapi_destroy(cubeb * context);
   1556 
   1557 HRESULT
   1558 register_notification_client(cubeb_stream * stm)
   1559 {
   1560  XASSERT(stm->device_enumerator && !stm->notification_client);
   1561 
   1562  stm->notification_client.reset(new wasapi_endpoint_notification_client(
   1563      stm->reconfigure_event, stm->role));
   1564 
   1565  HRESULT hr = stm->device_enumerator->RegisterEndpointNotificationCallback(
   1566      stm->notification_client.get());
   1567  if (FAILED(hr)) {
   1568    LOG("Could not register endpoint notification callback: %lx", hr);
   1569    stm->notification_client = nullptr;
   1570  }
   1571 
   1572  return hr;
   1573 }
   1574 
   1575 HRESULT
   1576 unregister_notification_client(cubeb_stream * stm)
   1577 {
   1578  XASSERT(stm->device_enumerator && stm->notification_client);
   1579 
   1580  HRESULT hr = stm->device_enumerator->UnregisterEndpointNotificationCallback(
   1581      stm->notification_client.get());
   1582  if (FAILED(hr)) {
   1583    // We can't really do anything here, we'll probably leak the
   1584    // notification client.
   1585    return S_OK;
   1586  }
   1587 
   1588  stm->notification_client = nullptr;
   1589 
   1590  return S_OK;
   1591 }
   1592 
   1593 HRESULT
   1594 get_endpoint(com_ptr<IMMDevice> & device, LPCWSTR devid)
   1595 {
   1596  com_ptr<IMMDeviceEnumerator> enumerator;
   1597  HRESULT hr =
   1598      CoCreateInstance(__uuidof(MMDeviceEnumerator), NULL, CLSCTX_INPROC_SERVER,
   1599                       IID_PPV_ARGS(enumerator.receive()));
   1600  if (FAILED(hr)) {
   1601    LOG("Could not get device enumerator: %lx", hr);
   1602    return hr;
   1603  }
   1604 
   1605  hr = enumerator->GetDevice(devid, device.receive());
   1606  if (FAILED(hr)) {
   1607    LOG("Could not get device: %lx", hr);
   1608    return hr;
   1609  }
   1610 
   1611  return S_OK;
   1612 }
   1613 
   1614 HRESULT
   1615 register_collection_notification_client(cubeb * context)
   1616 {
   1617  context->lock.assert_current_thread_owns();
   1618  XASSERT(!context->device_collection_enumerator &&
   1619          !context->collection_notification_client);
   1620  HRESULT hr = CoCreateInstance(
   1621      __uuidof(MMDeviceEnumerator), NULL, CLSCTX_INPROC_SERVER,
   1622      IID_PPV_ARGS(context->device_collection_enumerator.receive()));
   1623  if (FAILED(hr)) {
   1624    LOG("Could not get device enumerator: %lx", hr);
   1625    return hr;
   1626  }
   1627 
   1628  context->collection_notification_client.reset(
   1629      new wasapi_collection_notification_client(context));
   1630 
   1631  hr = context->device_collection_enumerator
   1632           ->RegisterEndpointNotificationCallback(
   1633               context->collection_notification_client.get());
   1634  if (FAILED(hr)) {
   1635    LOG("Could not register endpoint notification callback: %lx", hr);
   1636    context->collection_notification_client.reset();
   1637    context->device_collection_enumerator.reset();
   1638  }
   1639 
   1640  return hr;
   1641 }
   1642 
   1643 HRESULT
   1644 unregister_collection_notification_client(cubeb * context)
   1645 {
   1646  context->lock.assert_current_thread_owns();
   1647  XASSERT(context->device_collection_enumerator &&
   1648          context->collection_notification_client);
   1649  HRESULT hr = context->device_collection_enumerator
   1650                   ->UnregisterEndpointNotificationCallback(
   1651                       context->collection_notification_client.get());
   1652  if (FAILED(hr)) {
   1653    return hr;
   1654  }
   1655 
   1656  context->collection_notification_client = nullptr;
   1657  context->device_collection_enumerator = nullptr;
   1658 
   1659  return hr;
   1660 }
   1661 
   1662 HRESULT
   1663 get_default_endpoint(com_ptr<IMMDevice> & device, EDataFlow direction,
   1664                     ERole role)
   1665 {
   1666  com_ptr<IMMDeviceEnumerator> enumerator;
   1667  HRESULT hr =
   1668      CoCreateInstance(__uuidof(MMDeviceEnumerator), NULL, CLSCTX_INPROC_SERVER,
   1669                       IID_PPV_ARGS(enumerator.receive()));
   1670  if (FAILED(hr)) {
   1671    LOG("Could not get device enumerator: %lx", hr);
   1672    return hr;
   1673  }
   1674  hr = enumerator->GetDefaultAudioEndpoint(direction, role, device.receive());
   1675  if (FAILED(hr)) {
   1676    LOG("Could not get default audio endpoint: %lx", hr);
   1677    return hr;
   1678  }
   1679 
   1680  return ERROR_SUCCESS;
   1681 }
   1682 
   1683 double
   1684 current_stream_delay(cubeb_stream * stm)
   1685 {
   1686  stm->stream_reset_lock.assert_current_thread_owns();
   1687 
   1688  /* If the default audio endpoint went away during playback and we weren't
   1689     able to configure a new one, it's possible the caller may call this
   1690     before the error callback has propogated back. */
   1691  if (!stm->audio_clock) {
   1692    return 0;
   1693  }
   1694 
   1695  UINT64 freq;
   1696  HRESULT hr = stm->audio_clock->GetFrequency(&freq);
   1697  if (FAILED(hr)) {
   1698    LOG("GetFrequency failed: %lx", hr);
   1699    return 0;
   1700  }
   1701 
   1702  UINT64 pos;
   1703  hr = stm->audio_clock->GetPosition(&pos, NULL);
   1704  if (FAILED(hr)) {
   1705    LOG("GetPosition failed: %lx", hr);
   1706    return 0;
   1707  }
   1708 
   1709  double cur_pos = static_cast<double>(pos) / freq;
   1710  double max_pos =
   1711      static_cast<double>(stm->frames_written) / stm->output_mix_params.rate;
   1712  double delay = std::max(max_pos - cur_pos, 0.0);
   1713 
   1714  return delay;
   1715 }
   1716 
   1717 #ifdef CUBEB_WASAPI_USE_IAUDIOSTREAMVOLUME
   1718 int
   1719 stream_set_volume(cubeb_stream * stm, float volume)
   1720 {
   1721  stm->stream_reset_lock.assert_current_thread_owns();
   1722 
   1723  if (!stm->audio_stream_volume) {
   1724    return CUBEB_ERROR;
   1725  }
   1726 
   1727  uint32_t channels;
   1728  HRESULT hr = stm->audio_stream_volume->GetChannelCount(&channels);
   1729  if (FAILED(hr)) {
   1730    LOG("could not get the channel count: %lx", hr);
   1731    return CUBEB_ERROR;
   1732  }
   1733 
   1734  /* up to 9.1 for now */
   1735  if (channels > 10) {
   1736    return CUBEB_ERROR_NOT_SUPPORTED;
   1737  }
   1738 
   1739  float volumes[10];
   1740  for (uint32_t i = 0; i < channels; i++) {
   1741    volumes[i] = volume;
   1742  }
   1743 
   1744  hr = stm->audio_stream_volume->SetAllVolumes(channels, volumes);
   1745  if (FAILED(hr)) {
   1746    LOG("could not set the channels volume: %lx", hr);
   1747    return CUBEB_ERROR;
   1748  }
   1749 
   1750  return CUBEB_OK;
   1751 }
   1752 #endif
   1753 } // namespace
   1754 
   1755 extern "C" {
   1756 int
   1757 wasapi_init(cubeb ** context, char const * context_name)
   1758 {
   1759  /* We don't use the device yet, but need to make sure we can initialize one
   1760     so that this backend is not incorrectly enabled on platforms that don't
   1761     support WASAPI. */
   1762  com_ptr<IMMDevice> device;
   1763  HRESULT hr = get_default_endpoint(device, eRender, eConsole);
   1764  if (FAILED(hr)) {
   1765    XASSERT(hr != CO_E_NOTINITIALIZED);
   1766    LOG("It wasn't able to find a default rendering device: %lx", hr);
   1767    hr = get_default_endpoint(device, eCapture, eConsole);
   1768    if (FAILED(hr)) {
   1769      LOG("It wasn't able to find a default capture device: %lx", hr);
   1770      return CUBEB_ERROR;
   1771    }
   1772  }
   1773 
   1774  cubeb * ctx = new cubeb();
   1775 
   1776  ctx->ops = &wasapi_ops;
   1777  auto_lock lock(ctx->lock);
   1778  if (cubeb_strings_init(&ctx->device_ids) != CUBEB_OK) {
   1779    delete ctx;
   1780    return CUBEB_ERROR;
   1781  }
   1782 
   1783  LARGE_INTEGER frequency;
   1784  if (QueryPerformanceFrequency(&frequency)) {
   1785    ctx->performance_counter_frequency = frequency.QuadPart;
   1786  } else {
   1787    LOG("Failed getting performance counter frequency, latency reporting will "
   1788        "be inacurate");
   1789    ctx->performance_counter_frequency = 0;
   1790  }
   1791 
   1792  *context = ctx;
   1793 
   1794  return CUBEB_OK;
   1795 }
   1796 }
   1797 
   1798 namespace {
   1799 
   1800 bool
   1801 stop_and_join_render_thread(cubeb_stream * stm)
   1802 {
   1803  LOG("%p: Stop and join render thread: %p", stm, stm->thread);
   1804  if (!stm->thread) {
   1805    return true;
   1806  }
   1807 
   1808  BOOL ok = SetEvent(stm->shutdown_event);
   1809  if (!ok) {
   1810    LOG("stop_and_join_render_thread: SetEvent failed: %lx", GetLastError());
   1811    return false;
   1812  }
   1813 
   1814  DWORD r = WaitForSingleObject(stm->thread, INFINITE);
   1815  if (r != WAIT_OBJECT_0) {
   1816    LOG("stop_and_join_render_thread: WaitForSingleObject on thread failed: "
   1817        "%lx, %lx",
   1818        r, GetLastError());
   1819    return false;
   1820  }
   1821 
   1822  return true;
   1823 }
   1824 
   1825 void
   1826 wasapi_destroy(cubeb * context)
   1827 {
   1828  {
   1829    auto_lock lock(context->lock);
   1830    XASSERT(!context->device_collection_enumerator &&
   1831            !context->collection_notification_client);
   1832 
   1833    if (context->device_ids) {
   1834      cubeb_strings_destroy(context->device_ids);
   1835    }
   1836  }
   1837 
   1838  delete context;
   1839 }
   1840 
   1841 char const *
   1842 wasapi_get_backend_id(cubeb * context)
   1843 {
   1844  return "wasapi";
   1845 }
   1846 
   1847 int
   1848 wasapi_get_max_channel_count(cubeb * ctx, uint32_t * max_channels)
   1849 {
   1850  XASSERT(ctx && max_channels);
   1851 
   1852  com_ptr<IMMDevice> device;
   1853  HRESULT hr = get_default_endpoint(device, eRender, eConsole);
   1854  if (FAILED(hr)) {
   1855    return CUBEB_ERROR;
   1856  }
   1857 
   1858  com_ptr<IAudioClient> client;
   1859  hr = device->Activate(__uuidof(IAudioClient), CLSCTX_INPROC_SERVER, NULL,
   1860                        client.receive_vpp());
   1861  if (FAILED(hr)) {
   1862    return CUBEB_ERROR;
   1863  }
   1864 
   1865  WAVEFORMATEX * tmp = nullptr;
   1866  hr = client->GetMixFormat(&tmp);
   1867  if (FAILED(hr)) {
   1868    return CUBEB_ERROR;
   1869  }
   1870  com_heap_ptr<WAVEFORMATEX> mix_format(tmp);
   1871 
   1872  *max_channels = mix_format->nChannels;
   1873 
   1874  return CUBEB_OK;
   1875 }
   1876 
   1877 int
   1878 wasapi_get_min_latency(cubeb * ctx, cubeb_stream_params params,
   1879                       uint32_t * latency_frames)
   1880 {
   1881  if (params.format != CUBEB_SAMPLE_FLOAT32NE &&
   1882      params.format != CUBEB_SAMPLE_S16NE) {
   1883    return CUBEB_ERROR_INVALID_FORMAT;
   1884  }
   1885 
   1886  ERole role = pref_to_role(params.prefs);
   1887 
   1888  com_ptr<IMMDevice> device;
   1889  HRESULT hr = get_default_endpoint(device, eRender, role);
   1890  if (FAILED(hr)) {
   1891    LOG("Could not get default endpoint: %lx", hr);
   1892    return CUBEB_ERROR;
   1893  }
   1894 
   1895  com_ptr<IAudioClient> client;
   1896  hr = device->Activate(__uuidof(IAudioClient), CLSCTX_INPROC_SERVER, NULL,
   1897                        client.receive_vpp());
   1898  if (FAILED(hr)) {
   1899    LOG("Could not activate device for latency: %lx", hr);
   1900    return CUBEB_ERROR;
   1901  }
   1902 
   1903  REFERENCE_TIME minimum_period;
   1904  REFERENCE_TIME default_period;
   1905  hr = client->GetDevicePeriod(&default_period, &minimum_period);
   1906  if (FAILED(hr)) {
   1907    LOG("Could not get device period: %lx", hr);
   1908    return CUBEB_ERROR;
   1909  }
   1910 
   1911  LOG("default device period: %I64d, minimum device period: %I64d",
   1912      default_period, minimum_period);
   1913 
   1914  /* If we're on Windows 10, we can use IAudioClient3 to get minimal latency.
   1915     Otherwise, according to the docs, the best latency we can achieve is by
   1916     synchronizing the stream and the engine.
   1917     http://msdn.microsoft.com/en-us/library/windows/desktop/dd370871%28v=vs.85%29.aspx
   1918   */
   1919 
   1920  // #ifdef _WIN32_WINNT_WIN10
   1921 #if 0
   1922     *latency_frames = hns_to_frames(params.rate, minimum_period);
   1923 #else
   1924  *latency_frames = hns_to_frames(params.rate, default_period);
   1925 #endif
   1926 
   1927  LOG("Minimum latency in frames: %u", *latency_frames);
   1928 
   1929  return CUBEB_OK;
   1930 }
   1931 
   1932 int
   1933 wasapi_get_preferred_sample_rate(cubeb * ctx, uint32_t * rate)
   1934 {
   1935  com_ptr<IMMDevice> device;
   1936  HRESULT hr = get_default_endpoint(device, eRender, eConsole);
   1937  if (FAILED(hr)) {
   1938    return CUBEB_ERROR;
   1939  }
   1940 
   1941  com_ptr<IAudioClient> client;
   1942  hr = device->Activate(__uuidof(IAudioClient), CLSCTX_INPROC_SERVER, NULL,
   1943                        client.receive_vpp());
   1944  if (FAILED(hr)) {
   1945    return CUBEB_ERROR;
   1946  }
   1947 
   1948  WAVEFORMATEX * tmp = nullptr;
   1949  hr = client->GetMixFormat(&tmp);
   1950  if (FAILED(hr)) {
   1951    return CUBEB_ERROR;
   1952  }
   1953  com_heap_ptr<WAVEFORMATEX> mix_format(tmp);
   1954 
   1955  *rate = mix_format->nSamplesPerSec;
   1956 
   1957  LOG("Preferred sample rate for output: %u", *rate);
   1958 
   1959  return CUBEB_OK;
   1960 }
   1961 
   1962 int
   1963 wasapi_get_supported_input_processing_params(
   1964    cubeb * ctx, cubeb_input_processing_params * params)
   1965 {
   1966  // This is not entirely accurate -- windows doesn't document precisely what
   1967  // AudioCategory_Communications does -- but assume that we can set all or none
   1968  // of them.
   1969  *params = static_cast<cubeb_input_processing_params>(
   1970      CUBEB_INPUT_PROCESSING_PARAM_ECHO_CANCELLATION |
   1971      CUBEB_INPUT_PROCESSING_PARAM_NOISE_SUPPRESSION |
   1972      CUBEB_INPUT_PROCESSING_PARAM_AUTOMATIC_GAIN_CONTROL |
   1973      CUBEB_INPUT_PROCESSING_PARAM_VOICE_ISOLATION);
   1974  return CUBEB_OK;
   1975 }
   1976 
   1977 static void
   1978 waveformatex_update_derived_properties(WAVEFORMATEX * format)
   1979 {
   1980  format->nBlockAlign = format->wBitsPerSample * format->nChannels / 8;
   1981  format->nAvgBytesPerSec = format->nSamplesPerSec * format->nBlockAlign;
   1982  if (format->wFormatTag == WAVE_FORMAT_EXTENSIBLE) {
   1983    WAVEFORMATEXTENSIBLE * format_pcm =
   1984        reinterpret_cast<WAVEFORMATEXTENSIBLE *>(format);
   1985    format_pcm->Samples.wValidBitsPerSample = format->wBitsPerSample;
   1986  }
   1987 }
   1988 
   1989 /* Based on the mix format and the stream format, try to find a way to play
   1990   what the user requested. */
   1991 static void
   1992 handle_channel_layout(cubeb_stream * stm, EDataFlow direction,
   1993                      com_heap_ptr<WAVEFORMATEX> & mix_format,
   1994                      const cubeb_stream_params * stream_params)
   1995 {
   1996  com_ptr<IAudioClient> & audio_client =
   1997      (direction == eRender) ? stm->output_client : stm->input_client;
   1998  XASSERT(audio_client);
   1999  /* The docs say that GetMixFormat is always of type WAVEFORMATEXTENSIBLE [1],
   2000     so the reinterpret_cast below should be safe. In practice, this is not
   2001     true, and we just want to bail out and let the rest of the code find a good
   2002     conversion path instead of trying to make WASAPI do it by itself.
   2003     [1]:
   2004     http://msdn.microsoft.com/en-us/library/windows/desktop/dd370811%28v=vs.85%29.aspx*/
   2005  if (mix_format->wFormatTag != WAVE_FORMAT_EXTENSIBLE) {
   2006    return;
   2007  }
   2008 
   2009  WAVEFORMATEXTENSIBLE * format_pcm =
   2010      reinterpret_cast<WAVEFORMATEXTENSIBLE *>(mix_format.get());
   2011 
   2012  /* Stash a copy of the original mix format in case we need to restore it
   2013   * later. */
   2014  WAVEFORMATEXTENSIBLE hw_mix_format = *format_pcm;
   2015 
   2016  /* Get the channel mask by the channel layout.
   2017     If the layout is not supported, we will get a closest settings below. */
   2018  format_pcm->dwChannelMask = stream_params->layout;
   2019  mix_format->nChannels = stream_params->channels;
   2020  waveformatex_update_derived_properties(mix_format.get());
   2021 
   2022  /* Check if wasapi will accept our channel layout request. */
   2023  WAVEFORMATEX * tmp = nullptr;
   2024  HRESULT hr = audio_client->IsFormatSupported(AUDCLNT_SHAREMODE_SHARED,
   2025                                               mix_format.get(), &tmp);
   2026  com_heap_ptr<WAVEFORMATEX> closest(tmp);
   2027  if (hr == S_FALSE) {
   2028    /* Channel layout not supported, but WASAPI gives us a suggestion. Use it,
   2029       and handle the eventual upmix/downmix ourselves. Ignore the subformat of
   2030       the suggestion, since it seems to always be IEEE_FLOAT. */
   2031    LOG("Using WASAPI suggested format: channels: %d", closest->nChannels);
   2032    XASSERT(closest->wFormatTag == WAVE_FORMAT_EXTENSIBLE);
   2033    WAVEFORMATEXTENSIBLE * closest_pcm =
   2034        reinterpret_cast<WAVEFORMATEXTENSIBLE *>(closest.get());
   2035    format_pcm->dwChannelMask = closest_pcm->dwChannelMask;
   2036    mix_format->nChannels = closest->nChannels;
   2037    waveformatex_update_derived_properties(mix_format.get());
   2038  } else if (hr == AUDCLNT_E_UNSUPPORTED_FORMAT) {
   2039    /* Not supported, no suggestion. This should not happen, but it does in the
   2040       field with some sound cards. We restore the mix format, and let the rest
   2041       of the code figure out the right conversion path. */
   2042    XASSERT(mix_format->wFormatTag == WAVE_FORMAT_EXTENSIBLE);
   2043    *reinterpret_cast<WAVEFORMATEXTENSIBLE *>(mix_format.get()) = hw_mix_format;
   2044  } else if (hr == S_OK) {
   2045    LOG("Requested format accepted by WASAPI.");
   2046  } else {
   2047    LOG("IsFormatSupported unhandled error: %lx", hr);
   2048  }
   2049 }
   2050 
   2051 static int
   2052 initialize_iaudioclient2(com_ptr<IAudioClient> & audio_client,
   2053                         AudioClient2Option option)
   2054 {
   2055  com_ptr<IAudioClient2> audio_client2;
   2056  audio_client->QueryInterface<IAudioClient2>(audio_client2.receive());
   2057  if (!audio_client2) {
   2058    LOG("Could not get IAudioClient2 interface, not setting "
   2059        "AUDCLNT_STREAMOPTIONS_RAW.");
   2060    return CUBEB_OK;
   2061  }
   2062  AudioClientProperties properties = {};
   2063  properties.cbSize = sizeof(AudioClientProperties);
   2064 #ifndef __MINGW32__
   2065  if (option == CUBEB_AUDIO_CLIENT2_RAW) {
   2066    properties.Options |= AUDCLNT_STREAMOPTIONS_RAW;
   2067  } else if (option == CUBEB_AUDIO_CLIENT2_VOICE) {
   2068    properties.eCategory = AudioCategory_Communications;
   2069  }
   2070 #endif
   2071  HRESULT hr = audio_client2->SetClientProperties(&properties);
   2072  if (FAILED(hr)) {
   2073    LOG("IAudioClient2::SetClientProperties error: %lx", GetLastError());
   2074    return CUBEB_ERROR;
   2075  }
   2076  return CUBEB_OK;
   2077 }
   2078 
   2079 #if 0
   2080 bool
   2081 initialize_iaudioclient3(com_ptr<IAudioClient> & audio_client,
   2082                         cubeb_stream * stm,
   2083                         const com_heap_ptr<WAVEFORMATEX> & mix_format,
   2084                         DWORD flags, EDataFlow direction)
   2085 {
   2086  com_ptr<IAudioClient3> audio_client3;
   2087  audio_client->QueryInterface<IAudioClient3>(audio_client3.receive());
   2088  if (!audio_client3) {
   2089    LOG("Could not get IAudioClient3 interface");
   2090    return false;
   2091  }
   2092 
   2093  if (flags & AUDCLNT_STREAMFLAGS_LOOPBACK) {
   2094    // IAudioClient3 doesn't work with loopback streams, and will return error
   2095    // 88890021: AUDCLNT_E_INVALID_STREAM_FLAG
   2096    LOG("Audio stream is loopback, not using IAudioClient3");
   2097    return false;
   2098  }
   2099 
   2100  // Some people have reported glitches with capture streams:
   2101  // http://blog.nirbheek.in/2018/03/low-latency-audio-on-windows-with.html
   2102  if (direction == eCapture) {
   2103    LOG("Audio stream is capture, not using IAudioClient3");
   2104    return false;
   2105  }
   2106 
   2107  // Possibly initialize a shared-mode stream using IAudioClient3. Initializing
   2108  // a stream this way lets you request lower latencies, but also locks the
   2109  // global WASAPI engine at that latency.
   2110  // - If we request a shared-mode stream, streams created with IAudioClient
   2111  // will
   2112  //   have their latency adjusted to match. When  the shared-mode stream is
   2113  //   closed, they'll go back to normal.
   2114  // - If there's already a shared-mode stream running, then we cannot request
   2115  //   the engine change to a different latency - we have to match it.
   2116  // - It's antisocial to lock the WASAPI engine at its default latency. If we
   2117  //   would do this, then stop and use IAudioClient instead.
   2118 
   2119  HRESULT hr;
   2120  uint32_t default_period = 0, fundamental_period = 0, min_period = 0,
   2121           max_period = 0;
   2122  hr = audio_client3->GetSharedModeEnginePeriod(
   2123      mix_format.get(), &default_period, &fundamental_period, &min_period,
   2124      &max_period);
   2125  if (FAILED(hr)) {
   2126    LOG("Could not get shared mode engine period: error: %lx", hr);
   2127    return false;
   2128  }
   2129  uint32_t requested_latency = stm->latency;
   2130  if (requested_latency >= default_period) {
   2131    LOG("Requested latency %i greater than default latency %i, not using "
   2132        "IAudioClient3",
   2133        requested_latency, default_period);
   2134    return false;
   2135  }
   2136  LOG("Got shared mode engine period: default=%i fundamental=%i min=%i max=%i",
   2137      default_period, fundamental_period, min_period, max_period);
   2138  // Snap requested latency to a valid value
   2139  uint32_t old_requested_latency = requested_latency;
   2140  if (requested_latency < min_period) {
   2141    requested_latency = min_period;
   2142  }
   2143  requested_latency -= (requested_latency - min_period) % fundamental_period;
   2144  if (requested_latency != old_requested_latency) {
   2145    LOG("Requested latency %i was adjusted to %i", old_requested_latency,
   2146        requested_latency);
   2147  }
   2148 
   2149  hr = audio_client3->InitializeSharedAudioStream(flags, requested_latency,
   2150                                                  mix_format.get(), NULL);
   2151  if (SUCCEEDED(hr)) {
   2152    return true;
   2153  } else if (hr == AUDCLNT_E_ENGINE_PERIODICITY_LOCKED) {
   2154    LOG("Got AUDCLNT_E_ENGINE_PERIODICITY_LOCKED, adjusting latency request");
   2155  } else {
   2156    LOG("Could not initialize shared stream with IAudioClient3: error: %lx",
   2157        hr);
   2158    return false;
   2159  }
   2160 
   2161  uint32_t current_period = 0;
   2162  WAVEFORMATEX * current_format = nullptr;
   2163  // We have to pass a valid WAVEFORMATEX** and not nullptr, otherwise
   2164  // GetCurrentSharedModeEnginePeriod will return E_POINTER
   2165  hr = audio_client3->GetCurrentSharedModeEnginePeriod(&current_format,
   2166                                                       &current_period);
   2167  CoTaskMemFree(current_format);
   2168  if (FAILED(hr)) {
   2169    LOG("Could not get current shared mode engine period: error: %lx", hr);
   2170    return false;
   2171  }
   2172 
   2173  if (current_period >= default_period) {
   2174    LOG("Current shared mode engine period %i too high, not using IAudioClient",
   2175        current_period);
   2176    return false;
   2177  }
   2178 
   2179  hr = audio_client3->InitializeSharedAudioStream(flags, current_period,
   2180                                                  mix_format.get(), NULL);
   2181  if (SUCCEEDED(hr)) {
   2182    LOG("Current shared mode engine period is %i instead of requested %i",
   2183        current_period, requested_latency);
   2184    return true;
   2185  }
   2186 
   2187  LOG("Could not initialize shared stream with IAudioClient3: error: %lx", hr);
   2188  return false;
   2189 }
   2190 #endif
   2191 
   2192 #define DIRECTION_NAME (direction == eCapture ? "capture" : "render")
   2193 
   2194 template <typename T>
   2195 int
   2196 setup_wasapi_stream_one_side(cubeb_stream * stm,
   2197                             cubeb_stream_params * stream_params,
   2198                             wchar_t const * devid, EDataFlow direction,
   2199                             REFIID riid, com_ptr<IAudioClient> & audio_client,
   2200                             uint32_t * buffer_frame_count, HANDLE & event,
   2201                             T & render_or_capture_client,
   2202                             cubeb_stream_params * mix_params,
   2203                             com_ptr<IMMDevice> & device)
   2204 {
   2205  XASSERT(direction == eCapture || direction == eRender);
   2206 
   2207  HRESULT hr;
   2208  bool is_loopback = stream_params->prefs & CUBEB_STREAM_PREF_LOOPBACK;
   2209  if (is_loopback && direction != eCapture) {
   2210    LOG("Loopback pref can only be used with capture streams!\n");
   2211    return CUBEB_ERROR;
   2212  }
   2213 
   2214  stm->stream_reset_lock.assert_current_thread_owns();
   2215  // If user doesn't specify a particular device, we can choose another one when
   2216  // the given devid is unavailable.
   2217  bool allow_fallback =
   2218      direction == eCapture ? !stm->input_device_id : !stm->output_device_id;
   2219  bool try_again = false;
   2220  // This loops until we find a device that works, or we've exhausted all
   2221  // possibilities.
   2222  do {
   2223    if (devid) {
   2224      hr = get_endpoint(device, devid);
   2225      if (FAILED(hr)) {
   2226        LOG("Could not get %s endpoint, error: %lx\n", DIRECTION_NAME, hr);
   2227        return CUBEB_ERROR;
   2228      }
   2229    } else {
   2230      // If caller has requested loopback but not specified a device, look for
   2231      // the default render device. Otherwise look for the default device
   2232      // appropriate to the direction.
   2233      hr = get_default_endpoint(device, is_loopback ? eRender : direction,
   2234                                pref_to_role(stream_params->prefs));
   2235      if (FAILED(hr)) {
   2236        if (is_loopback) {
   2237          LOG("Could not get default render endpoint for loopback, error: "
   2238              "%lx\n",
   2239              hr);
   2240        } else {
   2241          LOG("Could not get default %s endpoint, error: %lx\n", DIRECTION_NAME,
   2242              hr);
   2243        }
   2244        return CUBEB_ERROR;
   2245      }
   2246    }
   2247 
   2248    /* Get a client. We will get all other interfaces we need from
   2249     * this pointer. */
   2250 #if 0 // See https://bugzilla.mozilla.org/show_bug.cgi?id=1590902
   2251    hr = device->Activate(__uuidof(IAudioClient3),
   2252                          CLSCTX_INPROC_SERVER,
   2253                          NULL, audio_client.receive_vpp());
   2254    if (hr == E_NOINTERFACE) {
   2255 #endif
   2256    hr = device->Activate(__uuidof(IAudioClient), CLSCTX_INPROC_SERVER, NULL,
   2257                          audio_client.receive_vpp());
   2258 #if 0
   2259    }
   2260 #endif
   2261 
   2262    if (FAILED(hr)) {
   2263      LOG("Could not activate the device to get an audio"
   2264          " client for %s: error: %lx\n",
   2265          DIRECTION_NAME, hr);
   2266      // A particular device can't be activated because it has been
   2267      // unplugged, try fall back to the default audio device.
   2268      if (devid && hr == AUDCLNT_E_DEVICE_INVALIDATED && allow_fallback) {
   2269        LOG("Trying again with the default %s audio device.", DIRECTION_NAME);
   2270        devid = nullptr;
   2271        device = nullptr;
   2272        try_again = true;
   2273      } else {
   2274        return CUBEB_ERROR;
   2275      }
   2276    } else {
   2277      try_again = false;
   2278    }
   2279  } while (try_again);
   2280 
   2281  /* We have to distinguish between the format the mixer uses,
   2282   * and the format the stream we want to play uses. */
   2283  WAVEFORMATEX * tmp = nullptr;
   2284  hr = audio_client->GetMixFormat(&tmp);
   2285  if (FAILED(hr)) {
   2286    LOG("Could not fetch current mix format from the audio"
   2287        " client for %s: error: %lx",
   2288        DIRECTION_NAME, hr);
   2289    return CUBEB_ERROR;
   2290  }
   2291  com_heap_ptr<WAVEFORMATEX> mix_format(tmp);
   2292 
   2293  mix_format->wBitsPerSample = stm->bytes_per_sample * 8;
   2294  if (mix_format->wFormatTag == WAVE_FORMAT_PCM ||
   2295      mix_format->wFormatTag == WAVE_FORMAT_IEEE_FLOAT) {
   2296    switch (mix_format->wBitsPerSample) {
   2297    case 8:
   2298    case 16:
   2299      mix_format->wFormatTag = WAVE_FORMAT_PCM;
   2300      break;
   2301    case 32:
   2302      mix_format->wFormatTag = WAVE_FORMAT_IEEE_FLOAT;
   2303      break;
   2304    default:
   2305      LOG("%u bits per sample is incompatible with PCM wave formats",
   2306          mix_format->wBitsPerSample);
   2307      return CUBEB_ERROR;
   2308    }
   2309  }
   2310 
   2311  if (mix_format->wFormatTag == WAVE_FORMAT_EXTENSIBLE) {
   2312    WAVEFORMATEXTENSIBLE * format_pcm =
   2313        reinterpret_cast<WAVEFORMATEXTENSIBLE *>(mix_format.get());
   2314    format_pcm->SubFormat = stm->waveformatextensible_sub_format;
   2315  }
   2316  waveformatex_update_derived_properties(mix_format.get());
   2317 
   2318  /* Set channel layout only when there're more than two channels. Otherwise,
   2319   * use the default setting retrieved from the stream format of the audio
   2320   * engine's internal processing by GetMixFormat. */
   2321  if (mix_format->nChannels > 2) {
   2322    handle_channel_layout(stm, direction, mix_format, stream_params);
   2323  }
   2324 
   2325  mix_params->format = stream_params->format;
   2326  mix_params->rate = mix_format->nSamplesPerSec;
   2327  mix_params->channels = mix_format->nChannels;
   2328  mix_params->layout = mask_to_channel_layout(mix_format.get());
   2329 
   2330  LOG("Setup requested=[f=%d r=%u c=%u l=%u] mix=[f=%d r=%u c=%u l=%u]",
   2331      stream_params->format, stream_params->rate, stream_params->channels,
   2332      stream_params->layout, mix_params->format, mix_params->rate,
   2333      mix_params->channels, mix_params->layout);
   2334 
   2335  DWORD flags = 0;
   2336 
   2337  // Check if a loopback device should be requested. Note that event callbacks
   2338  // do not work with loopback devices, so only request these if not looping.
   2339  if (is_loopback) {
   2340    flags |= AUDCLNT_STREAMFLAGS_LOOPBACK;
   2341  } else {
   2342    flags |= AUDCLNT_STREAMFLAGS_EVENTCALLBACK;
   2343  }
   2344 
   2345  REFERENCE_TIME latency_hns = frames_to_hns(stream_params->rate, stm->latency);
   2346 
   2347  // Adjust input latency and check if input is using bluetooth handsfree
   2348  // protocol.
   2349  if (direction == eCapture) {
   2350    stm->input_bluetooth_handsfree = false;
   2351 
   2352    wasapi_default_devices default_devices(stm->device_enumerator.get());
   2353    cubeb_device_info device_info;
   2354    if (wasapi_create_device(stm->context, device_info,
   2355                             stm->device_enumerator.get(), device.get(),
   2356                             &default_devices) == CUBEB_OK) {
   2357      if (device_info.latency_hi == 0) {
   2358        LOG("Input: could not query latency_hi to guess safe latency");
   2359        wasapi_destroy_device(&device_info);
   2360        return CUBEB_ERROR;
   2361      }
   2362      // This multiplicator has been found empirically.
   2363      uint32_t latency_frames = device_info.latency_hi * 8;
   2364      LOG("Input: latency increased to %u frames from a default of %u",
   2365          latency_frames, device_info.latency_hi);
   2366      latency_hns = frames_to_hns(device_info.default_rate, latency_frames);
   2367 
   2368      const char * HANDSFREE_TAG = "BTHHFENUM";
   2369      size_t len = sizeof(HANDSFREE_TAG);
   2370      if (strlen(device_info.group_id) >= len &&
   2371          strncmp(device_info.group_id, HANDSFREE_TAG, len) == 0) {
   2372        LOG("Input device is using bluetooth handsfree protocol");
   2373        stm->input_bluetooth_handsfree = true;
   2374      }
   2375 
   2376      wasapi_destroy_device(&device_info);
   2377    } else {
   2378      LOG("Could not get cubeb_device_info. Skip customizing input settings");
   2379    }
   2380  }
   2381 
   2382  if (stream_params->prefs & CUBEB_STREAM_PREF_RAW) {
   2383    if (initialize_iaudioclient2(audio_client, CUBEB_AUDIO_CLIENT2_RAW) !=
   2384        CUBEB_OK) {
   2385      LOG("Can't initialize an IAudioClient2, error: %lx", GetLastError());
   2386      // This is not fatal.
   2387    }
   2388  } else if (direction == eCapture &&
   2389             (stream_params->prefs & CUBEB_STREAM_PREF_VOICE) &&
   2390             stream_params->input_params != CUBEB_INPUT_PROCESSING_PARAM_NONE) {
   2391    if (stream_params->input_params ==
   2392        (CUBEB_INPUT_PROCESSING_PARAM_ECHO_CANCELLATION |
   2393         CUBEB_INPUT_PROCESSING_PARAM_NOISE_SUPPRESSION |
   2394         CUBEB_INPUT_PROCESSING_PARAM_AUTOMATIC_GAIN_CONTROL |
   2395         CUBEB_INPUT_PROCESSING_PARAM_VOICE_ISOLATION)) {
   2396      if (initialize_iaudioclient2(audio_client, CUBEB_AUDIO_CLIENT2_VOICE) !=
   2397          CUBEB_OK) {
   2398        LOG("Can't initialize an IAudioClient2, error: %lx", GetLastError());
   2399        // This is not fatal.
   2400      }
   2401    } else {
   2402      LOG("Invalid combination of input processing params %#x",
   2403          stream_params->input_params);
   2404      return CUBEB_ERROR;
   2405    }
   2406  }
   2407 
   2408 #if 0 // See https://bugzilla.mozilla.org/show_bug.cgi?id=1590902
   2409  if (initialize_iaudioclient3(audio_client, stm, mix_format, flags, direction)) {
   2410    LOG("Initialized with IAudioClient3");
   2411  } else {
   2412 #endif
   2413  hr = audio_client->Initialize(AUDCLNT_SHAREMODE_SHARED, flags, latency_hns, 0,
   2414                                mix_format.get(), NULL);
   2415 #if 0
   2416  }
   2417 #endif
   2418  if (FAILED(hr)) {
   2419    LOG("Unable to initialize audio client for %s: %lx.", DIRECTION_NAME, hr);
   2420    return CUBEB_ERROR;
   2421  }
   2422 
   2423  hr = audio_client->GetBufferSize(buffer_frame_count);
   2424  if (FAILED(hr)) {
   2425    LOG("Could not get the buffer size from the client"
   2426        " for %s %lx.",
   2427        DIRECTION_NAME, hr);
   2428    return CUBEB_ERROR;
   2429  }
   2430 
   2431  LOG("Buffer size is: %d for %s\n", *buffer_frame_count, DIRECTION_NAME);
   2432 
   2433  // Events are used if not looping back
   2434  if (!is_loopback) {
   2435    hr = audio_client->SetEventHandle(event);
   2436    if (FAILED(hr)) {
   2437      LOG("Could set the event handle for the %s client %lx.", DIRECTION_NAME,
   2438          hr);
   2439      return CUBEB_ERROR;
   2440    }
   2441  }
   2442 
   2443  hr = audio_client->GetService(riid, render_or_capture_client.receive_vpp());
   2444  if (FAILED(hr)) {
   2445    LOG("Could not get the %s client %lx.", DIRECTION_NAME, hr);
   2446    return CUBEB_ERROR;
   2447  }
   2448 
   2449  return CUBEB_OK;
   2450 }
   2451 
   2452 #undef DIRECTION_NAME
   2453 
   2454 // Returns a non-null cubeb_devid if we find a matched device, or nullptr
   2455 // otherwise.
   2456 cubeb_devid
   2457 wasapi_find_bt_handsfree_output_device(cubeb_stream * stm)
   2458 {
   2459  HRESULT hr;
   2460  cubeb_device_info * input_device = nullptr;
   2461  cubeb_device_collection collection;
   2462 
   2463  // Only try to match to an output device if the input device is a bluetooth
   2464  // device that is using the handsfree protocol
   2465  if (!stm->input_bluetooth_handsfree) {
   2466    return nullptr;
   2467  }
   2468 
   2469  wchar_t * tmp = nullptr;
   2470  hr = stm->input_device->GetId(&tmp);
   2471  if (FAILED(hr)) {
   2472    LOG("Couldn't get input device id in "
   2473        "wasapi_find_bt_handsfree_output_device");
   2474    return nullptr;
   2475  }
   2476  com_heap_ptr<wchar_t> device_id(tmp);
   2477  cubeb_devid input_device_id = reinterpret_cast<cubeb_devid>(
   2478      intern_device_id(stm->context, device_id.get()));
   2479  if (!input_device_id) {
   2480    return nullptr;
   2481  }
   2482 
   2483  int rv = wasapi_enumerate_devices_internal(
   2484      stm->context,
   2485      (cubeb_device_type)(CUBEB_DEVICE_TYPE_INPUT | CUBEB_DEVICE_TYPE_OUTPUT),
   2486      &collection, DEVICE_STATE_ACTIVE);
   2487  if (rv != CUBEB_OK) {
   2488    return nullptr;
   2489  }
   2490 
   2491  // Find the input device, and then find the output device with the same group
   2492  // id and the same rate.
   2493  for (uint32_t i = 0; i < collection.count; i++) {
   2494    if (collection.device[i].devid == input_device_id) {
   2495      input_device = &collection.device[i];
   2496      break;
   2497    }
   2498  }
   2499 
   2500  cubeb_devid matched_output = nullptr;
   2501 
   2502  if (input_device) {
   2503    for (uint32_t i = 0; i < collection.count; i++) {
   2504      cubeb_device_info & dev = collection.device[i];
   2505      if (dev.type == CUBEB_DEVICE_TYPE_OUTPUT && dev.group_id &&
   2506          !strcmp(dev.group_id, input_device->group_id) &&
   2507          dev.default_rate == input_device->default_rate) {
   2508        LOG("Found matching device for %s: %s", input_device->friendly_name,
   2509            dev.friendly_name);
   2510        matched_output = dev.devid;
   2511        break;
   2512      }
   2513    }
   2514  }
   2515 
   2516  wasapi_device_collection_destroy(stm->context, &collection);
   2517  return matched_output;
   2518 }
   2519 
   2520 std::unique_ptr<wchar_t[]>
   2521 copy_wide_string(const wchar_t * src)
   2522 {
   2523  XASSERT(src);
   2524  size_t len = wcslen(src);
   2525  std::unique_ptr<wchar_t[]> copy(new wchar_t[len + 1]);
   2526  if (wcsncpy_s(copy.get(), len + 1, src, len) != 0) {
   2527    return nullptr;
   2528  }
   2529  return copy;
   2530 }
   2531 
   2532 int
   2533 setup_wasapi_stream(cubeb_stream * stm)
   2534 {
   2535  int rv;
   2536 
   2537  stm->stream_reset_lock.assert_current_thread_owns();
   2538 
   2539  XASSERT((!stm->output_client || !stm->input_client) &&
   2540          "WASAPI stream already setup, close it first.");
   2541 
   2542  std::unique_ptr<const wchar_t[]> selected_output_device_id;
   2543  if (stm->output_device_id) {
   2544    if (std::unique_ptr<wchar_t[]> tmp =
   2545            copy_wide_string(stm->output_device_id.get())) {
   2546      selected_output_device_id = std::move(tmp);
   2547    } else {
   2548      LOG("Failed to copy output device identifier.");
   2549      return CUBEB_ERROR;
   2550    }
   2551  }
   2552 
   2553  if (has_input(stm)) {
   2554    LOG("(%p) Setup capture: device=%p", stm, stm->input_device_id.get());
   2555    rv = setup_wasapi_stream_one_side(
   2556        stm, &stm->input_stream_params, stm->input_device_id.get(), eCapture,
   2557        __uuidof(IAudioCaptureClient), stm->input_client,
   2558        &stm->input_buffer_frame_count, stm->input_available_event,
   2559        stm->capture_client, &stm->input_mix_params, stm->input_device);
   2560    if (rv != CUBEB_OK) {
   2561      LOG("Failure to open the input side.");
   2562      return rv;
   2563    }
   2564 
   2565    // We initializing an input stream, buffer ahead two buffers worth of
   2566    // silence. This delays the input side slightly, but allow to not glitch
   2567    // when no input is available when calling into the resampler to call the
   2568    // callback: the input refill event will be set shortly after to compensate
   2569    // for this lack of data. In debug, four buffers are used, to avoid tripping
   2570    // up assertions down the line.
   2571 #if !defined(DEBUG)
   2572    const int silent_buffer_count = 2;
   2573 #else
   2574    const int silent_buffer_count = 6;
   2575 #endif
   2576    stm->linear_input_buffer->push_silence(stm->input_buffer_frame_count *
   2577                                           stm->input_stream_params.channels *
   2578                                           silent_buffer_count);
   2579 
   2580    // If this is a bluetooth device, and the output device is the default
   2581    // device, and the default device is the same bluetooth device, pick the
   2582    // right output device, running at the same rate and with the same protocol
   2583    // as the input.
   2584    if (!selected_output_device_id) {
   2585      cubeb_devid matched = wasapi_find_bt_handsfree_output_device(stm);
   2586      if (matched) {
   2587        selected_output_device_id =
   2588            utf8_to_wstr(reinterpret_cast<char const *>(matched));
   2589      }
   2590    }
   2591  }
   2592 
   2593  // If we don't have an output device but are requesting a loopback device,
   2594  // we attempt to open that same device in output mode in order to drive the
   2595  // loopback via the output events.
   2596  stm->has_dummy_output = false;
   2597  if (!has_output(stm) &&
   2598      stm->input_stream_params.prefs & CUBEB_STREAM_PREF_LOOPBACK) {
   2599    stm->output_stream_params.rate = stm->input_stream_params.rate;
   2600    stm->output_stream_params.channels = stm->input_stream_params.channels;
   2601    stm->output_stream_params.layout = stm->input_stream_params.layout;
   2602    if (stm->input_device_id) {
   2603      if (std::unique_ptr<wchar_t[]> tmp =
   2604              copy_wide_string(stm->input_device_id.get())) {
   2605        XASSERT(!selected_output_device_id);
   2606        selected_output_device_id = std::move(tmp);
   2607      } else {
   2608        LOG("Failed to copy device identifier while copying input stream "
   2609            "configuration to output stream configuration to drive loopback.");
   2610        return CUBEB_ERROR;
   2611      }
   2612    }
   2613    stm->has_dummy_output = true;
   2614  }
   2615 
   2616  if (has_output(stm)) {
   2617    LOG("(%p) Setup render: device=%p", stm, selected_output_device_id.get());
   2618    rv = setup_wasapi_stream_one_side(
   2619        stm, &stm->output_stream_params, selected_output_device_id.get(),
   2620        eRender, __uuidof(IAudioRenderClient), stm->output_client,
   2621        &stm->output_buffer_frame_count, stm->refill_event, stm->render_client,
   2622        &stm->output_mix_params, stm->output_device);
   2623    if (rv != CUBEB_OK) {
   2624      LOG("Failure to open the output side.");
   2625      return rv;
   2626    }
   2627 
   2628    HRESULT hr = 0;
   2629 #ifdef CUBEB_WASAPI_USE_IAUDIOSTREAMVOLUME
   2630    hr = stm->output_client->GetService(__uuidof(IAudioStreamVolume),
   2631                                        stm->audio_stream_volume.receive_vpp());
   2632    if (FAILED(hr)) {
   2633      LOG("Could not get the IAudioStreamVolume: %lx", hr);
   2634      return CUBEB_ERROR;
   2635    }
   2636 #endif
   2637 
   2638    XASSERT(stm->frames_written == 0);
   2639    hr = stm->output_client->GetService(__uuidof(IAudioClock),
   2640                                        stm->audio_clock.receive_vpp());
   2641    if (FAILED(hr)) {
   2642      LOG("Could not get the IAudioClock: %lx", hr);
   2643      return CUBEB_ERROR;
   2644    }
   2645 
   2646 #ifdef CUBEB_WASAPI_USE_IAUDIOSTREAMVOLUME
   2647    /* Restore the stream volume over a device change. */
   2648    if (stream_set_volume(stm, stm->volume) != CUBEB_OK) {
   2649      LOG("Could not set the volume.");
   2650      return CUBEB_ERROR;
   2651    }
   2652 #endif
   2653  }
   2654 
   2655  /* If we have both input and output, we resample to
   2656   * the highest sample rate available. */
   2657  int32_t target_sample_rate;
   2658  if (has_input(stm) && has_output(stm)) {
   2659    XASSERT(stm->input_stream_params.rate == stm->output_stream_params.rate);
   2660    target_sample_rate = stm->input_stream_params.rate;
   2661  } else if (has_input(stm)) {
   2662    target_sample_rate = stm->input_stream_params.rate;
   2663  } else {
   2664    XASSERT(has_output(stm));
   2665    target_sample_rate = stm->output_stream_params.rate;
   2666  }
   2667 
   2668  LOG("Target sample rate: %d", target_sample_rate);
   2669 
   2670  /* If we are playing/capturing a mono stream, we only resample one channel,
   2671   and copy it over, so we are always resampling the number
   2672   of channels of the stream, not the number of channels
   2673   that WASAPI wants. */
   2674  cubeb_stream_params input_params = stm->input_mix_params;
   2675  input_params.channels = stm->input_stream_params.channels;
   2676  cubeb_stream_params output_params = stm->output_mix_params;
   2677  output_params.channels = stm->output_stream_params.channels;
   2678 
   2679  stm->resampler.reset(cubeb_resampler_create(
   2680      stm, has_input(stm) ? &input_params : nullptr,
   2681      has_output(stm) && !stm->has_dummy_output ? &output_params : nullptr,
   2682      target_sample_rate, wasapi_data_callback, stm->user_ptr,
   2683      stm->voice ? CUBEB_RESAMPLER_QUALITY_VOIP
   2684                 : CUBEB_RESAMPLER_QUALITY_DESKTOP,
   2685      CUBEB_RESAMPLER_RECLOCK_NONE));
   2686  if (!stm->resampler) {
   2687    LOG("Could not get a resampler");
   2688    return CUBEB_ERROR;
   2689  }
   2690 
   2691  XASSERT(has_input(stm) || has_output(stm));
   2692 
   2693  if (has_input(stm) && has_output(stm)) {
   2694    stm->refill_callback = refill_callback_duplex;
   2695  } else if (has_input(stm)) {
   2696    stm->refill_callback = refill_callback_input;
   2697  } else if (has_output(stm)) {
   2698    stm->refill_callback = refill_callback_output;
   2699  }
   2700 
   2701  // Create input mixer.
   2702  if (has_input(stm) &&
   2703      ((stm->input_mix_params.layout != CUBEB_LAYOUT_UNDEFINED &&
   2704        stm->input_mix_params.layout != stm->input_stream_params.layout) ||
   2705       (stm->input_mix_params.channels != stm->input_stream_params.channels))) {
   2706    if (stm->input_mix_params.layout == CUBEB_LAYOUT_UNDEFINED) {
   2707      LOG("Input stream using undefined layout! Any mixing may be "
   2708          "unpredictable!\n");
   2709    }
   2710    stm->input_mixer.reset(cubeb_mixer_create(
   2711        stm->input_stream_params.format, stm->input_mix_params.channels,
   2712        stm->input_mix_params.layout, stm->input_stream_params.channels,
   2713        stm->input_stream_params.layout));
   2714    assert(stm->input_mixer);
   2715  }
   2716 
   2717  // Create output mixer.
   2718  if (has_output(stm) &&
   2719      stm->output_mix_params.layout != stm->output_stream_params.layout) {
   2720    if (stm->output_mix_params.layout == CUBEB_LAYOUT_UNDEFINED) {
   2721      LOG("Output stream using undefined layout! Any mixing may be "
   2722          "unpredictable!\n");
   2723    }
   2724    stm->output_mixer.reset(cubeb_mixer_create(
   2725        stm->output_stream_params.format, stm->output_stream_params.channels,
   2726        stm->output_stream_params.layout, stm->output_mix_params.channels,
   2727        stm->output_mix_params.layout));
   2728    assert(stm->output_mixer);
   2729    // Input is up/down mixed when depacketized in get_input_buffer.
   2730    stm->mix_buffer.resize(
   2731        frames_to_bytes_before_mix(stm, stm->output_buffer_frame_count));
   2732  }
   2733 
   2734  return CUBEB_OK;
   2735 }
   2736 
   2737 ERole
   2738 pref_to_role(cubeb_stream_prefs prefs)
   2739 {
   2740  if (prefs & CUBEB_STREAM_PREF_VOICE) {
   2741    return eCommunications;
   2742  }
   2743 
   2744  return eConsole;
   2745 }
   2746 
   2747 int
   2748 wasapi_stream_init(cubeb * context, cubeb_stream ** stream,
   2749                   char const * stream_name, cubeb_devid input_device,
   2750                   cubeb_stream_params * input_stream_params,
   2751                   cubeb_devid output_device,
   2752                   cubeb_stream_params * output_stream_params,
   2753                   unsigned int latency_frames,
   2754                   cubeb_data_callback data_callback,
   2755                   cubeb_state_callback state_callback, void * user_ptr)
   2756 {
   2757  int rv;
   2758 
   2759  XASSERT(context && stream && (input_stream_params || output_stream_params));
   2760 
   2761  if (output_stream_params && input_stream_params &&
   2762      output_stream_params->format != input_stream_params->format) {
   2763    return CUBEB_ERROR_INVALID_FORMAT;
   2764  }
   2765 
   2766  cubeb_stream * stm = new cubeb_stream();
   2767  auto_stream_ref stream_ref(stm);
   2768 
   2769  stm->context = context;
   2770  stm->data_callback = data_callback;
   2771  stm->state_callback = state_callback;
   2772  stm->user_ptr = user_ptr;
   2773  stm->role = eConsole;
   2774  stm->input_bluetooth_handsfree = false;
   2775 
   2776  HRESULT hr =
   2777      CoCreateInstance(__uuidof(MMDeviceEnumerator), NULL, CLSCTX_INPROC_SERVER,
   2778                       IID_PPV_ARGS(stm->device_enumerator.receive()));
   2779  if (FAILED(hr)) {
   2780    LOG("Could not get device enumerator: %lx", hr);
   2781    return hr;
   2782  }
   2783 
   2784  if (input_stream_params) {
   2785    stm->input_stream_params = *input_stream_params;
   2786    stm->input_device_id =
   2787        utf8_to_wstr(reinterpret_cast<char const *>(input_device));
   2788  }
   2789  if (output_stream_params) {
   2790    stm->output_stream_params = *output_stream_params;
   2791    stm->output_device_id =
   2792        utf8_to_wstr(reinterpret_cast<char const *>(output_device));
   2793  }
   2794 
   2795  if (stm->output_stream_params.prefs & CUBEB_STREAM_PREF_VOICE ||
   2796      stm->input_stream_params.prefs & CUBEB_STREAM_PREF_VOICE) {
   2797    stm->voice = true;
   2798  } else {
   2799    stm->voice = false;
   2800  }
   2801 
   2802  switch (output_stream_params ? output_stream_params->format
   2803                               : input_stream_params->format) {
   2804  case CUBEB_SAMPLE_S16NE:
   2805    stm->bytes_per_sample = sizeof(short);
   2806    stm->waveformatextensible_sub_format = KSDATAFORMAT_SUBTYPE_PCM;
   2807    stm->linear_input_buffer.reset(new auto_array_wrapper_impl<short>);
   2808    break;
   2809  case CUBEB_SAMPLE_FLOAT32NE:
   2810    stm->bytes_per_sample = sizeof(float);
   2811    stm->waveformatextensible_sub_format = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT;
   2812    stm->linear_input_buffer.reset(new auto_array_wrapper_impl<float>);
   2813    break;
   2814  default:
   2815    return CUBEB_ERROR_INVALID_FORMAT;
   2816  }
   2817 
   2818  stm->latency = latency_frames;
   2819 
   2820  stm->reconfigure_event = CreateEvent(NULL, 0, 0, NULL);
   2821  if (!stm->reconfigure_event) {
   2822    LOG("Can't create the reconfigure event, error: %lx", GetLastError());
   2823    return CUBEB_ERROR;
   2824  }
   2825 
   2826  /* Unconditionally create the two events so that the wait logic is simpler. */
   2827  stm->refill_event = CreateEvent(NULL, 0, 0, NULL);
   2828  if (!stm->refill_event) {
   2829    LOG("Can't create the refill event, error: %lx", GetLastError());
   2830    return CUBEB_ERROR;
   2831  }
   2832 
   2833  stm->input_available_event = CreateEvent(NULL, 0, 0, NULL);
   2834  if (!stm->input_available_event) {
   2835    LOG("Can't create the input available event , error: %lx", GetLastError());
   2836    return CUBEB_ERROR;
   2837  }
   2838 
   2839  stm->shutdown_event = CreateEvent(NULL, 0, 0, NULL);
   2840  if (!stm->shutdown_event) {
   2841    LOG("Can't create the shutdown event, error: %lx", GetLastError());
   2842    return CUBEB_ERROR;
   2843  }
   2844 
   2845  stm->thread_ready_event = CreateEvent(NULL, 0, 0, NULL);
   2846  if (!stm->thread_ready_event) {
   2847    LOG("Can't create the thread ready event, error: %lx", GetLastError());
   2848    return CUBEB_ERROR;
   2849  }
   2850 
   2851  {
   2852    /* Locking here is not strictly necessary, because we don't have a
   2853       notification client that can reset the stream yet, but it lets us
   2854       assert that the lock is held in the function. */
   2855    auto_lock lock(stm->stream_reset_lock);
   2856    rv = setup_wasapi_stream(stm);
   2857  }
   2858  if (rv != CUBEB_OK) {
   2859    return rv;
   2860  }
   2861 
   2862  // Follow the system default devices when not specifying devices explicitly
   2863  // and CUBEB_STREAM_PREF_DISABLE_DEVICE_SWITCHING is not set.
   2864  if ((!input_device && input_stream_params &&
   2865       !(input_stream_params->prefs &
   2866         CUBEB_STREAM_PREF_DISABLE_DEVICE_SWITCHING)) ||
   2867      (!output_device && output_stream_params &&
   2868       !(output_stream_params->prefs &
   2869         CUBEB_STREAM_PREF_DISABLE_DEVICE_SWITCHING))) {
   2870    LOG("Follow the system default input or/and output devices");
   2871    HRESULT hr = register_notification_client(stm);
   2872    if (FAILED(hr)) {
   2873      /* this is not fatal, we can still play audio, but we won't be able
   2874         to keep using the default audio endpoint if it changes. */
   2875      LOG("failed to register notification client, %lx", hr);
   2876    }
   2877  }
   2878 
   2879  cubeb_async_log_reset_threads();
   2880  stm->thread =
   2881      (HANDLE)_beginthreadex(NULL, 512 * 1024, wasapi_stream_render_loop, stm,
   2882                             STACK_SIZE_PARAM_IS_A_RESERVATION, NULL);
   2883  if (stm->thread == NULL) {
   2884    LOG("could not create WASAPI render thread.");
   2885    return CUBEB_ERROR;
   2886  }
   2887 
   2888  // Wait for the wasapi_stream_render_loop thread to signal that COM has been
   2889  // initialized and the stream's ref_count has been incremented.
   2890  hr = WaitForSingleObject(stm->thread_ready_event, INFINITE);
   2891  XASSERT(hr == WAIT_OBJECT_0);
   2892  CloseHandle(stm->thread_ready_event);
   2893  stm->thread_ready_event = 0;
   2894 
   2895  wasapi_stream_add_ref(stm);
   2896  *stream = stm;
   2897 
   2898  LOG("Stream init successful (%p)", *stream);
   2899  return CUBEB_OK;
   2900 }
   2901 
   2902 void
   2903 close_wasapi_stream(cubeb_stream * stm)
   2904 {
   2905  XASSERT(stm);
   2906 
   2907  stm->stream_reset_lock.assert_current_thread_owns();
   2908 
   2909 #ifdef CUBEB_WASAPI_USE_IAUDIOSTREAMVOLUME
   2910  stm->audio_stream_volume = nullptr;
   2911 #endif
   2912  stm->audio_clock = nullptr;
   2913  stm->render_client = nullptr;
   2914  stm->output_client = nullptr;
   2915  stm->output_device = nullptr;
   2916 
   2917  stm->capture_client = nullptr;
   2918  stm->input_client = nullptr;
   2919  stm->input_device = nullptr;
   2920 
   2921  stm->total_frames_written += static_cast<UINT64>(
   2922      round(stm->frames_written *
   2923            stream_to_mix_samplerate_ratio(stm->output_stream_params,
   2924                                           stm->output_mix_params)));
   2925  stm->frames_written = 0;
   2926 
   2927  stm->resampler.reset();
   2928  stm->output_mixer.reset();
   2929  stm->input_mixer.reset();
   2930  stm->mix_buffer.clear();
   2931  if (stm->linear_input_buffer) {
   2932    stm->linear_input_buffer->clear();
   2933  }
   2934 }
   2935 
   2936 LONG
   2937 wasapi_stream_add_ref(cubeb_stream * stm)
   2938 {
   2939  XASSERT(stm);
   2940  LONG result = InterlockedIncrement(&stm->ref_count);
   2941  LOGV("Stream ref count incremented = %ld (%p)", result, stm);
   2942  return result;
   2943 }
   2944 
   2945 LONG
   2946 wasapi_stream_release(cubeb_stream * stm)
   2947 {
   2948  XASSERT(stm);
   2949 
   2950  LONG result = InterlockedDecrement(&stm->ref_count);
   2951  LOGV("Stream ref count decremented = %ld (%p)", result, stm);
   2952  if (result == 0) {
   2953    LOG("Stream ref count hit zero, destroying (%p)", stm);
   2954 
   2955    if (stm->notification_client) {
   2956      unregister_notification_client(stm);
   2957    }
   2958 
   2959    CloseHandle(stm->shutdown_event);
   2960    CloseHandle(stm->reconfigure_event);
   2961    CloseHandle(stm->refill_event);
   2962    CloseHandle(stm->input_available_event);
   2963 
   2964    CloseHandle(stm->thread);
   2965 
   2966    // The variables intialized in wasapi_stream_init,
   2967    // must be destroyed in wasapi_stream_release.
   2968    stm->linear_input_buffer.reset();
   2969 
   2970    {
   2971      auto_lock lock(stm->stream_reset_lock);
   2972      close_wasapi_stream(stm);
   2973    }
   2974 
   2975    delete stm;
   2976  }
   2977 
   2978  return result;
   2979 }
   2980 
   2981 void
   2982 wasapi_stream_destroy(cubeb_stream * stm)
   2983 {
   2984  XASSERT(stm);
   2985  LOG("Stream destroy called, decrementing ref count (%p)", stm);
   2986 
   2987  stop_and_join_render_thread(stm);
   2988  wasapi_stream_release(stm);
   2989 }
   2990 
   2991 enum StreamDirection { OUTPUT, INPUT };
   2992 
   2993 int
   2994 stream_start_one_side(cubeb_stream * stm, StreamDirection dir)
   2995 {
   2996  XASSERT(stm);
   2997  XASSERT((dir == OUTPUT && stm->output_client) ||
   2998          (dir == INPUT && stm->input_client));
   2999 
   3000  HRESULT hr =
   3001      dir == OUTPUT ? stm->output_client->Start() : stm->input_client->Start();
   3002  if (hr == AUDCLNT_E_DEVICE_INVALIDATED) {
   3003    LOG("audioclient invalidated for %s device, reconfiguring",
   3004        dir == OUTPUT ? "output" : "input");
   3005 
   3006    BOOL ok = ResetEvent(stm->reconfigure_event);
   3007    if (!ok) {
   3008      LOG("resetting reconfig event failed for %s stream: %lx",
   3009          dir == OUTPUT ? "output" : "input", GetLastError());
   3010    }
   3011 
   3012    close_wasapi_stream(stm);
   3013    int r = setup_wasapi_stream(stm);
   3014    if (r != CUBEB_OK) {
   3015      LOG("reconfigure failed");
   3016      return r;
   3017    }
   3018 
   3019    HRESULT hr2 = dir == OUTPUT ? stm->output_client->Start()
   3020                                : stm->input_client->Start();
   3021    if (FAILED(hr2)) {
   3022      LOG("could not start the %s stream after reconfig: %lx",
   3023          dir == OUTPUT ? "output" : "input", hr);
   3024      return CUBEB_ERROR;
   3025    }
   3026  } else if (FAILED(hr)) {
   3027    LOG("could not start the %s stream: %lx.",
   3028        dir == OUTPUT ? "output" : "input", hr);
   3029    return CUBEB_ERROR;
   3030  }
   3031 
   3032  return CUBEB_OK;
   3033 }
   3034 
   3035 int
   3036 wasapi_stream_start(cubeb_stream * stm)
   3037 {
   3038  auto_lock lock(stm->stream_reset_lock);
   3039 
   3040  XASSERT(stm);
   3041  XASSERT(stm->output_client || stm->input_client);
   3042 
   3043  if (stm->output_client) {
   3044    int rv = stream_start_one_side(stm, OUTPUT);
   3045    if (rv != CUBEB_OK) {
   3046      return rv;
   3047    }
   3048  }
   3049 
   3050  if (stm->input_client) {
   3051    int rv = stream_start_one_side(stm, INPUT);
   3052    if (rv != CUBEB_OK) {
   3053      return rv;
   3054    }
   3055  }
   3056 
   3057  stm->active = true;
   3058 
   3059  stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STARTED);
   3060 
   3061  return CUBEB_OK;
   3062 }
   3063 
   3064 int
   3065 wasapi_stream_stop(cubeb_stream * stm)
   3066 {
   3067  XASSERT(stm);
   3068  HRESULT hr;
   3069 
   3070  {
   3071    auto_lock lock(stm->stream_reset_lock);
   3072 
   3073    if (stm->output_client) {
   3074      hr = stm->output_client->Stop();
   3075      if (FAILED(hr)) {
   3076        LOG("could not stop AudioClient (output)");
   3077        return CUBEB_ERROR;
   3078      }
   3079    }
   3080 
   3081    if (stm->input_client) {
   3082      hr = stm->input_client->Stop();
   3083      if (FAILED(hr)) {
   3084        LOG("could not stop AudioClient (input)");
   3085        return CUBEB_ERROR;
   3086      }
   3087    }
   3088 
   3089    stm->active = false;
   3090 
   3091    wasapi_state_callback(stm, stm->user_ptr, CUBEB_STATE_STOPPED);
   3092  }
   3093 
   3094  return CUBEB_OK;
   3095 }
   3096 
   3097 int
   3098 wasapi_stream_get_position(cubeb_stream * stm, uint64_t * position)
   3099 {
   3100  XASSERT(stm && position);
   3101  auto_lock lock(stm->stream_reset_lock);
   3102 
   3103  if (!has_output(stm)) {
   3104    return CUBEB_ERROR;
   3105  }
   3106 
   3107  /* Calculate how far behind the current stream head the playback cursor is. */
   3108  uint64_t stream_delay = static_cast<uint64_t>(current_stream_delay(stm) *
   3109                                                stm->output_stream_params.rate);
   3110 
   3111  /* Calculate the logical stream head in frames at the stream sample rate. */
   3112  uint64_t max_pos =
   3113      stm->total_frames_written +
   3114      static_cast<uint64_t>(
   3115          round(stm->frames_written *
   3116                stream_to_mix_samplerate_ratio(stm->output_stream_params,
   3117                                               stm->output_mix_params)));
   3118 
   3119  *position = max_pos;
   3120  if (stream_delay <= *position) {
   3121    *position -= stream_delay;
   3122  }
   3123 
   3124  if (*position < stm->prev_position) {
   3125    *position = stm->prev_position;
   3126  }
   3127  stm->prev_position = *position;
   3128 
   3129  return CUBEB_OK;
   3130 }
   3131 
   3132 int
   3133 wasapi_stream_get_latency(cubeb_stream * stm, uint32_t * latency)
   3134 {
   3135  XASSERT(stm && latency);
   3136 
   3137  if (!has_output(stm)) {
   3138    return CUBEB_ERROR;
   3139  }
   3140 
   3141  auto_lock lock(stm->stream_reset_lock);
   3142 
   3143  /* The GetStreamLatency method only works if the
   3144     AudioClient has been initialized. */
   3145  if (!stm->output_client) {
   3146    LOG("get_latency: No output_client.");
   3147    return CUBEB_ERROR;
   3148  }
   3149 
   3150  REFERENCE_TIME latency_hns;
   3151  HRESULT hr = stm->output_client->GetStreamLatency(&latency_hns);
   3152  if (FAILED(hr)) {
   3153    LOG("GetStreamLatency failed %lx.", hr);
   3154    return CUBEB_ERROR;
   3155  }
   3156  // This happens on windows 10: no error, but always 0 for latency.
   3157  if (latency_hns == 0) {
   3158    LOG("GetStreamLatency returned 0, using workaround.");
   3159    double delay_s = current_stream_delay(stm);
   3160    // convert to sample-frames
   3161    *latency = delay_s * stm->output_stream_params.rate;
   3162  } else {
   3163    *latency = hns_to_frames(stm, latency_hns);
   3164  }
   3165 
   3166  LOG("Output latency %u frames.", *latency);
   3167 
   3168  return CUBEB_OK;
   3169 }
   3170 
   3171 int
   3172 wasapi_stream_get_input_latency(cubeb_stream * stm, uint32_t * latency)
   3173 {
   3174  XASSERT(stm && latency);
   3175 
   3176  if (!has_input(stm)) {
   3177    LOG("Input latency queried on an output-only stream.");
   3178    return CUBEB_ERROR;
   3179  }
   3180 
   3181  auto_lock lock(stm->stream_reset_lock);
   3182 
   3183  if (stm->input_latency_hns == LATENCY_NOT_AVAILABLE_YET) {
   3184    LOG("Input latency not available yet.");
   3185    return CUBEB_ERROR;
   3186  }
   3187 
   3188  *latency = hns_to_frames(stm, stm->input_latency_hns);
   3189 
   3190  return CUBEB_OK;
   3191 }
   3192 
   3193 int
   3194 wasapi_stream_set_volume(cubeb_stream * stm, float volume)
   3195 {
   3196  auto_lock lock(stm->stream_reset_lock);
   3197 
   3198  if (!has_output(stm)) {
   3199    return CUBEB_ERROR;
   3200  }
   3201 
   3202 #ifdef CUBEB_WASAPI_USE_IAUDIOSTREAMVOLUME
   3203  if (stream_set_volume(stm, volume) != CUBEB_OK) {
   3204    return CUBEB_ERROR;
   3205  }
   3206 #endif
   3207 
   3208  stm->volume = volume;
   3209 
   3210  return CUBEB_OK;
   3211 }
   3212 
   3213 static std::unique_ptr<char const[]>
   3214 wstr_to_utf8(LPCWSTR str)
   3215 {
   3216  int size = ::WideCharToMultiByte(CP_UTF8, 0, str, -1, nullptr, 0, NULL, NULL);
   3217  if (size <= 0) {
   3218    return nullptr;
   3219  }
   3220 
   3221  std::unique_ptr<char[]> ret(new char[size]);
   3222  ::WideCharToMultiByte(CP_UTF8, 0, str, -1, ret.get(), size, NULL, NULL);
   3223  return ret;
   3224 }
   3225 
   3226 static std::unique_ptr<wchar_t const[]>
   3227 utf8_to_wstr(char const * str)
   3228 {
   3229  int size = ::MultiByteToWideChar(CP_UTF8, 0, str, -1, nullptr, 0);
   3230  if (size <= 0) {
   3231    return nullptr;
   3232  }
   3233 
   3234  std::unique_ptr<wchar_t[]> ret(new wchar_t[size]);
   3235  ::MultiByteToWideChar(CP_UTF8, 0, str, -1, ret.get(), size);
   3236  return ret;
   3237 }
   3238 
   3239 static com_ptr<IMMDevice>
   3240 wasapi_get_device_node(IMMDeviceEnumerator * enumerator, IMMDevice * dev)
   3241 {
   3242  com_ptr<IMMDevice> ret;
   3243  com_ptr<IDeviceTopology> devtopo;
   3244  com_ptr<IConnector> connector;
   3245 
   3246  if (SUCCEEDED(dev->Activate(__uuidof(IDeviceTopology), CLSCTX_ALL, NULL,
   3247                              devtopo.receive_vpp())) &&
   3248      SUCCEEDED(devtopo->GetConnector(0, connector.receive()))) {
   3249    wchar_t * tmp = nullptr;
   3250    if (SUCCEEDED(connector->GetDeviceIdConnectedTo(&tmp))) {
   3251      com_heap_ptr<wchar_t> filterid(tmp);
   3252      if (FAILED(enumerator->GetDevice(filterid.get(), ret.receive())))
   3253        ret = NULL;
   3254    }
   3255  }
   3256 
   3257  return ret;
   3258 }
   3259 
   3260 static com_heap_ptr<wchar_t>
   3261 wasapi_get_default_device_id(EDataFlow flow, ERole role,
   3262                             IMMDeviceEnumerator * enumerator)
   3263 {
   3264  com_ptr<IMMDevice> dev;
   3265 
   3266  HRESULT hr = enumerator->GetDefaultAudioEndpoint(flow, role, dev.receive());
   3267  if (SUCCEEDED(hr)) {
   3268    wchar_t * tmp = nullptr;
   3269    if (SUCCEEDED(dev->GetId(&tmp))) {
   3270      com_heap_ptr<wchar_t> devid(tmp);
   3271      return devid;
   3272    }
   3273  }
   3274 
   3275  return nullptr;
   3276 }
   3277 
   3278 /* `ret` must be deallocated with `wasapi_destroy_device`, iff the return value
   3279 * of this function is `CUBEB_OK`. */
   3280 int
   3281 wasapi_create_device(cubeb * ctx, cubeb_device_info & ret,
   3282                     IMMDeviceEnumerator * enumerator, IMMDevice * dev,
   3283                     wasapi_default_devices * defaults)
   3284 {
   3285  com_ptr<IMMEndpoint> endpoint;
   3286  com_ptr<IMMDevice> devnode;
   3287  com_ptr<IAudioClient> client;
   3288  EDataFlow flow;
   3289  DWORD state = DEVICE_STATE_NOTPRESENT;
   3290  com_ptr<IPropertyStore> propstore;
   3291  REFERENCE_TIME def_period, min_period;
   3292  HRESULT hr;
   3293 
   3294  XASSERT(enumerator && dev && defaults);
   3295 
   3296  // zero-out to be able to safely delete the pointers to friendly_name and
   3297  // group_id at all time in this function.
   3298  PodZero(&ret, 1);
   3299 
   3300  struct prop_variant : public PROPVARIANT {
   3301    prop_variant() { PropVariantInit(this); }
   3302    ~prop_variant() { PropVariantClear(this); }
   3303    prop_variant(prop_variant const &) = delete;
   3304    prop_variant & operator=(prop_variant const &) = delete;
   3305  };
   3306 
   3307  hr = dev->QueryInterface(IID_PPV_ARGS(endpoint.receive()));
   3308  if (FAILED(hr)) {
   3309    wasapi_destroy_device(&ret);
   3310    return CUBEB_ERROR;
   3311  }
   3312 
   3313  hr = endpoint->GetDataFlow(&flow);
   3314  if (FAILED(hr)) {
   3315    wasapi_destroy_device(&ret);
   3316    return CUBEB_ERROR;
   3317  }
   3318 
   3319  wchar_t * tmp = nullptr;
   3320  hr = dev->GetId(&tmp);
   3321  if (FAILED(hr)) {
   3322    wasapi_destroy_device(&ret);
   3323    return CUBEB_ERROR;
   3324  }
   3325  com_heap_ptr<wchar_t> device_id(tmp);
   3326 
   3327  char const * device_id_intern = intern_device_id(ctx, device_id.get());
   3328  if (!device_id_intern) {
   3329    wasapi_destroy_device(&ret);
   3330    return CUBEB_ERROR;
   3331  }
   3332 
   3333  hr = dev->OpenPropertyStore(STGM_READ, propstore.receive());
   3334  if (FAILED(hr)) {
   3335    wasapi_destroy_device(&ret);
   3336    return CUBEB_ERROR;
   3337  }
   3338 
   3339  hr = dev->GetState(&state);
   3340  if (FAILED(hr)) {
   3341    wasapi_destroy_device(&ret);
   3342    return CUBEB_ERROR;
   3343  }
   3344 
   3345  ret.device_id = device_id_intern;
   3346  ret.devid = reinterpret_cast<cubeb_devid>(ret.device_id);
   3347  prop_variant namevar;
   3348  hr = propstore->GetValue(PKEY_Device_FriendlyName, &namevar);
   3349  if (SUCCEEDED(hr) && namevar.vt == VT_LPWSTR) {
   3350    ret.friendly_name = wstr_to_utf8(namevar.pwszVal).release();
   3351  }
   3352  if (!ret.friendly_name) {
   3353    // This is not fatal, but a valid string is expected in all cases.
   3354    char * empty = new char[1];
   3355    empty[0] = '\0';
   3356    ret.friendly_name = empty;
   3357  }
   3358 
   3359  devnode = wasapi_get_device_node(enumerator, dev);
   3360  if (devnode) {
   3361    com_ptr<IPropertyStore> ps;
   3362    hr = devnode->OpenPropertyStore(STGM_READ, ps.receive());
   3363    if (FAILED(hr)) {
   3364      wasapi_destroy_device(&ret);
   3365      return CUBEB_ERROR;
   3366    }
   3367 
   3368    prop_variant instancevar;
   3369    hr = ps->GetValue(PKEY_Device_InstanceId, &instancevar);
   3370    if (SUCCEEDED(hr) && instancevar.vt == VT_LPWSTR) {
   3371      ret.group_id = wstr_to_utf8(instancevar.pwszVal).release();
   3372    }
   3373  }
   3374 
   3375  if (!ret.group_id) {
   3376    // This is not fatal, but a valid string is expected in all cases.
   3377    char * empty = new char[1];
   3378    empty[0] = '\0';
   3379    ret.group_id = empty;
   3380  }
   3381 
   3382  ret.preferred = CUBEB_DEVICE_PREF_NONE;
   3383  if (defaults->is_default(flow, eConsole, device_id.get())) {
   3384    ret.preferred =
   3385        (cubeb_device_pref)(ret.preferred | CUBEB_DEVICE_PREF_MULTIMEDIA |
   3386                            CUBEB_DEVICE_PREF_NOTIFICATION);
   3387  }
   3388  if (defaults->is_default(flow, eCommunications, device_id.get())) {
   3389    ret.preferred =
   3390        (cubeb_device_pref)(ret.preferred | CUBEB_DEVICE_PREF_VOICE);
   3391  }
   3392 
   3393  if (flow == eRender) {
   3394    ret.type = CUBEB_DEVICE_TYPE_OUTPUT;
   3395  } else if (flow == eCapture) {
   3396    ret.type = CUBEB_DEVICE_TYPE_INPUT;
   3397  }
   3398 
   3399  switch (state) {
   3400  case DEVICE_STATE_ACTIVE:
   3401    ret.state = CUBEB_DEVICE_STATE_ENABLED;
   3402    break;
   3403  case DEVICE_STATE_UNPLUGGED:
   3404    ret.state = CUBEB_DEVICE_STATE_UNPLUGGED;
   3405    break;
   3406  default:
   3407    ret.state = CUBEB_DEVICE_STATE_DISABLED;
   3408    break;
   3409  };
   3410 
   3411  ret.format = static_cast<cubeb_device_fmt>(CUBEB_DEVICE_FMT_F32NE |
   3412                                             CUBEB_DEVICE_FMT_S16NE);
   3413  ret.default_format = CUBEB_DEVICE_FMT_F32NE;
   3414  prop_variant fmtvar;
   3415  hr = propstore->GetValue(PKEY_AudioEngine_DeviceFormat, &fmtvar);
   3416  if (SUCCEEDED(hr) && fmtvar.vt == VT_BLOB) {
   3417    if (fmtvar.blob.cbSize == sizeof(PCMWAVEFORMAT)) {
   3418      const PCMWAVEFORMAT * pcm =
   3419          reinterpret_cast<const PCMWAVEFORMAT *>(fmtvar.blob.pBlobData);
   3420 
   3421      ret.max_rate = ret.min_rate = ret.default_rate = pcm->wf.nSamplesPerSec;
   3422      ret.max_channels = pcm->wf.nChannels;
   3423    } else if (fmtvar.blob.cbSize >= sizeof(WAVEFORMATEX)) {
   3424      WAVEFORMATEX * wfx =
   3425          reinterpret_cast<WAVEFORMATEX *>(fmtvar.blob.pBlobData);
   3426 
   3427      if (fmtvar.blob.cbSize >= sizeof(WAVEFORMATEX) + wfx->cbSize ||
   3428          wfx->wFormatTag == WAVE_FORMAT_PCM) {
   3429        ret.max_rate = ret.min_rate = ret.default_rate = wfx->nSamplesPerSec;
   3430        ret.max_channels = wfx->nChannels;
   3431      }
   3432    }
   3433  }
   3434 
   3435  if (SUCCEEDED(dev->Activate(__uuidof(IAudioClient), CLSCTX_INPROC_SERVER,
   3436                              NULL, client.receive_vpp())) &&
   3437      SUCCEEDED(client->GetDevicePeriod(&def_period, &min_period))) {
   3438    ret.latency_lo = hns_to_frames(ret.default_rate, min_period);
   3439    ret.latency_hi = hns_to_frames(ret.default_rate, def_period);
   3440  } else {
   3441    ret.latency_lo = 0;
   3442    ret.latency_hi = 0;
   3443  }
   3444 
   3445  XASSERT(ret.friendly_name && ret.group_id);
   3446 
   3447  return CUBEB_OK;
   3448 }
   3449 
   3450 void
   3451 wasapi_destroy_device(cubeb_device_info * device)
   3452 {
   3453  delete[] device->friendly_name;
   3454  delete[] device->group_id;
   3455 }
   3456 
   3457 static int
   3458 wasapi_enumerate_devices_internal(cubeb * context, cubeb_device_type type,
   3459                                  cubeb_device_collection * out,
   3460                                  DWORD state_mask)
   3461 {
   3462  com_ptr<IMMDeviceEnumerator> enumerator;
   3463  com_ptr<IMMDeviceCollection> collection;
   3464  HRESULT hr;
   3465  UINT cc, i;
   3466  EDataFlow flow;
   3467 
   3468  hr =
   3469      CoCreateInstance(__uuidof(MMDeviceEnumerator), NULL, CLSCTX_INPROC_SERVER,
   3470                       IID_PPV_ARGS(enumerator.receive()));
   3471  if (FAILED(hr)) {
   3472    LOG("Could not get device enumerator: %lx", hr);
   3473    return CUBEB_ERROR;
   3474  }
   3475 
   3476  wasapi_default_devices default_devices(enumerator.get());
   3477 
   3478  if (type == CUBEB_DEVICE_TYPE_OUTPUT) {
   3479    flow = eRender;
   3480  } else if (type == CUBEB_DEVICE_TYPE_INPUT) {
   3481    flow = eCapture;
   3482  } else if (type & (CUBEB_DEVICE_TYPE_INPUT | CUBEB_DEVICE_TYPE_OUTPUT)) {
   3483    flow = eAll;
   3484  } else {
   3485    return CUBEB_ERROR;
   3486  }
   3487 
   3488  hr = enumerator->EnumAudioEndpoints(flow, state_mask, collection.receive());
   3489  if (FAILED(hr)) {
   3490    LOG("Could not enumerate audio endpoints: %lx", hr);
   3491    return CUBEB_ERROR;
   3492  }
   3493 
   3494  hr = collection->GetCount(&cc);
   3495  if (FAILED(hr)) {
   3496    LOG("IMMDeviceCollection::GetCount() failed: %lx", hr);
   3497    return CUBEB_ERROR;
   3498  }
   3499  cubeb_device_info * devices = new cubeb_device_info[cc];
   3500  if (!devices)
   3501    return CUBEB_ERROR;
   3502 
   3503  PodZero(devices, cc);
   3504  out->count = 0;
   3505  for (i = 0; i < cc; i++) {
   3506    com_ptr<IMMDevice> dev;
   3507    hr = collection->Item(i, dev.receive());
   3508    if (FAILED(hr)) {
   3509      LOG("IMMDeviceCollection::Item(%u) failed: %lx", i - 1, hr);
   3510      continue;
   3511    }
   3512    if (wasapi_create_device(context, devices[out->count], enumerator.get(),
   3513                             dev.get(), &default_devices) == CUBEB_OK) {
   3514      out->count += 1;
   3515    }
   3516  }
   3517 
   3518  out->device = devices;
   3519  return CUBEB_OK;
   3520 }
   3521 
   3522 static int
   3523 wasapi_enumerate_devices(cubeb * context, cubeb_device_type type,
   3524                         cubeb_device_collection * out)
   3525 {
   3526  return wasapi_enumerate_devices_internal(
   3527      context, type, out,
   3528      DEVICE_STATE_ACTIVE | DEVICE_STATE_DISABLED | DEVICE_STATE_UNPLUGGED);
   3529 }
   3530 
   3531 static int
   3532 wasapi_device_collection_destroy(cubeb * /*ctx*/,
   3533                                 cubeb_device_collection * collection)
   3534 {
   3535  XASSERT(collection);
   3536 
   3537  for (size_t n = 0; n < collection->count; n++) {
   3538    cubeb_device_info & dev = collection->device[n];
   3539    wasapi_destroy_device(&dev);
   3540  }
   3541 
   3542  delete[] collection->device;
   3543  return CUBEB_OK;
   3544 }
   3545 
   3546 int
   3547 wasapi_set_input_processing_params(cubeb_stream * stream,
   3548                                   cubeb_input_processing_params params)
   3549 {
   3550  LOG("Cannot set voice processing params after init. Use cubeb_stream_init.");
   3551  return CUBEB_ERROR_NOT_SUPPORTED;
   3552 }
   3553 
   3554 static int
   3555 wasapi_register_device_collection_changed(
   3556    cubeb * context, cubeb_device_type devtype,
   3557    cubeb_device_collection_changed_callback collection_changed_callback,
   3558    void * user_ptr)
   3559 {
   3560  auto_lock lock(context->lock);
   3561  if (devtype == CUBEB_DEVICE_TYPE_UNKNOWN) {
   3562    return CUBEB_ERROR_INVALID_PARAMETER;
   3563  }
   3564 
   3565  if (collection_changed_callback) {
   3566    // Make sure it has been unregistered first.
   3567    XASSERT(((devtype & CUBEB_DEVICE_TYPE_INPUT) &&
   3568             !context->input_collection_changed_callback) ||
   3569            ((devtype & CUBEB_DEVICE_TYPE_OUTPUT) &&
   3570             !context->output_collection_changed_callback));
   3571 
   3572    // Stop the notification client. Notifications arrive on
   3573    // a separate thread. We stop them here to avoid
   3574    // synchronization issues during the update.
   3575    if (context->device_collection_enumerator.get()) {
   3576      HRESULT hr = unregister_collection_notification_client(context);
   3577      if (FAILED(hr)) {
   3578        return CUBEB_ERROR;
   3579      }
   3580    }
   3581 
   3582    if (devtype & CUBEB_DEVICE_TYPE_INPUT) {
   3583      context->input_collection_changed_callback = collection_changed_callback;
   3584      context->input_collection_changed_user_ptr = user_ptr;
   3585    }
   3586    if (devtype & CUBEB_DEVICE_TYPE_OUTPUT) {
   3587      context->output_collection_changed_callback = collection_changed_callback;
   3588      context->output_collection_changed_user_ptr = user_ptr;
   3589    }
   3590 
   3591    HRESULT hr = register_collection_notification_client(context);
   3592    if (FAILED(hr)) {
   3593      return CUBEB_ERROR;
   3594    }
   3595  } else {
   3596    if (!context->device_collection_enumerator.get()) {
   3597      // Already unregistered, ignore it.
   3598      return CUBEB_OK;
   3599    }
   3600 
   3601    HRESULT hr = unregister_collection_notification_client(context);
   3602    if (FAILED(hr)) {
   3603      return CUBEB_ERROR;
   3604    }
   3605    if (devtype & CUBEB_DEVICE_TYPE_INPUT) {
   3606      context->input_collection_changed_callback = nullptr;
   3607      context->input_collection_changed_user_ptr = nullptr;
   3608    }
   3609    if (devtype & CUBEB_DEVICE_TYPE_OUTPUT) {
   3610      context->output_collection_changed_callback = nullptr;
   3611      context->output_collection_changed_user_ptr = nullptr;
   3612    }
   3613 
   3614    // If after the updates we still have registered
   3615    // callbacks restart the notification client.
   3616    if (context->input_collection_changed_callback ||
   3617        context->output_collection_changed_callback) {
   3618      hr = register_collection_notification_client(context);
   3619      if (FAILED(hr)) {
   3620        return CUBEB_ERROR;
   3621      }
   3622    }
   3623  }
   3624 
   3625  return CUBEB_OK;
   3626 }
   3627 
   3628 cubeb_ops const wasapi_ops = {
   3629    /*.init =*/wasapi_init,
   3630    /*.get_backend_id =*/wasapi_get_backend_id,
   3631    /*.get_max_channel_count =*/wasapi_get_max_channel_count,
   3632    /*.get_min_latency =*/wasapi_get_min_latency,
   3633    /*.get_preferred_sample_rate =*/wasapi_get_preferred_sample_rate,
   3634    /*.get_supported_input_processing_params =*/
   3635    wasapi_get_supported_input_processing_params,
   3636    /*.enumerate_devices =*/wasapi_enumerate_devices,
   3637    /*.device_collection_destroy =*/wasapi_device_collection_destroy,
   3638    /*.destroy =*/wasapi_destroy,
   3639    /*.stream_init =*/wasapi_stream_init,
   3640    /*.stream_destroy =*/wasapi_stream_destroy,
   3641    /*.stream_start =*/wasapi_stream_start,
   3642    /*.stream_stop =*/wasapi_stream_stop,
   3643    /*.stream_get_position =*/wasapi_stream_get_position,
   3644    /*.stream_get_latency =*/wasapi_stream_get_latency,
   3645    /*.stream_get_input_latency =*/wasapi_stream_get_input_latency,
   3646    /*.stream_set_volume =*/wasapi_stream_set_volume,
   3647    /*.stream_set_name =*/NULL,
   3648    /*.stream_get_current_device =*/NULL,
   3649    /*.stream_set_input_mute =*/NULL,
   3650    /*.stream_set_input_processing_params =*/wasapi_set_input_processing_params,
   3651    /*.stream_device_destroy =*/NULL,
   3652    /*.stream_register_device_changed_callback =*/NULL,
   3653    /*.register_device_collection_changed =*/
   3654    wasapi_register_device_collection_changed,
   3655 };
   3656 } // namespace