Don't call GetForegroundWindow on UWP
[openal-soft.git] / alc / backends / wasapi.cpp
blob0e51996b7c55a5c366ac04f3d04bbdc0de5f5677
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 <wtypes.h>
33 #include <mmdeviceapi.h>
34 #include <audiosessiontypes.h>
35 #include <audioclient.h>
36 #include <spatialaudioclient.h>
37 #include <cguid.h>
38 #include <devpropdef.h>
39 #include <mmreg.h>
40 #include <propsys.h>
41 #include <propkey.h>
42 #include <devpkey.h>
43 #ifndef _WAVEFORMATEXTENSIBLE_
44 #include <ks.h>
45 #include <ksmedia.h>
46 #endif
48 #include <algorithm>
49 #include <atomic>
50 #include <chrono>
51 #include <condition_variable>
52 #include <cstring>
53 #include <deque>
54 #include <functional>
55 #include <future>
56 #include <mutex>
57 #include <string>
58 #include <string_view>
59 #include <thread>
60 #include <vector>
62 #include "albit.h"
63 #include "alc/alconfig.h"
64 #include "alnumeric.h"
65 #include "alspan.h"
66 #include "alstring.h"
67 #include "althrd_setname.h"
68 #include "comptr.h"
69 #include "core/converter.h"
70 #include "core/device.h"
71 #include "core/helpers.h"
72 #include "core/logging.h"
73 #include "ringbuffer.h"
74 #include "strutils.h"
76 #if defined(ALSOFT_UWP)
78 #include <winrt/Windows.Media.Core.h> // !!This is important!!
79 #include <winrt/Windows.Foundation.Collections.h>
80 #include <winrt/Windows.Devices.h>
81 #include <winrt/Windows.Foundation.h>
82 #include <winrt/Windows.Devices.Enumeration.h>
83 #include <winrt/Windows.Media.Devices.h>
85 using namespace winrt;
86 using namespace Windows::Foundation;
87 using namespace Windows::Media::Devices;
88 using namespace Windows::Devices::Enumeration;
89 using namespace Windows::Media::Devices;
90 #endif
92 /* Some headers seem to define these as macros for __uuidof, which is annoying
93 * since some headers don't declare them at all. Hopefully the ifdef is enough
94 * to tell if they need to be declared.
96 #ifndef KSDATAFORMAT_SUBTYPE_PCM
97 DEFINE_GUID(KSDATAFORMAT_SUBTYPE_PCM, 0x00000001, 0x0000, 0x0010, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71);
98 #endif
99 #ifndef KSDATAFORMAT_SUBTYPE_IEEE_FLOAT
100 DEFINE_GUID(KSDATAFORMAT_SUBTYPE_IEEE_FLOAT, 0x00000003, 0x0000, 0x0010, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71);
101 #endif
102 #if !defined(ALSOFT_UWP)
103 DEFINE_DEVPROPKEY(DEVPKEY_Device_FriendlyName, 0xa45c254e, 0xdf1c, 0x4efd, 0x80,0x20, 0x67,0xd1,0x46,0xa8,0x50,0xe0, 14);
104 DEFINE_PROPERTYKEY(PKEY_AudioEndpoint_FormFactor, 0x1da5d803, 0xd492, 0x4edd, 0x8c,0x23, 0xe0,0xc0,0xff,0xee,0x7f,0x0e, 0);
105 DEFINE_PROPERTYKEY(PKEY_AudioEndpoint_GUID, 0x1da5d803, 0xd492, 0x4edd, 0x8c, 0x23,0xe0, 0xc0,0xff,0xee,0x7f,0x0e, 4 );
106 #endif
108 namespace {
110 using namespace std::string_view_literals;
111 using std::chrono::nanoseconds;
112 using std::chrono::milliseconds;
113 using std::chrono::seconds;
115 [[nodiscard]] constexpr auto GetDevicePrefix() noexcept { return "OpenAL Soft on "sv; }
117 using ReferenceTime = std::chrono::duration<REFERENCE_TIME,std::ratio<1,10'000'000>>;
120 #define MONO SPEAKER_FRONT_CENTER
121 #define STEREO (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT)
122 #define QUAD (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT|SPEAKER_BACK_LEFT|SPEAKER_BACK_RIGHT)
123 #define X5DOT1 (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT|SPEAKER_FRONT_CENTER|SPEAKER_LOW_FREQUENCY|SPEAKER_SIDE_LEFT|SPEAKER_SIDE_RIGHT)
124 #define X5DOT1REAR (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT|SPEAKER_FRONT_CENTER|SPEAKER_LOW_FREQUENCY|SPEAKER_BACK_LEFT|SPEAKER_BACK_RIGHT)
125 #define X6DOT1 (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT|SPEAKER_FRONT_CENTER|SPEAKER_LOW_FREQUENCY|SPEAKER_BACK_CENTER|SPEAKER_SIDE_LEFT|SPEAKER_SIDE_RIGHT)
126 #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)
127 #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)
129 constexpr inline DWORD MaskFromTopBits(DWORD b) noexcept
131 b |= b>>1;
132 b |= b>>2;
133 b |= b>>4;
134 b |= b>>8;
135 b |= b>>16;
136 return b;
138 constexpr DWORD MonoMask{MaskFromTopBits(MONO)};
139 constexpr DWORD StereoMask{MaskFromTopBits(STEREO)};
140 constexpr DWORD QuadMask{MaskFromTopBits(QUAD)};
141 constexpr DWORD X51Mask{MaskFromTopBits(X5DOT1)};
142 constexpr DWORD X51RearMask{MaskFromTopBits(X5DOT1REAR)};
143 constexpr DWORD X61Mask{MaskFromTopBits(X6DOT1)};
144 constexpr DWORD X71Mask{MaskFromTopBits(X7DOT1)};
145 constexpr DWORD X714Mask{MaskFromTopBits(X7DOT1DOT4)};
148 #ifndef _MSC_VER
149 constexpr AudioObjectType operator|(AudioObjectType lhs, AudioObjectType rhs) noexcept
150 { return static_cast<AudioObjectType>(lhs | al::to_underlying(rhs)); }
151 #endif
153 constexpr AudioObjectType ChannelMask_Mono{AudioObjectType_FrontCenter};
154 constexpr AudioObjectType ChannelMask_Stereo{AudioObjectType_FrontLeft
155 | AudioObjectType_FrontRight};
156 constexpr AudioObjectType ChannelMask_Quad{AudioObjectType_FrontLeft | AudioObjectType_FrontRight
157 | AudioObjectType_BackLeft | AudioObjectType_BackRight};
158 constexpr AudioObjectType ChannelMask_X51{AudioObjectType_FrontLeft | AudioObjectType_FrontRight
159 | AudioObjectType_FrontCenter | AudioObjectType_LowFrequency | AudioObjectType_SideLeft
160 | AudioObjectType_SideRight};
161 constexpr AudioObjectType ChannelMask_X51Rear{AudioObjectType_FrontLeft
162 | AudioObjectType_FrontRight | AudioObjectType_FrontCenter | AudioObjectType_LowFrequency
163 | AudioObjectType_BackLeft | AudioObjectType_BackRight};
164 constexpr AudioObjectType ChannelMask_X61{AudioObjectType_FrontLeft | AudioObjectType_FrontRight
165 | AudioObjectType_FrontCenter | AudioObjectType_LowFrequency | AudioObjectType_SideLeft
166 | AudioObjectType_SideRight | AudioObjectType_BackCenter};
167 constexpr AudioObjectType ChannelMask_X71{AudioObjectType_FrontLeft | AudioObjectType_FrontRight
168 | AudioObjectType_FrontCenter | AudioObjectType_LowFrequency | AudioObjectType_SideLeft
169 | AudioObjectType_SideRight | AudioObjectType_BackLeft | AudioObjectType_BackRight};
170 constexpr AudioObjectType ChannelMask_X714{AudioObjectType_FrontLeft | AudioObjectType_FrontRight
171 | AudioObjectType_FrontCenter | AudioObjectType_LowFrequency | AudioObjectType_SideLeft
172 | AudioObjectType_SideRight | AudioObjectType_BackLeft | AudioObjectType_BackRight
173 | AudioObjectType_TopFrontLeft | AudioObjectType_TopFrontRight | AudioObjectType_TopBackLeft
174 | AudioObjectType_TopBackRight};
175 constexpr AudioObjectType ChannelMask_X7144{AudioObjectType_FrontLeft | AudioObjectType_FrontRight
176 | AudioObjectType_FrontCenter | AudioObjectType_LowFrequency | AudioObjectType_SideLeft
177 | AudioObjectType_SideRight | AudioObjectType_BackLeft | AudioObjectType_BackRight
178 | AudioObjectType_TopFrontLeft | AudioObjectType_TopFrontRight | AudioObjectType_TopBackLeft
179 | AudioObjectType_TopBackRight | AudioObjectType_BottomFrontLeft
180 | AudioObjectType_BottomFrontRight | AudioObjectType_BottomBackLeft
181 | AudioObjectType_BottomBackRight};
184 template<typename... Ts>
185 struct overloaded : Ts... { using Ts::operator()...; };
187 template<typename... Ts>
188 overloaded(Ts...) -> overloaded<Ts...>;
191 template<typename T>
192 constexpr auto as_unsigned(T value) noexcept
194 using UT = std::make_unsigned_t<T>;
195 return static_cast<UT>(value);
199 /* Scales the given reftime value, rounding the result. */
200 template<typename T>
201 constexpr uint RefTime2Samples(const ReferenceTime &val, T srate) noexcept
203 const auto retval = (val*srate + ReferenceTime{seconds{1}}/2) / seconds{1};
204 return static_cast<uint>(std::min<decltype(retval)>(retval, std::numeric_limits<uint>::max()));
208 class GuidPrinter {
209 std::array<char,64> mMsg{};
211 public:
212 GuidPrinter(const GUID &guid)
214 std::snprintf(mMsg.data(), mMsg.size(), "{%08lx-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x}",
215 DWORD{guid.Data1}, guid.Data2, guid.Data3, guid.Data4[0], guid.Data4[1], guid.Data4[2],
216 guid.Data4[3], guid.Data4[4], guid.Data4[5], guid.Data4[6], guid.Data4[7]);
218 [[nodiscard]] auto c_str() const -> const char* { return mMsg.data(); }
221 struct PropVariant {
222 PROPVARIANT mProp{};
224 public:
225 PropVariant() { PropVariantInit(&mProp); }
226 PropVariant(const PropVariant &rhs) : PropVariant{} { PropVariantCopy(&mProp, &rhs.mProp); }
227 ~PropVariant() { clear(); }
229 auto operator=(const PropVariant &rhs) -> PropVariant&
231 if(this != &rhs)
232 PropVariantCopy(&mProp, &rhs.mProp);
233 return *this;
236 void clear() { PropVariantClear(&mProp); }
238 PROPVARIANT* get() noexcept { return &mProp; }
240 /* NOLINTBEGIN(cppcoreguidelines-pro-type-union-access) */
241 [[nodiscard]]
242 auto type() const noexcept -> VARTYPE { return mProp.vt; }
244 template<typename T> [[nodiscard]]
245 auto value() const -> T
247 if constexpr(std::is_same_v<T,uint>)
249 alassert(mProp.vt == VT_UI4 || mProp.vt == VT_UINT);
250 return mProp.uintVal;
252 else if constexpr(std::is_same_v<T,std::wstring_view> || std::is_same_v<T,std::wstring>
253 || std::is_same_v<T,LPWSTR> || std::is_same_v<T,LPCWSTR>)
255 alassert(mProp.vt == VT_LPWSTR);
256 return mProp.pwszVal;
260 void setBlob(const al::span<BYTE> data)
262 if constexpr(sizeof(size_t) > sizeof(ULONG))
263 alassert(data.size() <= std::numeric_limits<ULONG>::max());
264 mProp.vt = VT_BLOB;
265 mProp.blob.cbSize = static_cast<ULONG>(data.size());
266 mProp.blob.pBlobData = data.data();
268 /* NOLINTEND(cppcoreguidelines-pro-type-union-access) */
271 struct DevMap {
272 std::string name;
273 std::string endpoint_guid; // obtained from PKEY_AudioEndpoint_GUID , set to "Unknown device GUID" if absent.
274 std::wstring devid;
276 template<typename T0, typename T1, typename T2>
277 DevMap(T0&& name_, T1&& guid_, T2&& devid_)
278 : name{std::forward<T0>(name_)}
279 , endpoint_guid{std::forward<T1>(guid_)}
280 , devid{std::forward<T2>(devid_)}
282 /* To prevent GCC from complaining it doesn't want to inline this. */
283 ~DevMap();
285 DevMap::~DevMap() = default;
287 bool checkName(const al::span<DevMap> list, const std::string_view name)
289 auto match_name = [name](const DevMap &entry) -> bool { return entry.name == name; };
290 return std::find_if(list.cbegin(), list.cend(), match_name) != list.cend();
294 struct DeviceList {
295 auto lock() noexcept(noexcept(mMutex.lock())) { return mMutex.lock(); }
296 auto unlock() noexcept(noexcept(mMutex.unlock())) { return mMutex.unlock(); }
298 private:
299 std::mutex mMutex;
300 std::vector<DevMap> mPlayback;
301 std::vector<DevMap> mCapture;
302 std::wstring mPlaybackDefaultId;
303 std::wstring mCaptureDefaultId;
305 friend struct DeviceListLock;
307 struct DeviceListLock : public std::unique_lock<DeviceList> {
308 using std::unique_lock<DeviceList>::unique_lock;
310 [[nodiscard]] auto& getPlaybackList() const noexcept { return mutex()->mPlayback; }
311 [[nodiscard]] auto& getCaptureList() const noexcept { return mutex()->mCapture; }
313 void setPlaybackDefaultId(std::wstring_view devid) const { mutex()->mPlaybackDefaultId = devid; }
314 [[nodiscard]] auto getPlaybackDefaultId() const noexcept -> std::wstring_view { return mutex()->mPlaybackDefaultId; }
315 void setCaptureDefaultId(std::wstring_view devid) const { mutex()->mCaptureDefaultId = devid; }
316 [[nodiscard]] auto getCaptureDefaultId() const noexcept -> std::wstring_view { return mutex()->mCaptureDefaultId; }
319 DeviceList gDeviceList;
322 #if defined(ALSOFT_UWP)
323 enum EDataFlow {
324 eRender = 0,
325 eCapture = (eRender + 1),
326 eAll = (eCapture + 1),
327 EDataFlow_enum_count = (eAll + 1)
329 #endif
331 #if defined(ALSOFT_UWP)
332 using DeviceHandle = Windows::Devices::Enumeration::DeviceInformation;
333 using EventRegistrationToken = winrt::event_token;
334 #else
335 using DeviceHandle = ComPtr<IMMDevice>;
336 #endif
339 using NameGUIDPair = std::pair<std::string,std::string>;
340 NameGUIDPair GetDeviceNameAndGuid(const DeviceHandle &device)
342 constexpr auto UnknownName = "Unknown Device Name"sv;
343 constexpr auto UnknownGuid = "Unknown Device GUID"sv;
345 #if !defined(ALSOFT_UWP)
346 std::string name, guid;
348 ComPtr<IPropertyStore> ps;
349 HRESULT hr{device->OpenPropertyStore(STGM_READ, al::out_ptr(ps))};
350 if(FAILED(hr))
352 WARN("OpenPropertyStore failed: 0x%08lx\n", hr);
353 return {std::string{UnknownName}, std::string{UnknownGuid}};
356 PropVariant pvprop;
357 hr = ps->GetValue(al::bit_cast<PROPERTYKEY>(DEVPKEY_Device_FriendlyName), pvprop.get());
358 if(FAILED(hr))
359 WARN("GetValue Device_FriendlyName failed: 0x%08lx\n", hr);
360 else if(pvprop.type() == VT_LPWSTR)
361 name = wstr_to_utf8(pvprop.value<std::wstring_view>());
362 else
363 WARN("Unexpected Device_FriendlyName PROPVARIANT type: 0x%04x\n", pvprop.type());
365 pvprop.clear();
366 hr = ps->GetValue(al::bit_cast<PROPERTYKEY>(PKEY_AudioEndpoint_GUID), pvprop.get());
367 if(FAILED(hr))
368 WARN("GetValue AudioEndpoint_GUID failed: 0x%08lx\n", hr);
369 else if(pvprop.type() == VT_LPWSTR)
370 guid = wstr_to_utf8(pvprop.value<std::wstring_view>());
371 else
372 WARN("Unexpected AudioEndpoint_GUID PROPVARIANT type: 0x%04x\n", pvprop.type());
373 #else
374 std::string name{wstr_to_utf8(device.Name())};
375 std::string guid;
376 // device->Id is DeviceInterfacePath: \\?\SWD#MMDEVAPI#{0.0.0.00000000}.{a21c17a0-fc1d-405e-ab5a-b513422b57d1}#{e6327cad-dcec-4949-ae8a-991e976a79d2}
377 auto devIfPath = device.Id();
378 if(auto devIdStart = wcsstr(devIfPath.data(), L"}."))
380 devIdStart += 2; // L"}."
381 if(auto devIdStartEnd = wcschr(devIdStart, L'#'))
383 std::wstring wDevId{devIdStart, static_cast<size_t>(devIdStartEnd - devIdStart)};
384 guid = wstr_to_utf8(wDevId.c_str());
385 std::transform(guid.begin(), guid.end(), guid.begin(),
386 [](char ch) { return static_cast<char>(std::toupper(ch)); });
389 #endif
390 if(name.empty()) name = UnknownName;
391 if(guid.empty()) guid = UnknownGuid;
392 return {std::move(name), std::move(guid)};
394 #if !defined(ALSOFT_UWP)
395 EndpointFormFactor GetDeviceFormfactor(IMMDevice *device)
397 ComPtr<IPropertyStore> ps;
398 HRESULT hr{device->OpenPropertyStore(STGM_READ, al::out_ptr(ps))};
399 if(FAILED(hr))
401 WARN("OpenPropertyStore failed: 0x%08lx\n", hr);
402 return UnknownFormFactor;
405 EndpointFormFactor formfactor{UnknownFormFactor};
406 PropVariant pvform;
407 hr = ps->GetValue(PKEY_AudioEndpoint_FormFactor, pvform.get());
408 if(FAILED(hr))
409 WARN("GetValue AudioEndpoint_FormFactor failed: 0x%08lx\n", hr);
410 else if(pvform.type() == VT_UI4)
411 formfactor = static_cast<EndpointFormFactor>(pvform.value<uint>());
412 else if(pvform.type() != VT_EMPTY)
413 WARN("Unexpected PROPVARIANT type: 0x%04x\n", pvform.type());
414 return formfactor;
416 #endif
419 #if defined(ALSOFT_UWP)
420 struct DeviceHelper final : public IActivateAudioInterfaceCompletionHandler
421 #else
422 struct DeviceHelper final : private IMMNotificationClient
423 #endif
425 #if defined(ALSOFT_UWP)
426 DeviceHelper()
428 /* TODO: UWP also needs to watch for device added/removed events and
429 * dynamically add/remove devices from the lists.
431 mActiveClientEvent = CreateEventW(nullptr, FALSE, FALSE, nullptr);
433 mRenderDeviceChangedToken = MediaDevice::DefaultAudioRenderDeviceChanged([this](const IInspectable& /*sender*/, const DefaultAudioRenderDeviceChangedEventArgs& args) {
434 if (args.Role() == AudioDeviceRole::Default)
436 const std::string msg{ "Default playback device changed: " +
437 wstr_to_utf8(args.Id())};
438 alc::Event(alc::EventType::DefaultDeviceChanged, alc::DeviceType::Playback,
439 msg);
443 mCaptureDeviceChangedToken = MediaDevice::DefaultAudioCaptureDeviceChanged([this](const IInspectable& /*sender*/, const DefaultAudioCaptureDeviceChangedEventArgs& args) {
444 if (args.Role() == AudioDeviceRole::Default)
446 const std::string msg{ "Default capture device changed: " +
447 wstr_to_utf8(args.Id()) };
448 alc::Event(alc::EventType::DefaultDeviceChanged, alc::DeviceType::Capture,
449 msg);
453 #else
454 DeviceHelper() = default;
455 #endif
456 ~DeviceHelper()
458 #if defined(ALSOFT_UWP)
459 MediaDevice::DefaultAudioRenderDeviceChanged(mRenderDeviceChangedToken);
460 MediaDevice::DefaultAudioCaptureDeviceChanged(mCaptureDeviceChangedToken);
462 if(mActiveClientEvent != nullptr)
463 CloseHandle(mActiveClientEvent);
464 mActiveClientEvent = nullptr;
465 #else
466 if(mEnumerator)
467 mEnumerator->UnregisterEndpointNotificationCallback(this);
468 mEnumerator = nullptr;
469 #endif
472 /** -------------------------- IUnknown ----------------------------- */
473 std::atomic<ULONG> mRefCount{1};
474 STDMETHODIMP_(ULONG) AddRef() noexcept override { return mRefCount.fetch_add(1u) + 1u; }
475 STDMETHODIMP_(ULONG) Release() noexcept override { return mRefCount.fetch_sub(1u) - 1u; }
477 STDMETHODIMP QueryInterface(const IID& IId, void **UnknownPtrPtr) noexcept override
479 // Three rules of QueryInterface:
480 // https://docs.microsoft.com/en-us/windows/win32/com/rules-for-implementing-queryinterface
481 // 1. Objects must have identity.
482 // 2. The set of interfaces on an object instance must be static.
483 // 3. It must be possible to query successfully for any interface on an object from any other interface.
485 // If ppvObject(the address) is nullptr, then this method returns E_POINTER.
486 if(!UnknownPtrPtr)
487 return E_POINTER;
489 // https://docs.microsoft.com/en-us/windows/win32/com/implementing-reference-counting
490 // Whenever a client calls a method(or API function), such as QueryInterface, that returns a new interface
491 // pointer, the method being called is responsible for incrementing the reference count through the returned
492 // pointer. For example, when a client first creates an object, it receives an interface pointer to an object
493 // that, from the client's point of view, has a reference count of one. If the client then calls AddRef on the
494 // interface pointer, the reference count becomes two. The client must call Release twice on the interface
495 // pointer to drop all of its references to the object.
496 #if defined(ALSOFT_UWP)
497 if(IId == __uuidof(IActivateAudioInterfaceCompletionHandler))
499 *UnknownPtrPtr = static_cast<IActivateAudioInterfaceCompletionHandler*>(this);
500 AddRef();
501 return S_OK;
503 #else
504 if(IId == __uuidof(IMMNotificationClient))
506 *UnknownPtrPtr = static_cast<IMMNotificationClient*>(this);
507 AddRef();
508 return S_OK;
510 #endif
511 else if(IId == __uuidof(IAgileObject) || IId == __uuidof(IUnknown))
513 *UnknownPtrPtr = static_cast<IUnknown*>(this);
514 AddRef();
515 return S_OK;
518 // This method returns S_OK if the interface is supported, and E_NOINTERFACE otherwise.
519 *UnknownPtrPtr = nullptr;
520 return E_NOINTERFACE;
523 #if defined(ALSOFT_UWP)
524 /** ----------------------- IActivateAudioInterfaceCompletionHandler ------------ */
525 HRESULT ActivateCompleted(IActivateAudioInterfaceAsyncOperation*) override
527 SetEvent(mActiveClientEvent);
529 // Need to return S_OK
530 return S_OK;
532 #else
533 /** ----------------------- IMMNotificationClient ------------ */
534 STDMETHODIMP OnDeviceStateChanged(LPCWSTR /*pwstrDeviceId*/, DWORD /*dwNewState*/) noexcept override { return S_OK; }
536 STDMETHODIMP OnDeviceAdded(LPCWSTR pwstrDeviceId) noexcept override
538 ComPtr<IMMDevice> device;
539 HRESULT hr{mEnumerator->GetDevice(pwstrDeviceId, al::out_ptr(device))};
540 if(FAILED(hr))
542 ERR("Failed to get device: 0x%08lx\n", hr);
543 return S_OK;
546 ComPtr<IMMEndpoint> endpoint;
547 hr = device->QueryInterface(__uuidof(IMMEndpoint), al::out_ptr(endpoint));
548 if(FAILED(hr))
550 ERR("Failed to get device endpoint: 0x%08lx\n", hr);
551 return S_OK;
554 EDataFlow flowdir{};
555 hr = endpoint->GetDataFlow(&flowdir);
556 if(FAILED(hr))
558 ERR("Failed to get endpoint data flow: 0x%08lx\n", hr);
559 return S_OK;
562 auto devlock = DeviceListLock{gDeviceList};
563 auto &list = (flowdir==eRender) ? devlock.getPlaybackList() : devlock.getCaptureList();
565 if(AddDevice(device, pwstrDeviceId, list))
567 const auto devtype = (flowdir==eRender) ? alc::DeviceType::Playback
568 : alc::DeviceType::Capture;
569 const std::string msg{"Device added: "+list.back().name};
570 alc::Event(alc::EventType::DeviceAdded, devtype, msg);
573 return S_OK;
576 STDMETHODIMP OnDeviceRemoved(LPCWSTR pwstrDeviceId) noexcept override
578 auto devlock = DeviceListLock{gDeviceList};
579 for(auto flowdir : std::array{eRender, eCapture})
581 auto &list = (flowdir==eRender) ? devlock.getPlaybackList() : devlock.getCaptureList();
582 auto devtype = (flowdir==eRender)?alc::DeviceType::Playback : alc::DeviceType::Capture;
584 /* Find the ID in the list to remove. */
585 auto iter = std::find_if(list.begin(), list.end(),
586 [pwstrDeviceId](const DevMap &entry) noexcept
587 { return pwstrDeviceId == entry.devid; });
588 if(iter == list.end()) continue;
590 TRACE("Removing device \"%s\", \"%s\", \"%ls\"\n", iter->name.c_str(),
591 iter->endpoint_guid.c_str(), iter->devid.c_str());
593 std::string msg{"Device removed: "+std::move(iter->name)};
594 list.erase(iter);
596 alc::Event(alc::EventType::DeviceRemoved, devtype, msg);
598 return S_OK;
601 STDMETHODIMP OnPropertyValueChanged(LPCWSTR /*pwstrDeviceId*/, const PROPERTYKEY /*key*/) noexcept override { return S_OK; }
603 STDMETHODIMP OnDefaultDeviceChanged(EDataFlow flow, ERole role, LPCWSTR pwstrDefaultDeviceId) noexcept override
605 if(role != eMultimedia)
606 return S_OK;
608 const std::wstring_view devid{pwstrDefaultDeviceId ? pwstrDefaultDeviceId
609 : std::wstring_view{}};
610 if(flow == eRender)
612 DeviceListLock{gDeviceList}.setPlaybackDefaultId(devid);
613 const std::string msg{"Default playback device changed: " + wstr_to_utf8(devid)};
614 alc::Event(alc::EventType::DefaultDeviceChanged, alc::DeviceType::Playback, msg);
616 else if(flow == eCapture)
618 DeviceListLock{gDeviceList}.setCaptureDefaultId(devid);
619 const std::string msg{"Default capture device changed: " + wstr_to_utf8(devid)};
620 alc::Event(alc::EventType::DefaultDeviceChanged, alc::DeviceType::Capture, msg);
622 return S_OK;
624 #endif
626 /** -------------------------- DeviceHelper ----------------------------- */
627 HRESULT init()
629 #if !defined(ALSOFT_UWP)
630 HRESULT hr{CoCreateInstance(CLSID_MMDeviceEnumerator, nullptr, CLSCTX_INPROC_SERVER,
631 __uuidof(IMMDeviceEnumerator), al::out_ptr(mEnumerator))};
632 if(SUCCEEDED(hr))
633 mEnumerator->RegisterEndpointNotificationCallback(this);
634 else
635 WARN("Failed to create IMMDeviceEnumerator instance: 0x%08lx\n", hr);
636 return hr;
637 #else
638 return S_OK;
639 #endif
642 HRESULT openDevice(std::wstring_view devid, EDataFlow flow, DeviceHandle& device)
644 #if !defined(ALSOFT_UWP)
645 HRESULT hr{E_FAIL};
646 if(mEnumerator)
648 if(devid.empty())
649 hr = mEnumerator->GetDefaultAudioEndpoint(flow, eMultimedia, al::out_ptr(device));
650 else
651 hr = mEnumerator->GetDevice(devid.data(), al::out_ptr(device));
653 return hr;
654 #else
655 const auto deviceRole = Windows::Media::Devices::AudioDeviceRole::Default;
656 auto devIfPath =
657 devid.empty() ? (flow == eRender ? MediaDevice::GetDefaultAudioRenderId(deviceRole) : MediaDevice::GetDefaultAudioCaptureId(deviceRole))
658 : winrt::hstring(devid.data());
659 if (devIfPath.empty())
660 return E_POINTER;
662 auto&& deviceInfo = DeviceInformation::CreateFromIdAsync(devIfPath, nullptr, DeviceInformationKind::DeviceInterface).get();
663 if (!deviceInfo)
664 return E_NOINTERFACE;
665 device = deviceInfo;
666 return S_OK;
667 #endif
670 #if !defined(ALSOFT_UWP)
671 static HRESULT activateAudioClient(_In_ DeviceHandle &device, REFIID iid, void **ppv)
672 { return device->Activate(iid, CLSCTX_INPROC_SERVER, nullptr, ppv); }
673 #else
674 HRESULT activateAudioClient(_In_ DeviceHandle &device, _In_ REFIID iid, void **ppv)
676 ComPtr<IActivateAudioInterfaceAsyncOperation> asyncOp;
677 HRESULT hr{ActivateAudioInterfaceAsync(device.Id().data(), iid, nullptr, this,
678 al::out_ptr(asyncOp))};
679 if(FAILED(hr))
680 return hr;
682 /* I don't like waiting for INFINITE time, but the activate operation
683 * can take an indefinite amount of time since it can require user
684 * input.
686 DWORD res{WaitForSingleObjectEx(mActiveClientEvent, INFINITE, FALSE)};
687 if(res != WAIT_OBJECT_0)
689 ERR("WaitForSingleObjectEx error: 0x%lx\n", res);
690 return E_FAIL;
693 HRESULT hrActivateRes{E_FAIL};
694 ComPtr<IUnknown> punkAudioIface;
695 hr = asyncOp->GetActivateResult(&hrActivateRes, al::out_ptr(punkAudioIface));
696 if(SUCCEEDED(hr)) hr = hrActivateRes;
697 if(FAILED(hr)) return hr;
699 return punkAudioIface->QueryInterface(iid, ppv);
701 #endif
703 std::wstring probeDevices(EDataFlow flowdir, std::vector<DevMap> &list)
705 std::wstring defaultId;
706 std::vector<DevMap>{}.swap(list);
708 #if !defined(ALSOFT_UWP)
709 ComPtr<IMMDeviceCollection> coll;
710 HRESULT hr{mEnumerator->EnumAudioEndpoints(flowdir, DEVICE_STATE_ACTIVE,
711 al::out_ptr(coll))};
712 if(FAILED(hr))
714 ERR("Failed to enumerate audio endpoints: 0x%08lx\n", hr);
715 return defaultId;
718 UINT count{0};
719 hr = coll->GetCount(&count);
720 if(SUCCEEDED(hr) && count > 0)
721 list.reserve(count);
723 ComPtr<IMMDevice> device;
724 hr = mEnumerator->GetDefaultAudioEndpoint(flowdir, eMultimedia, al::out_ptr(device));
725 if(SUCCEEDED(hr))
727 if(WCHAR *devid{GetDeviceId(device.get())})
729 defaultId = devid;
730 CoTaskMemFree(devid);
732 device = nullptr;
735 for(UINT i{0};i < count;++i)
737 hr = coll->Item(i, al::out_ptr(device));
738 if(FAILED(hr))
739 continue;
741 if(WCHAR *devid{GetDeviceId(device.get())})
743 std::ignore = AddDevice(device, devid, list);
744 CoTaskMemFree(devid);
746 device = nullptr;
748 #else
749 const auto deviceRole = Windows::Media::Devices::AudioDeviceRole::Default;
750 auto DefaultAudioId = flowdir == eRender ? MediaDevice::GetDefaultAudioRenderId(deviceRole)
751 : MediaDevice::GetDefaultAudioCaptureId(deviceRole);
752 if(!DefaultAudioId.empty())
754 auto deviceInfo = DeviceInformation::CreateFromIdAsync(DefaultAudioId, nullptr,
755 DeviceInformationKind::DeviceInterface).get();
756 if(deviceInfo)
757 defaultId = deviceInfo.Id().data();
760 // Get the string identifier of the audio renderer
761 auto AudioSelector = flowdir == eRender ? MediaDevice::GetAudioRenderSelector() : MediaDevice::GetAudioCaptureSelector();
763 // Setup the asynchronous callback
764 auto&& DeviceInfoCollection = DeviceInformation::FindAllAsync(AudioSelector, /*PropertyList*/nullptr, DeviceInformationKind::DeviceInterface).get();
765 if(DeviceInfoCollection)
767 try {
768 auto deviceCount = DeviceInfoCollection.Size();
769 for(unsigned int i{0};i < deviceCount;++i)
771 auto deviceInfo = DeviceInfoCollection.GetAt(i);
772 if(deviceInfo)
773 std::ignore = AddDevice(deviceInfo, deviceInfo.Id().data(), list);
776 catch (const winrt::hresult_error& /*ex*/) {
779 #endif
781 return defaultId;
784 private:
785 static bool AddDevice(const DeviceHandle &device, const WCHAR *devid, std::vector<DevMap> &list)
787 for(auto &entry : list)
789 if(entry.devid == devid)
790 return false;
793 auto name_guid = GetDeviceNameAndGuid(device);
794 int count{1};
795 std::string newname{name_guid.first};
796 while(checkName(list, newname))
798 newname = name_guid.first;
799 newname += " #";
800 newname += std::to_string(++count);
802 list.emplace_back(std::move(newname), std::move(name_guid.second), devid);
803 const DevMap &newentry = list.back();
805 TRACE("Got device \"%s\", \"%s\", \"%ls\"\n", newentry.name.c_str(),
806 newentry.endpoint_guid.c_str(), newentry.devid.c_str());
807 return true;
810 #if !defined(ALSOFT_UWP)
811 static WCHAR *GetDeviceId(IMMDevice *device)
813 WCHAR *devid;
815 const HRESULT hr{device->GetId(&devid)};
816 if(FAILED(hr))
818 ERR("Failed to get device id: %lx\n", hr);
819 return nullptr;
822 return devid;
824 ComPtr<IMMDeviceEnumerator> mEnumerator{nullptr};
826 #else
828 HANDLE mActiveClientEvent{nullptr};
830 EventRegistrationToken mRenderDeviceChangedToken;
831 EventRegistrationToken mCaptureDeviceChangedToken;
832 #endif
835 bool MakeExtensible(WAVEFORMATEXTENSIBLE *out, const WAVEFORMATEX *in)
837 *out = WAVEFORMATEXTENSIBLE{};
838 if(in->wFormatTag == WAVE_FORMAT_EXTENSIBLE)
840 *out = *CONTAINING_RECORD(in, const WAVEFORMATEXTENSIBLE, Format);
841 out->Format.cbSize = sizeof(*out) - sizeof(out->Format);
843 else if(in->wFormatTag == WAVE_FORMAT_PCM)
845 out->Format = *in;
846 out->Format.cbSize = 0;
847 /* NOLINTNEXTLINE(cppcoreguidelines-pro-type-union-access) */
848 out->Samples.wValidBitsPerSample = out->Format.wBitsPerSample;
849 if(out->Format.nChannels == 1)
850 out->dwChannelMask = MONO;
851 else if(out->Format.nChannels == 2)
852 out->dwChannelMask = STEREO;
853 else
854 ERR("Unhandled PCM channel count: %d\n", out->Format.nChannels);
855 out->SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
857 else if(in->wFormatTag == WAVE_FORMAT_IEEE_FLOAT)
859 out->Format = *in;
860 out->Format.cbSize = 0;
861 /* NOLINTNEXTLINE(cppcoreguidelines-pro-type-union-access) */
862 out->Samples.wValidBitsPerSample = out->Format.wBitsPerSample;
863 if(out->Format.nChannels == 1)
864 out->dwChannelMask = MONO;
865 else if(out->Format.nChannels == 2)
866 out->dwChannelMask = STEREO;
867 else
868 ERR("Unhandled IEEE float channel count: %d\n", out->Format.nChannels);
869 out->SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT;
871 else
873 ERR("Unhandled format tag: 0x%04x\n", in->wFormatTag);
874 return false;
876 return true;
879 void TraceFormat(const char *msg, const WAVEFORMATEX *format)
881 constexpr size_t fmtex_extra_size{sizeof(WAVEFORMATEXTENSIBLE)-sizeof(WAVEFORMATEX)};
882 if(format->wFormatTag == WAVE_FORMAT_EXTENSIBLE && format->cbSize >= fmtex_extra_size)
884 const WAVEFORMATEXTENSIBLE *fmtex{
885 CONTAINING_RECORD(format, const WAVEFORMATEXTENSIBLE, Format)};
886 /* NOLINTBEGIN(cppcoreguidelines-pro-type-union-access) */
887 TRACE("%s:\n"
888 " FormatTag = 0x%04x\n"
889 " Channels = %d\n"
890 " SamplesPerSec = %lu\n"
891 " AvgBytesPerSec = %lu\n"
892 " BlockAlign = %d\n"
893 " BitsPerSample = %d\n"
894 " Size = %d\n"
895 " Samples = %d\n"
896 " ChannelMask = 0x%lx\n"
897 " SubFormat = %s\n",
898 msg, fmtex->Format.wFormatTag, fmtex->Format.nChannels, fmtex->Format.nSamplesPerSec,
899 fmtex->Format.nAvgBytesPerSec, fmtex->Format.nBlockAlign, fmtex->Format.wBitsPerSample,
900 fmtex->Format.cbSize, fmtex->Samples.wReserved, fmtex->dwChannelMask,
901 GuidPrinter{fmtex->SubFormat}.c_str());
902 /* NOLINTEND(cppcoreguidelines-pro-type-union-access) */
904 else
905 TRACE("%s:\n"
906 " FormatTag = 0x%04x\n"
907 " Channels = %d\n"
908 " SamplesPerSec = %lu\n"
909 " AvgBytesPerSec = %lu\n"
910 " BlockAlign = %d\n"
911 " BitsPerSample = %d\n"
912 " Size = %d\n",
913 msg, format->wFormatTag, format->nChannels, format->nSamplesPerSec,
914 format->nAvgBytesPerSec, format->nBlockAlign, format->wBitsPerSample, format->cbSize);
918 enum class MsgType {
919 OpenDevice,
920 ResetDevice,
921 StartDevice,
922 StopDevice,
923 CloseDevice,
925 QuitThread
928 constexpr const char *GetMessageTypeName(MsgType type) noexcept
930 switch(type)
932 case MsgType::OpenDevice: return "Open Device";
933 case MsgType::ResetDevice: return "Reset Device";
934 case MsgType::StartDevice: return "Start Device";
935 case MsgType::StopDevice: return "Stop Device";
936 case MsgType::CloseDevice: return "Close Device";
937 case MsgType::QuitThread: break;
939 return "";
943 /* Proxy interface used by the message handler. */
944 struct WasapiProxy {
945 WasapiProxy() = default;
946 WasapiProxy(const WasapiProxy&) = delete;
947 WasapiProxy(WasapiProxy&&) = delete;
948 virtual ~WasapiProxy() = default;
950 void operator=(const WasapiProxy&) = delete;
951 void operator=(WasapiProxy&&) = delete;
953 virtual HRESULT openProxy(std::string_view name) = 0;
954 virtual void closeProxy() = 0;
956 virtual HRESULT resetProxy() = 0;
957 virtual HRESULT startProxy() = 0;
958 virtual void stopProxy() = 0;
960 struct Msg {
961 MsgType mType;
962 WasapiProxy *mProxy;
963 std::string_view mParam;
964 std::promise<HRESULT> mPromise;
966 explicit operator bool() const noexcept { return mType != MsgType::QuitThread; }
968 static inline std::deque<Msg> mMsgQueue;
969 static inline std::mutex mMsgQueueLock;
970 static inline std::condition_variable mMsgQueueCond;
972 static inline std::optional<DeviceHelper> sDeviceHelper;
974 std::future<HRESULT> pushMessage(MsgType type, std::string_view param={})
976 std::promise<HRESULT> promise;
977 std::future<HRESULT> future{promise.get_future()};
979 std::lock_guard<std::mutex> msglock{mMsgQueueLock};
980 mMsgQueue.emplace_back(Msg{type, this, param, std::move(promise)});
982 mMsgQueueCond.notify_one();
983 return future;
986 static std::future<HRESULT> pushMessageStatic(MsgType type)
988 std::promise<HRESULT> promise;
989 std::future<HRESULT> future{promise.get_future()};
991 std::lock_guard<std::mutex> msglock{mMsgQueueLock};
992 mMsgQueue.emplace_back(Msg{type, nullptr, {}, std::move(promise)});
994 mMsgQueueCond.notify_one();
995 return future;
998 static Msg popMessage()
1000 std::unique_lock<std::mutex> lock{mMsgQueueLock};
1001 mMsgQueueCond.wait(lock, []{return !mMsgQueue.empty();});
1002 Msg msg{std::move(mMsgQueue.front())};
1003 mMsgQueue.pop_front();
1004 return msg;
1007 static int messageHandler(std::promise<HRESULT> *promise);
1010 int WasapiProxy::messageHandler(std::promise<HRESULT> *promise)
1012 TRACE("Starting message thread\n");
1014 ComWrapper com{COINIT_MULTITHREADED};
1015 if(!com)
1017 WARN("Failed to initialize COM: 0x%08lx\n", com.status());
1018 promise->set_value(com.status());
1019 return 0;
1022 struct HelperResetter {
1023 ~HelperResetter() { sDeviceHelper.reset(); }
1025 HelperResetter scoped_watcher;
1027 HRESULT hr{sDeviceHelper.emplace().init()};
1028 promise->set_value(hr);
1029 promise = nullptr;
1030 if(FAILED(hr))
1031 return 0;
1034 auto devlock = DeviceListLock{gDeviceList};
1035 auto defaultId = sDeviceHelper->probeDevices(eRender, devlock.getPlaybackList());
1036 if(!defaultId.empty()) devlock.setPlaybackDefaultId(defaultId);
1037 defaultId = sDeviceHelper->probeDevices(eCapture, devlock.getCaptureList());
1038 if(!defaultId.empty()) devlock.setCaptureDefaultId(defaultId);
1041 TRACE("Starting message loop\n");
1042 while(Msg msg{popMessage()})
1044 TRACE("Got message \"%s\" (0x%04x, this=%p, param=\"%.*s\")\n",
1045 GetMessageTypeName(msg.mType), static_cast<uint>(msg.mType),
1046 static_cast<void*>(msg.mProxy), al::sizei(msg.mParam), msg.mParam.data());
1048 switch(msg.mType)
1050 case MsgType::OpenDevice:
1051 hr = msg.mProxy->openProxy(msg.mParam);
1052 msg.mPromise.set_value(hr);
1053 continue;
1055 case MsgType::ResetDevice:
1056 hr = msg.mProxy->resetProxy();
1057 msg.mPromise.set_value(hr);
1058 continue;
1060 case MsgType::StartDevice:
1061 hr = msg.mProxy->startProxy();
1062 msg.mPromise.set_value(hr);
1063 continue;
1065 case MsgType::StopDevice:
1066 msg.mProxy->stopProxy();
1067 msg.mPromise.set_value(S_OK);
1068 continue;
1070 case MsgType::CloseDevice:
1071 msg.mProxy->closeProxy();
1072 msg.mPromise.set_value(S_OK);
1073 continue;
1075 case MsgType::QuitThread:
1076 break;
1078 ERR("Unexpected message: %u\n", static_cast<uint>(msg.mType));
1079 msg.mPromise.set_value(E_FAIL);
1081 TRACE("Message loop finished\n");
1083 return 0;
1086 struct WasapiPlayback final : public BackendBase, WasapiProxy {
1087 WasapiPlayback(DeviceBase *device) noexcept : BackendBase{device} { }
1088 ~WasapiPlayback() override;
1090 int mixerProc();
1091 int mixerSpatialProc();
1093 void open(std::string_view name) override;
1094 HRESULT openProxy(std::string_view name) override;
1095 void closeProxy() override;
1097 bool reset() override;
1098 HRESULT resetProxy() override;
1099 void start() override;
1100 HRESULT startProxy() override;
1101 void stop() override;
1102 void stopProxy() override;
1104 ClockLatency getClockLatency() override;
1106 void prepareFormat(WAVEFORMATEXTENSIBLE &OutputType);
1107 void finalizeFormat(WAVEFORMATEXTENSIBLE &OutputType);
1109 auto initSpatial() -> bool;
1111 HRESULT mOpenStatus{E_FAIL};
1112 DeviceHandle mMMDev{nullptr};
1114 struct PlainDevice {
1115 ComPtr<IAudioClient> mClient{nullptr};
1116 ComPtr<IAudioRenderClient> mRender{nullptr};
1118 struct SpatialDevice {
1119 ComPtr<ISpatialAudioClient> mClient{nullptr};
1120 ComPtr<ISpatialAudioObjectRenderStream> mRender{nullptr};
1121 AudioObjectType mStaticMask{};
1123 std::variant<std::monostate,PlainDevice,SpatialDevice> mAudio;
1124 HANDLE mNotifyEvent{nullptr};
1126 UINT32 mOrigBufferSize{}, mOrigUpdateSize{};
1127 std::vector<char> mResampleBuffer{};
1128 uint mBufferFilled{0};
1129 SampleConverterPtr mResampler;
1131 WAVEFORMATEXTENSIBLE mFormat{};
1132 std::atomic<UINT32> mPadding{0u};
1134 std::mutex mMutex;
1136 std::atomic<bool> mKillNow{true};
1137 std::thread mThread;
1140 WasapiPlayback::~WasapiPlayback()
1142 if(SUCCEEDED(mOpenStatus))
1143 pushMessage(MsgType::CloseDevice).wait();
1144 mOpenStatus = E_FAIL;
1146 if(mNotifyEvent != nullptr)
1147 CloseHandle(mNotifyEvent);
1148 mNotifyEvent = nullptr;
1152 FORCE_ALIGN int WasapiPlayback::mixerProc()
1154 ComWrapper com{COINIT_MULTITHREADED};
1155 if(!com)
1157 ERR("CoInitializeEx(nullptr, COINIT_MULTITHREADED) failed: 0x%08lx\n", com.status());
1158 mDevice->handleDisconnect("COM init failed: 0x%08lx", com.status());
1159 return 1;
1162 auto &audio = std::get<PlainDevice>(mAudio);
1164 SetRTPriority();
1165 althrd_setname(GetMixerThreadName());
1167 const uint frame_size{mFormat.Format.nChannels * mFormat.Format.wBitsPerSample / 8u};
1168 const uint update_size{mOrigUpdateSize};
1169 const UINT32 buffer_len{mOrigBufferSize};
1170 const void *resbufferptr{};
1172 mBufferFilled = 0;
1173 while(!mKillNow.load(std::memory_order_relaxed))
1175 UINT32 written;
1176 HRESULT hr{audio.mClient->GetCurrentPadding(&written)};
1177 if(FAILED(hr))
1179 ERR("Failed to get padding: 0x%08lx\n", hr);
1180 mDevice->handleDisconnect("Failed to retrieve buffer padding: 0x%08lx", hr);
1181 break;
1183 mPadding.store(written, std::memory_order_relaxed);
1185 uint len{buffer_len - written};
1186 if(len < update_size)
1188 DWORD res{WaitForSingleObjectEx(mNotifyEvent, 2000, FALSE)};
1189 if(res != WAIT_OBJECT_0)
1190 ERR("WaitForSingleObjectEx error: 0x%lx\n", res);
1191 continue;
1194 BYTE *buffer;
1195 hr = audio.mRender->GetBuffer(len, &buffer);
1196 if(SUCCEEDED(hr))
1198 if(mResampler)
1200 std::lock_guard<std::mutex> dlock{mMutex};
1201 auto dst = al::span{buffer, size_t{len}*frame_size};
1202 for(UINT32 done{0};done < len;)
1204 if(mBufferFilled == 0)
1206 mDevice->renderSamples(mResampleBuffer.data(), mDevice->UpdateSize,
1207 mFormat.Format.nChannels);
1208 resbufferptr = mResampleBuffer.data();
1209 mBufferFilled = mDevice->UpdateSize;
1212 uint got{mResampler->convert(&resbufferptr, &mBufferFilled, dst.data(),
1213 len-done)};
1214 dst = dst.subspan(size_t{got}*frame_size);
1215 done += got;
1217 mPadding.store(written + done, std::memory_order_relaxed);
1220 else
1222 std::lock_guard<std::mutex> dlock{mMutex};
1223 mDevice->renderSamples(buffer, len, mFormat.Format.nChannels);
1224 mPadding.store(written + len, std::memory_order_relaxed);
1226 hr = audio.mRender->ReleaseBuffer(len, 0);
1228 if(FAILED(hr))
1230 ERR("Failed to buffer data: 0x%08lx\n", hr);
1231 mDevice->handleDisconnect("Failed to send playback samples: 0x%08lx", hr);
1232 break;
1235 mPadding.store(0u, std::memory_order_release);
1237 return 0;
1240 FORCE_ALIGN int WasapiPlayback::mixerSpatialProc()
1242 ComWrapper com{COINIT_MULTITHREADED};
1243 if(!com)
1245 ERR("CoInitializeEx(nullptr, COINIT_MULTITHREADED) failed: 0x%08lx\n", com.status());
1246 mDevice->handleDisconnect("COM init failed: 0x%08lx", com.status());
1247 return 1;
1250 auto &audio = std::get<SpatialDevice>(mAudio);
1252 SetRTPriority();
1253 althrd_setname(GetMixerThreadName());
1255 std::vector<ComPtr<ISpatialAudioObject>> channels;
1256 std::vector<void*> buffers;
1257 std::vector<void*> resbuffers;
1258 std::vector<const void*> tmpbuffers;
1260 /* TODO: Set mPadding appropriately. There doesn't seem to be a way to
1261 * update it dynamically based on the stream, so a fixed size may be the
1262 * best we can do.
1264 mPadding.store(mOrigBufferSize-mOrigUpdateSize, std::memory_order_release);
1266 mBufferFilled = 0;
1267 while(!mKillNow.load(std::memory_order_relaxed))
1269 if(DWORD res{WaitForSingleObjectEx(mNotifyEvent, 1000, FALSE)}; res != WAIT_OBJECT_0)
1271 ERR("WaitForSingleObjectEx error: 0x%lx\n", res);
1273 HRESULT hr{audio.mRender->Reset()};
1274 if(FAILED(hr))
1276 ERR("ISpatialAudioObjectRenderStream::Reset failed: 0x%08lx\n", hr);
1277 mDevice->handleDisconnect("Device lost: 0x%08lx", hr);
1278 break;
1282 UINT32 dynamicCount{}, framesToDo{};
1283 HRESULT hr{audio.mRender->BeginUpdatingAudioObjects(&dynamicCount, &framesToDo)};
1284 if(SUCCEEDED(hr))
1286 if(channels.empty()) UNLIKELY
1288 auto flags = as_unsigned(audio.mStaticMask);
1289 channels.reserve(as_unsigned(al::popcount(flags)));
1290 while(flags)
1292 auto id = decltype(flags){1} << al::countr_zero(flags);
1293 flags &= ~id;
1295 channels.emplace_back();
1296 audio.mRender->ActivateSpatialAudioObject(static_cast<AudioObjectType>(id),
1297 al::out_ptr(channels.back()));
1299 buffers.resize(channels.size());
1300 if(mResampler)
1302 tmpbuffers.resize(buffers.size());
1303 resbuffers.resize(buffers.size());
1304 auto bufptr = mResampleBuffer.begin();
1305 for(size_t i{0};i < tmpbuffers.size();++i)
1307 resbuffers[i] = al::to_address(bufptr);
1308 bufptr += ptrdiff_t(mDevice->UpdateSize*sizeof(float));
1313 /* We have to call to get each channel's buffer individually every
1314 * update, unfortunately.
1316 std::transform(channels.cbegin(), channels.cend(), buffers.begin(),
1317 [](const ComPtr<ISpatialAudioObject> &obj) -> void*
1319 auto buffer = LPBYTE{};
1320 auto size = UINT32{};
1321 obj->GetBuffer(&buffer, &size);
1322 return buffer;
1325 if(!mResampler)
1326 mDevice->renderSamples(buffers, framesToDo);
1327 else
1329 std::lock_guard<std::mutex> dlock{mMutex};
1330 for(UINT32 pos{0};pos < framesToDo;)
1332 if(mBufferFilled == 0)
1334 mDevice->renderSamples(resbuffers, mDevice->UpdateSize);
1335 std::copy(resbuffers.cbegin(), resbuffers.cend(), tmpbuffers.begin());
1336 mBufferFilled = mDevice->UpdateSize;
1339 const uint got{mResampler->convertPlanar(tmpbuffers.data(), &mBufferFilled,
1340 buffers.data(), framesToDo-pos)};
1341 for(auto &buf : buffers)
1342 buf = static_cast<float*>(buf) + got; /* NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic) */
1343 pos += got;
1347 hr = audio.mRender->EndUpdatingAudioObjects();
1350 if(FAILED(hr))
1351 ERR("Failed to update playback objects: 0x%08lx\n", hr);
1353 mPadding.store(0u, std::memory_order_release);
1355 return 0;
1359 void WasapiPlayback::open(std::string_view name)
1361 if(SUCCEEDED(mOpenStatus))
1362 throw al::backend_exception{al::backend_error::DeviceError,
1363 "Unexpected duplicate open call"};
1365 mNotifyEvent = CreateEventW(nullptr, FALSE, FALSE, nullptr);
1366 if(mNotifyEvent == nullptr)
1368 ERR("Failed to create notify events: %lu\n", GetLastError());
1369 throw al::backend_exception{al::backend_error::DeviceError,
1370 "Failed to create notify events"};
1373 if(const auto prefix = GetDevicePrefix(); al::starts_with(name, prefix))
1374 name = name.substr(prefix.size());
1376 mOpenStatus = pushMessage(MsgType::OpenDevice, name).get();
1377 if(FAILED(mOpenStatus))
1378 throw al::backend_exception{al::backend_error::DeviceError, "Device init failed: 0x%08lx",
1379 mOpenStatus};
1382 HRESULT WasapiPlayback::openProxy(std::string_view name)
1384 std::string devname;
1385 std::wstring devid;
1386 if(!name.empty())
1388 auto devlock = DeviceListLock{gDeviceList};
1389 auto list = al::span{devlock.getPlaybackList()};
1390 auto iter = std::find_if(list.cbegin(), list.cend(),
1391 [name](const DevMap &entry) -> bool
1392 { return entry.name == name || entry.endpoint_guid == name; });
1393 if(iter == list.cend())
1395 const std::wstring wname{utf8_to_wstr(name)};
1396 iter = std::find_if(list.cbegin(), list.cend(),
1397 [&wname](const DevMap &entry) -> bool
1398 { return entry.devid == wname; });
1400 if(iter == list.cend())
1402 WARN("Failed to find device name matching \"%.*s\"\n", al::sizei(name), name.data());
1403 return E_FAIL;
1405 devname = iter->name;
1406 devid = iter->devid;
1409 HRESULT hr{sDeviceHelper->openDevice(devid, eRender, mMMDev)};
1410 if(FAILED(hr))
1412 WARN("Failed to open device \"%s\"\n", devname.empty() ? "(default)" : devname.c_str());
1413 return hr;
1415 if(!devname.empty())
1416 mDevice->DeviceName = std::string{GetDevicePrefix()}+std::move(devname);
1417 else
1418 mDevice->DeviceName = std::string{GetDevicePrefix()}+GetDeviceNameAndGuid(mMMDev).first;
1420 return S_OK;
1423 void WasapiPlayback::closeProxy()
1425 mAudio.emplace<std::monostate>();
1426 mMMDev = nullptr;
1430 void WasapiPlayback::prepareFormat(WAVEFORMATEXTENSIBLE &OutputType)
1432 bool isRear51{false};
1434 if(!mDevice->Flags.test(FrequencyRequest))
1435 mDevice->Frequency = OutputType.Format.nSamplesPerSec;
1436 if(!mDevice->Flags.test(ChannelsRequest))
1438 /* If not requesting a channel configuration, auto-select given what
1439 * fits the mask's lsb (to ensure no gaps in the output channels). If
1440 * there's no mask, we can only assume mono or stereo.
1442 const uint32_t chancount{OutputType.Format.nChannels};
1443 const DWORD chanmask{OutputType.dwChannelMask};
1444 if(chancount >= 12 && (chanmask&X714Mask) == X7DOT1DOT4)
1445 mDevice->FmtChans = DevFmtX714;
1446 else if(chancount >= 8 && (chanmask&X71Mask) == X7DOT1)
1447 mDevice->FmtChans = DevFmtX71;
1448 else if(chancount >= 7 && (chanmask&X61Mask) == X6DOT1)
1449 mDevice->FmtChans = DevFmtX61;
1450 else if(chancount >= 6 && (chanmask&X51Mask) == X5DOT1)
1451 mDevice->FmtChans = DevFmtX51;
1452 else if(chancount >= 6 && (chanmask&X51RearMask) == X5DOT1REAR)
1454 mDevice->FmtChans = DevFmtX51;
1455 isRear51 = true;
1457 else if(chancount >= 4 && (chanmask&QuadMask) == QUAD)
1458 mDevice->FmtChans = DevFmtQuad;
1459 else if(chancount >= 2 && ((chanmask&StereoMask) == STEREO || !chanmask))
1460 mDevice->FmtChans = DevFmtStereo;
1461 else if(chancount >= 1 && ((chanmask&MonoMask) == MONO || !chanmask))
1462 mDevice->FmtChans = DevFmtMono;
1463 else
1464 ERR("Unhandled channel config: %d -- 0x%08lx\n", chancount, chanmask);
1466 else
1468 const uint32_t chancount{OutputType.Format.nChannels};
1469 const DWORD chanmask{OutputType.dwChannelMask};
1470 isRear51 = (chancount == 6 && (chanmask&X51RearMask) == X5DOT1REAR);
1473 OutputType.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
1474 switch(mDevice->FmtChans)
1476 case DevFmtMono:
1477 OutputType.Format.nChannels = 1;
1478 OutputType.dwChannelMask = MONO;
1479 break;
1480 case DevFmtAmbi3D:
1481 mDevice->FmtChans = DevFmtStereo;
1482 /*fall-through*/
1483 case DevFmtStereo:
1484 OutputType.Format.nChannels = 2;
1485 OutputType.dwChannelMask = STEREO;
1486 break;
1487 case DevFmtQuad:
1488 OutputType.Format.nChannels = 4;
1489 OutputType.dwChannelMask = QUAD;
1490 break;
1491 case DevFmtX51:
1492 OutputType.Format.nChannels = 6;
1493 OutputType.dwChannelMask = isRear51 ? X5DOT1REAR : X5DOT1;
1494 break;
1495 case DevFmtX61:
1496 OutputType.Format.nChannels = 7;
1497 OutputType.dwChannelMask = X6DOT1;
1498 break;
1499 case DevFmtX71:
1500 case DevFmtX3D71:
1501 OutputType.Format.nChannels = 8;
1502 OutputType.dwChannelMask = X7DOT1;
1503 break;
1504 case DevFmtX7144:
1505 mDevice->FmtChans = DevFmtX714;
1506 /*fall-through*/
1507 case DevFmtX714:
1508 OutputType.Format.nChannels = 12;
1509 OutputType.dwChannelMask = X7DOT1DOT4;
1510 break;
1512 switch(mDevice->FmtType)
1514 case DevFmtByte:
1515 mDevice->FmtType = DevFmtUByte;
1516 /* fall-through */
1517 case DevFmtUByte:
1518 OutputType.Format.wBitsPerSample = 8;
1519 OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
1520 break;
1521 case DevFmtUShort:
1522 mDevice->FmtType = DevFmtShort;
1523 /* fall-through */
1524 case DevFmtShort:
1525 OutputType.Format.wBitsPerSample = 16;
1526 OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
1527 break;
1528 case DevFmtUInt:
1529 mDevice->FmtType = DevFmtInt;
1530 /* fall-through */
1531 case DevFmtInt:
1532 OutputType.Format.wBitsPerSample = 32;
1533 OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
1534 break;
1535 case DevFmtFloat:
1536 OutputType.Format.wBitsPerSample = 32;
1537 OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT;
1538 break;
1540 /* NOLINTNEXTLINE(cppcoreguidelines-pro-type-union-access) */
1541 OutputType.Samples.wValidBitsPerSample = OutputType.Format.wBitsPerSample;
1542 OutputType.Format.nSamplesPerSec = mDevice->Frequency;
1544 OutputType.Format.nBlockAlign = static_cast<WORD>(OutputType.Format.nChannels *
1545 OutputType.Format.wBitsPerSample / 8);
1546 OutputType.Format.nAvgBytesPerSec = OutputType.Format.nSamplesPerSec *
1547 OutputType.Format.nBlockAlign;
1550 void WasapiPlayback::finalizeFormat(WAVEFORMATEXTENSIBLE &OutputType)
1552 if(!GetConfigValueBool(mDevice->DeviceName, "wasapi", "allow-resampler", true))
1553 mDevice->Frequency = uint(OutputType.Format.nSamplesPerSec);
1554 else
1555 mDevice->Frequency = std::min(mDevice->Frequency, uint(OutputType.Format.nSamplesPerSec));
1557 const uint32_t chancount{OutputType.Format.nChannels};
1558 const DWORD chanmask{OutputType.dwChannelMask};
1559 /* Don't update the channel format if the requested format fits what's
1560 * supported.
1562 bool chansok{false};
1563 if(mDevice->Flags.test(ChannelsRequest))
1565 /* When requesting a channel configuration, make sure it fits the
1566 * mask's lsb (to ensure no gaps in the output channels). If there's no
1567 * mask, assume the request fits with enough channels.
1569 switch(mDevice->FmtChans)
1571 case DevFmtMono:
1572 chansok = (chancount >= 1 && ((chanmask&MonoMask) == MONO || !chanmask));
1573 break;
1574 case DevFmtStereo:
1575 chansok = (chancount >= 2 && ((chanmask&StereoMask) == STEREO || !chanmask));
1576 break;
1577 case DevFmtQuad:
1578 chansok = (chancount >= 4 && ((chanmask&QuadMask) == QUAD || !chanmask));
1579 break;
1580 case DevFmtX51:
1581 chansok = (chancount >= 6 && ((chanmask&X51Mask) == X5DOT1
1582 || (chanmask&X51RearMask) == X5DOT1REAR || !chanmask));
1583 break;
1584 case DevFmtX61:
1585 chansok = (chancount >= 7 && ((chanmask&X61Mask) == X6DOT1 || !chanmask));
1586 break;
1587 case DevFmtX71:
1588 case DevFmtX3D71:
1589 chansok = (chancount >= 8 && ((chanmask&X71Mask) == X7DOT1 || !chanmask));
1590 break;
1591 case DevFmtX714:
1592 chansok = (chancount >= 12 && ((chanmask&X714Mask) == X7DOT1DOT4 || !chanmask));
1593 case DevFmtX7144:
1594 case DevFmtAmbi3D:
1595 break;
1598 if(!chansok)
1600 if(chancount >= 12 && (chanmask&X714Mask) == X7DOT1DOT4)
1601 mDevice->FmtChans = DevFmtX714;
1602 else if(chancount >= 8 && (chanmask&X71Mask) == X7DOT1)
1603 mDevice->FmtChans = DevFmtX71;
1604 else if(chancount >= 7 && (chanmask&X61Mask) == X6DOT1)
1605 mDevice->FmtChans = DevFmtX61;
1606 else if(chancount >= 6 && ((chanmask&X51Mask) == X5DOT1
1607 || (chanmask&X51RearMask) == X5DOT1REAR))
1608 mDevice->FmtChans = DevFmtX51;
1609 else if(chancount >= 4 && (chanmask&QuadMask) == QUAD)
1610 mDevice->FmtChans = DevFmtQuad;
1611 else if(chancount >= 2 && ((chanmask&StereoMask) == STEREO || !chanmask))
1612 mDevice->FmtChans = DevFmtStereo;
1613 else if(chancount >= 1 && ((chanmask&MonoMask) == MONO || !chanmask))
1614 mDevice->FmtChans = DevFmtMono;
1615 else
1617 ERR("Unhandled extensible channels: %d -- 0x%08lx\n", OutputType.Format.nChannels,
1618 OutputType.dwChannelMask);
1619 mDevice->FmtChans = DevFmtStereo;
1620 OutputType.Format.nChannels = 2;
1621 OutputType.dwChannelMask = STEREO;
1625 if(IsEqualGUID(OutputType.SubFormat, KSDATAFORMAT_SUBTYPE_PCM))
1627 if(OutputType.Format.wBitsPerSample == 8)
1628 mDevice->FmtType = DevFmtUByte;
1629 else if(OutputType.Format.wBitsPerSample == 16)
1630 mDevice->FmtType = DevFmtShort;
1631 else if(OutputType.Format.wBitsPerSample == 32)
1632 mDevice->FmtType = DevFmtInt;
1633 else
1635 mDevice->FmtType = DevFmtShort;
1636 OutputType.Format.wBitsPerSample = 16;
1639 else if(IsEqualGUID(OutputType.SubFormat, KSDATAFORMAT_SUBTYPE_IEEE_FLOAT))
1641 mDevice->FmtType = DevFmtFloat;
1642 OutputType.Format.wBitsPerSample = 32;
1644 else
1646 ERR("Unhandled format sub-type: %s\n", GuidPrinter{OutputType.SubFormat}.c_str());
1647 mDevice->FmtType = DevFmtShort;
1648 if(OutputType.Format.wFormatTag != WAVE_FORMAT_EXTENSIBLE)
1649 OutputType.Format.wFormatTag = WAVE_FORMAT_PCM;
1650 OutputType.Format.wBitsPerSample = 16;
1651 OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
1653 /* NOLINTNEXTLINE(cppcoreguidelines-pro-type-union-access) */
1654 OutputType.Samples.wValidBitsPerSample = OutputType.Format.wBitsPerSample;
1658 auto WasapiPlayback::initSpatial() -> bool
1660 auto &audio = mAudio.emplace<SpatialDevice>();
1661 HRESULT hr{sDeviceHelper->activateAudioClient(mMMDev, __uuidof(ISpatialAudioClient),
1662 al::out_ptr(audio.mClient))};
1663 if(FAILED(hr))
1665 ERR("Failed to activate spatial audio client: 0x%08lx\n", hr);
1666 return false;
1669 ComPtr<IAudioFormatEnumerator> fmtenum;
1670 hr = audio.mClient->GetSupportedAudioObjectFormatEnumerator(al::out_ptr(fmtenum));
1671 if(FAILED(hr))
1673 ERR("Failed to get format enumerator: 0x%08lx\n", hr);
1674 return false;
1677 UINT32 fmtcount{};
1678 hr = fmtenum->GetCount(&fmtcount);
1679 if(FAILED(hr) || fmtcount == 0)
1681 ERR("Failed to get format count: 0x%08lx\n", hr);
1682 return false;
1685 WAVEFORMATEX *preferredFormat{};
1686 hr = fmtenum->GetFormat(0, &preferredFormat);
1687 if(FAILED(hr))
1689 ERR("Failed to get preferred format: 0x%08lx\n", hr);
1690 return false;
1692 TraceFormat("Preferred mix format", preferredFormat);
1694 UINT32 maxFrames{};
1695 hr = audio.mClient->GetMaxFrameCount(preferredFormat, &maxFrames);
1696 if(FAILED(hr))
1697 ERR("Failed to get max frames: 0x%08lx\n", hr);
1698 else
1699 TRACE("Max sample frames: %u\n", maxFrames);
1700 for(UINT32 i{1};i < fmtcount;++i)
1702 WAVEFORMATEX *otherFormat{};
1703 hr = fmtenum->GetFormat(i, &otherFormat);
1704 if(FAILED(hr))
1705 ERR("Failed to format %u: 0x%08lx\n", i+1, hr);
1706 else
1708 TraceFormat("Other mix format", otherFormat);
1709 UINT32 otherMaxFrames{};
1710 hr = audio.mClient->GetMaxFrameCount(otherFormat, &otherMaxFrames);
1711 if(FAILED(hr))
1712 ERR("Failed to get max frames: 0x%08lx\n", hr);
1713 else
1714 TRACE("Max sample frames: %u\n", otherMaxFrames);
1718 WAVEFORMATEXTENSIBLE OutputType;
1719 if(!MakeExtensible(&OutputType, preferredFormat))
1720 return false;
1722 /* Force 32-bit float. This is currently required for planar output. */
1723 if(OutputType.Format.wFormatTag != WAVE_FORMAT_EXTENSIBLE
1724 && OutputType.Format.wFormatTag != WAVE_FORMAT_IEEE_FLOAT)
1726 OutputType.Format.wFormatTag = WAVE_FORMAT_IEEE_FLOAT;
1727 OutputType.Format.cbSize = 0;
1729 if(OutputType.Format.wBitsPerSample != 32)
1731 OutputType.Format.nAvgBytesPerSec = OutputType.Format.nAvgBytesPerSec * 32u
1732 / OutputType.Format.wBitsPerSample;
1733 OutputType.Format.nBlockAlign = static_cast<WORD>(OutputType.Format.nBlockAlign * 32
1734 / OutputType.Format.wBitsPerSample);
1735 OutputType.Format.wBitsPerSample = 32;
1737 /* NOLINTNEXTLINE(cppcoreguidelines-pro-type-union-access) */
1738 OutputType.Samples.wValidBitsPerSample = OutputType.Format.wBitsPerSample;
1739 OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT;
1741 /* Match the output rate if not requesting anything specific. */
1742 if(!mDevice->Flags.test(FrequencyRequest))
1743 mDevice->Frequency = OutputType.Format.nSamplesPerSec;
1745 bool isRear51{false};
1746 if(!mDevice->Flags.test(ChannelsRequest))
1748 const uint32_t chancount{OutputType.Format.nChannels};
1749 const DWORD chanmask{OutputType.dwChannelMask};
1750 if(chancount >= 12 && (chanmask&X714Mask) == X7DOT1DOT4)
1751 mDevice->FmtChans = DevFmtX714;
1752 else if(chancount >= 8 && (chanmask&X71Mask) == X7DOT1)
1753 mDevice->FmtChans = DevFmtX71;
1754 else if(chancount >= 7 && (chanmask&X61Mask) == X6DOT1)
1755 mDevice->FmtChans = DevFmtX61;
1756 else if(chancount >= 6 && (chanmask&X51Mask) == X5DOT1)
1757 mDevice->FmtChans = DevFmtX51;
1758 else if(chancount >= 6 && (chanmask&X51RearMask) == X5DOT1REAR)
1760 mDevice->FmtChans = DevFmtX51;
1761 isRear51 = true;
1763 else if(chancount >= 4 && (chanmask&QuadMask) == QUAD)
1764 mDevice->FmtChans = DevFmtQuad;
1765 else if(chancount >= 2 && ((chanmask&StereoMask) == STEREO || !chanmask))
1766 mDevice->FmtChans = DevFmtStereo;
1767 /* HACK: Don't autoselect mono. Wine returns this and makes the audio
1768 * terrible.
1770 else if(!(chancount >= 1 && ((chanmask&MonoMask) == MONO || !chanmask)))
1771 ERR("Unhandled channel config: %d -- 0x%08lx\n", chancount, chanmask);
1773 else
1775 const uint32_t chancount{OutputType.Format.nChannels};
1776 const DWORD chanmask{OutputType.dwChannelMask};
1777 isRear51 = (chancount == 6 && (chanmask&X51RearMask) == X5DOT1REAR);
1780 auto getTypeMask = [isRear51](DevFmtChannels chans) noexcept
1782 switch(chans)
1784 case DevFmtMono: return ChannelMask_Mono;
1785 case DevFmtStereo: return ChannelMask_Stereo;
1786 case DevFmtQuad: return ChannelMask_Quad;
1787 case DevFmtX51: return isRear51 ? ChannelMask_X51Rear : ChannelMask_X51;
1788 case DevFmtX61: return ChannelMask_X61;
1789 case DevFmtX3D71:
1790 case DevFmtX71: return ChannelMask_X71;
1791 case DevFmtX714: return ChannelMask_X714;
1792 case DevFmtX7144: return ChannelMask_X7144;
1793 case DevFmtAmbi3D:
1794 break;
1796 return ChannelMask_Stereo;
1799 SpatialAudioObjectRenderStreamActivationParams streamParams{};
1800 streamParams.ObjectFormat = &OutputType.Format;
1801 streamParams.StaticObjectTypeMask = getTypeMask(mDevice->FmtChans);
1802 streamParams.Category = AudioCategory_Media;
1803 streamParams.EventHandle = mNotifyEvent;
1805 PropVariant paramProp{};
1806 paramProp.setBlob({reinterpret_cast<BYTE*>(&streamParams), sizeof(streamParams)});
1808 hr = audio.mClient->ActivateSpatialAudioStream(paramProp.get(),
1809 __uuidof(ISpatialAudioObjectRenderStream), al::out_ptr(audio.mRender));
1810 if(FAILED(hr))
1812 ERR("Failed to activate spatial audio stream: 0x%08lx\n", hr);
1813 return false;
1816 audio.mStaticMask = streamParams.StaticObjectTypeMask;
1817 mFormat = OutputType;
1819 mDevice->FmtType = DevFmtFloat;
1820 mDevice->Flags.reset(DirectEar).set(Virtualization);
1821 if(streamParams.StaticObjectTypeMask == ChannelMask_Stereo)
1822 mDevice->FmtChans = DevFmtStereo;
1823 if(!GetConfigValueBool(mDevice->DeviceName, "wasapi", "allow-resampler", true))
1824 mDevice->Frequency = uint(OutputType.Format.nSamplesPerSec);
1825 else
1826 mDevice->Frequency = std::min(mDevice->Frequency,
1827 uint(OutputType.Format.nSamplesPerSec));
1829 setDefaultWFXChannelOrder();
1831 /* FIXME: Get the real update and buffer size. Presumably the actual device
1832 * is configured once ActivateSpatialAudioStream succeeds, and an
1833 * IAudioClient from the same IMMDevice accesses the same device
1834 * configuration. This isn't obviously correct, but for now assume
1835 * IAudioClient::GetDevicePeriod returns the current device period time
1836 * that ISpatialAudioObjectRenderStream will try to wake up at.
1838 * Unfortunately this won't get the buffer size of the
1839 * ISpatialAudioObjectRenderStream, so we only assume there's two periods.
1841 mOrigUpdateSize = mDevice->UpdateSize;
1842 mOrigBufferSize = mOrigUpdateSize*2;
1843 ReferenceTime per_time{ReferenceTime{seconds{mDevice->UpdateSize}} / mDevice->Frequency};
1845 ComPtr<IAudioClient> tmpClient;
1846 hr = sDeviceHelper->activateAudioClient(mMMDev, __uuidof(IAudioClient),
1847 al::out_ptr(tmpClient));
1848 if(FAILED(hr))
1849 ERR("Failed to activate audio client: 0x%08lx\n", hr);
1850 else
1852 hr = tmpClient->GetDevicePeriod(&reinterpret_cast<REFERENCE_TIME&>(per_time), nullptr);
1853 if(FAILED(hr))
1854 ERR("Failed to get device period: 0x%08lx\n", hr);
1855 else
1857 mOrigUpdateSize = RefTime2Samples(per_time, mFormat.Format.nSamplesPerSec);
1858 mOrigBufferSize = mOrigUpdateSize*2;
1861 tmpClient = nullptr;
1863 mDevice->UpdateSize = RefTime2Samples(per_time, mDevice->Frequency);
1864 mDevice->BufferSize = mDevice->UpdateSize*2;
1866 mResampler = nullptr;
1867 mResampleBuffer.clear();
1868 mResampleBuffer.shrink_to_fit();
1869 mBufferFilled = 0;
1870 if(mDevice->Frequency != mFormat.Format.nSamplesPerSec)
1872 const auto flags = as_unsigned(streamParams.StaticObjectTypeMask);
1873 const auto channelCount = as_unsigned(al::popcount(flags));
1874 mResampler = SampleConverter::Create(mDevice->FmtType, mDevice->FmtType, channelCount,
1875 mDevice->Frequency, mFormat.Format.nSamplesPerSec, Resampler::FastBSinc24);
1876 mResampleBuffer.resize(size_t{mDevice->UpdateSize} * channelCount *
1877 mFormat.Format.wBitsPerSample / 8);
1879 TRACE("Created converter for %s/%s format, dst: %luhz (%u), src: %uhz (%u)\n",
1880 DevFmtChannelsString(mDevice->FmtChans), DevFmtTypeString(mDevice->FmtType),
1881 mFormat.Format.nSamplesPerSec, mOrigUpdateSize, mDevice->Frequency,
1882 mDevice->UpdateSize);
1885 return true;
1888 bool WasapiPlayback::reset()
1890 HRESULT hr{pushMessage(MsgType::ResetDevice).get()};
1891 if(FAILED(hr))
1892 throw al::backend_exception{al::backend_error::DeviceError, "0x%08lx", hr};
1893 return true;
1896 HRESULT WasapiPlayback::resetProxy()
1898 if(GetConfigValueBool(mDevice->DeviceName, "wasapi", "spatial-api", false))
1900 if(initSpatial())
1901 return S_OK;
1904 mDevice->Flags.reset(Virtualization);
1906 auto &audio = mAudio.emplace<PlainDevice>();
1907 HRESULT hr{sDeviceHelper->activateAudioClient(mMMDev, __uuidof(IAudioClient),
1908 al::out_ptr(audio.mClient))};
1909 if(FAILED(hr))
1911 ERR("Failed to reactivate audio client: 0x%08lx\n", hr);
1912 return hr;
1915 WAVEFORMATEX *wfx;
1916 hr = audio.mClient->GetMixFormat(&wfx);
1917 if(FAILED(hr))
1919 ERR("Failed to get mix format: 0x%08lx\n", hr);
1920 return hr;
1922 TraceFormat("Device mix format", wfx);
1924 WAVEFORMATEXTENSIBLE OutputType;
1925 if(!MakeExtensible(&OutputType, wfx))
1927 CoTaskMemFree(wfx);
1928 return E_FAIL;
1930 CoTaskMemFree(wfx);
1931 wfx = nullptr;
1933 const ReferenceTime per_time{ReferenceTime{seconds{mDevice->UpdateSize}} / mDevice->Frequency};
1934 const ReferenceTime buf_time{ReferenceTime{seconds{mDevice->BufferSize}} / mDevice->Frequency};
1936 prepareFormat(OutputType);
1938 TraceFormat("Requesting playback format", &OutputType.Format);
1939 hr = audio.mClient->IsFormatSupported(AUDCLNT_SHAREMODE_SHARED, &OutputType.Format, &wfx);
1940 if(FAILED(hr))
1942 WARN("Failed to check format support: 0x%08lx\n", hr);
1943 hr = audio.mClient->GetMixFormat(&wfx);
1945 if(FAILED(hr))
1947 ERR("Failed to find a supported format: 0x%08lx\n", hr);
1948 return hr;
1951 if(wfx != nullptr)
1953 TraceFormat("Got playback format", wfx);
1954 if(!MakeExtensible(&OutputType, wfx))
1956 CoTaskMemFree(wfx);
1957 return E_FAIL;
1959 CoTaskMemFree(wfx);
1960 wfx = nullptr;
1962 finalizeFormat(OutputType);
1964 mFormat = OutputType;
1966 #if !defined(ALSOFT_UWP)
1967 const EndpointFormFactor formfactor{GetDeviceFormfactor(mMMDev.get())};
1968 mDevice->Flags.set(DirectEar, (formfactor == Headphones || formfactor == Headset));
1969 #else
1970 mDevice->Flags.set(DirectEar, false);
1971 #endif
1972 setDefaultWFXChannelOrder();
1974 hr = audio.mClient->Initialize(AUDCLNT_SHAREMODE_SHARED, AUDCLNT_STREAMFLAGS_EVENTCALLBACK,
1975 buf_time.count(), 0, &OutputType.Format, nullptr);
1976 if(FAILED(hr))
1978 ERR("Failed to initialize audio client: 0x%08lx\n", hr);
1979 return hr;
1982 UINT32 buffer_len{};
1983 ReferenceTime min_per{};
1984 hr = audio.mClient->GetDevicePeriod(&reinterpret_cast<REFERENCE_TIME&>(min_per), nullptr);
1985 if(SUCCEEDED(hr))
1986 hr = audio.mClient->GetBufferSize(&buffer_len);
1987 if(FAILED(hr))
1989 ERR("Failed to get audio buffer info: 0x%08lx\n", hr);
1990 return hr;
1993 hr = audio.mClient->SetEventHandle(mNotifyEvent);
1994 if(FAILED(hr))
1996 ERR("Failed to set event handle: 0x%08lx\n", hr);
1997 return hr;
2000 /* Find the nearest multiple of the period size to the update size */
2001 if(min_per < per_time)
2002 min_per *= std::max<int64_t>((per_time + min_per/2) / min_per, 1_i64);
2004 mOrigBufferSize = buffer_len;
2005 mOrigUpdateSize = std::min(RefTime2Samples(min_per, mFormat.Format.nSamplesPerSec),
2006 buffer_len/2u);
2008 mDevice->BufferSize = static_cast<uint>(uint64_t{buffer_len} * mDevice->Frequency /
2009 mFormat.Format.nSamplesPerSec);
2010 mDevice->UpdateSize = std::min(RefTime2Samples(min_per, mDevice->Frequency),
2011 mDevice->BufferSize/2u);
2013 mResampler = nullptr;
2014 mResampleBuffer.clear();
2015 mResampleBuffer.shrink_to_fit();
2016 mBufferFilled = 0;
2017 if(mDevice->Frequency != mFormat.Format.nSamplesPerSec)
2019 mResampler = SampleConverter::Create(mDevice->FmtType, mDevice->FmtType,
2020 mFormat.Format.nChannels, mDevice->Frequency, mFormat.Format.nSamplesPerSec,
2021 Resampler::FastBSinc24);
2022 mResampleBuffer.resize(size_t{mDevice->UpdateSize} * mFormat.Format.nChannels *
2023 mFormat.Format.wBitsPerSample / 8);
2025 TRACE("Created converter for %s/%s format, dst: %luhz (%u), src: %uhz (%u)\n",
2026 DevFmtChannelsString(mDevice->FmtChans), DevFmtTypeString(mDevice->FmtType),
2027 mFormat.Format.nSamplesPerSec, mOrigUpdateSize, mDevice->Frequency,
2028 mDevice->UpdateSize);
2031 return hr;
2035 void WasapiPlayback::start()
2037 const HRESULT hr{pushMessage(MsgType::StartDevice).get()};
2038 if(FAILED(hr))
2039 throw al::backend_exception{al::backend_error::DeviceError,
2040 "Failed to start playback: 0x%lx", hr};
2043 HRESULT WasapiPlayback::startProxy()
2045 ResetEvent(mNotifyEvent);
2047 auto mstate_fallback = [](std::monostate) -> HRESULT
2048 { return E_FAIL; };
2049 auto start_plain = [&](PlainDevice &audio) -> HRESULT
2051 HRESULT hr{audio.mClient->Start()};
2052 if(FAILED(hr))
2054 ERR("Failed to start audio client: 0x%08lx\n", hr);
2055 return hr;
2058 hr = audio.mClient->GetService(__uuidof(IAudioRenderClient), al::out_ptr(audio.mRender));
2059 if(SUCCEEDED(hr))
2061 try {
2062 mKillNow.store(false, std::memory_order_release);
2063 mThread = std::thread{std::mem_fn(&WasapiPlayback::mixerProc), this};
2065 catch(...) {
2066 audio.mRender = nullptr;
2067 ERR("Failed to start thread\n");
2068 hr = E_FAIL;
2071 if(FAILED(hr))
2072 audio.mClient->Stop();
2073 return hr;
2075 auto start_spatial = [&](SpatialDevice &audio) -> HRESULT
2077 HRESULT hr{audio.mRender->Start()};
2078 if(FAILED(hr))
2080 ERR("Failed to start spatial audio stream: 0x%08lx\n", hr);
2081 return hr;
2084 try {
2085 mKillNow.store(false, std::memory_order_release);
2086 mThread = std::thread{std::mem_fn(&WasapiPlayback::mixerSpatialProc), this};
2088 catch(...) {
2089 ERR("Failed to start thread\n");
2090 hr = E_FAIL;
2093 if(FAILED(hr))
2095 audio.mRender->Stop();
2096 audio.mRender->Reset();
2098 return hr;
2101 return std::visit(overloaded{mstate_fallback, start_plain, start_spatial}, mAudio);
2105 void WasapiPlayback::stop()
2106 { pushMessage(MsgType::StopDevice).wait(); }
2108 void WasapiPlayback::stopProxy()
2110 if(!mThread.joinable())
2111 return;
2113 mKillNow.store(true, std::memory_order_release);
2114 mThread.join();
2116 auto mstate_fallback = [](std::monostate) -> void
2117 { };
2118 auto stop_plain = [](PlainDevice &audio) -> void
2120 audio.mRender = nullptr;
2121 audio.mClient->Stop();
2123 auto stop_spatial = [](SpatialDevice &audio) -> void
2125 audio.mRender->Stop();
2126 audio.mRender->Reset();
2128 std::visit(overloaded{mstate_fallback, stop_plain, stop_spatial}, mAudio);
2132 ClockLatency WasapiPlayback::getClockLatency()
2134 std::lock_guard<std::mutex> dlock{mMutex};
2135 ClockLatency ret{};
2136 ret.ClockTime = mDevice->getClockTime();
2137 ret.Latency = seconds{mPadding.load(std::memory_order_relaxed)};
2138 ret.Latency /= mFormat.Format.nSamplesPerSec;
2139 if(mResampler)
2141 auto extra = mResampler->currentInputDelay();
2142 ret.Latency += std::chrono::duration_cast<nanoseconds>(extra) / mDevice->Frequency;
2143 ret.Latency += nanoseconds{seconds{mBufferFilled}} / mDevice->Frequency;
2146 return ret;
2150 struct WasapiCapture final : public BackendBase, WasapiProxy {
2151 WasapiCapture(DeviceBase *device) noexcept : BackendBase{device} { }
2152 ~WasapiCapture() override;
2154 int recordProc();
2156 void open(std::string_view name) override;
2157 HRESULT openProxy(std::string_view name) override;
2158 void closeProxy() override;
2160 HRESULT resetProxy() override;
2161 void start() override;
2162 HRESULT startProxy() override;
2163 void stop() override;
2164 void stopProxy() override;
2166 void captureSamples(std::byte *buffer, uint samples) override;
2167 uint availableSamples() override;
2169 HRESULT mOpenStatus{E_FAIL};
2170 DeviceHandle mMMDev{nullptr};
2171 ComPtr<IAudioClient> mClient{nullptr};
2172 ComPtr<IAudioCaptureClient> mCapture{nullptr};
2173 HANDLE mNotifyEvent{nullptr};
2175 ChannelConverter mChannelConv{};
2176 SampleConverterPtr mSampleConv;
2177 RingBufferPtr mRing;
2179 std::atomic<bool> mKillNow{true};
2180 std::thread mThread;
2183 WasapiCapture::~WasapiCapture()
2185 if(SUCCEEDED(mOpenStatus))
2186 pushMessage(MsgType::CloseDevice).wait();
2187 mOpenStatus = E_FAIL;
2189 if(mNotifyEvent != nullptr)
2190 CloseHandle(mNotifyEvent);
2191 mNotifyEvent = nullptr;
2195 FORCE_ALIGN int WasapiCapture::recordProc()
2197 ComWrapper com{COINIT_MULTITHREADED};
2198 if(!com)
2200 ERR("CoInitializeEx(nullptr, COINIT_MULTITHREADED) failed: 0x%08lx\n", com.status());
2201 mDevice->handleDisconnect("COM init failed: 0x%08lx", com.status());
2202 return 1;
2205 althrd_setname(GetRecordThreadName());
2207 std::vector<float> samples;
2208 while(!mKillNow.load(std::memory_order_relaxed))
2210 UINT32 avail;
2211 HRESULT hr{mCapture->GetNextPacketSize(&avail)};
2212 if(FAILED(hr))
2213 ERR("Failed to get next packet size: 0x%08lx\n", hr);
2214 else if(avail > 0)
2216 UINT32 numsamples;
2217 DWORD flags;
2218 BYTE *rdata;
2220 hr = mCapture->GetBuffer(&rdata, &numsamples, &flags, nullptr, nullptr);
2221 if(FAILED(hr))
2222 ERR("Failed to get capture buffer: 0x%08lx\n", hr);
2223 else
2225 if(mChannelConv.is_active())
2227 samples.resize(numsamples*2_uz);
2228 mChannelConv.convert(rdata, samples.data(), numsamples);
2229 rdata = reinterpret_cast<BYTE*>(samples.data());
2232 auto data = mRing->getWriteVector();
2234 size_t dstframes;
2235 if(mSampleConv)
2237 static constexpr auto lenlimit = size_t{std::numeric_limits<int>::max()};
2238 const void *srcdata{rdata};
2239 uint srcframes{numsamples};
2241 dstframes = mSampleConv->convert(&srcdata, &srcframes, data.first.buf,
2242 static_cast<uint>(std::min(data.first.len, lenlimit)));
2243 if(srcframes > 0 && dstframes == data.first.len && data.second.len > 0)
2245 /* If some source samples remain, all of the first dest
2246 * block was filled, and there's space in the second
2247 * dest block, do another run for the second block.
2249 dstframes += mSampleConv->convert(&srcdata, &srcframes, data.second.buf,
2250 static_cast<uint>(std::min(data.second.len, lenlimit)));
2253 else
2255 const uint framesize{mDevice->frameSizeFromFmt()};
2256 auto dst = al::span{rdata, size_t{numsamples}*framesize};
2257 size_t len1{std::min(data.first.len, size_t{numsamples})};
2258 size_t len2{std::min(data.second.len, numsamples-len1)};
2260 memcpy(data.first.buf, dst.data(), len1*framesize);
2261 if(len2 > 0)
2263 dst = dst.subspan(len1*framesize);
2264 memcpy(data.second.buf, dst.data(), len2*framesize);
2266 dstframes = len1 + len2;
2269 mRing->writeAdvance(dstframes);
2271 hr = mCapture->ReleaseBuffer(numsamples);
2272 if(FAILED(hr)) ERR("Failed to release capture buffer: 0x%08lx\n", hr);
2276 if(FAILED(hr))
2278 mDevice->handleDisconnect("Failed to capture samples: 0x%08lx", hr);
2279 break;
2282 DWORD res{WaitForSingleObjectEx(mNotifyEvent, 2000, FALSE)};
2283 if(res != WAIT_OBJECT_0)
2284 ERR("WaitForSingleObjectEx error: 0x%lx\n", res);
2287 return 0;
2291 void WasapiCapture::open(std::string_view name)
2293 if(SUCCEEDED(mOpenStatus))
2294 throw al::backend_exception{al::backend_error::DeviceError,
2295 "Unexpected duplicate open call"};
2297 mNotifyEvent = CreateEventW(nullptr, FALSE, FALSE, nullptr);
2298 if(mNotifyEvent == nullptr)
2300 ERR("Failed to create notify events: %lu\n", GetLastError());
2301 throw al::backend_exception{al::backend_error::DeviceError,
2302 "Failed to create notify events"};
2305 if(const auto prefix = GetDevicePrefix(); al::starts_with(name, prefix))
2306 name = name.substr(prefix.size());
2308 mOpenStatus = pushMessage(MsgType::OpenDevice, name).get();
2309 if(FAILED(mOpenStatus))
2310 throw al::backend_exception{al::backend_error::DeviceError, "Device init failed: 0x%08lx",
2311 mOpenStatus};
2313 HRESULT hr{pushMessage(MsgType::ResetDevice).get()};
2314 if(FAILED(hr))
2316 if(hr == E_OUTOFMEMORY)
2317 throw al::backend_exception{al::backend_error::OutOfMemory, "Out of memory"};
2318 throw al::backend_exception{al::backend_error::DeviceError, "Device reset failed"};
2322 HRESULT WasapiCapture::openProxy(std::string_view name)
2324 std::string devname;
2325 std::wstring devid;
2326 if(!name.empty())
2328 auto devlock = DeviceListLock{gDeviceList};
2329 auto devlist = al::span{devlock.getCaptureList()};
2330 auto iter = std::find_if(devlist.cbegin(), devlist.cend(),
2331 [name](const DevMap &entry) -> bool
2332 { return entry.name == name || entry.endpoint_guid == name; });
2333 if(iter == devlist.cend())
2335 const std::wstring wname{utf8_to_wstr(name)};
2336 iter = std::find_if(devlist.cbegin(), devlist.cend(),
2337 [&wname](const DevMap &entry) -> bool
2338 { return entry.devid == wname; });
2340 if(iter == devlist.cend())
2342 WARN("Failed to find device name matching \"%.*s\"\n", al::sizei(name), name.data());
2343 return E_FAIL;
2345 devname = iter->name;
2346 devid = iter->devid;
2349 HRESULT hr{sDeviceHelper->openDevice(devid, eCapture, mMMDev)};
2350 if(FAILED(hr))
2352 WARN("Failed to open device \"%s\"\n", devname.empty() ? "(default)" : devname.c_str());
2353 return hr;
2355 mClient = nullptr;
2356 if(!devname.empty())
2357 mDevice->DeviceName = std::string{GetDevicePrefix()}+std::move(devname);
2358 else
2359 mDevice->DeviceName = std::string{GetDevicePrefix()}+GetDeviceNameAndGuid(mMMDev).first;
2361 return S_OK;
2364 void WasapiCapture::closeProxy()
2366 mClient = nullptr;
2367 mMMDev = nullptr;
2370 HRESULT WasapiCapture::resetProxy()
2372 mClient = nullptr;
2374 HRESULT hr{sDeviceHelper->activateAudioClient(mMMDev, __uuidof(IAudioClient),
2375 al::out_ptr(mClient))};
2376 if(FAILED(hr))
2378 ERR("Failed to reactivate audio client: 0x%08lx\n", hr);
2379 return hr;
2382 WAVEFORMATEX *wfx;
2383 hr = mClient->GetMixFormat(&wfx);
2384 if(FAILED(hr))
2386 ERR("Failed to get capture format: 0x%08lx\n", hr);
2387 return hr;
2389 TraceFormat("Device capture format", wfx);
2391 WAVEFORMATEXTENSIBLE InputType{};
2392 if(!MakeExtensible(&InputType, wfx))
2394 CoTaskMemFree(wfx);
2395 return E_FAIL;
2397 CoTaskMemFree(wfx);
2398 wfx = nullptr;
2400 const bool isRear51{InputType.Format.nChannels == 6
2401 && (InputType.dwChannelMask&X51RearMask) == X5DOT1REAR};
2403 // Make sure buffer is at least 100ms in size
2404 ReferenceTime buf_time{ReferenceTime{seconds{mDevice->BufferSize}} / mDevice->Frequency};
2405 buf_time = std::max(buf_time, ReferenceTime{milliseconds{100}});
2407 InputType = {};
2408 InputType.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
2409 switch(mDevice->FmtChans)
2411 case DevFmtMono:
2412 InputType.Format.nChannels = 1;
2413 InputType.dwChannelMask = MONO;
2414 break;
2415 case DevFmtStereo:
2416 InputType.Format.nChannels = 2;
2417 InputType.dwChannelMask = STEREO;
2418 break;
2419 case DevFmtQuad:
2420 InputType.Format.nChannels = 4;
2421 InputType.dwChannelMask = QUAD;
2422 break;
2423 case DevFmtX51:
2424 InputType.Format.nChannels = 6;
2425 InputType.dwChannelMask = isRear51 ? X5DOT1REAR : X5DOT1;
2426 break;
2427 case DevFmtX61:
2428 InputType.Format.nChannels = 7;
2429 InputType.dwChannelMask = X6DOT1;
2430 break;
2431 case DevFmtX71:
2432 InputType.Format.nChannels = 8;
2433 InputType.dwChannelMask = X7DOT1;
2434 break;
2435 case DevFmtX714:
2436 InputType.Format.nChannels = 12;
2437 InputType.dwChannelMask = X7DOT1DOT4;
2438 break;
2440 case DevFmtX7144:
2441 case DevFmtX3D71:
2442 case DevFmtAmbi3D:
2443 return E_FAIL;
2445 switch(mDevice->FmtType)
2447 /* NOTE: Signedness doesn't matter, the converter will handle it. */
2448 case DevFmtByte:
2449 case DevFmtUByte:
2450 InputType.Format.wBitsPerSample = 8;
2451 InputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
2452 break;
2453 case DevFmtShort:
2454 case DevFmtUShort:
2455 InputType.Format.wBitsPerSample = 16;
2456 InputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
2457 break;
2458 case DevFmtInt:
2459 case DevFmtUInt:
2460 InputType.Format.wBitsPerSample = 32;
2461 InputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
2462 break;
2463 case DevFmtFloat:
2464 InputType.Format.wBitsPerSample = 32;
2465 InputType.SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT;
2466 break;
2468 /* NOLINTNEXTLINE(cppcoreguidelines-pro-type-union-access) */
2469 InputType.Samples.wValidBitsPerSample = InputType.Format.wBitsPerSample;
2470 InputType.Format.nSamplesPerSec = mDevice->Frequency;
2472 InputType.Format.nBlockAlign = static_cast<WORD>(InputType.Format.nChannels *
2473 InputType.Format.wBitsPerSample / 8);
2474 InputType.Format.nAvgBytesPerSec = InputType.Format.nSamplesPerSec *
2475 InputType.Format.nBlockAlign;
2476 InputType.Format.cbSize = sizeof(InputType) - sizeof(InputType.Format);
2478 TraceFormat("Requesting capture format", &InputType.Format);
2479 hr = mClient->IsFormatSupported(AUDCLNT_SHAREMODE_SHARED, &InputType.Format, &wfx);
2480 if(FAILED(hr))
2482 WARN("Failed to check capture format support: 0x%08lx\n", hr);
2483 hr = mClient->GetMixFormat(&wfx);
2485 if(FAILED(hr))
2487 ERR("Failed to find a supported capture format: 0x%08lx\n", hr);
2488 return hr;
2491 mSampleConv = nullptr;
2492 mChannelConv = {};
2494 if(wfx != nullptr)
2496 TraceFormat("Got capture format", wfx);
2497 if(!MakeExtensible(&InputType, wfx))
2499 CoTaskMemFree(wfx);
2500 return E_FAIL;
2502 CoTaskMemFree(wfx);
2503 wfx = nullptr;
2505 auto validate_fmt = [](DeviceBase *device, uint32_t chancount, DWORD chanmask) noexcept
2506 -> bool
2508 switch(device->FmtChans)
2510 /* If the device wants mono, we can handle any input. */
2511 case DevFmtMono:
2512 return true;
2513 /* If the device wants stereo, we can handle mono or stereo input. */
2514 case DevFmtStereo:
2515 return (chancount == 2 && (chanmask == 0 || (chanmask&StereoMask) == STEREO))
2516 || (chancount == 1 && (chanmask&MonoMask) == MONO);
2517 /* Otherwise, the device must match the input type. */
2518 case DevFmtQuad:
2519 return (chancount == 4 && (chanmask == 0 || (chanmask&QuadMask) == QUAD));
2520 /* 5.1 (Side) and 5.1 (Rear) are interchangeable here. */
2521 case DevFmtX51:
2522 return (chancount == 6 && (chanmask == 0 || (chanmask&X51Mask) == X5DOT1
2523 || (chanmask&X51RearMask) == X5DOT1REAR));
2524 case DevFmtX61:
2525 return (chancount == 7 && (chanmask == 0 || (chanmask&X61Mask) == X6DOT1));
2526 case DevFmtX71:
2527 case DevFmtX3D71:
2528 return (chancount == 8 && (chanmask == 0 || (chanmask&X71Mask) == X7DOT1));
2529 case DevFmtX714:
2530 return (chancount == 12 && (chanmask == 0 || (chanmask&X714Mask) == X7DOT1DOT4));
2531 case DevFmtX7144:
2532 return (chancount == 16 && chanmask == 0);
2533 case DevFmtAmbi3D:
2534 return (chanmask == 0 && chancount == device->channelsFromFmt());
2536 return false;
2538 if(!validate_fmt(mDevice, InputType.Format.nChannels, InputType.dwChannelMask))
2540 ERR("Failed to match format, wanted: %s %s %uhz, got: 0x%08lx mask %d channel%s %d-bit %luhz\n",
2541 DevFmtChannelsString(mDevice->FmtChans), DevFmtTypeString(mDevice->FmtType),
2542 mDevice->Frequency, InputType.dwChannelMask, InputType.Format.nChannels,
2543 (InputType.Format.nChannels==1)?"":"s", InputType.Format.wBitsPerSample,
2544 InputType.Format.nSamplesPerSec);
2545 return E_FAIL;
2549 DevFmtType srcType{};
2550 if(IsEqualGUID(InputType.SubFormat, KSDATAFORMAT_SUBTYPE_PCM))
2552 if(InputType.Format.wBitsPerSample == 8)
2553 srcType = DevFmtUByte;
2554 else if(InputType.Format.wBitsPerSample == 16)
2555 srcType = DevFmtShort;
2556 else if(InputType.Format.wBitsPerSample == 32)
2557 srcType = DevFmtInt;
2558 else
2560 ERR("Unhandled integer bit depth: %d\n", InputType.Format.wBitsPerSample);
2561 return E_FAIL;
2564 else if(IsEqualGUID(InputType.SubFormat, KSDATAFORMAT_SUBTYPE_IEEE_FLOAT))
2566 if(InputType.Format.wBitsPerSample == 32)
2567 srcType = DevFmtFloat;
2568 else
2570 ERR("Unhandled float bit depth: %d\n", InputType.Format.wBitsPerSample);
2571 return E_FAIL;
2574 else
2576 ERR("Unhandled format sub-type: %s\n", GuidPrinter{InputType.SubFormat}.c_str());
2577 return E_FAIL;
2580 if(mDevice->FmtChans == DevFmtMono && InputType.Format.nChannels != 1)
2582 uint chanmask{(1u<<InputType.Format.nChannels) - 1u};
2583 /* Exclude LFE from the downmix. */
2584 if((InputType.dwChannelMask&SPEAKER_LOW_FREQUENCY))
2586 constexpr auto lfemask = MaskFromTopBits(SPEAKER_LOW_FREQUENCY);
2587 const int lfeidx{al::popcount(InputType.dwChannelMask&lfemask) - 1};
2588 chanmask &= ~(1u << lfeidx);
2591 mChannelConv = ChannelConverter{srcType, InputType.Format.nChannels, chanmask,
2592 mDevice->FmtChans};
2593 TRACE("Created %s multichannel-to-mono converter\n", DevFmtTypeString(srcType));
2594 /* The channel converter always outputs float, so change the input type
2595 * for the resampler/type-converter.
2597 srcType = DevFmtFloat;
2599 else if(mDevice->FmtChans == DevFmtStereo && InputType.Format.nChannels == 1)
2601 mChannelConv = ChannelConverter{srcType, 1, 0x1, mDevice->FmtChans};
2602 TRACE("Created %s mono-to-stereo converter\n", DevFmtTypeString(srcType));
2603 srcType = DevFmtFloat;
2606 if(mDevice->Frequency != InputType.Format.nSamplesPerSec || mDevice->FmtType != srcType)
2608 mSampleConv = SampleConverter::Create(srcType, mDevice->FmtType,
2609 mDevice->channelsFromFmt(), InputType.Format.nSamplesPerSec, mDevice->Frequency,
2610 Resampler::FastBSinc24);
2611 if(!mSampleConv)
2613 ERR("Failed to create converter for %s format, dst: %s %uhz, src: %s %luhz\n",
2614 DevFmtChannelsString(mDevice->FmtChans), DevFmtTypeString(mDevice->FmtType),
2615 mDevice->Frequency, DevFmtTypeString(srcType), InputType.Format.nSamplesPerSec);
2616 return E_FAIL;
2618 TRACE("Created converter for %s format, dst: %s %uhz, src: %s %luhz\n",
2619 DevFmtChannelsString(mDevice->FmtChans), DevFmtTypeString(mDevice->FmtType),
2620 mDevice->Frequency, DevFmtTypeString(srcType), InputType.Format.nSamplesPerSec);
2623 hr = mClient->Initialize(AUDCLNT_SHAREMODE_SHARED, AUDCLNT_STREAMFLAGS_EVENTCALLBACK,
2624 buf_time.count(), 0, &InputType.Format, nullptr);
2625 if(FAILED(hr))
2627 ERR("Failed to initialize audio client: 0x%08lx\n", hr);
2628 return hr;
2631 UINT32 buffer_len{};
2632 ReferenceTime min_per{};
2633 hr = mClient->GetDevicePeriod(&reinterpret_cast<REFERENCE_TIME&>(min_per), nullptr);
2634 if(SUCCEEDED(hr))
2635 hr = mClient->GetBufferSize(&buffer_len);
2636 if(FAILED(hr))
2638 ERR("Failed to get buffer size: 0x%08lx\n", hr);
2639 return hr;
2641 mDevice->UpdateSize = RefTime2Samples(min_per, mDevice->Frequency);
2642 mDevice->BufferSize = buffer_len;
2644 mRing = RingBuffer::Create(buffer_len, mDevice->frameSizeFromFmt(), false);
2646 hr = mClient->SetEventHandle(mNotifyEvent);
2647 if(FAILED(hr))
2649 ERR("Failed to set event handle: 0x%08lx\n", hr);
2650 return hr;
2653 return hr;
2657 void WasapiCapture::start()
2659 const HRESULT hr{pushMessage(MsgType::StartDevice).get()};
2660 if(FAILED(hr))
2661 throw al::backend_exception{al::backend_error::DeviceError,
2662 "Failed to start recording: 0x%lx", hr};
2665 HRESULT WasapiCapture::startProxy()
2667 ResetEvent(mNotifyEvent);
2669 HRESULT hr{mClient->Start()};
2670 if(FAILED(hr))
2672 ERR("Failed to start audio client: 0x%08lx\n", hr);
2673 return hr;
2676 hr = mClient->GetService(__uuidof(IAudioCaptureClient), al::out_ptr(mCapture));
2677 if(SUCCEEDED(hr))
2679 try {
2680 mKillNow.store(false, std::memory_order_release);
2681 mThread = std::thread{std::mem_fn(&WasapiCapture::recordProc), this};
2683 catch(...) {
2684 mCapture = nullptr;
2685 ERR("Failed to start thread\n");
2686 hr = E_FAIL;
2690 if(FAILED(hr))
2692 mClient->Stop();
2693 mClient->Reset();
2696 return hr;
2700 void WasapiCapture::stop()
2701 { pushMessage(MsgType::StopDevice).wait(); }
2703 void WasapiCapture::stopProxy()
2705 if(!mCapture || !mThread.joinable())
2706 return;
2708 mKillNow.store(true, std::memory_order_release);
2709 mThread.join();
2711 mCapture = nullptr;
2712 mClient->Stop();
2713 mClient->Reset();
2717 void WasapiCapture::captureSamples(std::byte *buffer, uint samples)
2718 { std::ignore = mRing->read(buffer, samples); }
2720 uint WasapiCapture::availableSamples()
2721 { return static_cast<uint>(mRing->readSpace()); }
2723 } // namespace
2726 bool WasapiBackendFactory::init()
2728 static HRESULT InitResult{E_FAIL};
2729 if(FAILED(InitResult)) try
2731 std::promise<HRESULT> promise;
2732 auto future = promise.get_future();
2734 std::thread{&WasapiProxy::messageHandler, &promise}.detach();
2735 InitResult = future.get();
2737 catch(...) {
2740 return SUCCEEDED(InitResult);
2743 bool WasapiBackendFactory::querySupport(BackendType type)
2744 { return type == BackendType::Playback || type == BackendType::Capture; }
2746 auto WasapiBackendFactory::enumerate(BackendType type) -> std::vector<std::string>
2748 std::vector<std::string> outnames;
2750 auto devlock = DeviceListLock{gDeviceList};
2751 switch(type)
2753 case BackendType::Playback:
2755 auto defaultId = devlock.getPlaybackDefaultId();
2756 for(const DevMap &entry : devlock.getPlaybackList())
2758 if(entry.devid != defaultId)
2760 outnames.emplace_back(std::string{GetDevicePrefix()}+entry.name);
2761 continue;
2763 /* Default device goes first. */
2764 outnames.emplace(outnames.cbegin(), std::string{GetDevicePrefix()}+entry.name);
2767 break;
2769 case BackendType::Capture:
2771 auto defaultId = devlock.getCaptureDefaultId();
2772 for(const DevMap &entry : devlock.getCaptureList())
2774 if(entry.devid != defaultId)
2776 outnames.emplace_back(std::string{GetDevicePrefix()}+entry.name);
2777 continue;
2779 outnames.emplace(outnames.cbegin(), std::string{GetDevicePrefix()}+entry.name);
2782 break;
2785 return outnames;
2788 BackendPtr WasapiBackendFactory::createBackend(DeviceBase *device, BackendType type)
2790 if(type == BackendType::Playback)
2791 return BackendPtr{new WasapiPlayback{device}};
2792 if(type == BackendType::Capture)
2793 return BackendPtr{new WasapiCapture{device}};
2794 return nullptr;
2797 BackendFactory &WasapiBackendFactory::getFactory()
2799 static WasapiBackendFactory factory{};
2800 return factory;
2803 alc::EventSupport WasapiBackendFactory::queryEventSupport(alc::EventType eventType, BackendType)
2805 switch(eventType)
2807 case alc::EventType::DefaultDeviceChanged:
2808 return alc::EventSupport::FullSupport;
2810 case alc::EventType::DeviceAdded:
2811 case alc::EventType::DeviceRemoved:
2812 #if !defined(ALSOFT_UWP)
2813 return alc::EventSupport::FullSupport;
2814 #endif
2816 case alc::EventType::Count:
2817 break;
2819 return alc::EventSupport::NoSupport;