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, ceiling the result. */
125 inline ALuint
RefTime2Samples(const ReferenceTime
&val
, ALuint srate
)
127 const auto retval
= (val
*srate
+ (seconds
{1}-1_reftime
)) / seconds
{1};
128 return static_cast<ALuint
>(retval
);
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 std::string name
{DEVNAME_HEAD
};
257 ComPtr
<IPropertyStore
> ps
;
258 HRESULT hr
= device
->OpenPropertyStore(STGM_READ
, ps
.getPtr());
261 WARN("OpenPropertyStore failed: 0x%08lx\n", hr
);
262 return std::make_pair("Unknown Device Name", "Unknown Device GUID");
266 hr
= ps
->GetValue(reinterpret_cast<const PROPERTYKEY
&>(DEVPKEY_Device_FriendlyName
), pvprop
.get());
269 WARN("GetValue Device_FriendlyName failed: 0x%08lx\n", hr
);
270 name
+= "Unknown Device Name";
272 else if(pvprop
->vt
== VT_LPWSTR
)
273 name
+= wstr_to_utf8(pvprop
->pwszVal
);
276 WARN("Unexpected PROPVARIANT type: 0x%04x\n", pvprop
->vt
);
277 name
+= "Unknown Device Name";
281 hr
= ps
->GetValue(reinterpret_cast<const PROPERTYKEY
&>(PKEY_AudioEndpoint_GUID
), pvprop
.get());
284 WARN("GetValue AudioEndpoint_GUID failed: 0x%08lx\n", hr
);
285 guid
= "Unknown Device GUID";
287 else if(pvprop
->vt
== VT_LPWSTR
)
288 guid
= wstr_to_utf8(pvprop
->pwszVal
);
291 WARN("Unexpected PROPVARIANT type: 0x%04x\n", pvprop
->vt
);
292 guid
= "Unknown Device GUID";
295 return std::make_pair(std::move(name
), std::move(guid
));
298 void get_device_formfactor(IMMDevice
*device
, EndpointFormFactor
*formfactor
)
300 ComPtr
<IPropertyStore
> ps
;
301 HRESULT hr
= device
->OpenPropertyStore(STGM_READ
, ps
.getPtr());
304 WARN("OpenPropertyStore failed: 0x%08lx\n", hr
);
309 hr
= ps
->GetValue(reinterpret_cast<const PROPERTYKEY
&>(PKEY_AudioEndpoint_FormFactor
), pvform
.get());
311 WARN("GetValue AudioEndpoint_FormFactor failed: 0x%08lx\n", hr
);
312 else if(pvform
->vt
== VT_UI4
)
313 *formfactor
= static_cast<EndpointFormFactor
>(pvform
->ulVal
);
314 else if(pvform
->vt
== VT_EMPTY
)
315 *formfactor
= UnknownFormFactor
;
317 WARN("Unexpected PROPVARIANT type: 0x%04x\n", pvform
->vt
);
321 void add_device(IMMDevice
*device
, const WCHAR
*devid
, al::vector
<DevMap
> &list
)
323 for(auto &entry
: list
)
325 if(entry
.devid
== devid
)
329 auto name_guid
= get_device_name_and_guid(device
);
332 std::string newname
{name_guid
.first
};
333 while(checkName(list
, newname
))
335 newname
= name_guid
.first
;
337 newname
+= std::to_string(++count
);
339 list
.emplace_back(std::move(newname
), std::move(name_guid
.second
), devid
);
340 const DevMap
&newentry
= list
.back();
342 TRACE("Got device \"%s\", \"%s\", \"%ls\"\n", newentry
.name
.c_str(),
343 newentry
.endpoint_guid
.c_str(), newentry
.devid
.c_str());
346 WCHAR
*get_device_id(IMMDevice
*device
)
350 const HRESULT hr
{device
->GetId(&devid
)};
353 ERR("Failed to get device id: %lx\n", hr
);
360 void probe_devices(IMMDeviceEnumerator
*devenum
, EDataFlow flowdir
, al::vector
<DevMap
> &list
)
362 al::vector
<DevMap
>{}.swap(list
);
364 ComPtr
<IMMDeviceCollection
> coll
;
365 HRESULT hr
{devenum
->EnumAudioEndpoints(flowdir
, DEVICE_STATE_ACTIVE
, coll
.getPtr())};
368 ERR("Failed to enumerate audio endpoints: 0x%08lx\n", hr
);
373 hr
= coll
->GetCount(&count
);
374 if(SUCCEEDED(hr
) && count
> 0)
377 ComPtr
<IMMDevice
> device
;
378 hr
= devenum
->GetDefaultAudioEndpoint(flowdir
, eMultimedia
, device
.getPtr());
381 if(WCHAR
*devid
{get_device_id(device
.get())})
383 add_device(device
.get(), devid
, list
);
384 CoTaskMemFree(devid
);
389 for(UINT i
{0};i
< count
;++i
)
391 hr
= coll
->Item(i
, device
.getPtr());
392 if(FAILED(hr
)) continue;
394 if(WCHAR
*devid
{get_device_id(device
.get())})
396 add_device(device
.get(), devid
, list
);
397 CoTaskMemFree(devid
);
404 bool MakeExtensible(WAVEFORMATEXTENSIBLE
*out
, const WAVEFORMATEX
*in
)
406 *out
= WAVEFORMATEXTENSIBLE
{};
407 if(in
->wFormatTag
== WAVE_FORMAT_EXTENSIBLE
)
409 *out
= *CONTAINING_RECORD(in
, const WAVEFORMATEXTENSIBLE
, Format
);
410 out
->Format
.cbSize
= sizeof(*out
) - sizeof(out
->Format
);
412 else if(in
->wFormatTag
== WAVE_FORMAT_PCM
)
415 out
->Format
.cbSize
= 0;
416 out
->Samples
.wValidBitsPerSample
= out
->Format
.wBitsPerSample
;
417 if(out
->Format
.nChannels
== 1)
418 out
->dwChannelMask
= MONO
;
419 else if(out
->Format
.nChannels
== 2)
420 out
->dwChannelMask
= STEREO
;
422 ERR("Unhandled PCM channel count: %d\n", out
->Format
.nChannels
);
423 out
->SubFormat
= KSDATAFORMAT_SUBTYPE_PCM
;
425 else if(in
->wFormatTag
== WAVE_FORMAT_IEEE_FLOAT
)
428 out
->Format
.cbSize
= 0;
429 out
->Samples
.wValidBitsPerSample
= out
->Format
.wBitsPerSample
;
430 if(out
->Format
.nChannels
== 1)
431 out
->dwChannelMask
= MONO
;
432 else if(out
->Format
.nChannels
== 2)
433 out
->dwChannelMask
= STEREO
;
435 ERR("Unhandled IEEE float channel count: %d\n", out
->Format
.nChannels
);
436 out
->SubFormat
= KSDATAFORMAT_SUBTYPE_IEEE_FLOAT
;
440 ERR("Unhandled format tag: 0x%04x\n", in
->wFormatTag
);
446 void TraceFormat(const char *msg
, const WAVEFORMATEX
*format
)
448 constexpr size_t fmtex_extra_size
{sizeof(WAVEFORMATEXTENSIBLE
)-sizeof(WAVEFORMATEX
)};
449 if(format
->wFormatTag
== WAVE_FORMAT_EXTENSIBLE
&& format
->cbSize
>= fmtex_extra_size
)
451 const WAVEFORMATEXTENSIBLE
*fmtex
{
452 CONTAINING_RECORD(format
, const WAVEFORMATEXTENSIBLE
, Format
)};
454 " FormatTag = 0x%04x\n"
456 " SamplesPerSec = %lu\n"
457 " AvgBytesPerSec = %lu\n"
459 " BitsPerSample = %d\n"
462 " ChannelMask = 0x%lx\n"
464 msg
, fmtex
->Format
.wFormatTag
, fmtex
->Format
.nChannels
, fmtex
->Format
.nSamplesPerSec
,
465 fmtex
->Format
.nAvgBytesPerSec
, fmtex
->Format
.nBlockAlign
, fmtex
->Format
.wBitsPerSample
,
466 fmtex
->Format
.cbSize
, fmtex
->Samples
.wReserved
, fmtex
->dwChannelMask
,
467 GuidPrinter
{fmtex
->SubFormat
}.c_str());
471 " FormatTag = 0x%04x\n"
473 " SamplesPerSec = %lu\n"
474 " AvgBytesPerSec = %lu\n"
476 " BitsPerSample = %d\n"
478 msg
, format
->wFormatTag
, format
->nChannels
, format
->nSamplesPerSec
,
479 format
->nAvgBytesPerSec
, format
->nBlockAlign
, format
->wBitsPerSample
, format
->cbSize
);
496 constexpr char MessageStr
[static_cast<size_t>(MsgType::Count
)][20]{
502 "Enumerate Playback",
508 /* Proxy interface used by the message handler. */
510 virtual ~WasapiProxy() = default;
512 virtual HRESULT
openProxy() = 0;
513 virtual void closeProxy() = 0;
515 virtual HRESULT
resetProxy() = 0;
516 virtual HRESULT
startProxy() = 0;
517 virtual void stopProxy() = 0;
522 std::promise
<HRESULT
> mPromise
;
524 static std::deque
<Msg
> mMsgQueue
;
525 static std::mutex mMsgQueueLock
;
526 static std::condition_variable mMsgQueueCond
;
528 std::future
<HRESULT
> pushMessage(MsgType type
)
530 std::promise
<HRESULT
> promise
;
531 std::future
<HRESULT
> future
{promise
.get_future()};
533 std::lock_guard
<std::mutex
> _
{mMsgQueueLock
};
534 mMsgQueue
.emplace_back(Msg
{type
, this, std::move(promise
)});
536 mMsgQueueCond
.notify_one();
540 static std::future
<HRESULT
> pushMessageStatic(MsgType type
)
542 std::promise
<HRESULT
> promise
;
543 std::future
<HRESULT
> future
{promise
.get_future()};
545 std::lock_guard
<std::mutex
> _
{mMsgQueueLock
};
546 mMsgQueue
.emplace_back(Msg
{type
, nullptr, std::move(promise
)});
548 mMsgQueueCond
.notify_one();
552 static bool popMessage(Msg
&msg
)
554 std::unique_lock
<std::mutex
> lock
{mMsgQueueLock
};
555 mMsgQueueCond
.wait(lock
, []{return !mMsgQueue
.empty();});
556 msg
= std::move(mMsgQueue
.front());
557 mMsgQueue
.pop_front();
558 return msg
.mType
!= MsgType::QuitThread
;
561 static int messageHandler(std::promise
<HRESULT
> *promise
);
563 std::deque
<WasapiProxy::Msg
> WasapiProxy::mMsgQueue
;
564 std::mutex
WasapiProxy::mMsgQueueLock
;
565 std::condition_variable
WasapiProxy::mMsgQueueCond
;
567 int WasapiProxy::messageHandler(std::promise
<HRESULT
> *promise
)
569 TRACE("Starting message thread\n");
571 HRESULT cohr
{CoInitializeEx(nullptr, COINIT_MULTITHREADED
)};
574 WARN("Failed to initialize COM: 0x%08lx\n", cohr
);
575 promise
->set_value(cohr
);
580 HRESULT hr
{CoCreateInstance(CLSID_MMDeviceEnumerator
, nullptr, CLSCTX_INPROC_SERVER
,
581 IID_IMMDeviceEnumerator
, &ptr
)};
584 WARN("Failed to create IMMDeviceEnumerator instance: 0x%08lx\n", hr
);
585 promise
->set_value(hr
);
589 static_cast<IMMDeviceEnumerator
*>(ptr
)->Release();
592 TRACE("Message thread initialization complete\n");
593 promise
->set_value(S_OK
);
596 TRACE("Starting message loop\n");
597 ALuint deviceCount
{0};
599 while(popMessage(msg
))
601 TRACE("Got message \"%s\" (0x%04x, this=%p)\n",
602 MessageStr
[static_cast<size_t>(msg
.mType
)], static_cast<int>(msg
.mType
),
603 decltype(std::declval
<void*>()){msg
.mProxy
});
607 case MsgType::OpenDevice
:
609 if(++deviceCount
== 1)
610 hr
= cohr
= CoInitializeEx(nullptr, COINIT_MULTITHREADED
);
612 hr
= msg
.mProxy
->openProxy();
613 msg
.mPromise
.set_value(hr
);
617 if(--deviceCount
== 0 && SUCCEEDED(cohr
))
622 case MsgType::ResetDevice
:
623 hr
= msg
.mProxy
->resetProxy();
624 msg
.mPromise
.set_value(hr
);
627 case MsgType::StartDevice
:
628 hr
= msg
.mProxy
->startProxy();
629 msg
.mPromise
.set_value(hr
);
632 case MsgType::StopDevice
:
633 msg
.mProxy
->stopProxy();
634 msg
.mPromise
.set_value(S_OK
);
637 case MsgType::CloseDevice
:
638 msg
.mProxy
->closeProxy();
639 msg
.mPromise
.set_value(S_OK
);
641 if(--deviceCount
== 0)
645 case MsgType::EnumeratePlayback
:
646 case MsgType::EnumerateCapture
:
648 if(++deviceCount
== 1)
649 hr
= cohr
= CoInitializeEx(nullptr, COINIT_MULTITHREADED
);
651 hr
= CoCreateInstance(CLSID_MMDeviceEnumerator
, nullptr, CLSCTX_INPROC_SERVER
,
652 IID_IMMDeviceEnumerator
, &ptr
);
654 msg
.mPromise
.set_value(hr
);
657 ComPtr
<IMMDeviceEnumerator
> enumerator
{static_cast<IMMDeviceEnumerator
*>(ptr
)};
659 if(msg
.mType
== MsgType::EnumeratePlayback
)
660 probe_devices(enumerator
.get(), eRender
, PlaybackDevices
);
661 else if(msg
.mType
== MsgType::EnumerateCapture
)
662 probe_devices(enumerator
.get(), eCapture
, CaptureDevices
);
663 msg
.mPromise
.set_value(S_OK
);
666 if(--deviceCount
== 0 && SUCCEEDED(cohr
))
671 ERR("Unexpected message: %u\n", static_cast<unsigned int>(msg
.mType
));
672 msg
.mPromise
.set_value(E_FAIL
);
676 TRACE("Message loop finished\n");
682 struct WasapiPlayback final
: public BackendBase
, WasapiProxy
{
683 WasapiPlayback(ALCdevice
*device
) noexcept
: BackendBase
{device
} { }
684 ~WasapiPlayback() override
;
688 void open(const ALCchar
*name
) override
;
689 HRESULT
openProxy() override
;
690 void closeProxy() override
;
692 bool reset() override
;
693 HRESULT
resetProxy() override
;
694 void start() override
;
695 HRESULT
startProxy() override
;
696 void stop() override
;
697 void stopProxy() override
;
699 ClockLatency
getClockLatency() override
;
703 HRESULT mOpenStatus
{E_FAIL
};
704 ComPtr
<IMMDevice
> mMMDev
{nullptr};
705 ComPtr
<IAudioClient
> mClient
{nullptr};
706 ComPtr
<IAudioRenderClient
> mRender
{nullptr};
707 HANDLE mNotifyEvent
{nullptr};
709 UINT32 mFrameStep
{0u};
710 std::atomic
<UINT32
> mPadding
{0u};
714 std::atomic
<bool> mKillNow
{true};
717 DEF_NEWDEL(WasapiPlayback
)
720 WasapiPlayback::~WasapiPlayback()
722 if(SUCCEEDED(mOpenStatus
))
723 pushMessage(MsgType::CloseDevice
).wait();
724 mOpenStatus
= E_FAIL
;
726 if(mNotifyEvent
!= nullptr)
727 CloseHandle(mNotifyEvent
);
728 mNotifyEvent
= nullptr;
732 FORCE_ALIGN
int WasapiPlayback::mixerProc()
734 HRESULT hr
{CoInitializeEx(nullptr, COINIT_MULTITHREADED
)};
737 ERR("CoInitializeEx(nullptr, COINIT_MULTITHREADED) failed: 0x%08lx\n", hr
);
738 mDevice
->handleDisconnect("COM init failed: 0x%08lx", hr
);
743 althrd_setname(MIXER_THREAD_NAME
);
745 const ALuint update_size
{mDevice
->UpdateSize
};
746 const UINT32 buffer_len
{mDevice
->BufferSize
};
747 while(!mKillNow
.load(std::memory_order_relaxed
))
750 hr
= mClient
->GetCurrentPadding(&written
);
753 ERR("Failed to get padding: 0x%08lx\n", hr
);
754 mDevice
->handleDisconnect("Failed to retrieve buffer padding: 0x%08lx", hr
);
757 mPadding
.store(written
, std::memory_order_relaxed
);
759 ALuint len
{buffer_len
- written
};
760 if(len
< update_size
)
762 DWORD res
{WaitForSingleObjectEx(mNotifyEvent
, 2000, FALSE
)};
763 if(res
!= WAIT_OBJECT_0
)
764 ERR("WaitForSingleObjectEx error: 0x%lx\n", res
);
769 hr
= mRender
->GetBuffer(len
, &buffer
);
773 std::lock_guard
<std::mutex
> _
{mMutex
};
774 mDevice
->renderSamples(buffer
, len
, mFrameStep
);
775 mPadding
.store(written
+ len
, std::memory_order_relaxed
);
777 hr
= mRender
->ReleaseBuffer(len
, 0);
781 ERR("Failed to buffer data: 0x%08lx\n", hr
);
782 mDevice
->handleDisconnect("Failed to send playback samples: 0x%08lx", hr
);
786 mPadding
.store(0u, std::memory_order_release
);
793 void WasapiPlayback::open(const ALCchar
*name
)
797 mNotifyEvent
= CreateEventW(nullptr, FALSE
, FALSE
, nullptr);
798 if(mNotifyEvent
== nullptr)
800 ERR("Failed to create notify events: %lu\n", GetLastError());
808 if(PlaybackDevices
.empty())
809 pushMessage(MsgType::EnumeratePlayback
).wait();
812 auto iter
= std::find_if(PlaybackDevices
.cbegin(), PlaybackDevices
.cend(),
813 [name
](const DevMap
&entry
) -> bool
814 { return entry
.name
== name
|| entry
.endpoint_guid
== name
; });
815 if(iter
== PlaybackDevices
.cend())
817 const std::wstring wname
{utf8_to_wstr(name
)};
818 iter
= std::find_if(PlaybackDevices
.cbegin(), PlaybackDevices
.cend(),
819 [&wname
](const DevMap
&entry
) -> bool
820 { return entry
.devid
== wname
; });
822 if(iter
== PlaybackDevices
.cend())
823 WARN("Failed to find device name matching \"%s\"\n", name
);
826 mDevId
= iter
->devid
;
827 mDevice
->DeviceName
= iter
->name
;
834 hr
= pushMessage(MsgType::OpenDevice
).get();
839 if(mNotifyEvent
!= nullptr)
840 CloseHandle(mNotifyEvent
);
841 mNotifyEvent
= nullptr;
845 throw al::backend_exception
{ALC_INVALID_VALUE
, "Device init failed: 0x%08lx", hr
};
849 HRESULT
WasapiPlayback::openProxy()
852 HRESULT hr
{CoCreateInstance(CLSID_MMDeviceEnumerator
, nullptr, CLSCTX_INPROC_SERVER
,
853 IID_IMMDeviceEnumerator
, &ptr
)};
856 ComPtr
<IMMDeviceEnumerator
> enumerator
{static_cast<IMMDeviceEnumerator
*>(ptr
)};
858 hr
= enumerator
->GetDefaultAudioEndpoint(eRender
, eMultimedia
, mMMDev
.getPtr());
860 hr
= enumerator
->GetDevice(mDevId
.c_str(), mMMDev
.getPtr());
863 hr
= mMMDev
->Activate(IID_IAudioClient
, CLSCTX_INPROC_SERVER
, nullptr, &ptr
);
866 mClient
= ComPtr
<IAudioClient
>{static_cast<IAudioClient
*>(ptr
)};
867 if(mDevice
->DeviceName
.empty())
868 mDevice
->DeviceName
= get_device_name_and_guid(mMMDev
.get()).first
;
877 void WasapiPlayback::closeProxy()
884 bool WasapiPlayback::reset()
886 HRESULT hr
{pushMessage(MsgType::ResetDevice
).get()};
888 throw al::backend_exception
{ALC_INVALID_VALUE
, "0x%08lx", hr
};
892 HRESULT
WasapiPlayback::resetProxy()
897 HRESULT hr
{mMMDev
->Activate(IID_IAudioClient
, CLSCTX_INPROC_SERVER
, nullptr, &ptr
)};
900 ERR("Failed to reactivate audio client: 0x%08lx\n", hr
);
903 mClient
= ComPtr
<IAudioClient
>{static_cast<IAudioClient
*>(ptr
)};
906 hr
= mClient
->GetMixFormat(&wfx
);
909 ERR("Failed to get mix format: 0x%08lx\n", hr
);
913 WAVEFORMATEXTENSIBLE OutputType
;
914 if(!MakeExtensible(&OutputType
, wfx
))
922 const ReferenceTime per_time
{ReferenceTime
{seconds
{mDevice
->UpdateSize
}} / mDevice
->Frequency
};
923 const ReferenceTime buf_time
{ReferenceTime
{seconds
{mDevice
->BufferSize
}} / mDevice
->Frequency
};
925 if(!mDevice
->Flags
.get
<FrequencyRequest
>())
926 mDevice
->Frequency
= OutputType
.Format
.nSamplesPerSec
;
927 if(!mDevice
->Flags
.get
<ChannelsRequest
>())
929 const uint32_t chancount
{OutputType
.Format
.nChannels
};
930 const DWORD chanmask
{OutputType
.dwChannelMask
};
931 if(chancount
>= 8 && (chanmask
&X71Mask
) == X7DOT1
)
932 mDevice
->FmtChans
= DevFmtX71
;
933 else if(chancount
>= 7 && (chanmask
&X61Mask
) == X6DOT1
)
934 mDevice
->FmtChans
= DevFmtX61
;
935 else if(chancount
>= 6 && (chanmask
&X51Mask
) == X5DOT1
)
936 mDevice
->FmtChans
= DevFmtX51
;
937 else if(chancount
>= 6 && (chanmask
&X51RearMask
) == X5DOT1REAR
)
938 mDevice
->FmtChans
= DevFmtX51Rear
;
939 else if(chancount
>= 4 && (chanmask
&QuadMask
) == QUAD
)
940 mDevice
->FmtChans
= DevFmtQuad
;
941 else if(chancount
>= 2 && (chanmask
&StereoMask
) == STEREO
)
942 mDevice
->FmtChans
= DevFmtStereo
;
943 else if(chancount
>= 1 && (chanmask
&MonoMask
) == MONO
)
944 mDevice
->FmtChans
= DevFmtMono
;
946 ERR("Unhandled channel config: %d -- 0x%08lx\n", chancount
, chanmask
);
949 OutputType
.Format
.wFormatTag
= WAVE_FORMAT_EXTENSIBLE
;
950 switch(mDevice
->FmtChans
)
953 OutputType
.Format
.nChannels
= 1;
954 OutputType
.dwChannelMask
= MONO
;
957 mDevice
->FmtChans
= DevFmtStereo
;
960 OutputType
.Format
.nChannels
= 2;
961 OutputType
.dwChannelMask
= STEREO
;
964 OutputType
.Format
.nChannels
= 4;
965 OutputType
.dwChannelMask
= QUAD
;
968 OutputType
.Format
.nChannels
= 6;
969 OutputType
.dwChannelMask
= X5DOT1
;
972 OutputType
.Format
.nChannels
= 6;
973 OutputType
.dwChannelMask
= X5DOT1REAR
;
976 OutputType
.Format
.nChannels
= 7;
977 OutputType
.dwChannelMask
= X6DOT1
;
980 OutputType
.Format
.nChannels
= 8;
981 OutputType
.dwChannelMask
= X7DOT1
;
984 switch(mDevice
->FmtType
)
987 mDevice
->FmtType
= DevFmtUByte
;
990 OutputType
.Format
.wBitsPerSample
= 8;
991 OutputType
.Samples
.wValidBitsPerSample
= 8;
992 OutputType
.SubFormat
= KSDATAFORMAT_SUBTYPE_PCM
;
995 mDevice
->FmtType
= DevFmtShort
;
998 OutputType
.Format
.wBitsPerSample
= 16;
999 OutputType
.Samples
.wValidBitsPerSample
= 16;
1000 OutputType
.SubFormat
= KSDATAFORMAT_SUBTYPE_PCM
;
1003 mDevice
->FmtType
= DevFmtInt
;
1006 OutputType
.Format
.wBitsPerSample
= 32;
1007 OutputType
.Samples
.wValidBitsPerSample
= 32;
1008 OutputType
.SubFormat
= KSDATAFORMAT_SUBTYPE_PCM
;
1011 OutputType
.Format
.wBitsPerSample
= 32;
1012 OutputType
.Samples
.wValidBitsPerSample
= 32;
1013 OutputType
.SubFormat
= KSDATAFORMAT_SUBTYPE_IEEE_FLOAT
;
1016 OutputType
.Format
.nSamplesPerSec
= mDevice
->Frequency
;
1018 OutputType
.Format
.nBlockAlign
= static_cast<WORD
>(OutputType
.Format
.nChannels
*
1019 OutputType
.Format
.wBitsPerSample
/ 8);
1020 OutputType
.Format
.nAvgBytesPerSec
= OutputType
.Format
.nSamplesPerSec
*
1021 OutputType
.Format
.nBlockAlign
;
1023 TraceFormat("Requesting playback format", &OutputType
.Format
);
1024 hr
= mClient
->IsFormatSupported(AUDCLNT_SHAREMODE_SHARED
, &OutputType
.Format
, &wfx
);
1027 ERR("Failed to check format support: 0x%08lx\n", hr
);
1028 hr
= mClient
->GetMixFormat(&wfx
);
1032 ERR("Failed to find a supported format: 0x%08lx\n", hr
);
1038 TraceFormat("Got playback format", wfx
);
1039 if(!MakeExtensible(&OutputType
, wfx
))
1047 mDevice
->Frequency
= OutputType
.Format
.nSamplesPerSec
;
1048 const uint32_t chancount
{OutputType
.Format
.nChannels
};
1049 const DWORD chanmask
{OutputType
.dwChannelMask
};
1050 if(chancount
>= 8 && (chanmask
&X71Mask
) == X7DOT1
)
1051 mDevice
->FmtChans
= DevFmtX71
;
1052 else if(chancount
>= 7 && (chanmask
&X61Mask
) == X6DOT1
)
1053 mDevice
->FmtChans
= DevFmtX61
;
1054 else if(chancount
>= 6 && (chanmask
&X51Mask
) == X5DOT1
)
1055 mDevice
->FmtChans
= DevFmtX51
;
1056 else if(chancount
>= 6 && (chanmask
&X51RearMask
) == X5DOT1REAR
)
1057 mDevice
->FmtChans
= DevFmtX51Rear
;
1058 else if(chancount
>= 4 && (chanmask
&QuadMask
) == QUAD
)
1059 mDevice
->FmtChans
= DevFmtQuad
;
1060 else if(chancount
>= 2 && (chanmask
&StereoMask
) == STEREO
)
1061 mDevice
->FmtChans
= DevFmtStereo
;
1062 else if(chancount
>= 1 && (chanmask
&MonoMask
) == MONO
)
1063 mDevice
->FmtChans
= DevFmtMono
;
1066 ERR("Unhandled extensible channels: %d -- 0x%08lx\n", OutputType
.Format
.nChannels
,
1067 OutputType
.dwChannelMask
);
1068 mDevice
->FmtChans
= DevFmtStereo
;
1069 OutputType
.Format
.nChannels
= 2;
1070 OutputType
.dwChannelMask
= STEREO
;
1073 if(IsEqualGUID(OutputType
.SubFormat
, KSDATAFORMAT_SUBTYPE_PCM
))
1075 if(OutputType
.Format
.wBitsPerSample
== 8)
1076 mDevice
->FmtType
= DevFmtUByte
;
1077 else if(OutputType
.Format
.wBitsPerSample
== 16)
1078 mDevice
->FmtType
= DevFmtShort
;
1079 else if(OutputType
.Format
.wBitsPerSample
== 32)
1080 mDevice
->FmtType
= DevFmtInt
;
1083 mDevice
->FmtType
= DevFmtShort
;
1084 OutputType
.Format
.wBitsPerSample
= 16;
1087 else if(IsEqualGUID(OutputType
.SubFormat
, KSDATAFORMAT_SUBTYPE_IEEE_FLOAT
))
1089 mDevice
->FmtType
= DevFmtFloat
;
1090 OutputType
.Format
.wBitsPerSample
= 32;
1094 ERR("Unhandled format sub-type: %s\n", GuidPrinter
{OutputType
.SubFormat
}.c_str());
1095 mDevice
->FmtType
= DevFmtShort
;
1096 if(OutputType
.Format
.wFormatTag
!= WAVE_FORMAT_EXTENSIBLE
)
1097 OutputType
.Format
.wFormatTag
= WAVE_FORMAT_PCM
;
1098 OutputType
.Format
.wBitsPerSample
= 16;
1099 OutputType
.SubFormat
= KSDATAFORMAT_SUBTYPE_PCM
;
1101 OutputType
.Samples
.wValidBitsPerSample
= OutputType
.Format
.wBitsPerSample
;
1103 mFrameStep
= OutputType
.Format
.nChannels
;
1105 EndpointFormFactor formfactor
{UnknownFormFactor
};
1106 get_device_formfactor(mMMDev
.get(), &formfactor
);
1107 mDevice
->IsHeadphones
= (mDevice
->FmtChans
== DevFmtStereo
1108 && (formfactor
== Headphones
|| formfactor
== Headset
));
1110 setChannelOrderFromWFXMask(OutputType
.dwChannelMask
);
1112 hr
= mClient
->Initialize(AUDCLNT_SHAREMODE_SHARED
, AUDCLNT_STREAMFLAGS_EVENTCALLBACK
,
1113 buf_time
.count(), 0, &OutputType
.Format
, nullptr);
1116 ERR("Failed to initialize audio client: 0x%08lx\n", hr
);
1120 UINT32 buffer_len
{};
1121 ReferenceTime min_per
{};
1122 hr
= mClient
->GetDevicePeriod(&reinterpret_cast<REFERENCE_TIME
&>(min_per
), nullptr);
1124 hr
= mClient
->GetBufferSize(&buffer_len
);
1127 ERR("Failed to get audio buffer info: 0x%08lx\n", hr
);
1131 /* Find the nearest multiple of the period size to the update size */
1132 if(min_per
< per_time
)
1133 min_per
*= maxi64((per_time
+ min_per
/2) / min_per
, 1);
1134 mDevice
->UpdateSize
= minu(RefTime2Samples(min_per
, mDevice
->Frequency
), buffer_len
/2);
1135 mDevice
->BufferSize
= buffer_len
;
1137 hr
= mClient
->SetEventHandle(mNotifyEvent
);
1140 ERR("Failed to set event handle: 0x%08lx\n", hr
);
1148 void WasapiPlayback::start()
1150 const HRESULT hr
{pushMessage(MsgType::StartDevice
).get()};
1152 throw al::backend_exception
{ALC_INVALID_DEVICE
, "Failed to start playback: 0x%lx", hr
};
1155 HRESULT
WasapiPlayback::startProxy()
1157 ResetEvent(mNotifyEvent
);
1159 HRESULT hr
{mClient
->Start()};
1162 ERR("Failed to start audio client: 0x%08lx\n", hr
);
1167 hr
= mClient
->GetService(IID_IAudioRenderClient
, &ptr
);
1170 mRender
= ComPtr
<IAudioRenderClient
>{static_cast<IAudioRenderClient
*>(ptr
)};
1172 mKillNow
.store(false, std::memory_order_release
);
1173 mThread
= std::thread
{std::mem_fn(&WasapiPlayback::mixerProc
), this};
1177 ERR("Failed to start thread\n");
1189 void WasapiPlayback::stop()
1190 { pushMessage(MsgType::StopDevice
).wait(); }
1192 void WasapiPlayback::stopProxy()
1194 if(!mRender
|| !mThread
.joinable())
1197 mKillNow
.store(true, std::memory_order_release
);
1205 ClockLatency
WasapiPlayback::getClockLatency()
1209 std::lock_guard
<std::mutex
> _
{mMutex
};
1210 ret
.ClockTime
= GetDeviceClockTime(mDevice
);
1211 ret
.Latency
= std::chrono::seconds
{mPadding
.load(std::memory_order_relaxed
)};
1212 ret
.Latency
/= mDevice
->Frequency
;
1218 struct WasapiCapture final
: public BackendBase
, WasapiProxy
{
1219 WasapiCapture(ALCdevice
*device
) noexcept
: BackendBase
{device
} { }
1220 ~WasapiCapture() override
;
1224 void open(const ALCchar
*name
) override
;
1225 HRESULT
openProxy() override
;
1226 void closeProxy() override
;
1228 HRESULT
resetProxy() override
;
1229 void start() override
;
1230 HRESULT
startProxy() override
;
1231 void stop() override
;
1232 void stopProxy() override
;
1234 ALCenum
captureSamples(al::byte
*buffer
, ALCuint samples
) override
;
1235 ALCuint
availableSamples() override
;
1237 std::wstring mDevId
;
1239 HRESULT mOpenStatus
{E_FAIL
};
1240 ComPtr
<IMMDevice
> mMMDev
{nullptr};
1241 ComPtr
<IAudioClient
> mClient
{nullptr};
1242 ComPtr
<IAudioCaptureClient
> mCapture
{nullptr};
1243 HANDLE mNotifyEvent
{nullptr};
1245 ChannelConverter mChannelConv
{};
1246 SampleConverterPtr mSampleConv
;
1247 RingBufferPtr mRing
;
1249 std::atomic
<bool> mKillNow
{true};
1250 std::thread mThread
;
1252 DEF_NEWDEL(WasapiCapture
)
1255 WasapiCapture::~WasapiCapture()
1257 if(SUCCEEDED(mOpenStatus
))
1258 pushMessage(MsgType::CloseDevice
).wait();
1259 mOpenStatus
= E_FAIL
;
1261 if(mNotifyEvent
!= nullptr)
1262 CloseHandle(mNotifyEvent
);
1263 mNotifyEvent
= nullptr;
1267 FORCE_ALIGN
int WasapiCapture::recordProc()
1269 HRESULT hr
{CoInitializeEx(nullptr, COINIT_MULTITHREADED
)};
1272 ERR("CoInitializeEx(nullptr, COINIT_MULTITHREADED) failed: 0x%08lx\n", hr
);
1273 mDevice
->handleDisconnect("COM init failed: 0x%08lx", hr
);
1277 althrd_setname(RECORD_THREAD_NAME
);
1279 al::vector
<float> samples
;
1280 while(!mKillNow
.load(std::memory_order_relaxed
))
1283 hr
= mCapture
->GetNextPacketSize(&avail
);
1285 ERR("Failed to get next packet size: 0x%08lx\n", hr
);
1292 hr
= mCapture
->GetBuffer(&rdata
, &numsamples
, &flags
, nullptr, nullptr);
1294 ERR("Failed to get capture buffer: 0x%08lx\n", hr
);
1297 if(mChannelConv
.is_active())
1299 samples
.resize(numsamples
*2);
1300 mChannelConv
.convert(rdata
, samples
.data(), numsamples
);
1301 rdata
= reinterpret_cast<BYTE
*>(samples
.data());
1304 auto data
= mRing
->getWriteVector();
1309 const void *srcdata
{rdata
};
1310 ALuint srcframes
{numsamples
};
1312 dstframes
= mSampleConv
->convert(&srcdata
, &srcframes
, data
.first
.buf
,
1313 static_cast<ALuint
>(minz(data
.first
.len
, INT_MAX
)));
1314 if(srcframes
> 0 && dstframes
== data
.first
.len
&& data
.second
.len
> 0)
1316 /* If some source samples remain, all of the first dest
1317 * block was filled, and there's space in the second
1318 * dest block, do another run for the second block.
1320 dstframes
+= mSampleConv
->convert(&srcdata
, &srcframes
, data
.second
.buf
,
1321 static_cast<ALuint
>(minz(data
.second
.len
, INT_MAX
)));
1326 const auto framesize
= static_cast<ALuint
>(mDevice
->frameSizeFromFmt());
1327 size_t len1
{minz(data
.first
.len
, numsamples
)};
1328 size_t len2
{minz(data
.second
.len
, numsamples
-len1
)};
1330 memcpy(data
.first
.buf
, rdata
, len1
*framesize
);
1332 memcpy(data
.second
.buf
, rdata
+len1
*framesize
, len2
*framesize
);
1333 dstframes
= len1
+ len2
;
1336 mRing
->writeAdvance(dstframes
);
1338 hr
= mCapture
->ReleaseBuffer(numsamples
);
1339 if(FAILED(hr
)) ERR("Failed to release capture buffer: 0x%08lx\n", hr
);
1345 mDevice
->handleDisconnect("Failed to capture samples: 0x%08lx", hr
);
1349 DWORD res
{WaitForSingleObjectEx(mNotifyEvent
, 2000, FALSE
)};
1350 if(res
!= WAIT_OBJECT_0
)
1351 ERR("WaitForSingleObjectEx error: 0x%lx\n", res
);
1359 void WasapiCapture::open(const ALCchar
*name
)
1363 mNotifyEvent
= CreateEventW(nullptr, FALSE
, FALSE
, nullptr);
1364 if(mNotifyEvent
== nullptr)
1366 ERR("Failed to create notify event: %lu\n", GetLastError());
1374 if(CaptureDevices
.empty())
1375 pushMessage(MsgType::EnumerateCapture
).wait();
1378 auto iter
= std::find_if(CaptureDevices
.cbegin(), CaptureDevices
.cend(),
1379 [name
](const DevMap
&entry
) -> bool
1380 { return entry
.name
== name
|| entry
.endpoint_guid
== name
; });
1381 if(iter
== CaptureDevices
.cend())
1383 const std::wstring wname
{utf8_to_wstr(name
)};
1384 iter
= std::find_if(CaptureDevices
.cbegin(), CaptureDevices
.cend(),
1385 [&wname
](const DevMap
&entry
) -> bool
1386 { return entry
.devid
== wname
; });
1388 if(iter
== CaptureDevices
.cend())
1389 WARN("Failed to find device name matching \"%s\"\n", name
);
1392 mDevId
= iter
->devid
;
1393 mDevice
->DeviceName
= iter
->name
;
1400 hr
= pushMessage(MsgType::OpenDevice
).get();
1405 if(mNotifyEvent
!= nullptr)
1406 CloseHandle(mNotifyEvent
);
1407 mNotifyEvent
= nullptr;
1411 throw al::backend_exception
{ALC_INVALID_VALUE
, "Device init failed: 0x%08lx", hr
};
1414 hr
= pushMessage(MsgType::ResetDevice
).get();
1417 if(hr
== E_OUTOFMEMORY
)
1418 throw al::backend_exception
{ALC_OUT_OF_MEMORY
, "Out of memory"};
1419 throw al::backend_exception
{ALC_INVALID_VALUE
, "Device reset failed"};
1423 HRESULT
WasapiCapture::openProxy()
1426 HRESULT hr
{CoCreateInstance(CLSID_MMDeviceEnumerator
, nullptr, CLSCTX_INPROC_SERVER
,
1427 IID_IMMDeviceEnumerator
, &ptr
)};
1430 ComPtr
<IMMDeviceEnumerator
> enumerator
{static_cast<IMMDeviceEnumerator
*>(ptr
)};
1432 hr
= enumerator
->GetDefaultAudioEndpoint(eCapture
, eMultimedia
, mMMDev
.getPtr());
1434 hr
= enumerator
->GetDevice(mDevId
.c_str(), mMMDev
.getPtr());
1437 hr
= mMMDev
->Activate(IID_IAudioClient
, CLSCTX_INPROC_SERVER
, nullptr, &ptr
);
1440 mClient
= ComPtr
<IAudioClient
>{static_cast<IAudioClient
*>(ptr
)};
1441 if(mDevice
->DeviceName
.empty())
1442 mDevice
->DeviceName
= get_device_name_and_guid(mMMDev
.get()).first
;
1451 void WasapiCapture::closeProxy()
1457 HRESULT
WasapiCapture::resetProxy()
1462 HRESULT hr
{mMMDev
->Activate(IID_IAudioClient
, CLSCTX_INPROC_SERVER
, nullptr, &ptr
)};
1465 ERR("Failed to reactivate audio client: 0x%08lx\n", hr
);
1468 mClient
= ComPtr
<IAudioClient
>{static_cast<IAudioClient
*>(ptr
)};
1470 // Make sure buffer is at least 100ms in size
1471 ReferenceTime buf_time
{ReferenceTime
{seconds
{mDevice
->BufferSize
}} / mDevice
->Frequency
};
1472 buf_time
= std::max(buf_time
, ReferenceTime
{milliseconds
{100}});
1474 WAVEFORMATEXTENSIBLE OutputType
{};
1475 OutputType
.Format
.wFormatTag
= WAVE_FORMAT_EXTENSIBLE
;
1476 switch(mDevice
->FmtChans
)
1479 OutputType
.Format
.nChannels
= 1;
1480 OutputType
.dwChannelMask
= MONO
;
1483 OutputType
.Format
.nChannels
= 2;
1484 OutputType
.dwChannelMask
= STEREO
;
1487 OutputType
.Format
.nChannels
= 4;
1488 OutputType
.dwChannelMask
= QUAD
;
1491 OutputType
.Format
.nChannels
= 6;
1492 OutputType
.dwChannelMask
= X5DOT1
;
1495 OutputType
.Format
.nChannels
= 6;
1496 OutputType
.dwChannelMask
= X5DOT1REAR
;
1499 OutputType
.Format
.nChannels
= 7;
1500 OutputType
.dwChannelMask
= X6DOT1
;
1503 OutputType
.Format
.nChannels
= 8;
1504 OutputType
.dwChannelMask
= X7DOT1
;
1510 switch(mDevice
->FmtType
)
1512 /* NOTE: Signedness doesn't matter, the converter will handle it. */
1515 OutputType
.Format
.wBitsPerSample
= 8;
1516 OutputType
.SubFormat
= KSDATAFORMAT_SUBTYPE_PCM
;
1520 OutputType
.Format
.wBitsPerSample
= 16;
1521 OutputType
.SubFormat
= KSDATAFORMAT_SUBTYPE_PCM
;
1525 OutputType
.Format
.wBitsPerSample
= 32;
1526 OutputType
.SubFormat
= KSDATAFORMAT_SUBTYPE_PCM
;
1529 OutputType
.Format
.wBitsPerSample
= 32;
1530 OutputType
.SubFormat
= KSDATAFORMAT_SUBTYPE_IEEE_FLOAT
;
1533 OutputType
.Samples
.wValidBitsPerSample
= OutputType
.Format
.wBitsPerSample
;
1534 OutputType
.Format
.nSamplesPerSec
= mDevice
->Frequency
;
1536 OutputType
.Format
.nBlockAlign
= static_cast<WORD
>(OutputType
.Format
.nChannels
*
1537 OutputType
.Format
.wBitsPerSample
/ 8);
1538 OutputType
.Format
.nAvgBytesPerSec
= OutputType
.Format
.nSamplesPerSec
*
1539 OutputType
.Format
.nBlockAlign
;
1540 OutputType
.Format
.cbSize
= sizeof(OutputType
) - sizeof(OutputType
.Format
);
1542 TraceFormat("Requesting capture format", &OutputType
.Format
);
1544 hr
= mClient
->IsFormatSupported(AUDCLNT_SHAREMODE_SHARED
, &OutputType
.Format
, &wfx
);
1547 ERR("Failed to check format support: 0x%08lx\n", hr
);
1551 mSampleConv
= nullptr;
1556 TraceFormat("Got capture format", wfx
);
1557 if(!(wfx
->nChannels
== OutputType
.Format
.nChannels
||
1558 (wfx
->nChannels
== 1 && OutputType
.Format
.nChannels
== 2) ||
1559 (wfx
->nChannels
== 2 && OutputType
.Format
.nChannels
== 1)))
1561 ERR("Failed to get matching format, wanted: %s %s %uhz, got: %d channel%s %d-bit %luhz\n",
1562 DevFmtChannelsString(mDevice
->FmtChans
), DevFmtTypeString(mDevice
->FmtType
),
1563 mDevice
->Frequency
, wfx
->nChannels
, (wfx
->nChannels
==1)?"":"s", wfx
->wBitsPerSample
,
1564 wfx
->nSamplesPerSec
);
1569 if(!MakeExtensible(&OutputType
, wfx
))
1579 if(IsEqualGUID(OutputType
.SubFormat
, KSDATAFORMAT_SUBTYPE_PCM
))
1581 if(OutputType
.Format
.wBitsPerSample
== 8)
1582 srcType
= DevFmtUByte
;
1583 else if(OutputType
.Format
.wBitsPerSample
== 16)
1584 srcType
= DevFmtShort
;
1585 else if(OutputType
.Format
.wBitsPerSample
== 32)
1586 srcType
= DevFmtInt
;
1589 ERR("Unhandled integer bit depth: %d\n", OutputType
.Format
.wBitsPerSample
);
1593 else if(IsEqualGUID(OutputType
.SubFormat
, KSDATAFORMAT_SUBTYPE_IEEE_FLOAT
))
1595 if(OutputType
.Format
.wBitsPerSample
== 32)
1596 srcType
= DevFmtFloat
;
1599 ERR("Unhandled float bit depth: %d\n", OutputType
.Format
.wBitsPerSample
);
1605 ERR("Unhandled format sub-type: %s\n", GuidPrinter
{OutputType
.SubFormat
}.c_str());
1609 if(mDevice
->FmtChans
== DevFmtMono
&& OutputType
.Format
.nChannels
== 2)
1611 mChannelConv
= ChannelConverter
{srcType
, DevFmtStereo
, mDevice
->FmtChans
};
1612 TRACE("Created %s stereo-to-mono converter\n", DevFmtTypeString(srcType
));
1613 /* The channel converter always outputs float, so change the input type
1614 * for the resampler/type-converter.
1616 srcType
= DevFmtFloat
;
1618 else if(mDevice
->FmtChans
== DevFmtStereo
&& OutputType
.Format
.nChannels
== 1)
1620 mChannelConv
= ChannelConverter
{srcType
, DevFmtMono
, mDevice
->FmtChans
};
1621 TRACE("Created %s mono-to-stereo converter\n", DevFmtTypeString(srcType
));
1622 srcType
= DevFmtFloat
;
1625 if(mDevice
->Frequency
!= OutputType
.Format
.nSamplesPerSec
|| mDevice
->FmtType
!= srcType
)
1627 mSampleConv
= CreateSampleConverter(srcType
, mDevice
->FmtType
, mDevice
->channelsFromFmt(),
1628 OutputType
.Format
.nSamplesPerSec
, mDevice
->Frequency
, Resampler::FastBSinc24
);
1631 ERR("Failed to create converter for %s format, dst: %s %uhz, src: %s %luhz\n",
1632 DevFmtChannelsString(mDevice
->FmtChans
), DevFmtTypeString(mDevice
->FmtType
),
1633 mDevice
->Frequency
, DevFmtTypeString(srcType
), OutputType
.Format
.nSamplesPerSec
);
1636 TRACE("Created converter for %s format, dst: %s %uhz, src: %s %luhz\n",
1637 DevFmtChannelsString(mDevice
->FmtChans
), DevFmtTypeString(mDevice
->FmtType
),
1638 mDevice
->Frequency
, DevFmtTypeString(srcType
), OutputType
.Format
.nSamplesPerSec
);
1641 hr
= mClient
->Initialize(AUDCLNT_SHAREMODE_SHARED
, AUDCLNT_STREAMFLAGS_EVENTCALLBACK
,
1642 buf_time
.count(), 0, &OutputType
.Format
, nullptr);
1645 ERR("Failed to initialize audio client: 0x%08lx\n", hr
);
1649 UINT32 buffer_len
{};
1650 ReferenceTime min_per
{};
1651 hr
= mClient
->GetDevicePeriod(&reinterpret_cast<REFERENCE_TIME
&>(min_per
), nullptr);
1653 hr
= mClient
->GetBufferSize(&buffer_len
);
1656 ERR("Failed to get buffer size: 0x%08lx\n", hr
);
1659 mDevice
->UpdateSize
= RefTime2Samples(min_per
, mDevice
->Frequency
);
1660 mDevice
->BufferSize
= buffer_len
;
1662 mRing
= RingBuffer::Create(buffer_len
, mDevice
->frameSizeFromFmt(), false);
1664 hr
= mClient
->SetEventHandle(mNotifyEvent
);
1667 ERR("Failed to set event handle: 0x%08lx\n", hr
);
1675 void WasapiCapture::start()
1677 const HRESULT hr
{pushMessage(MsgType::StartDevice
).get()};
1679 throw al::backend_exception
{ALC_INVALID_DEVICE
, "Failed to start recording: 0x%lx", hr
};
1682 HRESULT
WasapiCapture::startProxy()
1684 ResetEvent(mNotifyEvent
);
1686 HRESULT hr
{mClient
->Start()};
1689 ERR("Failed to start audio client: 0x%08lx\n", hr
);
1694 hr
= mClient
->GetService(IID_IAudioCaptureClient
, &ptr
);
1697 mCapture
= ComPtr
<IAudioCaptureClient
>{static_cast<IAudioCaptureClient
*>(ptr
)};
1699 mKillNow
.store(false, std::memory_order_release
);
1700 mThread
= std::thread
{std::mem_fn(&WasapiCapture::recordProc
), this};
1704 ERR("Failed to start thread\n");
1719 void WasapiCapture::stop()
1720 { pushMessage(MsgType::StopDevice
).wait(); }
1722 void WasapiCapture::stopProxy()
1724 if(!mCapture
|| !mThread
.joinable())
1727 mKillNow
.store(true, std::memory_order_release
);
1736 ALCuint
WasapiCapture::availableSamples()
1737 { return static_cast<ALCuint
>(mRing
->readSpace()); }
1739 ALCenum
WasapiCapture::captureSamples(al::byte
*buffer
, ALCuint samples
)
1741 mRing
->read(buffer
, samples
);
1742 return ALC_NO_ERROR
;
1748 bool WasapiBackendFactory::init()
1750 static HRESULT InitResult
{E_FAIL
};
1752 if(FAILED(InitResult
)) try
1754 std::promise
<HRESULT
> promise
;
1755 auto future
= promise
.get_future();
1757 std::thread
{&WasapiProxy::messageHandler
, &promise
}.detach();
1758 InitResult
= future
.get();
1763 return SUCCEEDED(InitResult
) ? ALC_TRUE
: ALC_FALSE
;
1766 bool WasapiBackendFactory::querySupport(BackendType type
)
1767 { return type
== BackendType::Playback
|| type
== BackendType::Capture
; }
1769 std::string
WasapiBackendFactory::probe(BackendType type
)
1771 std::string outnames
;
1772 auto add_device
= [&outnames
](const DevMap
&entry
) -> void
1774 /* +1 to also append the null char (to ensure a null-separated list and
1775 * double-null terminated list).
1777 outnames
.append(entry
.name
.c_str(), entry
.name
.length()+1);
1782 case BackendType::Playback
:
1783 WasapiProxy::pushMessageStatic(MsgType::EnumeratePlayback
).wait();
1784 std::for_each(PlaybackDevices
.cbegin(), PlaybackDevices
.cend(), add_device
);
1787 case BackendType::Capture
:
1788 WasapiProxy::pushMessageStatic(MsgType::EnumerateCapture
).wait();
1789 std::for_each(CaptureDevices
.cbegin(), CaptureDevices
.cend(), add_device
);
1796 BackendPtr
WasapiBackendFactory::createBackend(ALCdevice
*device
, BackendType type
)
1798 if(type
== BackendType::Playback
)
1799 return BackendPtr
{new WasapiPlayback
{device
}};
1800 if(type
== BackendType::Capture
)
1801 return BackendPtr
{new WasapiCapture
{device
}};
1805 BackendFactory
&WasapiBackendFactory::getFactory()
1807 static WasapiBackendFactory factory
{};