From 9a06d22608280b854bc4a67aa27ca92cf6df7edf Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 13 Aug 2015 00:58:05 -0700 Subject: [PATCH] WASAPI: detect channel layout of exclusive device --- README.md | 1 - src/coreaudio.cpp | 7 +- src/jack.cpp | 7 +- src/soundio.cpp | 4 +- src/wasapi.cpp | 182 ++++++++++++++++++++++++++++++++++++++-------- 5 files changed, 161 insertions(+), 40 deletions(-) diff --git a/README.md b/README.md index 7b96404..fac1b77 100644 --- a/README.md +++ b/README.md @@ -252,7 +252,6 @@ view `coverage/index.html` in a browser. 0. implement WASAPI (Windows) backend, get examples working - list devices - raw mode - - channel layout - period duration - watching - sine wave diff --git a/src/coreaudio.cpp b/src/coreaudio.cpp index 0979426..bec7f42 100644 --- a/src/coreaudio.cpp +++ b/src/coreaudio.cpp @@ -589,10 +589,8 @@ static int refresh_devices(struct SoundIoPrivate *si) { rd.device->aim = aim; rd.device->id = soundio_str_dupe(rd.device_uid, rd.device_uid_len); rd.device->name = soundio_str_dupe(rd.device_name, rd.device_name_len); - rd.device->layout_count = 1; - rd.device->layouts = allocate(1); - if (!rd.device->id || !rd.device->name || !rd.device->layouts) { + if (!rd.device->id || !rd.device->name) { deinit_refresh_devices(&rd); return SoundIoErrorNoMem; } @@ -625,7 +623,8 @@ static int refresh_devices(struct SoundIoPrivate *si) { rd.device->current_layout = *guessed_layout; } - rd.device->layouts[0] = rd.device->current_layout; + rd.device->layout_count = 1; + rd.device->layouts = &rd.device->current_layout; // in CoreAudio, format is always 32-bit native endian float rd.device->format_count = 1; rd.device->formats = &dev->prealloc_format; diff --git a/src/jack.cpp b/src/jack.cpp index 9f51b1a..451e1fe 100644 --- a/src/jack.cpp +++ b/src/jack.cpp @@ -187,8 +187,6 @@ static int refresh_devices_bare(SoundIoPrivate *si) { device->aim = client->aim; device->id = soundio_str_dupe(client->name, client->name_len); device->name = allocate(description_len); - device->layout_count = 1; - device->layouts = allocate(1); device->current_format = SoundIoFormatFloat32NE; device->sample_rate_count = 1; device->sample_rates = &dev->prealloc_sample_rate_range; @@ -204,7 +202,7 @@ static int refresh_devices_bare(SoundIoPrivate *si) { dj->port_count = client->port_count; dj->ports = allocate(dj->port_count); - if (!device->id || !device->name || !device->layouts || !dj->ports) { + if (!device->id || !device->name || !dj->ports) { jack_free(port_names); soundio_device_unref(device); soundio_destroy_devices_info(devices_info); @@ -254,7 +252,8 @@ static int refresh_devices_bare(SoundIoPrivate *si) { soundio_channel_layout_detect_builtin(&device->current_layout); } - device->layouts[0] = device->current_layout; + device->layout_count = 1; + device->layouts = &device->current_layout; device->format_count = 1; device->formats = &dev->prealloc_format; device->formats[0] = device->current_format; diff --git a/src/soundio.cpp b/src/soundio.cpp index ae19279..cda86fe 100644 --- a/src/soundio.cpp +++ b/src/soundio.cpp @@ -374,7 +374,6 @@ void soundio_device_unref(struct SoundIoDevice *device) { free(device->id); free(device->name); - free(device->layouts); if (device->sample_rates != &dev->prealloc_sample_rate_range) free(device->sample_rates); @@ -382,6 +381,9 @@ void soundio_device_unref(struct SoundIoDevice *device) { if (device->formats != &dev->prealloc_format) free(device->formats); + if (device->layouts != &device->current_layout) + free(device->layouts); + free(dev); } } diff --git a/src/wasapi.cpp b/src/wasapi.cpp index edefcf3..07c537b 100644 --- a/src/wasapi.cpp +++ b/src/wasapi.cpp @@ -51,7 +51,7 @@ static int test_sample_rates[] = { 5644800, }; -// If you modify this list, also modify `to_wave_format` appropriately. +// If you modify this list, also modify `to_wave_format_format` appropriately. static SoundIoFormat test_formats[] = { SoundIoFormatU8, SoundIoFormatS16LE, @@ -61,6 +61,17 @@ static SoundIoFormat test_formats[] = { SoundIoFormatFloat64LE, }; +// If you modify this list, also modify `to_wave_format_layout` appropriately. +static SoundIoChannelLayoutId test_layouts[] = { + SoundIoChannelLayoutIdMono, + SoundIoChannelLayoutIdStereo, + SoundIoChannelLayoutIdQuad, + SoundIoChannelLayoutId4Point0, + SoundIoChannelLayoutId5Point1, + SoundIoChannelLayoutId7Point1, + SoundIoChannelLayoutId5Point1Back, +}; + // converts a windows wide string to a UTF-8 encoded char * // Possible errors: // * SoundIoErrorNoMem @@ -87,49 +98,54 @@ static int from_lpwstr(LPWSTR lpwstr, char **out_str, int *out_str_len) { return 0; } -static void from_wave_format_layout(WAVEFORMATEXTENSIBLE *wave_format, SoundIoChannelLayout *layout) { - assert(wave_format->Format.wFormatTag == WAVE_FORMAT_EXTENSIBLE); +static void from_channel_mask_layout(UINT channel_mask, SoundIoChannelLayout *layout) { layout->channel_count = 0; - if (wave_format->dwChannelMask & SPEAKER_FRONT_LEFT) + if (channel_mask & SPEAKER_FRONT_LEFT) layout->channels[layout->channel_count++] = SoundIoChannelIdFrontLeft; - if (wave_format->dwChannelMask & SPEAKER_FRONT_RIGHT) + if (channel_mask & SPEAKER_FRONT_RIGHT) layout->channels[layout->channel_count++] = SoundIoChannelIdFrontRight; - if (wave_format->dwChannelMask & SPEAKER_FRONT_CENTER) + if (channel_mask & SPEAKER_FRONT_CENTER) layout->channels[layout->channel_count++] = SoundIoChannelIdFrontCenter; - if (wave_format->dwChannelMask & SPEAKER_LOW_FREQUENCY) + if (channel_mask & SPEAKER_LOW_FREQUENCY) layout->channels[layout->channel_count++] = SoundIoChannelIdLfe; - if (wave_format->dwChannelMask & SPEAKER_BACK_LEFT) + if (channel_mask & SPEAKER_BACK_LEFT) layout->channels[layout->channel_count++] = SoundIoChannelIdBackLeft; - if (wave_format->dwChannelMask & SPEAKER_BACK_RIGHT) + if (channel_mask & SPEAKER_BACK_RIGHT) layout->channels[layout->channel_count++] = SoundIoChannelIdBackRight; - if (wave_format->dwChannelMask & SPEAKER_FRONT_LEFT_OF_CENTER) + if (channel_mask & SPEAKER_FRONT_LEFT_OF_CENTER) layout->channels[layout->channel_count++] = SoundIoChannelIdFrontLeftCenter; - if (wave_format->dwChannelMask & SPEAKER_FRONT_RIGHT_OF_CENTER) + if (channel_mask & SPEAKER_FRONT_RIGHT_OF_CENTER) layout->channels[layout->channel_count++] = SoundIoChannelIdFrontRightCenter; - if (wave_format->dwChannelMask & SPEAKER_BACK_CENTER) + if (channel_mask & SPEAKER_BACK_CENTER) layout->channels[layout->channel_count++] = SoundIoChannelIdBackCenter; - if (wave_format->dwChannelMask & SPEAKER_SIDE_LEFT) + if (channel_mask & SPEAKER_SIDE_LEFT) layout->channels[layout->channel_count++] = SoundIoChannelIdSideLeft; - if (wave_format->dwChannelMask & SPEAKER_SIDE_RIGHT) + if (channel_mask & SPEAKER_SIDE_RIGHT) layout->channels[layout->channel_count++] = SoundIoChannelIdSideRight; - if (wave_format->dwChannelMask & SPEAKER_TOP_CENTER) + if (channel_mask & SPEAKER_TOP_CENTER) layout->channels[layout->channel_count++] = SoundIoChannelIdTopCenter; - if (wave_format->dwChannelMask & SPEAKER_TOP_FRONT_LEFT) + if (channel_mask & SPEAKER_TOP_FRONT_LEFT) layout->channels[layout->channel_count++] = SoundIoChannelIdTopFrontLeft; - if (wave_format->dwChannelMask & SPEAKER_TOP_FRONT_CENTER) + if (channel_mask & SPEAKER_TOP_FRONT_CENTER) layout->channels[layout->channel_count++] = SoundIoChannelIdTopFrontCenter; - if (wave_format->dwChannelMask & SPEAKER_TOP_FRONT_RIGHT) + if (channel_mask & SPEAKER_TOP_FRONT_RIGHT) layout->channels[layout->channel_count++] = SoundIoChannelIdTopFrontRight; - if (wave_format->dwChannelMask & SPEAKER_TOP_BACK_LEFT) + if (channel_mask & SPEAKER_TOP_BACK_LEFT) layout->channels[layout->channel_count++] = SoundIoChannelIdTopBackLeft; - if (wave_format->dwChannelMask & SPEAKER_TOP_BACK_CENTER) + if (channel_mask & SPEAKER_TOP_BACK_CENTER) layout->channels[layout->channel_count++] = SoundIoChannelIdTopBackCenter; - if (wave_format->dwChannelMask & SPEAKER_TOP_BACK_RIGHT) + if (channel_mask & SPEAKER_TOP_BACK_RIGHT) layout->channels[layout->channel_count++] = SoundIoChannelIdTopBackRight; soundio_channel_layout_detect_builtin(layout); } +static void from_wave_format_layout(WAVEFORMATEXTENSIBLE *wave_format, SoundIoChannelLayout *layout) { + assert(wave_format->Format.wFormatTag == WAVE_FORMAT_EXTENSIBLE); + layout->channel_count = 0; + from_channel_mask_layout(wave_format->dwChannelMask, layout); +} + static SoundIoFormat from_wave_format_format(WAVEFORMATEXTENSIBLE *wave_format) { assert(wave_format->Format.wFormatTag == WAVE_FORMAT_EXTENSIBLE); bool is_pcm = IsEqualGUID(wave_format->SubFormat, SOUNDIO_KSDATAFORMAT_SUBTYPE_PCM); @@ -160,8 +176,75 @@ static SoundIoFormat from_wave_format_format(WAVEFORMATEXTENSIBLE *wave_format) return SoundIoFormatInvalid; } +// only needs to support the layouts in test_layouts +static void to_wave_format_layout(const SoundIoChannelLayout *layout, WAVEFORMATEXTENSIBLE *wave_format) { + wave_format->dwChannelMask = 0; + wave_format->Format.nChannels = layout->channel_count; + for (int i = 0; i < layout->channel_count; i += 1) { + SoundIoChannelId channel_id = layout->channels[i]; + switch (channel_id) { + case SoundIoChannelIdFrontLeft: + wave_format->dwChannelMask |= SPEAKER_FRONT_LEFT; + break; + case SoundIoChannelIdFrontRight: + wave_format->dwChannelMask |= SPEAKER_FRONT_RIGHT; + break; + case SoundIoChannelIdFrontCenter: + wave_format->dwChannelMask |= SPEAKER_FRONT_CENTER; + break; + case SoundIoChannelIdLfe: + wave_format->dwChannelMask |= SPEAKER_LOW_FREQUENCY; + break; + case SoundIoChannelIdBackLeft: + wave_format->dwChannelMask |= SPEAKER_BACK_LEFT; + break; + case SoundIoChannelIdBackRight: + wave_format->dwChannelMask |= SPEAKER_BACK_RIGHT; + break; + case SoundIoChannelIdFrontLeftCenter: + wave_format->dwChannelMask |= SPEAKER_FRONT_LEFT_OF_CENTER; + break; + case SoundIoChannelIdFrontRightCenter: + wave_format->dwChannelMask |= SPEAKER_FRONT_RIGHT_OF_CENTER; + break; + case SoundIoChannelIdBackCenter: + wave_format->dwChannelMask |= SPEAKER_BACK_CENTER; + break; + case SoundIoChannelIdSideLeft: + wave_format->dwChannelMask |= SPEAKER_SIDE_LEFT; + break; + case SoundIoChannelIdSideRight: + wave_format->dwChannelMask |= SPEAKER_SIDE_RIGHT; + break; + case SoundIoChannelIdTopCenter: + wave_format->dwChannelMask |= SPEAKER_TOP_CENTER; + break; + case SoundIoChannelIdTopFrontLeft: + wave_format->dwChannelMask |= SPEAKER_TOP_FRONT_LEFT; + break; + case SoundIoChannelIdTopFrontCenter: + wave_format->dwChannelMask |= SPEAKER_TOP_FRONT_CENTER; + break; + case SoundIoChannelIdTopFrontRight: + wave_format->dwChannelMask |= SPEAKER_TOP_FRONT_RIGHT; + break; + case SoundIoChannelIdTopBackLeft: + wave_format->dwChannelMask |= SPEAKER_TOP_BACK_LEFT; + break; + case SoundIoChannelIdTopBackCenter: + wave_format->dwChannelMask |= SPEAKER_TOP_BACK_CENTER; + break; + case SoundIoChannelIdTopBackRight: + wave_format->dwChannelMask |= SPEAKER_TOP_BACK_RIGHT; + break; + default: + soundio_panic("to_wave_format_layout: unsupported channel id"); + } + } +} + // only needs to support the formats in test_formats -static void to_wave_format(SoundIoFormat format, WAVEFORMATEXTENSIBLE *wave_format) { +static void to_wave_format_format(SoundIoFormat format, WAVEFORMATEXTENSIBLE *wave_format) { switch (format) { case SoundIoFormatU8: wave_format->SubFormat = SOUNDIO_KSDATAFORMAT_SUBTYPE_PCM; @@ -194,7 +277,7 @@ static void to_wave_format(SoundIoFormat format, WAVEFORMATEXTENSIBLE *wave_form wave_format->Samples.wValidBitsPerSample = 64; break; default: - soundio_panic("to_wave_format: unsupported format"); + soundio_panic("to_wave_format_format: unsupported format"); } } @@ -258,6 +341,46 @@ static void deinit_refresh_devices(RefreshDevices *rd) { CoTaskMemFree(rd->wave_format); } +static int detect_valid_layouts(RefreshDevices *rd, WAVEFORMATEXTENSIBLE *wave_format, + SoundIoDevicePrivate *dev, AUDCLNT_SHAREMODE share_mode) +{ + SoundIoDevice *device = &dev->pub; + SoundIoDeviceWasapi *dw = &dev->backend_data.wasapi; + HRESULT hr; + + device->layout_count = 0; + device->layouts = allocate(array_length(test_layouts)); + if (!device->layouts) + return SoundIoErrorNoMem; + + WAVEFORMATEX *closest_match = nullptr; + WAVEFORMATEXTENSIBLE orig_wave_format = *wave_format; + + for (int i = 0; i < array_length(test_formats); i += 1) { + SoundIoChannelLayoutId test_layout_id = test_layouts[i]; + const SoundIoChannelLayout *test_layout = soundio_channel_layout_get_builtin(test_layout_id); + to_wave_format_layout(test_layout, wave_format); + + HRESULT hr = IAudioClient_IsFormatSupported(dw->audio_client, share_mode, + (WAVEFORMATEX*)wave_format, &closest_match); + if (closest_match) { + CoTaskMemFree(closest_match); + closest_match = nullptr; + } + if (hr == S_OK) { + device->layouts[device->layout_count++] = *test_layout; + } else if (hr == AUDCLNT_E_UNSUPPORTED_FORMAT || hr == S_FALSE || hr == E_INVALIDARG) { + continue; + } else { + *wave_format = orig_wave_format; + return SoundIoErrorOpeningDevice; + } + } + + *wave_format = orig_wave_format; + return 0; +} + static int detect_valid_formats(RefreshDevices *rd, WAVEFORMATEXTENSIBLE *wave_format, SoundIoDevicePrivate *dev, AUDCLNT_SHAREMODE share_mode) { @@ -275,7 +398,7 @@ static int detect_valid_formats(RefreshDevices *rd, WAVEFORMATEXTENSIBLE *wave_f for (int i = 0; i < array_length(test_formats); i += 1) { SoundIoFormat test_format = test_formats[i]; - to_wave_format(test_format, wave_format); + to_wave_format_format(test_format, wave_format); HRESULT hr = IAudioClient_IsFormatSupported(dw->audio_client, share_mode, (WAVEFORMATEX*)wave_format, &closest_match); @@ -642,16 +765,15 @@ static int refresh_devices(SoundIoPrivate *si) { from_wave_format_layout(rd.wave_format, &rd.device_shared->current_layout); rd.device_shared->layout_count = 1; - rd.device_shared->layouts = allocate(1); + rd.device_shared->layouts = &rd.device_shared->current_layout; - if (!rd.device_shared->layouts) { + if ((err = detect_valid_layouts(&rd, valid_wave_format, dev_raw, + AUDCLNT_SHAREMODE_EXCLUSIVE))) + { deinit_refresh_devices(&rd); - return SoundIoErrorNoMem; + return err; } - rd.device_shared->layouts[0] = rd.device_shared->current_layout; - - SoundIoList *device_list; if (rd.device_shared->aim == SoundIoDeviceAimOutput) {