2 * OpenAL cross platform audio library
3 * Copyright (C) 2011 by authors.
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Library General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) any later version.
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Library General Public License for more details.
14 * You should have received a copy of the GNU Library General Public
15 * License along with this library; if not, write to the
16 * Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 * Or go to http://www.gnu.org/copyleft/lgpl.html
25 #define WIN32_LEAN_AND_MEAN
33 #include <mmdeviceapi.h>
34 #include <audiosessiontypes.h>
35 #include <audioclient.h>
36 #include <spatialaudioclient.h>
38 #include <devpropdef.h>
43 #ifndef _WAVEFORMATEXTENSIBLE_
51 #include <condition_variable>
58 #include <string_view>
63 #include "alc/alconfig.h"
64 #include "alnumeric.h"
67 #include "althrd_setname.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"
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
;
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);
99 #ifndef KSDATAFORMAT_SUBTYPE_IEEE_FLOAT
100 DEFINE_GUID(KSDATAFORMAT_SUBTYPE_IEEE_FLOAT
, 0x00000003, 0x0000, 0x0010, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71);
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 );
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
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
)};
149 constexpr AudioObjectType
operator|(AudioObjectType lhs
, AudioObjectType rhs
) noexcept
150 { return static_cast<AudioObjectType
>(lhs
| al::to_underlying(rhs
)); }
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
...>;
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. */
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()));
209 std::array
<char,64> mMsg
{};
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(); }
225 PropVariant() { PropVariantInit(&mProp
); }
226 PropVariant(const PropVariant
&rhs
) : PropVariant
{} { PropVariantCopy(&mProp
, &rhs
.mProp
); }
227 ~PropVariant() { clear(); }
229 auto operator=(const PropVariant
&rhs
) -> PropVariant
&
232 PropVariantCopy(&mProp
, &rhs
.mProp
);
236 void clear() { PropVariantClear(&mProp
); }
238 PROPVARIANT
* get() noexcept
{ return &mProp
; }
240 /* NOLINTBEGIN(cppcoreguidelines-pro-type-union-access) */
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());
265 mProp
.blob
.cbSize
= static_cast<ULONG
>(data
.size());
266 mProp
.blob
.pBlobData
= data
.data();
268 /* NOLINTEND(cppcoreguidelines-pro-type-union-access) */
273 std::string endpoint_guid
; // obtained from PKEY_AudioEndpoint_GUID , set to "Unknown device GUID" if absent.
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. */
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();
295 auto lock() noexcept(noexcept(mMutex
.lock())) { return mMutex
.lock(); }
296 auto unlock() noexcept(noexcept(mMutex
.unlock())) { return mMutex
.unlock(); }
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)
325 eCapture
= (eRender
+ 1),
326 eAll
= (eCapture
+ 1),
327 EDataFlow_enum_count
= (eAll
+ 1)
331 #if defined(ALSOFT_UWP)
332 using DeviceHandle
= Windows::Devices::Enumeration::DeviceInformation
;
333 using EventRegistrationToken
= winrt::event_token
;
335 using DeviceHandle
= ComPtr
<IMMDevice
>;
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
))};
352 WARN("OpenPropertyStore failed: 0x%08lx\n", hr
);
353 return {std::string
{UnknownName
}, std::string
{UnknownGuid
}};
357 hr
= ps
->GetValue(al::bit_cast
<PROPERTYKEY
>(DEVPKEY_Device_FriendlyName
), pvprop
.get());
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
>());
363 WARN("Unexpected Device_FriendlyName PROPVARIANT type: 0x%04x\n", pvprop
.type());
366 hr
= ps
->GetValue(al::bit_cast
<PROPERTYKEY
>(PKEY_AudioEndpoint_GUID
), pvprop
.get());
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
>());
372 WARN("Unexpected AudioEndpoint_GUID PROPVARIANT type: 0x%04x\n", pvprop
.type());
374 std::string name
{wstr_to_utf8(device
.Name())};
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
)); });
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
))};
401 WARN("OpenPropertyStore failed: 0x%08lx\n", hr
);
402 return UnknownFormFactor
;
405 EndpointFormFactor formfactor
{UnknownFormFactor
};
407 hr
= ps
->GetValue(PKEY_AudioEndpoint_FormFactor
, pvform
.get());
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());
419 #if defined(ALSOFT_UWP)
420 struct DeviceHelper final
: public IActivateAudioInterfaceCompletionHandler
422 struct DeviceHelper final
: private IMMNotificationClient
425 #if defined(ALSOFT_UWP)
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
,
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
,
454 DeviceHelper() = default;
458 #if defined(ALSOFT_UWP)
459 MediaDevice::DefaultAudioRenderDeviceChanged(mRenderDeviceChangedToken
);
460 MediaDevice::DefaultAudioCaptureDeviceChanged(mCaptureDeviceChangedToken
);
462 if(mActiveClientEvent
!= nullptr)
463 CloseHandle(mActiveClientEvent
);
464 mActiveClientEvent
= nullptr;
467 mEnumerator
->UnregisterEndpointNotificationCallback(this);
468 mEnumerator
= nullptr;
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.
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);
504 if(IId
== __uuidof(IMMNotificationClient
))
506 *UnknownPtrPtr
= static_cast<IMMNotificationClient
*>(this);
511 else if(IId
== __uuidof(IAgileObject
) || IId
== __uuidof(IUnknown
))
513 *UnknownPtrPtr
= static_cast<IUnknown
*>(this);
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
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
))};
542 ERR("Failed to get device: 0x%08lx\n", hr
);
546 ComPtr
<IMMEndpoint
> endpoint
;
547 hr
= device
->QueryInterface(__uuidof(IMMEndpoint
), al::out_ptr(endpoint
));
550 ERR("Failed to get device endpoint: 0x%08lx\n", hr
);
555 hr
= endpoint
->GetDataFlow(&flowdir
);
558 ERR("Failed to get endpoint data flow: 0x%08lx\n", hr
);
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
);
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
)};
596 alc::Event(alc::EventType::DeviceRemoved
, devtype
, msg
);
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
)
608 const std::wstring_view devid
{pwstrDefaultDeviceId
? pwstrDefaultDeviceId
609 : std::wstring_view
{}};
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
);
626 /** -------------------------- DeviceHelper ----------------------------- */
629 #if !defined(ALSOFT_UWP)
630 HRESULT hr
{CoCreateInstance(CLSID_MMDeviceEnumerator
, nullptr, CLSCTX_INPROC_SERVER
,
631 __uuidof(IMMDeviceEnumerator
), al::out_ptr(mEnumerator
))};
633 mEnumerator
->RegisterEndpointNotificationCallback(this);
635 WARN("Failed to create IMMDeviceEnumerator instance: 0x%08lx\n", hr
);
642 HRESULT
openDevice(std::wstring_view devid
, EDataFlow flow
, DeviceHandle
& device
)
644 #if !defined(ALSOFT_UWP)
649 hr
= mEnumerator
->GetDefaultAudioEndpoint(flow
, eMultimedia
, al::out_ptr(device
));
651 hr
= mEnumerator
->GetDevice(devid
.data(), al::out_ptr(device
));
655 const auto deviceRole
= Windows::Media::Devices::AudioDeviceRole::Default
;
657 devid
.empty() ? (flow
== eRender
? MediaDevice::GetDefaultAudioRenderId(deviceRole
) : MediaDevice::GetDefaultAudioCaptureId(deviceRole
))
658 : winrt::hstring(devid
.data());
659 if (devIfPath
.empty())
662 auto&& deviceInfo
= DeviceInformation::CreateFromIdAsync(devIfPath
, nullptr, DeviceInformationKind::DeviceInterface
).get();
664 return E_NOINTERFACE
;
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
); }
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
))};
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
686 DWORD res
{WaitForSingleObjectEx(mActiveClientEvent
, INFINITE
, FALSE
)};
687 if(res
!= WAIT_OBJECT_0
)
689 ERR("WaitForSingleObjectEx error: 0x%lx\n", res
);
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
);
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
,
714 ERR("Failed to enumerate audio endpoints: 0x%08lx\n", hr
);
719 hr
= coll
->GetCount(&count
);
720 if(SUCCEEDED(hr
) && count
> 0)
723 ComPtr
<IMMDevice
> device
;
724 hr
= mEnumerator
->GetDefaultAudioEndpoint(flowdir
, eMultimedia
, al::out_ptr(device
));
727 if(WCHAR
*devid
{GetDeviceId(device
.get())})
730 CoTaskMemFree(devid
);
735 for(UINT i
{0};i
< count
;++i
)
737 hr
= coll
->Item(i
, al::out_ptr(device
));
741 if(WCHAR
*devid
{GetDeviceId(device
.get())})
743 std::ignore
= AddDevice(device
, devid
, list
);
744 CoTaskMemFree(devid
);
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();
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
)
768 auto deviceCount
= DeviceInfoCollection
.Size();
769 for(unsigned int i
{0};i
< deviceCount
;++i
)
771 auto deviceInfo
= DeviceInfoCollection
.GetAt(i
);
773 std::ignore
= AddDevice(deviceInfo
, deviceInfo
.Id().data(), list
);
776 catch (const winrt::hresult_error
& /*ex*/) {
785 static bool AddDevice(const DeviceHandle
&device
, const WCHAR
*devid
, std::vector
<DevMap
> &list
)
787 for(auto &entry
: list
)
789 if(entry
.devid
== devid
)
793 auto name_guid
= GetDeviceNameAndGuid(device
);
795 std::string newname
{name_guid
.first
};
796 while(checkName(list
, newname
))
798 newname
= name_guid
.first
;
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());
810 #if !defined(ALSOFT_UWP)
811 static WCHAR
*GetDeviceId(IMMDevice
*device
)
815 const HRESULT hr
{device
->GetId(&devid
)};
818 ERR("Failed to get device id: %lx\n", hr
);
824 ComPtr
<IMMDeviceEnumerator
> mEnumerator
{nullptr};
828 HANDLE mActiveClientEvent
{nullptr};
830 EventRegistrationToken mRenderDeviceChangedToken
;
831 EventRegistrationToken mCaptureDeviceChangedToken
;
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
)
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
;
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
)
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
;
868 ERR("Unhandled IEEE float channel count: %d\n", out
->Format
.nChannels
);
869 out
->SubFormat
= KSDATAFORMAT_SUBTYPE_IEEE_FLOAT
;
873 ERR("Unhandled format tag: 0x%04x\n", in
->wFormatTag
);
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) */
888 " FormatTag = 0x%04x\n"
890 " SamplesPerSec = %lu\n"
891 " AvgBytesPerSec = %lu\n"
893 " BitsPerSample = %d\n"
896 " ChannelMask = 0x%lx\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) */
906 " FormatTag = 0x%04x\n"
908 " SamplesPerSec = %lu\n"
909 " AvgBytesPerSec = %lu\n"
911 " BitsPerSample = %d\n"
913 msg
, format
->wFormatTag
, format
->nChannels
, format
->nSamplesPerSec
,
914 format
->nAvgBytesPerSec
, format
->nBlockAlign
, format
->wBitsPerSample
, format
->cbSize
);
928 constexpr const char *GetMessageTypeName(MsgType type
) noexcept
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;
943 /* Proxy interface used by the message handler. */
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;
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();
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();
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();
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
};
1017 WARN("Failed to initialize COM: 0x%08lx\n", com
.status());
1018 promise
->set_value(com
.status());
1022 struct HelperResetter
{
1023 ~HelperResetter() { sDeviceHelper
.reset(); }
1025 HelperResetter scoped_watcher
;
1027 HRESULT hr
{sDeviceHelper
.emplace().init()};
1028 promise
->set_value(hr
);
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());
1050 case MsgType::OpenDevice
:
1051 hr
= msg
.mProxy
->openProxy(msg
.mParam
);
1052 msg
.mPromise
.set_value(hr
);
1055 case MsgType::ResetDevice
:
1056 hr
= msg
.mProxy
->resetProxy();
1057 msg
.mPromise
.set_value(hr
);
1060 case MsgType::StartDevice
:
1061 hr
= msg
.mProxy
->startProxy();
1062 msg
.mPromise
.set_value(hr
);
1065 case MsgType::StopDevice
:
1066 msg
.mProxy
->stopProxy();
1067 msg
.mPromise
.set_value(S_OK
);
1070 case MsgType::CloseDevice
:
1071 msg
.mProxy
->closeProxy();
1072 msg
.mPromise
.set_value(S_OK
);
1075 case MsgType::QuitThread
:
1078 ERR("Unexpected message: %u\n", static_cast<uint
>(msg
.mType
));
1079 msg
.mPromise
.set_value(E_FAIL
);
1081 TRACE("Message loop finished\n");
1086 struct WasapiPlayback final
: public BackendBase
, WasapiProxy
{
1087 WasapiPlayback(DeviceBase
*device
) noexcept
: BackendBase
{device
} { }
1088 ~WasapiPlayback() override
;
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};
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
};
1157 ERR("CoInitializeEx(nullptr, COINIT_MULTITHREADED) failed: 0x%08lx\n", com
.status());
1158 mDevice
->handleDisconnect("COM init failed: 0x%08lx", com
.status());
1162 auto &audio
= std::get
<PlainDevice
>(mAudio
);
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
{};
1173 while(!mKillNow
.load(std::memory_order_relaxed
))
1176 HRESULT hr
{audio
.mClient
->GetCurrentPadding(&written
)};
1179 ERR("Failed to get padding: 0x%08lx\n", hr
);
1180 mDevice
->handleDisconnect("Failed to retrieve buffer padding: 0x%08lx", hr
);
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
);
1195 hr
= audio
.mRender
->GetBuffer(len
, &buffer
);
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(),
1214 dst
= dst
.subspan(size_t{got
}*frame_size
);
1217 mPadding
.store(written
+ done
, std::memory_order_relaxed
);
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);
1230 ERR("Failed to buffer data: 0x%08lx\n", hr
);
1231 mDevice
->handleDisconnect("Failed to send playback samples: 0x%08lx", hr
);
1235 mPadding
.store(0u, std::memory_order_release
);
1240 FORCE_ALIGN
int WasapiPlayback::mixerSpatialProc()
1242 ComWrapper com
{COINIT_MULTITHREADED
};
1245 ERR("CoInitializeEx(nullptr, COINIT_MULTITHREADED) failed: 0x%08lx\n", com
.status());
1246 mDevice
->handleDisconnect("COM init failed: 0x%08lx", com
.status());
1250 auto &audio
= std::get
<SpatialDevice
>(mAudio
);
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
1264 mPadding
.store(mOrigBufferSize
-mOrigUpdateSize
, std::memory_order_release
);
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()};
1276 ERR("ISpatialAudioObjectRenderStream::Reset failed: 0x%08lx\n", hr
);
1277 mDevice
->handleDisconnect("Device lost: 0x%08lx", hr
);
1282 UINT32 dynamicCount
{}, framesToDo
{};
1283 HRESULT hr
{audio
.mRender
->BeginUpdatingAudioObjects(&dynamicCount
, &framesToDo
)};
1286 if(channels
.empty()) UNLIKELY
1288 auto flags
= as_unsigned(audio
.mStaticMask
);
1289 channels
.reserve(as_unsigned(al::popcount(flags
)));
1292 auto id
= decltype(flags
){1} << al::countr_zero(flags
);
1295 channels
.emplace_back();
1296 audio
.mRender
->ActivateSpatialAudioObject(static_cast<AudioObjectType
>(id
),
1297 al::out_ptr(channels
.back()));
1299 buffers
.resize(channels
.size());
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
);
1326 mDevice
->renderSamples(buffers
, framesToDo
);
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) */
1347 hr
= audio
.mRender
->EndUpdatingAudioObjects();
1351 ERR("Failed to update playback objects: 0x%08lx\n", hr
);
1353 mPadding
.store(0u, std::memory_order_release
);
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",
1382 HRESULT
WasapiPlayback::openProxy(std::string_view name
)
1384 std::string devname
;
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());
1405 devname
= iter
->name
;
1406 devid
= iter
->devid
;
1409 HRESULT hr
{sDeviceHelper
->openDevice(devid
, eRender
, mMMDev
)};
1412 WARN("Failed to open device \"%s\"\n", devname
.empty() ? "(default)" : devname
.c_str());
1415 if(!devname
.empty())
1416 mDevice
->DeviceName
= std::string
{GetDevicePrefix()}+std::move(devname
);
1418 mDevice
->DeviceName
= std::string
{GetDevicePrefix()}+GetDeviceNameAndGuid(mMMDev
).first
;
1423 void WasapiPlayback::closeProxy()
1425 mAudio
.emplace
<std::monostate
>();
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
;
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
;
1464 ERR("Unhandled channel config: %d -- 0x%08lx\n", chancount
, chanmask
);
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
)
1477 OutputType
.Format
.nChannels
= 1;
1478 OutputType
.dwChannelMask
= MONO
;
1481 mDevice
->FmtChans
= DevFmtStereo
;
1484 OutputType
.Format
.nChannels
= 2;
1485 OutputType
.dwChannelMask
= STEREO
;
1488 OutputType
.Format
.nChannels
= 4;
1489 OutputType
.dwChannelMask
= QUAD
;
1492 OutputType
.Format
.nChannels
= 6;
1493 OutputType
.dwChannelMask
= isRear51
? X5DOT1REAR
: X5DOT1
;
1496 OutputType
.Format
.nChannels
= 7;
1497 OutputType
.dwChannelMask
= X6DOT1
;
1501 OutputType
.Format
.nChannels
= 8;
1502 OutputType
.dwChannelMask
= X7DOT1
;
1505 mDevice
->FmtChans
= DevFmtX714
;
1508 OutputType
.Format
.nChannels
= 12;
1509 OutputType
.dwChannelMask
= X7DOT1DOT4
;
1512 switch(mDevice
->FmtType
)
1515 mDevice
->FmtType
= DevFmtUByte
;
1518 OutputType
.Format
.wBitsPerSample
= 8;
1519 OutputType
.SubFormat
= KSDATAFORMAT_SUBTYPE_PCM
;
1522 mDevice
->FmtType
= DevFmtShort
;
1525 OutputType
.Format
.wBitsPerSample
= 16;
1526 OutputType
.SubFormat
= KSDATAFORMAT_SUBTYPE_PCM
;
1529 mDevice
->FmtType
= DevFmtInt
;
1532 OutputType
.Format
.wBitsPerSample
= 32;
1533 OutputType
.SubFormat
= KSDATAFORMAT_SUBTYPE_PCM
;
1536 OutputType
.Format
.wBitsPerSample
= 32;
1537 OutputType
.SubFormat
= KSDATAFORMAT_SUBTYPE_IEEE_FLOAT
;
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
);
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
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
)
1572 chansok
= (chancount
>= 1 && ((chanmask
&MonoMask
) == MONO
|| !chanmask
));
1575 chansok
= (chancount
>= 2 && ((chanmask
&StereoMask
) == STEREO
|| !chanmask
));
1578 chansok
= (chancount
>= 4 && ((chanmask
&QuadMask
) == QUAD
|| !chanmask
));
1581 chansok
= (chancount
>= 6 && ((chanmask
&X51Mask
) == X5DOT1
1582 || (chanmask
&X51RearMask
) == X5DOT1REAR
|| !chanmask
));
1585 chansok
= (chancount
>= 7 && ((chanmask
&X61Mask
) == X6DOT1
|| !chanmask
));
1589 chansok
= (chancount
>= 8 && ((chanmask
&X71Mask
) == X7DOT1
|| !chanmask
));
1592 chansok
= (chancount
>= 12 && ((chanmask
&X714Mask
) == X7DOT1DOT4
|| !chanmask
));
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
;
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
;
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;
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
))};
1665 ERR("Failed to activate spatial audio client: 0x%08lx\n", hr
);
1669 ComPtr
<IAudioFormatEnumerator
> fmtenum
;
1670 hr
= audio
.mClient
->GetSupportedAudioObjectFormatEnumerator(al::out_ptr(fmtenum
));
1673 ERR("Failed to get format enumerator: 0x%08lx\n", hr
);
1678 hr
= fmtenum
->GetCount(&fmtcount
);
1679 if(FAILED(hr
) || fmtcount
== 0)
1681 ERR("Failed to get format count: 0x%08lx\n", hr
);
1685 WAVEFORMATEX
*preferredFormat
{};
1686 hr
= fmtenum
->GetFormat(0, &preferredFormat
);
1689 ERR("Failed to get preferred format: 0x%08lx\n", hr
);
1692 TraceFormat("Preferred mix format", preferredFormat
);
1695 hr
= audio
.mClient
->GetMaxFrameCount(preferredFormat
, &maxFrames
);
1697 ERR("Failed to get max frames: 0x%08lx\n", hr
);
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
);
1705 ERR("Failed to format %u: 0x%08lx\n", i
+1, hr
);
1708 TraceFormat("Other mix format", otherFormat
);
1709 UINT32 otherMaxFrames
{};
1710 hr
= audio
.mClient
->GetMaxFrameCount(otherFormat
, &otherMaxFrames
);
1712 ERR("Failed to get max frames: 0x%08lx\n", hr
);
1714 TRACE("Max sample frames: %u\n", otherMaxFrames
);
1718 WAVEFORMATEXTENSIBLE OutputType
;
1719 if(!MakeExtensible(&OutputType
, preferredFormat
))
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
;
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
1770 else if(!(chancount
>= 1 && ((chanmask
&MonoMask
) == MONO
|| !chanmask
)))
1771 ERR("Unhandled channel config: %d -- 0x%08lx\n", chancount
, chanmask
);
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
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
;
1790 case DevFmtX71
: return ChannelMask_X71
;
1791 case DevFmtX714
: return ChannelMask_X714
;
1792 case DevFmtX7144
: return ChannelMask_X7144
;
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
));
1812 ERR("Failed to activate spatial audio stream: 0x%08lx\n", hr
);
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
);
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
));
1849 ERR("Failed to activate audio client: 0x%08lx\n", hr
);
1852 hr
= tmpClient
->GetDevicePeriod(&reinterpret_cast<REFERENCE_TIME
&>(per_time
), nullptr);
1854 ERR("Failed to get device period: 0x%08lx\n", hr
);
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();
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
);
1888 bool WasapiPlayback::reset()
1890 HRESULT hr
{pushMessage(MsgType::ResetDevice
).get()};
1892 throw al::backend_exception
{al::backend_error::DeviceError
, "0x%08lx", hr
};
1896 HRESULT
WasapiPlayback::resetProxy()
1898 if(GetConfigValueBool(mDevice
->DeviceName
, "wasapi", "spatial-api", false))
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
))};
1911 ERR("Failed to reactivate audio client: 0x%08lx\n", hr
);
1916 hr
= audio
.mClient
->GetMixFormat(&wfx
);
1919 ERR("Failed to get mix format: 0x%08lx\n", hr
);
1922 TraceFormat("Device mix format", wfx
);
1924 WAVEFORMATEXTENSIBLE OutputType
;
1925 if(!MakeExtensible(&OutputType
, wfx
))
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
);
1942 WARN("Failed to check format support: 0x%08lx\n", hr
);
1943 hr
= audio
.mClient
->GetMixFormat(&wfx
);
1947 ERR("Failed to find a supported format: 0x%08lx\n", hr
);
1953 TraceFormat("Got playback format", wfx
);
1954 if(!MakeExtensible(&OutputType
, wfx
))
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
));
1970 mDevice
->Flags
.set(DirectEar
, false);
1972 setDefaultWFXChannelOrder();
1974 hr
= audio
.mClient
->Initialize(AUDCLNT_SHAREMODE_SHARED
, AUDCLNT_STREAMFLAGS_EVENTCALLBACK
,
1975 buf_time
.count(), 0, &OutputType
.Format
, nullptr);
1978 ERR("Failed to initialize audio client: 0x%08lx\n", hr
);
1982 UINT32 buffer_len
{};
1983 ReferenceTime min_per
{};
1984 hr
= audio
.mClient
->GetDevicePeriod(&reinterpret_cast<REFERENCE_TIME
&>(min_per
), nullptr);
1986 hr
= audio
.mClient
->GetBufferSize(&buffer_len
);
1989 ERR("Failed to get audio buffer info: 0x%08lx\n", hr
);
1993 hr
= audio
.mClient
->SetEventHandle(mNotifyEvent
);
1996 ERR("Failed to set event handle: 0x%08lx\n", 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
),
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();
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
);
2035 void WasapiPlayback::start()
2037 const HRESULT hr
{pushMessage(MsgType::StartDevice
).get()};
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
2049 auto start_plain
= [&](PlainDevice
&audio
) -> HRESULT
2051 HRESULT hr
{audio
.mClient
->Start()};
2054 ERR("Failed to start audio client: 0x%08lx\n", hr
);
2058 hr
= audio
.mClient
->GetService(__uuidof(IAudioRenderClient
), al::out_ptr(audio
.mRender
));
2062 mKillNow
.store(false, std::memory_order_release
);
2063 mThread
= std::thread
{std::mem_fn(&WasapiPlayback::mixerProc
), this};
2066 audio
.mRender
= nullptr;
2067 ERR("Failed to start thread\n");
2072 audio
.mClient
->Stop();
2075 auto start_spatial
= [&](SpatialDevice
&audio
) -> HRESULT
2077 HRESULT hr
{audio
.mRender
->Start()};
2080 ERR("Failed to start spatial audio stream: 0x%08lx\n", hr
);
2085 mKillNow
.store(false, std::memory_order_release
);
2086 mThread
= std::thread
{std::mem_fn(&WasapiPlayback::mixerSpatialProc
), this};
2089 ERR("Failed to start thread\n");
2095 audio
.mRender
->Stop();
2096 audio
.mRender
->Reset();
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())
2113 mKillNow
.store(true, std::memory_order_release
);
2116 auto mstate_fallback
= [](std::monostate
) -> void
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
};
2136 ret
.ClockTime
= mDevice
->getClockTime();
2137 ret
.Latency
= seconds
{mPadding
.load(std::memory_order_relaxed
)};
2138 ret
.Latency
/= mFormat
.Format
.nSamplesPerSec
;
2141 auto extra
= mResampler
->currentInputDelay();
2142 ret
.Latency
+= std::chrono::duration_cast
<nanoseconds
>(extra
) / mDevice
->Frequency
;
2143 ret
.Latency
+= nanoseconds
{seconds
{mBufferFilled
}} / mDevice
->Frequency
;
2150 struct WasapiCapture final
: public BackendBase
, WasapiProxy
{
2151 WasapiCapture(DeviceBase
*device
) noexcept
: BackendBase
{device
} { }
2152 ~WasapiCapture() override
;
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
};
2200 ERR("CoInitializeEx(nullptr, COINIT_MULTITHREADED) failed: 0x%08lx\n", com
.status());
2201 mDevice
->handleDisconnect("COM init failed: 0x%08lx", com
.status());
2205 althrd_setname(GetRecordThreadName());
2207 std::vector
<float> samples
;
2208 while(!mKillNow
.load(std::memory_order_relaxed
))
2211 HRESULT hr
{mCapture
->GetNextPacketSize(&avail
)};
2213 ERR("Failed to get next packet size: 0x%08lx\n", hr
);
2220 hr
= mCapture
->GetBuffer(&rdata
, &numsamples
, &flags
, nullptr, nullptr);
2222 ERR("Failed to get capture buffer: 0x%08lx\n", hr
);
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();
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
)));
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
);
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
);
2278 mDevice
->handleDisconnect("Failed to capture samples: 0x%08lx", hr
);
2282 DWORD res
{WaitForSingleObjectEx(mNotifyEvent
, 2000, FALSE
)};
2283 if(res
!= WAIT_OBJECT_0
)
2284 ERR("WaitForSingleObjectEx error: 0x%lx\n", res
);
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",
2313 HRESULT hr
{pushMessage(MsgType::ResetDevice
).get()};
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
;
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());
2345 devname
= iter
->name
;
2346 devid
= iter
->devid
;
2349 HRESULT hr
{sDeviceHelper
->openDevice(devid
, eCapture
, mMMDev
)};
2352 WARN("Failed to open device \"%s\"\n", devname
.empty() ? "(default)" : devname
.c_str());
2356 if(!devname
.empty())
2357 mDevice
->DeviceName
= std::string
{GetDevicePrefix()}+std::move(devname
);
2359 mDevice
->DeviceName
= std::string
{GetDevicePrefix()}+GetDeviceNameAndGuid(mMMDev
).first
;
2364 void WasapiCapture::closeProxy()
2370 HRESULT
WasapiCapture::resetProxy()
2374 HRESULT hr
{sDeviceHelper
->activateAudioClient(mMMDev
, __uuidof(IAudioClient
),
2375 al::out_ptr(mClient
))};
2378 ERR("Failed to reactivate audio client: 0x%08lx\n", hr
);
2383 hr
= mClient
->GetMixFormat(&wfx
);
2386 ERR("Failed to get capture format: 0x%08lx\n", hr
);
2389 TraceFormat("Device capture format", wfx
);
2391 WAVEFORMATEXTENSIBLE InputType
{};
2392 if(!MakeExtensible(&InputType
, wfx
))
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}});
2408 InputType
.Format
.wFormatTag
= WAVE_FORMAT_EXTENSIBLE
;
2409 switch(mDevice
->FmtChans
)
2412 InputType
.Format
.nChannels
= 1;
2413 InputType
.dwChannelMask
= MONO
;
2416 InputType
.Format
.nChannels
= 2;
2417 InputType
.dwChannelMask
= STEREO
;
2420 InputType
.Format
.nChannels
= 4;
2421 InputType
.dwChannelMask
= QUAD
;
2424 InputType
.Format
.nChannels
= 6;
2425 InputType
.dwChannelMask
= isRear51
? X5DOT1REAR
: X5DOT1
;
2428 InputType
.Format
.nChannels
= 7;
2429 InputType
.dwChannelMask
= X6DOT1
;
2432 InputType
.Format
.nChannels
= 8;
2433 InputType
.dwChannelMask
= X7DOT1
;
2436 InputType
.Format
.nChannels
= 12;
2437 InputType
.dwChannelMask
= X7DOT1DOT4
;
2445 switch(mDevice
->FmtType
)
2447 /* NOTE: Signedness doesn't matter, the converter will handle it. */
2450 InputType
.Format
.wBitsPerSample
= 8;
2451 InputType
.SubFormat
= KSDATAFORMAT_SUBTYPE_PCM
;
2455 InputType
.Format
.wBitsPerSample
= 16;
2456 InputType
.SubFormat
= KSDATAFORMAT_SUBTYPE_PCM
;
2460 InputType
.Format
.wBitsPerSample
= 32;
2461 InputType
.SubFormat
= KSDATAFORMAT_SUBTYPE_PCM
;
2464 InputType
.Format
.wBitsPerSample
= 32;
2465 InputType
.SubFormat
= KSDATAFORMAT_SUBTYPE_IEEE_FLOAT
;
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
);
2482 WARN("Failed to check capture format support: 0x%08lx\n", hr
);
2483 hr
= mClient
->GetMixFormat(&wfx
);
2487 ERR("Failed to find a supported capture format: 0x%08lx\n", hr
);
2491 mSampleConv
= nullptr;
2496 TraceFormat("Got capture format", wfx
);
2497 if(!MakeExtensible(&InputType
, wfx
))
2505 auto validate_fmt
= [](DeviceBase
*device
, uint32_t chancount
, DWORD chanmask
) noexcept
2508 switch(device
->FmtChans
)
2510 /* If the device wants mono, we can handle any input. */
2513 /* If the device wants stereo, we can handle mono or stereo input. */
2515 return (chancount
== 2 && (chanmask
== 0 || (chanmask
&StereoMask
) == STEREO
))
2516 || (chancount
== 1 && (chanmask
&MonoMask
) == MONO
);
2517 /* Otherwise, the device must match the input type. */
2519 return (chancount
== 4 && (chanmask
== 0 || (chanmask
&QuadMask
) == QUAD
));
2520 /* 5.1 (Side) and 5.1 (Rear) are interchangeable here. */
2522 return (chancount
== 6 && (chanmask
== 0 || (chanmask
&X51Mask
) == X5DOT1
2523 || (chanmask
&X51RearMask
) == X5DOT1REAR
));
2525 return (chancount
== 7 && (chanmask
== 0 || (chanmask
&X61Mask
) == X6DOT1
));
2528 return (chancount
== 8 && (chanmask
== 0 || (chanmask
&X71Mask
) == X7DOT1
));
2530 return (chancount
== 12 && (chanmask
== 0 || (chanmask
&X714Mask
) == X7DOT1DOT4
));
2532 return (chancount
== 16 && chanmask
== 0);
2534 return (chanmask
== 0 && chancount
== device
->channelsFromFmt());
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
);
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
;
2560 ERR("Unhandled integer bit depth: %d\n", InputType
.Format
.wBitsPerSample
);
2564 else if(IsEqualGUID(InputType
.SubFormat
, KSDATAFORMAT_SUBTYPE_IEEE_FLOAT
))
2566 if(InputType
.Format
.wBitsPerSample
== 32)
2567 srcType
= DevFmtFloat
;
2570 ERR("Unhandled float bit depth: %d\n", InputType
.Format
.wBitsPerSample
);
2576 ERR("Unhandled format sub-type: %s\n", GuidPrinter
{InputType
.SubFormat
}.c_str());
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
,
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
);
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
);
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);
2627 ERR("Failed to initialize audio client: 0x%08lx\n", hr
);
2631 UINT32 buffer_len
{};
2632 ReferenceTime min_per
{};
2633 hr
= mClient
->GetDevicePeriod(&reinterpret_cast<REFERENCE_TIME
&>(min_per
), nullptr);
2635 hr
= mClient
->GetBufferSize(&buffer_len
);
2638 ERR("Failed to get buffer size: 0x%08lx\n", 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
);
2649 ERR("Failed to set event handle: 0x%08lx\n", hr
);
2657 void WasapiCapture::start()
2659 const HRESULT hr
{pushMessage(MsgType::StartDevice
).get()};
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()};
2672 ERR("Failed to start audio client: 0x%08lx\n", hr
);
2676 hr
= mClient
->GetService(__uuidof(IAudioCaptureClient
), al::out_ptr(mCapture
));
2680 mKillNow
.store(false, std::memory_order_release
);
2681 mThread
= std::thread
{std::mem_fn(&WasapiCapture::recordProc
), this};
2685 ERR("Failed to start thread\n");
2700 void WasapiCapture::stop()
2701 { pushMessage(MsgType::StopDevice
).wait(); }
2703 void WasapiCapture::stopProxy()
2705 if(!mCapture
|| !mThread
.joinable())
2708 mKillNow
.store(true, std::memory_order_release
);
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()); }
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();
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
};
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
);
2763 /* Default device goes first. */
2764 outnames
.emplace(outnames
.cbegin(), std::string
{GetDevicePrefix()}+entry
.name
);
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
);
2779 outnames
.emplace(outnames
.cbegin(), std::string
{GetDevicePrefix()}+entry
.name
);
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
}};
2797 BackendFactory
&WasapiBackendFactory::getFactory()
2799 static WasapiBackendFactory factory
{};
2803 alc::EventSupport
WasapiBackendFactory::queryEventSupport(alc::EventType eventType
, BackendType
)
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
;
2816 case alc::EventType::Count
:
2819 return alc::EventSupport::NoSupport
;