diff options
author | Chris Robinson <chris.kcat@gmail.com> | 2021-03-08 22:29:40 -0800 |
---|---|---|
committer | Chris Robinson <chris.kcat@gmail.com> | 2021-03-08 22:29:40 -0800 |
commit | 730c964029f7b649510490d8766aba801f576492 (patch) | |
tree | f9415b25e80d49ac40a02d6f94a96cfa355ca8b0 /alc/backends/wasapi.cpp | |
parent | 0f7ed495e199f7158295c6a61ea0e0080a5b4339 (diff) |
Allow calling BackendBase::open multiple times on playback devices
It will not be called while the device is running. If the first call succeeds,
a subsequent call that happens to fail must leave the existing device state as
it was so it can be resumed.
This is a rough first pass. It will fail when trying to re-open the same device
which can only be opened once (for instance, with direct hardware access, on
hardware that doesn't do its own mixing). Some backends won't guarantee the new
device is usable until the reset() or start() call.
Diffstat (limited to 'alc/backends/wasapi.cpp')
-rw-r--r-- | alc/backends/wasapi.cpp | 262 |
1 files changed, 130 insertions, 132 deletions
diff --git a/alc/backends/wasapi.cpp b/alc/backends/wasapi.cpp index 0786a7d7..b594aebe 100644 --- a/alc/backends/wasapi.cpp +++ b/alc/backends/wasapi.cpp @@ -238,10 +238,9 @@ struct DevMap { bool checkName(const al::vector<DevMap> &list, const std::string &name) { - return std::find_if(list.cbegin(), list.cend(), - [&name](const DevMap &entry) -> bool - { return entry.name == name; } - ) != list.cend(); + auto match_name = [&name](const DevMap &entry) -> bool + { return entry.name == name; }; + return std::find_if(list.cbegin(), list.cend(), match_name) != list.cend(); } al::vector<DevMap> PlaybackDevices; @@ -297,26 +296,26 @@ NameGUIDPair get_device_name_and_guid(IMMDevice *device) return std::make_pair(std::move(name), std::move(guid)); } -void get_device_formfactor(IMMDevice *device, EndpointFormFactor *formfactor) +EndpointFormFactor get_device_formfactor(IMMDevice *device) { ComPtr<IPropertyStore> ps; - HRESULT hr = device->OpenPropertyStore(STGM_READ, ps.getPtr()); + HRESULT hr{device->OpenPropertyStore(STGM_READ, ps.getPtr())}; if(FAILED(hr)) { WARN("OpenPropertyStore failed: 0x%08lx\n", hr); - return; + return UnknownFormFactor; } + EndpointFormFactor formfactor{UnknownFormFactor}; PropVariant pvform; - hr = ps->GetValue(reinterpret_cast<const PROPERTYKEY&>(PKEY_AudioEndpoint_FormFactor), pvform.get()); + 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) - *formfactor = UnknownFormFactor; - else + formfactor = static_cast<EndpointFormFactor>(pvform->ulVal); + else if(pvform->vt != VT_EMPTY) WARN("Unexpected PROPVARIANT type: 0x%04x\n", pvform->vt); + return formfactor; } @@ -484,6 +483,7 @@ void TraceFormat(const char *msg, const WAVEFORMATEX *format) enum class MsgType { OpenDevice, + ReopenDevice, ResetDevice, StartDevice, StopDevice, @@ -497,6 +497,7 @@ enum class MsgType { constexpr char MessageStr[static_cast<size_t>(MsgType::Count)][20]{ "Open Device", + "Reopen Device", "Reset Device", "Start Device", "Stop Device", @@ -511,7 +512,7 @@ constexpr char MessageStr[static_cast<size_t>(MsgType::Count)][20]{ struct WasapiProxy { virtual ~WasapiProxy() = default; - virtual HRESULT openProxy() = 0; + virtual HRESULT openProxy(const char *name) = 0; virtual void closeProxy() = 0; virtual HRESULT resetProxy() = 0; @@ -521,19 +522,20 @@ struct WasapiProxy { struct Msg { MsgType mType; WasapiProxy *mProxy; + const char *mParam; std::promise<HRESULT> mPromise; }; static std::deque<Msg> mMsgQueue; static std::mutex mMsgQueueLock; static std::condition_variable mMsgQueueCond; - std::future<HRESULT> pushMessage(MsgType type) + std::future<HRESULT> pushMessage(MsgType type, const char *param=nullptr) { std::promise<HRESULT> promise; std::future<HRESULT> future{promise.get_future()}; { std::lock_guard<std::mutex> _{mMsgQueueLock}; - mMsgQueue.emplace_back(Msg{type, this, std::move(promise)}); + mMsgQueue.emplace_back(Msg{type, this, param, std::move(promise)}); } mMsgQueueCond.notify_one(); return future; @@ -545,7 +547,7 @@ struct WasapiProxy { std::future<HRESULT> future{promise.get_future()}; { std::lock_guard<std::mutex> _{mMsgQueueLock}; - mMsgQueue.emplace_back(Msg{type, nullptr, std::move(promise)}); + mMsgQueue.emplace_back(Msg{type, nullptr, nullptr, std::move(promise)}); } mMsgQueueCond.notify_one(); return future; @@ -600,9 +602,9 @@ int WasapiProxy::messageHandler(std::promise<HRESULT> *promise) Msg msg; while(popMessage(msg)) { - TRACE("Got message \"%s\" (0x%04x, this=%p)\n", + TRACE("Got message \"%s\" (0x%04x, this=%p, param=%p)\n", MessageStr[static_cast<size_t>(msg.mType)], static_cast<uint>(msg.mType), - decltype(std::declval<void*>()){msg.mProxy}); + static_cast<void*>(msg.mProxy), static_cast<const void*>(msg.mParam)); switch(msg.mType) { @@ -611,7 +613,7 @@ int WasapiProxy::messageHandler(std::promise<HRESULT> *promise) if(++deviceCount == 1) hr = cohr = CoInitializeEx(nullptr, COINIT_MULTITHREADED); if(SUCCEEDED(hr)) - hr = msg.mProxy->openProxy(); + hr = msg.mProxy->openProxy(msg.mParam); msg.mPromise.set_value(hr); if(FAILED(hr)) @@ -621,6 +623,11 @@ int WasapiProxy::messageHandler(std::promise<HRESULT> *promise) } continue; + case MsgType::ReopenDevice: + hr = msg.mProxy->openProxy(msg.mParam); + msg.mPromise.set_value(hr); + continue; + case MsgType::ResetDevice: hr = msg.mProxy->resetProxy(); msg.mPromise.set_value(hr); @@ -688,7 +695,7 @@ struct WasapiPlayback final : public BackendBase, WasapiProxy { int mixerProc(); void open(const char *name) override; - HRESULT openProxy() override; + HRESULT openProxy(const char *name) override; void closeProxy() override; bool reset() override; @@ -700,8 +707,6 @@ struct WasapiPlayback final : public BackendBase, WasapiProxy { ClockLatency getClockLatency() override; - std::wstring mDevId; - HRESULT mOpenStatus{E_FAIL}; ComPtr<IMMDevice> mMMDev{nullptr}; ComPtr<IAudioClient> mClient{nullptr}; @@ -796,83 +801,86 @@ void WasapiPlayback::open(const char *name) { HRESULT hr{S_OK}; - mNotifyEvent = CreateEventW(nullptr, FALSE, FALSE, nullptr); - if(mNotifyEvent == nullptr) + if(!mNotifyEvent) { - ERR("Failed to create notify events: %lu\n", GetLastError()); - hr = E_FAIL; - } - - if(SUCCEEDED(hr)) - { - if(name) + mNotifyEvent = CreateEventW(nullptr, FALSE, FALSE, nullptr); + if(mNotifyEvent == nullptr) { - if(PlaybackDevices.empty()) - pushMessage(MsgType::EnumeratePlayback).wait(); - + ERR("Failed to create notify events: %lu\n", GetLastError()); hr = E_FAIL; - auto iter = std::find_if(PlaybackDevices.cbegin(), PlaybackDevices.cend(), - [name](const DevMap &entry) -> bool - { return entry.name == name || entry.endpoint_guid == name; }); - if(iter == PlaybackDevices.cend()) - { - const std::wstring wname{utf8_to_wstr(name)}; - iter = std::find_if(PlaybackDevices.cbegin(), PlaybackDevices.cend(), - [&wname](const DevMap &entry) -> bool - { return entry.devid == wname; }); - } - if(iter == PlaybackDevices.cend()) - WARN("Failed to find device name matching \"%s\"\n", name); - else - { - mDevId = iter->devid; - mDevice->DeviceName = iter->name; - hr = S_OK; - } } } if(SUCCEEDED(hr)) - hr = pushMessage(MsgType::OpenDevice).get(); - mOpenStatus = hr; - - if(FAILED(hr)) { - if(mNotifyEvent != nullptr) - CloseHandle(mNotifyEvent); - mNotifyEvent = nullptr; + if(name && PlaybackDevices.empty()) + pushMessage(MsgType::EnumeratePlayback).wait(); - mDevId.clear(); + if(SUCCEEDED(mOpenStatus)) + hr = pushMessage(MsgType::ReopenDevice, name).get(); + else + { + hr = pushMessage(MsgType::OpenDevice, name).get(); + mOpenStatus = hr; + } + } + if(FAILED(hr)) throw al::backend_exception{al::backend_error::DeviceError, "Device init failed: 0x%08lx", hr}; - } } -HRESULT WasapiPlayback::openProxy() +HRESULT WasapiPlayback::openProxy(const char *name) { + const wchar_t *devid{nullptr}; + if(name) + { + auto iter = std::find_if(PlaybackDevices.cbegin(), PlaybackDevices.cend(), + [name](const DevMap &entry) -> bool + { return entry.name == name || entry.endpoint_guid == name; }); + if(iter == PlaybackDevices.cend()) + { + const std::wstring wname{utf8_to_wstr(name)}; + iter = std::find_if(PlaybackDevices.cbegin(), PlaybackDevices.cend(), + [&wname](const DevMap &entry) -> bool + { return entry.devid == wname; }); + } + if(iter == PlaybackDevices.cend()) + { + WARN("Failed to find device name matching \"%s\"\n", name); + return E_FAIL; + } + name = iter->name.c_str(); + devid = iter->devid.c_str(); + } + void *ptr; HRESULT hr{CoCreateInstance(CLSID_MMDeviceEnumerator, nullptr, CLSCTX_INPROC_SERVER, IID_IMMDeviceEnumerator, &ptr)}; - if(SUCCEEDED(hr)) + ComPtr<IMMDeviceEnumerator> enumerator{static_cast<IMMDeviceEnumerator*>(ptr)}; + ComPtr<IMMDevice> mmdev; + if(!devid) + hr = enumerator->GetDefaultAudioEndpoint(eRender, eMultimedia, mmdev.getPtr()); + else + hr = enumerator->GetDevice(devid, mmdev.getPtr()); + enumerator = nullptr; + if(FAILED(hr)) { - ComPtr<IMMDeviceEnumerator> enumerator{static_cast<IMMDeviceEnumerator*>(ptr)}; - if(mDevId.empty()) - hr = enumerator->GetDefaultAudioEndpoint(eRender, eMultimedia, mMMDev.getPtr()); - else - hr = enumerator->GetDevice(mDevId.c_str(), mMMDev.getPtr()); + WARN("Failed to open device \"%s\"\n", name?name:"(default)"); + return hr; } - if(SUCCEEDED(hr)) - hr = mMMDev->Activate(IID_IAudioClient, CLSCTX_INPROC_SERVER, nullptr, &ptr); - if(SUCCEEDED(hr)) + + hr = mmdev->Activate(IID_IAudioClient, CLSCTX_INPROC_SERVER, nullptr, &ptr); + if(FAILED(hr)) { - mClient = ComPtr<IAudioClient>{static_cast<IAudioClient*>(ptr)}; - if(mDevice->DeviceName.empty()) - mDevice->DeviceName = get_device_name_and_guid(mMMDev.get()).first; + WARN("Failed to activate device \"%s\"\n", name?name:"(default)"); + return hr; } - if(FAILED(hr)) - mMMDev = nullptr; + mClient = ComPtr<IAudioClient>{static_cast<IAudioClient*>(ptr)}; + mMMDev = std::move(mmdev); + if(name) mDevice->DeviceName = name; + else mDevice->DeviceName = get_device_name_and_guid(mMMDev.get()).first; return hr; } @@ -1105,8 +1113,7 @@ HRESULT WasapiPlayback::resetProxy() } mFrameStep = OutputType.Format.nChannels; - EndpointFormFactor formfactor{UnknownFormFactor}; - get_device_formfactor(mMMDev.get(), &formfactor); + const EndpointFormFactor formfactor{get_device_formfactor(mMMDev.get())}; mDevice->IsHeadphones = (mDevice->FmtChans == DevFmtStereo && (formfactor == Headphones || formfactor == Headset)); @@ -1226,7 +1233,7 @@ struct WasapiCapture final : public BackendBase, WasapiProxy { int recordProc(); void open(const char *name) override; - HRESULT openProxy() override; + HRESULT openProxy(const char *name) override; void closeProxy() override; HRESULT resetProxy() override; @@ -1238,8 +1245,6 @@ struct WasapiCapture final : public BackendBase, WasapiProxy { void captureSamples(al::byte *buffer, uint samples) override; uint availableSamples() override; - std::wstring mDevId; - HRESULT mOpenStatus{E_FAIL}; ComPtr<IMMDevice> mMMDev{nullptr}; ComPtr<IAudioClient> mClient{nullptr}; @@ -1373,48 +1378,15 @@ void WasapiCapture::open(const char *name) if(SUCCEEDED(hr)) { - if(name) - { - if(CaptureDevices.empty()) - pushMessage(MsgType::EnumerateCapture).wait(); - - hr = E_FAIL; - auto iter = std::find_if(CaptureDevices.cbegin(), CaptureDevices.cend(), - [name](const DevMap &entry) -> bool - { return entry.name == name || entry.endpoint_guid == name; }); - if(iter == CaptureDevices.cend()) - { - const std::wstring wname{utf8_to_wstr(name)}; - iter = std::find_if(CaptureDevices.cbegin(), CaptureDevices.cend(), - [&wname](const DevMap &entry) -> bool - { return entry.devid == wname; }); - } - if(iter == CaptureDevices.cend()) - WARN("Failed to find device name matching \"%s\"\n", name); - else - { - mDevId = iter->devid; - mDevice->DeviceName = iter->name; - hr = S_OK; - } - } + if(name && CaptureDevices.empty()) + pushMessage(MsgType::EnumerateCapture).wait(); + hr = pushMessage(MsgType::OpenDevice, name).get(); } - - if(SUCCEEDED(hr)) - hr = pushMessage(MsgType::OpenDevice).get(); mOpenStatus = hr; if(FAILED(hr)) - { - if(mNotifyEvent != nullptr) - CloseHandle(mNotifyEvent); - mNotifyEvent = nullptr; - - mDevId.clear(); - throw al::backend_exception{al::backend_error::DeviceError, "Device init failed: 0x%08lx", hr}; - } hr = pushMessage(MsgType::ResetDevice).get(); if(FAILED(hr)) @@ -1425,30 +1397,56 @@ void WasapiCapture::open(const char *name) } } -HRESULT WasapiCapture::openProxy() +HRESULT WasapiCapture::openProxy(const char *name) { + const wchar_t *devid{nullptr}; + if(name) + { + auto iter = std::find_if(CaptureDevices.cbegin(), CaptureDevices.cend(), + [name](const DevMap &entry) -> bool + { return entry.name == name || entry.endpoint_guid == name; }); + if(iter == CaptureDevices.cend()) + { + const std::wstring wname{utf8_to_wstr(name)}; + iter = std::find_if(CaptureDevices.cbegin(), CaptureDevices.cend(), + [&wname](const DevMap &entry) -> bool + { return entry.devid == wname; }); + } + if(iter == CaptureDevices.cend()) + { + WARN("Failed to find device name matching \"%s\"\n", name); + return E_FAIL; + } + name = iter->name.c_str(); + devid = iter->devid.c_str(); + } + void *ptr; HRESULT hr{CoCreateInstance(CLSID_MMDeviceEnumerator, nullptr, CLSCTX_INPROC_SERVER, IID_IMMDeviceEnumerator, &ptr)}; - if(SUCCEEDED(hr)) - { - ComPtr<IMMDeviceEnumerator> enumerator{static_cast<IMMDeviceEnumerator*>(ptr)}; - if(mDevId.empty()) - hr = enumerator->GetDefaultAudioEndpoint(eCapture, eMultimedia, mMMDev.getPtr()); - else - hr = enumerator->GetDevice(mDevId.c_str(), mMMDev.getPtr()); - } - if(SUCCEEDED(hr)) - hr = mMMDev->Activate(IID_IAudioClient, CLSCTX_INPROC_SERVER, nullptr, &ptr); - if(SUCCEEDED(hr)) + ComPtr<IMMDeviceEnumerator> enumerator{static_cast<IMMDeviceEnumerator*>(ptr)}; + if(!devid) + hr = enumerator->GetDefaultAudioEndpoint(eCapture, eMultimedia, mMMDev.getPtr()); + else + hr = enumerator->GetDevice(devid, mMMDev.getPtr()); + enumerator = nullptr; + if(FAILED(hr)) { - mClient = ComPtr<IAudioClient>{static_cast<IAudioClient*>(ptr)}; - if(mDevice->DeviceName.empty()) - mDevice->DeviceName = get_device_name_and_guid(mMMDev.get()).first; + WARN("Failed to open device \"%s\"\n", name?name:"(default)"); + return hr; } + hr = mMMDev->Activate(IID_IAudioClient, CLSCTX_INPROC_SERVER, nullptr, &ptr); if(FAILED(hr)) + { + WARN("Failed to activate device \"%s\"\n", name?name:"(default)"); mMMDev = nullptr; + return hr; + } + + mClient = ComPtr<IAudioClient>{static_cast<IAudioClient*>(ptr)}; + if(name) mDevice->DeviceName = name; + else mDevice->DeviceName = get_device_name_and_guid(mMMDev.get()).first; return hr; } |