12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819 |
- /**
- * OpenAL cross platform audio library
- * Copyright (C) 2011 by authors.
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Library General Public
- * License as published by the Free Software Foundation; either
- * version 2 of the License, or (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Library General Public License for more details.
- *
- * You should have received a copy of the GNU Library General Public
- * License along with this library; if not, write to the
- * Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- * Or go to http://www.gnu.org/copyleft/lgpl.html
- */
- #include "config.h"
- #include "wasapi.h"
- #define WIN32_LEAN_AND_MEAN
- #include <windows.h>
- #include <cstdio>
- #include <cstdlib>
- #include <memory.h>
- #include <wtypes.h>
- #include <mmdeviceapi.h>
- #include <audiosessiontypes.h>
- #include <audioclient.h>
- #include <spatialaudioclient.h>
- #include <cguid.h>
- #include <devpropdef.h>
- #include <mmreg.h>
- #include <propsys.h>
- #include <propkey.h>
- #include <devpkey.h>
- #ifndef _WAVEFORMATEXTENSIBLE_
- #include <ks.h>
- #include <ksmedia.h>
- #endif
- #include <algorithm>
- #include <atomic>
- #include <chrono>
- #include <condition_variable>
- #include <cstring>
- #include <deque>
- #include <functional>
- #include <future>
- #include <mutex>
- #include <string>
- #include <string_view>
- #include <thread>
- #include <vector>
- #include "albit.h"
- #include "alc/alconfig.h"
- #include "alnumeric.h"
- #include "alspan.h"
- #include "alstring.h"
- #include "althrd_setname.h"
- #include "comptr.h"
- #include "core/converter.h"
- #include "core/device.h"
- #include "core/helpers.h"
- #include "core/logging.h"
- #include "ringbuffer.h"
- #include "strutils.h"
- #if defined(ALSOFT_UWP)
- #include <winrt/Windows.Media.Core.h> // !!This is important!!
- #include <winrt/Windows.Foundation.Collections.h>
- #include <winrt/Windows.Devices.h>
- #include <winrt/Windows.Foundation.h>
- #include <winrt/Windows.Devices.Enumeration.h>
- #include <winrt/Windows.Media.Devices.h>
- using namespace winrt;
- using namespace Windows::Foundation;
- using namespace Windows::Media::Devices;
- using namespace Windows::Devices::Enumeration;
- using namespace Windows::Media::Devices;
- #endif
- /* Some headers seem to define these as macros for __uuidof, which is annoying
- * since some headers don't declare them at all. Hopefully the ifdef is enough
- * to tell if they need to be declared.
- */
- #ifndef KSDATAFORMAT_SUBTYPE_PCM
- DEFINE_GUID(KSDATAFORMAT_SUBTYPE_PCM, 0x00000001, 0x0000, 0x0010, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71);
- #endif
- #ifndef KSDATAFORMAT_SUBTYPE_IEEE_FLOAT
- DEFINE_GUID(KSDATAFORMAT_SUBTYPE_IEEE_FLOAT, 0x00000003, 0x0000, 0x0010, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71);
- #endif
- #if !defined(ALSOFT_UWP)
- DEFINE_DEVPROPKEY(DEVPKEY_Device_FriendlyName, 0xa45c254e, 0xdf1c, 0x4efd, 0x80,0x20, 0x67,0xd1,0x46,0xa8,0x50,0xe0, 14);
- DEFINE_PROPERTYKEY(PKEY_AudioEndpoint_FormFactor, 0x1da5d803, 0xd492, 0x4edd, 0x8c,0x23, 0xe0,0xc0,0xff,0xee,0x7f,0x0e, 0);
- DEFINE_PROPERTYKEY(PKEY_AudioEndpoint_GUID, 0x1da5d803, 0xd492, 0x4edd, 0x8c, 0x23,0xe0, 0xc0,0xff,0xee,0x7f,0x0e, 4 );
- #endif
- namespace {
- using namespace std::string_view_literals;
- using std::chrono::nanoseconds;
- using std::chrono::milliseconds;
- using std::chrono::seconds;
- [[nodiscard]] constexpr auto GetDevicePrefix() noexcept { return "OpenAL Soft on "sv; }
- using ReferenceTime = std::chrono::duration<REFERENCE_TIME,std::ratio<1,10'000'000>>;
- #define MONO SPEAKER_FRONT_CENTER
- #define STEREO (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT)
- #define QUAD (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT|SPEAKER_BACK_LEFT|SPEAKER_BACK_RIGHT)
- #define X5DOT1 (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT|SPEAKER_FRONT_CENTER|SPEAKER_LOW_FREQUENCY|SPEAKER_SIDE_LEFT|SPEAKER_SIDE_RIGHT)
- #define X5DOT1REAR (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT|SPEAKER_FRONT_CENTER|SPEAKER_LOW_FREQUENCY|SPEAKER_BACK_LEFT|SPEAKER_BACK_RIGHT)
- #define X6DOT1 (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT|SPEAKER_FRONT_CENTER|SPEAKER_LOW_FREQUENCY|SPEAKER_BACK_CENTER|SPEAKER_SIDE_LEFT|SPEAKER_SIDE_RIGHT)
- #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)
- #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)
- constexpr inline DWORD MaskFromTopBits(DWORD b) noexcept
- {
- b |= b>>1;
- b |= b>>2;
- b |= b>>4;
- b |= b>>8;
- b |= b>>16;
- return b;
- }
- constexpr DWORD MonoMask{MaskFromTopBits(MONO)};
- constexpr DWORD StereoMask{MaskFromTopBits(STEREO)};
- constexpr DWORD QuadMask{MaskFromTopBits(QUAD)};
- constexpr DWORD X51Mask{MaskFromTopBits(X5DOT1)};
- constexpr DWORD X51RearMask{MaskFromTopBits(X5DOT1REAR)};
- constexpr DWORD X61Mask{MaskFromTopBits(X6DOT1)};
- constexpr DWORD X71Mask{MaskFromTopBits(X7DOT1)};
- constexpr DWORD X714Mask{MaskFromTopBits(X7DOT1DOT4)};
- #ifndef _MSC_VER
- constexpr AudioObjectType operator|(AudioObjectType lhs, AudioObjectType rhs) noexcept
- { return static_cast<AudioObjectType>(lhs | al::to_underlying(rhs)); }
- #endif
- constexpr AudioObjectType ChannelMask_Mono{AudioObjectType_FrontCenter};
- constexpr AudioObjectType ChannelMask_Stereo{AudioObjectType_FrontLeft
- | AudioObjectType_FrontRight};
- constexpr AudioObjectType ChannelMask_Quad{AudioObjectType_FrontLeft | AudioObjectType_FrontRight
- | AudioObjectType_BackLeft | AudioObjectType_BackRight};
- constexpr AudioObjectType ChannelMask_X51{AudioObjectType_FrontLeft | AudioObjectType_FrontRight
- | AudioObjectType_FrontCenter | AudioObjectType_LowFrequency | AudioObjectType_SideLeft
- | AudioObjectType_SideRight};
- constexpr AudioObjectType ChannelMask_X51Rear{AudioObjectType_FrontLeft
- | AudioObjectType_FrontRight | AudioObjectType_FrontCenter | AudioObjectType_LowFrequency
- | AudioObjectType_BackLeft | AudioObjectType_BackRight};
- constexpr AudioObjectType ChannelMask_X61{AudioObjectType_FrontLeft | AudioObjectType_FrontRight
- | AudioObjectType_FrontCenter | AudioObjectType_LowFrequency | AudioObjectType_SideLeft
- | AudioObjectType_SideRight | AudioObjectType_BackCenter};
- constexpr AudioObjectType ChannelMask_X71{AudioObjectType_FrontLeft | AudioObjectType_FrontRight
- | AudioObjectType_FrontCenter | AudioObjectType_LowFrequency | AudioObjectType_SideLeft
- | AudioObjectType_SideRight | AudioObjectType_BackLeft | AudioObjectType_BackRight};
- constexpr AudioObjectType ChannelMask_X714{AudioObjectType_FrontLeft | AudioObjectType_FrontRight
- | AudioObjectType_FrontCenter | AudioObjectType_LowFrequency | AudioObjectType_SideLeft
- | AudioObjectType_SideRight | AudioObjectType_BackLeft | AudioObjectType_BackRight
- | AudioObjectType_TopFrontLeft | AudioObjectType_TopFrontRight | AudioObjectType_TopBackLeft
- | AudioObjectType_TopBackRight};
- constexpr AudioObjectType ChannelMask_X7144{AudioObjectType_FrontLeft | AudioObjectType_FrontRight
- | AudioObjectType_FrontCenter | AudioObjectType_LowFrequency | AudioObjectType_SideLeft
- | AudioObjectType_SideRight | AudioObjectType_BackLeft | AudioObjectType_BackRight
- | AudioObjectType_TopFrontLeft | AudioObjectType_TopFrontRight | AudioObjectType_TopBackLeft
- | AudioObjectType_TopBackRight | AudioObjectType_BottomFrontLeft
- | AudioObjectType_BottomFrontRight | AudioObjectType_BottomBackLeft
- | AudioObjectType_BottomBackRight};
- template<typename... Ts>
- struct overloaded : Ts... { using Ts::operator()...; };
- template<typename... Ts>
- overloaded(Ts...) -> overloaded<Ts...>;
- template<typename T>
- constexpr auto as_unsigned(T value) noexcept
- {
- using UT = std::make_unsigned_t<T>;
- return static_cast<UT>(value);
- }
- /* Scales the given reftime value, rounding the result. */
- template<typename T>
- constexpr uint RefTime2Samples(const ReferenceTime &val, T srate) noexcept
- {
- const auto retval = (val*srate + ReferenceTime{seconds{1}}/2) / seconds{1};
- return static_cast<uint>(std::min<decltype(retval)>(retval, std::numeric_limits<uint>::max()));
- }
- class GuidPrinter {
- std::array<char,64> mMsg{};
- public:
- GuidPrinter(const GUID &guid)
- {
- std::snprintf(mMsg.data(), mMsg.size(), "{%08lx-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x}",
- DWORD{guid.Data1}, guid.Data2, guid.Data3, guid.Data4[0], guid.Data4[1], guid.Data4[2],
- guid.Data4[3], guid.Data4[4], guid.Data4[5], guid.Data4[6], guid.Data4[7]);
- }
- [[nodiscard]] auto c_str() const -> const char* { return mMsg.data(); }
- };
- struct PropVariant {
- PROPVARIANT mProp{};
- public:
- PropVariant() { PropVariantInit(&mProp); }
- PropVariant(const PropVariant &rhs) : PropVariant{} { PropVariantCopy(&mProp, &rhs.mProp); }
- ~PropVariant() { clear(); }
- auto operator=(const PropVariant &rhs) -> PropVariant&
- {
- if(this != &rhs)
- PropVariantCopy(&mProp, &rhs.mProp);
- return *this;
- }
- void clear() { PropVariantClear(&mProp); }
- PROPVARIANT* get() noexcept { return &mProp; }
- /* NOLINTBEGIN(cppcoreguidelines-pro-type-union-access) */
- [[nodiscard]]
- auto type() const noexcept -> VARTYPE { return mProp.vt; }
- template<typename T> [[nodiscard]]
- auto value() const -> T
- {
- if constexpr(std::is_same_v<T,uint>)
- {
- alassert(mProp.vt == VT_UI4);
- return mProp.uiVal;
- }
- else if constexpr(std::is_same_v<T,std::wstring_view> || std::is_same_v<T,std::wstring>)
- {
- alassert(mProp.vt == VT_LPWSTR);
- return mProp.pwszVal;
- }
- }
- void setBlob(const al::span<BYTE> data)
- {
- if constexpr(sizeof(size_t) > sizeof(ULONG))
- alassert(data.size() <= std::numeric_limits<ULONG>::max());
- mProp.vt = VT_BLOB;
- mProp.blob.cbSize = static_cast<ULONG>(data.size());
- mProp.blob.pBlobData = data.data();
- }
- /* NOLINTEND(cppcoreguidelines-pro-type-union-access) */
- };
- struct DevMap {
- std::string name;
- std::string endpoint_guid; // obtained from PKEY_AudioEndpoint_GUID , set to "Unknown device GUID" if absent.
- std::wstring devid;
- template<typename T0, typename T1, typename T2>
- DevMap(T0&& name_, T1&& guid_, T2&& devid_)
- : name{std::forward<T0>(name_)}
- , endpoint_guid{std::forward<T1>(guid_)}
- , devid{std::forward<T2>(devid_)}
- { }
- /* To prevent GCC from complaining it doesn't want to inline this. */
- ~DevMap();
- };
- DevMap::~DevMap() = default;
- bool checkName(const al::span<DevMap> list, const std::string_view name)
- {
- auto match_name = [name](const DevMap &entry) -> bool { return entry.name == name; };
- return std::find_if(list.cbegin(), list.cend(), match_name) != list.cend();
- }
- struct DeviceList {
- auto lock() noexcept(noexcept(mMutex.lock())) { return mMutex.lock(); }
- auto unlock() noexcept(noexcept(mMutex.unlock())) { return mMutex.unlock(); }
- private:
- std::mutex mMutex;
- std::vector<DevMap> mPlayback;
- std::vector<DevMap> mCapture;
- std::wstring mPlaybackDefaultId;
- std::wstring mCaptureDefaultId;
- friend struct DeviceListLock;
- };
- struct DeviceListLock : public std::unique_lock<DeviceList> {
- using std::unique_lock<DeviceList>::unique_lock;
- [[nodiscard]] auto& getPlaybackList() const noexcept { return mutex()->mPlayback; }
- [[nodiscard]] auto& getCaptureList() const noexcept { return mutex()->mCapture; }
- void setPlaybackDefaultId(std::wstring_view devid) const { mutex()->mPlaybackDefaultId = devid; }
- [[nodiscard]] auto getPlaybackDefaultId() const noexcept -> std::wstring_view { return mutex()->mPlaybackDefaultId; }
- void setCaptureDefaultId(std::wstring_view devid) const { mutex()->mCaptureDefaultId = devid; }
- [[nodiscard]] auto getCaptureDefaultId() const noexcept -> std::wstring_view { return mutex()->mCaptureDefaultId; }
- };
- DeviceList gDeviceList;
- #if defined(ALSOFT_UWP)
- enum EDataFlow {
- eRender = 0,
- eCapture = (eRender + 1),
- eAll = (eCapture + 1),
- EDataFlow_enum_count = (eAll + 1)
- };
- #endif
- #if defined(ALSOFT_UWP)
- using DeviceHandle = Windows::Devices::Enumeration::DeviceInformation;
- using EventRegistrationToken = winrt::event_token;
- #else
- using DeviceHandle = ComPtr<IMMDevice>;
- #endif
- using NameGUIDPair = std::pair<std::string,std::string>;
- NameGUIDPair GetDeviceNameAndGuid(const DeviceHandle &device)
- {
- constexpr auto UnknownName = "Unknown Device Name"sv;
- constexpr auto UnknownGuid = "Unknown Device GUID"sv;
- #if !defined(ALSOFT_UWP)
- std::string name, guid;
- ComPtr<IPropertyStore> ps;
- HRESULT hr{device->OpenPropertyStore(STGM_READ, al::out_ptr(ps))};
- if(FAILED(hr))
- {
- WARN("OpenPropertyStore failed: 0x%08lx\n", hr);
- return {std::string{UnknownName}, std::string{UnknownGuid}};
- }
- PropVariant pvprop;
- hr = ps->GetValue(al::bit_cast<PROPERTYKEY>(DEVPKEY_Device_FriendlyName), pvprop.get());
- if(FAILED(hr))
- WARN("GetValue Device_FriendlyName failed: 0x%08lx\n", hr);
- else if(pvprop.type() == VT_LPWSTR)
- name = wstr_to_utf8(pvprop.value<std::wstring_view>());
- else
- WARN("Unexpected Device_FriendlyName PROPVARIANT type: 0x%04x\n", pvprop.type());
- pvprop.clear();
- hr = ps->GetValue(al::bit_cast<PROPERTYKEY>(PKEY_AudioEndpoint_GUID), pvprop.get());
- if(FAILED(hr))
- WARN("GetValue AudioEndpoint_GUID failed: 0x%08lx\n", hr);
- else if(pvprop.type() == VT_LPWSTR)
- guid = wstr_to_utf8(pvprop.value<std::wstring_view>());
- else
- WARN("Unexpected AudioEndpoint_GUID PROPVARIANT type: 0x%04x\n", pvprop.type());
- #else
- std::string name{wstr_to_utf8(device.Name())};
- std::string guid;
- // device->Id is DeviceInterfacePath: \\?\SWD#MMDEVAPI#{0.0.0.00000000}.{a21c17a0-fc1d-405e-ab5a-b513422b57d1}#{e6327cad-dcec-4949-ae8a-991e976a79d2}
- auto devIfPath = device.Id();
- if(auto devIdStart = wcsstr(devIfPath.data(), L"}."))
- {
- devIdStart += 2; // L"}."
- if(auto devIdStartEnd = wcschr(devIdStart, L'#'))
- {
- std::wstring wDevId{devIdStart, static_cast<size_t>(devIdStartEnd - devIdStart)};
- guid = wstr_to_utf8(wDevId.c_str());
- std::transform(guid.begin(), guid.end(), guid.begin(),
- [](char ch) { return static_cast<char>(std::toupper(ch)); });
- }
- }
- #endif
- if(name.empty()) name = UnknownName;
- if(guid.empty()) guid = UnknownGuid;
- return {std::move(name), std::move(guid)};
- }
- #if !defined(ALSOFT_UWP)
- EndpointFormFactor GetDeviceFormfactor(IMMDevice *device)
- {
- ComPtr<IPropertyStore> ps;
- HRESULT hr{device->OpenPropertyStore(STGM_READ, al::out_ptr(ps))};
- if(FAILED(hr))
- {
- WARN("OpenPropertyStore failed: 0x%08lx\n", hr);
- return UnknownFormFactor;
- }
- EndpointFormFactor formfactor{UnknownFormFactor};
- PropVariant pvform;
- hr = ps->GetValue(PKEY_AudioEndpoint_FormFactor, pvform.get());
- if(FAILED(hr))
- WARN("GetValue AudioEndpoint_FormFactor failed: 0x%08lx\n", hr);
- else if(pvform.type() == VT_UI4)
- formfactor = static_cast<EndpointFormFactor>(pvform.value<uint>());
- else if(pvform.type() != VT_EMPTY)
- WARN("Unexpected PROPVARIANT type: 0x%04x\n", pvform.type());
- return formfactor;
- }
- #endif
- #if defined(ALSOFT_UWP)
- struct DeviceHelper final : public IActivateAudioInterfaceCompletionHandler
- #else
- struct DeviceHelper final : private IMMNotificationClient
- #endif
- {
- #if defined(ALSOFT_UWP)
- DeviceHelper()
- {
- /* TODO: UWP also needs to watch for device added/removed events and
- * dynamically add/remove devices from the lists.
- */
- mActiveClientEvent = CreateEventW(nullptr, FALSE, FALSE, nullptr);
- mRenderDeviceChangedToken = MediaDevice::DefaultAudioRenderDeviceChanged([this](const IInspectable& /*sender*/, const DefaultAudioRenderDeviceChangedEventArgs& args) {
- if (args.Role() == AudioDeviceRole::Default)
- {
- const std::string msg{ "Default playback device changed: " +
- wstr_to_utf8(args.Id())};
- alc::Event(alc::EventType::DefaultDeviceChanged, alc::DeviceType::Playback,
- msg);
- }
- });
- mCaptureDeviceChangedToken = MediaDevice::DefaultAudioCaptureDeviceChanged([this](const IInspectable& /*sender*/, const DefaultAudioCaptureDeviceChangedEventArgs& args) {
- if (args.Role() == AudioDeviceRole::Default)
- {
- const std::string msg{ "Default capture device changed: " +
- wstr_to_utf8(args.Id()) };
- alc::Event(alc::EventType::DefaultDeviceChanged, alc::DeviceType::Capture,
- msg);
- }
- });
- }
- #else
- DeviceHelper() = default;
- #endif
- ~DeviceHelper()
- {
- #if defined(ALSOFT_UWP)
- MediaDevice::DefaultAudioRenderDeviceChanged(mRenderDeviceChangedToken);
- MediaDevice::DefaultAudioCaptureDeviceChanged(mCaptureDeviceChangedToken);
- if(mActiveClientEvent != nullptr)
- CloseHandle(mActiveClientEvent);
- mActiveClientEvent = nullptr;
- #else
- if(mEnumerator)
- mEnumerator->UnregisterEndpointNotificationCallback(this);
- mEnumerator = nullptr;
- #endif
- }
- /** -------------------------- IUnknown ----------------------------- */
- std::atomic<ULONG> mRefCount{1};
- STDMETHODIMP_(ULONG) AddRef() noexcept override { return mRefCount.fetch_add(1u) + 1u; }
- STDMETHODIMP_(ULONG) Release() noexcept override { return mRefCount.fetch_sub(1u) - 1u; }
- STDMETHODIMP QueryInterface(const IID& IId, void **UnknownPtrPtr) noexcept override
- {
- // Three rules of QueryInterface:
- // https://docs.microsoft.com/en-us/windows/win32/com/rules-for-implementing-queryinterface
- // 1. Objects must have identity.
- // 2. The set of interfaces on an object instance must be static.
- // 3. It must be possible to query successfully for any interface on an object from any other interface.
- // If ppvObject(the address) is nullptr, then this method returns E_POINTER.
- if(!UnknownPtrPtr)
- return E_POINTER;
- // https://docs.microsoft.com/en-us/windows/win32/com/implementing-reference-counting
- // Whenever a client calls a method(or API function), such as QueryInterface, that returns a new interface
- // pointer, the method being called is responsible for incrementing the reference count through the returned
- // pointer. For example, when a client first creates an object, it receives an interface pointer to an object
- // that, from the client's point of view, has a reference count of one. If the client then calls AddRef on the
- // interface pointer, the reference count becomes two. The client must call Release twice on the interface
- // pointer to drop all of its references to the object.
- #if defined(ALSOFT_UWP)
- if(IId == __uuidof(IActivateAudioInterfaceCompletionHandler))
- {
- *UnknownPtrPtr = static_cast<IActivateAudioInterfaceCompletionHandler*>(this);
- AddRef();
- return S_OK;
- }
- #else
- if(IId == __uuidof(IMMNotificationClient))
- {
- *UnknownPtrPtr = static_cast<IMMNotificationClient*>(this);
- AddRef();
- return S_OK;
- }
- #endif
- else if(IId == __uuidof(IAgileObject) || IId == __uuidof(IUnknown))
- {
- *UnknownPtrPtr = static_cast<IUnknown*>(this);
- AddRef();
- return S_OK;
- }
- // This method returns S_OK if the interface is supported, and E_NOINTERFACE otherwise.
- *UnknownPtrPtr = nullptr;
- return E_NOINTERFACE;
- }
- #if defined(ALSOFT_UWP)
- /** ----------------------- IActivateAudioInterfaceCompletionHandler ------------ */
- HRESULT ActivateCompleted(IActivateAudioInterfaceAsyncOperation*) override
- {
- SetEvent(mActiveClientEvent);
- // Need to return S_OK
- return S_OK;
- }
- #else
- /** ----------------------- IMMNotificationClient ------------ */
- STDMETHODIMP OnDeviceStateChanged(LPCWSTR /*pwstrDeviceId*/, DWORD /*dwNewState*/) noexcept override { return S_OK; }
- STDMETHODIMP OnDeviceAdded(LPCWSTR pwstrDeviceId) noexcept override
- {
- ComPtr<IMMDevice> device;
- HRESULT hr{mEnumerator->GetDevice(pwstrDeviceId, al::out_ptr(device))};
- if(FAILED(hr))
- {
- ERR("Failed to get device: 0x%08lx\n", hr);
- return S_OK;
- }
- ComPtr<IMMEndpoint> endpoint;
- hr = device->QueryInterface(__uuidof(IMMEndpoint), al::out_ptr(endpoint));
- if(FAILED(hr))
- {
- ERR("Failed to get device endpoint: 0x%08lx\n", hr);
- return S_OK;
- }
- EDataFlow flowdir{};
- hr = endpoint->GetDataFlow(&flowdir);
- if(FAILED(hr))
- {
- ERR("Failed to get endpoint data flow: 0x%08lx\n", hr);
- return S_OK;
- }
- auto devlock = DeviceListLock{gDeviceList};
- auto &list = (flowdir==eRender) ? devlock.getPlaybackList() : devlock.getCaptureList();
- if(AddDevice(device, pwstrDeviceId, list))
- {
- const auto devtype = (flowdir==eRender) ? alc::DeviceType::Playback
- : alc::DeviceType::Capture;
- const std::string msg{"Device added: "+list.back().name};
- alc::Event(alc::EventType::DeviceAdded, devtype, msg);
- }
- return S_OK;
- }
- STDMETHODIMP OnDeviceRemoved(LPCWSTR pwstrDeviceId) noexcept override
- {
- auto devlock = DeviceListLock{gDeviceList};
- for(auto flowdir : std::array{eRender, eCapture})
- {
- auto &list = (flowdir==eRender) ? devlock.getPlaybackList() : devlock.getCaptureList();
- auto devtype = (flowdir==eRender)?alc::DeviceType::Playback : alc::DeviceType::Capture;
- /* Find the ID in the list to remove. */
- auto iter = std::find_if(list.begin(), list.end(),
- [pwstrDeviceId](const DevMap &entry) noexcept
- { return pwstrDeviceId == entry.devid; });
- if(iter == list.end()) continue;
- TRACE("Removing device \"%s\", \"%s\", \"%ls\"\n", iter->name.c_str(),
- iter->endpoint_guid.c_str(), iter->devid.c_str());
- std::string msg{"Device removed: "+std::move(iter->name)};
- list.erase(iter);
- alc::Event(alc::EventType::DeviceRemoved, devtype, msg);
- }
- return S_OK;
- }
- STDMETHODIMP OnPropertyValueChanged(LPCWSTR /*pwstrDeviceId*/, const PROPERTYKEY /*key*/) noexcept override { return S_OK; }
- STDMETHODIMP OnDefaultDeviceChanged(EDataFlow flow, ERole role, LPCWSTR pwstrDefaultDeviceId) noexcept override
- {
- if(role != eMultimedia)
- return S_OK;
- const std::wstring_view devid{pwstrDefaultDeviceId ? pwstrDefaultDeviceId
- : std::wstring_view{}};
- if(flow == eRender)
- {
- DeviceListLock{gDeviceList}.setPlaybackDefaultId(devid);
- const std::string msg{"Default playback device changed: " + wstr_to_utf8(devid)};
- alc::Event(alc::EventType::DefaultDeviceChanged, alc::DeviceType::Playback, msg);
- }
- else if(flow == eCapture)
- {
- DeviceListLock{gDeviceList}.setCaptureDefaultId(devid);
- const std::string msg{"Default capture device changed: " + wstr_to_utf8(devid)};
- alc::Event(alc::EventType::DefaultDeviceChanged, alc::DeviceType::Capture, msg);
- }
- return S_OK;
- }
- #endif
- /** -------------------------- DeviceHelper ----------------------------- */
- HRESULT init()
- {
- #if !defined(ALSOFT_UWP)
- HRESULT hr{CoCreateInstance(CLSID_MMDeviceEnumerator, nullptr, CLSCTX_INPROC_SERVER,
- __uuidof(IMMDeviceEnumerator), al::out_ptr(mEnumerator))};
- if(SUCCEEDED(hr))
- mEnumerator->RegisterEndpointNotificationCallback(this);
- else
- WARN("Failed to create IMMDeviceEnumerator instance: 0x%08lx\n", hr);
- return hr;
- #else
- return S_OK;
- #endif
- }
- HRESULT openDevice(std::wstring_view devid, EDataFlow flow, DeviceHandle& device)
- {
- #if !defined(ALSOFT_UWP)
- HRESULT hr{E_FAIL};
- if(mEnumerator)
- {
- if(devid.empty())
- hr = mEnumerator->GetDefaultAudioEndpoint(flow, eMultimedia, al::out_ptr(device));
- else
- hr = mEnumerator->GetDevice(devid.data(), al::out_ptr(device));
- }
- return hr;
- #else
- const auto deviceRole = Windows::Media::Devices::AudioDeviceRole::Default;
- auto devIfPath =
- devid.empty() ? (flow == eRender ? MediaDevice::GetDefaultAudioRenderId(deviceRole) : MediaDevice::GetDefaultAudioCaptureId(deviceRole))
- : winrt::hstring(devid.data());
- if (devIfPath.empty())
- return E_POINTER;
- auto&& deviceInfo = DeviceInformation::CreateFromIdAsync(devIfPath, nullptr, DeviceInformationKind::DeviceInterface).get();
- if (!deviceInfo)
- return E_NOINTERFACE;
- device = deviceInfo;
- return S_OK;
- #endif
- }
- #if !defined(ALSOFT_UWP)
- static HRESULT activateAudioClient(_In_ DeviceHandle &device, REFIID iid, void **ppv)
- { return device->Activate(iid, CLSCTX_INPROC_SERVER, nullptr, ppv); }
- #else
- HRESULT activateAudioClient(_In_ DeviceHandle &device, _In_ REFIID iid, void **ppv)
- {
- ComPtr<IActivateAudioInterfaceAsyncOperation> asyncOp;
- HRESULT hr{ActivateAudioInterfaceAsync(device.Id().data(), iid, nullptr, this,
- al::out_ptr(asyncOp))};
- if(FAILED(hr))
- return hr;
- /* I don't like waiting for INFINITE time, but the activate operation
- * can take an indefinite amount of time since it can require user
- * input.
- */
- DWORD res{WaitForSingleObjectEx(mActiveClientEvent, INFINITE, FALSE)};
- if(res != WAIT_OBJECT_0)
- {
- ERR("WaitForSingleObjectEx error: 0x%lx\n", res);
- return E_FAIL;
- }
- HRESULT hrActivateRes{E_FAIL};
- ComPtr<IUnknown> punkAudioIface;
- hr = asyncOp->GetActivateResult(&hrActivateRes, al::out_ptr(punkAudioIface));
- if(SUCCEEDED(hr)) hr = hrActivateRes;
- if(FAILED(hr)) return hr;
- return punkAudioIface->QueryInterface(iid, ppv);
- }
- #endif
- std::wstring probeDevices(EDataFlow flowdir, std::vector<DevMap> &list)
- {
- std::wstring defaultId;
- std::vector<DevMap>{}.swap(list);
- #if !defined(ALSOFT_UWP)
- ComPtr<IMMDeviceCollection> coll;
- HRESULT hr{mEnumerator->EnumAudioEndpoints(flowdir, DEVICE_STATE_ACTIVE,
- al::out_ptr(coll))};
- if(FAILED(hr))
- {
- ERR("Failed to enumerate audio endpoints: 0x%08lx\n", hr);
- return defaultId;
- }
- UINT count{0};
- hr = coll->GetCount(&count);
- if(SUCCEEDED(hr) && count > 0)
- list.reserve(count);
- ComPtr<IMMDevice> device;
- hr = mEnumerator->GetDefaultAudioEndpoint(flowdir, eMultimedia, al::out_ptr(device));
- if(SUCCEEDED(hr))
- {
- if(WCHAR *devid{GetDeviceId(device.get())})
- {
- defaultId = devid;
- CoTaskMemFree(devid);
- }
- device = nullptr;
- }
- for(UINT i{0};i < count;++i)
- {
- hr = coll->Item(i, al::out_ptr(device));
- if(FAILED(hr))
- continue;
- if(WCHAR *devid{GetDeviceId(device.get())})
- {
- std::ignore = AddDevice(device, devid, list);
- CoTaskMemFree(devid);
- }
- device = nullptr;
- }
- #else
- const auto deviceRole = Windows::Media::Devices::AudioDeviceRole::Default;
- auto DefaultAudioId = flowdir == eRender ? MediaDevice::GetDefaultAudioRenderId(deviceRole)
- : MediaDevice::GetDefaultAudioCaptureId(deviceRole);
- if(!DefaultAudioId.empty())
- {
- auto deviceInfo = DeviceInformation::CreateFromIdAsync(DefaultAudioId, nullptr,
- DeviceInformationKind::DeviceInterface).get();
- if(deviceInfo)
- defaultId = deviceInfo.Id().data();
- }
- // Get the string identifier of the audio renderer
- auto AudioSelector = flowdir == eRender ? MediaDevice::GetAudioRenderSelector() : MediaDevice::GetAudioCaptureSelector();
- // Setup the asynchronous callback
- auto&& DeviceInfoCollection = DeviceInformation::FindAllAsync(AudioSelector, /*PropertyList*/nullptr, DeviceInformationKind::DeviceInterface).get();
- if(DeviceInfoCollection)
- {
- try {
- auto deviceCount = DeviceInfoCollection.Size();
- for(unsigned int i{0};i < deviceCount;++i)
- {
- auto deviceInfo = DeviceInfoCollection.GetAt(i);
- if(deviceInfo)
- std::ignore = AddDevice(deviceInfo, deviceInfo.Id().data(), list);
- }
- }
- catch (const winrt::hresult_error& /*ex*/) {
- }
- }
- #endif
- return defaultId;
- }
- private:
- static bool AddDevice(const DeviceHandle &device, const WCHAR *devid, std::vector<DevMap> &list)
- {
- for(auto &entry : list)
- {
- if(entry.devid == devid)
- return false;
- }
- auto name_guid = GetDeviceNameAndGuid(device);
- int count{1};
- std::string newname{name_guid.first};
- while(checkName(list, newname))
- {
- newname = name_guid.first;
- newname += " #";
- newname += std::to_string(++count);
- }
- list.emplace_back(std::move(newname), std::move(name_guid.second), devid);
- const DevMap &newentry = list.back();
- TRACE("Got device \"%s\", \"%s\", \"%ls\"\n", newentry.name.c_str(),
- newentry.endpoint_guid.c_str(), newentry.devid.c_str());
- return true;
- }
- #if !defined(ALSOFT_UWP)
- static WCHAR *GetDeviceId(IMMDevice *device)
- {
- WCHAR *devid;
- const HRESULT hr{device->GetId(&devid)};
- if(FAILED(hr))
- {
- ERR("Failed to get device id: %lx\n", hr);
- return nullptr;
- }
- return devid;
- }
- ComPtr<IMMDeviceEnumerator> mEnumerator{nullptr};
- #else
- HANDLE mActiveClientEvent{nullptr};
- EventRegistrationToken mRenderDeviceChangedToken;
- EventRegistrationToken mCaptureDeviceChangedToken;
- #endif
- };
- bool MakeExtensible(WAVEFORMATEXTENSIBLE *out, const WAVEFORMATEX *in)
- {
- *out = WAVEFORMATEXTENSIBLE{};
- if(in->wFormatTag == WAVE_FORMAT_EXTENSIBLE)
- {
- *out = *CONTAINING_RECORD(in, const WAVEFORMATEXTENSIBLE, Format);
- out->Format.cbSize = sizeof(*out) - sizeof(out->Format);
- }
- else if(in->wFormatTag == WAVE_FORMAT_PCM)
- {
- out->Format = *in;
- out->Format.cbSize = 0;
- /* NOLINTNEXTLINE(cppcoreguidelines-pro-type-union-access) */
- out->Samples.wValidBitsPerSample = out->Format.wBitsPerSample;
- if(out->Format.nChannels == 1)
- out->dwChannelMask = MONO;
- else if(out->Format.nChannels == 2)
- out->dwChannelMask = STEREO;
- else
- ERR("Unhandled PCM channel count: %d\n", out->Format.nChannels);
- out->SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
- }
- else if(in->wFormatTag == WAVE_FORMAT_IEEE_FLOAT)
- {
- out->Format = *in;
- out->Format.cbSize = 0;
- /* NOLINTNEXTLINE(cppcoreguidelines-pro-type-union-access) */
- out->Samples.wValidBitsPerSample = out->Format.wBitsPerSample;
- if(out->Format.nChannels == 1)
- out->dwChannelMask = MONO;
- else if(out->Format.nChannels == 2)
- out->dwChannelMask = STEREO;
- else
- ERR("Unhandled IEEE float channel count: %d\n", out->Format.nChannels);
- out->SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT;
- }
- else
- {
- ERR("Unhandled format tag: 0x%04x\n", in->wFormatTag);
- return false;
- }
- return true;
- }
- void TraceFormat(const char *msg, const WAVEFORMATEX *format)
- {
- constexpr size_t fmtex_extra_size{sizeof(WAVEFORMATEXTENSIBLE)-sizeof(WAVEFORMATEX)};
- if(format->wFormatTag == WAVE_FORMAT_EXTENSIBLE && format->cbSize >= fmtex_extra_size)
- {
- const WAVEFORMATEXTENSIBLE *fmtex{
- CONTAINING_RECORD(format, const WAVEFORMATEXTENSIBLE, Format)};
- /* NOLINTBEGIN(cppcoreguidelines-pro-type-union-access) */
- TRACE("%s:\n"
- " FormatTag = 0x%04x\n"
- " Channels = %d\n"
- " SamplesPerSec = %lu\n"
- " AvgBytesPerSec = %lu\n"
- " BlockAlign = %d\n"
- " BitsPerSample = %d\n"
- " Size = %d\n"
- " Samples = %d\n"
- " ChannelMask = 0x%lx\n"
- " SubFormat = %s\n",
- msg, fmtex->Format.wFormatTag, fmtex->Format.nChannels, fmtex->Format.nSamplesPerSec,
- fmtex->Format.nAvgBytesPerSec, fmtex->Format.nBlockAlign, fmtex->Format.wBitsPerSample,
- fmtex->Format.cbSize, fmtex->Samples.wReserved, fmtex->dwChannelMask,
- GuidPrinter{fmtex->SubFormat}.c_str());
- /* NOLINTEND(cppcoreguidelines-pro-type-union-access) */
- }
- else
- TRACE("%s:\n"
- " FormatTag = 0x%04x\n"
- " Channels = %d\n"
- " SamplesPerSec = %lu\n"
- " AvgBytesPerSec = %lu\n"
- " BlockAlign = %d\n"
- " BitsPerSample = %d\n"
- " Size = %d\n",
- msg, format->wFormatTag, format->nChannels, format->nSamplesPerSec,
- format->nAvgBytesPerSec, format->nBlockAlign, format->wBitsPerSample, format->cbSize);
- }
- enum class MsgType {
- OpenDevice,
- ResetDevice,
- StartDevice,
- StopDevice,
- CloseDevice,
- QuitThread
- };
- constexpr const char *GetMessageTypeName(MsgType type) noexcept
- {
- switch(type)
- {
- case MsgType::OpenDevice: return "Open Device";
- case MsgType::ResetDevice: return "Reset Device";
- case MsgType::StartDevice: return "Start Device";
- case MsgType::StopDevice: return "Stop Device";
- case MsgType::CloseDevice: return "Close Device";
- case MsgType::QuitThread: break;
- }
- return "";
- }
- /* Proxy interface used by the message handler. */
- struct WasapiProxy {
- WasapiProxy() = default;
- WasapiProxy(const WasapiProxy&) = delete;
- WasapiProxy(WasapiProxy&&) = delete;
- virtual ~WasapiProxy() = default;
- void operator=(const WasapiProxy&) = delete;
- void operator=(WasapiProxy&&) = delete;
- virtual HRESULT openProxy(std::string_view name) = 0;
- virtual void closeProxy() = 0;
- virtual HRESULT resetProxy() = 0;
- virtual HRESULT startProxy() = 0;
- virtual void stopProxy() = 0;
- struct Msg {
- MsgType mType;
- WasapiProxy *mProxy;
- std::string_view mParam;
- std::promise<HRESULT> mPromise;
- explicit operator bool() const noexcept { return mType != MsgType::QuitThread; }
- };
- static inline std::deque<Msg> mMsgQueue;
- static inline std::mutex mMsgQueueLock;
- static inline std::condition_variable mMsgQueueCond;
- static inline std::optional<DeviceHelper> sDeviceHelper;
- std::future<HRESULT> pushMessage(MsgType type, std::string_view param={})
- {
- std::promise<HRESULT> promise;
- std::future<HRESULT> future{promise.get_future()};
- {
- std::lock_guard<std::mutex> msglock{mMsgQueueLock};
- mMsgQueue.emplace_back(Msg{type, this, param, std::move(promise)});
- }
- mMsgQueueCond.notify_one();
- return future;
- }
- static std::future<HRESULT> pushMessageStatic(MsgType type)
- {
- std::promise<HRESULT> promise;
- std::future<HRESULT> future{promise.get_future()};
- {
- std::lock_guard<std::mutex> msglock{mMsgQueueLock};
- mMsgQueue.emplace_back(Msg{type, nullptr, {}, std::move(promise)});
- }
- mMsgQueueCond.notify_one();
- return future;
- }
- static Msg popMessage()
- {
- std::unique_lock<std::mutex> lock{mMsgQueueLock};
- mMsgQueueCond.wait(lock, []{return !mMsgQueue.empty();});
- Msg msg{std::move(mMsgQueue.front())};
- mMsgQueue.pop_front();
- return msg;
- }
- static int messageHandler(std::promise<HRESULT> *promise);
- };
- int WasapiProxy::messageHandler(std::promise<HRESULT> *promise)
- {
- TRACE("Starting message thread\n");
- ComWrapper com{COINIT_MULTITHREADED};
- if(!com)
- {
- WARN("Failed to initialize COM: 0x%08lx\n", com.status());
- promise->set_value(com.status());
- return 0;
- }
- struct HelperResetter {
- ~HelperResetter() { sDeviceHelper.reset(); }
- };
- HelperResetter scoped_watcher;
- HRESULT hr{sDeviceHelper.emplace().init()};
- promise->set_value(hr);
- promise = nullptr;
- if(FAILED(hr))
- return 0;
- {
- auto devlock = DeviceListLock{gDeviceList};
- auto defaultId = sDeviceHelper->probeDevices(eRender, devlock.getPlaybackList());
- if(!defaultId.empty()) devlock.setPlaybackDefaultId(defaultId);
- defaultId = sDeviceHelper->probeDevices(eCapture, devlock.getCaptureList());
- if(!defaultId.empty()) devlock.setCaptureDefaultId(defaultId);
- }
- TRACE("Starting message loop\n");
- while(Msg msg{popMessage()})
- {
- TRACE("Got message \"%s\" (0x%04x, this=%p, param=\"%.*s\")\n",
- GetMessageTypeName(msg.mType), static_cast<uint>(msg.mType),
- static_cast<void*>(msg.mProxy), al::sizei(msg.mParam), msg.mParam.data());
- switch(msg.mType)
- {
- case MsgType::OpenDevice:
- hr = msg.mProxy->openProxy(msg.mParam);
- msg.mPromise.set_value(hr);
- continue;
- case MsgType::ResetDevice:
- hr = msg.mProxy->resetProxy();
- msg.mPromise.set_value(hr);
- continue;
- case MsgType::StartDevice:
- hr = msg.mProxy->startProxy();
- msg.mPromise.set_value(hr);
- continue;
- case MsgType::StopDevice:
- msg.mProxy->stopProxy();
- msg.mPromise.set_value(S_OK);
- continue;
- case MsgType::CloseDevice:
- msg.mProxy->closeProxy();
- msg.mPromise.set_value(S_OK);
- continue;
- case MsgType::QuitThread:
- break;
- }
- ERR("Unexpected message: %u\n", static_cast<uint>(msg.mType));
- msg.mPromise.set_value(E_FAIL);
- }
- TRACE("Message loop finished\n");
- return 0;
- }
- struct WasapiPlayback final : public BackendBase, WasapiProxy {
- WasapiPlayback(DeviceBase *device) noexcept : BackendBase{device} { }
- ~WasapiPlayback() override;
- int mixerProc();
- int mixerSpatialProc();
- void open(std::string_view name) override;
- HRESULT openProxy(std::string_view name) override;
- void closeProxy() override;
- bool reset() override;
- HRESULT resetProxy() override;
- void start() override;
- HRESULT startProxy() override;
- void stop() override;
- void stopProxy() override;
- ClockLatency getClockLatency() override;
- void prepareFormat(WAVEFORMATEXTENSIBLE &OutputType);
- void finalizeFormat(WAVEFORMATEXTENSIBLE &OutputType);
- auto initSpatial() -> bool;
- HRESULT mOpenStatus{E_FAIL};
- DeviceHandle mMMDev{nullptr};
- struct PlainDevice {
- ComPtr<IAudioClient> mClient{nullptr};
- ComPtr<IAudioRenderClient> mRender{nullptr};
- };
- struct SpatialDevice {
- ComPtr<ISpatialAudioClient> mClient{nullptr};
- ComPtr<ISpatialAudioObjectRenderStream> mRender{nullptr};
- AudioObjectType mStaticMask{};
- };
- std::variant<std::monostate,PlainDevice,SpatialDevice> mAudio;
- HANDLE mNotifyEvent{nullptr};
- UINT32 mOrigBufferSize{}, mOrigUpdateSize{};
- std::vector<char> mResampleBuffer{};
- uint mBufferFilled{0};
- SampleConverterPtr mResampler;
- WAVEFORMATEXTENSIBLE mFormat{};
- std::atomic<UINT32> mPadding{0u};
- std::mutex mMutex;
- std::atomic<bool> mKillNow{true};
- std::thread mThread;
- };
- WasapiPlayback::~WasapiPlayback()
- {
- if(SUCCEEDED(mOpenStatus))
- pushMessage(MsgType::CloseDevice).wait();
- mOpenStatus = E_FAIL;
- if(mNotifyEvent != nullptr)
- CloseHandle(mNotifyEvent);
- mNotifyEvent = nullptr;
- }
- FORCE_ALIGN int WasapiPlayback::mixerProc()
- {
- ComWrapper com{COINIT_MULTITHREADED};
- if(!com)
- {
- ERR("CoInitializeEx(nullptr, COINIT_MULTITHREADED) failed: 0x%08lx\n", com.status());
- mDevice->handleDisconnect("COM init failed: 0x%08lx", com.status());
- return 1;
- }
- auto &audio = std::get<PlainDevice>(mAudio);
- SetRTPriority();
- althrd_setname(GetMixerThreadName());
- const uint frame_size{mFormat.Format.nChannels * mFormat.Format.wBitsPerSample / 8u};
- const uint update_size{mOrigUpdateSize};
- const UINT32 buffer_len{mOrigBufferSize};
- const void *resbufferptr{};
- mBufferFilled = 0;
- while(!mKillNow.load(std::memory_order_relaxed))
- {
- UINT32 written;
- HRESULT hr{audio.mClient->GetCurrentPadding(&written)};
- if(FAILED(hr))
- {
- ERR("Failed to get padding: 0x%08lx\n", hr);
- mDevice->handleDisconnect("Failed to retrieve buffer padding: 0x%08lx", hr);
- break;
- }
- mPadding.store(written, std::memory_order_relaxed);
- uint len{buffer_len - written};
- if(len < update_size)
- {
- DWORD res{WaitForSingleObjectEx(mNotifyEvent, 2000, FALSE)};
- if(res != WAIT_OBJECT_0)
- ERR("WaitForSingleObjectEx error: 0x%lx\n", res);
- continue;
- }
- BYTE *buffer;
- hr = audio.mRender->GetBuffer(len, &buffer);
- if(SUCCEEDED(hr))
- {
- if(mResampler)
- {
- std::lock_guard<std::mutex> dlock{mMutex};
- auto dst = al::span{buffer, size_t{len}*frame_size};
- for(UINT32 done{0};done < len;)
- {
- if(mBufferFilled == 0)
- {
- mDevice->renderSamples(mResampleBuffer.data(), mDevice->UpdateSize,
- mFormat.Format.nChannels);
- resbufferptr = mResampleBuffer.data();
- mBufferFilled = mDevice->UpdateSize;
- }
- uint got{mResampler->convert(&resbufferptr, &mBufferFilled, dst.data(),
- len-done)};
- dst = dst.subspan(size_t{got}*frame_size);
- done += got;
- mPadding.store(written + done, std::memory_order_relaxed);
- }
- }
- else
- {
- std::lock_guard<std::mutex> dlock{mMutex};
- mDevice->renderSamples(buffer, len, mFormat.Format.nChannels);
- mPadding.store(written + len, std::memory_order_relaxed);
- }
- hr = audio.mRender->ReleaseBuffer(len, 0);
- }
- if(FAILED(hr))
- {
- ERR("Failed to buffer data: 0x%08lx\n", hr);
- mDevice->handleDisconnect("Failed to send playback samples: 0x%08lx", hr);
- break;
- }
- }
- mPadding.store(0u, std::memory_order_release);
- return 0;
- }
- FORCE_ALIGN int WasapiPlayback::mixerSpatialProc()
- {
- ComWrapper com{COINIT_MULTITHREADED};
- if(!com)
- {
- ERR("CoInitializeEx(nullptr, COINIT_MULTITHREADED) failed: 0x%08lx\n", com.status());
- mDevice->handleDisconnect("COM init failed: 0x%08lx", com.status());
- return 1;
- }
- auto &audio = std::get<SpatialDevice>(mAudio);
- SetRTPriority();
- althrd_setname(GetMixerThreadName());
- std::vector<ComPtr<ISpatialAudioObject>> channels;
- std::vector<float*> buffers;
- std::vector<float*> resbuffers;
- std::vector<const void*> tmpbuffers;
- /* TODO: Set mPadding appropriately. There doesn't seem to be a way to
- * update it dynamically based on the stream, so a fixed size may be the
- * best we can do.
- */
- mPadding.store(mOrigBufferSize-mOrigUpdateSize, std::memory_order_release);
- mBufferFilled = 0;
- while(!mKillNow.load(std::memory_order_relaxed))
- {
- if(DWORD res{WaitForSingleObjectEx(mNotifyEvent, 1000, FALSE)}; res != WAIT_OBJECT_0)
- {
- ERR("WaitForSingleObjectEx error: 0x%lx\n", res);
- HRESULT hr{audio.mRender->Reset()};
- if(FAILED(hr))
- {
- ERR("ISpatialAudioObjectRenderStream::Reset failed: 0x%08lx\n", hr);
- mDevice->handleDisconnect("Device lost: 0x%08lx", hr);
- break;
- }
- }
- UINT32 dynamicCount{}, framesToDo{};
- HRESULT hr{audio.mRender->BeginUpdatingAudioObjects(&dynamicCount, &framesToDo)};
- if(SUCCEEDED(hr))
- {
- if(channels.empty()) UNLIKELY
- {
- auto flags = as_unsigned(audio.mStaticMask);
- channels.reserve(as_unsigned(al::popcount(flags)));
- while(flags)
- {
- auto id = decltype(flags){1} << al::countr_zero(flags);
- flags &= ~id;
- channels.emplace_back();
- audio.mRender->ActivateSpatialAudioObject(static_cast<AudioObjectType>(id),
- al::out_ptr(channels.back()));
- }
- buffers.resize(channels.size());
- if(mResampler)
- {
- tmpbuffers.resize(buffers.size());
- resbuffers.resize(buffers.size());
- auto bufptr = mResampleBuffer.begin();
- for(size_t i{0};i < tmpbuffers.size();++i)
- {
- resbuffers[i] = reinterpret_cast<float*>(al::to_address(bufptr));
- bufptr += ptrdiff_t(mDevice->UpdateSize*sizeof(float));
- }
- }
- }
- /* We have to call to get each channel's buffer individually every
- * update, unfortunately.
- */
- std::transform(channels.cbegin(), channels.cend(), buffers.begin(),
- [](const ComPtr<ISpatialAudioObject> &obj) -> float*
- {
- BYTE *buffer{};
- UINT32 size{};
- obj->GetBuffer(&buffer, &size);
- return reinterpret_cast<float*>(buffer);
- });
- if(!mResampler)
- mDevice->renderSamples(buffers, framesToDo);
- else
- {
- std::lock_guard<std::mutex> dlock{mMutex};
- for(UINT32 pos{0};pos < framesToDo;)
- {
- if(mBufferFilled == 0)
- {
- mDevice->renderSamples(resbuffers, mDevice->UpdateSize);
- std::copy(resbuffers.cbegin(), resbuffers.cend(), tmpbuffers.begin());
- mBufferFilled = mDevice->UpdateSize;
- }
- const uint got{mResampler->convertPlanar(tmpbuffers.data(), &mBufferFilled,
- reinterpret_cast<void*const*>(buffers.data()), framesToDo-pos)};
- for(auto &buf : buffers)
- buf += got; /* NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic) */
- pos += got;
- }
- }
- hr = audio.mRender->EndUpdatingAudioObjects();
- }
- if(FAILED(hr))
- ERR("Failed to update playback objects: 0x%08lx\n", hr);
- }
- mPadding.store(0u, std::memory_order_release);
- return 0;
- }
- void WasapiPlayback::open(std::string_view name)
- {
- if(SUCCEEDED(mOpenStatus))
- throw al::backend_exception{al::backend_error::DeviceError,
- "Unexpected duplicate open call"};
- mNotifyEvent = CreateEventW(nullptr, FALSE, FALSE, nullptr);
- if(mNotifyEvent == nullptr)
- {
- ERR("Failed to create notify events: %lu\n", GetLastError());
- throw al::backend_exception{al::backend_error::DeviceError,
- "Failed to create notify events"};
- }
- if(const auto prefix = GetDevicePrefix(); al::starts_with(name, prefix))
- name = name.substr(prefix.size());
- mOpenStatus = pushMessage(MsgType::OpenDevice, name).get();
- if(FAILED(mOpenStatus))
- throw al::backend_exception{al::backend_error::DeviceError, "Device init failed: 0x%08lx",
- mOpenStatus};
- }
- HRESULT WasapiPlayback::openProxy(std::string_view name)
- {
- std::string devname;
- std::wstring devid;
- if(!name.empty())
- {
- auto devlock = DeviceListLock{gDeviceList};
- auto list = al::span{devlock.getPlaybackList()};
- auto iter = std::find_if(list.cbegin(), list.cend(),
- [name](const DevMap &entry) -> bool
- { return entry.name == name || entry.endpoint_guid == name; });
- if(iter == list.cend())
- {
- const std::wstring wname{utf8_to_wstr(name)};
- iter = std::find_if(list.cbegin(), list.cend(),
- [&wname](const DevMap &entry) -> bool
- { return entry.devid == wname; });
- }
- if(iter == list.cend())
- {
- WARN("Failed to find device name matching \"%.*s\"\n", al::sizei(name), name.data());
- return E_FAIL;
- }
- devname = iter->name;
- devid = iter->devid;
- }
- HRESULT hr{sDeviceHelper->openDevice(devid, eRender, mMMDev)};
- if(FAILED(hr))
- {
- WARN("Failed to open device \"%s\"\n", devname.empty() ? "(default)" : devname.c_str());
- return hr;
- }
- if(!devname.empty())
- mDevice->DeviceName = std::string{GetDevicePrefix()}+std::move(devname);
- else
- mDevice->DeviceName = std::string{GetDevicePrefix()}+GetDeviceNameAndGuid(mMMDev).first;
- return S_OK;
- }
- void WasapiPlayback::closeProxy()
- {
- mAudio.emplace<std::monostate>();
- mMMDev = nullptr;
- }
- void WasapiPlayback::prepareFormat(WAVEFORMATEXTENSIBLE &OutputType)
- {
- bool isRear51{false};
- if(!mDevice->Flags.test(FrequencyRequest))
- mDevice->Frequency = OutputType.Format.nSamplesPerSec;
- if(!mDevice->Flags.test(ChannelsRequest))
- {
- /* If not requesting a channel configuration, auto-select given what
- * fits the mask's lsb (to ensure no gaps in the output channels). If
- * there's no mask, we can only assume mono or stereo.
- */
- const uint32_t chancount{OutputType.Format.nChannels};
- const DWORD chanmask{OutputType.dwChannelMask};
- if(chancount >= 12 && (chanmask&X714Mask) == X7DOT1DOT4)
- mDevice->FmtChans = DevFmtX714;
- else if(chancount >= 8 && (chanmask&X71Mask) == X7DOT1)
- mDevice->FmtChans = DevFmtX71;
- else if(chancount >= 7 && (chanmask&X61Mask) == X6DOT1)
- mDevice->FmtChans = DevFmtX61;
- else if(chancount >= 6 && (chanmask&X51Mask) == X5DOT1)
- mDevice->FmtChans = DevFmtX51;
- else if(chancount >= 6 && (chanmask&X51RearMask) == X5DOT1REAR)
- {
- mDevice->FmtChans = DevFmtX51;
- isRear51 = true;
- }
- else if(chancount >= 4 && (chanmask&QuadMask) == QUAD)
- mDevice->FmtChans = DevFmtQuad;
- else if(chancount >= 2 && ((chanmask&StereoMask) == STEREO || !chanmask))
- mDevice->FmtChans = DevFmtStereo;
- else if(chancount >= 1 && ((chanmask&MonoMask) == MONO || !chanmask))
- mDevice->FmtChans = DevFmtMono;
- else
- ERR("Unhandled channel config: %d -- 0x%08lx\n", chancount, chanmask);
- }
- else
- {
- const uint32_t chancount{OutputType.Format.nChannels};
- const DWORD chanmask{OutputType.dwChannelMask};
- isRear51 = (chancount == 6 && (chanmask&X51RearMask) == X5DOT1REAR);
- }
- OutputType.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
- switch(mDevice->FmtChans)
- {
- case DevFmtMono:
- OutputType.Format.nChannels = 1;
- OutputType.dwChannelMask = MONO;
- break;
- case DevFmtAmbi3D:
- mDevice->FmtChans = DevFmtStereo;
- /*fall-through*/
- case DevFmtStereo:
- OutputType.Format.nChannels = 2;
- OutputType.dwChannelMask = STEREO;
- break;
- case DevFmtQuad:
- OutputType.Format.nChannels = 4;
- OutputType.dwChannelMask = QUAD;
- break;
- case DevFmtX51:
- OutputType.Format.nChannels = 6;
- OutputType.dwChannelMask = isRear51 ? X5DOT1REAR : X5DOT1;
- break;
- case DevFmtX61:
- OutputType.Format.nChannels = 7;
- OutputType.dwChannelMask = X6DOT1;
- break;
- case DevFmtX71:
- case DevFmtX3D71:
- OutputType.Format.nChannels = 8;
- OutputType.dwChannelMask = X7DOT1;
- break;
- case DevFmtX7144:
- mDevice->FmtChans = DevFmtX714;
- /*fall-through*/
- case DevFmtX714:
- OutputType.Format.nChannels = 12;
- OutputType.dwChannelMask = X7DOT1DOT4;
- break;
- }
- switch(mDevice->FmtType)
- {
- case DevFmtByte:
- mDevice->FmtType = DevFmtUByte;
- /* fall-through */
- case DevFmtUByte:
- OutputType.Format.wBitsPerSample = 8;
- OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
- break;
- case DevFmtUShort:
- mDevice->FmtType = DevFmtShort;
- /* fall-through */
- case DevFmtShort:
- OutputType.Format.wBitsPerSample = 16;
- OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
- break;
- case DevFmtUInt:
- mDevice->FmtType = DevFmtInt;
- /* fall-through */
- case DevFmtInt:
- OutputType.Format.wBitsPerSample = 32;
- OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
- break;
- case DevFmtFloat:
- OutputType.Format.wBitsPerSample = 32;
- OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT;
- break;
- }
- /* NOLINTNEXTLINE(cppcoreguidelines-pro-type-union-access) */
- OutputType.Samples.wValidBitsPerSample = OutputType.Format.wBitsPerSample;
- OutputType.Format.nSamplesPerSec = mDevice->Frequency;
- OutputType.Format.nBlockAlign = static_cast<WORD>(OutputType.Format.nChannels *
- OutputType.Format.wBitsPerSample / 8);
- OutputType.Format.nAvgBytesPerSec = OutputType.Format.nSamplesPerSec *
- OutputType.Format.nBlockAlign;
- }
- void WasapiPlayback::finalizeFormat(WAVEFORMATEXTENSIBLE &OutputType)
- {
- if(!GetConfigValueBool(mDevice->DeviceName, "wasapi", "allow-resampler", true))
- mDevice->Frequency = uint(OutputType.Format.nSamplesPerSec);
- else
- mDevice->Frequency = std::min(mDevice->Frequency, uint(OutputType.Format.nSamplesPerSec));
- const uint32_t chancount{OutputType.Format.nChannels};
- const DWORD chanmask{OutputType.dwChannelMask};
- /* Don't update the channel format if the requested format fits what's
- * supported.
- */
- bool chansok{false};
- if(mDevice->Flags.test(ChannelsRequest))
- {
- /* When requesting a channel configuration, make sure it fits the
- * mask's lsb (to ensure no gaps in the output channels). If there's no
- * mask, assume the request fits with enough channels.
- */
- switch(mDevice->FmtChans)
- {
- case DevFmtMono:
- chansok = (chancount >= 1 && ((chanmask&MonoMask) == MONO || !chanmask));
- break;
- case DevFmtStereo:
- chansok = (chancount >= 2 && ((chanmask&StereoMask) == STEREO || !chanmask));
- break;
- case DevFmtQuad:
- chansok = (chancount >= 4 && ((chanmask&QuadMask) == QUAD || !chanmask));
- break;
- case DevFmtX51:
- chansok = (chancount >= 6 && ((chanmask&X51Mask) == X5DOT1
- || (chanmask&X51RearMask) == X5DOT1REAR || !chanmask));
- break;
- case DevFmtX61:
- chansok = (chancount >= 7 && ((chanmask&X61Mask) == X6DOT1 || !chanmask));
- break;
- case DevFmtX71:
- case DevFmtX3D71:
- chansok = (chancount >= 8 && ((chanmask&X71Mask) == X7DOT1 || !chanmask));
- break;
- case DevFmtX714:
- chansok = (chancount >= 12 && ((chanmask&X714Mask) == X7DOT1DOT4 || !chanmask));
- case DevFmtX7144:
- case DevFmtAmbi3D:
- break;
- }
- }
- if(!chansok)
- {
- if(chancount >= 12 && (chanmask&X714Mask) == X7DOT1DOT4)
- mDevice->FmtChans = DevFmtX714;
- else if(chancount >= 8 && (chanmask&X71Mask) == X7DOT1)
- mDevice->FmtChans = DevFmtX71;
- else if(chancount >= 7 && (chanmask&X61Mask) == X6DOT1)
- mDevice->FmtChans = DevFmtX61;
- else if(chancount >= 6 && ((chanmask&X51Mask) == X5DOT1
- || (chanmask&X51RearMask) == X5DOT1REAR))
- mDevice->FmtChans = DevFmtX51;
- else if(chancount >= 4 && (chanmask&QuadMask) == QUAD)
- mDevice->FmtChans = DevFmtQuad;
- else if(chancount >= 2 && ((chanmask&StereoMask) == STEREO || !chanmask))
- mDevice->FmtChans = DevFmtStereo;
- else if(chancount >= 1 && ((chanmask&MonoMask) == MONO || !chanmask))
- mDevice->FmtChans = DevFmtMono;
- else
- {
- ERR("Unhandled extensible channels: %d -- 0x%08lx\n", OutputType.Format.nChannels,
- OutputType.dwChannelMask);
- mDevice->FmtChans = DevFmtStereo;
- OutputType.Format.nChannels = 2;
- OutputType.dwChannelMask = STEREO;
- }
- }
- if(IsEqualGUID(OutputType.SubFormat, KSDATAFORMAT_SUBTYPE_PCM))
- {
- if(OutputType.Format.wBitsPerSample == 8)
- mDevice->FmtType = DevFmtUByte;
- else if(OutputType.Format.wBitsPerSample == 16)
- mDevice->FmtType = DevFmtShort;
- else if(OutputType.Format.wBitsPerSample == 32)
- mDevice->FmtType = DevFmtInt;
- else
- {
- mDevice->FmtType = DevFmtShort;
- OutputType.Format.wBitsPerSample = 16;
- }
- }
- else if(IsEqualGUID(OutputType.SubFormat, KSDATAFORMAT_SUBTYPE_IEEE_FLOAT))
- {
- mDevice->FmtType = DevFmtFloat;
- OutputType.Format.wBitsPerSample = 32;
- }
- else
- {
- ERR("Unhandled format sub-type: %s\n", GuidPrinter{OutputType.SubFormat}.c_str());
- mDevice->FmtType = DevFmtShort;
- if(OutputType.Format.wFormatTag != WAVE_FORMAT_EXTENSIBLE)
- OutputType.Format.wFormatTag = WAVE_FORMAT_PCM;
- OutputType.Format.wBitsPerSample = 16;
- OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
- }
- /* NOLINTNEXTLINE(cppcoreguidelines-pro-type-union-access) */
- OutputType.Samples.wValidBitsPerSample = OutputType.Format.wBitsPerSample;
- }
- auto WasapiPlayback::initSpatial() -> bool
- {
- auto &audio = mAudio.emplace<SpatialDevice>();
- HRESULT hr{sDeviceHelper->activateAudioClient(mMMDev, __uuidof(ISpatialAudioClient),
- al::out_ptr(audio.mClient))};
- if(FAILED(hr))
- {
- ERR("Failed to activate spatial audio client: 0x%08lx\n", hr);
- return false;
- }
- ComPtr<IAudioFormatEnumerator> fmtenum;
- hr = audio.mClient->GetSupportedAudioObjectFormatEnumerator(al::out_ptr(fmtenum));
- if(FAILED(hr))
- {
- ERR("Failed to get format enumerator: 0x%08lx\n", hr);
- return false;
- }
- UINT32 fmtcount{};
- hr = fmtenum->GetCount(&fmtcount);
- if(FAILED(hr) || fmtcount == 0)
- {
- ERR("Failed to get format count: 0x%08lx\n", hr);
- return false;
- }
- WAVEFORMATEX *preferredFormat{};
- hr = fmtenum->GetFormat(0, &preferredFormat);
- if(FAILED(hr))
- {
- ERR("Failed to get preferred format: 0x%08lx\n", hr);
- return false;
- }
- TraceFormat("Preferred mix format", preferredFormat);
- UINT32 maxFrames{};
- hr = audio.mClient->GetMaxFrameCount(preferredFormat, &maxFrames);
- if(FAILED(hr))
- ERR("Failed to get max frames: 0x%08lx\n", hr);
- else
- TRACE("Max sample frames: %u\n", maxFrames);
- for(UINT32 i{1};i < fmtcount;++i)
- {
- WAVEFORMATEX *otherFormat{};
- hr = fmtenum->GetFormat(i, &otherFormat);
- if(FAILED(hr))
- ERR("Failed to format %u: 0x%08lx\n", i+1, hr);
- else
- {
- TraceFormat("Other mix format", otherFormat);
- UINT32 otherMaxFrames{};
- hr = audio.mClient->GetMaxFrameCount(otherFormat, &otherMaxFrames);
- if(FAILED(hr))
- ERR("Failed to get max frames: 0x%08lx\n", hr);
- else
- TRACE("Max sample frames: %u\n", otherMaxFrames);
- }
- }
- WAVEFORMATEXTENSIBLE OutputType;
- if(!MakeExtensible(&OutputType, preferredFormat))
- return false;
- /* Force 32-bit float. This is currently required for planar output. */
- if(OutputType.Format.wFormatTag != WAVE_FORMAT_EXTENSIBLE
- && OutputType.Format.wFormatTag != WAVE_FORMAT_IEEE_FLOAT)
- {
- OutputType.Format.wFormatTag = WAVE_FORMAT_IEEE_FLOAT;
- OutputType.Format.cbSize = 0;
- }
- if(OutputType.Format.wBitsPerSample != 32)
- {
- OutputType.Format.nAvgBytesPerSec = OutputType.Format.nAvgBytesPerSec * 32u
- / OutputType.Format.wBitsPerSample;
- OutputType.Format.nBlockAlign = static_cast<WORD>(OutputType.Format.nBlockAlign * 32
- / OutputType.Format.wBitsPerSample);
- OutputType.Format.wBitsPerSample = 32;
- }
- /* NOLINTNEXTLINE(cppcoreguidelines-pro-type-union-access) */
- OutputType.Samples.wValidBitsPerSample = OutputType.Format.wBitsPerSample;
- OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT;
- /* Match the output rate if not requesting anything specific. */
- if(!mDevice->Flags.test(FrequencyRequest))
- mDevice->Frequency = OutputType.Format.nSamplesPerSec;
- bool isRear51{false};
- if(!mDevice->Flags.test(ChannelsRequest))
- {
- const uint32_t chancount{OutputType.Format.nChannels};
- const DWORD chanmask{OutputType.dwChannelMask};
- if(chancount >= 12 && (chanmask&X714Mask) == X7DOT1DOT4)
- mDevice->FmtChans = DevFmtX714;
- else if(chancount >= 8 && (chanmask&X71Mask) == X7DOT1)
- mDevice->FmtChans = DevFmtX71;
- else if(chancount >= 7 && (chanmask&X61Mask) == X6DOT1)
- mDevice->FmtChans = DevFmtX61;
- else if(chancount >= 6 && (chanmask&X51Mask) == X5DOT1)
- mDevice->FmtChans = DevFmtX51;
- else if(chancount >= 6 && (chanmask&X51RearMask) == X5DOT1REAR)
- {
- mDevice->FmtChans = DevFmtX51;
- isRear51 = true;
- }
- else if(chancount >= 4 && (chanmask&QuadMask) == QUAD)
- mDevice->FmtChans = DevFmtQuad;
- else if(chancount >= 2 && ((chanmask&StereoMask) == STEREO || !chanmask))
- mDevice->FmtChans = DevFmtStereo;
- /* HACK: Don't autoselect mono. Wine returns this and makes the audio
- * terrible.
- */
- else if(!(chancount >= 1 && ((chanmask&MonoMask) == MONO || !chanmask)))
- ERR("Unhandled channel config: %d -- 0x%08lx\n", chancount, chanmask);
- }
- else
- {
- const uint32_t chancount{OutputType.Format.nChannels};
- const DWORD chanmask{OutputType.dwChannelMask};
- isRear51 = (chancount == 6 && (chanmask&X51RearMask) == X5DOT1REAR);
- }
- auto getTypeMask = [isRear51](DevFmtChannels chans) noexcept
- {
- switch(chans)
- {
- case DevFmtMono: return ChannelMask_Mono;
- case DevFmtStereo: return ChannelMask_Stereo;
- case DevFmtQuad: return ChannelMask_Quad;
- case DevFmtX51: return isRear51 ? ChannelMask_X51Rear : ChannelMask_X51;
- case DevFmtX61: return ChannelMask_X61;
- case DevFmtX3D71:
- case DevFmtX71: return ChannelMask_X71;
- case DevFmtX714: return ChannelMask_X714;
- case DevFmtX7144: return ChannelMask_X7144;
- case DevFmtAmbi3D:
- break;
- }
- return ChannelMask_Stereo;
- };
- SpatialAudioObjectRenderStreamActivationParams streamParams{};
- streamParams.ObjectFormat = &OutputType.Format;
- streamParams.StaticObjectTypeMask = getTypeMask(mDevice->FmtChans);
- streamParams.Category = AudioCategory_Media;
- streamParams.EventHandle = mNotifyEvent;
- PropVariant paramProp{};
- paramProp.setBlob({reinterpret_cast<BYTE*>(&streamParams), sizeof(streamParams)});
- hr = audio.mClient->ActivateSpatialAudioStream(paramProp.get(),
- __uuidof(ISpatialAudioObjectRenderStream), al::out_ptr(audio.mRender));
- if(FAILED(hr))
- {
- ERR("Failed to activate spatial audio stream: 0x%08lx\n", hr);
- return false;
- }
- audio.mStaticMask = streamParams.StaticObjectTypeMask;
- mFormat = OutputType;
- mDevice->FmtType = DevFmtFloat;
- mDevice->Flags.reset(DirectEar).set(Virtualization);
- if(streamParams.StaticObjectTypeMask == ChannelMask_Stereo)
- mDevice->FmtChans = DevFmtStereo;
- if(!GetConfigValueBool(mDevice->DeviceName, "wasapi", "allow-resampler", true))
- mDevice->Frequency = uint(OutputType.Format.nSamplesPerSec);
- else
- mDevice->Frequency = std::min(mDevice->Frequency,
- uint(OutputType.Format.nSamplesPerSec));
- setDefaultWFXChannelOrder();
- /* FIXME: Get the real update and buffer size. Presumably the actual device
- * is configured once ActivateSpatialAudioStream succeeds, and an
- * IAudioClient from the same IMMDevice accesses the same device
- * configuration. This isn't obviously correct, but for now assume
- * IAudioClient::GetDevicePeriod returns the current device period time
- * that ISpatialAudioObjectRenderStream will try to wake up at.
- *
- * Unfortunately this won't get the buffer size of the
- * ISpatialAudioObjectRenderStream, so we only assume there's two periods.
- */
- mOrigUpdateSize = mDevice->UpdateSize;
- mOrigBufferSize = mOrigUpdateSize*2;
- ReferenceTime per_time{ReferenceTime{seconds{mDevice->UpdateSize}} / mDevice->Frequency};
- ComPtr<IAudioClient> tmpClient;
- hr = sDeviceHelper->activateAudioClient(mMMDev, __uuidof(IAudioClient),
- al::out_ptr(tmpClient));
- if(FAILED(hr))
- ERR("Failed to activate audio client: 0x%08lx\n", hr);
- else
- {
- hr = tmpClient->GetDevicePeriod(&reinterpret_cast<REFERENCE_TIME&>(per_time), nullptr);
- if(FAILED(hr))
- ERR("Failed to get device period: 0x%08lx\n", hr);
- else
- {
- mOrigUpdateSize = RefTime2Samples(per_time, mFormat.Format.nSamplesPerSec);
- mOrigBufferSize = mOrigUpdateSize*2;
- }
- }
- tmpClient = nullptr;
- mDevice->UpdateSize = RefTime2Samples(per_time, mDevice->Frequency);
- mDevice->BufferSize = mDevice->UpdateSize*2;
- mResampler = nullptr;
- mResampleBuffer.clear();
- mResampleBuffer.shrink_to_fit();
- mBufferFilled = 0;
- if(mDevice->Frequency != mFormat.Format.nSamplesPerSec)
- {
- const auto flags = as_unsigned(streamParams.StaticObjectTypeMask);
- const auto channelCount = as_unsigned(al::popcount(flags));
- mResampler = SampleConverter::Create(mDevice->FmtType, mDevice->FmtType, channelCount,
- mDevice->Frequency, mFormat.Format.nSamplesPerSec, Resampler::FastBSinc24);
- mResampleBuffer.resize(size_t{mDevice->UpdateSize} * channelCount *
- mFormat.Format.wBitsPerSample / 8);
- TRACE("Created converter for %s/%s format, dst: %luhz (%u), src: %uhz (%u)\n",
- DevFmtChannelsString(mDevice->FmtChans), DevFmtTypeString(mDevice->FmtType),
- mFormat.Format.nSamplesPerSec, mOrigUpdateSize, mDevice->Frequency,
- mDevice->UpdateSize);
- }
- return true;
- }
- bool WasapiPlayback::reset()
- {
- HRESULT hr{pushMessage(MsgType::ResetDevice).get()};
- if(FAILED(hr))
- throw al::backend_exception{al::backend_error::DeviceError, "0x%08lx", hr};
- return true;
- }
- HRESULT WasapiPlayback::resetProxy()
- {
- if(GetConfigValueBool(mDevice->DeviceName, "wasapi", "spatial-api", false))
- {
- if(initSpatial())
- return S_OK;
- }
- mDevice->Flags.reset(Virtualization);
- auto &audio = mAudio.emplace<PlainDevice>();
- HRESULT hr{sDeviceHelper->activateAudioClient(mMMDev, __uuidof(IAudioClient),
- al::out_ptr(audio.mClient))};
- if(FAILED(hr))
- {
- ERR("Failed to reactivate audio client: 0x%08lx\n", hr);
- return hr;
- }
- WAVEFORMATEX *wfx;
- hr = audio.mClient->GetMixFormat(&wfx);
- if(FAILED(hr))
- {
- ERR("Failed to get mix format: 0x%08lx\n", hr);
- return hr;
- }
- TraceFormat("Device mix format", wfx);
- WAVEFORMATEXTENSIBLE OutputType;
- if(!MakeExtensible(&OutputType, wfx))
- {
- CoTaskMemFree(wfx);
- return E_FAIL;
- }
- CoTaskMemFree(wfx);
- wfx = nullptr;
- const ReferenceTime per_time{ReferenceTime{seconds{mDevice->UpdateSize}} / mDevice->Frequency};
- const ReferenceTime buf_time{ReferenceTime{seconds{mDevice->BufferSize}} / mDevice->Frequency};
- prepareFormat(OutputType);
- TraceFormat("Requesting playback format", &OutputType.Format);
- hr = audio.mClient->IsFormatSupported(AUDCLNT_SHAREMODE_SHARED, &OutputType.Format, &wfx);
- if(FAILED(hr))
- {
- WARN("Failed to check format support: 0x%08lx\n", hr);
- hr = audio.mClient->GetMixFormat(&wfx);
- }
- if(FAILED(hr))
- {
- ERR("Failed to find a supported format: 0x%08lx\n", hr);
- return hr;
- }
- if(wfx != nullptr)
- {
- TraceFormat("Got playback format", wfx);
- if(!MakeExtensible(&OutputType, wfx))
- {
- CoTaskMemFree(wfx);
- return E_FAIL;
- }
- CoTaskMemFree(wfx);
- wfx = nullptr;
- finalizeFormat(OutputType);
- }
- mFormat = OutputType;
- #if !defined(ALSOFT_UWP)
- const EndpointFormFactor formfactor{GetDeviceFormfactor(mMMDev.get())};
- mDevice->Flags.set(DirectEar, (formfactor == Headphones || formfactor == Headset));
- #else
- mDevice->Flags.set(DirectEar, false);
- #endif
- setDefaultWFXChannelOrder();
- hr = audio.mClient->Initialize(AUDCLNT_SHAREMODE_SHARED, AUDCLNT_STREAMFLAGS_EVENTCALLBACK,
- buf_time.count(), 0, &OutputType.Format, nullptr);
- if(FAILED(hr))
- {
- ERR("Failed to initialize audio client: 0x%08lx\n", hr);
- return hr;
- }
- UINT32 buffer_len{};
- ReferenceTime min_per{};
- hr = audio.mClient->GetDevicePeriod(&reinterpret_cast<REFERENCE_TIME&>(min_per), nullptr);
- if(SUCCEEDED(hr))
- hr = audio.mClient->GetBufferSize(&buffer_len);
- if(FAILED(hr))
- {
- ERR("Failed to get audio buffer info: 0x%08lx\n", hr);
- return hr;
- }
- hr = audio.mClient->SetEventHandle(mNotifyEvent);
- if(FAILED(hr))
- {
- ERR("Failed to set event handle: 0x%08lx\n", hr);
- return hr;
- }
- /* Find the nearest multiple of the period size to the update size */
- if(min_per < per_time)
- min_per *= std::max<int64_t>((per_time + min_per/2) / min_per, 1_i64);
- mOrigBufferSize = buffer_len;
- mOrigUpdateSize = std::min(RefTime2Samples(min_per, mFormat.Format.nSamplesPerSec),
- buffer_len/2u);
- mDevice->BufferSize = static_cast<uint>(uint64_t{buffer_len} * mDevice->Frequency /
- mFormat.Format.nSamplesPerSec);
- mDevice->UpdateSize = std::min(RefTime2Samples(min_per, mDevice->Frequency),
- mDevice->BufferSize/2u);
- mResampler = nullptr;
- mResampleBuffer.clear();
- mResampleBuffer.shrink_to_fit();
- mBufferFilled = 0;
- if(mDevice->Frequency != mFormat.Format.nSamplesPerSec)
- {
- mResampler = SampleConverter::Create(mDevice->FmtType, mDevice->FmtType,
- mFormat.Format.nChannels, mDevice->Frequency, mFormat.Format.nSamplesPerSec,
- Resampler::FastBSinc24);
- mResampleBuffer.resize(size_t{mDevice->UpdateSize} * mFormat.Format.nChannels *
- mFormat.Format.wBitsPerSample / 8);
- TRACE("Created converter for %s/%s format, dst: %luhz (%u), src: %uhz (%u)\n",
- DevFmtChannelsString(mDevice->FmtChans), DevFmtTypeString(mDevice->FmtType),
- mFormat.Format.nSamplesPerSec, mOrigUpdateSize, mDevice->Frequency,
- mDevice->UpdateSize);
- }
- return hr;
- }
- void WasapiPlayback::start()
- {
- const HRESULT hr{pushMessage(MsgType::StartDevice).get()};
- if(FAILED(hr))
- throw al::backend_exception{al::backend_error::DeviceError,
- "Failed to start playback: 0x%lx", hr};
- }
- HRESULT WasapiPlayback::startProxy()
- {
- ResetEvent(mNotifyEvent);
- auto mstate_fallback = [](std::monostate) -> HRESULT
- { return E_FAIL; };
- auto start_plain = [&](PlainDevice &audio) -> HRESULT
- {
- HRESULT hr{audio.mClient->Start()};
- if(FAILED(hr))
- {
- ERR("Failed to start audio client: 0x%08lx\n", hr);
- return hr;
- }
- hr = audio.mClient->GetService(__uuidof(IAudioRenderClient), al::out_ptr(audio.mRender));
- if(SUCCEEDED(hr))
- {
- try {
- mKillNow.store(false, std::memory_order_release);
- mThread = std::thread{std::mem_fn(&WasapiPlayback::mixerProc), this};
- }
- catch(...) {
- audio.mRender = nullptr;
- ERR("Failed to start thread\n");
- hr = E_FAIL;
- }
- }
- if(FAILED(hr))
- audio.mClient->Stop();
- return hr;
- };
- auto start_spatial = [&](SpatialDevice &audio) -> HRESULT
- {
- HRESULT hr{audio.mRender->Start()};
- if(FAILED(hr))
- {
- ERR("Failed to start spatial audio stream: 0x%08lx\n", hr);
- return hr;
- }
- try {
- mKillNow.store(false, std::memory_order_release);
- mThread = std::thread{std::mem_fn(&WasapiPlayback::mixerSpatialProc), this};
- }
- catch(...) {
- ERR("Failed to start thread\n");
- hr = E_FAIL;
- }
- if(FAILED(hr))
- {
- audio.mRender->Stop();
- audio.mRender->Reset();
- }
- return hr;
- };
- return std::visit(overloaded{mstate_fallback, start_plain, start_spatial}, mAudio);
- }
- void WasapiPlayback::stop()
- { pushMessage(MsgType::StopDevice).wait(); }
- void WasapiPlayback::stopProxy()
- {
- if(!mThread.joinable())
- return;
- mKillNow.store(true, std::memory_order_release);
- mThread.join();
- auto mstate_fallback = [](std::monostate) -> void
- { };
- auto stop_plain = [](PlainDevice &audio) -> void
- {
- audio.mRender = nullptr;
- audio.mClient->Stop();
- };
- auto stop_spatial = [](SpatialDevice &audio) -> void
- {
- audio.mRender->Stop();
- audio.mRender->Reset();
- };
- std::visit(overloaded{mstate_fallback, stop_plain, stop_spatial}, mAudio);
- }
- ClockLatency WasapiPlayback::getClockLatency()
- {
- std::lock_guard<std::mutex> dlock{mMutex};
- ClockLatency ret{};
- ret.ClockTime = mDevice->getClockTime();
- ret.Latency = seconds{mPadding.load(std::memory_order_relaxed)};
- ret.Latency /= mFormat.Format.nSamplesPerSec;
- if(mResampler)
- {
- auto extra = mResampler->currentInputDelay();
- ret.Latency += std::chrono::duration_cast<nanoseconds>(extra) / mDevice->Frequency;
- ret.Latency += nanoseconds{seconds{mBufferFilled}} / mDevice->Frequency;
- }
- return ret;
- }
- struct WasapiCapture final : public BackendBase, WasapiProxy {
- WasapiCapture(DeviceBase *device) noexcept : BackendBase{device} { }
- ~WasapiCapture() override;
- int recordProc();
- void open(std::string_view name) override;
- HRESULT openProxy(std::string_view name) override;
- void closeProxy() override;
- HRESULT resetProxy() override;
- void start() override;
- HRESULT startProxy() override;
- void stop() override;
- void stopProxy() override;
- void captureSamples(std::byte *buffer, uint samples) override;
- uint availableSamples() override;
- HRESULT mOpenStatus{E_FAIL};
- DeviceHandle mMMDev{nullptr};
- ComPtr<IAudioClient> mClient{nullptr};
- ComPtr<IAudioCaptureClient> mCapture{nullptr};
- HANDLE mNotifyEvent{nullptr};
- ChannelConverter mChannelConv{};
- SampleConverterPtr mSampleConv;
- RingBufferPtr mRing;
- std::atomic<bool> mKillNow{true};
- std::thread mThread;
- };
- WasapiCapture::~WasapiCapture()
- {
- if(SUCCEEDED(mOpenStatus))
- pushMessage(MsgType::CloseDevice).wait();
- mOpenStatus = E_FAIL;
- if(mNotifyEvent != nullptr)
- CloseHandle(mNotifyEvent);
- mNotifyEvent = nullptr;
- }
- FORCE_ALIGN int WasapiCapture::recordProc()
- {
- ComWrapper com{COINIT_MULTITHREADED};
- if(!com)
- {
- ERR("CoInitializeEx(nullptr, COINIT_MULTITHREADED) failed: 0x%08lx\n", com.status());
- mDevice->handleDisconnect("COM init failed: 0x%08lx", com.status());
- return 1;
- }
- althrd_setname(GetRecordThreadName());
- std::vector<float> samples;
- while(!mKillNow.load(std::memory_order_relaxed))
- {
- UINT32 avail;
- HRESULT hr{mCapture->GetNextPacketSize(&avail)};
- if(FAILED(hr))
- ERR("Failed to get next packet size: 0x%08lx\n", hr);
- else if(avail > 0)
- {
- UINT32 numsamples;
- DWORD flags;
- BYTE *rdata;
- hr = mCapture->GetBuffer(&rdata, &numsamples, &flags, nullptr, nullptr);
- if(FAILED(hr))
- ERR("Failed to get capture buffer: 0x%08lx\n", hr);
- else
- {
- if(mChannelConv.is_active())
- {
- samples.resize(numsamples*2_uz);
- mChannelConv.convert(rdata, samples.data(), numsamples);
- rdata = reinterpret_cast<BYTE*>(samples.data());
- }
- auto data = mRing->getWriteVector();
- size_t dstframes;
- if(mSampleConv)
- {
- static constexpr auto lenlimit = size_t{std::numeric_limits<int>::max()};
- const void *srcdata{rdata};
- uint srcframes{numsamples};
- dstframes = mSampleConv->convert(&srcdata, &srcframes, data.first.buf,
- static_cast<uint>(std::min(data.first.len, lenlimit)));
- if(srcframes > 0 && dstframes == data.first.len && data.second.len > 0)
- {
- /* If some source samples remain, all of the first dest
- * block was filled, and there's space in the second
- * dest block, do another run for the second block.
- */
- dstframes += mSampleConv->convert(&srcdata, &srcframes, data.second.buf,
- static_cast<uint>(std::min(data.second.len, lenlimit)));
- }
- }
- else
- {
- const uint framesize{mDevice->frameSizeFromFmt()};
- auto dst = al::span{rdata, size_t{numsamples}*framesize};
- size_t len1{std::min(data.first.len, size_t{numsamples})};
- size_t len2{std::min(data.second.len, numsamples-len1)};
- memcpy(data.first.buf, dst.data(), len1*framesize);
- if(len2 > 0)
- {
- dst = dst.subspan(len1*framesize);
- memcpy(data.second.buf, dst.data(), len2*framesize);
- }
- dstframes = len1 + len2;
- }
- mRing->writeAdvance(dstframes);
- hr = mCapture->ReleaseBuffer(numsamples);
- if(FAILED(hr)) ERR("Failed to release capture buffer: 0x%08lx\n", hr);
- }
- }
- if(FAILED(hr))
- {
- mDevice->handleDisconnect("Failed to capture samples: 0x%08lx", hr);
- break;
- }
- DWORD res{WaitForSingleObjectEx(mNotifyEvent, 2000, FALSE)};
- if(res != WAIT_OBJECT_0)
- ERR("WaitForSingleObjectEx error: 0x%lx\n", res);
- }
- return 0;
- }
- void WasapiCapture::open(std::string_view name)
- {
- if(SUCCEEDED(mOpenStatus))
- throw al::backend_exception{al::backend_error::DeviceError,
- "Unexpected duplicate open call"};
- mNotifyEvent = CreateEventW(nullptr, FALSE, FALSE, nullptr);
- if(mNotifyEvent == nullptr)
- {
- ERR("Failed to create notify events: %lu\n", GetLastError());
- throw al::backend_exception{al::backend_error::DeviceError,
- "Failed to create notify events"};
- }
- if(const auto prefix = GetDevicePrefix(); al::starts_with(name, prefix))
- name = name.substr(prefix.size());
- mOpenStatus = pushMessage(MsgType::OpenDevice, name).get();
- if(FAILED(mOpenStatus))
- throw al::backend_exception{al::backend_error::DeviceError, "Device init failed: 0x%08lx",
- mOpenStatus};
- HRESULT hr{pushMessage(MsgType::ResetDevice).get()};
- if(FAILED(hr))
- {
- if(hr == E_OUTOFMEMORY)
- throw al::backend_exception{al::backend_error::OutOfMemory, "Out of memory"};
- throw al::backend_exception{al::backend_error::DeviceError, "Device reset failed"};
- }
- }
- HRESULT WasapiCapture::openProxy(std::string_view name)
- {
- std::string devname;
- std::wstring devid;
- if(!name.empty())
- {
- auto devlock = DeviceListLock{gDeviceList};
- auto devlist = al::span{devlock.getCaptureList()};
- auto iter = std::find_if(devlist.cbegin(), devlist.cend(),
- [name](const DevMap &entry) -> bool
- { return entry.name == name || entry.endpoint_guid == name; });
- if(iter == devlist.cend())
- {
- const std::wstring wname{utf8_to_wstr(name)};
- iter = std::find_if(devlist.cbegin(), devlist.cend(),
- [&wname](const DevMap &entry) -> bool
- { return entry.devid == wname; });
- }
- if(iter == devlist.cend())
- {
- WARN("Failed to find device name matching \"%.*s\"\n", al::sizei(name), name.data());
- return E_FAIL;
- }
- devname = iter->name;
- devid = iter->devid;
- }
- HRESULT hr{sDeviceHelper->openDevice(devid, eCapture, mMMDev)};
- if(FAILED(hr))
- {
- WARN("Failed to open device \"%s\"\n", devname.empty() ? "(default)" : devname.c_str());
- return hr;
- }
- mClient = nullptr;
- if(!devname.empty())
- mDevice->DeviceName = std::string{GetDevicePrefix()}+std::move(devname);
- else
- mDevice->DeviceName = std::string{GetDevicePrefix()}+GetDeviceNameAndGuid(mMMDev).first;
- return S_OK;
- }
- void WasapiCapture::closeProxy()
- {
- mClient = nullptr;
- mMMDev = nullptr;
- }
- HRESULT WasapiCapture::resetProxy()
- {
- mClient = nullptr;
- HRESULT hr{sDeviceHelper->activateAudioClient(mMMDev, __uuidof(IAudioClient),
- al::out_ptr(mClient))};
- if(FAILED(hr))
- {
- ERR("Failed to reactivate audio client: 0x%08lx\n", hr);
- return hr;
- }
- WAVEFORMATEX *wfx;
- hr = mClient->GetMixFormat(&wfx);
- if(FAILED(hr))
- {
- ERR("Failed to get capture format: 0x%08lx\n", hr);
- return hr;
- }
- TraceFormat("Device capture format", wfx);
- WAVEFORMATEXTENSIBLE InputType{};
- if(!MakeExtensible(&InputType, wfx))
- {
- CoTaskMemFree(wfx);
- return E_FAIL;
- }
- CoTaskMemFree(wfx);
- wfx = nullptr;
- const bool isRear51{InputType.Format.nChannels == 6
- && (InputType.dwChannelMask&X51RearMask) == X5DOT1REAR};
- // Make sure buffer is at least 100ms in size
- ReferenceTime buf_time{ReferenceTime{seconds{mDevice->BufferSize}} / mDevice->Frequency};
- buf_time = std::max(buf_time, ReferenceTime{milliseconds{100}});
- InputType = {};
- InputType.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
- switch(mDevice->FmtChans)
- {
- case DevFmtMono:
- InputType.Format.nChannels = 1;
- InputType.dwChannelMask = MONO;
- break;
- case DevFmtStereo:
- InputType.Format.nChannels = 2;
- InputType.dwChannelMask = STEREO;
- break;
- case DevFmtQuad:
- InputType.Format.nChannels = 4;
- InputType.dwChannelMask = QUAD;
- break;
- case DevFmtX51:
- InputType.Format.nChannels = 6;
- InputType.dwChannelMask = isRear51 ? X5DOT1REAR : X5DOT1;
- break;
- case DevFmtX61:
- InputType.Format.nChannels = 7;
- InputType.dwChannelMask = X6DOT1;
- break;
- case DevFmtX71:
- InputType.Format.nChannels = 8;
- InputType.dwChannelMask = X7DOT1;
- break;
- case DevFmtX714:
- InputType.Format.nChannels = 12;
- InputType.dwChannelMask = X7DOT1DOT4;
- break;
- case DevFmtX7144:
- case DevFmtX3D71:
- case DevFmtAmbi3D:
- return E_FAIL;
- }
- switch(mDevice->FmtType)
- {
- /* NOTE: Signedness doesn't matter, the converter will handle it. */
- case DevFmtByte:
- case DevFmtUByte:
- InputType.Format.wBitsPerSample = 8;
- InputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
- break;
- case DevFmtShort:
- case DevFmtUShort:
- InputType.Format.wBitsPerSample = 16;
- InputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
- break;
- case DevFmtInt:
- case DevFmtUInt:
- InputType.Format.wBitsPerSample = 32;
- InputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
- break;
- case DevFmtFloat:
- InputType.Format.wBitsPerSample = 32;
- InputType.SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT;
- break;
- }
- /* NOLINTNEXTLINE(cppcoreguidelines-pro-type-union-access) */
- InputType.Samples.wValidBitsPerSample = InputType.Format.wBitsPerSample;
- InputType.Format.nSamplesPerSec = mDevice->Frequency;
- InputType.Format.nBlockAlign = static_cast<WORD>(InputType.Format.nChannels *
- InputType.Format.wBitsPerSample / 8);
- InputType.Format.nAvgBytesPerSec = InputType.Format.nSamplesPerSec *
- InputType.Format.nBlockAlign;
- InputType.Format.cbSize = sizeof(InputType) - sizeof(InputType.Format);
- TraceFormat("Requesting capture format", &InputType.Format);
- hr = mClient->IsFormatSupported(AUDCLNT_SHAREMODE_SHARED, &InputType.Format, &wfx);
- if(FAILED(hr))
- {
- WARN("Failed to check capture format support: 0x%08lx\n", hr);
- hr = mClient->GetMixFormat(&wfx);
- }
- if(FAILED(hr))
- {
- ERR("Failed to find a supported capture format: 0x%08lx\n", hr);
- return hr;
- }
- mSampleConv = nullptr;
- mChannelConv = {};
- if(wfx != nullptr)
- {
- TraceFormat("Got capture format", wfx);
- if(!MakeExtensible(&InputType, wfx))
- {
- CoTaskMemFree(wfx);
- return E_FAIL;
- }
- CoTaskMemFree(wfx);
- wfx = nullptr;
- auto validate_fmt = [](DeviceBase *device, uint32_t chancount, DWORD chanmask) noexcept
- -> bool
- {
- switch(device->FmtChans)
- {
- /* If the device wants mono, we can handle any input. */
- case DevFmtMono:
- return true;
- /* If the device wants stereo, we can handle mono or stereo input. */
- case DevFmtStereo:
- return (chancount == 2 && (chanmask == 0 || (chanmask&StereoMask) == STEREO))
- || (chancount == 1 && (chanmask&MonoMask) == MONO);
- /* Otherwise, the device must match the input type. */
- case DevFmtQuad:
- return (chancount == 4 && (chanmask == 0 || (chanmask&QuadMask) == QUAD));
- /* 5.1 (Side) and 5.1 (Rear) are interchangeable here. */
- case DevFmtX51:
- return (chancount == 6 && (chanmask == 0 || (chanmask&X51Mask) == X5DOT1
- || (chanmask&X51RearMask) == X5DOT1REAR));
- case DevFmtX61:
- return (chancount == 7 && (chanmask == 0 || (chanmask&X61Mask) == X6DOT1));
- case DevFmtX71:
- case DevFmtX3D71:
- return (chancount == 8 && (chanmask == 0 || (chanmask&X71Mask) == X7DOT1));
- case DevFmtX714:
- return (chancount == 12 && (chanmask == 0 || (chanmask&X714Mask) == X7DOT1DOT4));
- case DevFmtX7144:
- return (chancount == 16 && chanmask == 0);
- case DevFmtAmbi3D:
- return (chanmask == 0 && chancount == device->channelsFromFmt());
- }
- return false;
- };
- if(!validate_fmt(mDevice, InputType.Format.nChannels, InputType.dwChannelMask))
- {
- ERR("Failed to match format, wanted: %s %s %uhz, got: 0x%08lx mask %d channel%s %d-bit %luhz\n",
- DevFmtChannelsString(mDevice->FmtChans), DevFmtTypeString(mDevice->FmtType),
- mDevice->Frequency, InputType.dwChannelMask, InputType.Format.nChannels,
- (InputType.Format.nChannels==1)?"":"s", InputType.Format.wBitsPerSample,
- InputType.Format.nSamplesPerSec);
- return E_FAIL;
- }
- }
- DevFmtType srcType{};
- if(IsEqualGUID(InputType.SubFormat, KSDATAFORMAT_SUBTYPE_PCM))
- {
- if(InputType.Format.wBitsPerSample == 8)
- srcType = DevFmtUByte;
- else if(InputType.Format.wBitsPerSample == 16)
- srcType = DevFmtShort;
- else if(InputType.Format.wBitsPerSample == 32)
- srcType = DevFmtInt;
- else
- {
- ERR("Unhandled integer bit depth: %d\n", InputType.Format.wBitsPerSample);
- return E_FAIL;
- }
- }
- else if(IsEqualGUID(InputType.SubFormat, KSDATAFORMAT_SUBTYPE_IEEE_FLOAT))
- {
- if(InputType.Format.wBitsPerSample == 32)
- srcType = DevFmtFloat;
- else
- {
- ERR("Unhandled float bit depth: %d\n", InputType.Format.wBitsPerSample);
- return E_FAIL;
- }
- }
- else
- {
- ERR("Unhandled format sub-type: %s\n", GuidPrinter{InputType.SubFormat}.c_str());
- return E_FAIL;
- }
- if(mDevice->FmtChans == DevFmtMono && InputType.Format.nChannels != 1)
- {
- uint chanmask{(1u<<InputType.Format.nChannels) - 1u};
- /* Exclude LFE from the downmix. */
- if((InputType.dwChannelMask&SPEAKER_LOW_FREQUENCY))
- {
- constexpr auto lfemask = MaskFromTopBits(SPEAKER_LOW_FREQUENCY);
- const int lfeidx{al::popcount(InputType.dwChannelMask&lfemask) - 1};
- chanmask &= ~(1u << lfeidx);
- }
- mChannelConv = ChannelConverter{srcType, InputType.Format.nChannels, chanmask,
- mDevice->FmtChans};
- TRACE("Created %s multichannel-to-mono converter\n", DevFmtTypeString(srcType));
- /* The channel converter always outputs float, so change the input type
- * for the resampler/type-converter.
- */
- srcType = DevFmtFloat;
- }
- else if(mDevice->FmtChans == DevFmtStereo && InputType.Format.nChannels == 1)
- {
- mChannelConv = ChannelConverter{srcType, 1, 0x1, mDevice->FmtChans};
- TRACE("Created %s mono-to-stereo converter\n", DevFmtTypeString(srcType));
- srcType = DevFmtFloat;
- }
- if(mDevice->Frequency != InputType.Format.nSamplesPerSec || mDevice->FmtType != srcType)
- {
- mSampleConv = SampleConverter::Create(srcType, mDevice->FmtType,
- mDevice->channelsFromFmt(), InputType.Format.nSamplesPerSec, mDevice->Frequency,
- Resampler::FastBSinc24);
- if(!mSampleConv)
- {
- ERR("Failed to create converter for %s format, dst: %s %uhz, src: %s %luhz\n",
- DevFmtChannelsString(mDevice->FmtChans), DevFmtTypeString(mDevice->FmtType),
- mDevice->Frequency, DevFmtTypeString(srcType), InputType.Format.nSamplesPerSec);
- return E_FAIL;
- }
- TRACE("Created converter for %s format, dst: %s %uhz, src: %s %luhz\n",
- DevFmtChannelsString(mDevice->FmtChans), DevFmtTypeString(mDevice->FmtType),
- mDevice->Frequency, DevFmtTypeString(srcType), InputType.Format.nSamplesPerSec);
- }
- hr = mClient->Initialize(AUDCLNT_SHAREMODE_SHARED, AUDCLNT_STREAMFLAGS_EVENTCALLBACK,
- buf_time.count(), 0, &InputType.Format, nullptr);
- if(FAILED(hr))
- {
- ERR("Failed to initialize audio client: 0x%08lx\n", hr);
- return hr;
- }
- UINT32 buffer_len{};
- ReferenceTime min_per{};
- hr = mClient->GetDevicePeriod(&reinterpret_cast<REFERENCE_TIME&>(min_per), nullptr);
- if(SUCCEEDED(hr))
- hr = mClient->GetBufferSize(&buffer_len);
- if(FAILED(hr))
- {
- ERR("Failed to get buffer size: 0x%08lx\n", hr);
- return hr;
- }
- mDevice->UpdateSize = RefTime2Samples(min_per, mDevice->Frequency);
- mDevice->BufferSize = buffer_len;
- mRing = RingBuffer::Create(buffer_len, mDevice->frameSizeFromFmt(), false);
- hr = mClient->SetEventHandle(mNotifyEvent);
- if(FAILED(hr))
- {
- ERR("Failed to set event handle: 0x%08lx\n", hr);
- return hr;
- }
- return hr;
- }
- void WasapiCapture::start()
- {
- const HRESULT hr{pushMessage(MsgType::StartDevice).get()};
- if(FAILED(hr))
- throw al::backend_exception{al::backend_error::DeviceError,
- "Failed to start recording: 0x%lx", hr};
- }
- HRESULT WasapiCapture::startProxy()
- {
- ResetEvent(mNotifyEvent);
- HRESULT hr{mClient->Start()};
- if(FAILED(hr))
- {
- ERR("Failed to start audio client: 0x%08lx\n", hr);
- return hr;
- }
- hr = mClient->GetService(__uuidof(IAudioCaptureClient), al::out_ptr(mCapture));
- if(SUCCEEDED(hr))
- {
- try {
- mKillNow.store(false, std::memory_order_release);
- mThread = std::thread{std::mem_fn(&WasapiCapture::recordProc), this};
- }
- catch(...) {
- mCapture = nullptr;
- ERR("Failed to start thread\n");
- hr = E_FAIL;
- }
- }
- if(FAILED(hr))
- {
- mClient->Stop();
- mClient->Reset();
- }
- return hr;
- }
- void WasapiCapture::stop()
- { pushMessage(MsgType::StopDevice).wait(); }
- void WasapiCapture::stopProxy()
- {
- if(!mCapture || !mThread.joinable())
- return;
- mKillNow.store(true, std::memory_order_release);
- mThread.join();
- mCapture = nullptr;
- mClient->Stop();
- mClient->Reset();
- }
- void WasapiCapture::captureSamples(std::byte *buffer, uint samples)
- { std::ignore = mRing->read(buffer, samples); }
- uint WasapiCapture::availableSamples()
- { return static_cast<uint>(mRing->readSpace()); }
- } // namespace
- bool WasapiBackendFactory::init()
- {
- static HRESULT InitResult{E_FAIL};
- if(FAILED(InitResult)) try
- {
- std::promise<HRESULT> promise;
- auto future = promise.get_future();
- std::thread{&WasapiProxy::messageHandler, &promise}.detach();
- InitResult = future.get();
- }
- catch(...) {
- }
- return SUCCEEDED(InitResult);
- }
- bool WasapiBackendFactory::querySupport(BackendType type)
- { return type == BackendType::Playback || type == BackendType::Capture; }
- auto WasapiBackendFactory::enumerate(BackendType type) -> std::vector<std::string>
- {
- std::vector<std::string> outnames;
- auto devlock = DeviceListLock{gDeviceList};
- switch(type)
- {
- case BackendType::Playback:
- {
- auto defaultId = devlock.getPlaybackDefaultId();
- for(const DevMap &entry : devlock.getPlaybackList())
- {
- if(entry.devid != defaultId)
- {
- outnames.emplace_back(std::string{GetDevicePrefix()}+entry.name);
- continue;
- }
- /* Default device goes first. */
- outnames.emplace(outnames.cbegin(), std::string{GetDevicePrefix()}+entry.name);
- }
- }
- break;
- case BackendType::Capture:
- {
- auto defaultId = devlock.getCaptureDefaultId();
- for(const DevMap &entry : devlock.getCaptureList())
- {
- if(entry.devid != defaultId)
- {
- outnames.emplace_back(std::string{GetDevicePrefix()}+entry.name);
- continue;
- }
- outnames.emplace(outnames.cbegin(), std::string{GetDevicePrefix()}+entry.name);
- }
- }
- break;
- }
- return outnames;
- }
- BackendPtr WasapiBackendFactory::createBackend(DeviceBase *device, BackendType type)
- {
- if(type == BackendType::Playback)
- return BackendPtr{new WasapiPlayback{device}};
- if(type == BackendType::Capture)
- return BackendPtr{new WasapiCapture{device}};
- return nullptr;
- }
- BackendFactory &WasapiBackendFactory::getFactory()
- {
- static WasapiBackendFactory factory{};
- return factory;
- }
- alc::EventSupport WasapiBackendFactory::queryEventSupport(alc::EventType eventType, BackendType)
- {
- switch(eventType)
- {
- case alc::EventType::DefaultDeviceChanged:
- return alc::EventSupport::FullSupport;
- case alc::EventType::DeviceAdded:
- case alc::EventType::DeviceRemoved:
- #if !defined(ALSOFT_UWP)
- return alc::EventSupport::FullSupport;
- #endif
- case alc::EventType::Count:
- break;
- }
- return alc::EventSupport::NoSupport;
- }
|