WASAPI: detect channel layout of exclusive device

This commit is contained in:
Andrew Kelley 2015-08-13 00:58:05 -07:00
parent 1f58cdc0bd
commit 9a06d22608
5 changed files with 161 additions and 40 deletions

View file

@ -252,7 +252,6 @@ view `coverage/index.html` in a browser.
0. implement WASAPI (Windows) backend, get examples working 0. implement WASAPI (Windows) backend, get examples working
- list devices - list devices
- raw mode - raw mode
- channel layout
- period duration - period duration
- watching - watching
- sine wave - sine wave

View file

@ -589,10 +589,8 @@ static int refresh_devices(struct SoundIoPrivate *si) {
rd.device->aim = aim; rd.device->aim = aim;
rd.device->id = soundio_str_dupe(rd.device_uid, rd.device_uid_len); 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->name = soundio_str_dupe(rd.device_name, rd.device_name_len);
rd.device->layout_count = 1;
rd.device->layouts = allocate<SoundIoChannelLayout>(1);
if (!rd.device->id || !rd.device->name || !rd.device->layouts) { if (!rd.device->id || !rd.device->name) {
deinit_refresh_devices(&rd); deinit_refresh_devices(&rd);
return SoundIoErrorNoMem; return SoundIoErrorNoMem;
} }
@ -625,7 +623,8 @@ static int refresh_devices(struct SoundIoPrivate *si) {
rd.device->current_layout = *guessed_layout; 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 // in CoreAudio, format is always 32-bit native endian float
rd.device->format_count = 1; rd.device->format_count = 1;
rd.device->formats = &dev->prealloc_format; rd.device->formats = &dev->prealloc_format;

View file

@ -187,8 +187,6 @@ static int refresh_devices_bare(SoundIoPrivate *si) {
device->aim = client->aim; device->aim = client->aim;
device->id = soundio_str_dupe(client->name, client->name_len); device->id = soundio_str_dupe(client->name, client->name_len);
device->name = allocate<char>(description_len); device->name = allocate<char>(description_len);
device->layout_count = 1;
device->layouts = allocate<SoundIoChannelLayout>(1);
device->current_format = SoundIoFormatFloat32NE; device->current_format = SoundIoFormatFloat32NE;
device->sample_rate_count = 1; device->sample_rate_count = 1;
device->sample_rates = &dev->prealloc_sample_rate_range; 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->port_count = client->port_count;
dj->ports = allocate<SoundIoDeviceJackPort>(dj->port_count); dj->ports = allocate<SoundIoDeviceJackPort>(dj->port_count);
if (!device->id || !device->name || !device->layouts || !dj->ports) { if (!device->id || !device->name || !dj->ports) {
jack_free(port_names); jack_free(port_names);
soundio_device_unref(device); soundio_device_unref(device);
soundio_destroy_devices_info(devices_info); 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); 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->format_count = 1;
device->formats = &dev->prealloc_format; device->formats = &dev->prealloc_format;
device->formats[0] = device->current_format; device->formats[0] = device->current_format;

View file

@ -374,7 +374,6 @@ void soundio_device_unref(struct SoundIoDevice *device) {
free(device->id); free(device->id);
free(device->name); free(device->name);
free(device->layouts);
if (device->sample_rates != &dev->prealloc_sample_rate_range) if (device->sample_rates != &dev->prealloc_sample_rate_range)
free(device->sample_rates); free(device->sample_rates);
@ -382,6 +381,9 @@ void soundio_device_unref(struct SoundIoDevice *device) {
if (device->formats != &dev->prealloc_format) if (device->formats != &dev->prealloc_format)
free(device->formats); free(device->formats);
if (device->layouts != &device->current_layout)
free(device->layouts);
free(dev); free(dev);
} }
} }

View file

@ -51,7 +51,7 @@ static int test_sample_rates[] = {
5644800, 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[] = { static SoundIoFormat test_formats[] = {
SoundIoFormatU8, SoundIoFormatU8,
SoundIoFormatS16LE, SoundIoFormatS16LE,
@ -61,6 +61,17 @@ static SoundIoFormat test_formats[] = {
SoundIoFormatFloat64LE, 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 * // converts a windows wide string to a UTF-8 encoded char *
// Possible errors: // Possible errors:
// * SoundIoErrorNoMem // * SoundIoErrorNoMem
@ -87,49 +98,54 @@ static int from_lpwstr(LPWSTR lpwstr, char **out_str, int *out_str_len) {
return 0; return 0;
} }
static void from_wave_format_layout(WAVEFORMATEXTENSIBLE *wave_format, SoundIoChannelLayout *layout) { static void from_channel_mask_layout(UINT channel_mask, SoundIoChannelLayout *layout) {
assert(wave_format->Format.wFormatTag == WAVE_FORMAT_EXTENSIBLE);
layout->channel_count = 0; layout->channel_count = 0;
if (wave_format->dwChannelMask & SPEAKER_FRONT_LEFT) if (channel_mask & SPEAKER_FRONT_LEFT)
layout->channels[layout->channel_count++] = SoundIoChannelIdFrontLeft; 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; 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; 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; 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; 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; 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; 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; 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; 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; 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; 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; 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; 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; 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; 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; 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; 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; layout->channels[layout->channel_count++] = SoundIoChannelIdTopBackRight;
soundio_channel_layout_detect_builtin(layout); 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) { static SoundIoFormat from_wave_format_format(WAVEFORMATEXTENSIBLE *wave_format) {
assert(wave_format->Format.wFormatTag == WAVE_FORMAT_EXTENSIBLE); assert(wave_format->Format.wFormatTag == WAVE_FORMAT_EXTENSIBLE);
bool is_pcm = IsEqualGUID(wave_format->SubFormat, SOUNDIO_KSDATAFORMAT_SUBTYPE_PCM); 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; 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 // 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) { switch (format) {
case SoundIoFormatU8: case SoundIoFormatU8:
wave_format->SubFormat = SOUNDIO_KSDATAFORMAT_SUBTYPE_PCM; 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; wave_format->Samples.wValidBitsPerSample = 64;
break; break;
default: 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); 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<SoundIoChannelLayout>(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, static int detect_valid_formats(RefreshDevices *rd, WAVEFORMATEXTENSIBLE *wave_format,
SoundIoDevicePrivate *dev, AUDCLNT_SHAREMODE share_mode) 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) { for (int i = 0; i < array_length(test_formats); i += 1) {
SoundIoFormat test_format = test_formats[i]; 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, HRESULT hr = IAudioClient_IsFormatSupported(dw->audio_client, share_mode,
(WAVEFORMATEX*)wave_format, &closest_match); (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); from_wave_format_layout(rd.wave_format, &rd.device_shared->current_layout);
rd.device_shared->layout_count = 1; rd.device_shared->layout_count = 1;
rd.device_shared->layouts = allocate<SoundIoChannelLayout>(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); deinit_refresh_devices(&rd);
return SoundIoErrorNoMem; return err;
} }
rd.device_shared->layouts[0] = rd.device_shared->current_layout;
SoundIoList<SoundIoDevice *> *device_list; SoundIoList<SoundIoDevice *> *device_list;
if (rd.device_shared->aim == SoundIoDeviceAimOutput) { if (rd.device_shared->aim == SoundIoDeviceAimOutput) {