Simplify retrieving the WASAPI device name
[openal-soft.git] / alc / backends / wasapi.cpp
blob1d4261564432b5b2cb737f431f6b0219a6d5b1cd
1 /**
2 * OpenAL cross platform audio library
3 * Copyright (C) 2011 by authors.
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Library General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) any later version.
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
21 #include "config.h"
23 #include "wasapi.h"
25 #define WIN32_LEAN_AND_MEAN
26 #include <windows.h>
28 #include <cstdio>
29 #include <cstdlib>
30 #include <memory.h>
32 #include <avrt.h>
33 #include <wtypes.h>
34 #include <mmdeviceapi.h>
35 #include <audiosessiontypes.h>
36 #include <audioclient.h>
37 #include <spatialaudioclient.h>
38 #include <cguid.h>
39 #include <devpropdef.h>
40 #include <mmreg.h>
41 #include <propsys.h>
42 #include <propkey.h>
43 #include <devpkey.h>
44 #ifndef _WAVEFORMATEXTENSIBLE_
45 #include <ks.h>
46 #include <ksmedia.h>
47 #endif
49 #include <algorithm>
50 #include <atomic>
51 #include <chrono>
52 #include <condition_variable>
53 #include <cstring>
54 #include <deque>
55 #include <functional>
56 #include <future>
57 #include <mutex>
58 #include <string>
59 #include <string_view>
60 #include <thread>
61 #include <vector>
63 #include "albit.h"
64 #include "alc/alconfig.h"
65 #include "alnumeric.h"
66 #include "alspan.h"
67 #include "alstring.h"
68 #include "althrd_setname.h"
69 #include "comptr.h"
70 #include "core/converter.h"
71 #include "core/device.h"
72 #include "core/helpers.h"
73 #include "core/logging.h"
74 #include "ringbuffer.h"
75 #include "strutils.h"
77 #if defined(ALSOFT_UWP)
79 #include <winrt/Windows.Media.Core.h> // !!This is important!!
80 #include <winrt/Windows.Foundation.Collections.h>
81 #include <winrt/Windows.Devices.h>
82 #include <winrt/Windows.Foundation.h>
83 #include <winrt/Windows.Devices.Enumeration.h>
84 #include <winrt/Windows.Media.Devices.h>
86 using namespace winrt;
87 using namespace Windows::Foundation;
88 using namespace Windows::Media::Devices;
89 using namespace Windows::Devices::Enumeration;
90 using namespace Windows::Media::Devices;
91 #endif
93 /* Some headers seem to define these as macros for __uuidof, which is annoying
94 * since some headers don't declare them at all. Hopefully the ifdef is enough
95 * to tell if they need to be declared.
97 #ifndef KSDATAFORMAT_SUBTYPE_PCM
98 DEFINE_GUID(KSDATAFORMAT_SUBTYPE_PCM, 0x00000001, 0x0000, 0x0010, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71);
99 #endif
100 #ifndef KSDATAFORMAT_SUBTYPE_IEEE_FLOAT
101 DEFINE_GUID(KSDATAFORMAT_SUBTYPE_IEEE_FLOAT, 0x00000003, 0x0000, 0x0010, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71);
102 #endif
103 #if !defined(ALSOFT_UWP)
104 DEFINE_DEVPROPKEY(DEVPKEY_Device_FriendlyName, 0xa45c254e, 0xdf1c, 0x4efd, 0x80,0x20, 0x67,0xd1,0x46,0xa8,0x50,0xe0, 14);
105 DEFINE_PROPERTYKEY(PKEY_AudioEndpoint_FormFactor, 0x1da5d803, 0xd492, 0x4edd, 0x8c,0x23, 0xe0,0xc0,0xff,0xee,0x7f,0x0e, 0);
106 DEFINE_PROPERTYKEY(PKEY_AudioEndpoint_GUID, 0x1da5d803, 0xd492, 0x4edd, 0x8c, 0x23,0xe0, 0xc0,0xff,0xee,0x7f,0x0e, 4 );
107 #endif
109 namespace {
111 using namespace std::string_view_literals;
112 using std::chrono::nanoseconds;
113 using std::chrono::milliseconds;
114 using std::chrono::seconds;
116 using ReferenceTime = std::chrono::duration<REFERENCE_TIME,std::ratio<1,10'000'000>>;
119 #define MONO SPEAKER_FRONT_CENTER
120 #define STEREO (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT)
121 #define QUAD (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT|SPEAKER_BACK_LEFT|SPEAKER_BACK_RIGHT)
122 #define X5DOT1 (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT|SPEAKER_FRONT_CENTER|SPEAKER_LOW_FREQUENCY|SPEAKER_SIDE_LEFT|SPEAKER_SIDE_RIGHT)
123 #define X5DOT1REAR (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT|SPEAKER_FRONT_CENTER|SPEAKER_LOW_FREQUENCY|SPEAKER_BACK_LEFT|SPEAKER_BACK_RIGHT)
124 #define X6DOT1 (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT|SPEAKER_FRONT_CENTER|SPEAKER_LOW_FREQUENCY|SPEAKER_BACK_CENTER|SPEAKER_SIDE_LEFT|SPEAKER_SIDE_RIGHT)
125 #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)
126 #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)
128 constexpr inline DWORD MaskFromTopBits(DWORD b) noexcept
130 b |= b>>1;
131 b |= b>>2;
132 b |= b>>4;
133 b |= b>>8;
134 b |= b>>16;
135 return b;
137 constexpr DWORD MonoMask{MaskFromTopBits(MONO)};
138 constexpr DWORD StereoMask{MaskFromTopBits(STEREO)};
139 constexpr DWORD QuadMask{MaskFromTopBits(QUAD)};
140 constexpr DWORD X51Mask{MaskFromTopBits(X5DOT1)};
141 constexpr DWORD X51RearMask{MaskFromTopBits(X5DOT1REAR)};
142 constexpr DWORD X61Mask{MaskFromTopBits(X6DOT1)};
143 constexpr DWORD X71Mask{MaskFromTopBits(X7DOT1)};
144 constexpr DWORD X714Mask{MaskFromTopBits(X7DOT1DOT4)};
147 #ifndef _MSC_VER
148 constexpr AudioObjectType operator|(AudioObjectType lhs, AudioObjectType rhs) noexcept
149 { return static_cast<AudioObjectType>(lhs | al::to_underlying(rhs)); }
150 #endif
152 constexpr AudioObjectType ChannelMask_Mono{AudioObjectType_FrontCenter};
153 constexpr AudioObjectType ChannelMask_Stereo{AudioObjectType_FrontLeft
154 | AudioObjectType_FrontRight};
155 constexpr AudioObjectType ChannelMask_Quad{AudioObjectType_FrontLeft | AudioObjectType_FrontRight
156 | AudioObjectType_BackLeft | AudioObjectType_BackRight};
157 constexpr AudioObjectType ChannelMask_X51{AudioObjectType_FrontLeft | AudioObjectType_FrontRight
158 | AudioObjectType_FrontCenter | AudioObjectType_LowFrequency | AudioObjectType_SideLeft
159 | AudioObjectType_SideRight};
160 constexpr AudioObjectType ChannelMask_X61{AudioObjectType_FrontLeft | AudioObjectType_FrontRight
161 | AudioObjectType_FrontCenter | AudioObjectType_LowFrequency | AudioObjectType_SideLeft
162 | AudioObjectType_SideRight | AudioObjectType_BackCenter};
163 constexpr AudioObjectType ChannelMask_X71{AudioObjectType_FrontLeft | AudioObjectType_FrontRight
164 | AudioObjectType_FrontCenter | AudioObjectType_LowFrequency | AudioObjectType_SideLeft
165 | AudioObjectType_SideRight | AudioObjectType_BackLeft | AudioObjectType_BackRight};
166 constexpr AudioObjectType ChannelMask_X714{AudioObjectType_FrontLeft | AudioObjectType_FrontRight
167 | AudioObjectType_FrontCenter | AudioObjectType_LowFrequency | AudioObjectType_SideLeft
168 | AudioObjectType_SideRight | AudioObjectType_BackLeft | AudioObjectType_BackRight
169 | AudioObjectType_TopFrontLeft | AudioObjectType_TopFrontRight | AudioObjectType_TopBackLeft
170 | AudioObjectType_TopBackRight};
171 constexpr AudioObjectType ChannelMask_X7144{AudioObjectType_FrontLeft | AudioObjectType_FrontRight
172 | AudioObjectType_FrontCenter | AudioObjectType_LowFrequency | AudioObjectType_SideLeft
173 | AudioObjectType_SideRight | AudioObjectType_BackLeft | AudioObjectType_BackRight
174 | AudioObjectType_TopFrontLeft | AudioObjectType_TopFrontRight | AudioObjectType_TopBackLeft
175 | AudioObjectType_TopBackRight | AudioObjectType_BottomFrontLeft
176 | AudioObjectType_BottomFrontRight | AudioObjectType_BottomBackLeft
177 | AudioObjectType_BottomBackRight};
180 template<typename... Ts>
181 struct overloaded : Ts... { using Ts::operator()...; };
183 template<typename... Ts>
184 overloaded(Ts...) -> overloaded<Ts...>;
187 template<typename T>
188 constexpr auto as_unsigned(T value) noexcept
190 using UT = std::make_unsigned_t<T>;
191 return static_cast<UT>(value);
195 /* Scales the given reftime value, rounding the result. */
196 template<typename T>
197 constexpr uint RefTime2Samples(const ReferenceTime &val, T srate) noexcept
199 const auto retval = (val*srate + ReferenceTime{seconds{1}}/2) / seconds{1};
200 return static_cast<uint>(std::min<decltype(retval)>(retval, std::numeric_limits<uint>::max()));
204 class GuidPrinter {
205 std::array<char,64> mMsg{};
207 public:
208 GuidPrinter(const GUID &guid)
210 std::snprintf(mMsg.data(), mMsg.size(), "{%08lx-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x}",
211 DWORD{guid.Data1}, guid.Data2, guid.Data3, guid.Data4[0], guid.Data4[1], guid.Data4[2],
212 guid.Data4[3], guid.Data4[4], guid.Data4[5], guid.Data4[6], guid.Data4[7]);
214 [[nodiscard]] auto c_str() const -> const char* { return mMsg.data(); }
217 struct PropVariant {
218 PROPVARIANT mProp{};
220 public:
221 PropVariant() { PropVariantInit(&mProp); }
222 PropVariant(const PropVariant &rhs) : PropVariant{} { PropVariantCopy(&mProp, &rhs.mProp); }
223 ~PropVariant() { clear(); }
225 auto operator=(const PropVariant &rhs) -> PropVariant&
227 if(this != &rhs)
228 PropVariantCopy(&mProp, &rhs.mProp);
229 return *this;
232 void clear() { PropVariantClear(&mProp); }
234 PROPVARIANT* get() noexcept { return &mProp; }
236 /* NOLINTBEGIN(cppcoreguidelines-pro-type-union-access) */
237 [[nodiscard]]
238 auto type() const noexcept -> VARTYPE { return mProp.vt; }
240 template<typename T> [[nodiscard]]
241 auto value() const -> T
243 if constexpr(std::is_same_v<T,uint>)
245 alassert(mProp.vt == VT_UI4 || mProp.vt == VT_UINT);
246 return mProp.uintVal;
248 else if constexpr(std::is_same_v<T,std::wstring_view> || std::is_same_v<T,std::wstring>
249 || std::is_same_v<T,LPWSTR> || std::is_same_v<T,LPCWSTR>)
251 alassert(mProp.vt == VT_LPWSTR);
252 return mProp.pwszVal;
256 void setBlob(const al::span<BYTE> data)
258 if constexpr(sizeof(size_t) > sizeof(ULONG))
259 alassert(data.size() <= std::numeric_limits<ULONG>::max());
260 mProp.vt = VT_BLOB;
261 mProp.blob.cbSize = static_cast<ULONG>(data.size());
262 mProp.blob.pBlobData = data.data();
264 /* NOLINTEND(cppcoreguidelines-pro-type-union-access) */
267 struct DevMap {
268 std::string name;
269 std::string endpoint_guid; // obtained from PKEY_AudioEndpoint_GUID , set to "Unknown device GUID" if absent.
270 std::wstring devid;
272 template<typename T0, typename T1, typename T2>
273 DevMap(T0&& name_, T1&& guid_, T2&& devid_)
274 : name{std::forward<T0>(name_)}
275 , endpoint_guid{std::forward<T1>(guid_)}
276 , devid{std::forward<T2>(devid_)}
278 /* To prevent GCC from complaining it doesn't want to inline this. */
279 ~DevMap();
281 DevMap::~DevMap() = default;
283 bool checkName(const al::span<DevMap> list, const std::string_view name)
285 auto match_name = [name](const DevMap &entry) -> bool { return entry.name == name; };
286 return std::find_if(list.cbegin(), list.cend(), match_name) != list.cend();
290 struct DeviceList {
291 auto lock() noexcept(noexcept(mMutex.lock())) { return mMutex.lock(); }
292 auto unlock() noexcept(noexcept(mMutex.unlock())) { return mMutex.unlock(); }
294 private:
295 std::mutex mMutex;
296 std::vector<DevMap> mPlayback;
297 std::vector<DevMap> mCapture;
298 std::wstring mPlaybackDefaultId;
299 std::wstring mCaptureDefaultId;
301 friend struct DeviceListLock;
303 struct DeviceListLock : public std::unique_lock<DeviceList> {
304 using std::unique_lock<DeviceList>::unique_lock;
306 [[nodiscard]] auto& getPlaybackList() const noexcept { return mutex()->mPlayback; }
307 [[nodiscard]] auto& getCaptureList() const noexcept { return mutex()->mCapture; }
309 void setPlaybackDefaultId(std::wstring_view devid) const { mutex()->mPlaybackDefaultId = devid; }
310 [[nodiscard]] auto getPlaybackDefaultId() const noexcept -> std::wstring_view { return mutex()->mPlaybackDefaultId; }
311 void setCaptureDefaultId(std::wstring_view devid) const { mutex()->mCaptureDefaultId = devid; }
312 [[nodiscard]] auto getCaptureDefaultId() const noexcept -> std::wstring_view { return mutex()->mCaptureDefaultId; }
315 DeviceList gDeviceList;
318 #ifdef AVRTAPI
319 struct AvrtHandleCloser {
320 void operator()(HANDLE handle) { AvRevertMmThreadCharacteristics(handle); }
322 using AvrtHandlePtr = std::unique_ptr<std::remove_pointer_t<HANDLE>,AvrtHandleCloser>;
323 #endif
325 #if defined(ALSOFT_UWP)
326 enum EDataFlow {
327 eRender = 0,
328 eCapture = (eRender + 1),
329 eAll = (eCapture + 1),
330 EDataFlow_enum_count = (eAll + 1)
332 #endif
334 #if defined(ALSOFT_UWP)
335 using DeviceHandle = Windows::Devices::Enumeration::DeviceInformation;
336 using EventRegistrationToken = winrt::event_token;
337 #else
338 using DeviceHandle = ComPtr<IMMDevice>;
339 #endif
342 struct NameGUIDPair { std::string mName; std::string mGuid; };
343 auto GetDeviceNameAndGuid(const DeviceHandle &device) -> NameGUIDPair
345 constexpr auto UnknownName = "Unknown Device Name"sv;
346 constexpr auto UnknownGuid = "Unknown Device GUID"sv;
348 #if !defined(ALSOFT_UWP)
349 auto ps = ComPtr<IPropertyStore>{};
350 auto hr = device->OpenPropertyStore(STGM_READ, al::out_ptr(ps));
351 if(FAILED(hr))
353 WARN("OpenPropertyStore failed: 0x%08lx\n", hr);
354 return NameGUIDPair{std::string{UnknownName}, std::string{UnknownGuid}};
357 auto ret = NameGUIDPair{};
358 auto pvprop = PropVariant{};
359 hr = ps->GetValue(al::bit_cast<PROPERTYKEY>(DEVPKEY_Device_FriendlyName), pvprop.get());
360 if(FAILED(hr))
361 WARN("GetValue Device_FriendlyName failed: 0x%08lx\n", hr);
362 else if(pvprop.type() == VT_LPWSTR)
363 ret.mName = wstr_to_utf8(pvprop.value<std::wstring_view>());
364 else
365 WARN("Unexpected Device_FriendlyName PROPVARIANT type: 0x%04x\n", pvprop.type());
367 pvprop.clear();
368 hr = ps->GetValue(al::bit_cast<PROPERTYKEY>(PKEY_AudioEndpoint_GUID), pvprop.get());
369 if(FAILED(hr))
370 WARN("GetValue AudioEndpoint_GUID failed: 0x%08lx\n", hr);
371 else if(pvprop.type() == VT_LPWSTR)
372 ret.mGuid = wstr_to_utf8(pvprop.value<std::wstring_view>());
373 else
374 WARN("Unexpected AudioEndpoint_GUID PROPVARIANT type: 0x%04x\n", pvprop.type());
375 #else
376 auto ret = NameGUIDPair{wstr_to_utf8(device.Name()), {}};
378 // device->Id is DeviceInterfacePath: \\?\SWD#MMDEVAPI#{0.0.0.00000000}.{a21c17a0-fc1d-405e-ab5a-b513422b57d1}#{e6327cad-dcec-4949-ae8a-991e976a79d2}
379 auto devIfPath = device.Id();
380 if(auto devIdStart = wcsstr(devIfPath.data(), L"}."))
382 devIdStart += 2; // L"}."
383 if(auto devIdStartEnd = wcschr(devIdStart, L'#'))
385 ret.mGuid = wstr_to_utf8(std::wstring_view{devIdStart, devIdStartEnd});
386 std::transform(ret.mGuid.begin(), ret.mGuid.end(), ret.mGuid.begin(),
387 [](char ch) { return static_cast<char>(std::toupper(ch)); });
390 #endif
391 if(ret.mName.empty()) ret.mName = UnknownName;
392 if(ret.mGuid.empty()) ret.mGuid = UnknownGuid;
393 return ret;
395 #if !defined(ALSOFT_UWP)
396 EndpointFormFactor GetDeviceFormfactor(IMMDevice *device)
398 ComPtr<IPropertyStore> ps;
399 HRESULT hr{device->OpenPropertyStore(STGM_READ, al::out_ptr(ps))};
400 if(FAILED(hr))
402 WARN("OpenPropertyStore failed: 0x%08lx\n", hr);
403 return UnknownFormFactor;
406 EndpointFormFactor formfactor{UnknownFormFactor};
407 PropVariant pvform;
408 hr = ps->GetValue(PKEY_AudioEndpoint_FormFactor, pvform.get());
409 if(FAILED(hr))
410 WARN("GetValue AudioEndpoint_FormFactor failed: 0x%08lx\n", hr);
411 else if(pvform.type() == VT_UI4)
412 formfactor = static_cast<EndpointFormFactor>(pvform.value<uint>());
413 else if(pvform.type() != VT_EMPTY)
414 WARN("Unexpected PROPVARIANT type: 0x%04x\n", pvform.type());
415 return formfactor;
417 #endif
420 #if defined(ALSOFT_UWP)
421 struct DeviceHelper final : public IActivateAudioInterfaceCompletionHandler
422 #else
423 struct DeviceHelper final : private IMMNotificationClient
424 #endif
426 #if defined(ALSOFT_UWP)
427 DeviceHelper()
429 /* TODO: UWP also needs to watch for device added/removed events and
430 * dynamically add/remove devices from the lists.
432 mActiveClientEvent = CreateEventW(nullptr, FALSE, FALSE, nullptr);
434 mRenderDeviceChangedToken = MediaDevice::DefaultAudioRenderDeviceChanged([this](const IInspectable& /*sender*/, const DefaultAudioRenderDeviceChangedEventArgs& args) {
435 if (args.Role() == AudioDeviceRole::Default)
437 const std::string msg{ "Default playback device changed: " +
438 wstr_to_utf8(args.Id())};
439 alc::Event(alc::EventType::DefaultDeviceChanged, alc::DeviceType::Playback,
440 msg);
444 mCaptureDeviceChangedToken = MediaDevice::DefaultAudioCaptureDeviceChanged([this](const IInspectable& /*sender*/, const DefaultAudioCaptureDeviceChangedEventArgs& args) {
445 if (args.Role() == AudioDeviceRole::Default)
447 const std::string msg{ "Default capture device changed: " +
448 wstr_to_utf8(args.Id()) };
449 alc::Event(alc::EventType::DefaultDeviceChanged, alc::DeviceType::Capture,
450 msg);
454 #else
455 DeviceHelper() = default;
456 #endif
457 ~DeviceHelper()
459 #if defined(ALSOFT_UWP)
460 MediaDevice::DefaultAudioRenderDeviceChanged(mRenderDeviceChangedToken);
461 MediaDevice::DefaultAudioCaptureDeviceChanged(mCaptureDeviceChangedToken);
463 if(mActiveClientEvent != nullptr)
464 CloseHandle(mActiveClientEvent);
465 mActiveClientEvent = nullptr;
466 #else
467 if(mEnumerator)
468 mEnumerator->UnregisterEndpointNotificationCallback(this);
469 mEnumerator = nullptr;
470 #endif
473 template<typename T>
474 auto as() noexcept -> T { return T{this}; }
476 /** -------------------------- IUnknown ----------------------------- */
477 std::atomic<ULONG> mRefCount{1};
478 STDMETHODIMP_(ULONG) AddRef() noexcept override { return mRefCount.fetch_add(1u) + 1u; }
479 STDMETHODIMP_(ULONG) Release() noexcept override { return mRefCount.fetch_sub(1u) - 1u; }
481 STDMETHODIMP QueryInterface(const IID& IId, void **UnknownPtrPtr) noexcept override
483 // Three rules of QueryInterface:
484 // https://docs.microsoft.com/en-us/windows/win32/com/rules-for-implementing-queryinterface
485 // 1. Objects must have identity.
486 // 2. The set of interfaces on an object instance must be static.
487 // 3. It must be possible to query successfully for any interface on an object from any other interface.
489 // If ppvObject(the address) is nullptr, then this method returns E_POINTER.
490 if(!UnknownPtrPtr)
491 return E_POINTER;
493 // https://docs.microsoft.com/en-us/windows/win32/com/implementing-reference-counting
494 // Whenever a client calls a method(or API function), such as QueryInterface, that returns a new interface
495 // pointer, the method being called is responsible for incrementing the reference count through the returned
496 // pointer. For example, when a client first creates an object, it receives an interface pointer to an object
497 // that, from the client's point of view, has a reference count of one. If the client then calls AddRef on the
498 // interface pointer, the reference count becomes two. The client must call Release twice on the interface
499 // pointer to drop all of its references to the object.
500 #if defined(ALSOFT_UWP)
501 if(IId == __uuidof(IActivateAudioInterfaceCompletionHandler))
503 *UnknownPtrPtr = as<IActivateAudioInterfaceCompletionHandler*>();
504 AddRef();
505 return S_OK;
507 #else
508 if(IId == __uuidof(IMMNotificationClient))
510 *UnknownPtrPtr = as<IMMNotificationClient*>();
511 AddRef();
512 return S_OK;
514 #endif
515 else if(IId == __uuidof(IAgileObject) || IId == __uuidof(IUnknown))
517 *UnknownPtrPtr = as<IUnknown*>();
518 AddRef();
519 return S_OK;
522 // This method returns S_OK if the interface is supported, and E_NOINTERFACE otherwise.
523 *UnknownPtrPtr = nullptr;
524 return E_NOINTERFACE;
527 #if defined(ALSOFT_UWP)
528 /** ----------------------- IActivateAudioInterfaceCompletionHandler ------------ */
529 HRESULT ActivateCompleted(IActivateAudioInterfaceAsyncOperation*) override
531 SetEvent(mActiveClientEvent);
533 // Need to return S_OK
534 return S_OK;
536 #else
537 /** ----------------------- IMMNotificationClient ------------ */
538 STDMETHODIMP OnDeviceStateChanged(LPCWSTR /*pwstrDeviceId*/, DWORD /*dwNewState*/) noexcept override { return S_OK; }
540 STDMETHODIMP OnDeviceAdded(LPCWSTR pwstrDeviceId) noexcept override
542 ComPtr<IMMDevice> device;
543 HRESULT hr{mEnumerator->GetDevice(pwstrDeviceId, al::out_ptr(device))};
544 if(FAILED(hr))
546 ERR("Failed to get device: 0x%08lx\n", hr);
547 return S_OK;
550 ComPtr<IMMEndpoint> endpoint;
551 hr = device->QueryInterface(__uuidof(IMMEndpoint), al::out_ptr(endpoint));
552 if(FAILED(hr))
554 ERR("Failed to get device endpoint: 0x%08lx\n", hr);
555 return S_OK;
558 EDataFlow flowdir{};
559 hr = endpoint->GetDataFlow(&flowdir);
560 if(FAILED(hr))
562 ERR("Failed to get endpoint data flow: 0x%08lx\n", hr);
563 return S_OK;
566 auto devlock = DeviceListLock{gDeviceList};
567 auto &list = (flowdir==eRender) ? devlock.getPlaybackList() : devlock.getCaptureList();
569 if(AddDevice(device, pwstrDeviceId, list))
571 const auto devtype = (flowdir==eRender) ? alc::DeviceType::Playback
572 : alc::DeviceType::Capture;
573 const std::string msg{"Device added: "+list.back().name};
574 alc::Event(alc::EventType::DeviceAdded, devtype, msg);
577 return S_OK;
580 STDMETHODIMP OnDeviceRemoved(LPCWSTR pwstrDeviceId) noexcept override
582 auto devlock = DeviceListLock{gDeviceList};
583 for(auto flowdir : std::array{eRender, eCapture})
585 auto &list = (flowdir==eRender) ? devlock.getPlaybackList() : devlock.getCaptureList();
586 auto devtype = (flowdir==eRender)?alc::DeviceType::Playback : alc::DeviceType::Capture;
588 /* Find the ID in the list to remove. */
589 auto iter = std::find_if(list.begin(), list.end(),
590 [pwstrDeviceId](const DevMap &entry) noexcept
591 { return pwstrDeviceId == entry.devid; });
592 if(iter == list.end()) continue;
594 TRACE("Removing device \"%s\", \"%s\", \"%ls\"\n", iter->name.c_str(),
595 iter->endpoint_guid.c_str(), iter->devid.c_str());
597 std::string msg{"Device removed: "+std::move(iter->name)};
598 list.erase(iter);
600 alc::Event(alc::EventType::DeviceRemoved, devtype, msg);
602 return S_OK;
605 /* NOLINTNEXTLINE(clazy-function-args-by-ref) */
606 STDMETHODIMP OnPropertyValueChanged(LPCWSTR /*pwstrDeviceId*/, const PROPERTYKEY /*key*/) noexcept override { return S_OK; }
608 STDMETHODIMP OnDefaultDeviceChanged(EDataFlow flow, ERole role, LPCWSTR pwstrDefaultDeviceId) noexcept override
610 if(role != eMultimedia)
611 return S_OK;
613 const std::wstring_view devid{pwstrDefaultDeviceId ? pwstrDefaultDeviceId
614 : std::wstring_view{}};
615 if(flow == eRender)
617 DeviceListLock{gDeviceList}.setPlaybackDefaultId(devid);
618 const std::string msg{"Default playback device changed: " + wstr_to_utf8(devid)};
619 alc::Event(alc::EventType::DefaultDeviceChanged, alc::DeviceType::Playback, msg);
621 else if(flow == eCapture)
623 DeviceListLock{gDeviceList}.setCaptureDefaultId(devid);
624 const std::string msg{"Default capture device changed: " + wstr_to_utf8(devid)};
625 alc::Event(alc::EventType::DefaultDeviceChanged, alc::DeviceType::Capture, msg);
627 return S_OK;
629 #endif
631 /** -------------------------- DeviceHelper ----------------------------- */
632 HRESULT init()
634 #if !defined(ALSOFT_UWP)
635 HRESULT hr{CoCreateInstance(CLSID_MMDeviceEnumerator, nullptr, CLSCTX_INPROC_SERVER,
636 __uuidof(IMMDeviceEnumerator), al::out_ptr(mEnumerator))};
637 if(SUCCEEDED(hr))
638 mEnumerator->RegisterEndpointNotificationCallback(this);
639 else
640 WARN("Failed to create IMMDeviceEnumerator instance: 0x%08lx\n", hr);
641 return hr;
642 #else
643 return S_OK;
644 #endif
647 HRESULT openDevice(std::wstring_view devid, EDataFlow flow, DeviceHandle& device)
649 #if !defined(ALSOFT_UWP)
650 HRESULT hr{E_FAIL};
651 if(mEnumerator)
653 if(devid.empty())
654 hr = mEnumerator->GetDefaultAudioEndpoint(flow, eMultimedia, al::out_ptr(device));
655 else
656 hr = mEnumerator->GetDevice(devid.data(), al::out_ptr(device));
658 return hr;
659 #else
660 const auto deviceRole = Windows::Media::Devices::AudioDeviceRole::Default;
661 auto devIfPath =
662 devid.empty() ? (flow == eRender ? MediaDevice::GetDefaultAudioRenderId(deviceRole) : MediaDevice::GetDefaultAudioCaptureId(deviceRole))
663 : winrt::hstring(devid.data());
664 if (devIfPath.empty())
665 return E_POINTER;
667 auto&& deviceInfo = DeviceInformation::CreateFromIdAsync(devIfPath, nullptr, DeviceInformationKind::DeviceInterface).get();
668 if (!deviceInfo)
669 return E_NOINTERFACE;
670 device = deviceInfo;
671 return S_OK;
672 #endif
675 #if !defined(ALSOFT_UWP)
676 static HRESULT activateAudioClient(_In_ DeviceHandle &device, REFIID iid, void **ppv)
677 { return device->Activate(iid, CLSCTX_INPROC_SERVER, nullptr, ppv); }
678 #else
679 HRESULT activateAudioClient(_In_ DeviceHandle &device, _In_ REFIID iid, void **ppv)
681 ComPtr<IActivateAudioInterfaceAsyncOperation> asyncOp;
682 HRESULT hr{ActivateAudioInterfaceAsync(device.Id().data(), iid, nullptr, this,
683 al::out_ptr(asyncOp))};
684 if(FAILED(hr))
685 return hr;
687 /* I don't like waiting for INFINITE time, but the activate operation
688 * can take an indefinite amount of time since it can require user
689 * input.
691 DWORD res{WaitForSingleObjectEx(mActiveClientEvent, INFINITE, FALSE)};
692 if(res != WAIT_OBJECT_0)
694 ERR("WaitForSingleObjectEx error: 0x%lx\n", res);
695 return E_FAIL;
698 HRESULT hrActivateRes{E_FAIL};
699 ComPtr<IUnknown> punkAudioIface;
700 hr = asyncOp->GetActivateResult(&hrActivateRes, al::out_ptr(punkAudioIface));
701 if(SUCCEEDED(hr)) hr = hrActivateRes;
702 if(FAILED(hr)) return hr;
704 return punkAudioIface->QueryInterface(iid, ppv);
706 #endif
708 std::wstring probeDevices(EDataFlow flowdir, std::vector<DevMap> &list)
710 std::wstring defaultId;
711 std::vector<DevMap>{}.swap(list);
713 #if !defined(ALSOFT_UWP)
714 ComPtr<IMMDeviceCollection> coll;
715 HRESULT hr{mEnumerator->EnumAudioEndpoints(flowdir, DEVICE_STATE_ACTIVE,
716 al::out_ptr(coll))};
717 if(FAILED(hr))
719 ERR("Failed to enumerate audio endpoints: 0x%08lx\n", hr);
720 return defaultId;
723 UINT count{0};
724 hr = coll->GetCount(&count);
725 if(SUCCEEDED(hr) && count > 0)
726 list.reserve(count);
728 ComPtr<IMMDevice> device;
729 hr = mEnumerator->GetDefaultAudioEndpoint(flowdir, eMultimedia, al::out_ptr(device));
730 if(SUCCEEDED(hr))
732 if(WCHAR *devid{GetDeviceId(device.get())})
734 defaultId = devid;
735 CoTaskMemFree(devid);
737 device = nullptr;
740 for(UINT i{0};i < count;++i)
742 hr = coll->Item(i, al::out_ptr(device));
743 if(FAILED(hr))
744 continue;
746 if(WCHAR *devid{GetDeviceId(device.get())})
748 std::ignore = AddDevice(device, devid, list);
749 CoTaskMemFree(devid);
751 device = nullptr;
753 #else
754 const auto deviceRole = Windows::Media::Devices::AudioDeviceRole::Default;
755 auto DefaultAudioId = flowdir == eRender ? MediaDevice::GetDefaultAudioRenderId(deviceRole)
756 : MediaDevice::GetDefaultAudioCaptureId(deviceRole);
757 if(!DefaultAudioId.empty())
759 auto deviceInfo = DeviceInformation::CreateFromIdAsync(DefaultAudioId, nullptr,
760 DeviceInformationKind::DeviceInterface).get();
761 if(deviceInfo)
762 defaultId = deviceInfo.Id().data();
765 // Get the string identifier of the audio renderer
766 auto AudioSelector = flowdir == eRender ? MediaDevice::GetAudioRenderSelector() : MediaDevice::GetAudioCaptureSelector();
768 // Setup the asynchronous callback
769 auto&& DeviceInfoCollection = DeviceInformation::FindAllAsync(AudioSelector, /*PropertyList*/nullptr, DeviceInformationKind::DeviceInterface).get();
770 if(DeviceInfoCollection)
772 try {
773 auto deviceCount = DeviceInfoCollection.Size();
774 for(unsigned int i{0};i < deviceCount;++i)
776 auto deviceInfo = DeviceInfoCollection.GetAt(i);
777 if(deviceInfo)
778 std::ignore = AddDevice(deviceInfo, deviceInfo.Id().data(), list);
781 catch (const winrt::hresult_error& /*ex*/) {
784 #endif
786 return defaultId;
789 private:
790 static bool AddDevice(const DeviceHandle &device, const WCHAR *devid, std::vector<DevMap> &list)
792 for(auto &entry : list)
794 if(entry.devid == devid)
795 return false;
798 auto name_guid = GetDeviceNameAndGuid(device);
799 int count{1};
800 std::string newname{name_guid.mName};
801 while(checkName(list, newname))
803 newname = name_guid.mName;
804 newname += " #";
805 newname += std::to_string(++count);
807 list.emplace_back(std::move(newname), std::move(name_guid.mGuid), devid);
808 const DevMap &newentry = list.back();
810 TRACE("Got device \"%s\", \"%s\", \"%ls\"\n", newentry.name.c_str(),
811 newentry.endpoint_guid.c_str(), newentry.devid.c_str());
812 return true;
815 #if !defined(ALSOFT_UWP)
816 static WCHAR *GetDeviceId(IMMDevice *device)
818 WCHAR *devid;
820 const HRESULT hr{device->GetId(&devid)};
821 if(FAILED(hr))
823 ERR("Failed to get device id: %lx\n", hr);
824 return nullptr;
827 return devid;
829 ComPtr<IMMDeviceEnumerator> mEnumerator{nullptr};
831 #else
833 HANDLE mActiveClientEvent{nullptr};
835 EventRegistrationToken mRenderDeviceChangedToken;
836 EventRegistrationToken mCaptureDeviceChangedToken;
837 #endif
840 bool MakeExtensible(WAVEFORMATEXTENSIBLE *out, const WAVEFORMATEX *in)
842 *out = WAVEFORMATEXTENSIBLE{};
843 if(in->wFormatTag == WAVE_FORMAT_EXTENSIBLE)
845 *out = *CONTAINING_RECORD(in, const WAVEFORMATEXTENSIBLE, Format);
846 out->Format.cbSize = sizeof(*out) - sizeof(out->Format);
848 else if(in->wFormatTag == WAVE_FORMAT_PCM)
850 out->Format = *in;
851 out->Format.cbSize = 0;
852 /* NOLINTNEXTLINE(cppcoreguidelines-pro-type-union-access) */
853 out->Samples.wValidBitsPerSample = out->Format.wBitsPerSample;
854 if(out->Format.nChannels == 1)
855 out->dwChannelMask = MONO;
856 else if(out->Format.nChannels == 2)
857 out->dwChannelMask = STEREO;
858 else
859 ERR("Unhandled PCM channel count: %d\n", out->Format.nChannels);
860 out->SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
862 else if(in->wFormatTag == WAVE_FORMAT_IEEE_FLOAT)
864 out->Format = *in;
865 out->Format.cbSize = 0;
866 /* NOLINTNEXTLINE(cppcoreguidelines-pro-type-union-access) */
867 out->Samples.wValidBitsPerSample = out->Format.wBitsPerSample;
868 if(out->Format.nChannels == 1)
869 out->dwChannelMask = MONO;
870 else if(out->Format.nChannels == 2)
871 out->dwChannelMask = STEREO;
872 else
873 ERR("Unhandled IEEE float channel count: %d\n", out->Format.nChannels);
874 out->SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT;
876 else
878 ERR("Unhandled format tag: 0x%04x\n", in->wFormatTag);
879 return false;
881 return true;
884 void TraceFormat(const char *msg, const WAVEFORMATEX *format)
886 constexpr size_t fmtex_extra_size{sizeof(WAVEFORMATEXTENSIBLE)-sizeof(WAVEFORMATEX)};
887 if(format->wFormatTag == WAVE_FORMAT_EXTENSIBLE && format->cbSize >= fmtex_extra_size)
889 const WAVEFORMATEXTENSIBLE *fmtex{
890 CONTAINING_RECORD(format, const WAVEFORMATEXTENSIBLE, Format)};
891 /* NOLINTBEGIN(cppcoreguidelines-pro-type-union-access) */
892 TRACE("%s:\n"
893 " FormatTag = 0x%04x\n"
894 " Channels = %d\n"
895 " SamplesPerSec = %lu\n"
896 " AvgBytesPerSec = %lu\n"
897 " BlockAlign = %d\n"
898 " BitsPerSample = %d\n"
899 " Size = %d\n"
900 " Samples = %d\n"
901 " ChannelMask = 0x%lx\n"
902 " SubFormat = %s\n",
903 msg, fmtex->Format.wFormatTag, fmtex->Format.nChannels, fmtex->Format.nSamplesPerSec,
904 fmtex->Format.nAvgBytesPerSec, fmtex->Format.nBlockAlign, fmtex->Format.wBitsPerSample,
905 fmtex->Format.cbSize, fmtex->Samples.wReserved, fmtex->dwChannelMask,
906 GuidPrinter{fmtex->SubFormat}.c_str());
907 /* NOLINTEND(cppcoreguidelines-pro-type-union-access) */
909 else
910 TRACE("%s:\n"
911 " FormatTag = 0x%04x\n"
912 " Channels = %d\n"
913 " SamplesPerSec = %lu\n"
914 " AvgBytesPerSec = %lu\n"
915 " BlockAlign = %d\n"
916 " BitsPerSample = %d\n"
917 " Size = %d\n",
918 msg, format->wFormatTag, format->nChannels, format->nSamplesPerSec,
919 format->nAvgBytesPerSec, format->nBlockAlign, format->wBitsPerSample, format->cbSize);
923 enum class MsgType {
924 OpenDevice,
925 ResetDevice,
926 StartDevice,
927 StopDevice,
928 CloseDevice,
930 QuitThread
933 constexpr const char *GetMessageTypeName(MsgType type) noexcept
935 switch(type)
937 case MsgType::OpenDevice: return "Open Device";
938 case MsgType::ResetDevice: return "Reset Device";
939 case MsgType::StartDevice: return "Start Device";
940 case MsgType::StopDevice: return "Stop Device";
941 case MsgType::CloseDevice: return "Close Device";
942 case MsgType::QuitThread: break;
944 return "";
948 /* Proxy interface used by the message handler. */
949 struct WasapiProxy {
950 WasapiProxy() = default;
951 WasapiProxy(const WasapiProxy&) = delete;
952 WasapiProxy(WasapiProxy&&) = delete;
953 virtual ~WasapiProxy() = default;
955 void operator=(const WasapiProxy&) = delete;
956 void operator=(WasapiProxy&&) = delete;
958 virtual HRESULT openProxy(std::string_view name) = 0;
959 virtual void closeProxy() = 0;
961 virtual HRESULT resetProxy() = 0;
962 virtual HRESULT startProxy() = 0;
963 virtual void stopProxy() = 0;
965 struct Msg {
966 MsgType mType;
967 WasapiProxy *mProxy;
968 std::string_view mParam;
969 std::promise<HRESULT> mPromise;
971 explicit operator bool() const noexcept { return mType != MsgType::QuitThread; }
973 static inline std::deque<Msg> mMsgQueue;
974 static inline std::mutex mMsgQueueLock;
975 static inline std::condition_variable mMsgQueueCond;
976 static inline DWORD sAvIndex{};
978 static inline std::optional<DeviceHelper> sDeviceHelper;
980 std::future<HRESULT> pushMessage(MsgType type, std::string_view param={})
982 std::promise<HRESULT> promise;
983 std::future<HRESULT> future{promise.get_future()};
985 std::lock_guard<std::mutex> msglock{mMsgQueueLock};
986 mMsgQueue.emplace_back(Msg{type, this, param, std::move(promise)});
988 mMsgQueueCond.notify_one();
989 return future;
992 static std::future<HRESULT> pushMessageStatic(MsgType type)
994 std::promise<HRESULT> promise;
995 std::future<HRESULT> future{promise.get_future()};
997 std::lock_guard<std::mutex> msglock{mMsgQueueLock};
998 mMsgQueue.emplace_back(Msg{type, nullptr, {}, std::move(promise)});
1000 mMsgQueueCond.notify_one();
1001 return future;
1004 static Msg popMessage()
1006 std::unique_lock<std::mutex> lock{mMsgQueueLock};
1007 mMsgQueueCond.wait(lock, []{return !mMsgQueue.empty();});
1008 Msg msg{std::move(mMsgQueue.front())};
1009 mMsgQueue.pop_front();
1010 return msg;
1013 static int messageHandler(std::promise<HRESULT> *promise);
1016 int WasapiProxy::messageHandler(std::promise<HRESULT> *promise)
1018 TRACE("Starting message thread\n");
1020 ComWrapper com{COINIT_MULTITHREADED};
1021 if(!com)
1023 WARN("Failed to initialize COM: 0x%08lx\n", com.status());
1024 promise->set_value(com.status());
1025 return 0;
1028 struct HelperResetter {
1029 HelperResetter() = default;
1030 HelperResetter(const HelperResetter&) = delete;
1031 auto operator=(const HelperResetter&) -> HelperResetter& = delete;
1032 ~HelperResetter() { sDeviceHelper.reset(); }
1034 HelperResetter scoped_watcher;
1036 HRESULT hr{sDeviceHelper.emplace().init()};
1037 promise->set_value(hr);
1038 promise = nullptr;
1039 if(FAILED(hr))
1040 return 0;
1043 auto devlock = DeviceListLock{gDeviceList};
1044 auto defaultId = sDeviceHelper->probeDevices(eRender, devlock.getPlaybackList());
1045 if(!defaultId.empty()) devlock.setPlaybackDefaultId(defaultId);
1046 defaultId = sDeviceHelper->probeDevices(eCapture, devlock.getCaptureList());
1047 if(!defaultId.empty()) devlock.setCaptureDefaultId(defaultId);
1050 TRACE("Starting message loop\n");
1051 while(Msg msg{popMessage()})
1053 TRACE("Got message \"%s\" (0x%04x, this=%p, param=\"%.*s\")\n",
1054 GetMessageTypeName(msg.mType), static_cast<uint>(msg.mType),
1055 static_cast<void*>(msg.mProxy), al::sizei(msg.mParam), msg.mParam.data());
1057 switch(msg.mType)
1059 case MsgType::OpenDevice:
1060 hr = msg.mProxy->openProxy(msg.mParam);
1061 msg.mPromise.set_value(hr);
1062 continue;
1064 case MsgType::ResetDevice:
1065 hr = msg.mProxy->resetProxy();
1066 msg.mPromise.set_value(hr);
1067 continue;
1069 case MsgType::StartDevice:
1070 hr = msg.mProxy->startProxy();
1071 msg.mPromise.set_value(hr);
1072 continue;
1074 case MsgType::StopDevice:
1075 msg.mProxy->stopProxy();
1076 msg.mPromise.set_value(S_OK);
1077 continue;
1079 case MsgType::CloseDevice:
1080 msg.mProxy->closeProxy();
1081 msg.mPromise.set_value(S_OK);
1082 continue;
1084 case MsgType::QuitThread:
1085 break;
1087 ERR("Unexpected message: %u\n", static_cast<uint>(msg.mType));
1088 msg.mPromise.set_value(E_FAIL);
1090 TRACE("Message loop finished\n");
1092 return 0;
1095 struct WasapiPlayback final : public BackendBase, WasapiProxy {
1096 WasapiPlayback(DeviceBase *device) noexcept : BackendBase{device} { }
1097 ~WasapiPlayback() override;
1099 int mixerProc();
1100 int mixerSpatialProc();
1102 void open(std::string_view name) override;
1103 HRESULT openProxy(std::string_view name) override;
1104 void closeProxy() override;
1106 bool reset() override;
1107 HRESULT resetProxy() override;
1108 void start() override;
1109 HRESULT startProxy() override;
1110 void stop() override;
1111 void stopProxy() override;
1113 ClockLatency getClockLatency() override;
1115 void prepareFormat(WAVEFORMATEXTENSIBLE &OutputType);
1116 void finalizeFormat(WAVEFORMATEXTENSIBLE &OutputType);
1118 auto initSpatial() -> bool;
1120 HRESULT mOpenStatus{E_FAIL};
1121 DeviceHandle mMMDev{nullptr};
1123 struct PlainDevice {
1124 ComPtr<IAudioClient> mClient{nullptr};
1125 ComPtr<IAudioRenderClient> mRender{nullptr};
1127 struct SpatialDevice {
1128 ComPtr<ISpatialAudioClient> mClient{nullptr};
1129 ComPtr<ISpatialAudioObjectRenderStream> mRender{nullptr};
1130 AudioObjectType mStaticMask{};
1132 std::variant<PlainDevice,SpatialDevice> mAudio{std::in_place_index_t<0>{}};
1133 HANDLE mNotifyEvent{nullptr};
1135 UINT32 mOutBufferSize{}, mOutUpdateSize{};
1136 std::vector<char> mResampleBuffer{};
1137 uint mBufferFilled{0};
1138 SampleConverterPtr mResampler;
1140 WAVEFORMATEXTENSIBLE mFormat{};
1141 std::atomic<UINT32> mPadding{0u};
1143 std::mutex mMutex;
1145 std::atomic<bool> mKillNow{true};
1146 std::thread mThread;
1149 WasapiPlayback::~WasapiPlayback()
1151 if(SUCCEEDED(mOpenStatus))
1152 pushMessage(MsgType::CloseDevice).wait();
1153 mOpenStatus = E_FAIL;
1155 if(mNotifyEvent != nullptr)
1156 CloseHandle(mNotifyEvent);
1157 mNotifyEvent = nullptr;
1161 FORCE_ALIGN int WasapiPlayback::mixerProc()
1163 ComWrapper com{COINIT_MULTITHREADED};
1164 if(!com)
1166 ERR("CoInitializeEx(nullptr, COINIT_MULTITHREADED) failed: 0x%08lx\n", com.status());
1167 mDevice->handleDisconnect("COM init failed: 0x%08lx", com.status());
1168 return 1;
1171 auto &audio = std::get<PlainDevice>(mAudio);
1173 SetRTPriority();
1174 althrd_setname(GetMixerThreadName());
1176 const uint frame_size{mFormat.Format.nChannels * mFormat.Format.wBitsPerSample / 8u};
1177 const uint update_size{mOutUpdateSize};
1178 const UINT32 buffer_len{mOutBufferSize};
1179 const void *resbufferptr{};
1181 #ifdef AVRTAPI
1182 /* TODO: "Audio" or "Pro Audio"? The suggestion is to use "Pro Audio" for
1183 * device periods less than 10ms, and "Audio" for greater than or equal to
1184 * 10ms.
1186 auto taskname = (update_size < mFormat.Format.nSamplesPerSec/100) ? L"Pro Audio" : L"Audio";
1187 auto avhandle = AvrtHandlePtr{AvSetMmThreadCharacteristicsW(taskname, &sAvIndex)};
1188 #endif
1190 mBufferFilled = 0;
1191 while(!mKillNow.load(std::memory_order_relaxed))
1193 UINT32 written;
1194 HRESULT hr{audio.mClient->GetCurrentPadding(&written)};
1195 if(FAILED(hr))
1197 ERR("Failed to get padding: 0x%08lx\n", hr);
1198 mDevice->handleDisconnect("Failed to retrieve buffer padding: 0x%08lx", hr);
1199 break;
1201 mPadding.store(written, std::memory_order_relaxed);
1203 uint len{buffer_len - written};
1204 if(len < update_size)
1206 DWORD res{WaitForSingleObjectEx(mNotifyEvent, 2000, FALSE)};
1207 if(res != WAIT_OBJECT_0)
1208 ERR("WaitForSingleObjectEx error: 0x%lx\n", res);
1209 continue;
1212 BYTE *buffer;
1213 hr = audio.mRender->GetBuffer(len, &buffer);
1214 if(SUCCEEDED(hr))
1216 if(mResampler)
1218 std::lock_guard<std::mutex> dlock{mMutex};
1219 auto dst = al::span{buffer, size_t{len}*frame_size};
1220 for(UINT32 done{0};done < len;)
1222 if(mBufferFilled == 0)
1224 mDevice->renderSamples(mResampleBuffer.data(), mDevice->UpdateSize,
1225 mFormat.Format.nChannels);
1226 resbufferptr = mResampleBuffer.data();
1227 mBufferFilled = mDevice->UpdateSize;
1230 uint got{mResampler->convert(&resbufferptr, &mBufferFilled, dst.data(),
1231 len-done)};
1232 dst = dst.subspan(size_t{got}*frame_size);
1233 done += got;
1235 mPadding.store(written + done, std::memory_order_relaxed);
1238 else
1240 std::lock_guard<std::mutex> dlock{mMutex};
1241 mDevice->renderSamples(buffer, len, mFormat.Format.nChannels);
1242 mPadding.store(written + len, std::memory_order_relaxed);
1244 hr = audio.mRender->ReleaseBuffer(len, 0);
1246 if(FAILED(hr))
1248 ERR("Failed to buffer data: 0x%08lx\n", hr);
1249 mDevice->handleDisconnect("Failed to send playback samples: 0x%08lx", hr);
1250 break;
1253 mPadding.store(0u, std::memory_order_release);
1255 return 0;
1258 FORCE_ALIGN int WasapiPlayback::mixerSpatialProc()
1260 ComWrapper com{COINIT_MULTITHREADED};
1261 if(!com)
1263 ERR("CoInitializeEx(nullptr, COINIT_MULTITHREADED) failed: 0x%08lx\n", com.status());
1264 mDevice->handleDisconnect("COM init failed: 0x%08lx", com.status());
1265 return 1;
1268 auto &audio = std::get<SpatialDevice>(mAudio);
1270 SetRTPriority();
1271 althrd_setname(GetMixerThreadName());
1273 std::vector<ComPtr<ISpatialAudioObject>> channels;
1274 std::vector<void*> buffers;
1275 std::vector<void*> resbuffers;
1276 std::vector<const void*> tmpbuffers;
1278 #ifdef AVRTAPI
1279 auto taskname = (mOutUpdateSize < mFormat.Format.nSamplesPerSec/100) ? L"Pro Audio" : L"Audio";
1280 auto avhandle = AvrtHandlePtr{AvSetMmThreadCharacteristicsW(taskname, &sAvIndex)};
1281 #endif
1283 /* TODO: Set mPadding appropriately. There doesn't seem to be a way to
1284 * update it dynamically based on the stream, so a fixed size may be the
1285 * best we can do.
1287 mPadding.store(mOutBufferSize-mOutUpdateSize, std::memory_order_release);
1289 mBufferFilled = 0;
1290 while(!mKillNow.load(std::memory_order_relaxed))
1292 if(DWORD res{WaitForSingleObjectEx(mNotifyEvent, 1000, FALSE)}; res != WAIT_OBJECT_0)
1294 ERR("WaitForSingleObjectEx error: 0x%lx\n", res);
1296 HRESULT hr{audio.mRender->Reset()};
1297 if(FAILED(hr))
1299 ERR("ISpatialAudioObjectRenderStream::Reset failed: 0x%08lx\n", hr);
1300 mDevice->handleDisconnect("Device lost: 0x%08lx", hr);
1301 break;
1305 UINT32 dynamicCount{}, framesToDo{};
1306 HRESULT hr{audio.mRender->BeginUpdatingAudioObjects(&dynamicCount, &framesToDo)};
1307 if(SUCCEEDED(hr))
1309 if(channels.empty()) UNLIKELY
1311 auto flags = as_unsigned(audio.mStaticMask);
1312 channels.reserve(as_unsigned(al::popcount(flags)));
1313 while(flags)
1315 auto id = decltype(flags){1} << al::countr_zero(flags);
1316 flags &= ~id;
1318 channels.emplace_back();
1319 audio.mRender->ActivateSpatialAudioObject(static_cast<AudioObjectType>(id),
1320 al::out_ptr(channels.back()));
1322 buffers.resize(channels.size());
1323 if(mResampler)
1325 tmpbuffers.resize(buffers.size());
1326 resbuffers.resize(buffers.size());
1327 auto bufptr = mResampleBuffer.begin();
1328 for(size_t i{0};i < tmpbuffers.size();++i)
1330 resbuffers[i] = al::to_address(bufptr);
1331 bufptr += ptrdiff_t(mDevice->UpdateSize*sizeof(float));
1336 /* We have to call to get each channel's buffer individually every
1337 * update, unfortunately.
1339 std::transform(channels.cbegin(), channels.cend(), buffers.begin(),
1340 [](const ComPtr<ISpatialAudioObject> &obj) -> void*
1342 auto buffer = LPBYTE{};
1343 auto size = UINT32{};
1344 obj->GetBuffer(&buffer, &size);
1345 return buffer;
1348 if(!mResampler)
1349 mDevice->renderSamples(buffers, framesToDo);
1350 else
1352 std::lock_guard<std::mutex> dlock{mMutex};
1353 for(UINT32 pos{0};pos < framesToDo;)
1355 if(mBufferFilled == 0)
1357 mDevice->renderSamples(resbuffers, mDevice->UpdateSize);
1358 std::copy(resbuffers.cbegin(), resbuffers.cend(), tmpbuffers.begin());
1359 mBufferFilled = mDevice->UpdateSize;
1362 const uint got{mResampler->convertPlanar(tmpbuffers.data(), &mBufferFilled,
1363 buffers.data(), framesToDo-pos)};
1364 for(auto &buf : buffers)
1365 buf = static_cast<float*>(buf) + got; /* NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic) */
1366 pos += got;
1370 hr = audio.mRender->EndUpdatingAudioObjects();
1373 if(FAILED(hr))
1374 ERR("Failed to update playback objects: 0x%08lx\n", hr);
1376 mPadding.store(0u, std::memory_order_release);
1378 return 0;
1382 void WasapiPlayback::open(std::string_view name)
1384 if(SUCCEEDED(mOpenStatus))
1385 throw al::backend_exception{al::backend_error::DeviceError,
1386 "Unexpected duplicate open call"};
1388 mNotifyEvent = CreateEventW(nullptr, FALSE, FALSE, nullptr);
1389 if(mNotifyEvent == nullptr)
1391 ERR("Failed to create notify events: %lu\n", GetLastError());
1392 throw al::backend_exception{al::backend_error::DeviceError,
1393 "Failed to create notify events"};
1396 mOpenStatus = pushMessage(MsgType::OpenDevice, name).get();
1397 if(FAILED(mOpenStatus))
1398 throw al::backend_exception{al::backend_error::DeviceError, "Device init failed: 0x%08lx",
1399 mOpenStatus};
1402 HRESULT WasapiPlayback::openProxy(std::string_view name)
1404 std::string devname;
1405 std::wstring devid;
1406 if(!name.empty())
1408 auto devlock = DeviceListLock{gDeviceList};
1409 auto list = al::span{devlock.getPlaybackList()};
1410 auto iter = std::find_if(list.cbegin(), list.cend(),
1411 [name](const DevMap &entry) -> bool
1412 { return entry.name == name || entry.endpoint_guid == name; });
1413 if(iter == list.cend())
1415 const std::wstring wname{utf8_to_wstr(name)};
1416 iter = std::find_if(list.cbegin(), list.cend(),
1417 [&wname](const DevMap &entry) -> bool
1418 { return entry.devid == wname; });
1420 if(iter == list.cend())
1422 WARN("Failed to find device name matching \"%.*s\"\n", al::sizei(name), name.data());
1423 return E_FAIL;
1425 devname = iter->name;
1426 devid = iter->devid;
1429 HRESULT hr{sDeviceHelper->openDevice(devid, eRender, mMMDev)};
1430 if(FAILED(hr))
1432 WARN("Failed to open device \"%s\"\n", devname.empty() ? "(default)" : devname.c_str());
1433 return hr;
1435 if(!devname.empty())
1436 mDeviceName = std::move(devname);
1437 else
1438 mDeviceName = GetDeviceNameAndGuid(mMMDev).mName;
1440 return S_OK;
1443 void WasapiPlayback::closeProxy()
1445 mAudio.emplace<PlainDevice>();
1446 mMMDev = nullptr;
1450 void WasapiPlayback::prepareFormat(WAVEFORMATEXTENSIBLE &OutputType)
1452 bool isRear51{false};
1454 if(!mDevice->Flags.test(FrequencyRequest))
1455 mDevice->Frequency = OutputType.Format.nSamplesPerSec;
1456 if(!mDevice->Flags.test(ChannelsRequest))
1458 /* If not requesting a channel configuration, auto-select given what
1459 * fits the mask's lsb (to ensure no gaps in the output channels). If
1460 * there's no mask, we can only assume mono or stereo.
1462 const uint32_t chancount{OutputType.Format.nChannels};
1463 const DWORD chanmask{OutputType.dwChannelMask};
1464 if(chancount >= 12 && (chanmask&X714Mask) == X7DOT1DOT4)
1465 mDevice->FmtChans = DevFmtX714;
1466 else if(chancount >= 8 && (chanmask&X71Mask) == X7DOT1)
1467 mDevice->FmtChans = DevFmtX71;
1468 else if(chancount >= 7 && (chanmask&X61Mask) == X6DOT1)
1469 mDevice->FmtChans = DevFmtX61;
1470 else if(chancount >= 6 && (chanmask&X51Mask) == X5DOT1)
1471 mDevice->FmtChans = DevFmtX51;
1472 else if(chancount >= 6 && (chanmask&X51RearMask) == X5DOT1REAR)
1474 mDevice->FmtChans = DevFmtX51;
1475 isRear51 = true;
1477 else if(chancount >= 4 && (chanmask&QuadMask) == QUAD)
1478 mDevice->FmtChans = DevFmtQuad;
1479 else if(chancount >= 2 && ((chanmask&StereoMask) == STEREO || !chanmask))
1480 mDevice->FmtChans = DevFmtStereo;
1481 else if(chancount >= 1 && ((chanmask&MonoMask) == MONO || !chanmask))
1482 mDevice->FmtChans = DevFmtMono;
1483 else
1484 ERR("Unhandled channel config: %d -- 0x%08lx\n", chancount, chanmask);
1486 else
1488 const uint32_t chancount{OutputType.Format.nChannels};
1489 const DWORD chanmask{OutputType.dwChannelMask};
1490 isRear51 = (chancount == 6 && (chanmask&X51RearMask) == X5DOT1REAR);
1493 OutputType.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
1494 switch(mDevice->FmtChans)
1496 case DevFmtMono:
1497 OutputType.Format.nChannels = 1;
1498 OutputType.dwChannelMask = MONO;
1499 break;
1500 case DevFmtAmbi3D:
1501 mDevice->FmtChans = DevFmtStereo;
1502 /*fall-through*/
1503 case DevFmtStereo:
1504 OutputType.Format.nChannels = 2;
1505 OutputType.dwChannelMask = STEREO;
1506 break;
1507 case DevFmtQuad:
1508 OutputType.Format.nChannels = 4;
1509 OutputType.dwChannelMask = QUAD;
1510 break;
1511 case DevFmtX51:
1512 OutputType.Format.nChannels = 6;
1513 OutputType.dwChannelMask = isRear51 ? X5DOT1REAR : X5DOT1;
1514 break;
1515 case DevFmtX61:
1516 OutputType.Format.nChannels = 7;
1517 OutputType.dwChannelMask = X6DOT1;
1518 break;
1519 case DevFmtX71:
1520 case DevFmtX3D71:
1521 OutputType.Format.nChannels = 8;
1522 OutputType.dwChannelMask = X7DOT1;
1523 break;
1524 case DevFmtX7144:
1525 mDevice->FmtChans = DevFmtX714;
1526 /*fall-through*/
1527 case DevFmtX714:
1528 OutputType.Format.nChannels = 12;
1529 OutputType.dwChannelMask = X7DOT1DOT4;
1530 break;
1532 switch(mDevice->FmtType)
1534 case DevFmtByte:
1535 mDevice->FmtType = DevFmtUByte;
1536 /* fall-through */
1537 case DevFmtUByte:
1538 OutputType.Format.wBitsPerSample = 8;
1539 OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
1540 break;
1541 case DevFmtUShort:
1542 mDevice->FmtType = DevFmtShort;
1543 /* fall-through */
1544 case DevFmtShort:
1545 OutputType.Format.wBitsPerSample = 16;
1546 OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
1547 break;
1548 case DevFmtUInt:
1549 mDevice->FmtType = DevFmtInt;
1550 /* fall-through */
1551 case DevFmtInt:
1552 OutputType.Format.wBitsPerSample = 32;
1553 OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
1554 break;
1555 case DevFmtFloat:
1556 OutputType.Format.wBitsPerSample = 32;
1557 OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT;
1558 break;
1560 /* NOLINTNEXTLINE(cppcoreguidelines-pro-type-union-access) */
1561 OutputType.Samples.wValidBitsPerSample = OutputType.Format.wBitsPerSample;
1562 OutputType.Format.nSamplesPerSec = mDevice->Frequency;
1564 OutputType.Format.nBlockAlign = static_cast<WORD>(OutputType.Format.nChannels *
1565 OutputType.Format.wBitsPerSample / 8);
1566 OutputType.Format.nAvgBytesPerSec = OutputType.Format.nSamplesPerSec *
1567 OutputType.Format.nBlockAlign;
1570 void WasapiPlayback::finalizeFormat(WAVEFORMATEXTENSIBLE &OutputType)
1572 if(!GetConfigValueBool(mDevice->mDeviceName, "wasapi", "allow-resampler", true))
1573 mDevice->Frequency = uint(OutputType.Format.nSamplesPerSec);
1574 else
1575 mDevice->Frequency = std::min(mDevice->Frequency, uint(OutputType.Format.nSamplesPerSec));
1577 const uint32_t chancount{OutputType.Format.nChannels};
1578 const DWORD chanmask{OutputType.dwChannelMask};
1579 /* Don't update the channel format if the requested format fits what's
1580 * supported.
1582 bool chansok{false};
1583 if(mDevice->Flags.test(ChannelsRequest))
1585 /* When requesting a channel configuration, make sure it fits the
1586 * mask's lsb (to ensure no gaps in the output channels). If there's no
1587 * mask, assume the request fits with enough channels.
1589 switch(mDevice->FmtChans)
1591 case DevFmtMono:
1592 chansok = (chancount >= 1 && ((chanmask&MonoMask) == MONO || !chanmask));
1593 break;
1594 case DevFmtStereo:
1595 chansok = (chancount >= 2 && ((chanmask&StereoMask) == STEREO || !chanmask));
1596 break;
1597 case DevFmtQuad:
1598 chansok = (chancount >= 4 && ((chanmask&QuadMask) == QUAD || !chanmask));
1599 break;
1600 case DevFmtX51:
1601 chansok = (chancount >= 6 && ((chanmask&X51Mask) == X5DOT1
1602 || (chanmask&X51RearMask) == X5DOT1REAR || !chanmask));
1603 break;
1604 case DevFmtX61:
1605 chansok = (chancount >= 7 && ((chanmask&X61Mask) == X6DOT1 || !chanmask));
1606 break;
1607 case DevFmtX71:
1608 case DevFmtX3D71:
1609 chansok = (chancount >= 8 && ((chanmask&X71Mask) == X7DOT1 || !chanmask));
1610 break;
1611 case DevFmtX714:
1612 chansok = (chancount >= 12 && ((chanmask&X714Mask) == X7DOT1DOT4 || !chanmask));
1613 case DevFmtX7144:
1614 case DevFmtAmbi3D:
1615 break;
1618 if(!chansok)
1620 if(chancount >= 12 && (chanmask&X714Mask) == X7DOT1DOT4)
1621 mDevice->FmtChans = DevFmtX714;
1622 else if(chancount >= 8 && (chanmask&X71Mask) == X7DOT1)
1623 mDevice->FmtChans = DevFmtX71;
1624 else if(chancount >= 7 && (chanmask&X61Mask) == X6DOT1)
1625 mDevice->FmtChans = DevFmtX61;
1626 else if(chancount >= 6 && ((chanmask&X51Mask) == X5DOT1
1627 || (chanmask&X51RearMask) == X5DOT1REAR))
1628 mDevice->FmtChans = DevFmtX51;
1629 else if(chancount >= 4 && (chanmask&QuadMask) == QUAD)
1630 mDevice->FmtChans = DevFmtQuad;
1631 else if(chancount >= 2 && ((chanmask&StereoMask) == STEREO || !chanmask))
1632 mDevice->FmtChans = DevFmtStereo;
1633 else if(chancount >= 1 && ((chanmask&MonoMask) == MONO || !chanmask))
1634 mDevice->FmtChans = DevFmtMono;
1635 else
1637 ERR("Unhandled extensible channels: %d -- 0x%08lx\n", OutputType.Format.nChannels,
1638 OutputType.dwChannelMask);
1639 mDevice->FmtChans = DevFmtStereo;
1640 OutputType.Format.nChannels = 2;
1641 OutputType.dwChannelMask = STEREO;
1645 if(IsEqualGUID(OutputType.SubFormat, KSDATAFORMAT_SUBTYPE_PCM))
1647 if(OutputType.Format.wBitsPerSample == 8)
1648 mDevice->FmtType = DevFmtUByte;
1649 else if(OutputType.Format.wBitsPerSample == 16)
1650 mDevice->FmtType = DevFmtShort;
1651 else if(OutputType.Format.wBitsPerSample == 32)
1652 mDevice->FmtType = DevFmtInt;
1653 else
1655 mDevice->FmtType = DevFmtShort;
1656 OutputType.Format.wBitsPerSample = 16;
1659 else if(IsEqualGUID(OutputType.SubFormat, KSDATAFORMAT_SUBTYPE_IEEE_FLOAT))
1661 mDevice->FmtType = DevFmtFloat;
1662 OutputType.Format.wBitsPerSample = 32;
1664 else
1666 ERR("Unhandled format sub-type: %s\n", GuidPrinter{OutputType.SubFormat}.c_str());
1667 mDevice->FmtType = DevFmtShort;
1668 if(OutputType.Format.wFormatTag != WAVE_FORMAT_EXTENSIBLE)
1669 OutputType.Format.wFormatTag = WAVE_FORMAT_PCM;
1670 OutputType.Format.wBitsPerSample = 16;
1671 OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
1673 /* NOLINTNEXTLINE(cppcoreguidelines-pro-type-union-access) */
1674 OutputType.Samples.wValidBitsPerSample = OutputType.Format.wBitsPerSample;
1678 auto WasapiPlayback::initSpatial() -> bool
1680 auto &audio = mAudio.emplace<SpatialDevice>();
1681 HRESULT hr{sDeviceHelper->activateAudioClient(mMMDev, __uuidof(ISpatialAudioClient),
1682 al::out_ptr(audio.mClient))};
1683 if(FAILED(hr))
1685 ERR("Failed to activate spatial audio client: 0x%08lx\n", hr);
1686 return false;
1689 ComPtr<IAudioFormatEnumerator> fmtenum;
1690 hr = audio.mClient->GetSupportedAudioObjectFormatEnumerator(al::out_ptr(fmtenum));
1691 if(FAILED(hr))
1693 ERR("Failed to get format enumerator: 0x%08lx\n", hr);
1694 return false;
1697 UINT32 fmtcount{};
1698 hr = fmtenum->GetCount(&fmtcount);
1699 if(FAILED(hr) || fmtcount == 0)
1701 ERR("Failed to get format count: 0x%08lx\n", hr);
1702 return false;
1705 WAVEFORMATEX *preferredFormat{};
1706 hr = fmtenum->GetFormat(0, &preferredFormat);
1707 if(FAILED(hr))
1709 ERR("Failed to get preferred format: 0x%08lx\n", hr);
1710 return false;
1712 TraceFormat("Preferred mix format", preferredFormat);
1714 UINT32 maxFrames{};
1715 hr = audio.mClient->GetMaxFrameCount(preferredFormat, &maxFrames);
1716 if(FAILED(hr))
1717 ERR("Failed to get max frames: 0x%08lx\n", hr);
1718 else
1719 TRACE("Max sample frames: %u\n", maxFrames);
1720 for(UINT32 i{1};i < fmtcount;++i)
1722 WAVEFORMATEX *otherFormat{};
1723 hr = fmtenum->GetFormat(i, &otherFormat);
1724 if(FAILED(hr))
1725 ERR("Failed to format %u: 0x%08lx\n", i+1, hr);
1726 else
1728 TraceFormat("Other mix format", otherFormat);
1729 UINT32 otherMaxFrames{};
1730 hr = audio.mClient->GetMaxFrameCount(otherFormat, &otherMaxFrames);
1731 if(FAILED(hr))
1732 ERR("Failed to get max frames: 0x%08lx\n", hr);
1733 else
1734 TRACE("Max sample frames: %u\n", otherMaxFrames);
1738 WAVEFORMATEXTENSIBLE OutputType;
1739 if(!MakeExtensible(&OutputType, preferredFormat))
1740 return false;
1742 /* This seems to be the format of each "object", which should be mono. */
1743 if(!(OutputType.Format.nChannels == 1
1744 && (OutputType.dwChannelMask == MONO || !OutputType.dwChannelMask)))
1745 ERR("Unhandled channel config: %d -- 0x%08lx\n", OutputType.Format.nChannels,
1746 OutputType.dwChannelMask);
1748 /* Force 32-bit float. This is currently required for planar output. */
1749 if(OutputType.Format.wFormatTag != WAVE_FORMAT_EXTENSIBLE
1750 && OutputType.Format.wFormatTag != WAVE_FORMAT_IEEE_FLOAT)
1752 OutputType.Format.wFormatTag = WAVE_FORMAT_IEEE_FLOAT;
1753 OutputType.Format.cbSize = 0;
1755 if(OutputType.Format.wBitsPerSample != 32)
1757 OutputType.Format.nAvgBytesPerSec = OutputType.Format.nAvgBytesPerSec * 32u
1758 / OutputType.Format.wBitsPerSample;
1759 OutputType.Format.nBlockAlign = static_cast<WORD>(OutputType.Format.nBlockAlign * 32
1760 / OutputType.Format.wBitsPerSample);
1761 OutputType.Format.wBitsPerSample = 32;
1763 /* NOLINTNEXTLINE(cppcoreguidelines-pro-type-union-access) */
1764 OutputType.Samples.wValidBitsPerSample = OutputType.Format.wBitsPerSample;
1765 OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT;
1767 /* Match the output rate if not requesting anything specific. */
1768 if(!mDevice->Flags.test(FrequencyRequest))
1769 mDevice->Frequency = OutputType.Format.nSamplesPerSec;
1771 auto getTypeMask = [](DevFmtChannels chans) noexcept
1773 switch(chans)
1775 case DevFmtMono: return ChannelMask_Mono;
1776 case DevFmtStereo: return ChannelMask_Stereo;
1777 case DevFmtQuad: return ChannelMask_Quad;
1778 case DevFmtX51: return ChannelMask_X51;
1779 case DevFmtX61: return ChannelMask_X61;
1780 case DevFmtX3D71: [[fallthrough]];
1781 case DevFmtX71: return ChannelMask_X71;
1782 case DevFmtX714: return ChannelMask_X714;
1783 case DevFmtX7144: return ChannelMask_X7144;
1784 case DevFmtAmbi3D:
1785 break;
1787 return ChannelMask_Stereo;
1790 SpatialAudioObjectRenderStreamActivationParams streamParams{};
1791 streamParams.ObjectFormat = &OutputType.Format;
1792 streamParams.StaticObjectTypeMask = getTypeMask(mDevice->FmtChans);
1793 streamParams.Category = AudioCategory_Media;
1794 streamParams.EventHandle = mNotifyEvent;
1796 PropVariant paramProp{};
1797 paramProp.setBlob({reinterpret_cast<BYTE*>(&streamParams), sizeof(streamParams)});
1799 hr = audio.mClient->ActivateSpatialAudioStream(paramProp.get(),
1800 __uuidof(ISpatialAudioObjectRenderStream), al::out_ptr(audio.mRender));
1801 if(FAILED(hr))
1803 ERR("Failed to activate spatial audio stream: 0x%08lx\n", hr);
1804 return false;
1807 audio.mStaticMask = streamParams.StaticObjectTypeMask;
1808 mFormat = OutputType;
1810 mDevice->FmtType = DevFmtFloat;
1811 mDevice->Flags.reset(DirectEar).set(Virtualization);
1812 if(streamParams.StaticObjectTypeMask == ChannelMask_Stereo)
1813 mDevice->FmtChans = DevFmtStereo;
1814 if(!GetConfigValueBool(mDevice->mDeviceName, "wasapi", "allow-resampler", true))
1815 mDevice->Frequency = uint(OutputType.Format.nSamplesPerSec);
1816 else
1817 mDevice->Frequency = std::min(mDevice->Frequency,
1818 uint(OutputType.Format.nSamplesPerSec));
1820 setDefaultWFXChannelOrder();
1822 /* FIXME: Get the real update and buffer size. Presumably the actual device
1823 * is configured once ActivateSpatialAudioStream succeeds, and an
1824 * IAudioClient from the same IMMDevice accesses the same device
1825 * configuration. This isn't obviously correct, but for now assume
1826 * IAudioClient::GetDevicePeriod returns the current device period time
1827 * that ISpatialAudioObjectRenderStream will try to wake up at.
1829 * Unfortunately this won't get the buffer size of the
1830 * ISpatialAudioObjectRenderStream, so we only assume there's two periods.
1832 mOutUpdateSize = mDevice->UpdateSize;
1833 mOutBufferSize = mOutUpdateSize*2;
1834 ReferenceTime per_time{ReferenceTime{seconds{mDevice->UpdateSize}} / mDevice->Frequency};
1836 ComPtr<IAudioClient> tmpClient;
1837 hr = sDeviceHelper->activateAudioClient(mMMDev, __uuidof(IAudioClient),
1838 al::out_ptr(tmpClient));
1839 if(FAILED(hr))
1840 ERR("Failed to activate audio client: 0x%08lx\n", hr);
1841 else
1843 hr = tmpClient->GetDevicePeriod(&reinterpret_cast<REFERENCE_TIME&>(per_time), nullptr);
1844 if(FAILED(hr))
1845 ERR("Failed to get device period: 0x%08lx\n", hr);
1846 else
1848 mOutUpdateSize = RefTime2Samples(per_time, mFormat.Format.nSamplesPerSec);
1849 mOutBufferSize = mOutUpdateSize*2;
1852 tmpClient = nullptr;
1854 mDevice->UpdateSize = RefTime2Samples(per_time, mDevice->Frequency);
1855 mDevice->BufferSize = mDevice->UpdateSize*2;
1857 mResampler = nullptr;
1858 mResampleBuffer.clear();
1859 mResampleBuffer.shrink_to_fit();
1860 mBufferFilled = 0;
1861 if(mDevice->Frequency != mFormat.Format.nSamplesPerSec)
1863 const auto flags = as_unsigned(streamParams.StaticObjectTypeMask);
1864 const auto channelCount = as_unsigned(al::popcount(flags));
1865 mResampler = SampleConverter::Create(mDevice->FmtType, mDevice->FmtType, channelCount,
1866 mDevice->Frequency, mFormat.Format.nSamplesPerSec, Resampler::FastBSinc24);
1867 mResampleBuffer.resize(size_t{mDevice->UpdateSize} * channelCount *
1868 mFormat.Format.wBitsPerSample / 8);
1870 TRACE("Created converter for %s/%s format, dst: %luhz (%u), src: %uhz (%u)\n",
1871 DevFmtChannelsString(mDevice->FmtChans), DevFmtTypeString(mDevice->FmtType),
1872 mFormat.Format.nSamplesPerSec, mOutUpdateSize, mDevice->Frequency,
1873 mDevice->UpdateSize);
1876 return true;
1879 bool WasapiPlayback::reset()
1881 HRESULT hr{pushMessage(MsgType::ResetDevice).get()};
1882 if(FAILED(hr))
1883 throw al::backend_exception{al::backend_error::DeviceError, "0x%08lx", hr};
1884 return true;
1887 HRESULT WasapiPlayback::resetProxy()
1889 if(GetConfigValueBool(mDevice->mDeviceName, "wasapi", "spatial-api", false))
1891 if(initSpatial())
1892 return S_OK;
1895 mDevice->Flags.reset(Virtualization);
1897 auto &audio = mAudio.emplace<PlainDevice>();
1898 HRESULT hr{sDeviceHelper->activateAudioClient(mMMDev, __uuidof(IAudioClient),
1899 al::out_ptr(audio.mClient))};
1900 if(FAILED(hr))
1902 ERR("Failed to reactivate audio client: 0x%08lx\n", hr);
1903 return hr;
1906 WAVEFORMATEX *wfx;
1907 hr = audio.mClient->GetMixFormat(&wfx);
1908 if(FAILED(hr))
1910 ERR("Failed to get mix format: 0x%08lx\n", hr);
1911 return hr;
1913 TraceFormat("Device mix format", wfx);
1915 WAVEFORMATEXTENSIBLE OutputType;
1916 if(!MakeExtensible(&OutputType, wfx))
1918 CoTaskMemFree(wfx);
1919 return E_FAIL;
1921 CoTaskMemFree(wfx);
1922 wfx = nullptr;
1924 const ReferenceTime per_time{ReferenceTime{seconds{mDevice->UpdateSize}} / mDevice->Frequency};
1925 const ReferenceTime buf_time{ReferenceTime{seconds{mDevice->BufferSize}} / mDevice->Frequency};
1927 prepareFormat(OutputType);
1929 TraceFormat("Requesting playback format", &OutputType.Format);
1930 hr = audio.mClient->IsFormatSupported(AUDCLNT_SHAREMODE_SHARED, &OutputType.Format, &wfx);
1931 if(FAILED(hr))
1933 WARN("Failed to check format support: 0x%08lx\n", hr);
1934 hr = audio.mClient->GetMixFormat(&wfx);
1936 if(FAILED(hr))
1938 ERR("Failed to find a supported format: 0x%08lx\n", hr);
1939 return hr;
1942 if(wfx != nullptr)
1944 TraceFormat("Got playback format", wfx);
1945 if(!MakeExtensible(&OutputType, wfx))
1947 CoTaskMemFree(wfx);
1948 return E_FAIL;
1950 CoTaskMemFree(wfx);
1951 wfx = nullptr;
1953 finalizeFormat(OutputType);
1955 mFormat = OutputType;
1957 #if !defined(ALSOFT_UWP)
1958 const EndpointFormFactor formfactor{GetDeviceFormfactor(mMMDev.get())};
1959 mDevice->Flags.set(DirectEar, (formfactor == Headphones || formfactor == Headset));
1960 #else
1961 mDevice->Flags.set(DirectEar, false);
1962 #endif
1963 setDefaultWFXChannelOrder();
1965 hr = audio.mClient->Initialize(AUDCLNT_SHAREMODE_SHARED, AUDCLNT_STREAMFLAGS_EVENTCALLBACK,
1966 buf_time.count(), 0, &OutputType.Format, nullptr);
1967 if(FAILED(hr))
1969 ERR("Failed to initialize audio client: 0x%08lx\n", hr);
1970 return hr;
1973 UINT32 buffer_len{};
1974 ReferenceTime min_per{};
1975 hr = audio.mClient->GetDevicePeriod(&reinterpret_cast<REFERENCE_TIME&>(min_per), nullptr);
1976 if(SUCCEEDED(hr))
1977 hr = audio.mClient->GetBufferSize(&buffer_len);
1978 if(FAILED(hr))
1980 ERR("Failed to get audio buffer info: 0x%08lx\n", hr);
1981 return hr;
1984 hr = audio.mClient->SetEventHandle(mNotifyEvent);
1985 if(FAILED(hr))
1987 ERR("Failed to set event handle: 0x%08lx\n", hr);
1988 return hr;
1991 hr = audio.mClient->GetService(__uuidof(IAudioRenderClient), al::out_ptr(audio.mRender));
1992 if(FAILED(hr))
1994 ERR("Failed to get IAudioRenderClient: 0x%08lx\n", hr);
1995 return hr;
1998 /* Find the nearest multiple of the period size to the update size */
1999 if(min_per < per_time)
2000 min_per *= std::max<int64_t>((per_time + min_per/2) / min_per, 1_i64);
2002 mOutBufferSize = buffer_len;
2003 mOutUpdateSize = std::min(RefTime2Samples(min_per, mFormat.Format.nSamplesPerSec),
2004 buffer_len/2u);
2006 mDevice->BufferSize = static_cast<uint>(uint64_t{buffer_len} * mDevice->Frequency /
2007 mFormat.Format.nSamplesPerSec);
2008 mDevice->UpdateSize = std::min(RefTime2Samples(min_per, mDevice->Frequency),
2009 mDevice->BufferSize/2u);
2011 mResampler = nullptr;
2012 mResampleBuffer.clear();
2013 mResampleBuffer.shrink_to_fit();
2014 mBufferFilled = 0;
2015 if(mDevice->Frequency != mFormat.Format.nSamplesPerSec)
2017 mResampler = SampleConverter::Create(mDevice->FmtType, mDevice->FmtType,
2018 mFormat.Format.nChannels, mDevice->Frequency, mFormat.Format.nSamplesPerSec,
2019 Resampler::FastBSinc24);
2020 mResampleBuffer.resize(size_t{mDevice->UpdateSize} * mFormat.Format.nChannels *
2021 mFormat.Format.wBitsPerSample / 8);
2023 TRACE("Created converter for %s/%s format, dst: %luhz (%u), src: %uhz (%u)\n",
2024 DevFmtChannelsString(mDevice->FmtChans), DevFmtTypeString(mDevice->FmtType),
2025 mFormat.Format.nSamplesPerSec, mOutUpdateSize, mDevice->Frequency,
2026 mDevice->UpdateSize);
2029 return hr;
2033 void WasapiPlayback::start()
2035 const HRESULT hr{pushMessage(MsgType::StartDevice).get()};
2036 if(FAILED(hr))
2037 throw al::backend_exception{al::backend_error::DeviceError,
2038 "Failed to start playback: 0x%lx", hr};
2041 HRESULT WasapiPlayback::startProxy()
2043 ResetEvent(mNotifyEvent);
2045 auto start_plain = [&](PlainDevice &audio) -> HRESULT
2047 HRESULT hr{audio.mClient->Start()};
2048 if(FAILED(hr))
2050 ERR("Failed to start audio client: 0x%08lx\n", hr);
2051 return hr;
2054 try {
2055 mKillNow.store(false, std::memory_order_release);
2056 mThread = std::thread{std::mem_fn(&WasapiPlayback::mixerProc), this};
2058 catch(...) {
2059 ERR("Failed to start thread\n");
2060 audio.mClient->Stop();
2061 hr = E_FAIL;
2063 return hr;
2065 auto start_spatial = [&](SpatialDevice &audio) -> HRESULT
2067 HRESULT hr{audio.mRender->Start()};
2068 if(FAILED(hr))
2070 ERR("Failed to start spatial audio stream: 0x%08lx\n", hr);
2071 return hr;
2074 try {
2075 mKillNow.store(false, std::memory_order_release);
2076 mThread = std::thread{std::mem_fn(&WasapiPlayback::mixerSpatialProc), this};
2078 catch(...) {
2079 ERR("Failed to start thread\n");
2080 hr = E_FAIL;
2083 if(FAILED(hr))
2085 audio.mRender->Stop();
2086 audio.mRender->Reset();
2088 return hr;
2091 return std::visit(overloaded{start_plain, start_spatial}, mAudio);
2095 void WasapiPlayback::stop()
2096 { pushMessage(MsgType::StopDevice).wait(); }
2098 void WasapiPlayback::stopProxy()
2100 if(!mThread.joinable())
2101 return;
2103 mKillNow.store(true, std::memory_order_release);
2104 mThread.join();
2106 auto stop_plain = [](PlainDevice &audio) -> void
2107 { audio.mClient->Stop(); };
2108 auto stop_spatial = [](SpatialDevice &audio) -> void
2110 audio.mRender->Stop();
2111 audio.mRender->Reset();
2113 std::visit(overloaded{stop_plain, stop_spatial}, mAudio);
2117 ClockLatency WasapiPlayback::getClockLatency()
2119 std::lock_guard<std::mutex> dlock{mMutex};
2120 ClockLatency ret{};
2121 ret.ClockTime = mDevice->getClockTime();
2122 ret.Latency = seconds{mPadding.load(std::memory_order_relaxed)};
2123 ret.Latency /= mFormat.Format.nSamplesPerSec;
2124 if(mResampler)
2126 auto extra = mResampler->currentInputDelay();
2127 ret.Latency += std::chrono::duration_cast<nanoseconds>(extra) / mDevice->Frequency;
2128 ret.Latency += nanoseconds{seconds{mBufferFilled}} / mDevice->Frequency;
2131 return ret;
2135 struct WasapiCapture final : public BackendBase, WasapiProxy {
2136 WasapiCapture(DeviceBase *device) noexcept : BackendBase{device} { }
2137 ~WasapiCapture() override;
2139 int recordProc();
2141 void open(std::string_view name) override;
2142 HRESULT openProxy(std::string_view name) override;
2143 void closeProxy() override;
2145 HRESULT resetProxy() override;
2146 void start() override;
2147 HRESULT startProxy() override;
2148 void stop() override;
2149 void stopProxy() override;
2151 void captureSamples(std::byte *buffer, uint samples) override;
2152 uint availableSamples() override;
2154 HRESULT mOpenStatus{E_FAIL};
2155 DeviceHandle mMMDev{nullptr};
2156 ComPtr<IAudioClient> mClient{nullptr};
2157 ComPtr<IAudioCaptureClient> mCapture{nullptr};
2158 HANDLE mNotifyEvent{nullptr};
2160 ChannelConverter mChannelConv{};
2161 SampleConverterPtr mSampleConv;
2162 RingBufferPtr mRing;
2164 std::atomic<bool> mKillNow{true};
2165 std::thread mThread;
2168 WasapiCapture::~WasapiCapture()
2170 if(SUCCEEDED(mOpenStatus))
2171 pushMessage(MsgType::CloseDevice).wait();
2172 mOpenStatus = E_FAIL;
2174 if(mNotifyEvent != nullptr)
2175 CloseHandle(mNotifyEvent);
2176 mNotifyEvent = nullptr;
2180 FORCE_ALIGN int WasapiCapture::recordProc()
2182 ComWrapper com{COINIT_MULTITHREADED};
2183 if(!com)
2185 ERR("CoInitializeEx(nullptr, COINIT_MULTITHREADED) failed: 0x%08lx\n", com.status());
2186 mDevice->handleDisconnect("COM init failed: 0x%08lx", com.status());
2187 return 1;
2190 althrd_setname(GetRecordThreadName());
2192 std::vector<float> samples;
2193 while(!mKillNow.load(std::memory_order_relaxed))
2195 UINT32 avail;
2196 HRESULT hr{mCapture->GetNextPacketSize(&avail)};
2197 if(FAILED(hr))
2198 ERR("Failed to get next packet size: 0x%08lx\n", hr);
2199 else if(avail > 0)
2201 UINT32 numsamples;
2202 DWORD flags;
2203 BYTE *rdata;
2205 hr = mCapture->GetBuffer(&rdata, &numsamples, &flags, nullptr, nullptr);
2206 if(FAILED(hr))
2207 ERR("Failed to get capture buffer: 0x%08lx\n", hr);
2208 else
2210 if(mChannelConv.is_active())
2212 samples.resize(numsamples*2_uz);
2213 mChannelConv.convert(rdata, samples.data(), numsamples);
2214 rdata = reinterpret_cast<BYTE*>(samples.data());
2217 auto data = mRing->getWriteVector();
2219 size_t dstframes;
2220 if(mSampleConv)
2222 static constexpr auto lenlimit = size_t{std::numeric_limits<int>::max()};
2223 const void *srcdata{rdata};
2224 uint srcframes{numsamples};
2226 dstframes = mSampleConv->convert(&srcdata, &srcframes, data[0].buf,
2227 static_cast<uint>(std::min(data[0].len, lenlimit)));
2228 if(srcframes > 0 && dstframes == data[0].len && data[1].len > 0)
2230 /* If some source samples remain, all of the first dest
2231 * block was filled, and there's space in the second
2232 * dest block, do another run for the second block.
2234 dstframes += mSampleConv->convert(&srcdata, &srcframes, data[1].buf,
2235 static_cast<uint>(std::min(data[1].len, lenlimit)));
2238 else
2240 const uint framesize{mDevice->frameSizeFromFmt()};
2241 auto dst = al::span{rdata, size_t{numsamples}*framesize};
2242 size_t len1{std::min(data[0].len, size_t{numsamples})};
2243 size_t len2{std::min(data[1].len, numsamples-len1)};
2245 memcpy(data[0].buf, dst.data(), len1*framesize);
2246 if(len2 > 0)
2248 dst = dst.subspan(len1*framesize);
2249 memcpy(data[1].buf, dst.data(), len2*framesize);
2251 dstframes = len1 + len2;
2254 mRing->writeAdvance(dstframes);
2256 hr = mCapture->ReleaseBuffer(numsamples);
2257 if(FAILED(hr)) ERR("Failed to release capture buffer: 0x%08lx\n", hr);
2261 if(FAILED(hr))
2263 mDevice->handleDisconnect("Failed to capture samples: 0x%08lx", hr);
2264 break;
2267 DWORD res{WaitForSingleObjectEx(mNotifyEvent, 2000, FALSE)};
2268 if(res != WAIT_OBJECT_0)
2269 ERR("WaitForSingleObjectEx error: 0x%lx\n", res);
2272 return 0;
2276 void WasapiCapture::open(std::string_view name)
2278 if(SUCCEEDED(mOpenStatus))
2279 throw al::backend_exception{al::backend_error::DeviceError,
2280 "Unexpected duplicate open call"};
2282 mNotifyEvent = CreateEventW(nullptr, FALSE, FALSE, nullptr);
2283 if(mNotifyEvent == nullptr)
2285 ERR("Failed to create notify events: %lu\n", GetLastError());
2286 throw al::backend_exception{al::backend_error::DeviceError,
2287 "Failed to create notify events"};
2290 mOpenStatus = pushMessage(MsgType::OpenDevice, name).get();
2291 if(FAILED(mOpenStatus))
2292 throw al::backend_exception{al::backend_error::DeviceError, "Device init failed: 0x%08lx",
2293 mOpenStatus};
2295 HRESULT hr{pushMessage(MsgType::ResetDevice).get()};
2296 if(FAILED(hr))
2298 if(hr == E_OUTOFMEMORY)
2299 throw al::backend_exception{al::backend_error::OutOfMemory, "Out of memory"};
2300 throw al::backend_exception{al::backend_error::DeviceError, "Device reset failed"};
2304 HRESULT WasapiCapture::openProxy(std::string_view name)
2306 std::string devname;
2307 std::wstring devid;
2308 if(!name.empty())
2310 auto devlock = DeviceListLock{gDeviceList};
2311 auto devlist = al::span{devlock.getCaptureList()};
2312 auto iter = std::find_if(devlist.cbegin(), devlist.cend(),
2313 [name](const DevMap &entry) -> bool
2314 { return entry.name == name || entry.endpoint_guid == name; });
2315 if(iter == devlist.cend())
2317 const std::wstring wname{utf8_to_wstr(name)};
2318 iter = std::find_if(devlist.cbegin(), devlist.cend(),
2319 [&wname](const DevMap &entry) -> bool
2320 { return entry.devid == wname; });
2322 if(iter == devlist.cend())
2324 WARN("Failed to find device name matching \"%.*s\"\n", al::sizei(name), name.data());
2325 return E_FAIL;
2327 devname = iter->name;
2328 devid = iter->devid;
2331 HRESULT hr{sDeviceHelper->openDevice(devid, eCapture, mMMDev)};
2332 if(FAILED(hr))
2334 WARN("Failed to open device \"%s\"\n", devname.empty() ? "(default)" : devname.c_str());
2335 return hr;
2337 mClient = nullptr;
2338 if(!devname.empty())
2339 mDeviceName = std::move(devname);
2340 else
2341 mDeviceName = GetDeviceNameAndGuid(mMMDev).mName;
2343 return S_OK;
2346 void WasapiCapture::closeProxy()
2348 mCapture = nullptr;
2349 mClient = nullptr;
2350 mMMDev = nullptr;
2353 HRESULT WasapiCapture::resetProxy()
2355 mCapture = nullptr;
2356 mClient = nullptr;
2358 HRESULT hr{sDeviceHelper->activateAudioClient(mMMDev, __uuidof(IAudioClient),
2359 al::out_ptr(mClient))};
2360 if(FAILED(hr))
2362 ERR("Failed to reactivate audio client: 0x%08lx\n", hr);
2363 return hr;
2366 WAVEFORMATEX *wfx;
2367 hr = mClient->GetMixFormat(&wfx);
2368 if(FAILED(hr))
2370 ERR("Failed to get capture format: 0x%08lx\n", hr);
2371 return hr;
2373 TraceFormat("Device capture format", wfx);
2375 WAVEFORMATEXTENSIBLE InputType{};
2376 if(!MakeExtensible(&InputType, wfx))
2378 CoTaskMemFree(wfx);
2379 return E_FAIL;
2381 CoTaskMemFree(wfx);
2382 wfx = nullptr;
2384 const bool isRear51{InputType.Format.nChannels == 6
2385 && (InputType.dwChannelMask&X51RearMask) == X5DOT1REAR};
2387 // Make sure buffer is at least 100ms in size
2388 ReferenceTime buf_time{ReferenceTime{seconds{mDevice->BufferSize}} / mDevice->Frequency};
2389 buf_time = std::max(buf_time, ReferenceTime{milliseconds{100}});
2391 InputType = {};
2392 InputType.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
2393 switch(mDevice->FmtChans)
2395 case DevFmtMono:
2396 InputType.Format.nChannels = 1;
2397 InputType.dwChannelMask = MONO;
2398 break;
2399 case DevFmtStereo:
2400 InputType.Format.nChannels = 2;
2401 InputType.dwChannelMask = STEREO;
2402 break;
2403 case DevFmtQuad:
2404 InputType.Format.nChannels = 4;
2405 InputType.dwChannelMask = QUAD;
2406 break;
2407 case DevFmtX51:
2408 InputType.Format.nChannels = 6;
2409 InputType.dwChannelMask = isRear51 ? X5DOT1REAR : X5DOT1;
2410 break;
2411 case DevFmtX61:
2412 InputType.Format.nChannels = 7;
2413 InputType.dwChannelMask = X6DOT1;
2414 break;
2415 case DevFmtX71:
2416 InputType.Format.nChannels = 8;
2417 InputType.dwChannelMask = X7DOT1;
2418 break;
2419 case DevFmtX714:
2420 InputType.Format.nChannels = 12;
2421 InputType.dwChannelMask = X7DOT1DOT4;
2422 break;
2424 case DevFmtX7144:
2425 case DevFmtX3D71:
2426 case DevFmtAmbi3D:
2427 return E_FAIL;
2429 switch(mDevice->FmtType)
2431 /* NOTE: Signedness doesn't matter, the converter will handle it. */
2432 case DevFmtByte:
2433 case DevFmtUByte:
2434 InputType.Format.wBitsPerSample = 8;
2435 InputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
2436 break;
2437 case DevFmtShort:
2438 case DevFmtUShort:
2439 InputType.Format.wBitsPerSample = 16;
2440 InputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
2441 break;
2442 case DevFmtInt:
2443 case DevFmtUInt:
2444 InputType.Format.wBitsPerSample = 32;
2445 InputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
2446 break;
2447 case DevFmtFloat:
2448 InputType.Format.wBitsPerSample = 32;
2449 InputType.SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT;
2450 break;
2452 /* NOLINTNEXTLINE(cppcoreguidelines-pro-type-union-access) */
2453 InputType.Samples.wValidBitsPerSample = InputType.Format.wBitsPerSample;
2454 InputType.Format.nSamplesPerSec = mDevice->Frequency;
2456 InputType.Format.nBlockAlign = static_cast<WORD>(InputType.Format.nChannels *
2457 InputType.Format.wBitsPerSample / 8);
2458 InputType.Format.nAvgBytesPerSec = InputType.Format.nSamplesPerSec *
2459 InputType.Format.nBlockAlign;
2460 InputType.Format.cbSize = sizeof(InputType) - sizeof(InputType.Format);
2462 TraceFormat("Requesting capture format", &InputType.Format);
2463 hr = mClient->IsFormatSupported(AUDCLNT_SHAREMODE_SHARED, &InputType.Format, &wfx);
2464 if(FAILED(hr))
2466 WARN("Failed to check capture format support: 0x%08lx\n", hr);
2467 hr = mClient->GetMixFormat(&wfx);
2469 if(FAILED(hr))
2471 ERR("Failed to find a supported capture format: 0x%08lx\n", hr);
2472 return hr;
2475 mSampleConv = nullptr;
2476 mChannelConv = {};
2478 if(wfx != nullptr)
2480 TraceFormat("Got capture format", wfx);
2481 if(!MakeExtensible(&InputType, wfx))
2483 CoTaskMemFree(wfx);
2484 return E_FAIL;
2486 CoTaskMemFree(wfx);
2487 wfx = nullptr;
2489 auto validate_fmt = [](DeviceBase *device, uint32_t chancount, DWORD chanmask) noexcept
2490 -> bool
2492 switch(device->FmtChans)
2494 /* If the device wants mono, we can handle any input. */
2495 case DevFmtMono:
2496 return true;
2497 /* If the device wants stereo, we can handle mono or stereo input. */
2498 case DevFmtStereo:
2499 return (chancount == 2 && (chanmask == 0 || (chanmask&StereoMask) == STEREO))
2500 || (chancount == 1 && (chanmask&MonoMask) == MONO);
2501 /* Otherwise, the device must match the input type. */
2502 case DevFmtQuad:
2503 return (chancount == 4 && (chanmask == 0 || (chanmask&QuadMask) == QUAD));
2504 /* 5.1 (Side) and 5.1 (Rear) are interchangeable here. */
2505 case DevFmtX51:
2506 return (chancount == 6 && (chanmask == 0 || (chanmask&X51Mask) == X5DOT1
2507 || (chanmask&X51RearMask) == X5DOT1REAR));
2508 case DevFmtX61:
2509 return (chancount == 7 && (chanmask == 0 || (chanmask&X61Mask) == X6DOT1));
2510 case DevFmtX71:
2511 case DevFmtX3D71:
2512 return (chancount == 8 && (chanmask == 0 || (chanmask&X71Mask) == X7DOT1));
2513 case DevFmtX714:
2514 return (chancount == 12 && (chanmask == 0 || (chanmask&X714Mask) == X7DOT1DOT4));
2515 case DevFmtX7144:
2516 return (chancount == 16 && chanmask == 0);
2517 case DevFmtAmbi3D:
2518 return (chanmask == 0 && chancount == device->channelsFromFmt());
2520 return false;
2522 if(!validate_fmt(mDevice, InputType.Format.nChannels, InputType.dwChannelMask))
2524 ERR("Failed to match format, wanted: %s %s %uhz, got: 0x%08lx mask %d channel%s %d-bit %luhz\n",
2525 DevFmtChannelsString(mDevice->FmtChans), DevFmtTypeString(mDevice->FmtType),
2526 mDevice->Frequency, InputType.dwChannelMask, InputType.Format.nChannels,
2527 (InputType.Format.nChannels==1)?"":"s", InputType.Format.wBitsPerSample,
2528 InputType.Format.nSamplesPerSec);
2529 return E_FAIL;
2533 DevFmtType srcType{};
2534 if(IsEqualGUID(InputType.SubFormat, KSDATAFORMAT_SUBTYPE_PCM))
2536 if(InputType.Format.wBitsPerSample == 8)
2537 srcType = DevFmtUByte;
2538 else if(InputType.Format.wBitsPerSample == 16)
2539 srcType = DevFmtShort;
2540 else if(InputType.Format.wBitsPerSample == 32)
2541 srcType = DevFmtInt;
2542 else
2544 ERR("Unhandled integer bit depth: %d\n", InputType.Format.wBitsPerSample);
2545 return E_FAIL;
2548 else if(IsEqualGUID(InputType.SubFormat, KSDATAFORMAT_SUBTYPE_IEEE_FLOAT))
2550 if(InputType.Format.wBitsPerSample == 32)
2551 srcType = DevFmtFloat;
2552 else
2554 ERR("Unhandled float bit depth: %d\n", InputType.Format.wBitsPerSample);
2555 return E_FAIL;
2558 else
2560 ERR("Unhandled format sub-type: %s\n", GuidPrinter{InputType.SubFormat}.c_str());
2561 return E_FAIL;
2564 if(mDevice->FmtChans == DevFmtMono && InputType.Format.nChannels != 1)
2566 uint chanmask{(1u<<InputType.Format.nChannels) - 1u};
2567 /* Exclude LFE from the downmix. */
2568 if((InputType.dwChannelMask&SPEAKER_LOW_FREQUENCY))
2570 constexpr auto lfemask = MaskFromTopBits(SPEAKER_LOW_FREQUENCY);
2571 const int lfeidx{al::popcount(InputType.dwChannelMask&lfemask) - 1};
2572 chanmask &= ~(1u << lfeidx);
2575 mChannelConv = ChannelConverter{srcType, InputType.Format.nChannels, chanmask,
2576 mDevice->FmtChans};
2577 TRACE("Created %s multichannel-to-mono converter\n", DevFmtTypeString(srcType));
2578 /* The channel converter always outputs float, so change the input type
2579 * for the resampler/type-converter.
2581 srcType = DevFmtFloat;
2583 else if(mDevice->FmtChans == DevFmtStereo && InputType.Format.nChannels == 1)
2585 mChannelConv = ChannelConverter{srcType, 1, 0x1, mDevice->FmtChans};
2586 TRACE("Created %s mono-to-stereo converter\n", DevFmtTypeString(srcType));
2587 srcType = DevFmtFloat;
2590 if(mDevice->Frequency != InputType.Format.nSamplesPerSec || mDevice->FmtType != srcType)
2592 mSampleConv = SampleConverter::Create(srcType, mDevice->FmtType,
2593 mDevice->channelsFromFmt(), InputType.Format.nSamplesPerSec, mDevice->Frequency,
2594 Resampler::FastBSinc24);
2595 if(!mSampleConv)
2597 ERR("Failed to create converter for %s format, dst: %s %uhz, src: %s %luhz\n",
2598 DevFmtChannelsString(mDevice->FmtChans), DevFmtTypeString(mDevice->FmtType),
2599 mDevice->Frequency, DevFmtTypeString(srcType), InputType.Format.nSamplesPerSec);
2600 return E_FAIL;
2602 TRACE("Created converter for %s format, dst: %s %uhz, src: %s %luhz\n",
2603 DevFmtChannelsString(mDevice->FmtChans), DevFmtTypeString(mDevice->FmtType),
2604 mDevice->Frequency, DevFmtTypeString(srcType), InputType.Format.nSamplesPerSec);
2607 hr = mClient->Initialize(AUDCLNT_SHAREMODE_SHARED, AUDCLNT_STREAMFLAGS_EVENTCALLBACK,
2608 buf_time.count(), 0, &InputType.Format, nullptr);
2609 if(FAILED(hr))
2611 ERR("Failed to initialize audio client: 0x%08lx\n", hr);
2612 return hr;
2615 hr = mClient->GetService(__uuidof(IAudioCaptureClient), al::out_ptr(mCapture));
2616 if(FAILED(hr))
2618 ERR("Failed to get IAudioCaptureClient: 0x%08lx\n", hr);
2619 return hr;
2622 UINT32 buffer_len{};
2623 ReferenceTime min_per{};
2624 hr = mClient->GetDevicePeriod(&reinterpret_cast<REFERENCE_TIME&>(min_per), nullptr);
2625 if(SUCCEEDED(hr))
2626 hr = mClient->GetBufferSize(&buffer_len);
2627 if(FAILED(hr))
2629 ERR("Failed to get buffer size: 0x%08lx\n", hr);
2630 return hr;
2632 mDevice->UpdateSize = RefTime2Samples(min_per, mDevice->Frequency);
2633 mDevice->BufferSize = buffer_len;
2635 mRing = RingBuffer::Create(buffer_len, mDevice->frameSizeFromFmt(), false);
2637 hr = mClient->SetEventHandle(mNotifyEvent);
2638 if(FAILED(hr))
2640 ERR("Failed to set event handle: 0x%08lx\n", hr);
2641 return hr;
2644 return hr;
2648 void WasapiCapture::start()
2650 const HRESULT hr{pushMessage(MsgType::StartDevice).get()};
2651 if(FAILED(hr))
2652 throw al::backend_exception{al::backend_error::DeviceError,
2653 "Failed to start recording: 0x%lx", hr};
2656 HRESULT WasapiCapture::startProxy()
2658 ResetEvent(mNotifyEvent);
2660 HRESULT hr{mClient->Start()};
2661 if(FAILED(hr))
2663 ERR("Failed to start audio client: 0x%08lx\n", hr);
2664 return hr;
2667 try {
2668 mKillNow.store(false, std::memory_order_release);
2669 mThread = std::thread{std::mem_fn(&WasapiCapture::recordProc), this};
2671 catch(...) {
2672 ERR("Failed to start thread\n");
2673 mClient->Stop();
2674 mClient->Reset();
2675 hr = E_FAIL;
2678 return hr;
2682 void WasapiCapture::stop()
2683 { pushMessage(MsgType::StopDevice).wait(); }
2685 void WasapiCapture::stopProxy()
2687 if(!mThread.joinable())
2688 return;
2690 mKillNow.store(true, std::memory_order_release);
2691 mThread.join();
2693 mClient->Stop();
2694 mClient->Reset();
2698 void WasapiCapture::captureSamples(std::byte *buffer, uint samples)
2699 { std::ignore = mRing->read(buffer, samples); }
2701 uint WasapiCapture::availableSamples()
2702 { return static_cast<uint>(mRing->readSpace()); }
2704 } // namespace
2707 bool WasapiBackendFactory::init()
2709 static HRESULT InitResult{E_FAIL};
2710 if(FAILED(InitResult)) try
2712 std::promise<HRESULT> promise;
2713 auto future = promise.get_future();
2715 std::thread{&WasapiProxy::messageHandler, &promise}.detach();
2716 InitResult = future.get();
2718 catch(...) {
2721 return SUCCEEDED(InitResult);
2724 bool WasapiBackendFactory::querySupport(BackendType type)
2725 { return type == BackendType::Playback || type == BackendType::Capture; }
2727 auto WasapiBackendFactory::enumerate(BackendType type) -> std::vector<std::string>
2729 std::vector<std::string> outnames;
2731 auto devlock = DeviceListLock{gDeviceList};
2732 switch(type)
2734 case BackendType::Playback:
2736 auto defaultId = devlock.getPlaybackDefaultId();
2737 for(const DevMap &entry : devlock.getPlaybackList())
2739 if(entry.devid != defaultId)
2741 outnames.emplace_back(entry.name);
2742 continue;
2744 /* Default device goes first. */
2745 outnames.emplace(outnames.cbegin(), entry.name);
2748 break;
2750 case BackendType::Capture:
2752 auto defaultId = devlock.getCaptureDefaultId();
2753 for(const DevMap &entry : devlock.getCaptureList())
2755 if(entry.devid != defaultId)
2757 outnames.emplace_back(entry.name);
2758 continue;
2760 outnames.emplace(outnames.cbegin(), entry.name);
2763 break;
2766 return outnames;
2769 BackendPtr WasapiBackendFactory::createBackend(DeviceBase *device, BackendType type)
2771 if(type == BackendType::Playback)
2772 return BackendPtr{new WasapiPlayback{device}};
2773 if(type == BackendType::Capture)
2774 return BackendPtr{new WasapiCapture{device}};
2775 return nullptr;
2778 BackendFactory &WasapiBackendFactory::getFactory()
2780 static WasapiBackendFactory factory{};
2781 return factory;
2784 alc::EventSupport WasapiBackendFactory::queryEventSupport(alc::EventType eventType, BackendType)
2786 switch(eventType)
2788 case alc::EventType::DefaultDeviceChanged:
2789 return alc::EventSupport::FullSupport;
2791 case alc::EventType::DeviceAdded:
2792 case alc::EventType::DeviceRemoved:
2793 #if !defined(ALSOFT_UWP)
2794 return alc::EventSupport::FullSupport;
2795 #endif
2797 case alc::EventType::Count:
2798 break;
2800 return alc::EventSupport::NoSupport;