From 97abe08f0e692a995588b4a982d3820bac10eda8 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 10 Aug 2015 15:22:08 -0700 Subject: [PATCH] WASAPI: get the number of available audio devices --- README.md | 3 +- soundio/soundio.h | 10 ++- src/coreaudio.cpp | 6 ++ src/os.cpp | 4 ++ src/wasapi.cpp | 172 ++++++++++++++++++++++++++++++++++++++++++++-- src/wasapi.hpp | 15 ++++ 6 files changed, 198 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index f082796..a21597e 100644 --- a/README.md +++ b/README.md @@ -34,8 +34,7 @@ behavior on every platform. * 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 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 - of my many complaints against [PortAudio](http://www.portaudio.com/). + * Errors are communicated via return codes, not logging to stdio. * Supports channel layouts (also known as channel maps), important for surround sound applications. * Ability to monitor devices and get an event when available devices change. diff --git a/soundio/soundio.h b/soundio/soundio.h index e220dc4..3c6d54e 100644 --- a/soundio/soundio.h +++ b/soundio/soundio.h @@ -355,7 +355,7 @@ struct SoundIoDevice { // 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 // 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_max; double buffer_duration_current; @@ -420,7 +420,8 @@ struct SoundIoOutStream { // actual period duration, as near to this value as possible. // Defaults to `buffer_duration / 2` (and then clamped into range). // 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; // 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 PulseAudio's default value, usually large. If you set this // and the backend is PulseAudio, it sets `PA_STREAM_ADJUST_LATENCY` and - // is the value used for `maxlength`. With PulseAudio, this value is not - // replaced with the actual duration until `soundio_instream_start`. + // is the value used for `maxlength`. double buffer_duration; // The latency of the captured audio. @@ -505,8 +505,6 @@ struct SoundIoInStream { // Defaults to `buffer_duration / 8`. // If you set this and the backend is PulseAudio, it sets // `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; // Defaults to NULL. Put whatever you want here. diff --git a/src/coreaudio.cpp b/src/coreaudio.cpp index 5b92dac..3b8b1dc 100644 --- a/src/coreaudio.cpp +++ b/src/coreaudio.cpp @@ -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_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 *device_list; @@ -907,6 +911,8 @@ static int outstream_open_ca(struct SoundIoPrivate *si, struct SoundIoOutStreamP outstream->buffer_duration, device->buffer_duration_max); + outstream->period_duration = outstream->outstream->buffer_duration; + AudioComponentDescription desc = {0}; desc.componentType = kAudioUnitType_Output; desc.componentSubType = kAudioUnitSubType_HALOutput; diff --git a/src/os.cpp b/src/os.cpp index 722bca4..e25e827 100644 --- a/src/os.cpp +++ b/src/os.cpp @@ -49,6 +49,7 @@ #include #include +#include #else @@ -161,7 +162,10 @@ double soundio_os_get_time(void) { #if defined(SOUNDIO_OS_WINDOWS) static DWORD WINAPI run_win32_thread(LPVOID userdata) { struct SoundIoOsThread *thread = (struct SoundIoOsThread *)userdata; + HRESULT err = CoInitializeEx(nullptr, COINIT_MULTITHREADED); + assert(err == S_OK); thread->run(thread->arg); + CoUninitialize(); return 0; } #else diff --git a/src/wasapi.cpp b/src/wasapi.cpp index 68a1ad8..79b552e 100644 --- a/src/wasapi.cpp +++ b/src/wasapi.cpp @@ -1,16 +1,142 @@ #include "wasapi.hpp" #include "soundio.hpp" +#include + +#define INITGUID +#define CINTERFACE +#define COBJMACROS +#define WIN32_LEAN_AND_MEAN +#include + +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) { - 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) { - 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) { - 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) { @@ -73,10 +199,48 @@ static int instream_end_read_wasapi(struct SoundIoPrivate *si, struct SoundIoInS 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) { + 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->flush_events = flush_events_wasapi; si->wait_events = wait_events_wasapi; diff --git a/src/wasapi.hpp b/src/wasapi.hpp index 3252c9c..717036c 100644 --- a/src/wasapi.hpp +++ b/src/wasapi.hpp @@ -9,6 +9,9 @@ #define SOUNDIO_WASAPI_HPP #include "soundio/soundio.h" +#include "soundio/os.h" +#include "atomics.hpp" +struct IMMDeviceEnumerator; int soundio_wasapi_init(struct SoundIoPrivate *si); @@ -16,6 +19,18 @@ struct SoundIoDeviceWasapi { }; 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 {