diff options
author | Deal(一线灵) <[email protected]> | 2023-05-31 15:24:11 +0800 |
---|---|---|
committer | GitHub <[email protected]> | 2023-05-31 07:24:11 +0000 |
commit | 3caadcf616074c6bedcd45e8af2854379f08e275 (patch) | |
tree | 1c82f3e74906393c1ba767c4e6c330190c308c5f /alc/backends/wasapi.cpp | |
parent | cd27f8551dc593cc4fb29e1093ae45a57e6ca58e (diff) |
Improve wasapi backend UWP support (#853)
* Improve wasapi, support uwp build
* Fix compile errors
* [UWP] Support ReadALConfig from app roaming
* [UWP] Post disconnect event when default device changed
* [UWP] Fix appveyor ci
* [WIN32] Default device change notification support
* Fix warnings
* Add event to notify the app when the default device changes
- Event type: AL_EVENT_TYPE_DEFAULT_DEVICE_CHANGED_SOFT=0x19A7
- Event callback parameters:
void _onALSoftEvent(ALenum eventType,
ALuint object, // dataFlow: 0(render), 1(capture)
ALuint param, // 0
ALsizei length, // 0
const ALchar* message, // Default device changed:<deviceId>
void* userParam);
* Fix warnings
* Fire default device changed event in mixerProc thread
* Fix compile warning
* [UWP] Improve cmake
* Revert changes
* Notify default device change by system event callback
* Revert insignificant change
* Remove duplicate call
Diffstat (limited to 'alc/backends/wasapi.cpp')
-rw-r--r-- | alc/backends/wasapi.cpp | 689 |
1 files changed, 520 insertions, 169 deletions
diff --git a/alc/backends/wasapi.cpp b/alc/backends/wasapi.cpp index d4ad38e2..c8c03e8a 100644 --- a/alc/backends/wasapi.cpp +++ b/alc/backends/wasapi.cpp @@ -58,6 +58,7 @@ #include "albit.h" #include "alc/alconfig.h" +#include "alc/events.h" #include "alnumeric.h" #include "alspan.h" #include "comptr.h" @@ -69,6 +70,14 @@ #include "strutils.h" #include "threads.h" +#if defined(ALSOFT_UWP) +#include <collection.h> +using namespace Platform; +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 @@ -80,11 +89,11 @@ DEFINE_GUID(KSDATAFORMAT_SUBTYPE_PCM, 0x00000001, 0x0000, 0x0010, 0x80, 0x00, 0x #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 { @@ -190,159 +199,504 @@ bool checkName(const al::span<DevMap> list, const std::string &name) std::vector<DevMap> PlaybackDevices; std::vector<DevMap> CaptureDevices; - -using NameGUIDPair = std::pair<std::string,std::string>; -NameGUIDPair get_device_name_and_guid(IMMDevice *device) +#if defined(ALSOFT_UWP) +enum EDataFlow { - static constexpr char UnknownName[]{"Unknown Device Name"}; - static constexpr char UnknownGuid[]{"Unknown Device GUID"}; - std::string name, guid; + eRender = 0, + eCapture = (eRender + 1), + eAll = (eCapture + 1), + EDataFlow_enum_count = (eAll + 1) +}; +#endif - ComPtr<IPropertyStore> ps; - HRESULT hr = device->OpenPropertyStore(STGM_READ, al::out_ptr(ps)); - if(FAILED(hr)) +#if defined(ALSOFT_UWP) +struct DeviceHandle +{ + DeviceHandle& operator=(std::nullptr_t) { - WARN("OpenPropertyStore failed: 0x%08lx\n", hr); - return std::make_pair(UnknownName, UnknownGuid); + value = nullptr; + return *this; } + DeviceInformation^ value{nullptr}; +}; +using EventRegistrationToken = Windows::Foundation::EventRegistrationToken; +#else +using DeviceHandle = ComPtr<IMMDevice>; +using EventRegistrationToken = void*; +#endif - 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); - name += UnknownName; +#if defined(ALSOFT_UWP) +struct DeviceHelper final : public IActivateAudioInterfaceCompletionHandler +#else +struct DeviceHelper final : public IMMNotificationClient +#endif +{ +public: + DeviceHelper() + { +#if defined(ALSOFT_UWP) + mActiveClientEvent = CreateEventW(nullptr, FALSE, FALSE, nullptr); +#else + HRESULT hr = CoCreateInstance(CLSID_MMDeviceEnumerator, nullptr, CLSCTX_INPROC_SERVER, IID_IMMDeviceEnumerator, + al::out_ptr(mEnumerator)); + if (SUCCEEDED(hr)) + mEnumerator->RegisterEndpointNotificationCallback(this); + else + WARN("Failed to create IMMDeviceEnumerator instance: 0x%08lx\n", hr); +#endif } - else if(pvprop->vt == VT_LPWSTR) - name += wstr_to_utf8(pvprop->pwszVal); - else + ~DeviceHelper() { - WARN("Unexpected PROPVARIANT type: 0x%04x\n", pvprop->vt); - name += UnknownName; +#if defined(ALSOFT_UWP) + if (mActiveClientEvent != nullptr) + CloseHandle(mActiveClientEvent); + mActiveClientEvent = nullptr; +#else + if (mEnumerator) + mEnumerator->UnregisterEndpointNotificationCallback(this); + mEnumerator = nullptr; +#endif } - pvprop.clear(); - hr = ps->GetValue(al::bit_cast<PROPERTYKEY>(PKEY_AudioEndpoint_GUID), pvprop.get()); - if(FAILED(hr)) + /** -------------------------- IUnkonwn ----------------------------- */ + LONG mRefCount{1}; + ULONG STDMETHODCALLTYPE AddRef() override { return InterlockedIncrement(&mRefCount); } + + ULONG STDMETHODCALLTYPE Release() override { - WARN("GetValue AudioEndpoint_GUID failed: 0x%08lx\n", hr); - guid = UnknownGuid; + ULONG ulRef = InterlockedDecrement(&mRefCount); + if (0 == ulRef) + { + delete this; + } + return ulRef; } - else if(pvprop->vt == VT_LPWSTR) - guid = wstr_to_utf8(pvprop->pwszVal); - else + + HRESULT STDMETHODCALLTYPE QueryInterface(const IID& IId, void** UnknownPtrPtr) override { - WARN("Unexpected PROPVARIANT type: 0x%04x\n", pvprop->vt); - guid = UnknownGuid; - } + // 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. - return std::make_pair(std::move(name), std::move(guid)); -} + // If ppvObject(the address) is nullptr, then this method returns E_POINTER. + if (!UnknownPtrPtr) + { + return E_POINTER; + } -EndpointFormFactor get_device_formfactor(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; + // 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 = (IActivateAudioInterfaceCompletionHandler*)(this); + AddRef(); + return S_OK; + } +#else + if (IId == __uuidof(IMMNotificationClient)) + { + *UnknownPtrPtr = (IMMNotificationClient*)(this); + AddRef(); + return S_OK; + } +#endif + else if (IId == __uuidof(IAgileObject) || IId == __uuidof(IUnknown)) + { + *UnknownPtrPtr = (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; } - 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->vt == VT_UI4) - formfactor = static_cast<EndpointFormFactor>(pvform->ulVal); - else if(pvform->vt != VT_EMPTY) - WARN("Unexpected PROPVARIANT type: 0x%04x\n", pvform->vt); - return formfactor; -} +#if defined(ALSOFT_UWP) + /** ----------------------- IActivateAudioInterfaceCompletionHandler ------------ */ + HRESULT ActivateCompleted(IActivateAudioInterfaceAsyncOperation* operation) override + { + HRESULT hrActivateResult = S_OK; + IUnknown* punkAudioInterface = nullptr; + HRESULT hr = operation->GetActivateResult(&hrActivateResult, &punkAudioInterface); + // Check for a successful activation result + if (SUCCEEDED(hr) && SUCCEEDED(hrActivateResult)) + { + if (mPPV) + { + // Get the pointer for the Audio Client + IAudioClient3* audioClient; + punkAudioInterface->QueryInterface(IID_PPV_ARGS(&audioClient)); + *mPPV = audioClient; + } + } -void add_device(IMMDevice *device, const WCHAR *devid, std::vector<DevMap> &list) -{ - for(auto &entry : list) + SetEvent(mActiveClientEvent); + + // Need to return S_OK + return S_OK; + } +#else + /** ----------------------- IMMNotificationClient ------------ */ + STDMETHOD(OnDeviceStateChanged(LPCWSTR /*pwstrDeviceId*/, DWORD /*dwNewState*/)) override { return S_OK; } + STDMETHOD(OnDeviceAdded(LPCWSTR /*pwstrDeviceId*/)) override { return S_OK; } + STDMETHOD(OnDeviceRemoved(LPCWSTR /*pwstrDeviceId*/)) override { return S_OK; } + STDMETHOD(OnPropertyValueChanged(LPCWSTR /*pwstrDeviceId*/, const PROPERTYKEY /*key*/)) override { return S_OK; } + STDMETHOD(OnDefaultDeviceChanged(EDataFlow flow, ERole role, LPCWSTR pwstrDefaultDeviceId)) override { - if(entry.devid == devid) - return; + if (role == eMultimedia && (flow == eRender || flow == eCapture)) + { + std::lock_guard<std::mutex> lck(mDefaultChangeHandlersMtx); + for (auto& handlerItem : mDefaultChangeHandlers) + { + if (handlerItem.second.first == flow) + handlerItem.second.second(pwstrDefaultDeviceId); + } + } + return S_OK; } +#endif - auto name_guid = get_device_name_and_guid(device); + /** -------------------------- DeviceHelper ----------------------------- */ + HRESULT OpenDevice(LPCWSTR devid, EDataFlow flow, DeviceHandle& device) + { +#if !defined(ALSOFT_UWP) + HRESULT hr = E_POINTER; + if (mEnumerator) + { + if (!devid) + hr = mEnumerator->GetDefaultAudioEndpoint(flow, eMultimedia, al::out_ptr(device)); + else + hr = mEnumerator->GetDevice(devid, al::out_ptr(device)); + } + if (FAILED(hr)) + { + return hr; + } +#else + const auto deviceRole = Windows::Media::Devices::AudioDeviceRole::Default; + Platform::String^ devIfPath = + !devid ? (flow == eRender ? MediaDevice::GetDefaultAudioRenderId(deviceRole) : MediaDevice::GetDefaultAudioCaptureId(deviceRole)) + : ref new Platform::String(devid); + + Concurrency::task<DeviceInformation^> createDeviceOp( + DeviceInformation::CreateFromIdAsync(devIfPath, nullptr, DeviceInformationKind::DeviceInterface)); + auto status = createDeviceOp.then([&](DeviceInformation^ deviceInfo) { + device.value = deviceInfo; + }).wait(); + if (status != Concurrency::task_status::completed) + { + return E_NOINTERFACE; + } +#endif + return S_OK; + } - int count{1}; - std::string newname{name_guid.first}; - while(checkName(list, newname)) + HRESULT ActivateAudioClient(_In_ DeviceHandle& device, + void** ppv) { - newname = name_guid.first; - newname += " #"; - newname += std::to_string(++count); +#if !defined(ALSOFT_UWP) + HRESULT hr{device->Activate(__uuidof(IAudioClient3), CLSCTX_INPROC_SERVER, nullptr, ppv)}; +#else + HRESULT hr{ActivateAudioInterface(device.value->Id->Data(), + __uuidof(IAudioClient3), nullptr, ppv)}; +#endif + return hr; } - 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()); -} + HRESULT probe_devices(EDataFlow flowdir, std::vector<DevMap>& list) + { + std::vector<DevMap>{}.swap(list); -WCHAR *get_device_id(IMMDevice *device) -{ - WCHAR *devid; +#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 hr; + } - const HRESULT hr{device->GetId(&devid)}; - if(FAILED(hr)) - { - ERR("Failed to get device id: %lx\n", hr); - return nullptr; - } + UINT count{0}; + hr = coll->GetCount(&count); + if (SUCCEEDED(hr) && count > 0) + list.reserve(count); - return devid; -} + ComPtr<IMMDevice> device; + hr = mEnumerator->GetDefaultAudioEndpoint(flowdir, eMultimedia, al::out_ptr(device)); + if (SUCCEEDED(hr)) + { + if (WCHAR * devid{get_device_id(device.get())}) + { + add_device(device, devid, list); + CoTaskMemFree(devid); + } + device = nullptr; + } -void probe_devices(IMMDeviceEnumerator *devenum, EDataFlow flowdir, std::vector<DevMap> &list) -{ - std::vector<DevMap>{}.swap(list); + for (UINT i{0}; i < count; ++i) + { + hr = coll->Item(i, al::out_ptr(device)); + if (FAILED(hr)) + continue; - ComPtr<IMMDeviceCollection> coll; - HRESULT hr{devenum->EnumAudioEndpoints(flowdir, DEVICE_STATE_ACTIVE, al::out_ptr(coll))}; - if(FAILED(hr)) - { - ERR("Failed to enumerate audio endpoints: 0x%08lx\n", hr); - return; + if (WCHAR * devid{get_device_id(device.get())}) + { + add_device(device, devid, list); + CoTaskMemFree(devid); + } + device = nullptr; + } + + return S_OK; +#else + const auto deviceRole = Windows::Media::Devices::AudioDeviceRole::Default; + auto DefaultAudioId = flowdir == eRender ? MediaDevice::GetDefaultAudioRenderId(deviceRole) + : MediaDevice::GetDefaultAudioCaptureId(deviceRole); + Concurrency::task<DeviceInformation ^> createDefaultOp(DeviceInformation::CreateFromIdAsync(DefaultAudioId, nullptr, DeviceInformationKind::DeviceInterface)); + auto task_status = createDefaultOp + .then([this, &list](DeviceInformation ^ deviceInfo) { + if (deviceInfo) + add_device(DeviceHandle{deviceInfo}, deviceInfo->Id->Data(), list); + }).wait(); + if (task_status != Concurrency::task_group_status::completed) + return E_FAIL; + + // Get the string identifier of the audio renderer + auto AudioSelector = flowdir == eRender ? MediaDevice::GetAudioRenderSelector() : MediaDevice::GetAudioCaptureSelector(); + + // Setup the asynchronous callback + Concurrency::task<DeviceInformationCollection ^> enumOperation( + DeviceInformation::FindAllAsync(AudioSelector, /*PropertyList*/nullptr, DeviceInformationKind::DeviceInterface)); + task_status = enumOperation + .then([this, &list](DeviceInformationCollection ^ DeviceInfoCollection) { + if (DeviceInfoCollection) + { + try + { + auto deviceCount = DeviceInfoCollection->Size; + for (unsigned int i = 0; i < deviceCount; ++i) + { + DeviceInformation ^ deviceInfo = DeviceInfoCollection->GetAt(i); + if (deviceInfo) + add_device(DeviceHandle{deviceInfo}, deviceInfo->Id->Data(), list); + } + } + catch (Platform::Exception ^ e) + { + } + } + }).wait(); + + return task_status == Concurrency::task_group_status::completed ? S_OK : E_FAIL; +#endif } - UINT count{0}; - hr = coll->GetCount(&count); - if(SUCCEEDED(hr) && count > 0) - list.reserve(count); + using NameGUIDPair = std::pair<std::string, std::string>; + static NameGUIDPair get_device_name_and_guid(const DeviceHandle& device) + { +#if !defined(ALSOFT_UWP) + static constexpr char UnknownName[]{"Unknown Device Name"}; + static constexpr char UnknownGuid[]{"Unknown Device GUID"}; + std::string name, guid; - ComPtr<IMMDevice> device; - hr = devenum->GetDefaultAudioEndpoint(flowdir, eMultimedia, al::out_ptr(device)); - if(SUCCEEDED(hr)) + 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::make_pair(UnknownName, 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); + name += UnknownName; + } + else if (pvprop->vt == VT_LPWSTR) + name += wstr_to_utf8(pvprop->pwszVal); + else + { + WARN("Unexpected PROPVARIANT type: 0x%04x\n", pvprop->vt); + name += UnknownName; + } + + 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); + guid = UnknownGuid; + } + else if (pvprop->vt == VT_LPWSTR) + guid = wstr_to_utf8(pvprop->pwszVal); + else + { + WARN("Unexpected PROPVARIANT type: 0x%04x\n", pvprop->vt); + guid = UnknownGuid; + } + +#else + auto devInfo = device.value; + std::string name = wstr_to_utf8(devInfo->Name->Data()); + std::string guid; + // devInfo->Id is DeviceInterfacePath: \\?\SWD#MMDEVAPI#{0.0.0.00000000}.{a21c17a0-fc1d-405e-ab5a-b513422b57d1}#{e6327cad-dcec-4949-ae8a-991e976a79d2} + Platform::String ^ devIfPath = devInfo->Id; + auto wcsDevIfPath = devIfPath->Data(); + auto devIdStart = wcsstr(wcsDevIfPath, L"}."); + if (devIdStart) + { + devIdStart += 2; // L"}." + auto devIdStartEnd = wcschr(devIdStart, L'#'); + if (devIdStartEnd) + { + 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 + return std::make_pair(std::move(name), std::move(guid)); + } + + static void add_device(const DeviceHandle& device, const WCHAR* devid, std::vector<DevMap>& list) { - if(WCHAR *devid{get_device_id(device.get())}) + for (auto& entry : list) { - add_device(device.get(), devid, list); - CoTaskMemFree(devid); + if (entry.devid == devid) + return; } + + auto name_guid = get_device_name_and_guid(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()); } - for(UINT i{0};i < count;++i) +#if !defined(ALSOFT_UWP) + static WCHAR* get_device_id(IMMDevice* device) { - device = nullptr; - hr = coll->Item(i, al::out_ptr(device)); - if(FAILED(hr)) continue; + WCHAR* devid; - if(WCHAR *devid{get_device_id(device.get())}) + const HRESULT hr{device->GetId(&devid)}; + if (FAILED(hr)) { - add_device(device.get(), devid, list); - CoTaskMemFree(devid); + ERR("Failed to get device id: %lx\n", hr); + return nullptr; } + + return devid; } -} + static EndpointFormFactor get_device_formfactor(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->vt == VT_UI4) + formfactor = static_cast<EndpointFormFactor>(pvform->ulVal); + else if (pvform->vt != VT_EMPTY) + WARN("Unexpected PROPVARIANT type: 0x%04x\n", pvform->vt); + return formfactor; + } +#endif + + template <typename _Fty> + EventRegistrationToken RegisterDefaultChangeHandler(EDataFlow flow, void* target, _Fty&& cb) + { +#if defined(ALSOFT_UWP) + (void)target; + if (flow == eRender) + return MediaDevice::DefaultAudioRenderDeviceChanged += + ref new TypedEventHandler<Platform::Object ^, DefaultAudioRenderDeviceChangedEventArgs ^>( + [this, cb](Platform::Object ^ sender, DefaultAudioRenderDeviceChangedEventArgs ^ args) { + if (args->Role == AudioDeviceRole::Default) + cb(args->Id->Data()); + }); + else + return MediaDevice::DefaultAudioCaptureDeviceChanged += + ref new TypedEventHandler<Platform::Object ^, DefaultAudioCaptureDeviceChangedEventArgs ^>( + [this, cb](Platform::Object ^ sender, DefaultAudioCaptureDeviceChangedEventArgs ^ args) { + if (args->Role == AudioDeviceRole::Default) + cb(args->Id->Data()); + }); +#else + std::lock_guard<std::mutex> lck(mDefaultChangeHandlersMtx); + if (mDefaultChangeHandlers.emplace(target, std::make_pair(flow, cb)).second) + return target; + return nullptr; +#endif + } + + void UnregisterDefaultChangeHandler(EventRegistrationToken handler) + { +#if defined(ALSOFT_UWP) + MediaDevice::DefaultAudioRenderDeviceChanged -= handler; +#else + std::lock_guard<std::mutex> lck(mDefaultChangeHandlersMtx); + mDefaultChangeHandlers.erase(handler); +#endif + } +private: +#if defined(ALSOFT_UWP) + HRESULT ActivateAudioInterface(_In_ LPCWSTR deviceInterfacePath, + _In_ REFIID riid, + _In_opt_ PROPVARIANT* activationParams, + void** ppv) + { + IActivateAudioInterfaceAsyncOperation* asyncOp{nullptr}; + mPPV = ppv; + HRESULT hr = ActivateAudioInterfaceAsync(deviceInterfacePath, riid, activationParams, this, &asyncOp); + if (FAILED(hr)) + return hr; + if (asyncOp) + asyncOp->Release(); + + DWORD res{WaitForSingleObjectEx(mActiveClientEvent, 2000, FALSE)}; + if (res != WAIT_OBJECT_0) + ERR("WaitForSingleObjectEx error: 0x%lx\n", res); + return res; + } + + HANDLE mActiveClientEvent{nullptr}; + void** mPPV{nullptr}; +#else + ComPtr<IMMDeviceEnumerator> mEnumerator{nullptr}; + std::mutex mDefaultChangeHandlersMtx; + std::unordered_map<void*, std::pair<EDataFlow,std::function<void(LPCWSTR)>>> mDefaultChangeHandlers; +#endif +}; bool MakeExtensible(WAVEFORMATEXTENSIBLE *out, const WAVEFORMATEX *in) { @@ -473,6 +827,8 @@ struct WasapiProxy { static std::mutex sThreadLock; static size_t sInitCount; + static ComPtr<DeviceHelper> sDeviceHelper; + std::future<HRESULT> pushMessage(MsgType type, const char *param=nullptr) { std::promise<HRESULT> promise; @@ -544,6 +900,7 @@ std::deque<WasapiProxy::Msg> WasapiProxy::mMsgQueue; std::mutex WasapiProxy::mMsgQueueLock; std::condition_variable WasapiProxy::mMsgQueueCond; std::mutex WasapiProxy::sThreadLock; +ComPtr<DeviceHelper> WasapiProxy::sDeviceHelper; size_t WasapiProxy::sInitCount{0}; int WasapiProxy::messageHandler(std::promise<HRESULT> *promise) @@ -560,6 +917,8 @@ int WasapiProxy::messageHandler(std::promise<HRESULT> *promise) promise->set_value(S_OK); promise = nullptr; + sDeviceHelper.reset(new DeviceHelper()); + TRACE("Starting message loop\n"); while(Msg msg{popMessage()}) { @@ -597,19 +956,12 @@ int WasapiProxy::messageHandler(std::promise<HRESULT> *promise) case MsgType::EnumeratePlayback: case MsgType::EnumerateCapture: { - ComPtr<IMMDeviceEnumerator> devenum; - hr = CoCreateInstance(CLSID_MMDeviceEnumerator, nullptr, CLSCTX_INPROC_SERVER, - IID_IMMDeviceEnumerator, al::out_ptr(devenum)); - if(FAILED(hr)) - msg.mPromise.set_value(hr); - else - { - if(msg.mType == MsgType::EnumeratePlayback) - probe_devices(devenum.get(), eRender, PlaybackDevices); - else if(msg.mType == MsgType::EnumerateCapture) - probe_devices(devenum.get(), eCapture, CaptureDevices); - msg.mPromise.set_value(S_OK); - } + if (msg.mType == MsgType::EnumeratePlayback) + msg.mPromise.set_value(sDeviceHelper->probe_devices(eRender, PlaybackDevices)); + else if (msg.mType == MsgType::EnumerateCapture) + msg.mPromise.set_value(sDeviceHelper->probe_devices(eCapture, CaptureDevices)); + else + msg.mPromise.set_value(E_FAIL); continue; } @@ -625,7 +977,6 @@ int WasapiProxy::messageHandler(std::promise<HRESULT> *promise) return 0; } - struct WasapiPlayback final : public BackendBase, WasapiProxy { WasapiPlayback(DeviceBase *device) noexcept : BackendBase{device} { } ~WasapiPlayback() override; @@ -646,7 +997,7 @@ struct WasapiPlayback final : public BackendBase, WasapiProxy { ClockLatency getClockLatency() override; HRESULT mOpenStatus{E_FAIL}; - ComPtr<IMMDevice> mMMDev{nullptr}; + DeviceHandle mMMDev{nullptr}; ComPtr<IAudioClient> mClient{nullptr}; ComPtr<IAudioRenderClient> mRender{nullptr}; HANDLE mNotifyEvent{nullptr}; @@ -664,6 +1015,9 @@ struct WasapiPlayback final : public BackendBase, WasapiProxy { std::atomic<bool> mKillNow{true}; std::thread mThread; + std::string mDefaultDeviceId; + EventRegistrationToken mDefaultChangeHandler{}; + DEF_NEWDEL(WasapiPlayback) }; @@ -838,33 +1192,29 @@ HRESULT WasapiPlayback::openProxy(const char *name) devid = iter->devid.c_str(); } - ComPtr<IMMDeviceEnumerator> enumerator; - ComPtr<IMMDevice> mmdev; - HRESULT hr{CoCreateInstance(CLSID_MMDeviceEnumerator, nullptr, CLSCTX_INPROC_SERVER, - IID_IMMDeviceEnumerator, al::out_ptr(enumerator))}; - if(SUCCEEDED(hr)) - { - if(!devid) - hr = enumerator->GetDefaultAudioEndpoint(eRender, eMultimedia, al::out_ptr(mmdev)); - else - hr = enumerator->GetDevice(devid, al::out_ptr(mmdev)); - } - if(FAILED(hr)) + HRESULT hr{sDeviceHelper->OpenDevice(devid, eRender, mMMDev)}; + if (FAILED(hr)) { - WARN("Failed to open device \"%s\"\n", name?name:"(default)"); + WARN("Failed to open device \"%s\"\n", name ? name : "(default)"); return hr; } - mClient = nullptr; - mMMDev = std::move(mmdev); - if(name) mDevice->DeviceName = std::string{DevNameHead} + name; - else mDevice->DeviceName = DevNameHead + get_device_name_and_guid(mMMDev.get()).first; + if (name) + mDevice->DeviceName = std::string{DevNameHead} + name; + else + mDevice->DeviceName = DevNameHead + DeviceHelper::get_device_name_and_guid(mMMDev).first; - return hr; + mDefaultChangeHandler = sDeviceHelper->RegisterDefaultChangeHandler(eRender, this, [this](LPCWSTR devid) { + mDefaultDeviceId = wstr_to_utf8(devid); + alc::Event(alc::EventType::DefaultDeviceChanged, (ALCdevice*)mDevice, mDefaultDeviceId); + }); + + return S_OK; } void WasapiPlayback::closeProxy() { + sDeviceHelper->UnregisterDefaultChangeHandler(mDefaultChangeHandler); mClient = nullptr; mMMDev = nullptr; } @@ -881,9 +1231,7 @@ bool WasapiPlayback::reset() HRESULT WasapiPlayback::resetProxy() { mClient = nullptr; - - HRESULT hr{mMMDev->Activate(IID_IAudioClient, CLSCTX_INPROC_SERVER, nullptr, - al::out_ptr(mClient))}; + HRESULT hr{sDeviceHelper->ActivateAudioClient(mMMDev, al::out_ptr(mClient))}; if(FAILED(hr)) { ERR("Failed to reactivate audio client: 0x%08lx\n", hr); @@ -1154,9 +1502,12 @@ HRESULT WasapiPlayback::resetProxy() } mFormat = OutputType; - const EndpointFormFactor formfactor{get_device_formfactor(mMMDev.get())}; +#if !defined(ALSOFT_UWP) + const EndpointFormFactor formfactor{DeviceHelper::get_device_formfactor(mMMDev.get())}; mDevice->Flags.set(DirectEar, (formfactor == Headphones || formfactor == Headset)); - +#else + mDevice->Flags.set(DirectEar, false); +#endif setDefaultWFXChannelOrder(); hr = mClient->Initialize(AUDCLNT_SHAREMODE_SHARED, AUDCLNT_STREAMFLAGS_EVENTCALLBACK, @@ -1313,7 +1664,7 @@ struct WasapiCapture final : public BackendBase, WasapiProxy { uint availableSamples() override; HRESULT mOpenStatus{E_FAIL}; - ComPtr<IMMDevice> mMMDev{nullptr}; + DeviceHandle mMMDev{nullptr}; ComPtr<IAudioClient> mClient{nullptr}; ComPtr<IAudioCaptureClient> mCapture{nullptr}; HANDLE mNotifyEvent{nullptr}; @@ -1325,6 +1676,9 @@ struct WasapiCapture final : public BackendBase, WasapiProxy { std::atomic<bool> mKillNow{true}; std::thread mThread; + std::string mDefaultDeviceId; + EventRegistrationToken mDefaultChangeHandler{}; + DEF_NEWDEL(WasapiCapture) }; @@ -1509,31 +1863,28 @@ HRESULT WasapiCapture::openProxy(const char *name) devid = iter->devid.c_str(); } - ComPtr<IMMDeviceEnumerator> enumerator; - HRESULT hr{CoCreateInstance(CLSID_MMDeviceEnumerator, nullptr, CLSCTX_INPROC_SERVER, - IID_IMMDeviceEnumerator, al::out_ptr(enumerator))}; - if(SUCCEEDED(hr)) - { - if(!devid) - hr = enumerator->GetDefaultAudioEndpoint(eCapture, eMultimedia, al::out_ptr(mMMDev)); - else - hr = enumerator->GetDevice(devid, al::out_ptr(mMMDev)); - } - if(FAILED(hr)) + HRESULT hr{sDeviceHelper->OpenDevice(devid, eCapture, mMMDev)}; + if (FAILED(hr)) { - WARN("Failed to open device \"%s\"\n", name?name:"(default)"); + WARN("Failed to open device \"%s\"\n", name ? name : "(default)"); return hr; } - mClient = nullptr; - if(name) mDevice->DeviceName = std::string{DevNameHead} + name; - else mDevice->DeviceName = DevNameHead + get_device_name_and_guid(mMMDev.get()).first; + if (name) + mDevice->DeviceName = std::string{ DevNameHead } + name; + else + mDevice->DeviceName = DevNameHead + DeviceHelper::get_device_name_and_guid(mMMDev).first; - return hr; + mDefaultChangeHandler = sDeviceHelper->RegisterDefaultChangeHandler(eCapture, this, [this](LPCWSTR devid) { + mDefaultDeviceId = wstr_to_utf8(devid); + alc::Event(alc::EventType::DefaultDeviceChanged, (ALCdevice*)mDevice, mDefaultDeviceId); + }); + return S_OK; } void WasapiCapture::closeProxy() { + sDeviceHelper->UnregisterDefaultChangeHandler(mDefaultChangeHandler); mClient = nullptr; mMMDev = nullptr; } @@ -1542,8 +1893,7 @@ HRESULT WasapiCapture::resetProxy() { mClient = nullptr; - HRESULT hr{mMMDev->Activate(IID_IAudioClient, CLSCTX_INPROC_SERVER, nullptr, - al::out_ptr(mClient))}; + HRESULT hr{sDeviceHelper->ActivateAudioClient(mMMDev, al::out_ptr(mClient))}; if(FAILED(hr)) { ERR("Failed to reactivate audio client: 0x%08lx\n", hr); @@ -1904,13 +2254,14 @@ bool WasapiBackendFactory::init() WARN("Failed to initialize COM: 0x%08lx\n", hr); return hr; } - +#if !defined(ALSOFT_UWP) ComPtr<IMMDeviceEnumerator> enumerator; hr = CoCreateInstance(CLSID_MMDeviceEnumerator, nullptr, CLSCTX_INPROC_SERVER, IID_IMMDeviceEnumerator, al::out_ptr(enumerator)); if(FAILED(hr)) WARN("Failed to create IMMDeviceEnumerator instance: 0x%08lx\n", hr); enumerator = nullptr; +#endif CoUninitialize(); return hr; |