WASAPI: get the number of available audio devices

This commit is contained in:
Andrew Kelley 2015-08-10 15:22:08 -07:00
parent 21bf405859
commit 97abe08f0e
6 changed files with 198 additions and 12 deletions

View file

@ -34,8 +34,7 @@ behavior on every platform.
* C library. Depends only on the respective backend API libraries and libc. * C library. Depends only on the respective backend API libraries and libc.
Does *not* depend on libstdc++, and does *not* have exceptions, run-time type Does *not* depend on libstdc++, and does *not* have exceptions, run-time type
information, or [setjmp](http://latentcontent.net/2007/12/05/libpng-worst-api-ever/). information, or [setjmp](http://latentcontent.net/2007/12/05/libpng-worst-api-ever/).
* Errors are communicated via return codes, not logging to stdio. This is one * Errors are communicated via return codes, not logging to stdio.
of my many complaints against [PortAudio](http://www.portaudio.com/).
* Supports channel layouts (also known as channel maps), important for * Supports channel layouts (also known as channel maps), important for
surround sound applications. surround sound applications.
* Ability to monitor devices and get an event when available devices change. * Ability to monitor devices and get an event when available devices change.

View file

@ -355,7 +355,7 @@ struct SoundIoDevice {
// PulseAudio allows any value and so reasonable min/max of 0.10 and 4.0 // PulseAudio allows any value and so reasonable min/max of 0.10 and 4.0
// are used. You may check that the current backend is PulseAudio and // are used. You may check that the current backend is PulseAudio and
// ignore these min/max values. // ignore these min/max values.
// For JACK, buffer duration and period duration are the same. // For JACK and CoreAudio, buffer duration and period duration are the same.
double buffer_duration_min; double buffer_duration_min;
double buffer_duration_max; double buffer_duration_max;
double buffer_duration_current; double buffer_duration_current;
@ -420,7 +420,8 @@ struct SoundIoOutStream {
// actual period duration, as near to this value as possible. // actual period duration, as near to this value as possible.
// Defaults to `buffer_duration / 2` (and then clamped into range). // Defaults to `buffer_duration / 2` (and then clamped into range).
// If the device has unknown period duration min and max values, you may // If the device has unknown period duration min and max values, you may
// still set this. This value is meaningless for PulseAudio. // still set this. This value is meaningless for PulseAudio, JACK, and
// CoreAudio.
double period_duration; double period_duration;
// Defaults to NULL. Put whatever you want here. // Defaults to NULL. Put whatever you want here.
@ -494,8 +495,7 @@ struct SoundIoInStream {
// Defaults to 1 second (and then clamped into range). For PulseAudio, // Defaults to 1 second (and then clamped into range). For PulseAudio,
// defaults to PulseAudio's default value, usually large. If you set this // defaults to PulseAudio's default value, usually large. If you set this
// and the backend is PulseAudio, it sets `PA_STREAM_ADJUST_LATENCY` and // and the backend is PulseAudio, it sets `PA_STREAM_ADJUST_LATENCY` and
// is the value used for `maxlength`. With PulseAudio, this value is not // is the value used for `maxlength`.
// replaced with the actual duration until `soundio_instream_start`.
double buffer_duration; double buffer_duration;
// The latency of the captured audio. // The latency of the captured audio.
@ -505,8 +505,6 @@ struct SoundIoInStream {
// Defaults to `buffer_duration / 8`. // Defaults to `buffer_duration / 8`.
// If you set this and the backend is PulseAudio, it sets // If you set this and the backend is PulseAudio, it sets
// `PA_STREAM_ADJUST_LATENCY` and is the value used for `fragsize`. // `PA_STREAM_ADJUST_LATENCY` and is the value used for `fragsize`.
// With PulseAudio, this value is not replaced with the actual duration
// until `soundio_instream_start`.
double period_duration; double period_duration;
// Defaults to NULL. Put whatever you want here. // Defaults to NULL. Put whatever you want here.

View file

@ -724,6 +724,10 @@ static int refresh_devices(struct SoundIoPrivate *si) {
rd.device->buffer_duration_min = avr.mMinimum / use_sample_rate; rd.device->buffer_duration_min = avr.mMinimum / use_sample_rate;
rd.device->buffer_duration_max = avr.mMaximum / use_sample_rate; rd.device->buffer_duration_max = avr.mMaximum / use_sample_rate;
rd.device->period_duration_min = rd.device->buffer_duration_min;
rd.device->period_duration_max = rd.device->buffer_duration_max;
rd.device->period_duration_current = rd.device->buffer_duration_current;
SoundIoList<SoundIoDevice *> *device_list; SoundIoList<SoundIoDevice *> *device_list;
@ -907,6 +911,8 @@ static int outstream_open_ca(struct SoundIoPrivate *si, struct SoundIoOutStreamP
outstream->buffer_duration, outstream->buffer_duration,
device->buffer_duration_max); device->buffer_duration_max);
outstream->period_duration = outstream->outstream->buffer_duration;
AudioComponentDescription desc = {0}; AudioComponentDescription desc = {0};
desc.componentType = kAudioUnitType_Output; desc.componentType = kAudioUnitType_Output;
desc.componentSubType = kAudioUnitSubType_HALOutput; desc.componentSubType = kAudioUnitSubType_HALOutput;

View file

@ -49,6 +49,7 @@
#include <windows.h> #include <windows.h>
#include <mmsystem.h> #include <mmsystem.h>
#include <objbase.h>
#else #else
@ -161,7 +162,10 @@ double soundio_os_get_time(void) {
#if defined(SOUNDIO_OS_WINDOWS) #if defined(SOUNDIO_OS_WINDOWS)
static DWORD WINAPI run_win32_thread(LPVOID userdata) { static DWORD WINAPI run_win32_thread(LPVOID userdata) {
struct SoundIoOsThread *thread = (struct SoundIoOsThread *)userdata; struct SoundIoOsThread *thread = (struct SoundIoOsThread *)userdata;
HRESULT err = CoInitializeEx(nullptr, COINIT_MULTITHREADED);
assert(err == S_OK);
thread->run(thread->arg); thread->run(thread->arg);
CoUninitialize();
return 0; return 0;
} }
#else #else

View file

@ -1,16 +1,142 @@
#include "wasapi.hpp" #include "wasapi.hpp"
#include "soundio.hpp" #include "soundio.hpp"
#include <stdio.h>
#define INITGUID
#define CINTERFACE
#define COBJMACROS
#define WIN32_LEAN_AND_MEAN
#include <mmdeviceapi.h>
struct RefreshDevices {
IMMDeviceCollection *collection;
};
static void deinit_refresh_devices(RefreshDevices *rd) {
if (rd->collection)
IMMDeviceCollection_Release(rd->collection);
}
static int refresh_devices(SoundIoPrivate *si) {
SoundIoWasapi *siw = &si->backend_data.wasapi;
RefreshDevices rd = {0};
HRESULT hr;
if (FAILED(hr = IMMDeviceEnumerator_EnumAudioEndpoints(siw->device_enumerator,
eAll, DEVICE_STATE_ACTIVE, &rd.collection)))
{
deinit_refresh_devices(&rd);
return SoundIoErrorOpeningDevice;
}
UINT device_count;
if (FAILED(hr = IMMDeviceCollection_GetCount(rd.collection, &device_count))) {
deinit_refresh_devices(&rd);
return SoundIoErrorOpeningDevice;
}
// TODO
fprintf(stderr, "device count: %d\n", (int) device_count);
return 0;
}
static void shutdown_backend(SoundIoPrivate *si, int err) {
SoundIo *soundio = &si->pub;
SoundIoWasapi *siw = &si->backend_data.wasapi;
soundio_os_mutex_lock(siw->mutex);
siw->shutdown_err = err;
soundio->on_events_signal(soundio);
soundio_os_mutex_unlock(siw->mutex);
}
static void device_thread_run(void *arg) {
SoundIoPrivate *si = (SoundIoPrivate *)arg;
SoundIo *soundio = &si->pub;
SoundIoWasapi *siw = &si->backend_data.wasapi;
int err;
HRESULT hr = CoCreateInstance(CLSID_MMDeviceEnumerator, nullptr,
CLSCTX_ALL, IID_IMMDeviceEnumerator, (void**)&siw->device_enumerator);
if (FAILED(hr)) {
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 (;;) {
if (!siw->abort_flag.test_and_set())
break;
if (siw->device_scan_queued.exchange(false)) {
err = refresh_devices(si);
if (err)
shutdown_backend(si, err);
if (!siw->have_devices_flag.exchange(true)) {
soundio_os_cond_signal(siw->cond, nullptr);
soundio->on_events_signal(soundio);
}
if (err)
break;
soundio_os_cond_signal(siw->cond, nullptr);
}
soundio_os_cond_wait(siw->cond, nullptr);
}
IMMDeviceEnumerator_Release(siw->device_enumerator);
siw->device_enumerator = nullptr;
}
static void block_until_have_devices(SoundIoWasapi *siw) {
if (siw->have_devices_flag.load())
return;
while (!siw->have_devices_flag.load())
soundio_os_cond_wait(siw->cond, nullptr);
}
static void flush_events_wasapi(struct SoundIoPrivate *si) { static void flush_events_wasapi(struct SoundIoPrivate *si) {
soundio_panic("TODO"); SoundIo *soundio = &si->pub;
SoundIoWasapi *siw = &si->backend_data.wasapi;
block_until_have_devices(siw);
bool change = false;
bool cb_shutdown = false;
SoundIoDevicesInfo *old_devices_info = nullptr;
soundio_os_mutex_lock(siw->mutex);
if (siw->shutdown_err && !siw->emitted_shutdown_cb) {
siw->emitted_shutdown_cb = true;
cb_shutdown = true;
} else if (siw->ready_devices_info) {
old_devices_info = si->safe_devices_info;
si->safe_devices_info = siw->ready_devices_info;
siw->ready_devices_info = nullptr;
change = true;
}
soundio_os_mutex_unlock(siw->mutex);
if (cb_shutdown)
soundio->on_backend_disconnect(soundio, siw->shutdown_err);
else if (change)
soundio->on_devices_change(soundio);
soundio_destroy_devices_info(old_devices_info);
} }
static void wait_events_wasapi(struct SoundIoPrivate *si) { static void wait_events_wasapi(struct SoundIoPrivate *si) {
soundio_panic("TODO"); SoundIoWasapi *siw = &si->backend_data.wasapi;
flush_events_wasapi(si);
soundio_os_cond_wait(siw->cond, nullptr);
} }
static void wakeup_wasapi(struct SoundIoPrivate *si) { static void wakeup_wasapi(struct SoundIoPrivate *si) {
soundio_panic("TODO"); SoundIoWasapi *siw = &si->backend_data.wasapi;
soundio_os_cond_signal(siw->cond, nullptr);
} }
static void outstream_destroy_wasapi(struct SoundIoPrivate *si, struct SoundIoOutStreamPrivate *os) { static void outstream_destroy_wasapi(struct SoundIoPrivate *si, struct SoundIoOutStreamPrivate *os) {
@ -73,10 +199,48 @@ static int instream_end_read_wasapi(struct SoundIoPrivate *si, struct SoundIoInS
static void destroy_wasapi(struct SoundIoPrivate *si) { static void destroy_wasapi(struct SoundIoPrivate *si) {
soundio_panic("TODO"); SoundIoWasapi *siw = &si->backend_data.wasapi;
if (siw->thread) {
siw->abort_flag.clear();
soundio_os_cond_signal(siw->cond, nullptr);
soundio_os_thread_destroy(siw->thread);
}
if (siw->cond)
soundio_os_cond_destroy(siw->cond);
if (siw->mutex)
soundio_os_mutex_destroy(siw->mutex);
soundio_destroy_devices_info(siw->ready_devices_info);
} }
int soundio_wasapi_init(SoundIoPrivate *si) { int soundio_wasapi_init(SoundIoPrivate *si) {
SoundIoWasapi *siw = &si->backend_data.wasapi;
int err;
siw->have_devices_flag.store(false);
siw->device_scan_queued.store(true);
siw->abort_flag.test_and_set();
siw->mutex = soundio_os_mutex_create();
if (!siw->mutex) {
destroy_wasapi(si);
return SoundIoErrorNoMem;
}
siw->cond = soundio_os_cond_create();
if (!siw->cond) {
destroy_wasapi(si);
return SoundIoErrorNoMem;
}
if ((err = soundio_os_thread_create(device_thread_run, si, false, &siw->thread))) {
destroy_wasapi(si);
return err;
}
si->destroy = destroy_wasapi; si->destroy = destroy_wasapi;
si->flush_events = flush_events_wasapi; si->flush_events = flush_events_wasapi;
si->wait_events = wait_events_wasapi; si->wait_events = wait_events_wasapi;

View file

@ -9,6 +9,9 @@
#define SOUNDIO_WASAPI_HPP #define SOUNDIO_WASAPI_HPP
#include "soundio/soundio.h" #include "soundio/soundio.h"
#include "soundio/os.h"
#include "atomics.hpp"
struct IMMDeviceEnumerator;
int soundio_wasapi_init(struct SoundIoPrivate *si); int soundio_wasapi_init(struct SoundIoPrivate *si);
@ -16,6 +19,18 @@ struct SoundIoDeviceWasapi {
}; };
struct SoundIoWasapi { struct SoundIoWasapi {
SoundIoOsMutex *mutex;
SoundIoOsCond *cond;
struct SoundIoOsThread *thread;
atomic_flag abort_flag;
// this one is ready to be read with flush_events. protected by mutex
struct SoundIoDevicesInfo *ready_devices_info;
atomic_bool have_devices_flag;
atomic_bool device_scan_queued;
int shutdown_err;
bool emitted_shutdown_cb;
IMMDeviceEnumerator* device_enumerator;
}; };
struct SoundIoOutStreamWasapi { struct SoundIoOutStreamWasapi {