Parcourir la source

win-wasapi: Handle changes to the default monitoring device

Splits the WASAPINotify class out of win-wasapi.cpp and makes it a
shared object in the plugin, then also creates a callback to reset
audio monitoring if the default output device changes.
tt2468 il y a 3 ans
Parent
commit
1d5d4b29e7

+ 2 - 1
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)

+ 41 - 0
plugins/win-wasapi/plugin-main.cpp

@@ -1,9 +1,12 @@
+#include "wasapi-notify.hpp"
+
 #include <obs-module.h>
 
 #include <util/windows/win-version.h>
 
 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;
+}

+ 121 - 0
plugins/win-wasapi/wasapi-notify.cpp

@@ -0,0 +1,121 @@
+#include "wasapi-notify.hpp"
+
+#include <windows.h>
+#include <assert.h>
+
+#include <util/threading.h>
+
+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<std::mutex> l(mutex);
+	defaultDeviceChangedCallbacks[handle] = cb;
+}
+
+void WASAPINotify::RemoveDefaultDeviceChangedCallback(void *handle)
+{
+	if (!handle)
+		return;
+
+	std::lock_guard<std::mutex> l(mutex);
+	defaultDeviceChangedCallbacks.erase(handle);
+}
+
+void WASAPINotify::OnDefaultDeviceChanged(EDataFlow flow, ERole role,
+					  LPCWSTR id)
+{
+	std::lock_guard<std::mutex> l(mutex);
+	for (const auto &cb : defaultDeviceChangedCallbacks)
+		cb.second(flow, role, id);
+}

+ 34 - 0
plugins/win-wasapi/wasapi-notify.hpp

@@ -0,0 +1,34 @@
+#pragma once
+
+#include <util/windows/ComPtr.hpp>
+#include <Mmdeviceapi.h>
+
+#include <unordered_map>
+#include <functional>
+#include <mutex>
+
+typedef std::function<void(EDataFlow, ERole, LPCWSTR)>
+	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<IMMDeviceEnumerator> enumerator;
+	ComPtr<NotificationClient> notificationClient;
+
+	std::unordered_map<void *, WASAPINotifyDefaultDeviceChangedCallback>
+		defaultDeviceChangedCallbacks;
+};

+ 16 - 63
plugins/win-wasapi/win-wasapi.cpp

@@ -1,3 +1,4 @@
+#include "wasapi-notify.hpp"
 #include "enum-wasapi.hpp"
 
 #include <obs-module.h>
@@ -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<IMMNotificationClient> notify;
 	ComPtr<IMMDeviceEnumerator> enumerator;
 	ComPtr<IAudioClient> client;
 	ComPtr<IAudioCaptureClient> 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();
 }