Enable proper full C++ exception handling on MSVC
[openal-soft.git] / alc / backends / wasapi.cpp
blob6c52dca69ecf950eda51d9a1e54fbd930aa2fb7a
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 [[nodiscard]] constexpr auto GetDevicePrefix() noexcept { return "OpenAL Soft on "sv; }
118 using ReferenceTime = std::chrono::duration<REFERENCE_TIME,std::ratio<1,10'000'000>>;
121 #define MONO SPEAKER_FRONT_CENTER
122 #define STEREO (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT)
123 #define QUAD (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT|SPEAKER_BACK_LEFT|SPEAKER_BACK_RIGHT)
124 #define X5DOT1 (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT|SPEAKER_FRONT_CENTER|SPEAKER_LOW_FREQUENCY|SPEAKER_SIDE_LEFT|SPEAKER_SIDE_RIGHT)
125 #define X5DOT1REAR (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT|SPEAKER_FRONT_CENTER|SPEAKER_LOW_FREQUENCY|SPEAKER_BACK_LEFT|SPEAKER_BACK_RIGHT)
126 #define X6DOT1 (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT|SPEAKER_FRONT_CENTER|SPEAKER_LOW_FREQUENCY|SPEAKER_BACK_CENTER|SPEAKER_SIDE_LEFT|SPEAKER_SIDE_RIGHT)
127 #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)
128 #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)
130 constexpr inline DWORD MaskFromTopBits(DWORD b) noexcept
132 b |= b>>1;
133 b |= b>>2;
134 b |= b>>4;
135 b |= b>>8;
136 b |= b>>16;
137 return b;
139 constexpr DWORD MonoMask{MaskFromTopBits(MONO)};
140 constexpr DWORD StereoMask{MaskFromTopBits(STEREO)};
141 constexpr DWORD QuadMask{MaskFromTopBits(QUAD)};
142 constexpr DWORD X51Mask{MaskFromTopBits(X5DOT1)};
143 constexpr DWORD X51RearMask{MaskFromTopBits(X5DOT1REAR)};
144 constexpr DWORD X61Mask{MaskFromTopBits(X6DOT1)};
145 constexpr DWORD X71Mask{MaskFromTopBits(X7DOT1)};
146 constexpr DWORD X714Mask{MaskFromTopBits(X7DOT1DOT4)};
149 #ifndef _MSC_VER
150 constexpr AudioObjectType operator|(AudioObjectType lhs, AudioObjectType rhs) noexcept
151 { return static_cast<AudioObjectType>(lhs | al::to_underlying(rhs)); }
152 #endif
154 constexpr AudioObjectType ChannelMask_Mono{AudioObjectType_FrontCenter};
155 constexpr AudioObjectType ChannelMask_Stereo{AudioObjectType_FrontLeft
156 | AudioObjectType_FrontRight};
157 constexpr AudioObjectType ChannelMask_Quad{AudioObjectType_FrontLeft | AudioObjectType_FrontRight
158 | AudioObjectType_BackLeft | AudioObjectType_BackRight};
159 constexpr AudioObjectType ChannelMask_X51{AudioObjectType_FrontLeft | AudioObjectType_FrontRight
160 | AudioObjectType_FrontCenter | AudioObjectType_LowFrequency | AudioObjectType_SideLeft
161 | AudioObjectType_SideRight};
162 constexpr AudioObjectType ChannelMask_X51Rear{AudioObjectType_FrontLeft
163 | AudioObjectType_FrontRight | AudioObjectType_FrontCenter | AudioObjectType_LowFrequency
164 | AudioObjectType_BackLeft | AudioObjectType_BackRight};
165 constexpr AudioObjectType ChannelMask_X61{AudioObjectType_FrontLeft | AudioObjectType_FrontRight
166 | AudioObjectType_FrontCenter | AudioObjectType_LowFrequency | AudioObjectType_SideLeft
167 | AudioObjectType_SideRight | AudioObjectType_BackCenter};
168 constexpr AudioObjectType ChannelMask_X71{AudioObjectType_FrontLeft | AudioObjectType_FrontRight
169 | AudioObjectType_FrontCenter | AudioObjectType_LowFrequency | AudioObjectType_SideLeft
170 | AudioObjectType_SideRight | AudioObjectType_BackLeft | AudioObjectType_BackRight};
171 constexpr AudioObjectType ChannelMask_X714{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};
176 constexpr AudioObjectType ChannelMask_X7144{AudioObjectType_FrontLeft | AudioObjectType_FrontRight
177 | AudioObjectType_FrontCenter | AudioObjectType_LowFrequency | AudioObjectType_SideLeft
178 | AudioObjectType_SideRight | AudioObjectType_BackLeft | AudioObjectType_BackRight
179 | AudioObjectType_TopFrontLeft | AudioObjectType_TopFrontRight | AudioObjectType_TopBackLeft
180 | AudioObjectType_TopBackRight | AudioObjectType_BottomFrontLeft
181 | AudioObjectType_BottomFrontRight | AudioObjectType_BottomBackLeft
182 | AudioObjectType_BottomBackRight};
185 template<typename... Ts>
186 struct overloaded : Ts... { using Ts::operator()...; };
188 template<typename... Ts>
189 overloaded(Ts...) -> overloaded<Ts...>;
192 template<typename T>
193 constexpr auto as_unsigned(T value) noexcept
195 using UT = std::make_unsigned_t<T>;
196 return static_cast<UT>(value);
200 /* Scales the given reftime value, rounding the result. */
201 template<typename T>
202 constexpr uint RefTime2Samples(const ReferenceTime &val, T srate) noexcept
204 const auto retval = (val*srate + ReferenceTime{seconds{1}}/2) / seconds{1};
205 return static_cast<uint>(std::min<decltype(retval)>(retval, std::numeric_limits<uint>::max()));
209 class GuidPrinter {
210 std::array<char,64> mMsg{};
212 public:
213 GuidPrinter(const GUID &guid)
215 std::snprintf(mMsg.data(), mMsg.size(), "{%08lx-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x}",
216 DWORD{guid.Data1}, guid.Data2, guid.Data3, guid.Data4[0], guid.Data4[1], guid.Data4[2],
217 guid.Data4[3], guid.Data4[4], guid.Data4[5], guid.Data4[6], guid.Data4[7]);
219 [[nodiscard]] auto c_str() const -> const char* { return mMsg.data(); }
222 struct PropVariant {
223 PROPVARIANT mProp{};
225 public:
226 PropVariant() { PropVariantInit(&mProp); }
227 PropVariant(const PropVariant &rhs) : PropVariant{} { PropVariantCopy(&mProp, &rhs.mProp); }
228 ~PropVariant() { clear(); }
230 auto operator=(const PropVariant &rhs) -> PropVariant&
232 if(this != &rhs)
233 PropVariantCopy(&mProp, &rhs.mProp);
234 return *this;
237 void clear() { PropVariantClear(&mProp); }
239 PROPVARIANT* get() noexcept { return &mProp; }
241 /* NOLINTBEGIN(cppcoreguidelines-pro-type-union-access) */
242 [[nodiscard]]
243 auto type() const noexcept -> VARTYPE { return mProp.vt; }
245 template<typename T> [[nodiscard]]
246 auto value() const -> T
248 if constexpr(std::is_same_v<T,uint>)
250 alassert(mProp.vt == VT_UI4 || mProp.vt == VT_UINT);
251 return mProp.uintVal;
253 else if constexpr(std::is_same_v<T,std::wstring_view> || std::is_same_v<T,std::wstring>
254 || std::is_same_v<T,LPWSTR> || std::is_same_v<T,LPCWSTR>)
256 alassert(mProp.vt == VT_LPWSTR);
257 return mProp.pwszVal;
261 void setBlob(const al::span<BYTE> data)
263 if constexpr(sizeof(size_t) > sizeof(ULONG))
264 alassert(data.size() <= std::numeric_limits<ULONG>::max());
265 mProp.vt = VT_BLOB;
266 mProp.blob.cbSize = static_cast<ULONG>(data.size());
267 mProp.blob.pBlobData = data.data();
269 /* NOLINTEND(cppcoreguidelines-pro-type-union-access) */
272 struct DevMap {
273 std::string name;
274 std::string endpoint_guid; // obtained from PKEY_AudioEndpoint_GUID , set to "Unknown device GUID" if absent.
275 std::wstring devid;
277 template<typename T0, typename T1, typename T2>
278 DevMap(T0&& name_, T1&& guid_, T2&& devid_)
279 : name{std::forward<T0>(name_)}
280 , endpoint_guid{std::forward<T1>(guid_)}
281 , devid{std::forward<T2>(devid_)}
283 /* To prevent GCC from complaining it doesn't want to inline this. */
284 ~DevMap();
286 DevMap::~DevMap() = default;
288 bool checkName(const al::span<DevMap> list, const std::string_view name)
290 auto match_name = [name](const DevMap &entry) -> bool { return entry.name == name; };
291 return std::find_if(list.cbegin(), list.cend(), match_name) != list.cend();
295 struct DeviceList {
296 auto lock() noexcept(noexcept(mMutex.lock())) { return mMutex.lock(); }
297 auto unlock() noexcept(noexcept(mMutex.unlock())) { return mMutex.unlock(); }
299 private:
300 std::mutex mMutex;
301 std::vector<DevMap> mPlayback;
302 std::vector<DevMap> mCapture;
303 std::wstring mPlaybackDefaultId;
304 std::wstring mCaptureDefaultId;
306 friend struct DeviceListLock;
308 struct DeviceListLock : public std::unique_lock<DeviceList> {
309 using std::unique_lock<DeviceList>::unique_lock;
311 [[nodiscard]] auto& getPlaybackList() const noexcept { return mutex()->mPlayback; }
312 [[nodiscard]] auto& getCaptureList() const noexcept { return mutex()->mCapture; }
314 void setPlaybackDefaultId(std::wstring_view devid) const { mutex()->mPlaybackDefaultId = devid; }
315 [[nodiscard]] auto getPlaybackDefaultId() const noexcept -> std::wstring_view { return mutex()->mPlaybackDefaultId; }
316 void setCaptureDefaultId(std::wstring_view devid) const { mutex()->mCaptureDefaultId = devid; }
317 [[nodiscard]] auto getCaptureDefaultId() const noexcept -> std::wstring_view { return mutex()->mCaptureDefaultId; }
320 DeviceList gDeviceList;
323 #ifdef AVRTAPI
324 struct AvrtHandleCloser {
325 void operator()(HANDLE handle) { AvRevertMmThreadCharacteristics(handle); }
327 using AvrtHandlePtr = std::unique_ptr<std::remove_pointer_t<HANDLE>,AvrtHandleCloser>;
328 #endif
330 #if defined(ALSOFT_UWP)
331 enum EDataFlow {
332 eRender = 0,
333 eCapture = (eRender + 1),
334 eAll = (eCapture + 1),
335 EDataFlow_enum_count = (eAll + 1)
337 #endif
339 #if defined(ALSOFT_UWP)
340 using DeviceHandle = Windows::Devices::Enumeration::DeviceInformation;
341 using EventRegistrationToken = winrt::event_token;
342 #else
343 using DeviceHandle = ComPtr<IMMDevice>;
344 #endif
347 using NameGUIDPair = std::pair<std::string,std::string>;
348 NameGUIDPair GetDeviceNameAndGuid(const DeviceHandle &device)
350 constexpr auto UnknownName = "Unknown Device Name"sv;
351 constexpr auto UnknownGuid = "Unknown Device GUID"sv;
353 #if !defined(ALSOFT_UWP)
354 std::string name, guid;
356 ComPtr<IPropertyStore> ps;
357 HRESULT hr{device->OpenPropertyStore(STGM_READ, al::out_ptr(ps))};
358 if(FAILED(hr))
360 WARN("OpenPropertyStore failed: 0x%08lx\n", hr);
361 return {std::string{UnknownName}, std::string{UnknownGuid}};
364 PropVariant pvprop;
365 hr = ps->GetValue(al::bit_cast<PROPERTYKEY>(DEVPKEY_Device_FriendlyName), pvprop.get());
366 if(FAILED(hr))
367 WARN("GetValue Device_FriendlyName failed: 0x%08lx\n", hr);
368 else if(pvprop.type() == VT_LPWSTR)
369 name = wstr_to_utf8(pvprop.value<std::wstring_view>());
370 else
371 WARN("Unexpected Device_FriendlyName PROPVARIANT type: 0x%04x\n", pvprop.type());
373 pvprop.clear();
374 hr = ps->GetValue(al::bit_cast<PROPERTYKEY>(PKEY_AudioEndpoint_GUID), pvprop.get());
375 if(FAILED(hr))
376 WARN("GetValue AudioEndpoint_GUID failed: 0x%08lx\n", hr);
377 else if(pvprop.type() == VT_LPWSTR)
378 guid = wstr_to_utf8(pvprop.value<std::wstring_view>());
379 else
380 WARN("Unexpected AudioEndpoint_GUID PROPVARIANT type: 0x%04x\n", pvprop.type());
381 #else
382 std::string name{wstr_to_utf8(device.Name())};
383 std::string guid;
384 // device->Id is DeviceInterfacePath: \\?\SWD#MMDEVAPI#{0.0.0.00000000}.{a21c17a0-fc1d-405e-ab5a-b513422b57d1}#{e6327cad-dcec-4949-ae8a-991e976a79d2}
385 auto devIfPath = device.Id();
386 if(auto devIdStart = wcsstr(devIfPath.data(), L"}."))
388 devIdStart += 2; // L"}."
389 if(auto devIdStartEnd = wcschr(devIdStart, L'#'))
391 std::wstring wDevId{devIdStart, static_cast<size_t>(devIdStartEnd - devIdStart)};
392 guid = wstr_to_utf8(wDevId.c_str());
393 std::transform(guid.begin(), guid.end(), guid.begin(),
394 [](char ch) { return static_cast<char>(std::toupper(ch)); });
397 #endif
398 if(name.empty()) name = UnknownName;
399 if(guid.empty()) guid = UnknownGuid;
400 return {std::move(name), std::move(guid)};
402 #if !defined(ALSOFT_UWP)
403 EndpointFormFactor GetDeviceFormfactor(IMMDevice *device)
405 ComPtr<IPropertyStore> ps;
406 HRESULT hr{device->OpenPropertyStore(STGM_READ, al::out_ptr(ps))};
407 if(FAILED(hr))
409 WARN("OpenPropertyStore failed: 0x%08lx\n", hr);
410 return UnknownFormFactor;
413 EndpointFormFactor formfactor{UnknownFormFactor};
414 PropVariant pvform;
415 hr = ps->GetValue(PKEY_AudioEndpoint_FormFactor, pvform.get());
416 if(FAILED(hr))
417 WARN("GetValue AudioEndpoint_FormFactor failed: 0x%08lx\n", hr);
418 else if(pvform.type() == VT_UI4)
419 formfactor = static_cast<EndpointFormFactor>(pvform.value<uint>());
420 else if(pvform.type() != VT_EMPTY)
421 WARN("Unexpected PROPVARIANT type: 0x%04x\n", pvform.type());
422 return formfactor;
424 #endif
427 #if defined(ALSOFT_UWP)
428 struct DeviceHelper final : public IActivateAudioInterfaceCompletionHandler
429 #else
430 struct DeviceHelper final : private IMMNotificationClient
431 #endif
433 #if defined(ALSOFT_UWP)
434 DeviceHelper()
436 /* TODO: UWP also needs to watch for device added/removed events and
437 * dynamically add/remove devices from the lists.
439 mActiveClientEvent = CreateEventW(nullptr, FALSE, FALSE, nullptr);
441 mRenderDeviceChangedToken = MediaDevice::DefaultAudioRenderDeviceChanged([this](const IInspectable& /*sender*/, const DefaultAudioRenderDeviceChangedEventArgs& args) {
442 if (args.Role() == AudioDeviceRole::Default)
444 const std::string msg{ "Default playback device changed: " +
445 wstr_to_utf8(args.Id())};
446 alc::Event(alc::EventType::DefaultDeviceChanged, alc::DeviceType::Playback,
447 msg);
451 mCaptureDeviceChangedToken = MediaDevice::DefaultAudioCaptureDeviceChanged([this](const IInspectable& /*sender*/, const DefaultAudioCaptureDeviceChangedEventArgs& args) {
452 if (args.Role() == AudioDeviceRole::Default)
454 const std::string msg{ "Default capture device changed: " +
455 wstr_to_utf8(args.Id()) };
456 alc::Event(alc::EventType::DefaultDeviceChanged, alc::DeviceType::Capture,
457 msg);
461 #else
462 DeviceHelper() = default;
463 #endif
464 ~DeviceHelper()
466 #if defined(ALSOFT_UWP)
467 MediaDevice::DefaultAudioRenderDeviceChanged(mRenderDeviceChangedToken);
468 MediaDevice::DefaultAudioCaptureDeviceChanged(mCaptureDeviceChangedToken);
470 if(mActiveClientEvent != nullptr)
471 CloseHandle(mActiveClientEvent);
472 mActiveClientEvent = nullptr;
473 #else
474 if(mEnumerator)
475 mEnumerator->UnregisterEndpointNotificationCallback(this);
476 mEnumerator = nullptr;
477 #endif
480 /** -------------------------- IUnknown ----------------------------- */
481 std::atomic<ULONG> mRefCount{1};
482 STDMETHODIMP_(ULONG) AddRef() noexcept override { return mRefCount.fetch_add(1u) + 1u; }
483 STDMETHODIMP_(ULONG) Release() noexcept override { return mRefCount.fetch_sub(1u) - 1u; }
485 STDMETHODIMP QueryInterface(const IID& IId, void **UnknownPtrPtr) noexcept override
487 // Three rules of QueryInterface:
488 // https://docs.microsoft.com/en-us/windows/win32/com/rules-for-implementing-queryinterface
489 // 1. Objects must have identity.
490 // 2. The set of interfaces on an object instance must be static.
491 // 3. It must be possible to query successfully for any interface on an object from any other interface.
493 // If ppvObject(the address) is nullptr, then this method returns E_POINTER.
494 if(!UnknownPtrPtr)
495 return E_POINTER;
497 // https://docs.microsoft.com/en-us/windows/win32/com/implementing-reference-counting
498 // Whenever a client calls a method(or API function), such as QueryInterface, that returns a new interface
499 // pointer, the method being called is responsible for incrementing the reference count through the returned
500 // pointer. For example, when a client first creates an object, it receives an interface pointer to an object
501 // that, from the client's point of view, has a reference count of one. If the client then calls AddRef on the
502 // interface pointer, the reference count becomes two. The client must call Release twice on the interface
503 // pointer to drop all of its references to the object.
504 #if defined(ALSOFT_UWP)
505 if(IId == __uuidof(IActivateAudioInterfaceCompletionHandler))
507 *UnknownPtrPtr = static_cast<IActivateAudioInterfaceCompletionHandler*>(this);
508 AddRef();
509 return S_OK;
511 #else
512 if(IId == __uuidof(IMMNotificationClient))
514 *UnknownPtrPtr = static_cast<IMMNotificationClient*>(this);
515 AddRef();
516 return S_OK;
518 #endif
519 else if(IId == __uuidof(IAgileObject) || IId == __uuidof(IUnknown))
521 *UnknownPtrPtr = static_cast<IUnknown*>(this);
522 AddRef();
523 return S_OK;
526 // This method returns S_OK if the interface is supported, and E_NOINTERFACE otherwise.
527 *UnknownPtrPtr = nullptr;
528 return E_NOINTERFACE;
531 #if defined(ALSOFT_UWP)
532 /** ----------------------- IActivateAudioInterfaceCompletionHandler ------------ */
533 HRESULT ActivateCompleted(IActivateAudioInterfaceAsyncOperation*) override
535 SetEvent(mActiveClientEvent);
537 // Need to return S_OK
538 return S_OK;
540 #else
541 /** ----------------------- IMMNotificationClient ------------ */
542 STDMETHODIMP OnDeviceStateChanged(LPCWSTR /*pwstrDeviceId*/, DWORD /*dwNewState*/) noexcept override { return S_OK; }
544 STDMETHODIMP OnDeviceAdded(LPCWSTR pwstrDeviceId) noexcept override
546 ComPtr<IMMDevice> device;
547 HRESULT hr{mEnumerator->GetDevice(pwstrDeviceId, al::out_ptr(device))};
548 if(FAILED(hr))
550 ERR("Failed to get device: 0x%08lx\n", hr);
551 return S_OK;
554 ComPtr<IMMEndpoint> endpoint;
555 hr = device->QueryInterface(__uuidof(IMMEndpoint), al::out_ptr(endpoint));
556 if(FAILED(hr))
558 ERR("Failed to get device endpoint: 0x%08lx\n", hr);
559 return S_OK;
562 EDataFlow flowdir{};
563 hr = endpoint->GetDataFlow(&flowdir);
564 if(FAILED(hr))
566 ERR("Failed to get endpoint data flow: 0x%08lx\n", hr);
567 return S_OK;
570 auto devlock = DeviceListLock{gDeviceList};
571 auto &list = (flowdir==eRender) ? devlock.getPlaybackList() : devlock.getCaptureList();
573 if(AddDevice(device, pwstrDeviceId, list))
575 const auto devtype = (flowdir==eRender) ? alc::DeviceType::Playback
576 : alc::DeviceType::Capture;
577 const std::string msg{"Device added: "+list.back().name};
578 alc::Event(alc::EventType::DeviceAdded, devtype, msg);
581 return S_OK;
584 STDMETHODIMP OnDeviceRemoved(LPCWSTR pwstrDeviceId) noexcept override
586 auto devlock = DeviceListLock{gDeviceList};
587 for(auto flowdir : std::array{eRender, eCapture})
589 auto &list = (flowdir==eRender) ? devlock.getPlaybackList() : devlock.getCaptureList();
590 auto devtype = (flowdir==eRender)?alc::DeviceType::Playback : alc::DeviceType::Capture;
592 /* Find the ID in the list to remove. */
593 auto iter = std::find_if(list.begin(), list.end(),
594 [pwstrDeviceId](const DevMap &entry) noexcept
595 { return pwstrDeviceId == entry.devid; });
596 if(iter == list.end()) continue;
598 TRACE("Removing device \"%s\", \"%s\", \"%ls\"\n", iter->name.c_str(),
599 iter->endpoint_guid.c_str(), iter->devid.c_str());
601 std::string msg{"Device removed: "+std::move(iter->name)};
602 list.erase(iter);
604 alc::Event(alc::EventType::DeviceRemoved, devtype, msg);
606 return S_OK;
609 STDMETHODIMP OnPropertyValueChanged(LPCWSTR /*pwstrDeviceId*/, const PROPERTYKEY /*key*/) noexcept override { return S_OK; }
611 STDMETHODIMP OnDefaultDeviceChanged(EDataFlow flow, ERole role, LPCWSTR pwstrDefaultDeviceId) noexcept override
613 if(role != eMultimedia)
614 return S_OK;
616 const std::wstring_view devid{pwstrDefaultDeviceId ? pwstrDefaultDeviceId
617 : std::wstring_view{}};
618 if(flow == eRender)
620 DeviceListLock{gDeviceList}.setPlaybackDefaultId(devid);
621 const std::string msg{"Default playback device changed: " + wstr_to_utf8(devid)};
622 alc::Event(alc::EventType::DefaultDeviceChanged, alc::DeviceType::Playback, msg);
624 else if(flow == eCapture)
626 DeviceListLock{gDeviceList}.setCaptureDefaultId(devid);
627 const std::string msg{"Default capture device changed: " + wstr_to_utf8(devid)};
628 alc::Event(alc::EventType::DefaultDeviceChanged, alc::DeviceType::Capture, msg);
630 return S_OK;
632 #endif
634 /** -------------------------- DeviceHelper ----------------------------- */
635 HRESULT init()
637 #if !defined(ALSOFT_UWP)
638 HRESULT hr{CoCreateInstance(CLSID_MMDeviceEnumerator, nullptr, CLSCTX_INPROC_SERVER,
639 __uuidof(IMMDeviceEnumerator), al::out_ptr(mEnumerator))};
640 if(SUCCEEDED(hr))
641 mEnumerator->RegisterEndpointNotificationCallback(this);
642 else
643 WARN("Failed to create IMMDeviceEnumerator instance: 0x%08lx\n", hr);
644 return hr;
645 #else
646 return S_OK;
647 #endif
650 HRESULT openDevice(std::wstring_view devid, EDataFlow flow, DeviceHandle& device)
652 #if !defined(ALSOFT_UWP)
653 HRESULT hr{E_FAIL};
654 if(mEnumerator)
656 if(devid.empty())
657 hr = mEnumerator->GetDefaultAudioEndpoint(flow, eMultimedia, al::out_ptr(device));
658 else
659 hr = mEnumerator->GetDevice(devid.data(), al::out_ptr(device));
661 return hr;
662 #else
663 const auto deviceRole = Windows::Media::Devices::AudioDeviceRole::Default;
664 auto devIfPath =
665 devid.empty() ? (flow == eRender ? MediaDevice::GetDefaultAudioRenderId(deviceRole) : MediaDevice::GetDefaultAudioCaptureId(deviceRole))
666 : winrt::hstring(devid.data());
667 if (devIfPath.empty())
668 return E_POINTER;
670 auto&& deviceInfo = DeviceInformation::CreateFromIdAsync(devIfPath, nullptr, DeviceInformationKind::DeviceInterface).get();
671 if (!deviceInfo)
672 return E_NOINTERFACE;
673 device = deviceInfo;
674 return S_OK;
675 #endif
678 #if !defined(ALSOFT_UWP)
679 static HRESULT activateAudioClient(_In_ DeviceHandle &device, REFIID iid, void **ppv)
680 { return device->Activate(iid, CLSCTX_INPROC_SERVER, nullptr, ppv); }
681 #else
682 HRESULT activateAudioClient(_In_ DeviceHandle &device, _In_ REFIID iid, void **ppv)
684 ComPtr<IActivateAudioInterfaceAsyncOperation> asyncOp;
685 HRESULT hr{ActivateAudioInterfaceAsync(device.Id().data(), iid, nullptr, this,
686 al::out_ptr(asyncOp))};
687 if(FAILED(hr))
688 return hr;
690 /* I don't like waiting for INFINITE time, but the activate operation
691 * can take an indefinite amount of time since it can require user
692 * input.
694 DWORD res{WaitForSingleObjectEx(mActiveClientEvent, INFINITE, FALSE)};
695 if(res != WAIT_OBJECT_0)
697 ERR("WaitForSingleObjectEx error: 0x%lx\n", res);
698 return E_FAIL;
701 HRESULT hrActivateRes{E_FAIL};
702 ComPtr<IUnknown> punkAudioIface;
703 hr = asyncOp->GetActivateResult(&hrActivateRes, al::out_ptr(punkAudioIface));
704 if(SUCCEEDED(hr)) hr = hrActivateRes;
705 if(FAILED(hr)) return hr;
707 return punkAudioIface->QueryInterface(iid, ppv);
709 #endif
711 std::wstring probeDevices(EDataFlow flowdir, std::vector<DevMap> &list)
713 std::wstring defaultId;
714 std::vector<DevMap>{}.swap(list);
716 #if !defined(ALSOFT_UWP)
717 ComPtr<IMMDeviceCollection> coll;
718 HRESULT hr{mEnumerator->EnumAudioEndpoints(flowdir, DEVICE_STATE_ACTIVE,
719 al::out_ptr(coll))};
720 if(FAILED(hr))
722 ERR("Failed to enumerate audio endpoints: 0x%08lx\n", hr);
723 return defaultId;
726 UINT count{0};
727 hr = coll->GetCount(&count);
728 if(SUCCEEDED(hr) && count > 0)
729 list.reserve(count);
731 ComPtr<IMMDevice> device;
732 hr = mEnumerator->GetDefaultAudioEndpoint(flowdir, eMultimedia, al::out_ptr(device));
733 if(SUCCEEDED(hr))
735 if(WCHAR *devid{GetDeviceId(device.get())})
737 defaultId = devid;
738 CoTaskMemFree(devid);
740 device = nullptr;
743 for(UINT i{0};i < count;++i)
745 hr = coll->Item(i, al::out_ptr(device));
746 if(FAILED(hr))
747 continue;
749 if(WCHAR *devid{GetDeviceId(device.get())})
751 std::ignore = AddDevice(device, devid, list);
752 CoTaskMemFree(devid);
754 device = nullptr;
756 #else
757 const auto deviceRole = Windows::Media::Devices::AudioDeviceRole::Default;
758 auto DefaultAudioId = flowdir == eRender ? MediaDevice::GetDefaultAudioRenderId(deviceRole)
759 : MediaDevice::GetDefaultAudioCaptureId(deviceRole);
760 if(!DefaultAudioId.empty())
762 auto deviceInfo = DeviceInformation::CreateFromIdAsync(DefaultAudioId, nullptr,
763 DeviceInformationKind::DeviceInterface).get();
764 if(deviceInfo)
765 defaultId = deviceInfo.Id().data();
768 // Get the string identifier of the audio renderer
769 auto AudioSelector = flowdir == eRender ? MediaDevice::GetAudioRenderSelector() : MediaDevice::GetAudioCaptureSelector();
771 // Setup the asynchronous callback
772 auto&& DeviceInfoCollection = DeviceInformation::FindAllAsync(AudioSelector, /*PropertyList*/nullptr, DeviceInformationKind::DeviceInterface).get();
773 if(DeviceInfoCollection)
775 try {
776 auto deviceCount = DeviceInfoCollection.Size();
777 for(unsigned int i{0};i < deviceCount;++i)
779 auto deviceInfo = DeviceInfoCollection.GetAt(i);
780 if(deviceInfo)
781 std::ignore = AddDevice(deviceInfo, deviceInfo.Id().data(), list);
784 catch (const winrt::hresult_error& /*ex*/) {
787 #endif
789 return defaultId;
792 private:
793 static bool AddDevice(const DeviceHandle &device, const WCHAR *devid, std::vector<DevMap> &list)
795 for(auto &entry : list)
797 if(entry.devid == devid)
798 return false;
801 auto name_guid = GetDeviceNameAndGuid(device);
802 int count{1};
803 std::string newname{name_guid.first};
804 while(checkName(list, newname))
806 newname = name_guid.first;
807 newname += " #";
808 newname += std::to_string(++count);
810 list.emplace_back(std::move(newname), std::move(name_guid.second), devid);
811 const DevMap &newentry = list.back();
813 TRACE("Got device \"%s\", \"%s\", \"%ls\"\n", newentry.name.c_str(),
814 newentry.endpoint_guid.c_str(), newentry.devid.c_str());
815 return true;
818 #if !defined(ALSOFT_UWP)
819 static WCHAR *GetDeviceId(IMMDevice *device)
821 WCHAR *devid;
823 const HRESULT hr{device->GetId(&devid)};
824 if(FAILED(hr))
826 ERR("Failed to get device id: %lx\n", hr);
827 return nullptr;
830 return devid;
832 ComPtr<IMMDeviceEnumerator> mEnumerator{nullptr};
834 #else
836 HANDLE mActiveClientEvent{nullptr};
838 EventRegistrationToken mRenderDeviceChangedToken;
839 EventRegistrationToken mCaptureDeviceChangedToken;
840 #endif
843 bool MakeExtensible(WAVEFORMATEXTENSIBLE *out, const WAVEFORMATEX *in)
845 *out = WAVEFORMATEXTENSIBLE{};
846 if(in->wFormatTag == WAVE_FORMAT_EXTENSIBLE)
848 *out = *CONTAINING_RECORD(in, const WAVEFORMATEXTENSIBLE, Format);
849 out->Format.cbSize = sizeof(*out) - sizeof(out->Format);
851 else if(in->wFormatTag == WAVE_FORMAT_PCM)
853 out->Format = *in;
854 out->Format.cbSize = 0;
855 /* NOLINTNEXTLINE(cppcoreguidelines-pro-type-union-access) */
856 out->Samples.wValidBitsPerSample = out->Format.wBitsPerSample;
857 if(out->Format.nChannels == 1)
858 out->dwChannelMask = MONO;
859 else if(out->Format.nChannels == 2)
860 out->dwChannelMask = STEREO;
861 else
862 ERR("Unhandled PCM channel count: %d\n", out->Format.nChannels);
863 out->SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
865 else if(in->wFormatTag == WAVE_FORMAT_IEEE_FLOAT)
867 out->Format = *in;
868 out->Format.cbSize = 0;
869 /* NOLINTNEXTLINE(cppcoreguidelines-pro-type-union-access) */
870 out->Samples.wValidBitsPerSample = out->Format.wBitsPerSample;
871 if(out->Format.nChannels == 1)
872 out->dwChannelMask = MONO;
873 else if(out->Format.nChannels == 2)
874 out->dwChannelMask = STEREO;
875 else
876 ERR("Unhandled IEEE float channel count: %d\n", out->Format.nChannels);
877 out->SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT;
879 else
881 ERR("Unhandled format tag: 0x%04x\n", in->wFormatTag);
882 return false;
884 return true;
887 void TraceFormat(const char *msg, const WAVEFORMATEX *format)
889 constexpr size_t fmtex_extra_size{sizeof(WAVEFORMATEXTENSIBLE)-sizeof(WAVEFORMATEX)};
890 if(format->wFormatTag == WAVE_FORMAT_EXTENSIBLE && format->cbSize >= fmtex_extra_size)
892 const WAVEFORMATEXTENSIBLE *fmtex{
893 CONTAINING_RECORD(format, const WAVEFORMATEXTENSIBLE, Format)};
894 /* NOLINTBEGIN(cppcoreguidelines-pro-type-union-access) */
895 TRACE("%s:\n"
896 " FormatTag = 0x%04x\n"
897 " Channels = %d\n"
898 " SamplesPerSec = %lu\n"
899 " AvgBytesPerSec = %lu\n"
900 " BlockAlign = %d\n"
901 " BitsPerSample = %d\n"
902 " Size = %d\n"
903 " Samples = %d\n"
904 " ChannelMask = 0x%lx\n"
905 " SubFormat = %s\n",
906 msg, fmtex->Format.wFormatTag, fmtex->Format.nChannels, fmtex->Format.nSamplesPerSec,
907 fmtex->Format.nAvgBytesPerSec, fmtex->Format.nBlockAlign, fmtex->Format.wBitsPerSample,
908 fmtex->Format.cbSize, fmtex->Samples.wReserved, fmtex->dwChannelMask,
909 GuidPrinter{fmtex->SubFormat}.c_str());
910 /* NOLINTEND(cppcoreguidelines-pro-type-union-access) */
912 else
913 TRACE("%s:\n"
914 " FormatTag = 0x%04x\n"
915 " Channels = %d\n"
916 " SamplesPerSec = %lu\n"
917 " AvgBytesPerSec = %lu\n"
918 " BlockAlign = %d\n"
919 " BitsPerSample = %d\n"
920 " Size = %d\n",
921 msg, format->wFormatTag, format->nChannels, format->nSamplesPerSec,
922 format->nAvgBytesPerSec, format->nBlockAlign, format->wBitsPerSample, format->cbSize);
926 enum class MsgType {
927 OpenDevice,
928 ResetDevice,
929 StartDevice,
930 StopDevice,
931 CloseDevice,
933 QuitThread
936 constexpr const char *GetMessageTypeName(MsgType type) noexcept
938 switch(type)
940 case MsgType::OpenDevice: return "Open Device";
941 case MsgType::ResetDevice: return "Reset Device";
942 case MsgType::StartDevice: return "Start Device";
943 case MsgType::StopDevice: return "Stop Device";
944 case MsgType::CloseDevice: return "Close Device";
945 case MsgType::QuitThread: break;
947 return "";
951 /* Proxy interface used by the message handler. */
952 struct WasapiProxy {
953 WasapiProxy() = default;
954 WasapiProxy(const WasapiProxy&) = delete;
955 WasapiProxy(WasapiProxy&&) = delete;
956 virtual ~WasapiProxy() = default;
958 void operator=(const WasapiProxy&) = delete;
959 void operator=(WasapiProxy&&) = delete;
961 virtual HRESULT openProxy(std::string_view name) = 0;
962 virtual void closeProxy() = 0;
964 virtual HRESULT resetProxy() = 0;
965 virtual HRESULT startProxy() = 0;
966 virtual void stopProxy() = 0;
968 struct Msg {
969 MsgType mType;
970 WasapiProxy *mProxy;
971 std::string_view mParam;
972 std::promise<HRESULT> mPromise;
974 explicit operator bool() const noexcept { return mType != MsgType::QuitThread; }
976 static inline std::deque<Msg> mMsgQueue;
977 static inline std::mutex mMsgQueueLock;
978 static inline std::condition_variable mMsgQueueCond;
979 static inline DWORD sAvIndex{};
981 static inline std::optional<DeviceHelper> sDeviceHelper;
983 std::future<HRESULT> pushMessage(MsgType type, std::string_view param={})
985 std::promise<HRESULT> promise;
986 std::future<HRESULT> future{promise.get_future()};
988 std::lock_guard<std::mutex> msglock{mMsgQueueLock};
989 mMsgQueue.emplace_back(Msg{type, this, param, std::move(promise)});
991 mMsgQueueCond.notify_one();
992 return future;
995 static std::future<HRESULT> pushMessageStatic(MsgType type)
997 std::promise<HRESULT> promise;
998 std::future<HRESULT> future{promise.get_future()};
1000 std::lock_guard<std::mutex> msglock{mMsgQueueLock};
1001 mMsgQueue.emplace_back(Msg{type, nullptr, {}, std::move(promise)});
1003 mMsgQueueCond.notify_one();
1004 return future;
1007 static Msg popMessage()
1009 std::unique_lock<std::mutex> lock{mMsgQueueLock};
1010 mMsgQueueCond.wait(lock, []{return !mMsgQueue.empty();});
1011 Msg msg{std::move(mMsgQueue.front())};
1012 mMsgQueue.pop_front();
1013 return msg;
1016 static int messageHandler(std::promise<HRESULT> *promise);
1019 int WasapiProxy::messageHandler(std::promise<HRESULT> *promise)
1021 TRACE("Starting message thread\n");
1023 ComWrapper com{COINIT_MULTITHREADED};
1024 if(!com)
1026 WARN("Failed to initialize COM: 0x%08lx\n", com.status());
1027 promise->set_value(com.status());
1028 return 0;
1031 struct HelperResetter {
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 if(const auto prefix = GetDevicePrefix(); al::starts_with(name, prefix))
1397 name = name.substr(prefix.size());
1399 mOpenStatus = pushMessage(MsgType::OpenDevice, name).get();
1400 if(FAILED(mOpenStatus))
1401 throw al::backend_exception{al::backend_error::DeviceError, "Device init failed: 0x%08lx",
1402 mOpenStatus};
1405 HRESULT WasapiPlayback::openProxy(std::string_view name)
1407 std::string devname;
1408 std::wstring devid;
1409 if(!name.empty())
1411 auto devlock = DeviceListLock{gDeviceList};
1412 auto list = al::span{devlock.getPlaybackList()};
1413 auto iter = std::find_if(list.cbegin(), list.cend(),
1414 [name](const DevMap &entry) -> bool
1415 { return entry.name == name || entry.endpoint_guid == name; });
1416 if(iter == list.cend())
1418 const std::wstring wname{utf8_to_wstr(name)};
1419 iter = std::find_if(list.cbegin(), list.cend(),
1420 [&wname](const DevMap &entry) -> bool
1421 { return entry.devid == wname; });
1423 if(iter == list.cend())
1425 WARN("Failed to find device name matching \"%.*s\"\n", al::sizei(name), name.data());
1426 return E_FAIL;
1428 devname = iter->name;
1429 devid = iter->devid;
1432 HRESULT hr{sDeviceHelper->openDevice(devid, eRender, mMMDev)};
1433 if(FAILED(hr))
1435 WARN("Failed to open device \"%s\"\n", devname.empty() ? "(default)" : devname.c_str());
1436 return hr;
1438 if(!devname.empty())
1439 mDevice->DeviceName = std::string{GetDevicePrefix()}+std::move(devname);
1440 else
1441 mDevice->DeviceName = std::string{GetDevicePrefix()}+GetDeviceNameAndGuid(mMMDev).first;
1443 return S_OK;
1446 void WasapiPlayback::closeProxy()
1448 mAudio.emplace<PlainDevice>();
1449 mMMDev = nullptr;
1453 void WasapiPlayback::prepareFormat(WAVEFORMATEXTENSIBLE &OutputType)
1455 bool isRear51{false};
1457 if(!mDevice->Flags.test(FrequencyRequest))
1458 mDevice->Frequency = OutputType.Format.nSamplesPerSec;
1459 if(!mDevice->Flags.test(ChannelsRequest))
1461 /* If not requesting a channel configuration, auto-select given what
1462 * fits the mask's lsb (to ensure no gaps in the output channels). If
1463 * there's no mask, we can only assume mono or stereo.
1465 const uint32_t chancount{OutputType.Format.nChannels};
1466 const DWORD chanmask{OutputType.dwChannelMask};
1467 if(chancount >= 12 && (chanmask&X714Mask) == X7DOT1DOT4)
1468 mDevice->FmtChans = DevFmtX714;
1469 else if(chancount >= 8 && (chanmask&X71Mask) == X7DOT1)
1470 mDevice->FmtChans = DevFmtX71;
1471 else if(chancount >= 7 && (chanmask&X61Mask) == X6DOT1)
1472 mDevice->FmtChans = DevFmtX61;
1473 else if(chancount >= 6 && (chanmask&X51Mask) == X5DOT1)
1474 mDevice->FmtChans = DevFmtX51;
1475 else if(chancount >= 6 && (chanmask&X51RearMask) == X5DOT1REAR)
1477 mDevice->FmtChans = DevFmtX51;
1478 isRear51 = true;
1480 else if(chancount >= 4 && (chanmask&QuadMask) == QUAD)
1481 mDevice->FmtChans = DevFmtQuad;
1482 else if(chancount >= 2 && ((chanmask&StereoMask) == STEREO || !chanmask))
1483 mDevice->FmtChans = DevFmtStereo;
1484 else if(chancount >= 1 && ((chanmask&MonoMask) == MONO || !chanmask))
1485 mDevice->FmtChans = DevFmtMono;
1486 else
1487 ERR("Unhandled channel config: %d -- 0x%08lx\n", chancount, chanmask);
1489 else
1491 const uint32_t chancount{OutputType.Format.nChannels};
1492 const DWORD chanmask{OutputType.dwChannelMask};
1493 isRear51 = (chancount == 6 && (chanmask&X51RearMask) == X5DOT1REAR);
1496 OutputType.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
1497 switch(mDevice->FmtChans)
1499 case DevFmtMono:
1500 OutputType.Format.nChannels = 1;
1501 OutputType.dwChannelMask = MONO;
1502 break;
1503 case DevFmtAmbi3D:
1504 mDevice->FmtChans = DevFmtStereo;
1505 /*fall-through*/
1506 case DevFmtStereo:
1507 OutputType.Format.nChannels = 2;
1508 OutputType.dwChannelMask = STEREO;
1509 break;
1510 case DevFmtQuad:
1511 OutputType.Format.nChannels = 4;
1512 OutputType.dwChannelMask = QUAD;
1513 break;
1514 case DevFmtX51:
1515 OutputType.Format.nChannels = 6;
1516 OutputType.dwChannelMask = isRear51 ? X5DOT1REAR : X5DOT1;
1517 break;
1518 case DevFmtX61:
1519 OutputType.Format.nChannels = 7;
1520 OutputType.dwChannelMask = X6DOT1;
1521 break;
1522 case DevFmtX71:
1523 case DevFmtX3D71:
1524 OutputType.Format.nChannels = 8;
1525 OutputType.dwChannelMask = X7DOT1;
1526 break;
1527 case DevFmtX7144:
1528 mDevice->FmtChans = DevFmtX714;
1529 /*fall-through*/
1530 case DevFmtX714:
1531 OutputType.Format.nChannels = 12;
1532 OutputType.dwChannelMask = X7DOT1DOT4;
1533 break;
1535 switch(mDevice->FmtType)
1537 case DevFmtByte:
1538 mDevice->FmtType = DevFmtUByte;
1539 /* fall-through */
1540 case DevFmtUByte:
1541 OutputType.Format.wBitsPerSample = 8;
1542 OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
1543 break;
1544 case DevFmtUShort:
1545 mDevice->FmtType = DevFmtShort;
1546 /* fall-through */
1547 case DevFmtShort:
1548 OutputType.Format.wBitsPerSample = 16;
1549 OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
1550 break;
1551 case DevFmtUInt:
1552 mDevice->FmtType = DevFmtInt;
1553 /* fall-through */
1554 case DevFmtInt:
1555 OutputType.Format.wBitsPerSample = 32;
1556 OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
1557 break;
1558 case DevFmtFloat:
1559 OutputType.Format.wBitsPerSample = 32;
1560 OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT;
1561 break;
1563 /* NOLINTNEXTLINE(cppcoreguidelines-pro-type-union-access) */
1564 OutputType.Samples.wValidBitsPerSample = OutputType.Format.wBitsPerSample;
1565 OutputType.Format.nSamplesPerSec = mDevice->Frequency;
1567 OutputType.Format.nBlockAlign = static_cast<WORD>(OutputType.Format.nChannels *
1568 OutputType.Format.wBitsPerSample / 8);
1569 OutputType.Format.nAvgBytesPerSec = OutputType.Format.nSamplesPerSec *
1570 OutputType.Format.nBlockAlign;
1573 void WasapiPlayback::finalizeFormat(WAVEFORMATEXTENSIBLE &OutputType)
1575 if(!GetConfigValueBool(mDevice->DeviceName, "wasapi", "allow-resampler", true))
1576 mDevice->Frequency = uint(OutputType.Format.nSamplesPerSec);
1577 else
1578 mDevice->Frequency = std::min(mDevice->Frequency, uint(OutputType.Format.nSamplesPerSec));
1580 const uint32_t chancount{OutputType.Format.nChannels};
1581 const DWORD chanmask{OutputType.dwChannelMask};
1582 /* Don't update the channel format if the requested format fits what's
1583 * supported.
1585 bool chansok{false};
1586 if(mDevice->Flags.test(ChannelsRequest))
1588 /* When requesting a channel configuration, make sure it fits the
1589 * mask's lsb (to ensure no gaps in the output channels). If there's no
1590 * mask, assume the request fits with enough channels.
1592 switch(mDevice->FmtChans)
1594 case DevFmtMono:
1595 chansok = (chancount >= 1 && ((chanmask&MonoMask) == MONO || !chanmask));
1596 break;
1597 case DevFmtStereo:
1598 chansok = (chancount >= 2 && ((chanmask&StereoMask) == STEREO || !chanmask));
1599 break;
1600 case DevFmtQuad:
1601 chansok = (chancount >= 4 && ((chanmask&QuadMask) == QUAD || !chanmask));
1602 break;
1603 case DevFmtX51:
1604 chansok = (chancount >= 6 && ((chanmask&X51Mask) == X5DOT1
1605 || (chanmask&X51RearMask) == X5DOT1REAR || !chanmask));
1606 break;
1607 case DevFmtX61:
1608 chansok = (chancount >= 7 && ((chanmask&X61Mask) == X6DOT1 || !chanmask));
1609 break;
1610 case DevFmtX71:
1611 case DevFmtX3D71:
1612 chansok = (chancount >= 8 && ((chanmask&X71Mask) == X7DOT1 || !chanmask));
1613 break;
1614 case DevFmtX714:
1615 chansok = (chancount >= 12 && ((chanmask&X714Mask) == X7DOT1DOT4 || !chanmask));
1616 case DevFmtX7144:
1617 case DevFmtAmbi3D:
1618 break;
1621 if(!chansok)
1623 if(chancount >= 12 && (chanmask&X714Mask) == X7DOT1DOT4)
1624 mDevice->FmtChans = DevFmtX714;
1625 else if(chancount >= 8 && (chanmask&X71Mask) == X7DOT1)
1626 mDevice->FmtChans = DevFmtX71;
1627 else if(chancount >= 7 && (chanmask&X61Mask) == X6DOT1)
1628 mDevice->FmtChans = DevFmtX61;
1629 else if(chancount >= 6 && ((chanmask&X51Mask) == X5DOT1
1630 || (chanmask&X51RearMask) == X5DOT1REAR))
1631 mDevice->FmtChans = DevFmtX51;
1632 else if(chancount >= 4 && (chanmask&QuadMask) == QUAD)
1633 mDevice->FmtChans = DevFmtQuad;
1634 else if(chancount >= 2 && ((chanmask&StereoMask) == STEREO || !chanmask))
1635 mDevice->FmtChans = DevFmtStereo;
1636 else if(chancount >= 1 && ((chanmask&MonoMask) == MONO || !chanmask))
1637 mDevice->FmtChans = DevFmtMono;
1638 else
1640 ERR("Unhandled extensible channels: %d -- 0x%08lx\n", OutputType.Format.nChannels,
1641 OutputType.dwChannelMask);
1642 mDevice->FmtChans = DevFmtStereo;
1643 OutputType.Format.nChannels = 2;
1644 OutputType.dwChannelMask = STEREO;
1648 if(IsEqualGUID(OutputType.SubFormat, KSDATAFORMAT_SUBTYPE_PCM))
1650 if(OutputType.Format.wBitsPerSample == 8)
1651 mDevice->FmtType = DevFmtUByte;
1652 else if(OutputType.Format.wBitsPerSample == 16)
1653 mDevice->FmtType = DevFmtShort;
1654 else if(OutputType.Format.wBitsPerSample == 32)
1655 mDevice->FmtType = DevFmtInt;
1656 else
1658 mDevice->FmtType = DevFmtShort;
1659 OutputType.Format.wBitsPerSample = 16;
1662 else if(IsEqualGUID(OutputType.SubFormat, KSDATAFORMAT_SUBTYPE_IEEE_FLOAT))
1664 mDevice->FmtType = DevFmtFloat;
1665 OutputType.Format.wBitsPerSample = 32;
1667 else
1669 ERR("Unhandled format sub-type: %s\n", GuidPrinter{OutputType.SubFormat}.c_str());
1670 mDevice->FmtType = DevFmtShort;
1671 if(OutputType.Format.wFormatTag != WAVE_FORMAT_EXTENSIBLE)
1672 OutputType.Format.wFormatTag = WAVE_FORMAT_PCM;
1673 OutputType.Format.wBitsPerSample = 16;
1674 OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
1676 /* NOLINTNEXTLINE(cppcoreguidelines-pro-type-union-access) */
1677 OutputType.Samples.wValidBitsPerSample = OutputType.Format.wBitsPerSample;
1681 auto WasapiPlayback::initSpatial() -> bool
1683 auto &audio = mAudio.emplace<SpatialDevice>();
1684 HRESULT hr{sDeviceHelper->activateAudioClient(mMMDev, __uuidof(ISpatialAudioClient),
1685 al::out_ptr(audio.mClient))};
1686 if(FAILED(hr))
1688 ERR("Failed to activate spatial audio client: 0x%08lx\n", hr);
1689 return false;
1692 ComPtr<IAudioFormatEnumerator> fmtenum;
1693 hr = audio.mClient->GetSupportedAudioObjectFormatEnumerator(al::out_ptr(fmtenum));
1694 if(FAILED(hr))
1696 ERR("Failed to get format enumerator: 0x%08lx\n", hr);
1697 return false;
1700 UINT32 fmtcount{};
1701 hr = fmtenum->GetCount(&fmtcount);
1702 if(FAILED(hr) || fmtcount == 0)
1704 ERR("Failed to get format count: 0x%08lx\n", hr);
1705 return false;
1708 WAVEFORMATEX *preferredFormat{};
1709 hr = fmtenum->GetFormat(0, &preferredFormat);
1710 if(FAILED(hr))
1712 ERR("Failed to get preferred format: 0x%08lx\n", hr);
1713 return false;
1715 TraceFormat("Preferred mix format", preferredFormat);
1717 UINT32 maxFrames{};
1718 hr = audio.mClient->GetMaxFrameCount(preferredFormat, &maxFrames);
1719 if(FAILED(hr))
1720 ERR("Failed to get max frames: 0x%08lx\n", hr);
1721 else
1722 TRACE("Max sample frames: %u\n", maxFrames);
1723 for(UINT32 i{1};i < fmtcount;++i)
1725 WAVEFORMATEX *otherFormat{};
1726 hr = fmtenum->GetFormat(i, &otherFormat);
1727 if(FAILED(hr))
1728 ERR("Failed to format %u: 0x%08lx\n", i+1, hr);
1729 else
1731 TraceFormat("Other mix format", otherFormat);
1732 UINT32 otherMaxFrames{};
1733 hr = audio.mClient->GetMaxFrameCount(otherFormat, &otherMaxFrames);
1734 if(FAILED(hr))
1735 ERR("Failed to get max frames: 0x%08lx\n", hr);
1736 else
1737 TRACE("Max sample frames: %u\n", otherMaxFrames);
1741 WAVEFORMATEXTENSIBLE OutputType;
1742 if(!MakeExtensible(&OutputType, preferredFormat))
1743 return false;
1745 /* This seems to be the format of each "object", which should be mono. */
1746 if(!(OutputType.Format.nChannels == 1
1747 && (OutputType.dwChannelMask == MONO || !OutputType.dwChannelMask)))
1748 ERR("Unhandled channel config: %d -- 0x%08lx\n", OutputType.Format.nChannels,
1749 OutputType.dwChannelMask);
1751 /* Force 32-bit float. This is currently required for planar output. */
1752 if(OutputType.Format.wFormatTag != WAVE_FORMAT_EXTENSIBLE
1753 && OutputType.Format.wFormatTag != WAVE_FORMAT_IEEE_FLOAT)
1755 OutputType.Format.wFormatTag = WAVE_FORMAT_IEEE_FLOAT;
1756 OutputType.Format.cbSize = 0;
1758 if(OutputType.Format.wBitsPerSample != 32)
1760 OutputType.Format.nAvgBytesPerSec = OutputType.Format.nAvgBytesPerSec * 32u
1761 / OutputType.Format.wBitsPerSample;
1762 OutputType.Format.nBlockAlign = static_cast<WORD>(OutputType.Format.nBlockAlign * 32
1763 / OutputType.Format.wBitsPerSample);
1764 OutputType.Format.wBitsPerSample = 32;
1766 /* NOLINTNEXTLINE(cppcoreguidelines-pro-type-union-access) */
1767 OutputType.Samples.wValidBitsPerSample = OutputType.Format.wBitsPerSample;
1768 OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT;
1770 /* Match the output rate if not requesting anything specific. */
1771 if(!mDevice->Flags.test(FrequencyRequest))
1772 mDevice->Frequency = OutputType.Format.nSamplesPerSec;
1774 auto getTypeMask = [](DevFmtChannels chans) noexcept
1776 switch(chans)
1778 case DevFmtMono: return ChannelMask_Mono;
1779 case DevFmtStereo: return ChannelMask_Stereo;
1780 case DevFmtQuad: return ChannelMask_Quad;
1781 case DevFmtX51: return ChannelMask_X51;
1782 case DevFmtX61: return ChannelMask_X61;
1783 case DevFmtX3D71: [[fallthrough]];
1784 case DevFmtX71: return ChannelMask_X71;
1785 case DevFmtX714: return ChannelMask_X714;
1786 case DevFmtX7144: return ChannelMask_X7144;
1787 case DevFmtAmbi3D:
1788 break;
1790 return ChannelMask_Stereo;
1793 SpatialAudioObjectRenderStreamActivationParams streamParams{};
1794 streamParams.ObjectFormat = &OutputType.Format;
1795 streamParams.StaticObjectTypeMask = getTypeMask(mDevice->FmtChans);
1796 streamParams.Category = AudioCategory_Media;
1797 streamParams.EventHandle = mNotifyEvent;
1799 PropVariant paramProp{};
1800 paramProp.setBlob({reinterpret_cast<BYTE*>(&streamParams), sizeof(streamParams)});
1802 hr = audio.mClient->ActivateSpatialAudioStream(paramProp.get(),
1803 __uuidof(ISpatialAudioObjectRenderStream), al::out_ptr(audio.mRender));
1804 if(FAILED(hr))
1806 ERR("Failed to activate spatial audio stream: 0x%08lx\n", hr);
1807 return false;
1810 audio.mStaticMask = streamParams.StaticObjectTypeMask;
1811 mFormat = OutputType;
1813 mDevice->FmtType = DevFmtFloat;
1814 mDevice->Flags.reset(DirectEar).set(Virtualization);
1815 if(streamParams.StaticObjectTypeMask == ChannelMask_Stereo)
1816 mDevice->FmtChans = DevFmtStereo;
1817 if(!GetConfigValueBool(mDevice->DeviceName, "wasapi", "allow-resampler", true))
1818 mDevice->Frequency = uint(OutputType.Format.nSamplesPerSec);
1819 else
1820 mDevice->Frequency = std::min(mDevice->Frequency,
1821 uint(OutputType.Format.nSamplesPerSec));
1823 setDefaultWFXChannelOrder();
1825 /* FIXME: Get the real update and buffer size. Presumably the actual device
1826 * is configured once ActivateSpatialAudioStream succeeds, and an
1827 * IAudioClient from the same IMMDevice accesses the same device
1828 * configuration. This isn't obviously correct, but for now assume
1829 * IAudioClient::GetDevicePeriod returns the current device period time
1830 * that ISpatialAudioObjectRenderStream will try to wake up at.
1832 * Unfortunately this won't get the buffer size of the
1833 * ISpatialAudioObjectRenderStream, so we only assume there's two periods.
1835 mOutUpdateSize = mDevice->UpdateSize;
1836 mOutBufferSize = mOutUpdateSize*2;
1837 ReferenceTime per_time{ReferenceTime{seconds{mDevice->UpdateSize}} / mDevice->Frequency};
1839 ComPtr<IAudioClient> tmpClient;
1840 hr = sDeviceHelper->activateAudioClient(mMMDev, __uuidof(IAudioClient),
1841 al::out_ptr(tmpClient));
1842 if(FAILED(hr))
1843 ERR("Failed to activate audio client: 0x%08lx\n", hr);
1844 else
1846 hr = tmpClient->GetDevicePeriod(&reinterpret_cast<REFERENCE_TIME&>(per_time), nullptr);
1847 if(FAILED(hr))
1848 ERR("Failed to get device period: 0x%08lx\n", hr);
1849 else
1851 mOutUpdateSize = RefTime2Samples(per_time, mFormat.Format.nSamplesPerSec);
1852 mOutBufferSize = mOutUpdateSize*2;
1855 tmpClient = nullptr;
1857 mDevice->UpdateSize = RefTime2Samples(per_time, mDevice->Frequency);
1858 mDevice->BufferSize = mDevice->UpdateSize*2;
1860 mResampler = nullptr;
1861 mResampleBuffer.clear();
1862 mResampleBuffer.shrink_to_fit();
1863 mBufferFilled = 0;
1864 if(mDevice->Frequency != mFormat.Format.nSamplesPerSec)
1866 const auto flags = as_unsigned(streamParams.StaticObjectTypeMask);
1867 const auto channelCount = as_unsigned(al::popcount(flags));
1868 mResampler = SampleConverter::Create(mDevice->FmtType, mDevice->FmtType, channelCount,
1869 mDevice->Frequency, mFormat.Format.nSamplesPerSec, Resampler::FastBSinc24);
1870 mResampleBuffer.resize(size_t{mDevice->UpdateSize} * channelCount *
1871 mFormat.Format.wBitsPerSample / 8);
1873 TRACE("Created converter for %s/%s format, dst: %luhz (%u), src: %uhz (%u)\n",
1874 DevFmtChannelsString(mDevice->FmtChans), DevFmtTypeString(mDevice->FmtType),
1875 mFormat.Format.nSamplesPerSec, mOutUpdateSize, mDevice->Frequency,
1876 mDevice->UpdateSize);
1879 return true;
1882 bool WasapiPlayback::reset()
1884 HRESULT hr{pushMessage(MsgType::ResetDevice).get()};
1885 if(FAILED(hr))
1886 throw al::backend_exception{al::backend_error::DeviceError, "0x%08lx", hr};
1887 return true;
1890 HRESULT WasapiPlayback::resetProxy()
1892 if(GetConfigValueBool(mDevice->DeviceName, "wasapi", "spatial-api", false))
1894 if(initSpatial())
1895 return S_OK;
1898 mDevice->Flags.reset(Virtualization);
1900 auto &audio = mAudio.emplace<PlainDevice>();
1901 HRESULT hr{sDeviceHelper->activateAudioClient(mMMDev, __uuidof(IAudioClient),
1902 al::out_ptr(audio.mClient))};
1903 if(FAILED(hr))
1905 ERR("Failed to reactivate audio client: 0x%08lx\n", hr);
1906 return hr;
1909 WAVEFORMATEX *wfx;
1910 hr = audio.mClient->GetMixFormat(&wfx);
1911 if(FAILED(hr))
1913 ERR("Failed to get mix format: 0x%08lx\n", hr);
1914 return hr;
1916 TraceFormat("Device mix format", wfx);
1918 WAVEFORMATEXTENSIBLE OutputType;
1919 if(!MakeExtensible(&OutputType, wfx))
1921 CoTaskMemFree(wfx);
1922 return E_FAIL;
1924 CoTaskMemFree(wfx);
1925 wfx = nullptr;
1927 const ReferenceTime per_time{ReferenceTime{seconds{mDevice->UpdateSize}} / mDevice->Frequency};
1928 const ReferenceTime buf_time{ReferenceTime{seconds{mDevice->BufferSize}} / mDevice->Frequency};
1930 prepareFormat(OutputType);
1932 TraceFormat("Requesting playback format", &OutputType.Format);
1933 hr = audio.mClient->IsFormatSupported(AUDCLNT_SHAREMODE_SHARED, &OutputType.Format, &wfx);
1934 if(FAILED(hr))
1936 WARN("Failed to check format support: 0x%08lx\n", hr);
1937 hr = audio.mClient->GetMixFormat(&wfx);
1939 if(FAILED(hr))
1941 ERR("Failed to find a supported format: 0x%08lx\n", hr);
1942 return hr;
1945 if(wfx != nullptr)
1947 TraceFormat("Got playback format", wfx);
1948 if(!MakeExtensible(&OutputType, wfx))
1950 CoTaskMemFree(wfx);
1951 return E_FAIL;
1953 CoTaskMemFree(wfx);
1954 wfx = nullptr;
1956 finalizeFormat(OutputType);
1958 mFormat = OutputType;
1960 #if !defined(ALSOFT_UWP)
1961 const EndpointFormFactor formfactor{GetDeviceFormfactor(mMMDev.get())};
1962 mDevice->Flags.set(DirectEar, (formfactor == Headphones || formfactor == Headset));
1963 #else
1964 mDevice->Flags.set(DirectEar, false);
1965 #endif
1966 setDefaultWFXChannelOrder();
1968 hr = audio.mClient->Initialize(AUDCLNT_SHAREMODE_SHARED, AUDCLNT_STREAMFLAGS_EVENTCALLBACK,
1969 buf_time.count(), 0, &OutputType.Format, nullptr);
1970 if(FAILED(hr))
1972 ERR("Failed to initialize audio client: 0x%08lx\n", hr);
1973 return hr;
1976 UINT32 buffer_len{};
1977 ReferenceTime min_per{};
1978 hr = audio.mClient->GetDevicePeriod(&reinterpret_cast<REFERENCE_TIME&>(min_per), nullptr);
1979 if(SUCCEEDED(hr))
1980 hr = audio.mClient->GetBufferSize(&buffer_len);
1981 if(FAILED(hr))
1983 ERR("Failed to get audio buffer info: 0x%08lx\n", hr);
1984 return hr;
1987 hr = audio.mClient->SetEventHandle(mNotifyEvent);
1988 if(FAILED(hr))
1990 ERR("Failed to set event handle: 0x%08lx\n", hr);
1991 return hr;
1994 hr = audio.mClient->GetService(__uuidof(IAudioRenderClient), al::out_ptr(audio.mRender));
1995 if(FAILED(hr))
1997 ERR("Failed to get IAudioRenderClient: 0x%08lx\n", hr);
1998 return hr;
2001 /* Find the nearest multiple of the period size to the update size */
2002 if(min_per < per_time)
2003 min_per *= std::max<int64_t>((per_time + min_per/2) / min_per, 1_i64);
2005 mOutBufferSize = buffer_len;
2006 mOutUpdateSize = std::min(RefTime2Samples(min_per, mFormat.Format.nSamplesPerSec),
2007 buffer_len/2u);
2009 mDevice->BufferSize = static_cast<uint>(uint64_t{buffer_len} * mDevice->Frequency /
2010 mFormat.Format.nSamplesPerSec);
2011 mDevice->UpdateSize = std::min(RefTime2Samples(min_per, mDevice->Frequency),
2012 mDevice->BufferSize/2u);
2014 mResampler = nullptr;
2015 mResampleBuffer.clear();
2016 mResampleBuffer.shrink_to_fit();
2017 mBufferFilled = 0;
2018 if(mDevice->Frequency != mFormat.Format.nSamplesPerSec)
2020 mResampler = SampleConverter::Create(mDevice->FmtType, mDevice->FmtType,
2021 mFormat.Format.nChannels, mDevice->Frequency, mFormat.Format.nSamplesPerSec,
2022 Resampler::FastBSinc24);
2023 mResampleBuffer.resize(size_t{mDevice->UpdateSize} * mFormat.Format.nChannels *
2024 mFormat.Format.wBitsPerSample / 8);
2026 TRACE("Created converter for %s/%s format, dst: %luhz (%u), src: %uhz (%u)\n",
2027 DevFmtChannelsString(mDevice->FmtChans), DevFmtTypeString(mDevice->FmtType),
2028 mFormat.Format.nSamplesPerSec, mOutUpdateSize, mDevice->Frequency,
2029 mDevice->UpdateSize);
2032 return hr;
2036 void WasapiPlayback::start()
2038 const HRESULT hr{pushMessage(MsgType::StartDevice).get()};
2039 if(FAILED(hr))
2040 throw al::backend_exception{al::backend_error::DeviceError,
2041 "Failed to start playback: 0x%lx", hr};
2044 HRESULT WasapiPlayback::startProxy()
2046 ResetEvent(mNotifyEvent);
2048 auto start_plain = [&](PlainDevice &audio) -> HRESULT
2050 HRESULT hr{audio.mClient->Start()};
2051 if(FAILED(hr))
2053 ERR("Failed to start audio client: 0x%08lx\n", hr);
2054 return hr;
2057 try {
2058 mKillNow.store(false, std::memory_order_release);
2059 mThread = std::thread{std::mem_fn(&WasapiPlayback::mixerProc), this};
2061 catch(...) {
2062 ERR("Failed to start thread\n");
2063 audio.mClient->Stop();
2064 hr = E_FAIL;
2066 return hr;
2068 auto start_spatial = [&](SpatialDevice &audio) -> HRESULT
2070 HRESULT hr{audio.mRender->Start()};
2071 if(FAILED(hr))
2073 ERR("Failed to start spatial audio stream: 0x%08lx\n", hr);
2074 return hr;
2077 try {
2078 mKillNow.store(false, std::memory_order_release);
2079 mThread = std::thread{std::mem_fn(&WasapiPlayback::mixerSpatialProc), this};
2081 catch(...) {
2082 ERR("Failed to start thread\n");
2083 hr = E_FAIL;
2086 if(FAILED(hr))
2088 audio.mRender->Stop();
2089 audio.mRender->Reset();
2091 return hr;
2094 return std::visit(overloaded{start_plain, start_spatial}, mAudio);
2098 void WasapiPlayback::stop()
2099 { pushMessage(MsgType::StopDevice).wait(); }
2101 void WasapiPlayback::stopProxy()
2103 if(!mThread.joinable())
2104 return;
2106 mKillNow.store(true, std::memory_order_release);
2107 mThread.join();
2109 auto stop_plain = [](PlainDevice &audio) -> void
2110 { audio.mClient->Stop(); };
2111 auto stop_spatial = [](SpatialDevice &audio) -> void
2113 audio.mRender->Stop();
2114 audio.mRender->Reset();
2116 std::visit(overloaded{stop_plain, stop_spatial}, mAudio);
2120 ClockLatency WasapiPlayback::getClockLatency()
2122 std::lock_guard<std::mutex> dlock{mMutex};
2123 ClockLatency ret{};
2124 ret.ClockTime = mDevice->getClockTime();
2125 ret.Latency = seconds{mPadding.load(std::memory_order_relaxed)};
2126 ret.Latency /= mFormat.Format.nSamplesPerSec;
2127 if(mResampler)
2129 auto extra = mResampler->currentInputDelay();
2130 ret.Latency += std::chrono::duration_cast<nanoseconds>(extra) / mDevice->Frequency;
2131 ret.Latency += nanoseconds{seconds{mBufferFilled}} / mDevice->Frequency;
2134 return ret;
2138 struct WasapiCapture final : public BackendBase, WasapiProxy {
2139 WasapiCapture(DeviceBase *device) noexcept : BackendBase{device} { }
2140 ~WasapiCapture() override;
2142 int recordProc();
2144 void open(std::string_view name) override;
2145 HRESULT openProxy(std::string_view name) override;
2146 void closeProxy() override;
2148 HRESULT resetProxy() override;
2149 void start() override;
2150 HRESULT startProxy() override;
2151 void stop() override;
2152 void stopProxy() override;
2154 void captureSamples(std::byte *buffer, uint samples) override;
2155 uint availableSamples() override;
2157 HRESULT mOpenStatus{E_FAIL};
2158 DeviceHandle mMMDev{nullptr};
2159 ComPtr<IAudioClient> mClient{nullptr};
2160 ComPtr<IAudioCaptureClient> mCapture{nullptr};
2161 HANDLE mNotifyEvent{nullptr};
2163 ChannelConverter mChannelConv{};
2164 SampleConverterPtr mSampleConv;
2165 RingBufferPtr mRing;
2167 std::atomic<bool> mKillNow{true};
2168 std::thread mThread;
2171 WasapiCapture::~WasapiCapture()
2173 if(SUCCEEDED(mOpenStatus))
2174 pushMessage(MsgType::CloseDevice).wait();
2175 mOpenStatus = E_FAIL;
2177 if(mNotifyEvent != nullptr)
2178 CloseHandle(mNotifyEvent);
2179 mNotifyEvent = nullptr;
2183 FORCE_ALIGN int WasapiCapture::recordProc()
2185 ComWrapper com{COINIT_MULTITHREADED};
2186 if(!com)
2188 ERR("CoInitializeEx(nullptr, COINIT_MULTITHREADED) failed: 0x%08lx\n", com.status());
2189 mDevice->handleDisconnect("COM init failed: 0x%08lx", com.status());
2190 return 1;
2193 althrd_setname(GetRecordThreadName());
2195 std::vector<float> samples;
2196 while(!mKillNow.load(std::memory_order_relaxed))
2198 UINT32 avail;
2199 HRESULT hr{mCapture->GetNextPacketSize(&avail)};
2200 if(FAILED(hr))
2201 ERR("Failed to get next packet size: 0x%08lx\n", hr);
2202 else if(avail > 0)
2204 UINT32 numsamples;
2205 DWORD flags;
2206 BYTE *rdata;
2208 hr = mCapture->GetBuffer(&rdata, &numsamples, &flags, nullptr, nullptr);
2209 if(FAILED(hr))
2210 ERR("Failed to get capture buffer: 0x%08lx\n", hr);
2211 else
2213 if(mChannelConv.is_active())
2215 samples.resize(numsamples*2_uz);
2216 mChannelConv.convert(rdata, samples.data(), numsamples);
2217 rdata = reinterpret_cast<BYTE*>(samples.data());
2220 auto data = mRing->getWriteVector();
2222 size_t dstframes;
2223 if(mSampleConv)
2225 static constexpr auto lenlimit = size_t{std::numeric_limits<int>::max()};
2226 const void *srcdata{rdata};
2227 uint srcframes{numsamples};
2229 dstframes = mSampleConv->convert(&srcdata, &srcframes, data.first.buf,
2230 static_cast<uint>(std::min(data.first.len, lenlimit)));
2231 if(srcframes > 0 && dstframes == data.first.len && data.second.len > 0)
2233 /* If some source samples remain, all of the first dest
2234 * block was filled, and there's space in the second
2235 * dest block, do another run for the second block.
2237 dstframes += mSampleConv->convert(&srcdata, &srcframes, data.second.buf,
2238 static_cast<uint>(std::min(data.second.len, lenlimit)));
2241 else
2243 const uint framesize{mDevice->frameSizeFromFmt()};
2244 auto dst = al::span{rdata, size_t{numsamples}*framesize};
2245 size_t len1{std::min(data.first.len, size_t{numsamples})};
2246 size_t len2{std::min(data.second.len, numsamples-len1)};
2248 memcpy(data.first.buf, dst.data(), len1*framesize);
2249 if(len2 > 0)
2251 dst = dst.subspan(len1*framesize);
2252 memcpy(data.second.buf, dst.data(), len2*framesize);
2254 dstframes = len1 + len2;
2257 mRing->writeAdvance(dstframes);
2259 hr = mCapture->ReleaseBuffer(numsamples);
2260 if(FAILED(hr)) ERR("Failed to release capture buffer: 0x%08lx\n", hr);
2264 if(FAILED(hr))
2266 mDevice->handleDisconnect("Failed to capture samples: 0x%08lx", hr);
2267 break;
2270 DWORD res{WaitForSingleObjectEx(mNotifyEvent, 2000, FALSE)};
2271 if(res != WAIT_OBJECT_0)
2272 ERR("WaitForSingleObjectEx error: 0x%lx\n", res);
2275 return 0;
2279 void WasapiCapture::open(std::string_view name)
2281 if(SUCCEEDED(mOpenStatus))
2282 throw al::backend_exception{al::backend_error::DeviceError,
2283 "Unexpected duplicate open call"};
2285 mNotifyEvent = CreateEventW(nullptr, FALSE, FALSE, nullptr);
2286 if(mNotifyEvent == nullptr)
2288 ERR("Failed to create notify events: %lu\n", GetLastError());
2289 throw al::backend_exception{al::backend_error::DeviceError,
2290 "Failed to create notify events"};
2293 if(const auto prefix = GetDevicePrefix(); al::starts_with(name, prefix))
2294 name = name.substr(prefix.size());
2296 mOpenStatus = pushMessage(MsgType::OpenDevice, name).get();
2297 if(FAILED(mOpenStatus))
2298 throw al::backend_exception{al::backend_error::DeviceError, "Device init failed: 0x%08lx",
2299 mOpenStatus};
2301 HRESULT hr{pushMessage(MsgType::ResetDevice).get()};
2302 if(FAILED(hr))
2304 if(hr == E_OUTOFMEMORY)
2305 throw al::backend_exception{al::backend_error::OutOfMemory, "Out of memory"};
2306 throw al::backend_exception{al::backend_error::DeviceError, "Device reset failed"};
2310 HRESULT WasapiCapture::openProxy(std::string_view name)
2312 std::string devname;
2313 std::wstring devid;
2314 if(!name.empty())
2316 auto devlock = DeviceListLock{gDeviceList};
2317 auto devlist = al::span{devlock.getCaptureList()};
2318 auto iter = std::find_if(devlist.cbegin(), devlist.cend(),
2319 [name](const DevMap &entry) -> bool
2320 { return entry.name == name || entry.endpoint_guid == name; });
2321 if(iter == devlist.cend())
2323 const std::wstring wname{utf8_to_wstr(name)};
2324 iter = std::find_if(devlist.cbegin(), devlist.cend(),
2325 [&wname](const DevMap &entry) -> bool
2326 { return entry.devid == wname; });
2328 if(iter == devlist.cend())
2330 WARN("Failed to find device name matching \"%.*s\"\n", al::sizei(name), name.data());
2331 return E_FAIL;
2333 devname = iter->name;
2334 devid = iter->devid;
2337 HRESULT hr{sDeviceHelper->openDevice(devid, eCapture, mMMDev)};
2338 if(FAILED(hr))
2340 WARN("Failed to open device \"%s\"\n", devname.empty() ? "(default)" : devname.c_str());
2341 return hr;
2343 mClient = nullptr;
2344 if(!devname.empty())
2345 mDevice->DeviceName = std::string{GetDevicePrefix()}+std::move(devname);
2346 else
2347 mDevice->DeviceName = std::string{GetDevicePrefix()}+GetDeviceNameAndGuid(mMMDev).first;
2349 return S_OK;
2352 void WasapiCapture::closeProxy()
2354 mCapture = nullptr;
2355 mClient = nullptr;
2356 mMMDev = nullptr;
2359 HRESULT WasapiCapture::resetProxy()
2361 mCapture = nullptr;
2362 mClient = nullptr;
2364 HRESULT hr{sDeviceHelper->activateAudioClient(mMMDev, __uuidof(IAudioClient),
2365 al::out_ptr(mClient))};
2366 if(FAILED(hr))
2368 ERR("Failed to reactivate audio client: 0x%08lx\n", hr);
2369 return hr;
2372 WAVEFORMATEX *wfx;
2373 hr = mClient->GetMixFormat(&wfx);
2374 if(FAILED(hr))
2376 ERR("Failed to get capture format: 0x%08lx\n", hr);
2377 return hr;
2379 TraceFormat("Device capture format", wfx);
2381 WAVEFORMATEXTENSIBLE InputType{};
2382 if(!MakeExtensible(&InputType, wfx))
2384 CoTaskMemFree(wfx);
2385 return E_FAIL;
2387 CoTaskMemFree(wfx);
2388 wfx = nullptr;
2390 const bool isRear51{InputType.Format.nChannels == 6
2391 && (InputType.dwChannelMask&X51RearMask) == X5DOT1REAR};
2393 // Make sure buffer is at least 100ms in size
2394 ReferenceTime buf_time{ReferenceTime{seconds{mDevice->BufferSize}} / mDevice->Frequency};
2395 buf_time = std::max(buf_time, ReferenceTime{milliseconds{100}});
2397 InputType = {};
2398 InputType.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
2399 switch(mDevice->FmtChans)
2401 case DevFmtMono:
2402 InputType.Format.nChannels = 1;
2403 InputType.dwChannelMask = MONO;
2404 break;
2405 case DevFmtStereo:
2406 InputType.Format.nChannels = 2;
2407 InputType.dwChannelMask = STEREO;
2408 break;
2409 case DevFmtQuad:
2410 InputType.Format.nChannels = 4;
2411 InputType.dwChannelMask = QUAD;
2412 break;
2413 case DevFmtX51:
2414 InputType.Format.nChannels = 6;
2415 InputType.dwChannelMask = isRear51 ? X5DOT1REAR : X5DOT1;
2416 break;
2417 case DevFmtX61:
2418 InputType.Format.nChannels = 7;
2419 InputType.dwChannelMask = X6DOT1;
2420 break;
2421 case DevFmtX71:
2422 InputType.Format.nChannels = 8;
2423 InputType.dwChannelMask = X7DOT1;
2424 break;
2425 case DevFmtX714:
2426 InputType.Format.nChannels = 12;
2427 InputType.dwChannelMask = X7DOT1DOT4;
2428 break;
2430 case DevFmtX7144:
2431 case DevFmtX3D71:
2432 case DevFmtAmbi3D:
2433 return E_FAIL;
2435 switch(mDevice->FmtType)
2437 /* NOTE: Signedness doesn't matter, the converter will handle it. */
2438 case DevFmtByte:
2439 case DevFmtUByte:
2440 InputType.Format.wBitsPerSample = 8;
2441 InputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
2442 break;
2443 case DevFmtShort:
2444 case DevFmtUShort:
2445 InputType.Format.wBitsPerSample = 16;
2446 InputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
2447 break;
2448 case DevFmtInt:
2449 case DevFmtUInt:
2450 InputType.Format.wBitsPerSample = 32;
2451 InputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
2452 break;
2453 case DevFmtFloat:
2454 InputType.Format.wBitsPerSample = 32;
2455 InputType.SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT;
2456 break;
2458 /* NOLINTNEXTLINE(cppcoreguidelines-pro-type-union-access) */
2459 InputType.Samples.wValidBitsPerSample = InputType.Format.wBitsPerSample;
2460 InputType.Format.nSamplesPerSec = mDevice->Frequency;
2462 InputType.Format.nBlockAlign = static_cast<WORD>(InputType.Format.nChannels *
2463 InputType.Format.wBitsPerSample / 8);
2464 InputType.Format.nAvgBytesPerSec = InputType.Format.nSamplesPerSec *
2465 InputType.Format.nBlockAlign;
2466 InputType.Format.cbSize = sizeof(InputType) - sizeof(InputType.Format);
2468 TraceFormat("Requesting capture format", &InputType.Format);
2469 hr = mClient->IsFormatSupported(AUDCLNT_SHAREMODE_SHARED, &InputType.Format, &wfx);
2470 if(FAILED(hr))
2472 WARN("Failed to check capture format support: 0x%08lx\n", hr);
2473 hr = mClient->GetMixFormat(&wfx);
2475 if(FAILED(hr))
2477 ERR("Failed to find a supported capture format: 0x%08lx\n", hr);
2478 return hr;
2481 mSampleConv = nullptr;
2482 mChannelConv = {};
2484 if(wfx != nullptr)
2486 TraceFormat("Got capture format", wfx);
2487 if(!MakeExtensible(&InputType, wfx))
2489 CoTaskMemFree(wfx);
2490 return E_FAIL;
2492 CoTaskMemFree(wfx);
2493 wfx = nullptr;
2495 auto validate_fmt = [](DeviceBase *device, uint32_t chancount, DWORD chanmask) noexcept
2496 -> bool
2498 switch(device->FmtChans)
2500 /* If the device wants mono, we can handle any input. */
2501 case DevFmtMono:
2502 return true;
2503 /* If the device wants stereo, we can handle mono or stereo input. */
2504 case DevFmtStereo:
2505 return (chancount == 2 && (chanmask == 0 || (chanmask&StereoMask) == STEREO))
2506 || (chancount == 1 && (chanmask&MonoMask) == MONO);
2507 /* Otherwise, the device must match the input type. */
2508 case DevFmtQuad:
2509 return (chancount == 4 && (chanmask == 0 || (chanmask&QuadMask) == QUAD));
2510 /* 5.1 (Side) and 5.1 (Rear) are interchangeable here. */
2511 case DevFmtX51:
2512 return (chancount == 6 && (chanmask == 0 || (chanmask&X51Mask) == X5DOT1
2513 || (chanmask&X51RearMask) == X5DOT1REAR));
2514 case DevFmtX61:
2515 return (chancount == 7 && (chanmask == 0 || (chanmask&X61Mask) == X6DOT1));
2516 case DevFmtX71:
2517 case DevFmtX3D71:
2518 return (chancount == 8 && (chanmask == 0 || (chanmask&X71Mask) == X7DOT1));
2519 case DevFmtX714:
2520 return (chancount == 12 && (chanmask == 0 || (chanmask&X714Mask) == X7DOT1DOT4));
2521 case DevFmtX7144:
2522 return (chancount == 16 && chanmask == 0);
2523 case DevFmtAmbi3D:
2524 return (chanmask == 0 && chancount == device->channelsFromFmt());
2526 return false;
2528 if(!validate_fmt(mDevice, InputType.Format.nChannels, InputType.dwChannelMask))
2530 ERR("Failed to match format, wanted: %s %s %uhz, got: 0x%08lx mask %d channel%s %d-bit %luhz\n",
2531 DevFmtChannelsString(mDevice->FmtChans), DevFmtTypeString(mDevice->FmtType),
2532 mDevice->Frequency, InputType.dwChannelMask, InputType.Format.nChannels,
2533 (InputType.Format.nChannels==1)?"":"s", InputType.Format.wBitsPerSample,
2534 InputType.Format.nSamplesPerSec);
2535 return E_FAIL;
2539 DevFmtType srcType{};
2540 if(IsEqualGUID(InputType.SubFormat, KSDATAFORMAT_SUBTYPE_PCM))
2542 if(InputType.Format.wBitsPerSample == 8)
2543 srcType = DevFmtUByte;
2544 else if(InputType.Format.wBitsPerSample == 16)
2545 srcType = DevFmtShort;
2546 else if(InputType.Format.wBitsPerSample == 32)
2547 srcType = DevFmtInt;
2548 else
2550 ERR("Unhandled integer bit depth: %d\n", InputType.Format.wBitsPerSample);
2551 return E_FAIL;
2554 else if(IsEqualGUID(InputType.SubFormat, KSDATAFORMAT_SUBTYPE_IEEE_FLOAT))
2556 if(InputType.Format.wBitsPerSample == 32)
2557 srcType = DevFmtFloat;
2558 else
2560 ERR("Unhandled float bit depth: %d\n", InputType.Format.wBitsPerSample);
2561 return E_FAIL;
2564 else
2566 ERR("Unhandled format sub-type: %s\n", GuidPrinter{InputType.SubFormat}.c_str());
2567 return E_FAIL;
2570 if(mDevice->FmtChans == DevFmtMono && InputType.Format.nChannels != 1)
2572 uint chanmask{(1u<<InputType.Format.nChannels) - 1u};
2573 /* Exclude LFE from the downmix. */
2574 if((InputType.dwChannelMask&SPEAKER_LOW_FREQUENCY))
2576 constexpr auto lfemask = MaskFromTopBits(SPEAKER_LOW_FREQUENCY);
2577 const int lfeidx{al::popcount(InputType.dwChannelMask&lfemask) - 1};
2578 chanmask &= ~(1u << lfeidx);
2581 mChannelConv = ChannelConverter{srcType, InputType.Format.nChannels, chanmask,
2582 mDevice->FmtChans};
2583 TRACE("Created %s multichannel-to-mono converter\n", DevFmtTypeString(srcType));
2584 /* The channel converter always outputs float, so change the input type
2585 * for the resampler/type-converter.
2587 srcType = DevFmtFloat;
2589 else if(mDevice->FmtChans == DevFmtStereo && InputType.Format.nChannels == 1)
2591 mChannelConv = ChannelConverter{srcType, 1, 0x1, mDevice->FmtChans};
2592 TRACE("Created %s mono-to-stereo converter\n", DevFmtTypeString(srcType));
2593 srcType = DevFmtFloat;
2596 if(mDevice->Frequency != InputType.Format.nSamplesPerSec || mDevice->FmtType != srcType)
2598 mSampleConv = SampleConverter::Create(srcType, mDevice->FmtType,
2599 mDevice->channelsFromFmt(), InputType.Format.nSamplesPerSec, mDevice->Frequency,
2600 Resampler::FastBSinc24);
2601 if(!mSampleConv)
2603 ERR("Failed to create converter for %s format, dst: %s %uhz, src: %s %luhz\n",
2604 DevFmtChannelsString(mDevice->FmtChans), DevFmtTypeString(mDevice->FmtType),
2605 mDevice->Frequency, DevFmtTypeString(srcType), InputType.Format.nSamplesPerSec);
2606 return E_FAIL;
2608 TRACE("Created converter for %s format, dst: %s %uhz, src: %s %luhz\n",
2609 DevFmtChannelsString(mDevice->FmtChans), DevFmtTypeString(mDevice->FmtType),
2610 mDevice->Frequency, DevFmtTypeString(srcType), InputType.Format.nSamplesPerSec);
2613 hr = mClient->Initialize(AUDCLNT_SHAREMODE_SHARED, AUDCLNT_STREAMFLAGS_EVENTCALLBACK,
2614 buf_time.count(), 0, &InputType.Format, nullptr);
2615 if(FAILED(hr))
2617 ERR("Failed to initialize audio client: 0x%08lx\n", hr);
2618 return hr;
2621 hr = mClient->GetService(__uuidof(IAudioCaptureClient), al::out_ptr(mCapture));
2622 if(FAILED(hr))
2624 ERR("Failed to get IAudioCaptureClient: 0x%08lx\n", hr);
2625 return hr;
2628 UINT32 buffer_len{};
2629 ReferenceTime min_per{};
2630 hr = mClient->GetDevicePeriod(&reinterpret_cast<REFERENCE_TIME&>(min_per), nullptr);
2631 if(SUCCEEDED(hr))
2632 hr = mClient->GetBufferSize(&buffer_len);
2633 if(FAILED(hr))
2635 ERR("Failed to get buffer size: 0x%08lx\n", hr);
2636 return hr;
2638 mDevice->UpdateSize = RefTime2Samples(min_per, mDevice->Frequency);
2639 mDevice->BufferSize = buffer_len;
2641 mRing = RingBuffer::Create(buffer_len, mDevice->frameSizeFromFmt(), false);
2643 hr = mClient->SetEventHandle(mNotifyEvent);
2644 if(FAILED(hr))
2646 ERR("Failed to set event handle: 0x%08lx\n", hr);
2647 return hr;
2650 return hr;
2654 void WasapiCapture::start()
2656 const HRESULT hr{pushMessage(MsgType::StartDevice).get()};
2657 if(FAILED(hr))
2658 throw al::backend_exception{al::backend_error::DeviceError,
2659 "Failed to start recording: 0x%lx", hr};
2662 HRESULT WasapiCapture::startProxy()
2664 ResetEvent(mNotifyEvent);
2666 HRESULT hr{mClient->Start()};
2667 if(FAILED(hr))
2669 ERR("Failed to start audio client: 0x%08lx\n", hr);
2670 return hr;
2673 try {
2674 mKillNow.store(false, std::memory_order_release);
2675 mThread = std::thread{std::mem_fn(&WasapiCapture::recordProc), this};
2677 catch(...) {
2678 ERR("Failed to start thread\n");
2679 mClient->Stop();
2680 mClient->Reset();
2681 hr = E_FAIL;
2684 return hr;
2688 void WasapiCapture::stop()
2689 { pushMessage(MsgType::StopDevice).wait(); }
2691 void WasapiCapture::stopProxy()
2693 if(!mThread.joinable())
2694 return;
2696 mKillNow.store(true, std::memory_order_release);
2697 mThread.join();
2699 mClient->Stop();
2700 mClient->Reset();
2704 void WasapiCapture::captureSamples(std::byte *buffer, uint samples)
2705 { std::ignore = mRing->read(buffer, samples); }
2707 uint WasapiCapture::availableSamples()
2708 { return static_cast<uint>(mRing->readSpace()); }
2710 } // namespace
2713 bool WasapiBackendFactory::init()
2715 static HRESULT InitResult{E_FAIL};
2716 if(FAILED(InitResult)) try
2718 std::promise<HRESULT> promise;
2719 auto future = promise.get_future();
2721 std::thread{&WasapiProxy::messageHandler, &promise}.detach();
2722 InitResult = future.get();
2724 catch(...) {
2727 return SUCCEEDED(InitResult);
2730 bool WasapiBackendFactory::querySupport(BackendType type)
2731 { return type == BackendType::Playback || type == BackendType::Capture; }
2733 auto WasapiBackendFactory::enumerate(BackendType type) -> std::vector<std::string>
2735 std::vector<std::string> outnames;
2737 auto devlock = DeviceListLock{gDeviceList};
2738 switch(type)
2740 case BackendType::Playback:
2742 auto defaultId = devlock.getPlaybackDefaultId();
2743 for(const DevMap &entry : devlock.getPlaybackList())
2745 if(entry.devid != defaultId)
2747 outnames.emplace_back(std::string{GetDevicePrefix()}+entry.name);
2748 continue;
2750 /* Default device goes first. */
2751 outnames.emplace(outnames.cbegin(), std::string{GetDevicePrefix()}+entry.name);
2754 break;
2756 case BackendType::Capture:
2758 auto defaultId = devlock.getCaptureDefaultId();
2759 for(const DevMap &entry : devlock.getCaptureList())
2761 if(entry.devid != defaultId)
2763 outnames.emplace_back(std::string{GetDevicePrefix()}+entry.name);
2764 continue;
2766 outnames.emplace(outnames.cbegin(), std::string{GetDevicePrefix()}+entry.name);
2769 break;
2772 return outnames;
2775 BackendPtr WasapiBackendFactory::createBackend(DeviceBase *device, BackendType type)
2777 if(type == BackendType::Playback)
2778 return BackendPtr{new WasapiPlayback{device}};
2779 if(type == BackendType::Capture)
2780 return BackendPtr{new WasapiCapture{device}};
2781 return nullptr;
2784 BackendFactory &WasapiBackendFactory::getFactory()
2786 static WasapiBackendFactory factory{};
2787 return factory;
2790 alc::EventSupport WasapiBackendFactory::queryEventSupport(alc::EventType eventType, BackendType)
2792 switch(eventType)
2794 case alc::EventType::DefaultDeviceChanged:
2795 return alc::EventSupport::FullSupport;
2797 case alc::EventType::DeviceAdded:
2798 case alc::EventType::DeviceRemoved:
2799 #if !defined(ALSOFT_UWP)
2800 return alc::EventSupport::FullSupport;
2801 #endif
2803 case alc::EventType::Count:
2804 break;
2806 return alc::EventSupport::NoSupport;