diff --git a/plugins/win-wasapi/CMakeLists.txt b/plugins/win-wasapi/CMakeLists.txt index fa801ddde..6838a62a0 100644 --- a/plugins/win-wasapi/CMakeLists.txt +++ b/plugins/win-wasapi/CMakeLists.txt @@ -5,7 +5,8 @@ legacy_check() add_library(win-wasapi MODULE) add_library(OBS::wasapi ALIAS win-wasapi) -target_sources(win-wasapi PRIVATE enum-wasapi.cpp enum-wasapi.hpp plugin-main.cpp win-wasapi.cpp) +target_sources(win-wasapi PRIVATE win-wasapi.cpp wasapi-notify.cpp wasapi-notify.hpp enum-wasapi.cpp enum-wasapi.hpp + plugin-main.cpp) configure_file(cmake/windows/obs-module.rc.in win-wasapi.rc) target_sources(win-wasapi PRIVATE win-wasapi.rc) diff --git a/plugins/win-wasapi/plugin-main.cpp b/plugins/win-wasapi/plugin-main.cpp index 6a1a6157d..10c005f4e 100644 --- a/plugins/win-wasapi/plugin-main.cpp +++ b/plugins/win-wasapi/plugin-main.cpp @@ -1,9 +1,12 @@ +#include "wasapi-notify.hpp" + #include #include OBS_DECLARE_MODULE() OBS_MODULE_USE_DEFAULT_LOCALE("win-wasapi", "en-US") + MODULE_EXPORT const char *obs_module_description(void) { return "Windows WASAPI audio input/output sources"; @@ -13,6 +16,26 @@ void RegisterWASAPIInput(); void RegisterWASAPIDeviceOutput(); void RegisterWASAPIProcessOutput(); +WASAPINotify *notify = nullptr; + +static void default_device_changed_callback(EDataFlow flow, ERole, LPCWSTR) +{ + const char *id; + obs_get_audio_monitoring_device(nullptr, &id); + + if (!id || strcmp(id, "default") != 0) + return; + + if (flow != eRender) + return; + + auto task = [](void *) { + obs_reset_audio_monitoring(); + }; + + obs_queue_task(OBS_TASK_UI, task, nullptr, false); +} + bool obs_module_load(void) { /* MS says 20348, but process filtering seems to work earlier */ @@ -30,5 +53,23 @@ bool obs_module_load(void) RegisterWASAPIDeviceOutput(); if (process_filter_supported) RegisterWASAPIProcessOutput(); + + notify = new WASAPINotify(); + notify->AddDefaultDeviceChangedCallback( + obs_current_module(), default_device_changed_callback); + return true; } + +void obs_module_unload(void) +{ + if (notify) { + delete notify; + notify = nullptr; + } +} + +WASAPINotify *GetNotify() +{ + return notify; +} diff --git a/plugins/win-wasapi/wasapi-notify.cpp b/plugins/win-wasapi/wasapi-notify.cpp new file mode 100644 index 000000000..33d96935a --- /dev/null +++ b/plugins/win-wasapi/wasapi-notify.cpp @@ -0,0 +1,121 @@ +#include "wasapi-notify.hpp" + +#include +#include + +#include + +class NotificationClient : public IMMNotificationClient { + volatile long refs = 1; + WASAPINotifyDefaultDeviceChangedCallback cb; + +public: + NotificationClient(WASAPINotifyDefaultDeviceChangedCallback cb) : cb(cb) + { + assert(cb); + } + + STDMETHODIMP_(ULONG) AddRef() + { + return (ULONG)os_atomic_inc_long(&refs); + } + + STDMETHODIMP_(ULONG) STDMETHODCALLTYPE Release() + { + long val = os_atomic_dec_long(&refs); + if (val == 0) + delete this; + return (ULONG)val; + } + + STDMETHODIMP QueryInterface(REFIID riid, void **ptr) + { + if (riid == IID_IUnknown) { + *ptr = (IUnknown *)this; + } else if (riid == __uuidof(IMMNotificationClient)) { + *ptr = (IMMNotificationClient *)this; + } else { + *ptr = nullptr; + return E_NOINTERFACE; + } + + InterlockedIncrement(&refs); + return S_OK; + } + + STDMETHODIMP OnDeviceAdded(LPCWSTR) { return S_OK; } + + STDMETHODIMP OnDeviceRemoved(LPCWSTR) { return S_OK; } + + STDMETHODIMP OnDeviceStateChanged(LPCWSTR, DWORD) { return S_OK; } + + STDMETHODIMP OnPropertyValueChanged(LPCWSTR, const PROPERTYKEY) + { + return S_OK; + } + + STDMETHODIMP OnDefaultDeviceChanged(EDataFlow flow, ERole role, + LPCWSTR id) + { + if (cb && id) + cb(flow, role, id); + + return S_OK; + } +}; + +WASAPINotify::WASAPINotify() +{ + HRESULT res = CoCreateInstance(__uuidof(MMDeviceEnumerator), nullptr, + CLSCTX_ALL, + __uuidof(IMMDeviceEnumerator), + (LPVOID *)enumerator.Assign()); + if (SUCCEEDED(res)) { + notificationClient = new NotificationClient( + std::bind(&WASAPINotify::OnDefaultDeviceChanged, this, + std::placeholders::_1, std::placeholders::_2, + std::placeholders::_3)); + enumerator->RegisterEndpointNotificationCallback( + notificationClient); + } else { + enumerator.Clear(); + } +} + +WASAPINotify::~WASAPINotify() +{ + if (enumerator) { + enumerator->UnregisterEndpointNotificationCallback( + notificationClient); + enumerator.Clear(); + } + + notificationClient.Clear(); +} + +void WASAPINotify::AddDefaultDeviceChangedCallback( + void *handle, WASAPINotifyDefaultDeviceChangedCallback cb) +{ + if (!handle) + return; + + std::lock_guard l(mutex); + defaultDeviceChangedCallbacks[handle] = cb; +} + +void WASAPINotify::RemoveDefaultDeviceChangedCallback(void *handle) +{ + if (!handle) + return; + + std::lock_guard l(mutex); + defaultDeviceChangedCallbacks.erase(handle); +} + +void WASAPINotify::OnDefaultDeviceChanged(EDataFlow flow, ERole role, + LPCWSTR id) +{ + std::lock_guard l(mutex); + for (const auto &cb : defaultDeviceChangedCallbacks) + cb.second(flow, role, id); +} diff --git a/plugins/win-wasapi/wasapi-notify.hpp b/plugins/win-wasapi/wasapi-notify.hpp new file mode 100644 index 000000000..e82fc1520 --- /dev/null +++ b/plugins/win-wasapi/wasapi-notify.hpp @@ -0,0 +1,34 @@ +#pragma once + +#include +#include + +#include +#include +#include + +typedef std::function + WASAPINotifyDefaultDeviceChangedCallback; + +class NotificationClient; + +class WASAPINotify { +public: + WASAPINotify(); + ~WASAPINotify(); + + void AddDefaultDeviceChangedCallback( + void *handle, WASAPINotifyDefaultDeviceChangedCallback cb); + void RemoveDefaultDeviceChangedCallback(void *handle); + +private: + void OnDefaultDeviceChanged(EDataFlow flow, ERole role, LPCWSTR id); + + std::mutex mutex; + + ComPtr enumerator; + ComPtr notificationClient; + + std::unordered_map + defaultDeviceChangedCallbacks; +}; diff --git a/plugins/win-wasapi/win-wasapi.cpp b/plugins/win-wasapi/win-wasapi.cpp index 64d99335e..4906ec4ab 100644 --- a/plugins/win-wasapi/win-wasapi.cpp +++ b/plugins/win-wasapi/win-wasapi.cpp @@ -1,3 +1,4 @@ +#include "wasapi-notify.hpp" #include "enum-wasapi.hpp" #include @@ -28,6 +29,7 @@ using namespace std; #define OPT_WINDOW "window" #define OPT_PRIORITY "priority" +WASAPINotify *GetNotify(); static void GetWASAPIDefaults(obs_data_t *settings); #define OBS_KSAUDIO_SPEAKER_4POINT1 \ @@ -154,7 +156,6 @@ protected: }; class WASAPISource { - ComPtr notify; ComPtr enumerator; ComPtr client; ComPtr capture; @@ -313,57 +314,6 @@ public: HWND GetHwnd(); }; -class WASAPINotify : public IMMNotificationClient { - long refs = 0; /* auto-incremented to 1 by ComPtr */ - WASAPISource *source; - -public: - WASAPINotify(WASAPISource *source_) : source(source_) {} - - STDMETHODIMP_(ULONG) AddRef() - { - return (ULONG)os_atomic_inc_long(&refs); - } - - STDMETHODIMP_(ULONG) STDMETHODCALLTYPE Release() - { - long val = os_atomic_dec_long(&refs); - if (val == 0) - delete this; - return (ULONG)val; - } - - STDMETHODIMP QueryInterface(REFIID riid, void **ptr) - { - if (riid == IID_IUnknown) { - *ptr = (IUnknown *)this; - } else if (riid == __uuidof(IMMNotificationClient)) { - *ptr = (IMMNotificationClient *)this; - } else { - *ptr = nullptr; - return E_NOINTERFACE; - } - - os_atomic_inc_long(&refs); - return S_OK; - } - - STDMETHODIMP OnDefaultDeviceChanged(EDataFlow flow, ERole role, - LPCWSTR id) - { - source->SetDefaultDevice(flow, role, id); - return S_OK; - } - - STDMETHODIMP OnDeviceAdded(LPCWSTR) { return S_OK; } - STDMETHODIMP OnDeviceRemoved(LPCWSTR) { return S_OK; } - STDMETHODIMP OnDeviceStateChanged(LPCWSTR, DWORD) { return S_OK; } - STDMETHODIMP OnPropertyValueChanged(LPCWSTR, const PROPERTYKEY) - { - return S_OK; - } -}; - WASAPISource::WASAPISource(obs_data_t *settings, obs_source_t *source_, SourceType type) : source(source_), @@ -414,20 +364,12 @@ WASAPISource::WASAPISource(obs_data_t *settings, obs_source_t *source_, if (!reconnectSignal.Valid()) throw "Could not create reconnect signal"; - notify = new WASAPINotify(this); - if (!notify) - throw "Could not create WASAPINotify"; - HRESULT hr = CoCreateInstance(__uuidof(MMDeviceEnumerator), nullptr, CLSCTX_ALL, IID_PPV_ARGS(enumerator.Assign())); if (FAILED(hr)) throw HRError("Failed to create enumerator", hr); - hr = enumerator->RegisterEndpointNotificationCallback(notify); - if (FAILED(hr)) - throw HRError("Failed to register endpoint callback", hr); - /* OBS will already load DLL on startup if it exists */ const HMODULE rtwq_module = GetModuleHandle(L"RTWorkQ.dll"); @@ -511,12 +453,19 @@ WASAPISource::WASAPISource(obs_data_t *settings, obs_source_t *source_, WASAPISource::CaptureThread, this, 0, nullptr); if (!captureThread.Valid()) { - enumerator->UnregisterEndpointNotificationCallback( - notify); throw "Failed to create capture thread"; } } + auto notify = GetNotify(); + if (notify) { + notify->AddDefaultDeviceChangedCallback( + this, + std::bind(&WASAPISource::SetDefaultDevice, this, + std::placeholders::_1, std::placeholders::_2, + std::placeholders::_3)); + } + Start(); } @@ -561,7 +510,11 @@ void WASAPISource::Stop() WASAPISource::~WASAPISource() { - enumerator->UnregisterEndpointNotificationCallback(notify); + auto notify = GetNotify(); + if (notify) { + notify->RemoveDefaultDeviceChangedCallback(this); + } + Stop(); }