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