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