aboutsummaryrefslogtreecommitdiffstats
path: root/alc/backends/wasapi.cpp
diff options
context:
space:
mode:
authorDeal(一线灵) <[email protected]>2023-05-31 15:24:11 +0800
committerGitHub <[email protected]>2023-05-31 07:24:11 +0000
commit3caadcf616074c6bedcd45e8af2854379f08e275 (patch)
tree1c82f3e74906393c1ba767c4e6c330190c308c5f /alc/backends/wasapi.cpp
parentcd27f8551dc593cc4fb29e1093ae45a57e6ca58e (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.cpp689
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;