WASAPI: detecting device changes

This commit is contained in:
Andrew Kelley 2015-08-13 12:23:22 -07:00
parent e82c3e89da
commit 61b95a458c
3 changed files with 121 additions and 17 deletions

View file

@ -26,6 +26,10 @@ behavior on every platform.
- [CoreAudio](https://developer.apple.com/library/mac/documentation/MusicAudio/Conceptual/CoreAudioOverview/Introduction/Introduction.html) - [CoreAudio](https://developer.apple.com/library/mac/documentation/MusicAudio/Conceptual/CoreAudioOverview/Introduction/Introduction.html)
- (in progress) [WASAPI](https://msdn.microsoft.com/en-us/library/windows/desktop/dd371455%28v=vs.85%29.aspx) - (in progress) [WASAPI](https://msdn.microsoft.com/en-us/library/windows/desktop/dd371455%28v=vs.85%29.aspx)
- Dummy (silence) - Dummy (silence)
* Exposes both raw devices and shared devices. Raw devices give you the best
performance but prevent other applications from using them. Shared devices
are default and usually provide sample rate conversion and format
conversion.
* Supports optimal usage of each supported backend. The same API does the * Supports optimal usage of each supported backend. The same API does the
right thing whether the backend has a fixed buffer size, such as on JACK and right thing whether the backend has a fixed buffer size, such as on JACK and
CoreAudio, or whether it allows directly managing the buffer, such as on CoreAudio, or whether it allows directly managing the buffer, such as on
@ -250,8 +254,6 @@ view `coverage/index.html` in a browser.
## Roadmap ## Roadmap
0. implement WASAPI (Windows) backend, get examples working 0. implement WASAPI (Windows) backend, get examples working
- list devices
- watching
- sine wave - sine wave
- microphone - microphone
0. Make sure PulseAudio can handle refresh devices crashing before 0. Make sure PulseAudio can handle refresh devices crashing before

View file

@ -10,16 +10,6 @@
#include <stdio.h> #include <stdio.h>
#define INITGUID
#define CINTERFACE
#define COBJMACROS
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <mmdeviceapi.h>
#include <functiondiscoverykeys_devpkey.h>
#include <mmreg.h>
#include <audioclient.h>
// Attempting to use the Windows-supplied versions of these constants resulted // Attempting to use the Windows-supplied versions of these constants resulted
// in `undefined reference` linker errors. // in `undefined reference` linker errors.
const static GUID SOUNDIO_KSDATAFORMAT_SUBTYPE_IEEE_FLOAT = { const static GUID SOUNDIO_KSDATAFORMAT_SUBTYPE_IEEE_FLOAT = {
@ -849,6 +839,16 @@ static void device_thread_run(void *arg) {
return; return;
} }
if (FAILED(hr = IMMDeviceEnumerator_RegisterEndpointNotificationCallback(
siw->device_enumerator, &siw->device_events)))
{
shutdown_backend(si, SoundIoErrorSystemResources);
if (!siw->have_devices_flag.exchange(true)) {
soundio_os_cond_signal(siw->cond, nullptr);
soundio->on_events_signal(soundio);
}
return;
}
for (;;) { for (;;) {
if (!siw->abort_flag.test_and_set()) if (!siw->abort_flag.test_and_set())
@ -858,7 +858,6 @@ static void device_thread_run(void *arg) {
if (err) if (err)
shutdown_backend(si, err); shutdown_backend(si, err);
if (!siw->have_devices_flag.exchange(true)) { if (!siw->have_devices_flag.exchange(true)) {
// TODO separate cond for signaling devices like coreaudio
soundio_os_cond_signal(siw->cond, nullptr); soundio_os_cond_signal(siw->cond, nullptr);
soundio->on_events_signal(soundio); soundio->on_events_signal(soundio);
} }
@ -866,9 +865,10 @@ static void device_thread_run(void *arg) {
break; break;
soundio_os_cond_signal(siw->cond, nullptr); soundio_os_cond_signal(siw->cond, nullptr);
} }
soundio_os_cond_wait(siw->cond, nullptr); soundio_os_cond_wait(siw->scan_devices_cond, nullptr);
} }
IMMDeviceEnumerator_UnregisterEndpointNotificationCallback(siw->device_enumerator, &siw->device_events);
IMMDeviceEnumerator_Release(siw->device_enumerator); IMMDeviceEnumerator_Release(siw->device_enumerator);
siw->device_enumerator = nullptr; siw->device_enumerator = nullptr;
} }
@ -986,19 +986,101 @@ static void destroy_wasapi(struct SoundIoPrivate *si) {
if (siw->thread) { if (siw->thread) {
siw->abort_flag.clear(); siw->abort_flag.clear();
soundio_os_cond_signal(siw->cond, nullptr); soundio_os_cond_signal(siw->scan_devices_cond, nullptr);
soundio_os_thread_destroy(siw->thread); soundio_os_thread_destroy(siw->thread);
} }
if (siw->cond) if (siw->cond)
soundio_os_cond_destroy(siw->cond); soundio_os_cond_destroy(siw->cond);
if (siw->scan_devices_cond)
soundio_os_cond_destroy(siw->scan_devices_cond);
if (siw->mutex) if (siw->mutex)
soundio_os_mutex_destroy(siw->mutex); soundio_os_mutex_destroy(siw->mutex);
soundio_destroy_devices_info(siw->ready_devices_info); soundio_destroy_devices_info(siw->ready_devices_info);
} }
static inline SoundIoPrivate *soundio_MMNotificationClient_si(IMMNotificationClient *client) {
SoundIoWasapi *siw = (SoundIoWasapi *)(((char *)client) - offsetof(SoundIoWasapi, device_events));
SoundIoPrivate *si = (SoundIoPrivate *)(((char *)siw) - offsetof(SoundIoPrivate, backend_data));
return si;
}
static STDMETHODIMP soundio_MMNotificationClient_QueryInterface(IMMNotificationClient *client,
REFIID riid, void **ppv)
{
if (IsEqualIID(riid, IID_IUnknown) || IsEqualIID(riid, IID_IMMNotificationClient)) {
*ppv = client;
IUnknown_AddRef(client);
return S_OK;
} else {
*ppv = nullptr;
return E_NOINTERFACE;
}
}
static STDMETHODIMP_(ULONG) soundio_MMNotificationClient_AddRef(IMMNotificationClient *client) {
SoundIoPrivate *si = soundio_MMNotificationClient_si(client);
SoundIoWasapi *siw = &si->backend_data.wasapi;
return InterlockedIncrement(&siw->device_events_refs);
}
static STDMETHODIMP_(ULONG) soundio_MMNotificationClient_Release(IMMNotificationClient *client) {
SoundIoPrivate *si = soundio_MMNotificationClient_si(client);
SoundIoWasapi *siw = &si->backend_data.wasapi;
return InterlockedDecrement(&siw->device_events_refs);
}
static HRESULT queue_device_scan(IMMNotificationClient *client) {
SoundIoPrivate *si = soundio_MMNotificationClient_si(client);
SoundIoWasapi *siw = &si->backend_data.wasapi;
siw->device_scan_queued.store(true);
soundio_os_cond_signal(siw->scan_devices_cond, nullptr);
return S_OK;
}
static STDMETHODIMP soundio_MMNotificationClient_OnDeviceStateChanged(IMMNotificationClient *client,
LPCWSTR wid, DWORD state)
{
return queue_device_scan(client);
}
static STDMETHODIMP soundio_MMNotificationClient_OnDeviceAdded(IMMNotificationClient *client, LPCWSTR wid) {
return queue_device_scan(client);
}
static STDMETHODIMP soundio_MMNotificationClient_OnDeviceRemoved(IMMNotificationClient *client, LPCWSTR wid) {
return queue_device_scan(client);
}
static STDMETHODIMP soundio_MMNotificationClient_OnDefaultDeviceChange(IMMNotificationClient *client,
EDataFlow flow, ERole role, LPCWSTR wid)
{
return queue_device_scan(client);
}
static STDMETHODIMP soundio_MMNotificationClient_OnPropertyValueChanged(IMMNotificationClient *client,
LPCWSTR wid, const PROPERTYKEY key)
{
return queue_device_scan(client);
}
static struct IMMNotificationClientVtbl soundio_MMNotificationClient = {
soundio_MMNotificationClient_QueryInterface,
soundio_MMNotificationClient_AddRef,
soundio_MMNotificationClient_Release,
soundio_MMNotificationClient_OnDeviceStateChanged,
soundio_MMNotificationClient_OnDeviceAdded,
soundio_MMNotificationClient_OnDeviceRemoved,
soundio_MMNotificationClient_OnDefaultDeviceChange,
soundio_MMNotificationClient_OnPropertyValueChanged,
};
int soundio_wasapi_init(SoundIoPrivate *si) { int soundio_wasapi_init(SoundIoPrivate *si) {
SoundIoWasapi *siw = &si->backend_data.wasapi; SoundIoWasapi *siw = &si->backend_data.wasapi;
int err; int err;
@ -1019,6 +1101,15 @@ int soundio_wasapi_init(SoundIoPrivate *si) {
return SoundIoErrorNoMem; return SoundIoErrorNoMem;
} }
siw->scan_devices_cond = soundio_os_cond_create();
if (!siw->scan_devices_cond) {
destroy_wasapi(si);
return SoundIoErrorNoMem;
}
siw->device_events.lpVtbl = &soundio_MMNotificationClient;
siw->device_events_refs = 1;
if ((err = soundio_os_thread_create(device_thread_run, si, false, &siw->thread))) { if ((err = soundio_os_thread_create(device_thread_run, si, false, &siw->thread))) {
destroy_wasapi(si); destroy_wasapi(si);
return err; return err;

View file

@ -12,8 +12,16 @@
#include "soundio/os.h" #include "soundio/os.h"
#include "atomics.hpp" #include "atomics.hpp"
#include "list.hpp" #include "list.hpp"
struct IMMDeviceEnumerator;
struct IAudioClient; #define INITGUID
#define CINTERFACE
#define COBJMACROS
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <mmdeviceapi.h>
#include <functiondiscoverykeys_devpkey.h>
#include <mmreg.h>
#include <audioclient.h>
int soundio_wasapi_init(struct SoundIoPrivate *si); int soundio_wasapi_init(struct SoundIoPrivate *si);
@ -25,6 +33,7 @@ struct SoundIoDeviceWasapi {
struct SoundIoWasapi { struct SoundIoWasapi {
SoundIoOsMutex *mutex; SoundIoOsMutex *mutex;
SoundIoOsCond *cond; SoundIoOsCond *cond;
SoundIoOsCond *scan_devices_cond;
struct SoundIoOsThread *thread; struct SoundIoOsThread *thread;
atomic_flag abort_flag; atomic_flag abort_flag;
// this one is ready to be read with flush_events. protected by mutex // this one is ready to be read with flush_events. protected by mutex
@ -35,6 +44,8 @@ struct SoundIoWasapi {
bool emitted_shutdown_cb; bool emitted_shutdown_cb;
IMMDeviceEnumerator* device_enumerator; IMMDeviceEnumerator* device_enumerator;
IMMNotificationClient device_events;
LONG device_events_refs;
}; };
struct SoundIoOutStreamWasapi { struct SoundIoOutStreamWasapi {