diff options
-rw-r--r-- | Alc/backends/wasapi.cpp | 423 |
1 files changed, 160 insertions, 263 deletions
diff --git a/Alc/backends/wasapi.cpp b/Alc/backends/wasapi.cpp index 8f7c9f47..9dfbd247 100644 --- a/Alc/backends/wasapi.cpp +++ b/Alc/backends/wasapi.cpp @@ -40,12 +40,16 @@ #include <ksmedia.h> #endif +#include <deque> +#include <mutex> #include <atomic> #include <thread> #include <vector> #include <string> +#include <future> #include <algorithm> #include <functional> +#include <condition_variable> #include "alMain.h" #include "alu.h" @@ -136,48 +140,6 @@ al::vector<DevMap> PlaybackDevices; al::vector<DevMap> CaptureDevices; -HANDLE ThreadHdl; -DWORD ThreadID; - -struct ThreadRequest { - HANDLE FinishedEvt; - HRESULT result; -}; - - -#define WM_USER_First (WM_USER+0) -#define WM_USER_OpenDevice (WM_USER+0) -#define WM_USER_ResetDevice (WM_USER+1) -#define WM_USER_StartDevice (WM_USER+2) -#define WM_USER_StopDevice (WM_USER+3) -#define WM_USER_CloseDevice (WM_USER+4) -#define WM_USER_Enumerate (WM_USER+5) -#define WM_USER_Last (WM_USER+5) - -constexpr char MessageStr[WM_USER_Last+1-WM_USER][20] = { - "Open Device", - "Reset Device", - "Start Device", - "Stop Device", - "Close Device", - "Enumerate Devices", -}; - -inline void ReturnMsgResponse(ThreadRequest *req, HRESULT res) -{ - req->result = res; - SetEvent(req->FinishedEvt); -} - -HRESULT WaitForResponse(ThreadRequest *req) -{ - if(WaitForSingleObject(req->FinishedEvt, INFINITE) == WAIT_OBJECT_0) - return req->result; - ERR("Message response error: %lu\n", GetLastError()); - return E_FAIL; -} - - using NameGUIDPair = std::pair<std::string,std::string>; NameGUIDPair get_device_name_and_guid(IMMDevice *device) { @@ -338,6 +300,31 @@ HRESULT probe_devices(IMMDeviceEnumerator *devenum, EDataFlow flowdir, al::vecto } +enum class MsgType : unsigned int { + OpenDevice, + ResetDevice, + StartDevice, + StopDevice, + CloseDevice, + EnumeratePlayback, + EnumerateCapture, + QuitThread, + + Count +}; + +constexpr char MessageStr[static_cast<unsigned int>(MsgType::Count)][20]{ + "Open Device", + "Reset Device", + "Start Device", + "Stop Device", + "Close Device", + "Enumerate Playback", + "Enumerate Capture", + "Quit" +}; + + /* Proxy interface used by the message handler. */ struct WasapiProxy { virtual HRESULT openProxy() = 0; @@ -347,132 +334,153 @@ struct WasapiProxy { virtual HRESULT startProxy() = 0; virtual void stopProxy() = 0; - static DWORD CALLBACK messageHandler(void *ptr); + struct Msg { + MsgType mType; + WasapiProxy *mProxy; + 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::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)}); + } + 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> _{mMsgQueueLock}; + mMsgQueue.emplace_back(Msg{type, nullptr, std::move(promise)}); + } + mMsgQueueCond.notify_one(); + return future; + } + + static bool popMessage(Msg &msg) + { + std::unique_lock<std::mutex> lock{mMsgQueueLock}; + while(mMsgQueue.empty()) + mMsgQueueCond.wait(lock); + msg = std::move(mMsgQueue.front()); + mMsgQueue.pop_front(); + return msg.mType != MsgType::QuitThread; + } + + static int messageHandler(std::promise<HRESULT> *promise); static constexpr inline const char *CurrentPrefix() noexcept { return "WasapiProxy::"; } }; +std::deque<WasapiProxy::Msg> WasapiProxy::mMsgQueue; +std::mutex WasapiProxy::mMsgQueueLock; +std::condition_variable WasapiProxy::mMsgQueueCond; -DWORD WasapiProxy::messageHandler(void *ptr) +int WasapiProxy::messageHandler(std::promise<HRESULT> *promise) { - auto req = static_cast<ThreadRequest*>(ptr); - TRACE("Starting message thread\n"); HRESULT cohr = CoInitializeEx(nullptr, COINIT_MULTITHREADED); if(FAILED(cohr)) { WARN("Failed to initialize COM: 0x%08lx\n", cohr); - ReturnMsgResponse(req, cohr); + promise->set_value(cohr); return 0; } + void *ptr{}; HRESULT hr{CoCreateInstance(CLSID_MMDeviceEnumerator, nullptr, CLSCTX_INPROC_SERVER, IID_IMMDeviceEnumerator, &ptr)}; if(FAILED(hr)) { WARN("Failed to create IMMDeviceEnumerator instance: 0x%08lx\n", hr); + promise->set_value(hr); CoUninitialize(); - ReturnMsgResponse(req, hr); return 0; } auto Enumerator = static_cast<IMMDeviceEnumerator*>(ptr); Enumerator->Release(); Enumerator = nullptr; - CoUninitialize(); - /* HACK: Force Windows to create a message queue for this thread before - * returning success, otherwise PostThreadMessage may fail if it gets - * called before GetMessage. - */ - MSG msg; - PeekMessage(&msg, nullptr, WM_USER, WM_USER, PM_NOREMOVE); - TRACE("Message thread initialization complete\n"); - ReturnMsgResponse(req, S_OK); + promise->set_value(S_OK); + promise = nullptr; TRACE("Starting message loop\n"); ALuint deviceCount{0}; - while(GetMessage(&msg, nullptr, WM_USER_First, WM_USER_Last)) + Msg msg; + while(popMessage(msg)) { - TRACE("Got message \"%s\" (0x%04x, lparam=%p, wparam=%p)\n", - (msg.message >= WM_USER && msg.message <= WM_USER_Last) ? - MessageStr[msg.message-WM_USER] : "Unknown", - msg.message, (void*)msg.lParam, (void*)msg.wParam - ); + TRACE("Got message \"%s\" (0x%04x, this=%p)\n", + MessageStr[static_cast<unsigned int>(msg.mType)], static_cast<unsigned int>(msg.mType), + msg.mProxy); - WasapiProxy *proxy{nullptr}; - switch(msg.message) + switch(msg.mType) { - case WM_USER_OpenDevice: - req = reinterpret_cast<ThreadRequest*>(msg.wParam); - proxy = reinterpret_cast<WasapiProxy*>(msg.lParam); - + case MsgType::OpenDevice: hr = cohr = S_OK; if(++deviceCount == 1) hr = cohr = CoInitializeEx(nullptr, COINIT_MULTITHREADED); if(SUCCEEDED(hr)) - hr = proxy->openProxy(); + hr = msg.mProxy->openProxy(); + msg.mPromise.set_value(hr); + if(FAILED(hr)) { if(--deviceCount == 0 && SUCCEEDED(cohr)) CoUninitialize(); } - - ReturnMsgResponse(req, hr); continue; - case WM_USER_ResetDevice: - req = reinterpret_cast<ThreadRequest*>(msg.wParam); - proxy = reinterpret_cast<WasapiProxy*>(msg.lParam); - - hr = proxy->resetProxy(); - ReturnMsgResponse(req, hr); + case MsgType::ResetDevice: + hr = msg.mProxy->resetProxy(); + msg.mPromise.set_value(hr); continue; - case WM_USER_StartDevice: - req = reinterpret_cast<ThreadRequest*>(msg.wParam); - proxy = reinterpret_cast<WasapiProxy*>(msg.lParam); - - hr = proxy->startProxy(); - ReturnMsgResponse(req, hr); + case MsgType::StartDevice: + hr = msg.mProxy->startProxy(); + msg.mPromise.set_value(hr); continue; - case WM_USER_StopDevice: - req = reinterpret_cast<ThreadRequest*>(msg.wParam); - proxy = reinterpret_cast<WasapiProxy*>(msg.lParam); - - proxy->stopProxy(); - ReturnMsgResponse(req, S_OK); + case MsgType::StopDevice: + msg.mProxy->stopProxy(); + msg.mPromise.set_value(S_OK); continue; - case WM_USER_CloseDevice: - req = reinterpret_cast<ThreadRequest*>(msg.wParam); - proxy = reinterpret_cast<WasapiProxy*>(msg.lParam); + case MsgType::CloseDevice: + msg.mProxy->closeProxy(); + msg.mPromise.set_value(S_OK); - proxy->closeProxy(); if(--deviceCount == 0) CoUninitialize(); - - ReturnMsgResponse(req, S_OK); continue; - case WM_USER_Enumerate: - req = reinterpret_cast<ThreadRequest*>(msg.wParam); - + case MsgType::EnumeratePlayback: + case MsgType::EnumerateCapture: hr = cohr = S_OK; if(++deviceCount == 1) hr = cohr = CoInitializeEx(nullptr, COINIT_MULTITHREADED); if(SUCCEEDED(hr)) hr = CoCreateInstance(CLSID_MMDeviceEnumerator, nullptr, CLSCTX_INPROC_SERVER, IID_IMMDeviceEnumerator, &ptr); - if(SUCCEEDED(hr)) + if(FAILED(hr)) + msg.mPromise.set_value(hr); + else { Enumerator = static_cast<IMMDeviceEnumerator*>(ptr); - if(msg.lParam == static_cast<LPARAM>(DevProbe::Playback)) + if(msg.mType == MsgType::EnumeratePlayback) hr = probe_devices(Enumerator, eRender, PlaybackDevices); - else if(msg.lParam == static_cast<LPARAM>(DevProbe::Capture)) + else if(msg.mType == MsgType::EnumerateCapture) hr = probe_devices(Enumerator, eCapture, CaptureDevices); + msg.mPromise.set_value(hr); Enumerator->Release(); Enumerator = nullptr; @@ -480,12 +488,11 @@ DWORD WasapiProxy::messageHandler(void *ptr) if(--deviceCount == 0 && SUCCEEDED(cohr)) CoUninitialize(); - - ReturnMsgResponse(req, hr); continue; default: - ERR("Unexpected message: %u\n", msg.message); + ERR("Unexpected message: %u\n", static_cast<unsigned int>(msg.mType)); + msg.mPromise.set_value(E_FAIL); continue; } } @@ -521,8 +528,6 @@ struct WasapiPlayback final : public BackendBase, WasapiProxy { IAudioRenderClient *mRender{nullptr}; HANDLE mNotifyEvent{nullptr}; - HANDLE mMsgEvent{nullptr}; - std::atomic<UINT32> mPadding{0u}; std::atomic<bool> mKillNow{true}; @@ -534,23 +539,11 @@ struct WasapiPlayback final : public BackendBase, WasapiProxy { WasapiPlayback::~WasapiPlayback() { - if(mMsgEvent) - { - ThreadRequest req{ mMsgEvent, 0 }; - auto proxy = static_cast<WasapiProxy*>(this); - if(PostThreadMessage(ThreadID, WM_USER_CloseDevice, (WPARAM)&req, (LPARAM)proxy)) - (void)WaitForResponse(&req); - - CloseHandle(mMsgEvent); - mMsgEvent = nullptr; - } + pushMessage(MsgType::CloseDevice).wait(); if(mNotifyEvent != nullptr) CloseHandle(mNotifyEvent); mNotifyEvent = nullptr; - if(mMsgEvent != nullptr) - CloseHandle(mMsgEvent); - mMsgEvent = nullptr; } @@ -659,10 +652,9 @@ ALCenum WasapiPlayback::open(const ALCchar *name) HRESULT hr{S_OK}; mNotifyEvent = CreateEventW(nullptr, FALSE, FALSE, nullptr); - mMsgEvent = CreateEventW(nullptr, FALSE, FALSE, nullptr); - if(mNotifyEvent == nullptr || mMsgEvent == nullptr) + if(mNotifyEvent == nullptr) { - ERR("Failed to create message events: %lu\n", GetLastError()); + ERR("Failed to create notify events: %lu\n", GetLastError()); hr = E_FAIL; } @@ -671,11 +663,7 @@ ALCenum WasapiPlayback::open(const ALCchar *name) if(name) { if(PlaybackDevices.empty()) - { - ThreadRequest req = { mMsgEvent, 0 }; - if(PostThreadMessage(ThreadID, WM_USER_Enumerate, (WPARAM)&req, static_cast<LPARAM>(DevProbe::Playback))) - (void)WaitForResponse(&req); - } + pushMessage(MsgType::EnumeratePlayback).wait(); hr = E_FAIL; auto iter = std::find_if(PlaybackDevices.cbegin(), PlaybackDevices.cend(), @@ -702,25 +690,13 @@ ALCenum WasapiPlayback::open(const ALCchar *name) } if(SUCCEEDED(hr)) - { - ThreadRequest req{ mMsgEvent, 0 }; - auto proxy = static_cast<WasapiProxy*>(this); - - hr = E_FAIL; - if(PostThreadMessage(ThreadID, WM_USER_OpenDevice, (WPARAM)&req, (LPARAM)proxy)) - hr = WaitForResponse(&req); - else - ERR("Failed to post thread message: %lu\n", GetLastError()); - } + hr = pushMessage(MsgType::OpenDevice).get(); if(FAILED(hr)) { if(mNotifyEvent != nullptr) CloseHandle(mNotifyEvent); mNotifyEvent = nullptr; - if(mMsgEvent != nullptr) - CloseHandle(mMsgEvent); - mMsgEvent = nullptr; mDevId.clear(); @@ -777,12 +753,7 @@ void WasapiPlayback::closeProxy() ALCboolean WasapiPlayback::reset() { - ThreadRequest req{ mMsgEvent, 0 }; - auto proxy = static_cast<WasapiProxy*>(this); - HRESULT hr{E_FAIL}; - if(PostThreadMessage(ThreadID, WM_USER_ResetDevice, (WPARAM)&req, (LPARAM)proxy)) - hr = WaitForResponse(&req); - + HRESULT hr{pushMessage(MsgType::ResetDevice).get()}; return SUCCEEDED(hr) ? ALC_TRUE : ALC_FALSE; } @@ -1044,12 +1015,7 @@ HRESULT WasapiPlayback::resetProxy() ALCboolean WasapiPlayback::start() { - ThreadRequest req{ mMsgEvent, 0 }; - auto proxy = static_cast<WasapiProxy*>(this); - HRESULT hr{E_FAIL}; - if(PostThreadMessage(ThreadID, WM_USER_StartDevice, (WPARAM)&req, (LPARAM)proxy)) - hr = WaitForResponse(&req); - + HRESULT hr{pushMessage(MsgType::StartDevice).get()}; return SUCCEEDED(hr) ? ALC_TRUE : ALC_FALSE; } @@ -1089,12 +1055,7 @@ HRESULT WasapiPlayback::startProxy() void WasapiPlayback::stop() -{ - ThreadRequest req{ mMsgEvent, 0 }; - auto proxy = static_cast<WasapiProxy*>(this); - if(PostThreadMessage(ThreadID, WM_USER_StopDevice, (WPARAM)&req, (LPARAM)proxy)) - (void)WaitForResponse(&req); -} +{ pushMessage(MsgType::StopDevice).wait(); } void WasapiPlayback::stopProxy() { @@ -1150,8 +1111,6 @@ struct WasapiCapture final : public BackendBase, WasapiProxy { IAudioCaptureClient *mCapture{nullptr}; HANDLE mNotifyEvent{nullptr}; - HANDLE mMsgEvent{nullptr}; - ChannelConverterPtr mChannelConv; SampleConverterPtr mSampleConv; RingBufferPtr mRing; @@ -1165,16 +1124,7 @@ struct WasapiCapture final : public BackendBase, WasapiProxy { WasapiCapture::~WasapiCapture() { - if(mMsgEvent) - { - ThreadRequest req{ mMsgEvent, 0 }; - auto proxy = static_cast<WasapiProxy*>(this); - if(PostThreadMessage(ThreadID, WM_USER_CloseDevice, (WPARAM)&req, (LPARAM)proxy)) - (void)WaitForResponse(&req); - - CloseHandle(mMsgEvent); - mMsgEvent = nullptr; - } + pushMessage(MsgType::CloseDevice).wait(); if(mNotifyEvent != nullptr) CloseHandle(mNotifyEvent); @@ -1279,10 +1229,9 @@ ALCenum WasapiCapture::open(const ALCchar *name) HRESULT hr{S_OK}; mNotifyEvent = CreateEventW(nullptr, FALSE, FALSE, nullptr); - mMsgEvent = CreateEventW(nullptr, FALSE, FALSE, nullptr); - if(mNotifyEvent == nullptr || mMsgEvent == nullptr) + if(mNotifyEvent == nullptr) { - ERR("Failed to create message events: %lu\n", GetLastError()); + ERR("Failed to create notify event: %lu\n", GetLastError()); hr = E_FAIL; } @@ -1291,11 +1240,7 @@ ALCenum WasapiCapture::open(const ALCchar *name) if(name) { if(CaptureDevices.empty()) - { - ThreadRequest req{ mMsgEvent, 0 }; - if(PostThreadMessage(ThreadID, WM_USER_Enumerate, (WPARAM)&req, static_cast<LPARAM>(DevProbe::Capture))) - (void)WaitForResponse(&req); - } + pushMessage(MsgType::EnumerateCapture).wait(); hr = E_FAIL; auto iter = std::find_if(CaptureDevices.cbegin(), CaptureDevices.cend(), @@ -1322,46 +1267,26 @@ ALCenum WasapiCapture::open(const ALCchar *name) } if(SUCCEEDED(hr)) - { - ThreadRequest req{ mMsgEvent, 0 }; - auto proxy = static_cast<WasapiProxy*>(this); - hr = E_FAIL; - if(PostThreadMessage(ThreadID, WM_USER_OpenDevice, (WPARAM)&req, (LPARAM)proxy)) - hr = WaitForResponse(&req); - else - ERR("Failed to post thread message: %lu\n", GetLastError()); - } + hr = pushMessage(MsgType::OpenDevice).get(); if(FAILED(hr)) { if(mNotifyEvent != nullptr) CloseHandle(mNotifyEvent); mNotifyEvent = nullptr; - if(mMsgEvent != nullptr) - CloseHandle(mMsgEvent); - mMsgEvent = nullptr; mDevId.clear(); ERR("Device init failed: 0x%08lx\n", hr); return ALC_INVALID_VALUE; } - else - { - ThreadRequest req{ mMsgEvent, 0 }; - auto proxy = static_cast<WasapiProxy*>(this); - hr = E_FAIL; - if(PostThreadMessage(ThreadID, WM_USER_ResetDevice, (WPARAM)&req, (LPARAM)proxy)) - hr = WaitForResponse(&req); - else - ERR("Failed to post thread message: %lu\n", GetLastError()); - if(FAILED(hr)) - { - if(hr == E_OUTOFMEMORY) - return ALC_OUT_OF_MEMORY; - return ALC_INVALID_VALUE; - } + hr = pushMessage(MsgType::ResetDevice).get(); + if(FAILED(hr)) + { + if(hr == E_OUTOFMEMORY) + return ALC_OUT_OF_MEMORY; + return ALC_INVALID_VALUE; } return ALC_NO_ERROR; @@ -1645,12 +1570,7 @@ HRESULT WasapiCapture::resetProxy() ALCboolean WasapiCapture::start() { - ThreadRequest req{ mMsgEvent, 0 }; - auto proxy = static_cast<WasapiProxy*>(this); - HRESULT hr{E_FAIL}; - if(PostThreadMessage(ThreadID, WM_USER_StartDevice, (WPARAM)&req, (LPARAM)proxy)) - hr = WaitForResponse(&req); - + HRESULT hr{pushMessage(MsgType::StartDevice).get()}; return SUCCEEDED(hr) ? ALC_TRUE : ALC_FALSE; } @@ -1693,12 +1613,7 @@ HRESULT WasapiCapture::startProxy() void WasapiCapture::stop() -{ - ThreadRequest req{ mMsgEvent, 0 }; - auto proxy = static_cast<WasapiProxy*>(this); - if(PostThreadMessage(ThreadID, WM_USER_StopDevice, (WPARAM)&req, (LPARAM)proxy)) - (void)WaitForResponse(&req); -} +{ pushMessage(MsgType::StopDevice).wait(); } void WasapiCapture::stopProxy() { @@ -1729,22 +1644,17 @@ ALCenum WasapiCapture::captureSamples(void *buffer, ALCuint samples) bool WasapiBackendFactory::init() { - static HRESULT InitResult; + static HRESULT InitResult{E_FAIL}; - if(!ThreadHdl) + if(FAILED(InitResult)) try { - ThreadRequest req; - InitResult = E_FAIL; + std::promise<HRESULT> promise; + auto future = promise.get_future(); - req.FinishedEvt = CreateEventW(nullptr, FALSE, FALSE, nullptr); - if(req.FinishedEvt == nullptr) - ERR("Failed to create event: %lu\n", GetLastError()); - else - { - ThreadHdl = CreateThread(nullptr, 0, &WasapiProxy::messageHandler, &req, 0, &ThreadID); - if(ThreadHdl != nullptr) InitResult = WaitForResponse(&req); - CloseHandle(req.FinishedEvt); - } + std::thread{&WasapiProxy::messageHandler, &promise}.detach(); + InitResult = future.get(); + } + catch(...) { } return SUCCEEDED(InitResult) ? ALC_TRUE : ALC_FALSE; @@ -1755,13 +1665,8 @@ void WasapiBackendFactory::deinit() PlaybackDevices.clear(); CaptureDevices.clear(); - if(ThreadHdl) - { - TRACE("Sending WM_QUIT to Thread %04lx\n", ThreadID); - PostThreadMessage(ThreadID, WM_QUIT, 0, 0); - CloseHandle(ThreadHdl); - ThreadHdl = nullptr; - } + TRACE("Sending quit message\n"); + WasapiProxy::pushMessageStatic(MsgType::QuitThread); } bool WasapiBackendFactory::querySupport(BackendType type) @@ -1769,35 +1674,27 @@ bool WasapiBackendFactory::querySupport(BackendType type) void WasapiBackendFactory::probe(DevProbe type, std::string *outnames) { - ThreadRequest req{ nullptr, 0 }; - - req.FinishedEvt = CreateEventW(nullptr, FALSE, FALSE, nullptr); - if(req.FinishedEvt == nullptr) - ERR("Failed to create event: %lu\n", GetLastError()); - else + auto add_device = [outnames](const DevMap &entry) -> void { - auto add_device = [outnames](const DevMap &entry) -> void - { - /* +1 to also append the null char (to ensure a null-separated list - * and double-null terminated list). - */ - outnames->append(entry.name.c_str(), entry.name.length()+1); - }; - HRESULT hr = E_FAIL; - if(PostThreadMessage(ThreadID, WM_USER_Enumerate, (WPARAM)&req, static_cast<LPARAM>(type))) - hr = WaitForResponse(&req); - if(SUCCEEDED(hr)) switch(type) - { - case DevProbe::Playback: + /* +1 to also append the null char (to ensure a null-separated list and + * double-null terminated list). + */ + outnames->append(entry.name.c_str(), entry.name.length()+1); + }; + HRESULT hr{}; + switch(type) + { + case DevProbe::Playback: + hr = WasapiProxy::pushMessageStatic(MsgType::EnumeratePlayback).get(); + if(SUCCEEDED(hr)) std::for_each(PlaybackDevices.cbegin(), PlaybackDevices.cend(), add_device); - break; + break; - case DevProbe::Capture: + case DevProbe::Capture: + hr = WasapiProxy::pushMessageStatic(MsgType::EnumerateCapture).get(); + if(SUCCEEDED(hr)) std::for_each(CaptureDevices.cbegin(), CaptureDevices.cend(), add_device); - break; - } - CloseHandle(req.FinishedEvt); - req.FinishedEvt = nullptr; + break; } } |