1 /**
2  * OpenAL cross platform audio library
3  * Copyright (C) 2011 by authors.
4  * This library is free software; you can redistribute it and/or
5  *  modify it under the terms of the GNU Library General Public
6  *  License as published by the Free Software Foundation; either
7  *  version 2 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
11  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  *  Library General Public License for more details.
13  *
14  * You should have received a copy of the GNU Library General Public
main(int,char **)15  *  License along with this library; if not, write to the
16  *  Free Software Foundation, Inc.,
17  *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18  * Or go to http://www.gnu.org/copyleft/lgpl.html
19  */
20 
21 #include "config.h"
22 
23 #include "backends/wasapi.h"
24 
25 #define WIN32_LEAN_AND_MEAN
26 #include <windows.h>
27 
28 #include <stdlib.h>
29 #include <stdio.h>
30 #include <memory.h>
31 
32 #include <wtypes.h>
33 #include <mmdeviceapi.h>
34 #include <audioclient.h>
35 #include <cguid.h>
36 #include <devpropdef.h>
37 #include <mmreg.h>
38 #include <propsys.h>
39 #include <propkey.h>
40 #include <devpkey.h>
41 #ifndef _WAVEFORMATEXTENSIBLE_
42 #include <ks.h>
43 #include <ksmedia.h>
44 #endif
45 
46 #include <algorithm>
47 #include <atomic>
48 #include <chrono>
49 #include <condition_variable>
50 #include <deque>
51 #include <functional>
52 #include <future>
53 #include <mutex>
54 #include <string>
55 #include <thread>
56 #include <vector>
57 
58 #include "albit.h"
59 #include "alcmain.h"
60 #include "alu.h"
61 #include "compat.h"
62 #include "converter.h"
63 #include "core/logging.h"
64 #include "ringbuffer.h"
65 #include "strutils.h"
66 #include "threads.h"
67 
68 
69 /* Some headers seem to define these as macros for __uuidof, which is annoying
70  * since some headers don't declare them at all. Hopefully the ifdef is enough
71  * to tell if they need to be declared.
72  */
73 #ifndef KSDATAFORMAT_SUBTYPE_PCM
74 DEFINE_GUID(KSDATAFORMAT_SUBTYPE_PCM, 0x00000001, 0x0000, 0x0010, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71);
75 #endif
76 #ifndef KSDATAFORMAT_SUBTYPE_IEEE_FLOAT
77 DEFINE_GUID(KSDATAFORMAT_SUBTYPE_IEEE_FLOAT, 0x00000003, 0x0000, 0x0010, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71);
78 #endif
79 
80 DEFINE_DEVPROPKEY(DEVPKEY_Device_FriendlyName, 0xa45c254e, 0xdf1c, 0x4efd, 0x80,0x20, 0x67,0xd1,0x46,0xa8,0x50,0xe0, 14);
81 DEFINE_PROPERTYKEY(PKEY_AudioEndpoint_FormFactor, 0x1da5d803, 0xd492, 0x4edd, 0x8c,0x23, 0xe0,0xc0,0xff,0xee,0x7f,0x0e, 0);
82 DEFINE_PROPERTYKEY(PKEY_AudioEndpoint_GUID, 0x1da5d803, 0xd492, 0x4edd, 0x8c, 0x23,0xe0, 0xc0,0xff,0xee,0x7f,0x0e, 4 );
83 
84 
85 namespace {
86 
87 using std::chrono::milliseconds;
88 using std::chrono::seconds;
89 
90 using ReferenceTime = std::chrono::duration<REFERENCE_TIME,std::ratio<1,10000000>>;
91 
92 inline constexpr ReferenceTime operator "" _reftime(unsigned long long int n) noexcept
93 { return ReferenceTime{static_cast<REFERENCE_TIME>(n)}; }
94 
95 
96 #define MONO SPEAKER_FRONT_CENTER
97 #define STEREO (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT)
98 #define QUAD (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT|SPEAKER_BACK_LEFT|SPEAKER_BACK_RIGHT)
99 #define X5DOT1 (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT|SPEAKER_FRONT_CENTER|SPEAKER_LOW_FREQUENCY|SPEAKER_SIDE_LEFT|SPEAKER_SIDE_RIGHT)
100 #define X5DOT1REAR (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT|SPEAKER_FRONT_CENTER|SPEAKER_LOW_FREQUENCY|SPEAKER_BACK_LEFT|SPEAKER_BACK_RIGHT)
101 #define X6DOT1 (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT|SPEAKER_FRONT_CENTER|SPEAKER_LOW_FREQUENCY|SPEAKER_BACK_CENTER|SPEAKER_SIDE_LEFT|SPEAKER_SIDE_RIGHT)
102 #define X7DOT1 (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT|SPEAKER_FRONT_CENTER|SPEAKER_LOW_FREQUENCY|SPEAKER_BACK_LEFT|SPEAKER_BACK_RIGHT|SPEAKER_SIDE_LEFT|SPEAKER_SIDE_RIGHT)
103 
104 constexpr inline DWORD MaskFromTopBits(DWORD b) noexcept
105 {
106     b |= b>>1;
107     b |= b>>2;
108     b |= b>>4;
109     b |= b>>8;
110     b |= b>>16;
111     return b;
112 }
113 constexpr DWORD MonoMask{MaskFromTopBits(MONO)};
114 constexpr DWORD StereoMask{MaskFromTopBits(STEREO)};
115 constexpr DWORD QuadMask{MaskFromTopBits(QUAD)};
116 constexpr DWORD X51Mask{MaskFromTopBits(X5DOT1)};
117 constexpr DWORD X51RearMask{MaskFromTopBits(X5DOT1REAR)};
118 constexpr DWORD X61Mask{MaskFromTopBits(X6DOT1)};
119 constexpr DWORD X71Mask{MaskFromTopBits(X7DOT1)};
120 
121 #define DEVNAME_HEAD "OpenAL Soft on "
122 
123 
124 /* Scales the given reftime value, rounding the result. */
125 inline uint RefTime2Samples(const ReferenceTime &val, uint srate)
126 {
127     const auto retval = (val*srate + ReferenceTime{seconds{1}}/2) / seconds{1};
128     return static_cast<uint>(mini64(retval, std::numeric_limits<uint>::max()));
129 }
130 
131 
132 template<typename T>
133 class ComPtr {
134     T *mPtr{nullptr};
135 
136 public:
137     ComPtr() noexcept = default;
138     ComPtr(const ComPtr &rhs) : mPtr{rhs.mPtr} { if(mPtr) mPtr->AddRef(); }
139     ComPtr(ComPtr&& rhs) noexcept : mPtr{rhs.mPtr} { rhs.mPtr = nullptr; }
140     ComPtr(std::nullptr_t) noexcept { }
141     explicit ComPtr(T *ptr) noexcept : mPtr{ptr} { }
142     ~ComPtr() { if(mPtr) mPtr->Release(); }
143 
144     ComPtr& operator=(const ComPtr &rhs)
145     {
146         if(!rhs.mPtr)
147         {
148             if(mPtr)
149                 mPtr->Release();
150             mPtr = nullptr;
151         }
152         else
153         {
154             rhs.mPtr->AddRef();
155             try {
156                 if(mPtr)
157                     mPtr->Release();
158                 mPtr = rhs.mPtr;
159             }
160             catch(...) {
161                 rhs.mPtr->Release();
162                 throw;
163             }
164         }
165         return *this;
166     }
167     ComPtr& operator=(ComPtr&& rhs)
168     {
169         if(mPtr)
170             mPtr->Release();
171         mPtr = rhs.mPtr;
172         rhs.mPtr = nullptr;
173         return *this;
174     }
175 
176     operator bool() const noexcept { return mPtr != nullptr; }
177 
178     T& operator*() const noexcept { return *mPtr; }
179     T* operator->() const noexcept { return mPtr; }
180     T* get() const noexcept { return mPtr; }
181     T** getPtr() noexcept { return &mPtr; }
182 
183     T* release() noexcept
184     {
185         T *ret{mPtr};
186         mPtr = nullptr;
187         return ret;
188     }
189 
190     void swap(ComPtr &rhs) noexcept { std::swap(mPtr, rhs.mPtr); }
191     void swap(ComPtr&& rhs) noexcept { std::swap(mPtr, rhs.mPtr); }
192 };
193 
194 
195 class GuidPrinter {
196     char mMsg[64];
197 
198 public:
199     GuidPrinter(const GUID &guid)
200     {
201         std::snprintf(mMsg, al::size(mMsg), "{%08lx-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x}",
202             DWORD{guid.Data1}, guid.Data2, guid.Data3, guid.Data4[0], guid.Data4[1], guid.Data4[2],
203             guid.Data4[3], guid.Data4[4], guid.Data4[5], guid.Data4[6], guid.Data4[7]);
204     }
205     const char *c_str() const { return mMsg; }
206 };
207 
208 struct PropVariant {
209     PROPVARIANT mProp;
210 
211 public:
212     PropVariant() { PropVariantInit(&mProp); }
213     ~PropVariant() { clear(); }
214 
215     void clear() { PropVariantClear(&mProp); }
216 
217     PROPVARIANT* get() noexcept { return &mProp; }
218 
219     PROPVARIANT& operator*() noexcept { return mProp; }
220     const PROPVARIANT& operator*() const noexcept { return mProp; }
221 
222     PROPVARIANT* operator->() noexcept { return &mProp; }
223     const PROPVARIANT* operator->() const noexcept { return &mProp; }
224 };
225 
226 struct DevMap {
227     std::string name;
228     std::string endpoint_guid; // obtained from PKEY_AudioEndpoint_GUID , set to "Unknown device GUID" if absent.
229     std::wstring devid;
230 
231     template<typename T0, typename T1, typename T2>
232     DevMap(T0&& name_, T1&& guid_, T2&& devid_)
233       : name{std::forward<T0>(name_)}
234       , endpoint_guid{std::forward<T1>(guid_)}
235       , devid{std::forward<T2>(devid_)}
236     { }
237 };
238 
239 bool checkName(const al::vector<DevMap> &list, const std::string &name)
240 {
241     return std::find_if(list.cbegin(), list.cend(),
242         [&name](const DevMap &entry) -> bool
243         { return entry.name == name; }
244     ) != list.cend();
245 }
246 
247 al::vector<DevMap> PlaybackDevices;
248 al::vector<DevMap> CaptureDevices;
249 
250 
251 using NameGUIDPair = std::pair<std::string,std::string>;
252 NameGUIDPair get_device_name_and_guid(IMMDevice *device)
253 {
254     static constexpr char UnknownName[]{"Unknown Device Name"};
255     static constexpr char UnknownGuid[]{"Unknown Device GUID"};
256     std::string name{DEVNAME_HEAD};
257     std::string guid;
258 
259     ComPtr<IPropertyStore> ps;
260     HRESULT hr = device->OpenPropertyStore(STGM_READ, ps.getPtr());
261     if(FAILED(hr))
262     {
263         WARN("OpenPropertyStore failed: 0x%08lx\n", hr);
264         return std::make_pair(UnknownName, UnknownGuid);
265     }
266 
267     PropVariant pvprop;
268     hr = ps->GetValue(reinterpret_cast<const PROPERTYKEY&>(DEVPKEY_Device_FriendlyName), pvprop.get());
269     if(FAILED(hr))
270     {
271         WARN("GetValue Device_FriendlyName failed: 0x%08lx\n", hr);
272         name += UnknownName;
273     }
274     else if(pvprop->vt == VT_LPWSTR)
275         name += wstr_to_utf8(pvprop->pwszVal);
276     else
277     {
278         WARN("Unexpected PROPVARIANT type: 0x%04x\n", pvprop->vt);
279         name += UnknownName;
280     }
281 
282     pvprop.clear();
283     hr = ps->GetValue(reinterpret_cast<const PROPERTYKEY&>(PKEY_AudioEndpoint_GUID), pvprop.get());
284     if(FAILED(hr))
285     {
286         WARN("GetValue AudioEndpoint_GUID failed: 0x%08lx\n", hr);
287         guid = UnknownGuid;
288     }
289     else if(pvprop->vt == VT_LPWSTR)
290         guid = wstr_to_utf8(pvprop->pwszVal);
291     else
292     {
293         WARN("Unexpected PROPVARIANT type: 0x%04x\n", pvprop->vt);
294         guid = UnknownGuid;
295     }
296 
297     return std::make_pair(std::move(name), std::move(guid));
298 }
299 
300 void get_device_formfactor(IMMDevice *device, EndpointFormFactor *formfactor)
301 {
302     ComPtr<IPropertyStore> ps;
303     HRESULT hr = device->OpenPropertyStore(STGM_READ, ps.getPtr());
304     if(FAILED(hr))
305     {
306         WARN("OpenPropertyStore failed: 0x%08lx\n", hr);
307         return;
308     }
309 
310     PropVariant pvform;
311     hr = ps->GetValue(reinterpret_cast<const PROPERTYKEY&>(PKEY_AudioEndpoint_FormFactor), pvform.get());
312     if(FAILED(hr))
313         WARN("GetValue AudioEndpoint_FormFactor failed: 0x%08lx\n", hr);
314     else if(pvform->vt == VT_UI4)
315         *formfactor = static_cast<EndpointFormFactor>(pvform->ulVal);
316     else if(pvform->vt == VT_EMPTY)
317         *formfactor = UnknownFormFactor;
318     else
319         WARN("Unexpected PROPVARIANT type: 0x%04x\n", pvform->vt);
320 }
321 
322 
323 void add_device(IMMDevice *device, const WCHAR *devid, al::vector<DevMap> &list)
324 {
325     for(auto &entry : list)
326     {
327         if(entry.devid == devid)
328             return;
329     }
330 
331     auto name_guid = get_device_name_and_guid(device);
332 
333     int count{1};
334     std::string newname{name_guid.first};
335     while(checkName(list, newname))
336     {
337         newname = name_guid.first;
338         newname += " #";
339         newname += std::to_string(++count);
340     }
341     list.emplace_back(std::move(newname), std::move(name_guid.second), devid);
342     const DevMap &newentry = list.back();
343 
344     TRACE("Got device \"%s\", \"%s\", \"%ls\"\n", newentry.name.c_str(),
345           newentry.endpoint_guid.c_str(), newentry.devid.c_str());
346 }
347 
348 WCHAR *get_device_id(IMMDevice *device)
349 {
350     WCHAR *devid;
351 
352     const HRESULT hr{device->GetId(&devid)};
353     if(FAILED(hr))
354     {
355         ERR("Failed to get device id: %lx\n", hr);
356         return nullptr;
357     }
358 
359     return devid;
360 }
361 
362 void probe_devices(IMMDeviceEnumerator *devenum, EDataFlow flowdir, al::vector<DevMap> &list)
363 {
364     al::vector<DevMap>{}.swap(list);
365 
366     ComPtr<IMMDeviceCollection> coll;
367     HRESULT hr{devenum->EnumAudioEndpoints(flowdir, DEVICE_STATE_ACTIVE, coll.getPtr())};
368     if(FAILED(hr))
369     {
370         ERR("Failed to enumerate audio endpoints: 0x%08lx\n", hr);
371         return;
372     }
373 
374     UINT count{0};
375     hr = coll->GetCount(&count);
376     if(SUCCEEDED(hr) && count > 0)
377         list.reserve(count);
378 
379     ComPtr<IMMDevice> device;
380     hr = devenum->GetDefaultAudioEndpoint(flowdir, eMultimedia, device.getPtr());
381     if(SUCCEEDED(hr))
382     {
383         if(WCHAR *devid{get_device_id(device.get())})
384         {
385             add_device(device.get(), devid, list);
386             CoTaskMemFree(devid);
387         }
388         device = nullptr;
389     }
390 
391     for(UINT i{0};i < count;++i)
392     {
393         hr = coll->Item(i, device.getPtr());
394         if(FAILED(hr)) continue;
395 
396         if(WCHAR *devid{get_device_id(device.get())})
397         {
398             add_device(device.get(), devid, list);
399             CoTaskMemFree(devid);
400         }
401         device = nullptr;
402     }
403 }
404 
405 
406 bool MakeExtensible(WAVEFORMATEXTENSIBLE *out, const WAVEFORMATEX *in)
407 {
408     *out = WAVEFORMATEXTENSIBLE{};
409     if(in->wFormatTag == WAVE_FORMAT_EXTENSIBLE)
410     {
411         *out = *CONTAINING_RECORD(in, const WAVEFORMATEXTENSIBLE, Format);
412         out->Format.cbSize = sizeof(*out) - sizeof(out->Format);
413     }
414     else if(in->wFormatTag == WAVE_FORMAT_PCM)
415     {
416         out->Format = *in;
417         out->Format.cbSize = 0;
418         out->Samples.wValidBitsPerSample = out->Format.wBitsPerSample;
419         if(out->Format.nChannels == 1)
420             out->dwChannelMask = MONO;
421         else if(out->Format.nChannels == 2)
422             out->dwChannelMask = STEREO;
423         else
424             ERR("Unhandled PCM channel count: %d\n", out->Format.nChannels);
425         out->SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
426     }
427     else if(in->wFormatTag == WAVE_FORMAT_IEEE_FLOAT)
428     {
429         out->Format = *in;
430         out->Format.cbSize = 0;
431         out->Samples.wValidBitsPerSample = out->Format.wBitsPerSample;
432         if(out->Format.nChannels == 1)
433             out->dwChannelMask = MONO;
434         else if(out->Format.nChannels == 2)
435             out->dwChannelMask = STEREO;
436         else
437             ERR("Unhandled IEEE float channel count: %d\n", out->Format.nChannels);
438         out->SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT;
439     }
440     else
441     {
442         ERR("Unhandled format tag: 0x%04x\n", in->wFormatTag);
443         return false;
444     }
445     return true;
446 }
447 
448 void TraceFormat(const char *msg, const WAVEFORMATEX *format)
449 {
450     constexpr size_t fmtex_extra_size{sizeof(WAVEFORMATEXTENSIBLE)-sizeof(WAVEFORMATEX)};
451     if(format->wFormatTag == WAVE_FORMAT_EXTENSIBLE && format->cbSize >= fmtex_extra_size)
452     {
453         const WAVEFORMATEXTENSIBLE *fmtex{
454             CONTAINING_RECORD(format, const WAVEFORMATEXTENSIBLE, Format)};
455         TRACE("%s:\n"
456             "    FormatTag      = 0x%04x\n"
457             "    Channels       = %d\n"
458             "    SamplesPerSec  = %lu\n"
459             "    AvgBytesPerSec = %lu\n"
460             "    BlockAlign     = %d\n"
461             "    BitsPerSample  = %d\n"
462             "    Size           = %d\n"
463             "    Samples        = %d\n"
464             "    ChannelMask    = 0x%lx\n"
465             "    SubFormat      = %s\n",
466             msg, fmtex->Format.wFormatTag, fmtex->Format.nChannels, fmtex->Format.nSamplesPerSec,
467             fmtex->Format.nAvgBytesPerSec, fmtex->Format.nBlockAlign, fmtex->Format.wBitsPerSample,
468             fmtex->Format.cbSize, fmtex->Samples.wReserved, fmtex->dwChannelMask,
469             GuidPrinter{fmtex->SubFormat}.c_str());
470     }
471     else
472         TRACE("%s:\n"
473             "    FormatTag      = 0x%04x\n"
474             "    Channels       = %d\n"
475             "    SamplesPerSec  = %lu\n"
476             "    AvgBytesPerSec = %lu\n"
477             "    BlockAlign     = %d\n"
478             "    BitsPerSample  = %d\n"
479             "    Size           = %d\n",
480             msg, format->wFormatTag, format->nChannels, format->nSamplesPerSec,
481             format->nAvgBytesPerSec, format->nBlockAlign, format->wBitsPerSample, format->cbSize);
482 }
483 
484 
485 enum class MsgType {
486     OpenDevice,
487     ResetDevice,
488     StartDevice,
489     StopDevice,
490     CloseDevice,
491     EnumeratePlayback,
492     EnumerateCapture,
493     QuitThread,
494 
495     Count
496 };
497 
498 constexpr char MessageStr[static_cast<size_t>(MsgType::Count)][20]{
499     "Open Device",
500     "Reset Device",
501     "Start Device",
502     "Stop Device",
503     "Close Device",
504     "Enumerate Playback",
505     "Enumerate Capture",
506     "Quit"
507 };
508 
509 
510 /* Proxy interface used by the message handler. */
511 struct WasapiProxy {
512     virtual ~WasapiProxy() = default;
513 
514     virtual HRESULT openProxy() = 0;
515     virtual void closeProxy() = 0;
516 
517     virtual HRESULT resetProxy() = 0;
518     virtual HRESULT startProxy() = 0;
519     virtual void  stopProxy() = 0;
520 
521     struct Msg {
522         MsgType mType;
523         WasapiProxy *mProxy;
524         std::promise<HRESULT> mPromise;
525     };
526     static std::deque<Msg> mMsgQueue;
527     static std::mutex mMsgQueueLock;
528     static std::condition_variable mMsgQueueCond;
529 
530     std::future<HRESULT> pushMessage(MsgType type)
531     {
532         std::promise<HRESULT> promise;
533         std::future<HRESULT> future{promise.get_future()};
534         {
535             std::lock_guard<std::mutex> _{mMsgQueueLock};
536             mMsgQueue.emplace_back(Msg{type, this, std::move(promise)});
537         }
538         mMsgQueueCond.notify_one();
539         return future;
540     }
541 
542     static std::future<HRESULT> pushMessageStatic(MsgType type)
543     {
544         std::promise<HRESULT> promise;
545         std::future<HRESULT> future{promise.get_future()};
546         {
547             std::lock_guard<std::mutex> _{mMsgQueueLock};
548             mMsgQueue.emplace_back(Msg{type, nullptr, std::move(promise)});
549         }
550         mMsgQueueCond.notify_one();
551         return future;
552     }
553 
554     static bool popMessage(Msg &msg)
555     {
556         std::unique_lock<std::mutex> lock{mMsgQueueLock};
557         mMsgQueueCond.wait(lock, []{return !mMsgQueue.empty();});
558         msg = std::move(mMsgQueue.front());
559         mMsgQueue.pop_front();
560         return msg.mType != MsgType::QuitThread;
561     }
562 
563     static int messageHandler(std::promise<HRESULT> *promise);
564 };
565 std::deque<WasapiProxy::Msg> WasapiProxy::mMsgQueue;
566 std::mutex WasapiProxy::mMsgQueueLock;
567 std::condition_variable WasapiProxy::mMsgQueueCond;
568 
569 int WasapiProxy::messageHandler(std::promise<HRESULT> *promise)
570 {
571     TRACE("Starting message thread\n");
572 
573     HRESULT cohr{CoInitializeEx(nullptr, COINIT_MULTITHREADED)};
574     if(FAILED(cohr))
575     {
576         WARN("Failed to initialize COM: 0x%08lx\n", cohr);
577         promise->set_value(cohr);
578         return 0;
579     }
580 
581     void *ptr{};
582     HRESULT hr{CoCreateInstance(CLSID_MMDeviceEnumerator, nullptr, CLSCTX_INPROC_SERVER,
583         IID_IMMDeviceEnumerator, &ptr)};
584     if(FAILED(hr))
585     {
586         WARN("Failed to create IMMDeviceEnumerator instance: 0x%08lx\n", hr);
587         promise->set_value(hr);
588         CoUninitialize();
589         return 0;
590     }
591     static_cast<IMMDeviceEnumerator*>(ptr)->Release();
592     CoUninitialize();
593 
594     TRACE("Message thread initialization complete\n");
595     promise->set_value(S_OK);
596     promise = nullptr;
597 
598     TRACE("Starting message loop\n");
599     uint deviceCount{0};
600     Msg msg;
601     while(popMessage(msg))
602     {
603         TRACE("Got message \"%s\" (0x%04x, this=%p)\n",
604             MessageStr[static_cast<size_t>(msg.mType)], static_cast<uint>(msg.mType),
605             decltype(std::declval<void*>()){msg.mProxy});
606 
607         switch(msg.mType)
608         {
609         case MsgType::OpenDevice:
610             hr = cohr = S_OK;
611             if(++deviceCount == 1)
612                 hr = cohr = CoInitializeEx(nullptr, COINIT_MULTITHREADED);
613             if(SUCCEEDED(hr))
614                 hr = msg.mProxy->openProxy();
615             msg.mPromise.set_value(hr);
616 
617             if(FAILED(hr))
618             {
619                 if(--deviceCount == 0 && SUCCEEDED(cohr))
620                     CoUninitialize();
621             }
622             continue;
623 
624         case MsgType::ResetDevice:
625             hr = msg.mProxy->resetProxy();
626             msg.mPromise.set_value(hr);
627             continue;
628 
629         case MsgType::StartDevice:
630             hr = msg.mProxy->startProxy();
631             msg.mPromise.set_value(hr);
632             continue;
633 
634         case MsgType::StopDevice:
635             msg.mProxy->stopProxy();
636             msg.mPromise.set_value(S_OK);
637             continue;
638 
639         case MsgType::CloseDevice:
640             msg.mProxy->closeProxy();
641             msg.mPromise.set_value(S_OK);
642 
643             if(--deviceCount == 0)
644                 CoUninitialize();
645             continue;
646 
647         case MsgType::EnumeratePlayback:
648         case MsgType::EnumerateCapture:
649             hr = cohr = S_OK;
650             if(++deviceCount == 1)
651                 hr = cohr = CoInitializeEx(nullptr, COINIT_MULTITHREADED);
652             if(SUCCEEDED(hr))
653                 hr = CoCreateInstance(CLSID_MMDeviceEnumerator, nullptr, CLSCTX_INPROC_SERVER,
654                     IID_IMMDeviceEnumerator, &ptr);
655             if(FAILED(hr))
656                 msg.mPromise.set_value(hr);
657             else
658             {
659                 ComPtr<IMMDeviceEnumerator> enumerator{static_cast<IMMDeviceEnumerator*>(ptr)};
660 
661                 if(msg.mType == MsgType::EnumeratePlayback)
662                     probe_devices(enumerator.get(), eRender, PlaybackDevices);
663                 else if(msg.mType == MsgType::EnumerateCapture)
664                     probe_devices(enumerator.get(), eCapture, CaptureDevices);
665                 msg.mPromise.set_value(S_OK);
666             }
667 
668             if(--deviceCount == 0 && SUCCEEDED(cohr))
669                 CoUninitialize();
670             continue;
671 
672         default:
673             ERR("Unexpected message: %u\n", static_cast<uint>(msg.mType));
674             msg.mPromise.set_value(E_FAIL);
675             continue;
676         }
677     }
678     TRACE("Message loop finished\n");
679 
680     return 0;
681 }
682 
683 
684 struct WasapiPlayback final : public BackendBase, WasapiProxy {
685     WasapiPlayback(ALCdevice *device) noexcept : BackendBase{device} { }
686     ~WasapiPlayback() override;
687 
688     int mixerProc();
689 
690     void open(const char *name) override;
691     HRESULT openProxy() override;
692     void closeProxy() override;
693 
694     bool reset() override;
695     HRESULT resetProxy() override;
696     void start() override;
697     HRESULT startProxy() override;
698     void stop() override;
699     void stopProxy() override;
700 
701     ClockLatency getClockLatency() override;
702 
703     std::wstring mDevId;
704 
705     HRESULT mOpenStatus{E_FAIL};
706     ComPtr<IMMDevice> mMMDev{nullptr};
707     ComPtr<IAudioClient> mClient{nullptr};
708     ComPtr<IAudioRenderClient> mRender{nullptr};
709     HANDLE mNotifyEvent{nullptr};
710 
711     UINT32 mFrameStep{0u};
712     std::atomic<UINT32> mPadding{0u};
713 
714     std::mutex mMutex;
715 
716     std::atomic<bool> mKillNow{true};
717     std::thread mThread;
718 
719     DEF_NEWDEL(WasapiPlayback)
720 };
721 
722 WasapiPlayback::~WasapiPlayback()
723 {
724     if(SUCCEEDED(mOpenStatus))
725         pushMessage(MsgType::CloseDevice).wait();
726     mOpenStatus = E_FAIL;
727 
728     if(mNotifyEvent != nullptr)
729         CloseHandle(mNotifyEvent);
730     mNotifyEvent = nullptr;
731 }
732 
733 
734 FORCE_ALIGN int WasapiPlayback::mixerProc()
735 {
736     HRESULT hr{CoInitializeEx(nullptr, COINIT_MULTITHREADED)};
737     if(FAILED(hr))
738     {
739         ERR("CoInitializeEx(nullptr, COINIT_MULTITHREADED) failed: 0x%08lx\n", hr);
740         mDevice->handleDisconnect("COM init failed: 0x%08lx", hr);
741         return 1;
742     }
743 
744     SetRTPriority();
745     althrd_setname(MIXER_THREAD_NAME);
746 
747     const uint update_size{mDevice->UpdateSize};
748     const UINT32 buffer_len{mDevice->BufferSize};
749     while(!mKillNow.load(std::memory_order_relaxed))
750     {
751         UINT32 written;
752         hr = mClient->GetCurrentPadding(&written);
753         if(FAILED(hr))
754         {
755             ERR("Failed to get padding: 0x%08lx\n", hr);
756             mDevice->handleDisconnect("Failed to retrieve buffer padding: 0x%08lx", hr);
757             break;
758         }
759         mPadding.store(written, std::memory_order_relaxed);
760 
761         uint len{buffer_len - written};
762         if(len < update_size)
763         {
764             DWORD res{WaitForSingleObjectEx(mNotifyEvent, 2000, FALSE)};
765             if(res != WAIT_OBJECT_0)
766                 ERR("WaitForSingleObjectEx error: 0x%lx\n", res);
767             continue;
768         }
769 
770         BYTE *buffer;
771         hr = mRender->GetBuffer(len, &buffer);
772         if(SUCCEEDED(hr))
773         {
774             {
775                 std::lock_guard<std::mutex> _{mMutex};
776                 mDevice->renderSamples(buffer, len, mFrameStep);
777                 mPadding.store(written + len, std::memory_order_relaxed);
778             }
779             hr = mRender->ReleaseBuffer(len, 0);
780         }
781         if(FAILED(hr))
782         {
783             ERR("Failed to buffer data: 0x%08lx\n", hr);
784             mDevice->handleDisconnect("Failed to send playback samples: 0x%08lx", hr);
785             break;
786         }
787     }
788     mPadding.store(0u, std::memory_order_release);
789 
790     CoUninitialize();
791     return 0;
792 }
793 
794 
795 void WasapiPlayback::open(const char *name)
796 {
797     HRESULT hr{S_OK};
798 
799     mNotifyEvent = CreateEventW(nullptr, FALSE, FALSE, nullptr);
800     if(mNotifyEvent == nullptr)
801     {
802         ERR("Failed to create notify events: %lu\n", GetLastError());
803         hr = E_FAIL;
804     }
805 
806     if(SUCCEEDED(hr))
807     {
808         if(name)
809         {
810             if(PlaybackDevices.empty())
811                 pushMessage(MsgType::EnumeratePlayback).wait();
812 
813             hr = E_FAIL;
814             auto iter = std::find_if(PlaybackDevices.cbegin(), PlaybackDevices.cend(),
815                 [name](const DevMap &entry) -> bool
816                 { return entry.name == name || entry.endpoint_guid == name; });
817             if(iter == PlaybackDevices.cend())
818             {
819                 const std::wstring wname{utf8_to_wstr(name)};
820                 iter = std::find_if(PlaybackDevices.cbegin(), PlaybackDevices.cend(),
821                     [&wname](const DevMap &entry) -> bool
822                     { return entry.devid == wname; });
823             }
824             if(iter == PlaybackDevices.cend())
825                 WARN("Failed to find device name matching \"%s\"\n", name);
826             else
827             {
828                 mDevId = iter->devid;
829                 mDevice->DeviceName = iter->name;
830                 hr = S_OK;
831             }
832         }
833     }
834 
835     if(SUCCEEDED(hr))
836         hr = pushMessage(MsgType::OpenDevice).get();
837     mOpenStatus = hr;
838 
839     if(FAILED(hr))
840     {
841         if(mNotifyEvent != nullptr)
842             CloseHandle(mNotifyEvent);
843         mNotifyEvent = nullptr;
844 
845         mDevId.clear();
846 
847         throw al::backend_exception{al::backend_error::DeviceError, "Device init failed: 0x%08lx",
848             hr};
849     }
850 }
851 
852 HRESULT WasapiPlayback::openProxy()
853 {
854     void *ptr;
855     HRESULT hr{CoCreateInstance(CLSID_MMDeviceEnumerator, nullptr, CLSCTX_INPROC_SERVER,
856         IID_IMMDeviceEnumerator, &ptr)};
857     if(SUCCEEDED(hr))
858     {
859         ComPtr<IMMDeviceEnumerator> enumerator{static_cast<IMMDeviceEnumerator*>(ptr)};
860         if(mDevId.empty())
861             hr = enumerator->GetDefaultAudioEndpoint(eRender, eMultimedia, mMMDev.getPtr());
862         else
863             hr = enumerator->GetDevice(mDevId.c_str(), mMMDev.getPtr());
864     }
865     if(SUCCEEDED(hr))
866         hr = mMMDev->Activate(IID_IAudioClient, CLSCTX_INPROC_SERVER, nullptr, &ptr);
867     if(SUCCEEDED(hr))
868     {
869         mClient = ComPtr<IAudioClient>{static_cast<IAudioClient*>(ptr)};
870         if(mDevice->DeviceName.empty())
871             mDevice->DeviceName = get_device_name_and_guid(mMMDev.get()).first;
872     }
873 
874     if(FAILED(hr))
875         mMMDev = nullptr;
876 
877     return hr;
878 }
879 
880 void WasapiPlayback::closeProxy()
881 {
882     mClient = nullptr;
883     mMMDev = nullptr;
884 }
885 
886 
887 bool WasapiPlayback::reset()
888 {
889     HRESULT hr{pushMessage(MsgType::ResetDevice).get()};
890     if(FAILED(hr))
891         throw al::backend_exception{al::backend_error::DeviceError, "0x%08lx", hr};
892     return true;
893 }
894 
895 HRESULT WasapiPlayback::resetProxy()
896 {
897     mClient = nullptr;
898 
899     void *ptr;
900     HRESULT hr{mMMDev->Activate(IID_IAudioClient, CLSCTX_INPROC_SERVER, nullptr, &ptr)};
901     if(FAILED(hr))
902     {
903         ERR("Failed to reactivate audio client: 0x%08lx\n", hr);
904         return hr;
905     }
906     mClient = ComPtr<IAudioClient>{static_cast<IAudioClient*>(ptr)};
907 
908     WAVEFORMATEX *wfx;
909     hr = mClient->GetMixFormat(&wfx);
910     if(FAILED(hr))
911     {
912         ERR("Failed to get mix format: 0x%08lx\n", hr);
913         return hr;
914     }
915 
916     WAVEFORMATEXTENSIBLE OutputType;
917     if(!MakeExtensible(&OutputType, wfx))
918     {
919         CoTaskMemFree(wfx);
920         return E_FAIL;
921     }
922     CoTaskMemFree(wfx);
923     wfx = nullptr;
924 
925     const ReferenceTime per_time{ReferenceTime{seconds{mDevice->UpdateSize}} / mDevice->Frequency};
926     const ReferenceTime buf_time{ReferenceTime{seconds{mDevice->BufferSize}} / mDevice->Frequency};
927 
928     if(!mDevice->Flags.test(FrequencyRequest))
929         mDevice->Frequency = OutputType.Format.nSamplesPerSec;
930     if(!mDevice->Flags.test(ChannelsRequest))
931     {
932         const uint32_t chancount{OutputType.Format.nChannels};
933         const DWORD chanmask{OutputType.dwChannelMask};
934         if(chancount >= 8 && (chanmask&X71Mask) == X7DOT1)
935             mDevice->FmtChans = DevFmtX71;
936         else if(chancount >= 7 && (chanmask&X61Mask) == X6DOT1)
937             mDevice->FmtChans = DevFmtX61;
938         else if(chancount >= 6 && (chanmask&X51Mask) == X5DOT1)
939             mDevice->FmtChans = DevFmtX51;
940         else if(chancount >= 6 && (chanmask&X51RearMask) == X5DOT1REAR)
941             mDevice->FmtChans = DevFmtX51Rear;
942         else if(chancount >= 4 && (chanmask&QuadMask) == QUAD)
943             mDevice->FmtChans = DevFmtQuad;
944         else if(chancount >= 2 && (chanmask&StereoMask) == STEREO)
945             mDevice->FmtChans = DevFmtStereo;
946         else if(chancount >= 1 && (chanmask&MonoMask) == MONO)
947             mDevice->FmtChans = DevFmtMono;
948         else
949             ERR("Unhandled channel config: %d -- 0x%08lx\n", chancount, chanmask);
950     }
951 
952     OutputType.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
953     switch(mDevice->FmtChans)
954     {
955     case DevFmtMono:
956         OutputType.Format.nChannels = 1;
957         OutputType.dwChannelMask = MONO;
958         break;
959     case DevFmtAmbi3D:
960         mDevice->FmtChans = DevFmtStereo;
961         /*fall-through*/
962     case DevFmtStereo:
963         OutputType.Format.nChannels = 2;
964         OutputType.dwChannelMask = STEREO;
965         break;
966     case DevFmtQuad:
967         OutputType.Format.nChannels = 4;
968         OutputType.dwChannelMask = QUAD;
969         break;
970     case DevFmtX51:
971         OutputType.Format.nChannels = 6;
972         OutputType.dwChannelMask = X5DOT1;
973         break;
974     case DevFmtX51Rear:
975         OutputType.Format.nChannels = 6;
976         OutputType.dwChannelMask = X5DOT1REAR;
977         break;
978     case DevFmtX61:
979         OutputType.Format.nChannels = 7;
980         OutputType.dwChannelMask = X6DOT1;
981         break;
982     case DevFmtX71:
983         OutputType.Format.nChannels = 8;
984         OutputType.dwChannelMask = X7DOT1;
985         break;
986     }
987     switch(mDevice->FmtType)
988     {
989     case DevFmtByte:
990         mDevice->FmtType = DevFmtUByte;
991         /* fall-through */
992     case DevFmtUByte:
993         OutputType.Format.wBitsPerSample = 8;
994         OutputType.Samples.wValidBitsPerSample = 8;
995         OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
996         break;
997     case DevFmtUShort:
998         mDevice->FmtType = DevFmtShort;
999         /* fall-through */
1000     case DevFmtShort:
1001         OutputType.Format.wBitsPerSample = 16;
1002         OutputType.Samples.wValidBitsPerSample = 16;
1003         OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
1004         break;
1005     case DevFmtUInt:
1006         mDevice->FmtType = DevFmtInt;
1007         /* fall-through */
1008     case DevFmtInt:
1009         OutputType.Format.wBitsPerSample = 32;
1010         OutputType.Samples.wValidBitsPerSample = 32;
1011         OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
1012         break;
1013     case DevFmtFloat:
1014         OutputType.Format.wBitsPerSample = 32;
1015         OutputType.Samples.wValidBitsPerSample = 32;
1016         OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT;
1017         break;
1018     }
1019     OutputType.Format.nSamplesPerSec = mDevice->Frequency;
1020 
1021     OutputType.Format.nBlockAlign = static_cast<WORD>(OutputType.Format.nChannels *
1022         OutputType.Format.wBitsPerSample / 8);
1023     OutputType.Format.nAvgBytesPerSec = OutputType.Format.nSamplesPerSec *
1024         OutputType.Format.nBlockAlign;
1025 
1026     TraceFormat("Requesting playback format", &OutputType.Format);
1027     hr = mClient->IsFormatSupported(AUDCLNT_SHAREMODE_SHARED, &OutputType.Format, &wfx);
1028     if(FAILED(hr))
1029     {
1030         ERR("Failed to check format support: 0x%08lx\n", hr);
1031         hr = mClient->GetMixFormat(&wfx);
1032     }
1033     if(FAILED(hr))
1034     {
1035         ERR("Failed to find a supported format: 0x%08lx\n", hr);
1036         return hr;
1037     }
1038 
1039     if(wfx != nullptr)
1040     {
1041         TraceFormat("Got playback format", wfx);
1042         if(!MakeExtensible(&OutputType, wfx))
1043         {
1044             CoTaskMemFree(wfx);
1045             return E_FAIL;
1046         }
1047         CoTaskMemFree(wfx);
1048         wfx = nullptr;
1049 
1050         mDevice->Frequency = OutputType.Format.nSamplesPerSec;
1051         const uint32_t chancount{OutputType.Format.nChannels};
1052         const DWORD chanmask{OutputType.dwChannelMask};
1053         if(chancount >= 8 && (chanmask&X71Mask) == X7DOT1)
1054             mDevice->FmtChans = DevFmtX71;
1055         else if(chancount >= 7 && (chanmask&X61Mask) == X6DOT1)
1056             mDevice->FmtChans = DevFmtX61;
1057         else if(chancount >= 6 && (chanmask&X51Mask) == X5DOT1)
1058             mDevice->FmtChans = DevFmtX51;
1059         else if(chancount >= 6 && (chanmask&X51RearMask) == X5DOT1REAR)
1060             mDevice->FmtChans = DevFmtX51Rear;
1061         else if(chancount >= 4 && (chanmask&QuadMask) == QUAD)
1062             mDevice->FmtChans = DevFmtQuad;
1063         else if(chancount >= 2 && (chanmask&StereoMask) == STEREO)
1064             mDevice->FmtChans = DevFmtStereo;
1065         else if(chancount >= 1 && (chanmask&MonoMask) == MONO)
1066             mDevice->FmtChans = DevFmtMono;
1067         else
1068         {
1069             ERR("Unhandled extensible channels: %d -- 0x%08lx\n", OutputType.Format.nChannels,
1070                 OutputType.dwChannelMask);
1071             mDevice->FmtChans = DevFmtStereo;
1072             OutputType.Format.nChannels = 2;
1073             OutputType.dwChannelMask = STEREO;
1074         }
1075 
1076         if(IsEqualGUID(OutputType.SubFormat, KSDATAFORMAT_SUBTYPE_PCM))
1077         {
1078             if(OutputType.Format.wBitsPerSample == 8)
1079                 mDevice->FmtType = DevFmtUByte;
1080             else if(OutputType.Format.wBitsPerSample == 16)
1081                 mDevice->FmtType = DevFmtShort;
1082             else if(OutputType.Format.wBitsPerSample == 32)
1083                 mDevice->FmtType = DevFmtInt;
1084             else
1085             {
1086                 mDevice->FmtType = DevFmtShort;
1087                 OutputType.Format.wBitsPerSample = 16;
1088             }
1089         }
1090         else if(IsEqualGUID(OutputType.SubFormat, KSDATAFORMAT_SUBTYPE_IEEE_FLOAT))
1091         {
1092             mDevice->FmtType = DevFmtFloat;
1093             OutputType.Format.wBitsPerSample = 32;
1094         }
1095         else
1096         {
1097             ERR("Unhandled format sub-type: %s\n", GuidPrinter{OutputType.SubFormat}.c_str());
1098             mDevice->FmtType = DevFmtShort;
1099             if(OutputType.Format.wFormatTag != WAVE_FORMAT_EXTENSIBLE)
1100                 OutputType.Format.wFormatTag = WAVE_FORMAT_PCM;
1101             OutputType.Format.wBitsPerSample = 16;
1102             OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
1103         }
1104         OutputType.Samples.wValidBitsPerSample = OutputType.Format.wBitsPerSample;
1105     }
1106     mFrameStep = OutputType.Format.nChannels;
1107 
1108     EndpointFormFactor formfactor{UnknownFormFactor};
1109     get_device_formfactor(mMMDev.get(), &formfactor);
1110     mDevice->IsHeadphones = (mDevice->FmtChans == DevFmtStereo
1111         && (formfactor == Headphones || formfactor == Headset));
1112 
1113     setChannelOrderFromWFXMask(OutputType.dwChannelMask);
1114 
1115     hr = mClient->Initialize(AUDCLNT_SHAREMODE_SHARED, AUDCLNT_STREAMFLAGS_EVENTCALLBACK,
1116         buf_time.count(), 0, &OutputType.Format, nullptr);
1117     if(FAILED(hr))
1118     {
1119         ERR("Failed to initialize audio client: 0x%08lx\n", hr);
1120         return hr;
1121     }
1122 
1123     UINT32 buffer_len{};
1124     ReferenceTime min_per{};
1125     hr = mClient->GetDevicePeriod(&reinterpret_cast<REFERENCE_TIME&>(min_per), nullptr);
1126     if(SUCCEEDED(hr))
1127         hr = mClient->GetBufferSize(&buffer_len);
1128     if(FAILED(hr))
1129     {
1130         ERR("Failed to get audio buffer info: 0x%08lx\n", hr);
1131         return hr;
1132     }
1133 
1134     /* Find the nearest multiple of the period size to the update size */
1135     if(min_per < per_time)
1136         min_per *= maxi64((per_time + min_per/2) / min_per, 1);
1137     mDevice->UpdateSize = minu(RefTime2Samples(min_per, mDevice->Frequency), buffer_len/2);
1138     mDevice->BufferSize = buffer_len;
1139 
1140     hr = mClient->SetEventHandle(mNotifyEvent);
1141     if(FAILED(hr))
1142     {
1143         ERR("Failed to set event handle: 0x%08lx\n", hr);
1144         return hr;
1145     }
1146 
1147     return hr;
1148 }
1149 
1150 
1151 void WasapiPlayback::start()
1152 {
1153     const HRESULT hr{pushMessage(MsgType::StartDevice).get()};
1154     if(FAILED(hr))
1155         throw al::backend_exception{al::backend_error::DeviceError,
1156             "Failed to start playback: 0x%lx", hr};
1157 }
1158 
1159 HRESULT WasapiPlayback::startProxy()
1160 {
1161     ResetEvent(mNotifyEvent);
1162 
1163     HRESULT hr{mClient->Start()};
1164     if(FAILED(hr))
1165     {
1166         ERR("Failed to start audio client: 0x%08lx\n", hr);
1167         return hr;
1168     }
1169 
1170     void *ptr;
1171     hr = mClient->GetService(IID_IAudioRenderClient, &ptr);
1172     if(SUCCEEDED(hr))
1173     {
1174         mRender = ComPtr<IAudioRenderClient>{static_cast<IAudioRenderClient*>(ptr)};
1175         try {
1176             mKillNow.store(false, std::memory_order_release);
1177             mThread = std::thread{std::mem_fn(&WasapiPlayback::mixerProc), this};
1178         }
1179         catch(...) {
1180             mRender = nullptr;
1181             ERR("Failed to start thread\n");
1182             hr = E_FAIL;
1183         }
1184     }
1185 
1186     if(FAILED(hr))
1187         mClient->Stop();
1188 
1189     return hr;
1190 }
1191 
1192 
1193 void WasapiPlayback::stop()
1194 { pushMessage(MsgType::StopDevice).wait(); }
1195 
1196 void WasapiPlayback::stopProxy()
1197 {
1198     if(!mRender || !mThread.joinable())
1199         return;
1200 
1201     mKillNow.store(true, std::memory_order_release);
1202     mThread.join();
1203 
1204     mRender = nullptr;
1205     mClient->Stop();
1206 }
1207 
1208 
1209 ClockLatency WasapiPlayback::getClockLatency()
1210 {
1211     ClockLatency ret;
1212 
1213     std::lock_guard<std::mutex> _{mMutex};
1214     ret.ClockTime = GetDeviceClockTime(mDevice);
1215     ret.Latency  = std::chrono::seconds{mPadding.load(std::memory_order_relaxed)};
1216     ret.Latency /= mDevice->Frequency;
1217 
1218     return ret;
1219 }
1220 
1221 
1222 struct WasapiCapture final : public BackendBase, WasapiProxy {
1223     WasapiCapture(ALCdevice *device) noexcept : BackendBase{device} { }
1224     ~WasapiCapture() override;
1225 
1226     int recordProc();
1227 
1228     void open(const char *name) override;
1229     HRESULT openProxy() override;
1230     void closeProxy() override;
1231 
1232     HRESULT resetProxy() override;
1233     void start() override;
1234     HRESULT startProxy() override;
1235     void stop() override;
1236     void stopProxy() override;
1237 
1238     void captureSamples(al::byte *buffer, uint samples) override;
1239     uint availableSamples() override;
1240 
1241     std::wstring mDevId;
1242 
1243     HRESULT mOpenStatus{E_FAIL};
1244     ComPtr<IMMDevice> mMMDev{nullptr};
1245     ComPtr<IAudioClient> mClient{nullptr};
1246     ComPtr<IAudioCaptureClient> mCapture{nullptr};
1247     HANDLE mNotifyEvent{nullptr};
1248 
1249     ChannelConverter mChannelConv{};
1250     SampleConverterPtr mSampleConv;
1251     RingBufferPtr mRing;
1252 
1253     std::atomic<bool> mKillNow{true};
1254     std::thread mThread;
1255 
1256     DEF_NEWDEL(WasapiCapture)
1257 };
1258 
1259 WasapiCapture::~WasapiCapture()
1260 {
1261     if(SUCCEEDED(mOpenStatus))
1262         pushMessage(MsgType::CloseDevice).wait();
1263     mOpenStatus = E_FAIL;
1264 
1265     if(mNotifyEvent != nullptr)
1266         CloseHandle(mNotifyEvent);
1267     mNotifyEvent = nullptr;
1268 }
1269 
1270 
1271 FORCE_ALIGN int WasapiCapture::recordProc()
1272 {
1273     HRESULT hr{CoInitializeEx(nullptr, COINIT_MULTITHREADED)};
1274     if(FAILED(hr))
1275     {
1276         ERR("CoInitializeEx(nullptr, COINIT_MULTITHREADED) failed: 0x%08lx\n", hr);
1277         mDevice->handleDisconnect("COM init failed: 0x%08lx", hr);
1278         return 1;
1279     }
1280 
1281     althrd_setname(RECORD_THREAD_NAME);
1282 
1283     al::vector<float> samples;
1284     while(!mKillNow.load(std::memory_order_relaxed))
1285     {
1286         UINT32 avail;
1287         hr = mCapture->GetNextPacketSize(&avail);
1288         if(FAILED(hr))
1289             ERR("Failed to get next packet size: 0x%08lx\n", hr);
1290         else if(avail > 0)
1291         {
1292             UINT32 numsamples;
1293             DWORD flags;
1294             BYTE *rdata;
1295 
1296             hr = mCapture->GetBuffer(&rdata, &numsamples, &flags, nullptr, nullptr);
1297             if(FAILED(hr))
1298                 ERR("Failed to get capture buffer: 0x%08lx\n", hr);
1299             else
1300             {
1301                 if(mChannelConv.is_active())
1302                 {
1303                     samples.resize(numsamples*2);
1304                     mChannelConv.convert(rdata, samples.data(), numsamples);
1305                     rdata = reinterpret_cast<BYTE*>(samples.data());
1306                 }
1307 
1308                 auto data = mRing->getWriteVector();
1309 
1310                 size_t dstframes;
1311                 if(mSampleConv)
1312                 {
1313                     const void *srcdata{rdata};
1314                     uint srcframes{numsamples};
1315 
1316                     dstframes = mSampleConv->convert(&srcdata, &srcframes, data.first.buf,
1317                         static_cast<uint>(minz(data.first.len, INT_MAX)));
1318                     if(srcframes > 0 && dstframes == data.first.len && data.second.len > 0)
1319                     {
1320                         /* If some source samples remain, all of the first dest
1321                          * block was filled, and there's space in the second
1322                          * dest block, do another run for the second block.
1323                          */
1324                         dstframes += mSampleConv->convert(&srcdata, &srcframes, data.second.buf,
1325                             static_cast<uint>(minz(data.second.len, INT_MAX)));
1326                     }
1327                 }
1328                 else
1329                 {
1330                     const uint framesize{mDevice->frameSizeFromFmt()};
1331                     size_t len1{minz(data.first.len, numsamples)};
1332                     size_t len2{minz(data.second.len, numsamples-len1)};
1333 
1334                     memcpy(data.first.buf, rdata, len1*framesize);
1335                     if(len2 > 0)
1336                         memcpy(data.second.buf, rdata+len1*framesize, len2*framesize);
1337                     dstframes = len1 + len2;
1338                 }
1339 
1340                 mRing->writeAdvance(dstframes);
1341 
1342                 hr = mCapture->ReleaseBuffer(numsamples);
1343                 if(FAILED(hr)) ERR("Failed to release capture buffer: 0x%08lx\n", hr);
1344             }
1345         }
1346 
1347         if(FAILED(hr))
1348         {
1349             mDevice->handleDisconnect("Failed to capture samples: 0x%08lx", hr);
1350             break;
1351         }
1352 
1353         DWORD res{WaitForSingleObjectEx(mNotifyEvent, 2000, FALSE)};
1354         if(res != WAIT_OBJECT_0)
1355             ERR("WaitForSingleObjectEx error: 0x%lx\n", res);
1356     }
1357 
1358     CoUninitialize();
1359     return 0;
1360 }
1361 
1362 
1363 void WasapiCapture::open(const char *name)
1364 {
1365     HRESULT hr{S_OK};
1366 
1367     mNotifyEvent = CreateEventW(nullptr, FALSE, FALSE, nullptr);
1368     if(mNotifyEvent == nullptr)
1369     {
1370         ERR("Failed to create notify event: %lu\n", GetLastError());
1371         hr = E_FAIL;
1372     }
1373 
1374     if(SUCCEEDED(hr))
1375     {
1376         if(name)
1377         {
1378             if(CaptureDevices.empty())
1379                 pushMessage(MsgType::EnumerateCapture).wait();
1380 
1381             hr = E_FAIL;
1382             auto iter = std::find_if(CaptureDevices.cbegin(), CaptureDevices.cend(),
1383                 [name](const DevMap &entry) -> bool
1384                 { return entry.name == name || entry.endpoint_guid == name; });
1385             if(iter == CaptureDevices.cend())
1386             {
1387                 const std::wstring wname{utf8_to_wstr(name)};
1388                 iter = std::find_if(CaptureDevices.cbegin(), CaptureDevices.cend(),
1389                     [&wname](const DevMap &entry) -> bool
1390                     { return entry.devid == wname; });
1391             }
1392             if(iter == CaptureDevices.cend())
1393                 WARN("Failed to find device name matching \"%s\"\n", name);
1394             else
1395             {
1396                 mDevId = iter->devid;
1397                 mDevice->DeviceName = iter->name;
1398                 hr = S_OK;
1399             }
1400         }
1401     }
1402 
1403     if(SUCCEEDED(hr))
1404         hr = pushMessage(MsgType::OpenDevice).get();
1405     mOpenStatus = hr;
1406 
1407     if(FAILED(hr))
1408     {
1409         if(mNotifyEvent != nullptr)
1410             CloseHandle(mNotifyEvent);
1411         mNotifyEvent = nullptr;
1412 
1413         mDevId.clear();
1414 
1415         throw al::backend_exception{al::backend_error::DeviceError, "Device init failed: 0x%08lx",
1416             hr};
1417     }
1418 
1419     hr = pushMessage(MsgType::ResetDevice).get();
1420     if(FAILED(hr))
1421     {
1422         if(hr == E_OUTOFMEMORY)
1423             throw al::backend_exception{al::backend_error::OutOfMemory, "Out of memory"};
1424         throw al::backend_exception{al::backend_error::DeviceError, "Device reset failed"};
1425     }
1426 }
1427 
1428 HRESULT WasapiCapture::openProxy()
1429 {
1430     void *ptr;
1431     HRESULT hr{CoCreateInstance(CLSID_MMDeviceEnumerator, nullptr, CLSCTX_INPROC_SERVER,
1432         IID_IMMDeviceEnumerator, &ptr)};
1433     if(SUCCEEDED(hr))
1434     {
1435         ComPtr<IMMDeviceEnumerator> enumerator{static_cast<IMMDeviceEnumerator*>(ptr)};
1436         if(mDevId.empty())
1437             hr = enumerator->GetDefaultAudioEndpoint(eCapture, eMultimedia, mMMDev.getPtr());
1438         else
1439             hr = enumerator->GetDevice(mDevId.c_str(), mMMDev.getPtr());
1440     }
1441     if(SUCCEEDED(hr))
1442         hr = mMMDev->Activate(IID_IAudioClient, CLSCTX_INPROC_SERVER, nullptr, &ptr);
1443     if(SUCCEEDED(hr))
1444     {
1445         mClient = ComPtr<IAudioClient>{static_cast<IAudioClient*>(ptr)};
1446         if(mDevice->DeviceName.empty())
1447             mDevice->DeviceName = get_device_name_and_guid(mMMDev.get()).first;
1448     }
1449 
1450     if(FAILED(hr))
1451         mMMDev = nullptr;
1452 
1453     return hr;
1454 }
1455 
1456 void WasapiCapture::closeProxy()
1457 {
1458     mClient = nullptr;
1459     mMMDev = nullptr;
1460 }
1461 
1462 HRESULT WasapiCapture::resetProxy()
1463 {
1464     mClient = nullptr;
1465 
1466     void *ptr;
1467     HRESULT hr{mMMDev->Activate(IID_IAudioClient, CLSCTX_INPROC_SERVER, nullptr, &ptr)};
1468     if(FAILED(hr))
1469     {
1470         ERR("Failed to reactivate audio client: 0x%08lx\n", hr);
1471         return hr;
1472     }
1473     mClient = ComPtr<IAudioClient>{static_cast<IAudioClient*>(ptr)};
1474 
1475     // Make sure buffer is at least 100ms in size
1476     ReferenceTime buf_time{ReferenceTime{seconds{mDevice->BufferSize}} / mDevice->Frequency};
1477     buf_time = std::max(buf_time, ReferenceTime{milliseconds{100}});
1478 
1479     WAVEFORMATEXTENSIBLE InputType{};
1480     InputType.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
1481     switch(mDevice->FmtChans)
1482     {
1483     case DevFmtMono:
1484         InputType.Format.nChannels = 1;
1485         InputType.dwChannelMask = MONO;
1486         break;
1487     case DevFmtStereo:
1488         InputType.Format.nChannels = 2;
1489         InputType.dwChannelMask = STEREO;
1490         break;
1491     case DevFmtQuad:
1492         InputType.Format.nChannels = 4;
1493         InputType.dwChannelMask = QUAD;
1494         break;
1495     case DevFmtX51:
1496         InputType.Format.nChannels = 6;
1497         InputType.dwChannelMask = X5DOT1;
1498         break;
1499     case DevFmtX51Rear:
1500         InputType.Format.nChannels = 6;
1501         InputType.dwChannelMask = X5DOT1REAR;
1502         break;
1503     case DevFmtX61:
1504         InputType.Format.nChannels = 7;
1505         InputType.dwChannelMask = X6DOT1;
1506         break;
1507     case DevFmtX71:
1508         InputType.Format.nChannels = 8;
1509         InputType.dwChannelMask = X7DOT1;
1510         break;
1511 
1512     case DevFmtAmbi3D:
1513         return E_FAIL;
1514     }
1515     switch(mDevice->FmtType)
1516     {
1517     /* NOTE: Signedness doesn't matter, the converter will handle it. */
1518     case DevFmtByte:
1519     case DevFmtUByte:
1520         InputType.Format.wBitsPerSample = 8;
1521         InputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
1522         break;
1523     case DevFmtShort:
1524     case DevFmtUShort:
1525         InputType.Format.wBitsPerSample = 16;
1526         InputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
1527         break;
1528     case DevFmtInt:
1529     case DevFmtUInt:
1530         InputType.Format.wBitsPerSample = 32;
1531         InputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
1532         break;
1533     case DevFmtFloat:
1534         InputType.Format.wBitsPerSample = 32;
1535         InputType.SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT;
1536         break;
1537     }
1538     InputType.Samples.wValidBitsPerSample = InputType.Format.wBitsPerSample;
1539     InputType.Format.nSamplesPerSec = mDevice->Frequency;
1540 
1541     InputType.Format.nBlockAlign = static_cast<WORD>(InputType.Format.nChannels *
1542         InputType.Format.wBitsPerSample / 8);
1543     InputType.Format.nAvgBytesPerSec = InputType.Format.nSamplesPerSec *
1544         InputType.Format.nBlockAlign;
1545     InputType.Format.cbSize = sizeof(InputType) - sizeof(InputType.Format);
1546 
1547     TraceFormat("Requesting capture format", &InputType.Format);
1548     WAVEFORMATEX *wfx;
1549     hr = mClient->IsFormatSupported(AUDCLNT_SHAREMODE_SHARED, &InputType.Format, &wfx);
1550     if(FAILED(hr))
1551     {
1552         ERR("Failed to check format support: 0x%08lx\n", hr);
1553         return hr;
1554     }
1555 
1556     mSampleConv = nullptr;
1557     mChannelConv = {};
1558 
1559     if(wfx != nullptr)
1560     {
1561         TraceFormat("Got capture format", wfx);
1562         if(!MakeExtensible(&InputType, wfx))
1563         {
1564             CoTaskMemFree(wfx);
1565             return E_FAIL;
1566         }
1567         CoTaskMemFree(wfx);
1568         wfx = nullptr;
1569 
1570         auto validate_fmt = [](ALCdevice *device, uint32_t chancount, DWORD chanmask) noexcept
1571             -> bool
1572         {
1573             switch(device->FmtChans)
1574             {
1575             /* If the device wants mono, we can handle any input. */
1576             case DevFmtMono:
1577                 return true;
1578             /* If the device wants stereo, we can handle mono or stereo input. */
1579             case DevFmtStereo:
1580                 return (chancount == 2 && (chanmask == 0 || (chanmask&StereoMask) == STEREO))
1581                     || (chancount == 1 && (chanmask&MonoMask) == MONO);
1582             /* Otherwise, the device must match the input type. */
1583             case DevFmtQuad:
1584                 return (chancount == 4 && (chanmask == 0 || (chanmask&QuadMask) == QUAD));
1585             /* 5.1 (Side) and 5.1 (Rear) are interchangeable here. */
1586             case DevFmtX51:
1587             case DevFmtX51Rear:
1588                 return (chancount == 6 && (chanmask == 0 || (chanmask&X51Mask) == X5DOT1
1589                         || (chanmask&X51RearMask) == X5DOT1REAR));
1590             case DevFmtX61:
1591                 return (chancount == 7 && (chanmask == 0 || (chanmask&X61Mask) == X6DOT1));
1592             case DevFmtX71:
1593                 return (chancount == 8 && (chanmask == 0 || (chanmask&X71Mask) == X7DOT1));
1594             case DevFmtAmbi3D: return (chanmask == 0 && device->channelsFromFmt());
1595             }
1596             return false;
1597         };
1598         if(!validate_fmt(mDevice, InputType.Format.nChannels, InputType.dwChannelMask))
1599         {
1600             ERR("Failed to match format, wanted: %s %s %uhz, got: 0x%08lx mask %d channel%s %d-bit %luhz\n",
1601                 DevFmtChannelsString(mDevice->FmtChans), DevFmtTypeString(mDevice->FmtType),
1602                 mDevice->Frequency, InputType.dwChannelMask, InputType.Format.nChannels,
1603                 (InputType.Format.nChannels==1)?"":"s", InputType.Format.wBitsPerSample,
1604                 InputType.Format.nSamplesPerSec);
1605             return E_FAIL;
1606         }
1607     }
1608 
1609     DevFmtType srcType{};
1610     if(IsEqualGUID(InputType.SubFormat, KSDATAFORMAT_SUBTYPE_PCM))
1611     {
1612         if(InputType.Format.wBitsPerSample == 8)
1613             srcType = DevFmtUByte;
1614         else if(InputType.Format.wBitsPerSample == 16)
1615             srcType = DevFmtShort;
1616         else if(InputType.Format.wBitsPerSample == 32)
1617             srcType = DevFmtInt;
1618         else
1619         {
1620             ERR("Unhandled integer bit depth: %d\n", InputType.Format.wBitsPerSample);
1621             return E_FAIL;
1622         }
1623     }
1624     else if(IsEqualGUID(InputType.SubFormat, KSDATAFORMAT_SUBTYPE_IEEE_FLOAT))
1625     {
1626         if(InputType.Format.wBitsPerSample == 32)
1627             srcType = DevFmtFloat;
1628         else
1629         {
1630             ERR("Unhandled float bit depth: %d\n", InputType.Format.wBitsPerSample);
1631             return E_FAIL;
1632         }
1633     }
1634     else
1635     {
1636         ERR("Unhandled format sub-type: %s\n", GuidPrinter{InputType.SubFormat}.c_str());
1637         return E_FAIL;
1638     }
1639 
1640     if(mDevice->FmtChans == DevFmtMono && InputType.Format.nChannels != 1)
1641     {
1642         uint chanmask{(1u<<InputType.Format.nChannels) - 1u};
1643         /* Exclude LFE from the downmix. */
1644         if((InputType.dwChannelMask&SPEAKER_LOW_FREQUENCY))
1645         {
1646             constexpr auto lfemask = MaskFromTopBits(SPEAKER_LOW_FREQUENCY);
1647             const int lfeidx{al::popcount(InputType.dwChannelMask&lfemask) - 1};
1648             chanmask &= ~(1u << lfeidx);
1649         }
1650 
1651         mChannelConv = ChannelConverter{srcType, InputType.Format.nChannels, chanmask,
1652             mDevice->FmtChans};
1653         TRACE("Created %s multichannel-to-mono converter\n", DevFmtTypeString(srcType));
1654         /* The channel converter always outputs float, so change the input type
1655          * for the resampler/type-converter.
1656          */
1657         srcType = DevFmtFloat;
1658     }
1659     else if(mDevice->FmtChans == DevFmtStereo && InputType.Format.nChannels == 1)
1660     {
1661         mChannelConv = ChannelConverter{srcType, 1, 0x1, mDevice->FmtChans};
1662         TRACE("Created %s mono-to-stereo converter\n", DevFmtTypeString(srcType));
1663         srcType = DevFmtFloat;
1664     }
1665 
1666     if(mDevice->Frequency != InputType.Format.nSamplesPerSec || mDevice->FmtType != srcType)
1667     {
1668         mSampleConv = CreateSampleConverter(srcType, mDevice->FmtType, mDevice->channelsFromFmt(),
1669             InputType.Format.nSamplesPerSec, mDevice->Frequency, Resampler::FastBSinc24);
1670         if(!mSampleConv)
1671         {
1672             ERR("Failed to create converter for %s format, dst: %s %uhz, src: %s %luhz\n",
1673                 DevFmtChannelsString(mDevice->FmtChans), DevFmtTypeString(mDevice->FmtType),
1674                 mDevice->Frequency, DevFmtTypeString(srcType), InputType.Format.nSamplesPerSec);
1675             return E_FAIL;
1676         }
1677         TRACE("Created converter for %s format, dst: %s %uhz, src: %s %luhz\n",
1678             DevFmtChannelsString(mDevice->FmtChans), DevFmtTypeString(mDevice->FmtType),
1679             mDevice->Frequency, DevFmtTypeString(srcType), InputType.Format.nSamplesPerSec);
1680     }
1681 
1682     hr = mClient->Initialize(AUDCLNT_SHAREMODE_SHARED, AUDCLNT_STREAMFLAGS_EVENTCALLBACK,
1683         buf_time.count(), 0, &InputType.Format, nullptr);
1684     if(FAILED(hr))
1685     {
1686         ERR("Failed to initialize audio client: 0x%08lx\n", hr);
1687         return hr;
1688     }
1689 
1690     UINT32 buffer_len{};
1691     ReferenceTime min_per{};
1692     hr = mClient->GetDevicePeriod(&reinterpret_cast<REFERENCE_TIME&>(min_per), nullptr);
1693     if(SUCCEEDED(hr))
1694         hr = mClient->GetBufferSize(&buffer_len);
1695     if(FAILED(hr))
1696     {
1697         ERR("Failed to get buffer size: 0x%08lx\n", hr);
1698         return hr;
1699     }
1700     mDevice->UpdateSize = RefTime2Samples(min_per, mDevice->Frequency);
1701     mDevice->BufferSize = buffer_len;
1702 
1703     mRing = RingBuffer::Create(buffer_len, mDevice->frameSizeFromFmt(), false);
1704 
1705     hr = mClient->SetEventHandle(mNotifyEvent);
1706     if(FAILED(hr))
1707     {
1708         ERR("Failed to set event handle: 0x%08lx\n", hr);
1709         return hr;
1710     }
1711 
1712     return hr;
1713 }
1714 
1715 
1716 void WasapiCapture::start()
1717 {
1718     const HRESULT hr{pushMessage(MsgType::StartDevice).get()};
1719     if(FAILED(hr))
1720         throw al::backend_exception{al::backend_error::DeviceError,
1721             "Failed to start recording: 0x%lx", hr};
1722 }
1723 
1724 HRESULT WasapiCapture::startProxy()
1725 {
1726     ResetEvent(mNotifyEvent);
1727 
1728     HRESULT hr{mClient->Start()};
1729     if(FAILED(hr))
1730     {
1731         ERR("Failed to start audio client: 0x%08lx\n", hr);
1732         return hr;
1733     }
1734 
1735     void *ptr;
1736     hr = mClient->GetService(IID_IAudioCaptureClient, &ptr);
1737     if(SUCCEEDED(hr))
1738     {
1739         mCapture = ComPtr<IAudioCaptureClient>{static_cast<IAudioCaptureClient*>(ptr)};
1740         try {
1741             mKillNow.store(false, std::memory_order_release);
1742             mThread = std::thread{std::mem_fn(&WasapiCapture::recordProc), this};
1743         }
1744         catch(...) {
1745             mCapture = nullptr;
1746             ERR("Failed to start thread\n");
1747             hr = E_FAIL;
1748         }
1749     }
1750 
1751     if(FAILED(hr))
1752     {
1753         mClient->Stop();
1754         mClient->Reset();
1755     }
1756 
1757     return hr;
1758 }
1759 
1760 
1761 void WasapiCapture::stop()
1762 { pushMessage(MsgType::StopDevice).wait(); }
1763 
1764 void WasapiCapture::stopProxy()
1765 {
1766     if(!mCapture || !mThread.joinable())
1767         return;
1768 
1769     mKillNow.store(true, std::memory_order_release);
1770     mThread.join();
1771 
1772     mCapture = nullptr;
1773     mClient->Stop();
1774     mClient->Reset();
1775 }
1776 
1777 
1778 void WasapiCapture::captureSamples(al::byte *buffer, uint samples)
1779 { mRing->read(buffer, samples); }
1780 
1781 uint WasapiCapture::availableSamples()
1782 { return static_cast<uint>(mRing->readSpace()); }
1783 
1784 } // namespace
1785 
1786 
1787 bool WasapiBackendFactory::init()
1788 {
1789     static HRESULT InitResult{E_FAIL};
1790 
1791     if(FAILED(InitResult)) try
1792     {
1793         std::promise<HRESULT> promise;
1794         auto future = promise.get_future();
1795 
1796         std::thread{&WasapiProxy::messageHandler, &promise}.detach();
1797         InitResult = future.get();
1798     }
1799     catch(...) {
1800     }
1801 
1802     return SUCCEEDED(InitResult);
1803 }
1804 
1805 bool WasapiBackendFactory::querySupport(BackendType type)
1806 { return type == BackendType::Playback || type == BackendType::Capture; }
1807 
1808 std::string WasapiBackendFactory::probe(BackendType type)
1809 {
1810     std::string outnames;
1811     auto add_device = [&outnames](const DevMap &entry) -> void
1812     {
1813         /* +1 to also append the null char (to ensure a null-separated list and
1814          * double-null terminated list).
1815          */
1816         outnames.append(entry.name.c_str(), entry.name.length()+1);
1817     };
1818 
1819     switch(type)
1820     {
1821     case BackendType::Playback:
1822         WasapiProxy::pushMessageStatic(MsgType::EnumeratePlayback).wait();
1823         std::for_each(PlaybackDevices.cbegin(), PlaybackDevices.cend(), add_device);
1824         break;
1825 
1826     case BackendType::Capture:
1827         WasapiProxy::pushMessageStatic(MsgType::EnumerateCapture).wait();
1828         std::for_each(CaptureDevices.cbegin(), CaptureDevices.cend(), add_device);
1829         break;
1830     }
1831 
1832     return outnames;
1833 }
1834 
1835 BackendPtr WasapiBackendFactory::createBackend(ALCdevice *device, BackendType type)
1836 {
1837     if(type == BackendType::Playback)
1838         return BackendPtr{new WasapiPlayback{device}};
1839     if(type == BackendType::Capture)
1840         return BackendPtr{new WasapiCapture{device}};
1841     return nullptr;
1842 }
1843 
1844 BackendFactory &WasapiBackendFactory::getFactory()
1845 {
1846     static WasapiBackendFactory factory{};
1847     return factory;
1848 }
1849