win-wasapi: Add default audio device change detection

Probably long time coming, but when the user changes their default audio
device in Windows sound settings, OBS will now detect it and change the
audio device automatically to the new device if it was set to use the
"Default" device.
This commit is contained in:
jp9000
2021-02-06 00:29:54 -08:00
parent 951acf2dfe
commit 74e6448195

View File

@@ -10,6 +10,8 @@
#include <util/threading.h>
#include <util/util_uint64.h>
#include <thread>
using namespace std;
#define OPT_DEVICE_ID "device_id"
@@ -25,11 +27,14 @@ class WASAPISource {
ComPtr<IAudioClient> client;
ComPtr<IAudioCaptureClient> capture;
ComPtr<IAudioRenderClient> render;
ComPtr<IMMNotificationClient> notify;
obs_source_t *source;
wstring default_id;
string device_id;
string device_name;
string device_sample = "-";
uint64_t lastNotifyTime = 0;
bool isInputDevice;
bool useDeviceTiming = false;
bool isDefaultDevice = false;
@@ -74,6 +79,59 @@ public:
inline ~WASAPISource();
void Update(obs_data_t *settings);
void SetDefaultDevice(EDataFlow flow, ERole role, LPCWSTR id);
};
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_,
@@ -155,6 +213,13 @@ bool WASAPISource::InitDevice(IMMDeviceEnumerator *enumerator)
isInputDevice ? eCapture : eRender,
isInputDevice ? eCommunications : eConsole,
device.Assign());
if (FAILED(res))
return false;
CoTaskMemPtr<wchar_t> id;
res = device->GetId(&id);
default_id = id;
} else {
wchar_t *w_id;
os_utf8_to_wcs_ptr(device_id.c_str(), device_id.size(), &w_id);
@@ -311,6 +376,11 @@ void WASAPISource::Initialize()
device_name = GetDeviceName(device);
if (!notify) {
notify = new WASAPINotify(this);
enumerator->RegisterEndpointNotificationCallback(notify);
}
HRESULT resSample;
IPropertyStore *store = nullptr;
PWAVEFORMATEX deviceFormatProperties;
@@ -518,6 +588,35 @@ DWORD WINAPI WASAPISource::CaptureThread(LPVOID param)
return 0;
}
void WASAPISource::SetDefaultDevice(EDataFlow flow, ERole role, LPCWSTR id)
{
if (!isDefaultDevice)
return;
EDataFlow expectedFlow = isInputDevice ? eCapture : eRender;
ERole expectedRole = isInputDevice ? eCommunications : eConsole;
if (flow != expectedFlow || role != expectedRole)
return;
if (id && default_id.compare(id) == 0)
return;
blog(LOG_INFO, "WASAPI: Default %s device changed",
isInputDevice ? "input" : "output");
/* reset device only once every 300ms */
uint64_t t = os_gettime_ns();
if (t - lastNotifyTime < 300000000)
return;
std::thread([this]() {
Stop();
Start();
}).detach();
lastNotifyTime = t;
}
/* ------------------------------------------------------------------------- */
static const char *GetWASAPIInputName(void *)