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.
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.
14 * You should have received a copy of the GNU Library General Public
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
23 #include "backends/wasapi.h"
25 #define WIN32_LEAN_AND_MEAN
33 #include <mmdeviceapi.h>
34 #include <audioclient.h>
36 #include <devpropdef.h>
41 #ifndef _WAVEFORMATEXTENSIBLE_
49 #include <condition_variable>
62 #include "converter.h"
64 #include "ringbuffer.h"
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.
73 #ifndef KSDATAFORMAT_SUBTYPE_PCM
74 DEFINE_GUID(KSDATAFORMAT_SUBTYPE_PCM
, 0x00000001, 0x0000, 0x0010, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71);
76 #ifndef KSDATAFORMAT_SUBTYPE_IEEE_FLOAT
77 DEFINE_GUID(KSDATAFORMAT_SUBTYPE_IEEE_FLOAT
, 0x00000003, 0x0000, 0x0010, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71);
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 );
87 using std::chrono::milliseconds
;
88 using std::chrono::seconds
;
90 using ReferenceTime
= std::chrono::duration
<REFERENCE_TIME
,std::ratio
<1,10000000>>;
92 inline constexpr ReferenceTime
operator "" _reftime(unsigned long long int n
) noexcept
93 { return ReferenceTime
{static_cast<REFERENCE_TIME
>(n
)}; }
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)
104 constexpr inline DWORD
MaskFromTopBits(DWORD b
) noexcept
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
)};
121 #define DEVNAME_HEAD "OpenAL Soft on "
124 /* Scales the given reftime value, rounding the result. */
125 inline ALuint
RefTime2Samples(const ReferenceTime
&val
, ALuint srate
)
127 const auto retval
= (val
*srate
+ ReferenceTime
{seconds
{1}}/2) / seconds
{1};
128 return static_cast<ALuint
>(mini64(retval
, std::numeric_limits
<ALuint
>::max()));
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(); }
144 ComPtr
& operator=(const ComPtr
&rhs
)
167 ComPtr
& operator=(ComPtr
&& rhs
)
176 operator bool() const noexcept
{ return mPtr
!= nullptr; }
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
; }
183 T
* release() noexcept
190 void swap(ComPtr
&rhs
) noexcept
{ std::swap(mPtr
, rhs
.mPtr
); }
191 void swap(ComPtr
&& rhs
) noexcept
{ std::swap(mPtr
, rhs
.mPtr
); }
199 GuidPrinter(const GUID
&guid
)
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]);
205 const char *c_str() const { return mMsg
; }
212 PropVariant() { PropVariantInit(&mProp
); }
213 ~PropVariant() { clear(); }
215 void clear() { PropVariantClear(&mProp
); }
217 PROPVARIANT
* get() noexcept
{ return &mProp
; }
219 PROPVARIANT
& operator*() noexcept
{ return mProp
; }
220 const PROPVARIANT
& operator*() const noexcept
{ return mProp
; }
222 PROPVARIANT
* operator->() noexcept
{ return &mProp
; }
223 const PROPVARIANT
* operator->() const noexcept
{ return &mProp
; }
228 std::string endpoint_guid
; // obtained from PKEY_AudioEndpoint_GUID , set to "Unknown device GUID" if absent.
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_
)}
239 bool checkName(const al::vector
<DevMap
> &list
, const std::string
&name
)
241 return std::find_if(list
.cbegin(), list
.cend(),
242 [&name
](const DevMap
&entry
) -> bool
243 { return entry
.name
== name
; }
247 al::vector
<DevMap
> PlaybackDevices
;
248 al::vector
<DevMap
> CaptureDevices
;
251 using NameGUIDPair
= std::pair
<std::string
,std::string
>;
252 NameGUIDPair
get_device_name_and_guid(IMMDevice
*device
)
254 static constexpr char UnknownName
[]{"Unknown Device Name"};
255 static constexpr char UnknownGuid
[]{"Unknown Device GUID"};
256 std::string name
{DEVNAME_HEAD
};
259 ComPtr
<IPropertyStore
> ps
;
260 HRESULT hr
= device
->OpenPropertyStore(STGM_READ
, ps
.getPtr());
263 WARN("OpenPropertyStore failed: 0x%08lx\n", hr
);
264 return std::make_pair(UnknownName
, UnknownGuid
);
268 hr
= ps
->GetValue(reinterpret_cast<const PROPERTYKEY
&>(DEVPKEY_Device_FriendlyName
), pvprop
.get());
271 WARN("GetValue Device_FriendlyName failed: 0x%08lx\n", hr
);
274 else if(pvprop
->vt
== VT_LPWSTR
)
275 name
+= wstr_to_utf8(pvprop
->pwszVal
);
278 WARN("Unexpected PROPVARIANT type: 0x%04x\n", pvprop
->vt
);
283 hr
= ps
->GetValue(reinterpret_cast<const PROPERTYKEY
&>(PKEY_AudioEndpoint_GUID
), pvprop
.get());
286 WARN("GetValue AudioEndpoint_GUID failed: 0x%08lx\n", hr
);
289 else if(pvprop
->vt
== VT_LPWSTR
)
290 guid
= wstr_to_utf8(pvprop
->pwszVal
);
293 WARN("Unexpected PROPVARIANT type: 0x%04x\n", pvprop
->vt
);
297 return std::make_pair(std::move(name
), std::move(guid
));
300 void get_device_formfactor(IMMDevice
*device
, EndpointFormFactor
*formfactor
)
302 ComPtr
<IPropertyStore
> ps
;
303 HRESULT hr
= device
->OpenPropertyStore(STGM_READ
, ps
.getPtr());
306 WARN("OpenPropertyStore failed: 0x%08lx\n", hr
);
311 hr
= ps
->GetValue(reinterpret_cast<const PROPERTYKEY
&>(PKEY_AudioEndpoint_FormFactor
), pvform
.get());
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
;
319 WARN("Unexpected PROPVARIANT type: 0x%04x\n", pvform
->vt
);
323 void add_device(IMMDevice
*device
, const WCHAR
*devid
, al::vector
<DevMap
> &list
)
325 for(auto &entry
: list
)
327 if(entry
.devid
== devid
)
331 auto name_guid
= get_device_name_and_guid(device
);
334 std::string newname
{name_guid
.first
};
335 while(checkName(list
, newname
))
337 newname
= name_guid
.first
;
339 newname
+= std::to_string(++count
);
341 list
.emplace_back(std::move(newname
), std::move(name_guid
.second
), devid
);
342 const DevMap
&newentry
= list
.back();
344 TRACE("Got device \"%s\", \"%s\", \"%ls\"\n", newentry
.name
.c_str(),
345 newentry
.endpoint_guid
.c_str(), newentry
.devid
.c_str());
348 WCHAR
*get_device_id(IMMDevice
*device
)
352 const HRESULT hr
{device
->GetId(&devid
)};
355 ERR("Failed to get device id: %lx\n", hr
);
362 void probe_devices(IMMDeviceEnumerator
*devenum
, EDataFlow flowdir
, al::vector
<DevMap
> &list
)
364 al::vector
<DevMap
>{}.swap(list
);
366 ComPtr
<IMMDeviceCollection
> coll
;
367 HRESULT hr
{devenum
->EnumAudioEndpoints(flowdir
, DEVICE_STATE_ACTIVE
, coll
.getPtr())};
370 ERR("Failed to enumerate audio endpoints: 0x%08lx\n", hr
);
375 hr
= coll
->GetCount(&count
);
376 if(SUCCEEDED(hr
) && count
> 0)
379 ComPtr
<IMMDevice
> device
;
380 hr
= devenum
->GetDefaultAudioEndpoint(flowdir
, eMultimedia
, device
.getPtr());
383 if(WCHAR
*devid
{get_device_id(device
.get())})
385 add_device(device
.get(), devid
, list
);
386 CoTaskMemFree(devid
);
391 for(UINT i
{0};i
< count
;++i
)
393 hr
= coll
->Item(i
, device
.getPtr());
394 if(FAILED(hr
)) continue;
396 if(WCHAR
*devid
{get_device_id(device
.get())})
398 add_device(device
.get(), devid
, list
);
399 CoTaskMemFree(devid
);
406 bool MakeExtensible(WAVEFORMATEXTENSIBLE
*out
, const WAVEFORMATEX
*in
)
408 *out
= WAVEFORMATEXTENSIBLE
{};
409 if(in
->wFormatTag
== WAVE_FORMAT_EXTENSIBLE
)
411 *out
= *CONTAINING_RECORD(in
, const WAVEFORMATEXTENSIBLE
, Format
);
412 out
->Format
.cbSize
= sizeof(*out
) - sizeof(out
->Format
);
414 else if(in
->wFormatTag
== WAVE_FORMAT_PCM
)
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
;
424 ERR("Unhandled PCM channel count: %d\n", out
->Format
.nChannels
);
425 out
->SubFormat
= KSDATAFORMAT_SUBTYPE_PCM
;
427 else if(in
->wFormatTag
== WAVE_FORMAT_IEEE_FLOAT
)
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
;
437 ERR("Unhandled IEEE float channel count: %d\n", out
->Format
.nChannels
);
438 out
->SubFormat
= KSDATAFORMAT_SUBTYPE_IEEE_FLOAT
;
442 ERR("Unhandled format tag: 0x%04x\n", in
->wFormatTag
);
448 void TraceFormat(const char *msg
, const WAVEFORMATEX
*format
)
450 constexpr size_t fmtex_extra_size
{sizeof(WAVEFORMATEXTENSIBLE
)-sizeof(WAVEFORMATEX
)};
451 if(format
->wFormatTag
== WAVE_FORMAT_EXTENSIBLE
&& format
->cbSize
>= fmtex_extra_size
)
453 const WAVEFORMATEXTENSIBLE
*fmtex
{
454 CONTAINING_RECORD(format
, const WAVEFORMATEXTENSIBLE
, Format
)};
456 " FormatTag = 0x%04x\n"
458 " SamplesPerSec = %lu\n"
459 " AvgBytesPerSec = %lu\n"
461 " BitsPerSample = %d\n"
464 " ChannelMask = 0x%lx\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());
473 " FormatTag = 0x%04x\n"
475 " SamplesPerSec = %lu\n"
476 " AvgBytesPerSec = %lu\n"
478 " BitsPerSample = %d\n"
480 msg
, format
->wFormatTag
, format
->nChannels
, format
->nSamplesPerSec
,
481 format
->nAvgBytesPerSec
, format
->nBlockAlign
, format
->wBitsPerSample
, format
->cbSize
);
498 constexpr char MessageStr
[static_cast<size_t>(MsgType::Count
)][20]{
504 "Enumerate Playback",
510 /* Proxy interface used by the message handler. */
512 virtual ~WasapiProxy() = default;
514 virtual HRESULT
openProxy() = 0;
515 virtual void closeProxy() = 0;
517 virtual HRESULT
resetProxy() = 0;
518 virtual HRESULT
startProxy() = 0;
519 virtual void stopProxy() = 0;
524 std::promise
<HRESULT
> mPromise
;
526 static std::deque
<Msg
> mMsgQueue
;
527 static std::mutex mMsgQueueLock
;
528 static std::condition_variable mMsgQueueCond
;
530 std::future
<HRESULT
> pushMessage(MsgType type
)
532 std::promise
<HRESULT
> promise
;
533 std::future
<HRESULT
> future
{promise
.get_future()};
535 std::lock_guard
<std::mutex
> _
{mMsgQueueLock
};
536 mMsgQueue
.emplace_back(Msg
{type
, this, std::move(promise
)});
538 mMsgQueueCond
.notify_one();
542 static std::future
<HRESULT
> pushMessageStatic(MsgType type
)
544 std::promise
<HRESULT
> promise
;
545 std::future
<HRESULT
> future
{promise
.get_future()};
547 std::lock_guard
<std::mutex
> _
{mMsgQueueLock
};
548 mMsgQueue
.emplace_back(Msg
{type
, nullptr, std::move(promise
)});
550 mMsgQueueCond
.notify_one();
554 static bool popMessage(Msg
&msg
)
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
;
563 static int messageHandler(std::promise
<HRESULT
> *promise
);
565 std::deque
<WasapiProxy::Msg
> WasapiProxy::mMsgQueue
;
566 std::mutex
WasapiProxy::mMsgQueueLock
;
567 std::condition_variable
WasapiProxy::mMsgQueueCond
;
569 int WasapiProxy::messageHandler(std::promise
<HRESULT
> *promise
)
571 TRACE("Starting message thread\n");
573 HRESULT cohr
{CoInitializeEx(nullptr, COINIT_MULTITHREADED
)};
576 WARN("Failed to initialize COM: 0x%08lx\n", cohr
);
577 promise
->set_value(cohr
);
582 HRESULT hr
{CoCreateInstance(CLSID_MMDeviceEnumerator
, nullptr, CLSCTX_INPROC_SERVER
,
583 IID_IMMDeviceEnumerator
, &ptr
)};
586 WARN("Failed to create IMMDeviceEnumerator instance: 0x%08lx\n", hr
);
587 promise
->set_value(hr
);
591 static_cast<IMMDeviceEnumerator
*>(ptr
)->Release();
594 TRACE("Message thread initialization complete\n");
595 promise
->set_value(S_OK
);
598 TRACE("Starting message loop\n");
599 ALuint deviceCount
{0};
601 while(popMessage(msg
))
603 TRACE("Got message \"%s\" (0x%04x, this=%p)\n",
604 MessageStr
[static_cast<size_t>(msg
.mType
)], static_cast<int>(msg
.mType
),
605 decltype(std::declval
<void*>()){msg
.mProxy
});
609 case MsgType::OpenDevice
:
611 if(++deviceCount
== 1)
612 hr
= cohr
= CoInitializeEx(nullptr, COINIT_MULTITHREADED
);
614 hr
= msg
.mProxy
->openProxy();
615 msg
.mPromise
.set_value(hr
);
619 if(--deviceCount
== 0 && SUCCEEDED(cohr
))
624 case MsgType::ResetDevice
:
625 hr
= msg
.mProxy
->resetProxy();
626 msg
.mPromise
.set_value(hr
);
629 case MsgType::StartDevice
:
630 hr
= msg
.mProxy
->startProxy();
631 msg
.mPromise
.set_value(hr
);
634 case MsgType::StopDevice
:
635 msg
.mProxy
->stopProxy();
636 msg
.mPromise
.set_value(S_OK
);
639 case MsgType::CloseDevice
:
640 msg
.mProxy
->closeProxy();
641 msg
.mPromise
.set_value(S_OK
);
643 if(--deviceCount
== 0)
647 case MsgType::EnumeratePlayback
:
648 case MsgType::EnumerateCapture
:
650 if(++deviceCount
== 1)
651 hr
= cohr
= CoInitializeEx(nullptr, COINIT_MULTITHREADED
);
653 hr
= CoCreateInstance(CLSID_MMDeviceEnumerator
, nullptr, CLSCTX_INPROC_SERVER
,
654 IID_IMMDeviceEnumerator
, &ptr
);
656 msg
.mPromise
.set_value(hr
);
659 ComPtr
<IMMDeviceEnumerator
> enumerator
{static_cast<IMMDeviceEnumerator
*>(ptr
)};
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
);
668 if(--deviceCount
== 0 && SUCCEEDED(cohr
))
673 ERR("Unexpected message: %u\n", static_cast<unsigned int>(msg
.mType
));
674 msg
.mPromise
.set_value(E_FAIL
);
678 TRACE("Message loop finished\n");
684 struct WasapiPlayback final
: public BackendBase
, WasapiProxy
{
685 WasapiPlayback(ALCdevice
*device
) noexcept
: BackendBase
{device
} { }
686 ~WasapiPlayback() override
;
690 void open(const ALCchar
*name
) override
;
691 HRESULT
openProxy() override
;
692 void closeProxy() override
;
694 bool reset() override
;
695 HRESULT
resetProxy() override
;
696 void start() override
;
697 HRESULT
startProxy() override
;
698 void stop() override
;
699 void stopProxy() override
;
701 ClockLatency
getClockLatency() override
;
705 HRESULT mOpenStatus
{E_FAIL
};
706 ComPtr
<IMMDevice
> mMMDev
{nullptr};
707 ComPtr
<IAudioClient
> mClient
{nullptr};
708 ComPtr
<IAudioRenderClient
> mRender
{nullptr};
709 HANDLE mNotifyEvent
{nullptr};
711 UINT32 mFrameStep
{0u};
712 std::atomic
<UINT32
> mPadding
{0u};
716 std::atomic
<bool> mKillNow
{true};
719 DEF_NEWDEL(WasapiPlayback
)
722 WasapiPlayback::~WasapiPlayback()
724 if(SUCCEEDED(mOpenStatus
))
725 pushMessage(MsgType::CloseDevice
).wait();
726 mOpenStatus
= E_FAIL
;
728 if(mNotifyEvent
!= nullptr)
729 CloseHandle(mNotifyEvent
);
730 mNotifyEvent
= nullptr;
734 FORCE_ALIGN
int WasapiPlayback::mixerProc()
736 HRESULT hr
{CoInitializeEx(nullptr, COINIT_MULTITHREADED
)};
739 ERR("CoInitializeEx(nullptr, COINIT_MULTITHREADED) failed: 0x%08lx\n", hr
);
740 mDevice
->handleDisconnect("COM init failed: 0x%08lx", hr
);
745 althrd_setname(MIXER_THREAD_NAME
);
747 const ALuint update_size
{mDevice
->UpdateSize
};
748 const UINT32 buffer_len
{mDevice
->BufferSize
};
749 while(!mKillNow
.load(std::memory_order_relaxed
))
752 hr
= mClient
->GetCurrentPadding(&written
);
755 ERR("Failed to get padding: 0x%08lx\n", hr
);
756 mDevice
->handleDisconnect("Failed to retrieve buffer padding: 0x%08lx", hr
);
759 mPadding
.store(written
, std::memory_order_relaxed
);
761 ALuint len
{buffer_len
- written
};
762 if(len
< update_size
)
764 DWORD res
{WaitForSingleObjectEx(mNotifyEvent
, 2000, FALSE
)};
765 if(res
!= WAIT_OBJECT_0
)
766 ERR("WaitForSingleObjectEx error: 0x%lx\n", res
);
771 hr
= mRender
->GetBuffer(len
, &buffer
);
775 std::lock_guard
<std::mutex
> _
{mMutex
};
776 mDevice
->renderSamples(buffer
, len
, mFrameStep
);
777 mPadding
.store(written
+ len
, std::memory_order_relaxed
);
779 hr
= mRender
->ReleaseBuffer(len
, 0);
783 ERR("Failed to buffer data: 0x%08lx\n", hr
);
784 mDevice
->handleDisconnect("Failed to send playback samples: 0x%08lx", hr
);
788 mPadding
.store(0u, std::memory_order_release
);
795 void WasapiPlayback::open(const ALCchar
*name
)
799 mNotifyEvent
= CreateEventW(nullptr, FALSE
, FALSE
, nullptr);
800 if(mNotifyEvent
== nullptr)
802 ERR("Failed to create notify events: %lu\n", GetLastError());
810 if(PlaybackDevices
.empty())
811 pushMessage(MsgType::EnumeratePlayback
).wait();
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())
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
; });
824 if(iter
== PlaybackDevices
.cend())
825 WARN("Failed to find device name matching \"%s\"\n", name
);
828 mDevId
= iter
->devid
;
829 mDevice
->DeviceName
= iter
->name
;
836 hr
= pushMessage(MsgType::OpenDevice
).get();
841 if(mNotifyEvent
!= nullptr)
842 CloseHandle(mNotifyEvent
);
843 mNotifyEvent
= nullptr;
847 throw al::backend_exception
{ALC_INVALID_VALUE
, "Device init failed: 0x%08lx", hr
};
851 HRESULT
WasapiPlayback::openProxy()
854 HRESULT hr
{CoCreateInstance(CLSID_MMDeviceEnumerator
, nullptr, CLSCTX_INPROC_SERVER
,
855 IID_IMMDeviceEnumerator
, &ptr
)};
858 ComPtr
<IMMDeviceEnumerator
> enumerator
{static_cast<IMMDeviceEnumerator
*>(ptr
)};
860 hr
= enumerator
->GetDefaultAudioEndpoint(eRender
, eMultimedia
, mMMDev
.getPtr());
862 hr
= enumerator
->GetDevice(mDevId
.c_str(), mMMDev
.getPtr());
865 hr
= mMMDev
->Activate(IID_IAudioClient
, CLSCTX_INPROC_SERVER
, nullptr, &ptr
);
868 mClient
= ComPtr
<IAudioClient
>{static_cast<IAudioClient
*>(ptr
)};
869 if(mDevice
->DeviceName
.empty())
870 mDevice
->DeviceName
= get_device_name_and_guid(mMMDev
.get()).first
;
879 void WasapiPlayback::closeProxy()
886 bool WasapiPlayback::reset()
888 HRESULT hr
{pushMessage(MsgType::ResetDevice
).get()};
890 throw al::backend_exception
{ALC_INVALID_VALUE
, "0x%08lx", hr
};
894 HRESULT
WasapiPlayback::resetProxy()
899 HRESULT hr
{mMMDev
->Activate(IID_IAudioClient
, CLSCTX_INPROC_SERVER
, nullptr, &ptr
)};
902 ERR("Failed to reactivate audio client: 0x%08lx\n", hr
);
905 mClient
= ComPtr
<IAudioClient
>{static_cast<IAudioClient
*>(ptr
)};
908 hr
= mClient
->GetMixFormat(&wfx
);
911 ERR("Failed to get mix format: 0x%08lx\n", hr
);
915 WAVEFORMATEXTENSIBLE OutputType
;
916 if(!MakeExtensible(&OutputType
, wfx
))
924 const ReferenceTime per_time
{ReferenceTime
{seconds
{mDevice
->UpdateSize
}} / mDevice
->Frequency
};
925 const ReferenceTime buf_time
{ReferenceTime
{seconds
{mDevice
->BufferSize
}} / mDevice
->Frequency
};
927 if(!mDevice
->Flags
.get
<FrequencyRequest
>())
928 mDevice
->Frequency
= OutputType
.Format
.nSamplesPerSec
;
929 if(!mDevice
->Flags
.get
<ChannelsRequest
>())
931 const uint32_t chancount
{OutputType
.Format
.nChannels
};
932 const DWORD chanmask
{OutputType
.dwChannelMask
};
933 if(chancount
>= 8 && (chanmask
&X71Mask
) == X7DOT1
)
934 mDevice
->FmtChans
= DevFmtX71
;
935 else if(chancount
>= 7 && (chanmask
&X61Mask
) == X6DOT1
)
936 mDevice
->FmtChans
= DevFmtX61
;
937 else if(chancount
>= 6 && (chanmask
&X51Mask
) == X5DOT1
)
938 mDevice
->FmtChans
= DevFmtX51
;
939 else if(chancount
>= 6 && (chanmask
&X51RearMask
) == X5DOT1REAR
)
940 mDevice
->FmtChans
= DevFmtX51Rear
;
941 else if(chancount
>= 4 && (chanmask
&QuadMask
) == QUAD
)
942 mDevice
->FmtChans
= DevFmtQuad
;
943 else if(chancount
>= 2 && (chanmask
&StereoMask
) == STEREO
)
944 mDevice
->FmtChans
= DevFmtStereo
;
945 else if(chancount
>= 1 && (chanmask
&MonoMask
) == MONO
)
946 mDevice
->FmtChans
= DevFmtMono
;
948 ERR("Unhandled channel config: %d -- 0x%08lx\n", chancount
, chanmask
);
951 OutputType
.Format
.wFormatTag
= WAVE_FORMAT_EXTENSIBLE
;
952 switch(mDevice
->FmtChans
)
955 OutputType
.Format
.nChannels
= 1;
956 OutputType
.dwChannelMask
= MONO
;
959 mDevice
->FmtChans
= DevFmtStereo
;
962 OutputType
.Format
.nChannels
= 2;
963 OutputType
.dwChannelMask
= STEREO
;
966 OutputType
.Format
.nChannels
= 4;
967 OutputType
.dwChannelMask
= QUAD
;
970 OutputType
.Format
.nChannels
= 6;
971 OutputType
.dwChannelMask
= X5DOT1
;
974 OutputType
.Format
.nChannels
= 6;
975 OutputType
.dwChannelMask
= X5DOT1REAR
;
978 OutputType
.Format
.nChannels
= 7;
979 OutputType
.dwChannelMask
= X6DOT1
;
982 OutputType
.Format
.nChannels
= 8;
983 OutputType
.dwChannelMask
= X7DOT1
;
986 switch(mDevice
->FmtType
)
989 mDevice
->FmtType
= DevFmtUByte
;
992 OutputType
.Format
.wBitsPerSample
= 8;
993 OutputType
.Samples
.wValidBitsPerSample
= 8;
994 OutputType
.SubFormat
= KSDATAFORMAT_SUBTYPE_PCM
;
997 mDevice
->FmtType
= DevFmtShort
;
1000 OutputType
.Format
.wBitsPerSample
= 16;
1001 OutputType
.Samples
.wValidBitsPerSample
= 16;
1002 OutputType
.SubFormat
= KSDATAFORMAT_SUBTYPE_PCM
;
1005 mDevice
->FmtType
= DevFmtInt
;
1008 OutputType
.Format
.wBitsPerSample
= 32;
1009 OutputType
.Samples
.wValidBitsPerSample
= 32;
1010 OutputType
.SubFormat
= KSDATAFORMAT_SUBTYPE_PCM
;
1013 OutputType
.Format
.wBitsPerSample
= 32;
1014 OutputType
.Samples
.wValidBitsPerSample
= 32;
1015 OutputType
.SubFormat
= KSDATAFORMAT_SUBTYPE_IEEE_FLOAT
;
1018 OutputType
.Format
.nSamplesPerSec
= mDevice
->Frequency
;
1020 OutputType
.Format
.nBlockAlign
= static_cast<WORD
>(OutputType
.Format
.nChannels
*
1021 OutputType
.Format
.wBitsPerSample
/ 8);
1022 OutputType
.Format
.nAvgBytesPerSec
= OutputType
.Format
.nSamplesPerSec
*
1023 OutputType
.Format
.nBlockAlign
;
1025 TraceFormat("Requesting playback format", &OutputType
.Format
);
1026 hr
= mClient
->IsFormatSupported(AUDCLNT_SHAREMODE_SHARED
, &OutputType
.Format
, &wfx
);
1029 ERR("Failed to check format support: 0x%08lx\n", hr
);
1030 hr
= mClient
->GetMixFormat(&wfx
);
1034 ERR("Failed to find a supported format: 0x%08lx\n", hr
);
1040 TraceFormat("Got playback format", wfx
);
1041 if(!MakeExtensible(&OutputType
, wfx
))
1049 mDevice
->Frequency
= OutputType
.Format
.nSamplesPerSec
;
1050 const uint32_t chancount
{OutputType
.Format
.nChannels
};
1051 const DWORD chanmask
{OutputType
.dwChannelMask
};
1052 if(chancount
>= 8 && (chanmask
&X71Mask
) == X7DOT1
)
1053 mDevice
->FmtChans
= DevFmtX71
;
1054 else if(chancount
>= 7 && (chanmask
&X61Mask
) == X6DOT1
)
1055 mDevice
->FmtChans
= DevFmtX61
;
1056 else if(chancount
>= 6 && (chanmask
&X51Mask
) == X5DOT1
)
1057 mDevice
->FmtChans
= DevFmtX51
;
1058 else if(chancount
>= 6 && (chanmask
&X51RearMask
) == X5DOT1REAR
)
1059 mDevice
->FmtChans
= DevFmtX51Rear
;
1060 else if(chancount
>= 4 && (chanmask
&QuadMask
) == QUAD
)
1061 mDevice
->FmtChans
= DevFmtQuad
;
1062 else if(chancount
>= 2 && (chanmask
&StereoMask
) == STEREO
)
1063 mDevice
->FmtChans
= DevFmtStereo
;
1064 else if(chancount
>= 1 && (chanmask
&MonoMask
) == MONO
)
1065 mDevice
->FmtChans
= DevFmtMono
;
1068 ERR("Unhandled extensible channels: %d -- 0x%08lx\n", OutputType
.Format
.nChannels
,
1069 OutputType
.dwChannelMask
);
1070 mDevice
->FmtChans
= DevFmtStereo
;
1071 OutputType
.Format
.nChannels
= 2;
1072 OutputType
.dwChannelMask
= STEREO
;
1075 if(IsEqualGUID(OutputType
.SubFormat
, KSDATAFORMAT_SUBTYPE_PCM
))
1077 if(OutputType
.Format
.wBitsPerSample
== 8)
1078 mDevice
->FmtType
= DevFmtUByte
;
1079 else if(OutputType
.Format
.wBitsPerSample
== 16)
1080 mDevice
->FmtType
= DevFmtShort
;
1081 else if(OutputType
.Format
.wBitsPerSample
== 32)
1082 mDevice
->FmtType
= DevFmtInt
;
1085 mDevice
->FmtType
= DevFmtShort
;
1086 OutputType
.Format
.wBitsPerSample
= 16;
1089 else if(IsEqualGUID(OutputType
.SubFormat
, KSDATAFORMAT_SUBTYPE_IEEE_FLOAT
))
1091 mDevice
->FmtType
= DevFmtFloat
;
1092 OutputType
.Format
.wBitsPerSample
= 32;
1096 ERR("Unhandled format sub-type: %s\n", GuidPrinter
{OutputType
.SubFormat
}.c_str());
1097 mDevice
->FmtType
= DevFmtShort
;
1098 if(OutputType
.Format
.wFormatTag
!= WAVE_FORMAT_EXTENSIBLE
)
1099 OutputType
.Format
.wFormatTag
= WAVE_FORMAT_PCM
;
1100 OutputType
.Format
.wBitsPerSample
= 16;
1101 OutputType
.SubFormat
= KSDATAFORMAT_SUBTYPE_PCM
;
1103 OutputType
.Samples
.wValidBitsPerSample
= OutputType
.Format
.wBitsPerSample
;
1105 mFrameStep
= OutputType
.Format
.nChannels
;
1107 EndpointFormFactor formfactor
{UnknownFormFactor
};
1108 get_device_formfactor(mMMDev
.get(), &formfactor
);
1109 mDevice
->IsHeadphones
= (mDevice
->FmtChans
== DevFmtStereo
1110 && (formfactor
== Headphones
|| formfactor
== Headset
));
1112 setChannelOrderFromWFXMask(OutputType
.dwChannelMask
);
1114 hr
= mClient
->Initialize(AUDCLNT_SHAREMODE_SHARED
, AUDCLNT_STREAMFLAGS_EVENTCALLBACK
,
1115 buf_time
.count(), 0, &OutputType
.Format
, nullptr);
1118 ERR("Failed to initialize audio client: 0x%08lx\n", hr
);
1122 UINT32 buffer_len
{};
1123 ReferenceTime min_per
{};
1124 hr
= mClient
->GetDevicePeriod(&reinterpret_cast<REFERENCE_TIME
&>(min_per
), nullptr);
1126 hr
= mClient
->GetBufferSize(&buffer_len
);
1129 ERR("Failed to get audio buffer info: 0x%08lx\n", hr
);
1133 /* Find the nearest multiple of the period size to the update size */
1134 if(min_per
< per_time
)
1135 min_per
*= maxi64((per_time
+ min_per
/2) / min_per
, 1);
1136 mDevice
->UpdateSize
= minu(RefTime2Samples(min_per
, mDevice
->Frequency
), buffer_len
/2);
1137 mDevice
->BufferSize
= buffer_len
;
1139 hr
= mClient
->SetEventHandle(mNotifyEvent
);
1142 ERR("Failed to set event handle: 0x%08lx\n", hr
);
1150 void WasapiPlayback::start()
1152 const HRESULT hr
{pushMessage(MsgType::StartDevice
).get()};
1154 throw al::backend_exception
{ALC_INVALID_DEVICE
, "Failed to start playback: 0x%lx", hr
};
1157 HRESULT
WasapiPlayback::startProxy()
1159 ResetEvent(mNotifyEvent
);
1161 HRESULT hr
{mClient
->Start()};
1164 ERR("Failed to start audio client: 0x%08lx\n", hr
);
1169 hr
= mClient
->GetService(IID_IAudioRenderClient
, &ptr
);
1172 mRender
= ComPtr
<IAudioRenderClient
>{static_cast<IAudioRenderClient
*>(ptr
)};
1174 mKillNow
.store(false, std::memory_order_release
);
1175 mThread
= std::thread
{std::mem_fn(&WasapiPlayback::mixerProc
), this};
1179 ERR("Failed to start thread\n");
1191 void WasapiPlayback::stop()
1192 { pushMessage(MsgType::StopDevice
).wait(); }
1194 void WasapiPlayback::stopProxy()
1196 if(!mRender
|| !mThread
.joinable())
1199 mKillNow
.store(true, std::memory_order_release
);
1207 ClockLatency
WasapiPlayback::getClockLatency()
1211 std::lock_guard
<std::mutex
> _
{mMutex
};
1212 ret
.ClockTime
= GetDeviceClockTime(mDevice
);
1213 ret
.Latency
= std::chrono::seconds
{mPadding
.load(std::memory_order_relaxed
)};
1214 ret
.Latency
/= mDevice
->Frequency
;
1220 struct WasapiCapture final
: public BackendBase
, WasapiProxy
{
1221 WasapiCapture(ALCdevice
*device
) noexcept
: BackendBase
{device
} { }
1222 ~WasapiCapture() override
;
1226 void open(const ALCchar
*name
) override
;
1227 HRESULT
openProxy() override
;
1228 void closeProxy() override
;
1230 HRESULT
resetProxy() override
;
1231 void start() override
;
1232 HRESULT
startProxy() override
;
1233 void stop() override
;
1234 void stopProxy() override
;
1236 ALCenum
captureSamples(al::byte
*buffer
, ALCuint samples
) override
;
1237 ALCuint
availableSamples() override
;
1239 std::wstring mDevId
;
1241 HRESULT mOpenStatus
{E_FAIL
};
1242 ComPtr
<IMMDevice
> mMMDev
{nullptr};
1243 ComPtr
<IAudioClient
> mClient
{nullptr};
1244 ComPtr
<IAudioCaptureClient
> mCapture
{nullptr};
1245 HANDLE mNotifyEvent
{nullptr};
1247 ChannelConverter mChannelConv
{};
1248 SampleConverterPtr mSampleConv
;
1249 RingBufferPtr mRing
;
1251 std::atomic
<bool> mKillNow
{true};
1252 std::thread mThread
;
1254 DEF_NEWDEL(WasapiCapture
)
1257 WasapiCapture::~WasapiCapture()
1259 if(SUCCEEDED(mOpenStatus
))
1260 pushMessage(MsgType::CloseDevice
).wait();
1261 mOpenStatus
= E_FAIL
;
1263 if(mNotifyEvent
!= nullptr)
1264 CloseHandle(mNotifyEvent
);
1265 mNotifyEvent
= nullptr;
1269 FORCE_ALIGN
int WasapiCapture::recordProc()
1271 HRESULT hr
{CoInitializeEx(nullptr, COINIT_MULTITHREADED
)};
1274 ERR("CoInitializeEx(nullptr, COINIT_MULTITHREADED) failed: 0x%08lx\n", hr
);
1275 mDevice
->handleDisconnect("COM init failed: 0x%08lx", hr
);
1279 althrd_setname(RECORD_THREAD_NAME
);
1281 al::vector
<float> samples
;
1282 while(!mKillNow
.load(std::memory_order_relaxed
))
1285 hr
= mCapture
->GetNextPacketSize(&avail
);
1287 ERR("Failed to get next packet size: 0x%08lx\n", hr
);
1294 hr
= mCapture
->GetBuffer(&rdata
, &numsamples
, &flags
, nullptr, nullptr);
1296 ERR("Failed to get capture buffer: 0x%08lx\n", hr
);
1299 if(mChannelConv
.is_active())
1301 samples
.resize(numsamples
*2);
1302 mChannelConv
.convert(rdata
, samples
.data(), numsamples
);
1303 rdata
= reinterpret_cast<BYTE
*>(samples
.data());
1306 auto data
= mRing
->getWriteVector();
1311 const void *srcdata
{rdata
};
1312 ALuint srcframes
{numsamples
};
1314 dstframes
= mSampleConv
->convert(&srcdata
, &srcframes
, data
.first
.buf
,
1315 static_cast<ALuint
>(minz(data
.first
.len
, INT_MAX
)));
1316 if(srcframes
> 0 && dstframes
== data
.first
.len
&& data
.second
.len
> 0)
1318 /* If some source samples remain, all of the first dest
1319 * block was filled, and there's space in the second
1320 * dest block, do another run for the second block.
1322 dstframes
+= mSampleConv
->convert(&srcdata
, &srcframes
, data
.second
.buf
,
1323 static_cast<ALuint
>(minz(data
.second
.len
, INT_MAX
)));
1328 const auto framesize
= static_cast<ALuint
>(mDevice
->frameSizeFromFmt());
1329 size_t len1
{minz(data
.first
.len
, numsamples
)};
1330 size_t len2
{minz(data
.second
.len
, numsamples
-len1
)};
1332 memcpy(data
.first
.buf
, rdata
, len1
*framesize
);
1334 memcpy(data
.second
.buf
, rdata
+len1
*framesize
, len2
*framesize
);
1335 dstframes
= len1
+ len2
;
1338 mRing
->writeAdvance(dstframes
);
1340 hr
= mCapture
->ReleaseBuffer(numsamples
);
1341 if(FAILED(hr
)) ERR("Failed to release capture buffer: 0x%08lx\n", hr
);
1347 mDevice
->handleDisconnect("Failed to capture samples: 0x%08lx", hr
);
1351 DWORD res
{WaitForSingleObjectEx(mNotifyEvent
, 2000, FALSE
)};
1352 if(res
!= WAIT_OBJECT_0
)
1353 ERR("WaitForSingleObjectEx error: 0x%lx\n", res
);
1361 void WasapiCapture::open(const ALCchar
*name
)
1365 mNotifyEvent
= CreateEventW(nullptr, FALSE
, FALSE
, nullptr);
1366 if(mNotifyEvent
== nullptr)
1368 ERR("Failed to create notify event: %lu\n", GetLastError());
1376 if(CaptureDevices
.empty())
1377 pushMessage(MsgType::EnumerateCapture
).wait();
1380 auto iter
= std::find_if(CaptureDevices
.cbegin(), CaptureDevices
.cend(),
1381 [name
](const DevMap
&entry
) -> bool
1382 { return entry
.name
== name
|| entry
.endpoint_guid
== name
; });
1383 if(iter
== CaptureDevices
.cend())
1385 const std::wstring wname
{utf8_to_wstr(name
)};
1386 iter
= std::find_if(CaptureDevices
.cbegin(), CaptureDevices
.cend(),
1387 [&wname
](const DevMap
&entry
) -> bool
1388 { return entry
.devid
== wname
; });
1390 if(iter
== CaptureDevices
.cend())
1391 WARN("Failed to find device name matching \"%s\"\n", name
);
1394 mDevId
= iter
->devid
;
1395 mDevice
->DeviceName
= iter
->name
;
1402 hr
= pushMessage(MsgType::OpenDevice
).get();
1407 if(mNotifyEvent
!= nullptr)
1408 CloseHandle(mNotifyEvent
);
1409 mNotifyEvent
= nullptr;
1413 throw al::backend_exception
{ALC_INVALID_VALUE
, "Device init failed: 0x%08lx", hr
};
1416 hr
= pushMessage(MsgType::ResetDevice
).get();
1419 if(hr
== E_OUTOFMEMORY
)
1420 throw al::backend_exception
{ALC_OUT_OF_MEMORY
, "Out of memory"};
1421 throw al::backend_exception
{ALC_INVALID_VALUE
, "Device reset failed"};
1425 HRESULT
WasapiCapture::openProxy()
1428 HRESULT hr
{CoCreateInstance(CLSID_MMDeviceEnumerator
, nullptr, CLSCTX_INPROC_SERVER
,
1429 IID_IMMDeviceEnumerator
, &ptr
)};
1432 ComPtr
<IMMDeviceEnumerator
> enumerator
{static_cast<IMMDeviceEnumerator
*>(ptr
)};
1434 hr
= enumerator
->GetDefaultAudioEndpoint(eCapture
, eMultimedia
, mMMDev
.getPtr());
1436 hr
= enumerator
->GetDevice(mDevId
.c_str(), mMMDev
.getPtr());
1439 hr
= mMMDev
->Activate(IID_IAudioClient
, CLSCTX_INPROC_SERVER
, nullptr, &ptr
);
1442 mClient
= ComPtr
<IAudioClient
>{static_cast<IAudioClient
*>(ptr
)};
1443 if(mDevice
->DeviceName
.empty())
1444 mDevice
->DeviceName
= get_device_name_and_guid(mMMDev
.get()).first
;
1453 void WasapiCapture::closeProxy()
1459 HRESULT
WasapiCapture::resetProxy()
1464 HRESULT hr
{mMMDev
->Activate(IID_IAudioClient
, CLSCTX_INPROC_SERVER
, nullptr, &ptr
)};
1467 ERR("Failed to reactivate audio client: 0x%08lx\n", hr
);
1470 mClient
= ComPtr
<IAudioClient
>{static_cast<IAudioClient
*>(ptr
)};
1472 // Make sure buffer is at least 100ms in size
1473 ReferenceTime buf_time
{ReferenceTime
{seconds
{mDevice
->BufferSize
}} / mDevice
->Frequency
};
1474 buf_time
= std::max(buf_time
, ReferenceTime
{milliseconds
{100}});
1476 WAVEFORMATEXTENSIBLE OutputType
{};
1477 OutputType
.Format
.wFormatTag
= WAVE_FORMAT_EXTENSIBLE
;
1478 switch(mDevice
->FmtChans
)
1481 OutputType
.Format
.nChannels
= 1;
1482 OutputType
.dwChannelMask
= MONO
;
1485 OutputType
.Format
.nChannels
= 2;
1486 OutputType
.dwChannelMask
= STEREO
;
1489 OutputType
.Format
.nChannels
= 4;
1490 OutputType
.dwChannelMask
= QUAD
;
1493 OutputType
.Format
.nChannels
= 6;
1494 OutputType
.dwChannelMask
= X5DOT1
;
1497 OutputType
.Format
.nChannels
= 6;
1498 OutputType
.dwChannelMask
= X5DOT1REAR
;
1501 OutputType
.Format
.nChannels
= 7;
1502 OutputType
.dwChannelMask
= X6DOT1
;
1505 OutputType
.Format
.nChannels
= 8;
1506 OutputType
.dwChannelMask
= X7DOT1
;
1512 switch(mDevice
->FmtType
)
1514 /* NOTE: Signedness doesn't matter, the converter will handle it. */
1517 OutputType
.Format
.wBitsPerSample
= 8;
1518 OutputType
.SubFormat
= KSDATAFORMAT_SUBTYPE_PCM
;
1522 OutputType
.Format
.wBitsPerSample
= 16;
1523 OutputType
.SubFormat
= KSDATAFORMAT_SUBTYPE_PCM
;
1527 OutputType
.Format
.wBitsPerSample
= 32;
1528 OutputType
.SubFormat
= KSDATAFORMAT_SUBTYPE_PCM
;
1531 OutputType
.Format
.wBitsPerSample
= 32;
1532 OutputType
.SubFormat
= KSDATAFORMAT_SUBTYPE_IEEE_FLOAT
;
1535 OutputType
.Samples
.wValidBitsPerSample
= OutputType
.Format
.wBitsPerSample
;
1536 OutputType
.Format
.nSamplesPerSec
= mDevice
->Frequency
;
1538 OutputType
.Format
.nBlockAlign
= static_cast<WORD
>(OutputType
.Format
.nChannels
*
1539 OutputType
.Format
.wBitsPerSample
/ 8);
1540 OutputType
.Format
.nAvgBytesPerSec
= OutputType
.Format
.nSamplesPerSec
*
1541 OutputType
.Format
.nBlockAlign
;
1542 OutputType
.Format
.cbSize
= sizeof(OutputType
) - sizeof(OutputType
.Format
);
1544 TraceFormat("Requesting capture format", &OutputType
.Format
);
1546 hr
= mClient
->IsFormatSupported(AUDCLNT_SHAREMODE_SHARED
, &OutputType
.Format
, &wfx
);
1549 ERR("Failed to check format support: 0x%08lx\n", hr
);
1553 mSampleConv
= nullptr;
1558 TraceFormat("Got capture format", wfx
);
1559 if(!(wfx
->nChannels
== OutputType
.Format
.nChannels
||
1560 (wfx
->nChannels
== 1 && OutputType
.Format
.nChannels
== 2) ||
1561 (wfx
->nChannels
== 2 && OutputType
.Format
.nChannels
== 1)))
1563 ERR("Failed to get matching format, wanted: %s %s %uhz, got: %d channel%s %d-bit %luhz\n",
1564 DevFmtChannelsString(mDevice
->FmtChans
), DevFmtTypeString(mDevice
->FmtType
),
1565 mDevice
->Frequency
, wfx
->nChannels
, (wfx
->nChannels
==1)?"":"s", wfx
->wBitsPerSample
,
1566 wfx
->nSamplesPerSec
);
1571 if(!MakeExtensible(&OutputType
, wfx
))
1581 if(IsEqualGUID(OutputType
.SubFormat
, KSDATAFORMAT_SUBTYPE_PCM
))
1583 if(OutputType
.Format
.wBitsPerSample
== 8)
1584 srcType
= DevFmtUByte
;
1585 else if(OutputType
.Format
.wBitsPerSample
== 16)
1586 srcType
= DevFmtShort
;
1587 else if(OutputType
.Format
.wBitsPerSample
== 32)
1588 srcType
= DevFmtInt
;
1591 ERR("Unhandled integer bit depth: %d\n", OutputType
.Format
.wBitsPerSample
);
1595 else if(IsEqualGUID(OutputType
.SubFormat
, KSDATAFORMAT_SUBTYPE_IEEE_FLOAT
))
1597 if(OutputType
.Format
.wBitsPerSample
== 32)
1598 srcType
= DevFmtFloat
;
1601 ERR("Unhandled float bit depth: %d\n", OutputType
.Format
.wBitsPerSample
);
1607 ERR("Unhandled format sub-type: %s\n", GuidPrinter
{OutputType
.SubFormat
}.c_str());
1611 if(mDevice
->FmtChans
== DevFmtMono
&& OutputType
.Format
.nChannels
== 2)
1613 mChannelConv
= ChannelConverter
{srcType
, DevFmtStereo
, mDevice
->FmtChans
};
1614 TRACE("Created %s stereo-to-mono converter\n", DevFmtTypeString(srcType
));
1615 /* The channel converter always outputs float, so change the input type
1616 * for the resampler/type-converter.
1618 srcType
= DevFmtFloat
;
1620 else if(mDevice
->FmtChans
== DevFmtStereo
&& OutputType
.Format
.nChannels
== 1)
1622 mChannelConv
= ChannelConverter
{srcType
, DevFmtMono
, mDevice
->FmtChans
};
1623 TRACE("Created %s mono-to-stereo converter\n", DevFmtTypeString(srcType
));
1624 srcType
= DevFmtFloat
;
1627 if(mDevice
->Frequency
!= OutputType
.Format
.nSamplesPerSec
|| mDevice
->FmtType
!= srcType
)
1629 mSampleConv
= CreateSampleConverter(srcType
, mDevice
->FmtType
, mDevice
->channelsFromFmt(),
1630 OutputType
.Format
.nSamplesPerSec
, mDevice
->Frequency
, Resampler::FastBSinc24
);
1633 ERR("Failed to create converter for %s format, dst: %s %uhz, src: %s %luhz\n",
1634 DevFmtChannelsString(mDevice
->FmtChans
), DevFmtTypeString(mDevice
->FmtType
),
1635 mDevice
->Frequency
, DevFmtTypeString(srcType
), OutputType
.Format
.nSamplesPerSec
);
1638 TRACE("Created converter for %s format, dst: %s %uhz, src: %s %luhz\n",
1639 DevFmtChannelsString(mDevice
->FmtChans
), DevFmtTypeString(mDevice
->FmtType
),
1640 mDevice
->Frequency
, DevFmtTypeString(srcType
), OutputType
.Format
.nSamplesPerSec
);
1643 hr
= mClient
->Initialize(AUDCLNT_SHAREMODE_SHARED
, AUDCLNT_STREAMFLAGS_EVENTCALLBACK
,
1644 buf_time
.count(), 0, &OutputType
.Format
, nullptr);
1647 ERR("Failed to initialize audio client: 0x%08lx\n", hr
);
1651 UINT32 buffer_len
{};
1652 ReferenceTime min_per
{};
1653 hr
= mClient
->GetDevicePeriod(&reinterpret_cast<REFERENCE_TIME
&>(min_per
), nullptr);
1655 hr
= mClient
->GetBufferSize(&buffer_len
);
1658 ERR("Failed to get buffer size: 0x%08lx\n", hr
);
1661 mDevice
->UpdateSize
= RefTime2Samples(min_per
, mDevice
->Frequency
);
1662 mDevice
->BufferSize
= buffer_len
;
1664 mRing
= RingBuffer::Create(buffer_len
, mDevice
->frameSizeFromFmt(), false);
1666 hr
= mClient
->SetEventHandle(mNotifyEvent
);
1669 ERR("Failed to set event handle: 0x%08lx\n", hr
);
1677 void WasapiCapture::start()
1679 const HRESULT hr
{pushMessage(MsgType::StartDevice
).get()};
1681 throw al::backend_exception
{ALC_INVALID_DEVICE
, "Failed to start recording: 0x%lx", hr
};
1684 HRESULT
WasapiCapture::startProxy()
1686 ResetEvent(mNotifyEvent
);
1688 HRESULT hr
{mClient
->Start()};
1691 ERR("Failed to start audio client: 0x%08lx\n", hr
);
1696 hr
= mClient
->GetService(IID_IAudioCaptureClient
, &ptr
);
1699 mCapture
= ComPtr
<IAudioCaptureClient
>{static_cast<IAudioCaptureClient
*>(ptr
)};
1701 mKillNow
.store(false, std::memory_order_release
);
1702 mThread
= std::thread
{std::mem_fn(&WasapiCapture::recordProc
), this};
1706 ERR("Failed to start thread\n");
1721 void WasapiCapture::stop()
1722 { pushMessage(MsgType::StopDevice
).wait(); }
1724 void WasapiCapture::stopProxy()
1726 if(!mCapture
|| !mThread
.joinable())
1729 mKillNow
.store(true, std::memory_order_release
);
1738 ALCuint
WasapiCapture::availableSamples()
1739 { return static_cast<ALCuint
>(mRing
->readSpace()); }
1741 ALCenum
WasapiCapture::captureSamples(al::byte
*buffer
, ALCuint samples
)
1743 mRing
->read(buffer
, samples
);
1744 return ALC_NO_ERROR
;
1750 bool WasapiBackendFactory::init()
1752 static HRESULT InitResult
{E_FAIL
};
1754 if(FAILED(InitResult
)) try
1756 std::promise
<HRESULT
> promise
;
1757 auto future
= promise
.get_future();
1759 std::thread
{&WasapiProxy::messageHandler
, &promise
}.detach();
1760 InitResult
= future
.get();
1765 return SUCCEEDED(InitResult
) ? ALC_TRUE
: ALC_FALSE
;
1768 bool WasapiBackendFactory::querySupport(BackendType type
)
1769 { return type
== BackendType::Playback
|| type
== BackendType::Capture
; }
1771 std::string
WasapiBackendFactory::probe(BackendType type
)
1773 std::string outnames
;
1774 auto add_device
= [&outnames
](const DevMap
&entry
) -> void
1776 /* +1 to also append the null char (to ensure a null-separated list and
1777 * double-null terminated list).
1779 outnames
.append(entry
.name
.c_str(), entry
.name
.length()+1);
1784 case BackendType::Playback
:
1785 WasapiProxy::pushMessageStatic(MsgType::EnumeratePlayback
).wait();
1786 std::for_each(PlaybackDevices
.cbegin(), PlaybackDevices
.cend(), add_device
);
1789 case BackendType::Capture
:
1790 WasapiProxy::pushMessageStatic(MsgType::EnumerateCapture
).wait();
1791 std::for_each(CaptureDevices
.cbegin(), CaptureDevices
.cend(), add_device
);
1798 BackendPtr
WasapiBackendFactory::createBackend(ALCdevice
*device
, BackendType type
)
1800 if(type
== BackendType::Playback
)
1801 return BackendPtr
{new WasapiPlayback
{device
}};
1802 if(type
== BackendType::Capture
)
1803 return BackendPtr
{new WasapiCapture
{device
}};
1807 BackendFactory
&WasapiBackendFactory::getFactory()
1809 static WasapiBackendFactory factory
{};