diff --git a/README.md b/README.md index a21597e..b38d4d4 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/src/os.cpp b/src/os.cpp index e25e827..95d7978 100644 --- a/src/os.cpp +++ b/src/os.cpp @@ -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(); diff --git a/src/wasapi.cpp b/src/wasapi.cpp index 6d0aa1c..72a7697 100644 --- a/src/wasapi.cpp +++ b/src/wasapi.cpp @@ -10,7 +10,15 @@ #include #include #include +#include +// 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 *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); }