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