WASAPI: default device detection

This commit is contained in:
Andrew Kelley 2015-08-12 11:01:06 -07:00
parent 31e17477bc
commit 20b0515582
3 changed files with 109 additions and 8 deletions

View file

@ -24,9 +24,8 @@ behavior on every platform.
- [PulseAudio](http://www.freedesktop.org/wiki/Software/PulseAudio/)
- [ALSA](http://www.alsa-project.org/)
- [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)
- Dummy (silence)
- (planned) [WASAPI](https://msdn.microsoft.com/en-us/library/windows/desktop/dd371455%28v=vs.85%29.aspx)
- (planned) [ASIO](http://www.asio4all.com/)
* 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
CoreAudio, or whether it allows directly managing the buffer, such as on
@ -45,6 +44,9 @@ behavior on every platform.
an ALSA device open and a JACK device open at the same time.
* Meticulously checks all return codes and memory allocations and uses
meaningful error codes.
* Exposes extra API that is only available on some backends. For example you
can provide application name and stream names which is used by JACK and
PulseAudio.
## Synopsis
@ -166,7 +168,6 @@ or the server not running, or the platform is wrong, the next backend is tried.
0. ALSA (Linux)
0. CoreAudio (OSX)
0. WASAPI (Windows)
0. ASIO (Windows)
0. Dummy
If you don't like this order, you can use `soundio_connect_backend` to
@ -249,7 +250,15 @@ view `coverage/index.html` in a browser.
## Roadmap
0. implement WASAPI (Windows) backend, get examples working
0. implement ASIO (Windows) backend, get examples working
- list devices
- period duration
- buffer duration
- raw mode
- channel layout
- formats
- watching
- sine wave
- microphone
0. Make sure PulseAudio can handle refresh devices crashing before
block_until_have_devices
0. Do we really want `period_duration` in the API?
@ -288,6 +297,7 @@ view `coverage/index.html` in a browser.
0. Consider testing on FreeBSD
0. In ALSA do we need to wake up the poll when destroying the in or out stream?
0. Detect PulseAudio server going offline and emit `on_backend_disconnect`.
0. Add [sndio](http://www.sndio.org/) backend to support OpenBSD.
## Planned Uses for libsoundio

View file

@ -162,7 +162,7 @@ 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);
HRESULT err = CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED);
assert(err == S_OK);
thread->run(thread->arg);
CoUninitialize();

View file

@ -10,7 +10,15 @@
#include <windows.h>
#include <mmdeviceapi.h>
#include <functiondiscoverykeys_devpkey.h>
#include <mmreg.h>
// Attempting to use the Windows-supplied versions of these constants resulted
// in `undefined reference` linker errors.
const static GUID SOUNDIO_KSDATAFORMAT_SUBTYPE_IEEE_FLOAT = {
0x00000003,0x0000,0x0010, {0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71}};
const static GUID SOUNDIO_KSDATAFORMAT_SUBTYPE_PCM = {
0x00000001,0x0000,0x0010, {0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71}};
// converts a windows wide string to a UTF-8 encoded char *
// Possible errors:
@ -45,6 +53,8 @@ static SoundIoDeviceAim data_flow_to_aim(EDataFlow data_flow) {
struct RefreshDevices {
IMMDeviceCollection *collection;
IMMDevice *mm_device;
IMMDevice *default_render_device;
IMMDevice *default_capture_device;
IMMEndpoint *endpoint;
IPropertyStore *prop_store;
LPWSTR lpwstr;
@ -52,6 +62,10 @@ struct RefreshDevices {
bool prop_variant_value_inited;
SoundIoDevicesInfo *devices_info;
SoundIoDevice *device;
char *default_render_id;
int default_render_id_len;
char *default_capture_id;
int default_capture_id_len;
};
static void deinit_refresh_devices(RefreshDevices *rd) {
@ -59,6 +73,10 @@ static void deinit_refresh_devices(RefreshDevices *rd) {
soundio_device_unref(rd->device);
if (rd->mm_device)
IMMDevice_Release(rd->mm_device);
if (rd->default_render_device)
IMMDevice_Release(rd->default_render_device);
if (rd->default_capture_device)
IMMDevice_Release(rd->default_capture_device);
if (rd->collection)
IMMDeviceCollection_Release(rd->collection);
if (rd->lpwstr)
@ -77,6 +95,43 @@ static int refresh_devices(SoundIoPrivate *si) {
RefreshDevices rd = {0};
int err;
HRESULT hr;
if (FAILED(hr = IMMDeviceEnumerator_GetDefaultAudioEndpoint(siw->device_enumerator, eRender,
eMultimedia, &rd.default_render_device)))
{
deinit_refresh_devices(&rd);
return SoundIoErrorOpeningDevice;
}
if (rd.lpwstr)
CoTaskMemFree(rd.lpwstr);
if (FAILED(hr = IMMDevice_GetId(rd.default_render_device, &rd.lpwstr))) {
deinit_refresh_devices(&rd);
return SoundIoErrorOpeningDevice;
}
if ((err = from_lpwstr(rd.lpwstr, &rd.default_render_id, &rd.default_render_id_len))) {
deinit_refresh_devices(&rd);
return err;
}
if (FAILED(hr = IMMDeviceEnumerator_GetDefaultAudioEndpoint(siw->device_enumerator, eCapture,
eMultimedia, &rd.default_capture_device)))
{
deinit_refresh_devices(&rd);
return SoundIoErrorOpeningDevice;
}
if (rd.lpwstr)
CoTaskMemFree(rd.lpwstr);
if (FAILED(hr = IMMDevice_GetId(rd.default_capture_device, &rd.lpwstr))) {
deinit_refresh_devices(&rd);
return SoundIoErrorOpeningDevice;
}
if ((err = from_lpwstr(rd.lpwstr, &rd.default_capture_id, &rd.default_capture_id_len))) {
deinit_refresh_devices(&rd);
return err;
}
if (FAILED(hr = IMMDeviceEnumerator_EnumAudioEndpoints(siw->device_enumerator,
eAll, DEVICE_STATE_ACTIVE, &rd.collection)))
{
@ -177,16 +232,51 @@ static int refresh_devices(SoundIoPrivate *si) {
return SoundIoErrorOpeningDevice;
}
// TODO
if (rd.prop_variant_value_inited)
PropVariantClear(&rd.prop_variant_value);
PropVariantInit(&rd.prop_variant_value);
rd.prop_variant_value_inited = true;
if ((FAILED(hr = IPropertyStore_GetValue(rd.prop_store,
PKEY_AudioEngine_DeviceFormat, &rd.prop_variant_value))))
{
deinit_refresh_devices(&rd);
return SoundIoErrorOpeningDevice;
}
WAVEFORMATEXTENSIBLE *wave_format = (WAVEFORMATEXTENSIBLE *)rd.prop_variant_value.blob.pBlobData;
if (wave_format->Format.wFormatTag != WAVE_FORMAT_EXTENSIBLE) {
deinit_refresh_devices(&rd);
return SoundIoErrorOpeningDevice;
}
/*
if (IsEqualGUID(wave_format->SubFormat, SOUNDIO_KSDATAFORMAT_SUBTYPE_PCM)) {
} else if (IsEqualGUID(wave_format->SubFormat, SOUNDIO_KSDATAFORMAT_SUBTYPE_IEEE_FLOAT)) {
} else {
deinit_refresh_devices(&rd);
return SoundIoErrorOpeningDevice;
}
*/
rd.device->sample_rate_count = 1;
rd.device->sample_rates = &dev->prealloc_sample_rate_range;
rd.device->sample_rate_current = wave_format->Format.nSamplesPerSec;
rd.device->sample_rates[0].min = rd.device->sample_rate_current;
rd.device->sample_rates[0].max = rd.device->sample_rate_current;
fprintf(stderr, "bits per sample: %d\n", (int)wave_format->Format.wBitsPerSample);
SoundIoList<SoundIoDevice *> *device_list;
if (rd.device->aim == SoundIoDeviceAimOutput) {
device_list = &rd.devices_info->output_devices;
// TODO default detection
if (soundio_streql(rd.device->id, device_id_len, rd.default_render_id, rd.default_render_id_len))
rd.devices_info->default_output_index = device_list->length;
} else {
assert(rd.device->aim == SoundIoDeviceAimInput);
device_list = &rd.devices_info->input_devices;
// TODO default detection
if (soundio_streql(rd.device->id, device_id_len, rd.default_capture_id, rd.default_capture_id_len))
rd.devices_info->default_input_index = device_list->length;
}
if ((err = device_list->append(rd.device))) {
@ -244,6 +334,7 @@ static void device_thread_run(void *arg) {
if (err)
shutdown_backend(si, err);
if (!siw->have_devices_flag.exchange(true)) {
// TODO separate cond for signaling devices like coreaudio
soundio_os_cond_signal(siw->cond, nullptr);
soundio->on_events_signal(soundio);
}