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
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>
60 #include "alc/alconfig.h"
61 #include "alnumeric.h"
63 #include "core/converter.h"
64 #include "core/device.h"
65 #include "core/helpers.h"
66 #include "core/logging.h"
67 #include "ringbuffer.h"
72 /* Some headers seem to define these as macros for __uuidof, which is annoying
73 * since some headers don't declare them at all. Hopefully the ifdef is enough
74 * to tell if they need to be declared.
76 #ifndef KSDATAFORMAT_SUBTYPE_PCM
77 DEFINE_GUID(KSDATAFORMAT_SUBTYPE_PCM, 0x00000001, 0x0000, 0x0010, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71);
79 #ifndef KSDATAFORMAT_SUBTYPE_IEEE_FLOAT
80 DEFINE_GUID(KSDATAFORMAT_SUBTYPE_IEEE_FLOAT, 0x00000003, 0x0000, 0x0010, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71);
83 DEFINE_DEVPROPKEY(DEVPKEY_Device_FriendlyName, 0xa45c254e, 0xdf1c, 0x4efd, 0x80,0x20, 0x67,0xd1,0x46,0xa8,0x50,0xe0, 14);
84 DEFINE_PROPERTYKEY(PKEY_AudioEndpoint_FormFactor, 0x1da5d803, 0xd492, 0x4edd, 0x8c,0x23, 0xe0,0xc0,0xff,0xee,0x7f,0x0e, 0);
85 DEFINE_PROPERTYKEY(PKEY_AudioEndpoint_GUID, 0x1da5d803, 0xd492, 0x4edd, 0x8c, 0x23,0xe0, 0xc0,0xff,0xee,0x7f,0x0e, 4 );
90 using std::chrono::nanoseconds;
91 using std::chrono::milliseconds;
92 using std::chrono::seconds;
94 using ReferenceTime = std::chrono::duration<REFERENCE_TIME,std::ratio<1,10000000>>;
96 inline constexpr ReferenceTime operator "" _reftime(unsigned long long int n) noexcept
97 { return ReferenceTime{static_cast<REFERENCE_TIME>(n)}; }
100 #define MONO SPEAKER_FRONT_CENTER
101 #define STEREO (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT)
102 #define QUAD (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT|SPEAKER_BACK_LEFT|SPEAKER_BACK_RIGHT)
103 #define X5DOT1 (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT|SPEAKER_FRONT_CENTER|SPEAKER_LOW_FREQUENCY|SPEAKER_SIDE_LEFT|SPEAKER_SIDE_RIGHT)
104 #define X5DOT1REAR (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT|SPEAKER_FRONT_CENTER|SPEAKER_LOW_FREQUENCY|SPEAKER_BACK_LEFT|SPEAKER_BACK_RIGHT)
105 #define X6DOT1 (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT|SPEAKER_FRONT_CENTER|SPEAKER_LOW_FREQUENCY|SPEAKER_BACK_CENTER|SPEAKER_SIDE_LEFT|SPEAKER_SIDE_RIGHT)
106 #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)
107 #define X7DOT1DOT4 (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT|SPEAKER_FRONT_CENTER|SPEAKER_LOW_FREQUENCY|SPEAKER_BACK_LEFT|SPEAKER_BACK_RIGHT|SPEAKER_SIDE_LEFT|SPEAKER_SIDE_RIGHT|SPEAKER_TOP_FRONT_LEFT|SPEAKER_TOP_FRONT_RIGHT|SPEAKER_TOP_BACK_LEFT|SPEAKER_TOP_BACK_RIGHT)
109 constexpr inline DWORD MaskFromTopBits(DWORD b) noexcept
118 constexpr DWORD MonoMask{MaskFromTopBits(MONO)};
119 constexpr DWORD StereoMask{MaskFromTopBits(STEREO)};
120 constexpr DWORD QuadMask{MaskFromTopBits(QUAD)};
121 constexpr DWORD X51Mask{MaskFromTopBits(X5DOT1)};
122 constexpr DWORD X51RearMask{MaskFromTopBits(X5DOT1REAR)};
123 constexpr DWORD X61Mask{MaskFromTopBits(X6DOT1)};
124 constexpr DWORD X71Mask{MaskFromTopBits(X7DOT1)};
125 constexpr DWORD X714Mask{MaskFromTopBits(X7DOT1DOT4)};
127 constexpr char DevNameHead[] = "OpenAL Soft on ";
128 constexpr size_t DevNameHeadLen{al::size(DevNameHead) - 1};
131 /* Scales the given reftime value, rounding the result. */
132 inline uint RefTime2Samples(const ReferenceTime &val, uint srate)
134 const auto retval = (val*srate + ReferenceTime{seconds{1}}/2) / seconds{1};
135 return static_cast<uint>(mini64(retval, std::numeric_limits<uint>::max()));
143 GuidPrinter(const GUID &guid)
145 std::snprintf(mMsg, al::size(mMsg), "{%08lx-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x}",
146 DWORD{guid.Data1}, guid.Data2, guid.Data3, guid.Data4[0], guid.Data4[1], guid.Data4[2],
147 guid.Data4[3], guid.Data4[4], guid.Data4[5], guid.Data4[6], guid.Data4[7]);
149 const char *c_str() const { return mMsg; }
156 PropVariant() { PropVariantInit(&mProp); }
157 ~PropVariant() { clear(); }
159 void clear() { PropVariantClear(&mProp); }
161 PROPVARIANT* get() noexcept { return &mProp; }
163 PROPVARIANT& operator*() noexcept { return mProp; }
164 const PROPVARIANT& operator*() const noexcept { return mProp; }
166 PROPVARIANT* operator->() noexcept { return &mProp; }
167 const PROPVARIANT* operator->() const noexcept { return &mProp; }
172 std::string endpoint_guid; // obtained from PKEY_AudioEndpoint_GUID , set to "Unknown device GUID" if absent.
175 template<typename T0, typename T1, typename T2>
176 DevMap(T0&& name_, T1&& guid_, T2&& devid_)
177 : name{std::forward<T0>(name_)}
178 , endpoint_guid{std::forward<T1>(guid_)}
179 , devid{std::forward<T2>(devid_)}
183 bool checkName(const al::vector<DevMap> &list, const std::string &name)
185 auto match_name = [&name](const DevMap &entry) -> bool
186 { return entry.name == name; };
187 return std::find_if(list.cbegin(), list.cend(), match_name) != list.cend();
190 al::vector<DevMap> PlaybackDevices;
191 al::vector<DevMap> CaptureDevices;
194 using NameGUIDPair = std::pair<std::string,std::string>;
195 NameGUIDPair get_device_name_and_guid(IMMDevice *device)
197 static constexpr char UnknownName[]{"Unknown Device Name"};
198 static constexpr char UnknownGuid[]{"Unknown Device GUID"};
199 std::string name, guid;
201 ComPtr<IPropertyStore> ps;
202 HRESULT hr = device->OpenPropertyStore(STGM_READ, ps.getPtr());
205 WARN("OpenPropertyStore failed: 0x%08lx\n", hr);
206 return std::make_pair(UnknownName, UnknownGuid);
210 hr = ps->GetValue(reinterpret_cast<const PROPERTYKEY&>(DEVPKEY_Device_FriendlyName), pvprop.get());
213 WARN("GetValue Device_FriendlyName failed: 0x%08lx\n", hr);
216 else if(pvprop->vt == VT_LPWSTR)
217 name += wstr_to_utf8(pvprop->pwszVal);
220 WARN("Unexpected PROPVARIANT type: 0x%04x\n", pvprop->vt);
225 hr = ps->GetValue(reinterpret_cast<const PROPERTYKEY&>(PKEY_AudioEndpoint_GUID), pvprop.get());
228 WARN("GetValue AudioEndpoint_GUID failed: 0x%08lx\n", hr);
231 else if(pvprop->vt == VT_LPWSTR)
232 guid = wstr_to_utf8(pvprop->pwszVal);
235 WARN("Unexpected PROPVARIANT type: 0x%04x\n", pvprop->vt);
239 return std::make_pair(std::move(name), std::move(guid));
242 EndpointFormFactor get_device_formfactor(IMMDevice *device)
244 ComPtr<IPropertyStore> ps;
245 HRESULT hr{device->OpenPropertyStore(STGM_READ, ps.getPtr())};
248 WARN("OpenPropertyStore failed: 0x%08lx\n", hr);
249 return UnknownFormFactor;
252 EndpointFormFactor formfactor{UnknownFormFactor};
254 hr = ps->GetValue(PKEY_AudioEndpoint_FormFactor, pvform.get());
256 WARN("GetValue AudioEndpoint_FormFactor failed: 0x%08lx\n", hr);
257 else if(pvform->vt == VT_UI4)
258 formfactor = static_cast<EndpointFormFactor>(pvform->ulVal);
259 else if(pvform->vt != VT_EMPTY)
260 WARN("Unexpected PROPVARIANT type: 0x%04x\n", pvform->vt);
265 void add_device(IMMDevice *device, const WCHAR *devid, al::vector<DevMap> &list)
267 for(auto &entry : list)
269 if(entry.devid == devid)
273 auto name_guid = get_device_name_and_guid(device);
276 std::string newname{name_guid.first};
277 while(checkName(list, newname))
279 newname = name_guid.first;
281 newname += std::to_string(++count);
283 list.emplace_back(std::move(newname), std::move(name_guid.second), devid);
284 const DevMap &newentry = list.back();
286 TRACE("Got device \"%s\", \"%s\", \"%ls\"\n", newentry.name.c_str(),
287 newentry.endpoint_guid.c_str(), newentry.devid.c_str());
290 WCHAR *get_device_id(IMMDevice *device)
294 const HRESULT hr{device->GetId(&devid)};
297 ERR("Failed to get device id: %lx\n", hr);
304 void probe_devices(IMMDeviceEnumerator *devenum, EDataFlow flowdir, al::vector<DevMap> &list)
306 al::vector<DevMap>{}.swap(list);
308 ComPtr<IMMDeviceCollection> coll;
309 HRESULT hr{devenum->EnumAudioEndpoints(flowdir, DEVICE_STATE_ACTIVE, coll.getPtr())};
312 ERR("Failed to enumerate audio endpoints: 0x%08lx\n", hr);
317 hr = coll->GetCount(&count);
318 if(SUCCEEDED(hr) && count > 0)
321 ComPtr<IMMDevice> device;
322 hr = devenum->GetDefaultAudioEndpoint(flowdir, eMultimedia, device.getPtr());
325 if(WCHAR *devid{get_device_id(device.get())})
327 add_device(device.get(), devid, list);
328 CoTaskMemFree(devid);
333 for(UINT i{0};i < count;++i)
335 hr = coll->Item(i, device.getPtr());
336 if(FAILED(hr)) continue;
338 if(WCHAR *devid{get_device_id(device.get())})
340 add_device(device.get(), devid, list);
341 CoTaskMemFree(devid);
348 bool MakeExtensible(WAVEFORMATEXTENSIBLE *out, const WAVEFORMATEX *in)
350 *out = WAVEFORMATEXTENSIBLE{};
351 if(in->wFormatTag == WAVE_FORMAT_EXTENSIBLE)
353 *out = *CONTAINING_RECORD(in, const WAVEFORMATEXTENSIBLE, Format);
354 out->Format.cbSize = sizeof(*out) - sizeof(out->Format);
356 else if(in->wFormatTag == WAVE_FORMAT_PCM)
359 out->Format.cbSize = 0;
360 out->Samples.wValidBitsPerSample = out->Format.wBitsPerSample;
361 if(out->Format.nChannels == 1)
362 out->dwChannelMask = MONO;
363 else if(out->Format.nChannels == 2)
364 out->dwChannelMask = STEREO;
366 ERR("Unhandled PCM channel count: %d\n", out->Format.nChannels);
367 out->SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
369 else if(in->wFormatTag == WAVE_FORMAT_IEEE_FLOAT)
372 out->Format.cbSize = 0;
373 out->Samples.wValidBitsPerSample = out->Format.wBitsPerSample;
374 if(out->Format.nChannels == 1)
375 out->dwChannelMask = MONO;
376 else if(out->Format.nChannels == 2)
377 out->dwChannelMask = STEREO;
379 ERR("Unhandled IEEE float channel count: %d\n", out->Format.nChannels);
380 out->SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT;
384 ERR("Unhandled format tag: 0x%04x\n", in->wFormatTag);
390 void TraceFormat(const char *msg, const WAVEFORMATEX *format)
392 constexpr size_t fmtex_extra_size{sizeof(WAVEFORMATEXTENSIBLE)-sizeof(WAVEFORMATEX)};
393 if(format->wFormatTag == WAVE_FORMAT_EXTENSIBLE && format->cbSize >= fmtex_extra_size)
395 const WAVEFORMATEXTENSIBLE *fmtex{
396 CONTAINING_RECORD(format, const WAVEFORMATEXTENSIBLE, Format)};
398 " FormatTag = 0x%04x\n"
400 " SamplesPerSec = %lu\n"
401 " AvgBytesPerSec = %lu\n"
403 " BitsPerSample = %d\n"
406 " ChannelMask = 0x%lx\n"
408 msg, fmtex->Format.wFormatTag, fmtex->Format.nChannels, fmtex->Format.nSamplesPerSec,
409 fmtex->Format.nAvgBytesPerSec, fmtex->Format.nBlockAlign, fmtex->Format.wBitsPerSample,
410 fmtex->Format.cbSize, fmtex->Samples.wReserved, fmtex->dwChannelMask,
411 GuidPrinter{fmtex->SubFormat}.c_str());
415 " FormatTag = 0x%04x\n"
417 " SamplesPerSec = %lu\n"
418 " AvgBytesPerSec = %lu\n"
420 " BitsPerSample = %d\n"
422 msg, format->wFormatTag, format->nChannels, format->nSamplesPerSec,
423 format->nAvgBytesPerSec, format->nBlockAlign, format->wBitsPerSample, format->cbSize);
440 constexpr char MessageStr[static_cast<size_t>(MsgType::Count)][20]{
446 "Enumerate Playback",
451 /* Proxy interface used by the message handler. */
453 virtual ~WasapiProxy() = default;
455 virtual HRESULT openProxy(const char *name) = 0;
456 virtual void closeProxy() = 0;
458 virtual HRESULT resetProxy() = 0;
459 virtual HRESULT startProxy() = 0;
460 virtual void stopProxy() = 0;
466 std::promise<HRESULT> mPromise;
468 explicit operator bool() const noexcept { return mType != MsgType::QuitThread; }
470 static std::thread sThread;
471 static std::deque<Msg> mMsgQueue;
472 static std::mutex mMsgQueueLock;
473 static std::condition_variable mMsgQueueCond;
474 static std::mutex sThreadLock;
475 static size_t sInitCount;
477 std::future<HRESULT> pushMessage(MsgType type, const char *param=nullptr)
479 std::promise<HRESULT> promise;
480 std::future<HRESULT> future{promise.get_future()};
482 std::lock_guard<std::mutex> _{mMsgQueueLock};
483 mMsgQueue.emplace_back(Msg{type, this, param, std::move(promise)});
485 mMsgQueueCond.notify_one();
489 static std::future<HRESULT> pushMessageStatic(MsgType type)
491 std::promise<HRESULT> promise;
492 std::future<HRESULT> future{promise.get_future()};
494 std::lock_guard<std::mutex> _{mMsgQueueLock};
495 mMsgQueue.emplace_back(Msg{type, nullptr, nullptr, std::move(promise)});
497 mMsgQueueCond.notify_one();
501 static Msg popMessage()
503 std::unique_lock<std::mutex> lock{mMsgQueueLock};
504 mMsgQueueCond.wait(lock, []{return !mMsgQueue.empty();});
505 Msg msg{std::move(mMsgQueue.front())};
506 mMsgQueue.pop_front();
510 static int messageHandler(std::promise<HRESULT> *promise);
512 static HRESULT InitThread()
514 std::lock_guard<std::mutex> _{sThreadLock};
516 if(!sThread.joinable())
518 std::promise<HRESULT> promise;
519 auto future = promise.get_future();
521 sThread = std::thread{&WasapiProxy::messageHandler, &promise};
533 static void DeinitThread()
535 std::lock_guard<std::mutex> _{sThreadLock};
536 if(!--sInitCount && sThread.joinable())
538 pushMessageStatic(MsgType::QuitThread);
543 std::thread WasapiProxy::sThread;
544 std::deque<WasapiProxy::Msg> WasapiProxy::mMsgQueue;
545 std::mutex WasapiProxy::mMsgQueueLock;
546 std::condition_variable WasapiProxy::mMsgQueueCond;
547 std::mutex WasapiProxy::sThreadLock;
548 size_t WasapiProxy::sInitCount{0};
550 int WasapiProxy::messageHandler(std::promise<HRESULT> *promise)
552 TRACE("Starting message thread\n");
554 HRESULT hr{CoInitializeEx(nullptr, COINIT_MULTITHREADED)};
557 WARN("Failed to initialize COM: 0x%08lx\n", hr);
558 promise->set_value(hr);
561 promise->set_value(S_OK);
564 TRACE("Starting message loop\n");
565 while(Msg msg{popMessage()})
567 TRACE("Got message \"%s\" (0x%04x, this=%p, param=%p)\n",
568 MessageStr[static_cast<size_t>(msg.mType)], static_cast<uint>(msg.mType),
569 static_cast<void*>(msg.mProxy), static_cast<const void*>(msg.mParam));
573 case MsgType::OpenDevice:
574 hr = msg.mProxy->openProxy(msg.mParam);
575 msg.mPromise.set_value(hr);
578 case MsgType::ResetDevice:
579 hr = msg.mProxy->resetProxy();
580 msg.mPromise.set_value(hr);
583 case MsgType::StartDevice:
584 hr = msg.mProxy->startProxy();
585 msg.mPromise.set_value(hr);
588 case MsgType::StopDevice:
589 msg.mProxy->stopProxy();
590 msg.mPromise.set_value(S_OK);
593 case MsgType::CloseDevice:
594 msg.mProxy->closeProxy();
595 msg.mPromise.set_value(S_OK);
598 case MsgType::EnumeratePlayback:
599 case MsgType::EnumerateCapture:
602 hr = CoCreateInstance(CLSID_MMDeviceEnumerator, nullptr, CLSCTX_INPROC_SERVER,
603 IID_IMMDeviceEnumerator, &ptr);
605 msg.mPromise.set_value(hr);
608 ComPtr<IMMDeviceEnumerator> devenum{static_cast<IMMDeviceEnumerator*>(ptr)};
610 if(msg.mType == MsgType::EnumeratePlayback)
611 probe_devices(devenum.get(), eRender, PlaybackDevices);
612 else if(msg.mType == MsgType::EnumerateCapture)
613 probe_devices(devenum.get(), eCapture, CaptureDevices);
614 msg.mPromise.set_value(S_OK);
619 case MsgType::QuitThread:
622 ERR("Unexpected message: %u\n", static_cast<uint>(msg.mType));
623 msg.mPromise.set_value(E_FAIL);
625 TRACE("Message loop finished\n");
632 struct WasapiPlayback final : public BackendBase, WasapiProxy {
633 WasapiPlayback(DeviceBase *device) noexcept : BackendBase{device} { }
634 ~WasapiPlayback() override;
638 void open(const char *name) override;
639 HRESULT openProxy(const char *name) override;
640 void closeProxy() override;
642 bool reset() override;
643 HRESULT resetProxy() override;
644 void start() override;
645 HRESULT startProxy() override;
646 void stop() override;
647 void stopProxy() override;
649 ClockLatency getClockLatency() override;
651 HRESULT mOpenStatus{E_FAIL};
652 ComPtr<IMMDevice> mMMDev{nullptr};
653 ComPtr<IAudioClient> mClient{nullptr};
654 ComPtr<IAudioRenderClient> mRender{nullptr};
655 HANDLE mNotifyEvent{nullptr};
657 UINT32 mOrigBufferSize{}, mOrigUpdateSize{};
658 std::unique_ptr<char[]> mResampleBuffer{};
659 uint mBufferFilled{0};
660 SampleConverterPtr mResampler;
662 WAVEFORMATEXTENSIBLE mFormat{};
663 std::atomic<UINT32> mPadding{0u};
667 std::atomic<bool> mKillNow{true};
670 DEF_NEWDEL(WasapiPlayback)
673 WasapiPlayback::~WasapiPlayback()
675 if(SUCCEEDED(mOpenStatus))
677 pushMessage(MsgType::CloseDevice).wait();
680 mOpenStatus = E_FAIL;
682 if(mNotifyEvent != nullptr)
683 CloseHandle(mNotifyEvent);
684 mNotifyEvent = nullptr;
688 FORCE_ALIGN int WasapiPlayback::mixerProc()
690 HRESULT hr{CoInitializeEx(nullptr, COINIT_MULTITHREADED)};
693 ERR("CoInitializeEx(nullptr, COINIT_MULTITHREADED) failed: 0x%08lx\n", hr);
694 mDevice->handleDisconnect("COM init failed: 0x%08lx", hr);
699 althrd_setname(MIXER_THREAD_NAME);
701 const uint frame_size{mFormat.Format.nChannels * mFormat.Format.wBitsPerSample / 8u};
702 const uint update_size{mOrigUpdateSize};
703 const UINT32 buffer_len{mOrigBufferSize};
704 while(!mKillNow.load(std::memory_order_relaxed))
707 hr = mClient->GetCurrentPadding(&written);
710 ERR("Failed to get padding: 0x%08lx\n", hr);
711 mDevice->handleDisconnect("Failed to retrieve buffer padding: 0x%08lx", hr);
714 mPadding.store(written, std::memory_order_relaxed);
716 uint len{buffer_len - written};
717 if(len < update_size)
719 DWORD res{WaitForSingleObjectEx(mNotifyEvent, 2000, FALSE)};
720 if(res != WAIT_OBJECT_0)
721 ERR("WaitForSingleObjectEx error: 0x%lx\n", res);
726 hr = mRender->GetBuffer(len, &buffer);
731 std::lock_guard<std::mutex> _{mMutex};
732 for(UINT32 done{0};done < len;)
734 if(mBufferFilled == 0)
736 mDevice->renderSamples(mResampleBuffer.get(), mDevice->UpdateSize,
737 mFormat.Format.nChannels);
738 mBufferFilled = mDevice->UpdateSize;
741 const void *src{mResampleBuffer.get()};
742 uint srclen{mBufferFilled};
743 uint got{mResampler->convert(&src, &srclen, buffer, len-done)};
744 buffer += got*frame_size;
747 mPadding.store(written + done, std::memory_order_relaxed);
750 const char *bsrc{static_cast<const char*>(src)};
751 std::copy(bsrc, bsrc + srclen*frame_size, mResampleBuffer.get());
753 mBufferFilled = srclen;
758 std::lock_guard<std::mutex> _{mMutex};
759 mDevice->renderSamples(buffer, len, mFormat.Format.nChannels);
760 mPadding.store(written + len, std::memory_order_relaxed);
762 hr = mRender->ReleaseBuffer(len, 0);
766 ERR("Failed to buffer data: 0x%08lx\n", hr);
767 mDevice->handleDisconnect("Failed to send playback samples: 0x%08lx", hr);
771 mPadding.store(0u, std::memory_order_release);
778 void WasapiPlayback::open(const char *name)
780 if(SUCCEEDED(mOpenStatus))
781 throw al::backend_exception{al::backend_error::DeviceError,
782 "Unexpected duplicate open call"};
784 mNotifyEvent = CreateEventW(nullptr, FALSE, FALSE, nullptr);
785 if(mNotifyEvent == nullptr)
787 ERR("Failed to create notify events: %lu\n", GetLastError());
788 throw al::backend_exception{al::backend_error::DeviceError,
789 "Failed to create notify events"};
792 HRESULT hr{InitThread()};
795 throw al::backend_exception{al::backend_error::DeviceError,
796 "Failed to init COM thread: 0x%08lx", hr};
801 if(PlaybackDevices.empty())
802 pushMessage(MsgType::EnumeratePlayback);
803 if(std::strncmp(name, DevNameHead, DevNameHeadLen) == 0)
805 name += DevNameHeadLen;
811 mOpenStatus = pushMessage(MsgType::OpenDevice, name).get();
812 if(FAILED(mOpenStatus))
815 throw al::backend_exception{al::backend_error::DeviceError, "Device init failed: 0x%08lx",
820 HRESULT WasapiPlayback::openProxy(const char *name)
822 const wchar_t *devid{nullptr};
825 auto iter = std::find_if(PlaybackDevices.cbegin(), PlaybackDevices.cend(),
826 [name](const DevMap &entry) -> bool
827 { return entry.name == name || entry.endpoint_guid == name; });
828 if(iter == PlaybackDevices.cend())
830 const std::wstring wname{utf8_to_wstr(name)};
831 iter = std::find_if(PlaybackDevices.cbegin(), PlaybackDevices.cend(),
832 [&wname](const DevMap &entry) -> bool
833 { return entry.devid == wname; });
835 if(iter == PlaybackDevices.cend())
837 WARN("Failed to find device name matching \"%s\"\n", name);
840 name = iter->name.c_str();
841 devid = iter->devid.c_str();
845 ComPtr<IMMDevice> mmdev;
846 HRESULT hr{CoCreateInstance(CLSID_MMDeviceEnumerator, nullptr, CLSCTX_INPROC_SERVER,
847 IID_IMMDeviceEnumerator, &ptr)};
850 ComPtr<IMMDeviceEnumerator> enumerator{static_cast<IMMDeviceEnumerator*>(ptr)};
852 hr = enumerator->GetDefaultAudioEndpoint(eRender, eMultimedia, mmdev.getPtr());
854 hr = enumerator->GetDevice(devid, mmdev.getPtr());
858 WARN("Failed to open device \"%s\"\n", name?name:"(default)");
863 mMMDev = std::move(mmdev);
864 if(name) mDevice->DeviceName = std::string{DevNameHead} + name;
865 else mDevice->DeviceName = DevNameHead + get_device_name_and_guid(mMMDev.get()).first;
870 void WasapiPlayback::closeProxy()
877 bool WasapiPlayback::reset()
879 HRESULT hr{pushMessage(MsgType::ResetDevice).get()};
881 throw al::backend_exception{al::backend_error::DeviceError, "0x%08lx", hr};
885 HRESULT WasapiPlayback::resetProxy()
890 HRESULT hr{mMMDev->Activate(IID_IAudioClient, CLSCTX_INPROC_SERVER, nullptr, &ptr)};
893 ERR("Failed to reactivate audio client: 0x%08lx\n", hr);
896 mClient = ComPtr<IAudioClient>{static_cast<IAudioClient*>(ptr)};
899 hr = mClient->GetMixFormat(&wfx);
902 ERR("Failed to get mix format: 0x%08lx\n", hr);
905 TraceFormat("Device mix format", wfx);
907 WAVEFORMATEXTENSIBLE OutputType;
908 if(!MakeExtensible(&OutputType, wfx))
916 const ReferenceTime per_time{ReferenceTime{seconds{mDevice->UpdateSize}} / mDevice->Frequency};
917 const ReferenceTime buf_time{ReferenceTime{seconds{mDevice->BufferSize}} / mDevice->Frequency};
918 bool isRear51{false};
920 if(!mDevice->Flags.test(FrequencyRequest))
921 mDevice->Frequency = OutputType.Format.nSamplesPerSec;
922 if(!mDevice->Flags.test(ChannelsRequest))
924 /* If not requesting a channel configuration, auto-select given what
925 * fits the mask's lsb (to ensure no gaps in the output channels). If
926 * there's no mask, we can only assume mono or stereo.
928 const uint32_t chancount{OutputType.Format.nChannels};
929 const DWORD chanmask{OutputType.dwChannelMask};
930 if(chancount >= 12 && (chanmask&X714Mask) == X7DOT1DOT4)
931 mDevice->FmtChans = DevFmtX71;
932 else if(chancount >= 8 && (chanmask&X71Mask) == X7DOT1)
933 mDevice->FmtChans = DevFmtX71;
934 else if(chancount >= 7 && (chanmask&X61Mask) == X6DOT1)
935 mDevice->FmtChans = DevFmtX61;
936 else if(chancount >= 6 && (chanmask&X51Mask) == X5DOT1)
937 mDevice->FmtChans = DevFmtX51;
938 else if(chancount >= 6 && (chanmask&X51RearMask) == X5DOT1REAR)
940 mDevice->FmtChans = DevFmtX51;
943 else if(chancount >= 4 && (chanmask&QuadMask) == QUAD)
944 mDevice->FmtChans = DevFmtQuad;
945 else if(chancount >= 2 && ((chanmask&StereoMask) == STEREO || !chanmask))
946 mDevice->FmtChans = DevFmtStereo;
947 else if(chancount >= 1 && ((chanmask&MonoMask) == MONO || !chanmask))
948 mDevice->FmtChans = DevFmtMono;
950 ERR("Unhandled channel config: %d -- 0x%08lx\n", chancount, chanmask);
954 const uint32_t chancount{OutputType.Format.nChannels};
955 const DWORD chanmask{OutputType.dwChannelMask};
956 isRear51 = (chancount == 6 && (chanmask&X51RearMask) == X5DOT1REAR);
959 OutputType.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
960 switch(mDevice->FmtChans)
963 OutputType.Format.nChannels = 1;
964 OutputType.dwChannelMask = MONO;
967 mDevice->FmtChans = DevFmtStereo;
970 OutputType.Format.nChannels = 2;
971 OutputType.dwChannelMask = STEREO;
974 OutputType.Format.nChannels = 4;
975 OutputType.dwChannelMask = QUAD;
978 OutputType.Format.nChannels = 6;
979 OutputType.dwChannelMask = isRear51 ? X5DOT1REAR : X5DOT1;
982 OutputType.Format.nChannels = 7;
983 OutputType.dwChannelMask = X6DOT1;
987 OutputType.Format.nChannels = 8;
988 OutputType.dwChannelMask = X7DOT1;
991 OutputType.Format.nChannels = 12;
992 OutputType.dwChannelMask = X7DOT1DOT4;
995 switch(mDevice->FmtType)
998 mDevice->FmtType = DevFmtUByte;
1001 OutputType.Format.wBitsPerSample = 8;
1002 OutputType.Samples.wValidBitsPerSample = 8;
1003 OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
1006 mDevice->FmtType = DevFmtShort;
1009 OutputType.Format.wBitsPerSample = 16;
1010 OutputType.Samples.wValidBitsPerSample = 16;
1011 OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
1014 mDevice->FmtType = DevFmtInt;
1017 OutputType.Format.wBitsPerSample = 32;
1018 OutputType.Samples.wValidBitsPerSample = 32;
1019 OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
1022 OutputType.Format.wBitsPerSample = 32;
1023 OutputType.Samples.wValidBitsPerSample = 32;
1024 OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT;
1027 OutputType.Format.nSamplesPerSec = mDevice->Frequency;
1029 OutputType.Format.nBlockAlign = static_cast<WORD>(OutputType.Format.nChannels *
1030 OutputType.Format.wBitsPerSample / 8);
1031 OutputType.Format.nAvgBytesPerSec = OutputType.Format.nSamplesPerSec *
1032 OutputType.Format.nBlockAlign;
1034 TraceFormat("Requesting playback format", &OutputType.Format);
1035 hr = mClient->IsFormatSupported(AUDCLNT_SHAREMODE_SHARED, &OutputType.Format, &wfx);
1038 WARN("Failed to check format support: 0x%08lx\n", hr);
1039 hr = mClient->GetMixFormat(&wfx);
1043 ERR("Failed to find a supported format: 0x%08lx\n", hr);
1049 TraceFormat("Got playback format", wfx);
1050 if(!MakeExtensible(&OutputType, wfx))
1058 if(!GetConfigValueBool(mDevice->DeviceName.c_str(), "wasapi", "allow-resampler", true))
1059 mDevice->Frequency = OutputType.Format.nSamplesPerSec;
1061 mDevice->Frequency = minu(mDevice->Frequency, OutputType.Format.nSamplesPerSec);
1063 const uint32_t chancount{OutputType.Format.nChannels};
1064 const DWORD chanmask{OutputType.dwChannelMask};
1065 /* Don't update the channel format if the requested format fits what's
1068 bool chansok{false};
1069 if(mDevice->Flags.test(ChannelsRequest))
1071 /* When requesting a channel configuration, make sure it fits the
1072 * mask's lsb (to ensure no gaps in the output channels). If
1073 * there's no mask, assume the request fits with enough channels.
1075 switch(mDevice->FmtChans)
1078 chansok = (chancount >= 1 && ((chanmask&MonoMask) == MONO || !chanmask));
1081 chansok = (chancount >= 2 && ((chanmask&StereoMask) == STEREO || !chanmask));
1084 chansok = (chancount >= 4 && ((chanmask&QuadMask) == QUAD || !chanmask));
1087 chansok = (chancount >= 6 && ((chanmask&X51Mask) == X5DOT1
1088 || (chanmask&X51RearMask) == X5DOT1REAR || !chanmask));
1091 chansok = (chancount >= 7 && ((chanmask&X61Mask) == X6DOT1 || !chanmask));
1095 chansok = (chancount >= 8 && ((chanmask&X71Mask) == X7DOT1 || !chanmask));
1098 chansok = (chancount >= 12 && ((chanmask&X714Mask) == X7DOT1DOT4 || !chanmask));
1105 if(chancount >= 12 && (chanmask&X714Mask) == X7DOT1DOT4)
1106 mDevice->FmtChans = DevFmtX714;
1107 else if(chancount >= 8 && (chanmask&X71Mask) == X7DOT1)
1108 mDevice->FmtChans = DevFmtX71;
1109 else if(chancount >= 7 && (chanmask&X61Mask) == X6DOT1)
1110 mDevice->FmtChans = DevFmtX61;
1111 else if(chancount >= 6 && ((chanmask&X51Mask) == X5DOT1
1112 || (chanmask&X51RearMask) == X5DOT1REAR))
1113 mDevice->FmtChans = DevFmtX51;
1114 else if(chancount >= 4 && (chanmask&QuadMask) == QUAD)
1115 mDevice->FmtChans = DevFmtQuad;
1116 else if(chancount >= 2 && ((chanmask&StereoMask) == STEREO || !chanmask))
1117 mDevice->FmtChans = DevFmtStereo;
1118 else if(chancount >= 1 && ((chanmask&MonoMask) == MONO || !chanmask))
1119 mDevice->FmtChans = DevFmtMono;
1122 ERR("Unhandled extensible channels: %d -- 0x%08lx\n", OutputType.Format.nChannels,
1123 OutputType.dwChannelMask);
1124 mDevice->FmtChans = DevFmtStereo;
1125 OutputType.Format.nChannels = 2;
1126 OutputType.dwChannelMask = STEREO;
1130 if(IsEqualGUID(OutputType.SubFormat, KSDATAFORMAT_SUBTYPE_PCM))
1132 if(OutputType.Format.wBitsPerSample == 8)
1133 mDevice->FmtType = DevFmtUByte;
1134 else if(OutputType.Format.wBitsPerSample == 16)
1135 mDevice->FmtType = DevFmtShort;
1136 else if(OutputType.Format.wBitsPerSample == 32)
1137 mDevice->FmtType = DevFmtInt;
1140 mDevice->FmtType = DevFmtShort;
1141 OutputType.Format.wBitsPerSample = 16;
1144 else if(IsEqualGUID(OutputType.SubFormat, KSDATAFORMAT_SUBTYPE_IEEE_FLOAT))
1146 mDevice->FmtType = DevFmtFloat;
1147 OutputType.Format.wBitsPerSample = 32;
1151 ERR("Unhandled format sub-type: %s\n", GuidPrinter{OutputType.SubFormat}.c_str());
1152 mDevice->FmtType = DevFmtShort;
1153 if(OutputType.Format.wFormatTag != WAVE_FORMAT_EXTENSIBLE)
1154 OutputType.Format.wFormatTag = WAVE_FORMAT_PCM;
1155 OutputType.Format.wBitsPerSample = 16;
1156 OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
1158 OutputType.Samples.wValidBitsPerSample = OutputType.Format.wBitsPerSample;
1160 mFormat = OutputType;
1162 const EndpointFormFactor formfactor{get_device_formfactor(mMMDev.get())};
1163 mDevice->Flags.set(DirectEar, (formfactor == Headphones || formfactor == Headset));
1165 setDefaultWFXChannelOrder();
1167 hr = mClient->Initialize(AUDCLNT_SHAREMODE_SHARED, AUDCLNT_STREAMFLAGS_EVENTCALLBACK,
1168 buf_time.count(), 0, &OutputType.Format, nullptr);
1171 ERR("Failed to initialize audio client: 0x%08lx\n", hr);
1175 UINT32 buffer_len{};
1176 ReferenceTime min_per{};
1177 hr = mClient->GetDevicePeriod(&reinterpret_cast<REFERENCE_TIME&>(min_per), nullptr);
1179 hr = mClient->GetBufferSize(&buffer_len);
1182 ERR("Failed to get audio buffer info: 0x%08lx\n", hr);
1186 /* Find the nearest multiple of the period size to the update size */
1187 if(min_per < per_time)
1188 min_per *= maxi64((per_time + min_per/2) / min_per, 1);
1190 mOrigBufferSize = buffer_len;
1191 mOrigUpdateSize = minu(RefTime2Samples(min_per, mFormat.Format.nSamplesPerSec), buffer_len/2);
1193 mDevice->BufferSize = static_cast<uint>(uint64_t{buffer_len} * mDevice->Frequency /
1194 mFormat.Format.nSamplesPerSec);
1195 mDevice->UpdateSize = minu(RefTime2Samples(min_per, mDevice->Frequency),
1196 mDevice->BufferSize/2);
1198 mResampler = nullptr;
1199 mResampleBuffer = nullptr;
1201 if(mDevice->Frequency != mFormat.Format.nSamplesPerSec)
1203 mResampler = SampleConverter::Create(mDevice->FmtType, mDevice->FmtType,
1204 mFormat.Format.nChannels, mDevice->Frequency, mFormat.Format.nSamplesPerSec,
1205 Resampler::FastBSinc24);
1206 mResampleBuffer = std::make_unique<char[]>(size_t{mDevice->UpdateSize} *
1207 mFormat.Format.nChannels * mFormat.Format.wBitsPerSample / 8);
1209 TRACE("Created converter for %s/%s format, dst: %luhz (%u), src: %uhz (%u)\n",
1210 DevFmtChannelsString(mDevice->FmtChans), DevFmtTypeString(mDevice->FmtType),
1211 mFormat.Format.nSamplesPerSec, mOrigUpdateSize, mDevice->Frequency,
1212 mDevice->UpdateSize);
1215 hr = mClient->SetEventHandle(mNotifyEvent);
1218 ERR("Failed to set event handle: 0x%08lx\n", hr);
1226 void WasapiPlayback::start()
1228 const HRESULT hr{pushMessage(MsgType::StartDevice).get()};
1230 throw al::backend_exception{al::backend_error::DeviceError,
1231 "Failed to start playback: 0x%lx", hr};
1234 HRESULT WasapiPlayback::startProxy()
1236 ResetEvent(mNotifyEvent);
1238 HRESULT hr{mClient->Start()};
1241 ERR("Failed to start audio client: 0x%08lx\n", hr);
1246 hr = mClient->GetService(IID_IAudioRenderClient, &ptr);
1249 mRender = ComPtr<IAudioRenderClient>{static_cast<IAudioRenderClient*>(ptr)};
1251 mKillNow.store(false, std::memory_order_release);
1252 mThread = std::thread{std::mem_fn(&WasapiPlayback::mixerProc), this};
1256 ERR("Failed to start thread\n");
1268 void WasapiPlayback::stop()
1269 { pushMessage(MsgType::StopDevice).wait(); }
1271 void WasapiPlayback::stopProxy()
1273 if(!mRender || !mThread.joinable())
1276 mKillNow.store(true, std::memory_order_release);
1284 ClockLatency WasapiPlayback::getClockLatency()
1288 std::lock_guard<std::mutex> _{mMutex};
1289 ret.ClockTime = GetDeviceClockTime(mDevice);
1290 ret.Latency = seconds{mPadding.load(std::memory_order_relaxed)};
1291 ret.Latency /= mFormat.Format.nSamplesPerSec;
1294 auto extra = mResampler->currentInputDelay();
1295 ret.Latency += std::chrono::duration_cast<nanoseconds>(extra) / mDevice->Frequency;
1296 ret.Latency += nanoseconds{seconds{mBufferFilled}} / mDevice->Frequency;
1303 struct WasapiCapture final : public BackendBase, WasapiProxy {
1304 WasapiCapture(DeviceBase *device) noexcept : BackendBase{device} { }
1305 ~WasapiCapture() override;
1309 void open(const char *name) override;
1310 HRESULT openProxy(const char *name) override;
1311 void closeProxy() override;
1313 HRESULT resetProxy() override;
1314 void start() override;
1315 HRESULT startProxy() override;
1316 void stop() override;
1317 void stopProxy() override;
1319 void captureSamples(al::byte *buffer, uint samples) override;
1320 uint availableSamples() override;
1322 HRESULT mOpenStatus{E_FAIL};
1323 ComPtr<IMMDevice> mMMDev{nullptr};
1324 ComPtr<IAudioClient> mClient{nullptr};
1325 ComPtr<IAudioCaptureClient> mCapture{nullptr};
1326 HANDLE mNotifyEvent{nullptr};
1328 ChannelConverter mChannelConv{};
1329 SampleConverterPtr mSampleConv;
1330 RingBufferPtr mRing;
1332 std::atomic<bool> mKillNow{true};
1333 std::thread mThread;
1335 DEF_NEWDEL(WasapiCapture)
1338 WasapiCapture::~WasapiCapture()
1340 if(SUCCEEDED(mOpenStatus))
1342 pushMessage(MsgType::CloseDevice).wait();
1345 mOpenStatus = E_FAIL;
1347 if(mNotifyEvent != nullptr)
1348 CloseHandle(mNotifyEvent);
1349 mNotifyEvent = nullptr;
1353 FORCE_ALIGN int WasapiCapture::recordProc()
1355 HRESULT hr{CoInitializeEx(nullptr, COINIT_MULTITHREADED)};
1358 ERR("CoInitializeEx(nullptr, COINIT_MULTITHREADED) failed: 0x%08lx\n", hr);
1359 mDevice->handleDisconnect("COM init failed: 0x%08lx", hr);
1363 althrd_setname(RECORD_THREAD_NAME);
1365 al::vector<float> samples;
1366 while(!mKillNow.load(std::memory_order_relaxed))
1369 hr = mCapture->GetNextPacketSize(&avail);
1371 ERR("Failed to get next packet size: 0x%08lx\n", hr);
1378 hr = mCapture->GetBuffer(&rdata, &numsamples, &flags, nullptr, nullptr);
1380 ERR("Failed to get capture buffer: 0x%08lx\n", hr);
1383 if(mChannelConv.is_active())
1385 samples.resize(numsamples*2);
1386 mChannelConv.convert(rdata, samples.data(), numsamples);
1387 rdata = reinterpret_cast<BYTE*>(samples.data());
1390 auto data = mRing->getWriteVector();
1395 const void *srcdata{rdata};
1396 uint srcframes{numsamples};
1398 dstframes = mSampleConv->convert(&srcdata, &srcframes, data.first.buf,
1399 static_cast<uint>(minz(data.first.len, INT_MAX)));
1400 if(srcframes > 0 && dstframes == data.first.len && data.second.len > 0)
1402 /* If some source samples remain, all of the first dest
1403 * block was filled, and there's space in the second
1404 * dest block, do another run for the second block.
1406 dstframes += mSampleConv->convert(&srcdata, &srcframes, data.second.buf,
1407 static_cast<uint>(minz(data.second.len, INT_MAX)));
1412 const uint framesize{mDevice->frameSizeFromFmt()};
1413 size_t len1{minz(data.first.len, numsamples)};
1414 size_t len2{minz(data.second.len, numsamples-len1)};
1416 memcpy(data.first.buf, rdata, len1*framesize);
1418 memcpy(data.second.buf, rdata+len1*framesize, len2*framesize);
1419 dstframes = len1 + len2;
1422 mRing->writeAdvance(dstframes);
1424 hr = mCapture->ReleaseBuffer(numsamples);
1425 if(FAILED(hr)) ERR("Failed to release capture buffer: 0x%08lx\n", hr);
1431 mDevice->handleDisconnect("Failed to capture samples: 0x%08lx", hr);
1435 DWORD res{WaitForSingleObjectEx(mNotifyEvent, 2000, FALSE)};
1436 if(res != WAIT_OBJECT_0)
1437 ERR("WaitForSingleObjectEx error: 0x%lx\n", res);
1445 void WasapiCapture::open(const char *name)
1447 if(SUCCEEDED(mOpenStatus))
1448 throw al::backend_exception{al::backend_error::DeviceError,
1449 "Unexpected duplicate open call"};
1451 mNotifyEvent = CreateEventW(nullptr, FALSE, FALSE, nullptr);
1452 if(mNotifyEvent == nullptr)
1454 ERR("Failed to create notify events: %lu\n", GetLastError());
1455 throw al::backend_exception{al::backend_error::DeviceError,
1456 "Failed to create notify events"};
1459 HRESULT hr{InitThread()};
1462 throw al::backend_exception{al::backend_error::DeviceError,
1463 "Failed to init COM thread: 0x%08lx", hr};
1468 if(CaptureDevices.empty())
1469 pushMessage(MsgType::EnumerateCapture);
1470 if(std::strncmp(name, DevNameHead, DevNameHeadLen) == 0)
1472 name += DevNameHeadLen;
1478 mOpenStatus = pushMessage(MsgType::OpenDevice, name).get();
1479 if(FAILED(mOpenStatus))
1482 throw al::backend_exception{al::backend_error::DeviceError, "Device init failed: 0x%08lx",
1486 hr = pushMessage(MsgType::ResetDevice).get();
1489 if(hr == E_OUTOFMEMORY)
1490 throw al::backend_exception{al::backend_error::OutOfMemory, "Out of memory"};
1491 throw al::backend_exception{al::backend_error::DeviceError, "Device reset failed"};
1495 HRESULT WasapiCapture::openProxy(const char *name)
1497 const wchar_t *devid{nullptr};
1500 auto iter = std::find_if(CaptureDevices.cbegin(), CaptureDevices.cend(),
1501 [name](const DevMap &entry) -> bool
1502 { return entry.name == name || entry.endpoint_guid == name; });
1503 if(iter == CaptureDevices.cend())
1505 const std::wstring wname{utf8_to_wstr(name)};
1506 iter = std::find_if(CaptureDevices.cbegin(), CaptureDevices.cend(),
1507 [&wname](const DevMap &entry) -> bool
1508 { return entry.devid == wname; });
1510 if(iter == CaptureDevices.cend())
1512 WARN("Failed to find device name matching \"%s\"\n", name);
1515 name = iter->name.c_str();
1516 devid = iter->devid.c_str();
1520 HRESULT hr{CoCreateInstance(CLSID_MMDeviceEnumerator, nullptr, CLSCTX_INPROC_SERVER,
1521 IID_IMMDeviceEnumerator, &ptr)};
1524 ComPtr<IMMDeviceEnumerator> enumerator{static_cast<IMMDeviceEnumerator*>(ptr)};
1526 hr = enumerator->GetDefaultAudioEndpoint(eCapture, eMultimedia, mMMDev.getPtr());
1528 hr = enumerator->GetDevice(devid, mMMDev.getPtr());
1532 WARN("Failed to open device \"%s\"\n", name?name:"(default)");
1537 if(name) mDevice->DeviceName = std::string{DevNameHead} + name;
1538 else mDevice->DeviceName = DevNameHead + get_device_name_and_guid(mMMDev.get()).first;
1543 void WasapiCapture::closeProxy()
1549 HRESULT WasapiCapture::resetProxy()
1554 HRESULT hr{mMMDev->Activate(IID_IAudioClient, CLSCTX_INPROC_SERVER, nullptr, &ptr)};
1557 ERR("Failed to reactivate audio client: 0x%08lx\n", hr);
1560 mClient = ComPtr<IAudioClient>{static_cast<IAudioClient*>(ptr)};
1563 hr = mClient->GetMixFormat(&wfx);
1566 ERR("Failed to get capture format: 0x%08lx\n", hr);
1569 TraceFormat("Device capture format", wfx);
1571 WAVEFORMATEXTENSIBLE InputType{};
1572 if(!MakeExtensible(&InputType, wfx))
1580 const bool isRear51{InputType.Format.nChannels == 6
1581 && (InputType.dwChannelMask&X51RearMask) == X5DOT1REAR};
1583 // Make sure buffer is at least 100ms in size
1584 ReferenceTime buf_time{ReferenceTime{seconds{mDevice->BufferSize}} / mDevice->Frequency};
1585 buf_time = std::max(buf_time, ReferenceTime{milliseconds{100}});
1588 InputType.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
1589 switch(mDevice->FmtChans)
1592 InputType.Format.nChannels = 1;
1593 InputType.dwChannelMask = MONO;
1596 InputType.Format.nChannels = 2;
1597 InputType.dwChannelMask = STEREO;
1600 InputType.Format.nChannels = 4;
1601 InputType.dwChannelMask = QUAD;
1604 InputType.Format.nChannels = 6;
1605 InputType.dwChannelMask = isRear51 ? X5DOT1REAR : X5DOT1;
1608 InputType.Format.nChannels = 7;
1609 InputType.dwChannelMask = X6DOT1;
1612 InputType.Format.nChannels = 8;
1613 InputType.dwChannelMask = X7DOT1;
1616 InputType.Format.nChannels = 12;
1617 InputType.dwChannelMask = X7DOT1DOT4;
1624 switch(mDevice->FmtType)
1626 /* NOTE: Signedness doesn't matter, the converter will handle it. */
1629 InputType.Format.wBitsPerSample = 8;
1630 InputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
1634 InputType.Format.wBitsPerSample = 16;
1635 InputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
1639 InputType.Format.wBitsPerSample = 32;
1640 InputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
1643 InputType.Format.wBitsPerSample = 32;
1644 InputType.SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT;
1647 InputType.Samples.wValidBitsPerSample = InputType.Format.wBitsPerSample;
1648 InputType.Format.nSamplesPerSec = mDevice->Frequency;
1650 InputType.Format.nBlockAlign = static_cast<WORD>(InputType.Format.nChannels *
1651 InputType.Format.wBitsPerSample / 8);
1652 InputType.Format.nAvgBytesPerSec = InputType.Format.nSamplesPerSec *
1653 InputType.Format.nBlockAlign;
1654 InputType.Format.cbSize = sizeof(InputType) - sizeof(InputType.Format);
1656 TraceFormat("Requesting capture format", &InputType.Format);
1657 hr = mClient->IsFormatSupported(AUDCLNT_SHAREMODE_SHARED, &InputType.Format, &wfx);
1660 WARN("Failed to check capture format support: 0x%08lx\n", hr);
1661 hr = mClient->GetMixFormat(&wfx);
1665 ERR("Failed to find a supported capture format: 0x%08lx\n", hr);
1669 mSampleConv = nullptr;
1674 TraceFormat("Got capture format", wfx);
1675 if(!MakeExtensible(&InputType, wfx))
1683 auto validate_fmt = [](DeviceBase *device, uint32_t chancount, DWORD chanmask) noexcept
1686 switch(device->FmtChans)
1688 /* If the device wants mono, we can handle any input. */
1691 /* If the device wants stereo, we can handle mono or stereo input. */
1693 return (chancount == 2 && (chanmask == 0 || (chanmask&StereoMask) == STEREO))
1694 || (chancount == 1 && (chanmask&MonoMask) == MONO);
1695 /* Otherwise, the device must match the input type. */
1697 return (chancount == 4 && (chanmask == 0 || (chanmask&QuadMask) == QUAD));
1698 /* 5.1 (Side) and 5.1 (Rear) are interchangeable here. */
1700 return (chancount == 6 && (chanmask == 0 || (chanmask&X51Mask) == X5DOT1
1701 || (chanmask&X51RearMask) == X5DOT1REAR));
1703 return (chancount == 7 && (chanmask == 0 || (chanmask&X61Mask) == X6DOT1));
1706 return (chancount == 8 && (chanmask == 0 || (chanmask&X71Mask) == X7DOT1));
1708 return (chancount == 12 && (chanmask == 0 || (chanmask&X714Mask) == X7DOT1DOT4));
1710 return (chanmask == 0 && chancount == device->channelsFromFmt());
1714 if(!validate_fmt(mDevice, InputType.Format.nChannels, InputType.dwChannelMask))
1716 ERR("Failed to match format, wanted: %s %s %uhz, got: 0x%08lx mask %d channel%s %d-bit %luhz\n",
1717 DevFmtChannelsString(mDevice->FmtChans), DevFmtTypeString(mDevice->FmtType),
1718 mDevice->Frequency, InputType.dwChannelMask, InputType.Format.nChannels,
1719 (InputType.Format.nChannels==1)?"":"s", InputType.Format.wBitsPerSample,
1720 InputType.Format.nSamplesPerSec);
1725 DevFmtType srcType{};
1726 if(IsEqualGUID(InputType.SubFormat, KSDATAFORMAT_SUBTYPE_PCM))
1728 if(InputType.Format.wBitsPerSample == 8)
1729 srcType = DevFmtUByte;
1730 else if(InputType.Format.wBitsPerSample == 16)
1731 srcType = DevFmtShort;
1732 else if(InputType.Format.wBitsPerSample == 32)
1733 srcType = DevFmtInt;
1736 ERR("Unhandled integer bit depth: %d\n", InputType.Format.wBitsPerSample);
1740 else if(IsEqualGUID(InputType.SubFormat, KSDATAFORMAT_SUBTYPE_IEEE_FLOAT))
1742 if(InputType.Format.wBitsPerSample == 32)
1743 srcType = DevFmtFloat;
1746 ERR("Unhandled float bit depth: %d\n", InputType.Format.wBitsPerSample);
1752 ERR("Unhandled format sub-type: %s\n", GuidPrinter{InputType.SubFormat}.c_str());
1756 if(mDevice->FmtChans == DevFmtMono && InputType.Format.nChannels != 1)
1758 uint chanmask{(1u<<InputType.Format.nChannels) - 1u};
1759 /* Exclude LFE from the downmix. */
1760 if((InputType.dwChannelMask&SPEAKER_LOW_FREQUENCY))
1762 constexpr auto lfemask = MaskFromTopBits(SPEAKER_LOW_FREQUENCY);
1763 const int lfeidx{al::popcount(InputType.dwChannelMask&lfemask) - 1};
1764 chanmask &= ~(1u << lfeidx);
1767 mChannelConv = ChannelConverter{srcType, InputType.Format.nChannels, chanmask,
1769 TRACE("Created %s multichannel-to-mono converter\n", DevFmtTypeString(srcType));
1770 /* The channel converter always outputs float, so change the input type
1771 * for the resampler/type-converter.
1773 srcType = DevFmtFloat;
1775 else if(mDevice->FmtChans == DevFmtStereo && InputType.Format.nChannels == 1)
1777 mChannelConv = ChannelConverter{srcType, 1, 0x1, mDevice->FmtChans};
1778 TRACE("Created %s mono-to-stereo converter\n", DevFmtTypeString(srcType));
1779 srcType = DevFmtFloat;
1782 if(mDevice->Frequency != InputType.Format.nSamplesPerSec || mDevice->FmtType != srcType)
1784 mSampleConv = SampleConverter::Create(srcType, mDevice->FmtType,
1785 mDevice->channelsFromFmt(), InputType.Format.nSamplesPerSec, mDevice->Frequency,
1786 Resampler::FastBSinc24);
1789 ERR("Failed to create converter for %s format, dst: %s %uhz, src: %s %luhz\n",
1790 DevFmtChannelsString(mDevice->FmtChans), DevFmtTypeString(mDevice->FmtType),
1791 mDevice->Frequency, DevFmtTypeString(srcType), InputType.Format.nSamplesPerSec);
1794 TRACE("Created converter for %s format, dst: %s %uhz, src: %s %luhz\n",
1795 DevFmtChannelsString(mDevice->FmtChans), DevFmtTypeString(mDevice->FmtType),
1796 mDevice->Frequency, DevFmtTypeString(srcType), InputType.Format.nSamplesPerSec);
1799 hr = mClient->Initialize(AUDCLNT_SHAREMODE_SHARED, AUDCLNT_STREAMFLAGS_EVENTCALLBACK,
1800 buf_time.count(), 0, &InputType.Format, nullptr);
1803 ERR("Failed to initialize audio client: 0x%08lx\n", hr);
1807 UINT32 buffer_len{};
1808 ReferenceTime min_per{};
1809 hr = mClient->GetDevicePeriod(&reinterpret_cast<REFERENCE_TIME&>(min_per), nullptr);
1811 hr = mClient->GetBufferSize(&buffer_len);
1814 ERR("Failed to get buffer size: 0x%08lx\n", hr);
1817 mDevice->UpdateSize = RefTime2Samples(min_per, mDevice->Frequency);
1818 mDevice->BufferSize = buffer_len;
1820 mRing = RingBuffer::Create(buffer_len, mDevice->frameSizeFromFmt(), false);
1822 hr = mClient->SetEventHandle(mNotifyEvent);
1825 ERR("Failed to set event handle: 0x%08lx\n", hr);
1833 void WasapiCapture::start()
1835 const HRESULT hr{pushMessage(MsgType::StartDevice).get()};
1837 throw al::backend_exception{al::backend_error::DeviceError,
1838 "Failed to start recording: 0x%lx", hr};
1841 HRESULT WasapiCapture::startProxy()
1843 ResetEvent(mNotifyEvent);
1845 HRESULT hr{mClient->Start()};
1848 ERR("Failed to start audio client: 0x%08lx\n", hr);
1853 hr = mClient->GetService(IID_IAudioCaptureClient, &ptr);
1856 mCapture = ComPtr<IAudioCaptureClient>{static_cast<IAudioCaptureClient*>(ptr)};
1858 mKillNow.store(false, std::memory_order_release);
1859 mThread = std::thread{std::mem_fn(&WasapiCapture::recordProc), this};
1863 ERR("Failed to start thread\n");
1878 void WasapiCapture::stop()
1879 { pushMessage(MsgType::StopDevice).wait(); }
1881 void WasapiCapture::stopProxy()
1883 if(!mCapture || !mThread.joinable())
1886 mKillNow.store(true, std::memory_order_release);
1895 void WasapiCapture::captureSamples(al::byte *buffer, uint samples)
1896 { mRing->read(buffer, samples); }
1898 uint WasapiCapture::availableSamples()
1899 { return static_cast<uint>(mRing->readSpace()); }
1904 bool WasapiBackendFactory::init()
1906 static HRESULT InitResult{E_FAIL};
1908 if(FAILED(InitResult)) try
1910 auto res = std::async(std::launch::async, []() -> HRESULT
1912 HRESULT hr{CoInitializeEx(nullptr, COINIT_MULTITHREADED)};
1915 WARN("Failed to initialize COM: 0x%08lx\n", hr);
1920 hr = CoCreateInstance(CLSID_MMDeviceEnumerator, nullptr, CLSCTX_INPROC_SERVER,
1921 IID_IMMDeviceEnumerator, &ptr);
1924 WARN("Failed to create IMMDeviceEnumerator instance: 0x%08lx\n", hr);
1928 static_cast<IMMDeviceEnumerator*>(ptr)->Release();
1934 InitResult = res.get();
1939 return SUCCEEDED(InitResult);
1942 bool WasapiBackendFactory::querySupport(BackendType type)
1943 { return type == BackendType::Playback || type == BackendType::Capture; }
1945 std::string WasapiBackendFactory::probe(BackendType type)
1947 struct ProxyControl {
1949 ProxyControl() { mResult = WasapiProxy::InitThread(); }
1950 ~ProxyControl() { if(SUCCEEDED(mResult)) WasapiProxy::DeinitThread(); }
1954 std::string outnames;
1955 if(FAILED(proxy.mResult))
1960 case BackendType::Playback:
1961 WasapiProxy::pushMessageStatic(MsgType::EnumeratePlayback).wait();
1962 for(const DevMap &entry : PlaybackDevices)
1964 /* +1 to also append the null char (to ensure a null-separated list
1965 * and double-null terminated list).
1967 outnames.append(DevNameHead).append(entry.name.c_str(), entry.name.length()+1);
1971 case BackendType::Capture:
1972 WasapiProxy::pushMessageStatic(MsgType::EnumerateCapture).wait();
1973 for(const DevMap &entry : CaptureDevices)
1974 outnames.append(DevNameHead).append(entry.name.c_str(), entry.name.length()+1);
1981 BackendPtr WasapiBackendFactory::createBackend(DeviceBase *device, BackendType type)
1983 if(type == BackendType::Playback)
1984 return BackendPtr{new WasapiPlayback{device}};
1985 if(type == BackendType::Capture)
1986 return BackendPtr{new WasapiCapture{device}};
1990 BackendFactory &WasapiBackendFactory::getFactory()
1992 static WasapiBackendFactory factory{};