WASAPI: get formats for shared and exclusive devices

This commit is contained in:
Andrew Kelley 2015-08-13 00:11:54 -07:00
parent 709fc5fa19
commit 1f58cdc0bd
4 changed files with 246 additions and 12 deletions

View file

@ -254,7 +254,6 @@ view `coverage/index.html` in a browser.
- raw mode - raw mode
- channel layout - channel layout
- period duration - period duration
- formats
- watching - watching
- sine wave - sine wave
- microphone - microphone

View file

@ -112,8 +112,8 @@ int soundio_get_bytes_per_sample(enum SoundIoFormat format) {
const char * soundio_format_string(enum SoundIoFormat format) { const char * soundio_format_string(enum SoundIoFormat format) {
switch (format) { switch (format) {
case SoundIoFormatU8: return "signed 8-bit"; case SoundIoFormatS8: return "signed 8-bit";
case SoundIoFormatS8: return "unsigned 8-bit"; case SoundIoFormatU8: return "unsigned 8-bit";
case SoundIoFormatS16LE: return "signed 16-bit LE"; case SoundIoFormatS16LE: return "signed 16-bit LE";
case SoundIoFormatS16BE: return "signed 16-bit BE"; case SoundIoFormatS16BE: return "signed 16-bit BE";
case SoundIoFormatU16LE: return "unsigned 16-bit LE"; case SoundIoFormatU16LE: return "unsigned 16-bit LE";

View file

@ -28,6 +28,39 @@ const static GUID SOUNDIO_KSDATAFORMAT_SUBTYPE_IEEE_FLOAT = {
const static GUID SOUNDIO_KSDATAFORMAT_SUBTYPE_PCM = { const static GUID SOUNDIO_KSDATAFORMAT_SUBTYPE_PCM = {
0x00000001,0x0000,0x0010, {0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71}}; 0x00000001,0x0000,0x0010, {0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71}};
// Adding more common sample rates helps the heuristics; feel free to do that.
static int test_sample_rates[] = {
8000,
11025,
16000,
22050,
32000,
37800,
44056,
44100,
47250,
48000,
50000,
50400,
88200,
96000,
176400,
192000,
352800,
2822400,
5644800,
};
// If you modify this list, also modify `to_wave_format` appropriately.
static SoundIoFormat test_formats[] = {
SoundIoFormatU8,
SoundIoFormatS16LE,
SoundIoFormatS24LE,
SoundIoFormatS32LE,
SoundIoFormatFloat32LE,
SoundIoFormatFloat64LE,
};
// 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
@ -127,6 +160,44 @@ static SoundIoFormat from_wave_format_format(WAVEFORMATEXTENSIBLE *wave_format)
return SoundIoFormatInvalid; return SoundIoFormatInvalid;
} }
// only needs to support the formats in test_formats
static void to_wave_format(SoundIoFormat format, WAVEFORMATEXTENSIBLE *wave_format) {
switch (format) {
case SoundIoFormatU8:
wave_format->SubFormat = SOUNDIO_KSDATAFORMAT_SUBTYPE_PCM;
wave_format->Format.wBitsPerSample = 8;
wave_format->Samples.wValidBitsPerSample = 8;
break;
case SoundIoFormatS16LE:
wave_format->SubFormat = SOUNDIO_KSDATAFORMAT_SUBTYPE_PCM;
wave_format->Format.wBitsPerSample = 16;
wave_format->Samples.wValidBitsPerSample = 16;
break;
case SoundIoFormatS24LE:
wave_format->SubFormat = SOUNDIO_KSDATAFORMAT_SUBTYPE_PCM;
wave_format->Format.wBitsPerSample = 32;
wave_format->Samples.wValidBitsPerSample = 24;
break;
case SoundIoFormatS32LE:
wave_format->SubFormat = SOUNDIO_KSDATAFORMAT_SUBTYPE_PCM;
wave_format->Format.wBitsPerSample = 32;
wave_format->Samples.wValidBitsPerSample = 32;
break;
case SoundIoFormatFloat32LE:
wave_format->SubFormat = SOUNDIO_KSDATAFORMAT_SUBTYPE_IEEE_FLOAT;
wave_format->Format.wBitsPerSample = 32;
wave_format->Samples.wValidBitsPerSample = 32;
break;
case SoundIoFormatFloat64LE:
wave_format->SubFormat = SOUNDIO_KSDATAFORMAT_SUBTYPE_IEEE_FLOAT;
wave_format->Format.wBitsPerSample = 64;
wave_format->Samples.wValidBitsPerSample = 64;
break;
default:
soundio_panic("to_wave_format: unsupported format");
}
}
static SoundIoDeviceAim data_flow_to_aim(EDataFlow data_flow) { static SoundIoDeviceAim data_flow_to_aim(EDataFlow data_flow) {
return (data_flow == eRender) ? SoundIoDeviceAimOutput : SoundIoDeviceAimInput; return (data_flow == eRender) ? SoundIoDeviceAimOutput : SoundIoDeviceAimInput;
} }
@ -136,6 +207,13 @@ static double from_reference_time(REFERENCE_TIME rt) {
return ((double)rt) / 10000000.0; return ((double)rt) / 10000000.0;
} }
static void destruct_device(SoundIoDevicePrivate *dev) {
SoundIoDeviceWasapi *dw = &dev->backend_data.wasapi;
if (dw->audio_client)
IUnknown_Release(dw->audio_client);
dw->sample_rates.deinit();
}
struct RefreshDevices { struct RefreshDevices {
IMMDeviceCollection *collection; IMMDeviceCollection *collection;
IMMDevice *mm_device; IMMDevice *mm_device;
@ -180,12 +258,129 @@ static void deinit_refresh_devices(RefreshDevices *rd) {
CoTaskMemFree(rd->wave_format); CoTaskMemFree(rd->wave_format);
} }
static void destruct_device(SoundIoDevicePrivate *dev) { static int detect_valid_formats(RefreshDevices *rd, WAVEFORMATEXTENSIBLE *wave_format,
SoundIoDevicePrivate *dev, AUDCLNT_SHAREMODE share_mode)
{
SoundIoDevice *device = &dev->pub;
SoundIoDeviceWasapi *dw = &dev->backend_data.wasapi; SoundIoDeviceWasapi *dw = &dev->backend_data.wasapi;
if (dw->audio_client) HRESULT hr;
IUnknown_Release(dw->audio_client);
device->format_count = 0;
device->formats = allocate<SoundIoFormat>(array_length(test_formats));
if (!device->formats)
return SoundIoErrorNoMem;
WAVEFORMATEX *closest_match = nullptr;
WAVEFORMATEXTENSIBLE orig_wave_format = *wave_format;
for (int i = 0; i < array_length(test_formats); i += 1) {
SoundIoFormat test_format = test_formats[i];
to_wave_format(test_format, 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->formats[device->format_count++] = test_format;
} 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 add_sample_rate(SoundIoList<SoundIoSampleRateRange> *sample_rates, int *current_min, int the_max) {
int err;
if ((err = sample_rates->add_one()))
return err;
SoundIoSampleRateRange *last_range = &sample_rates->last();
last_range->min = *current_min;
last_range->max = the_max;
return 0;
}
static int do_sample_rate_test(SoundIoDevicePrivate *dev, WAVEFORMATEXTENSIBLE *wave_format,
int test_sample_rate, AUDCLNT_SHAREMODE share_mode, int *current_min, int *last_success_rate)
{
SoundIoDeviceWasapi *dw = &dev->backend_data.wasapi;
WAVEFORMATEX *closest_match = nullptr;
int err;
wave_format->Format.nSamplesPerSec = test_sample_rate;
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) {
if (*current_min == -1) {
*current_min = test_sample_rate;
}
*last_success_rate = test_sample_rate;
} else if (hr == AUDCLNT_E_UNSUPPORTED_FORMAT || hr == S_FALSE || hr == E_INVALIDARG) {
if (*current_min != -1) {
if ((err = add_sample_rate(&dw->sample_rates, current_min, *last_success_rate)))
return err;
*current_min = -1;
}
} else {
return SoundIoErrorOpeningDevice;
}
return 0;
}
static int detect_valid_sample_rates(RefreshDevices *rd, WAVEFORMATEXTENSIBLE *wave_format,
SoundIoDevicePrivate *dev, AUDCLNT_SHAREMODE share_mode)
{
SoundIoDeviceWasapi *dw = &dev->backend_data.wasapi;
int err;
DWORD orig_sample_rate = wave_format->Format.nSamplesPerSec;
assert(dw->sample_rates.length == 0);
int current_min = -1;
int last_success_rate = -1;
for (int i = 0; i < array_length(test_sample_rates); i += 1) {
for (int offset = -1; offset <= 1; offset += 1) {
int test_sample_rate = test_sample_rates[i] + offset;
if ((err = do_sample_rate_test(dev, wave_format, test_sample_rate, share_mode,
&current_min, &last_success_rate)))
{
wave_format->Format.nSamplesPerSec = orig_sample_rate;
return err;
}
}
}
if (current_min != -1) {
if ((err = add_sample_rate(&dw->sample_rates, &current_min, last_success_rate))) {
wave_format->Format.nSamplesPerSec = orig_sample_rate;
return err;
}
}
SoundIoDevice *device = &dev->pub;
device->sample_rate_count = dw->sample_rates.length;
device->sample_rates = dw->sample_rates.items;
wave_format->Format.nSamplesPerSec = orig_sample_rate;
return 0;
}
static int refresh_devices(SoundIoPrivate *si) { static int refresh_devices(SoundIoPrivate *si) {
SoundIo *soundio = &si->pub; SoundIo *soundio = &si->pub;
SoundIoWasapi *siw = &si->backend_data.wasapi; SoundIoWasapi *siw = &si->backend_data.wasapi;
@ -384,6 +579,37 @@ static int refresh_devices(SoundIoPrivate *si) {
return SoundIoErrorNoMem; return SoundIoErrorNoMem;
} }
// Get the format that WASAPI opens the device with for shared streams.
// This is guaranteed to work, so we use this to modulate the sample
// rate while holding the format constant and vice versa.
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 *valid_wave_format = (WAVEFORMATEXTENSIBLE *)rd.prop_variant_value.blob.pBlobData;
if (valid_wave_format->Format.wFormatTag != WAVE_FORMAT_EXTENSIBLE) {
deinit_refresh_devices(&rd);
return SoundIoErrorOpeningDevice;
}
if ((err = detect_valid_sample_rates(&rd, valid_wave_format, dev_raw,
AUDCLNT_SHAREMODE_EXCLUSIVE)))
{
deinit_refresh_devices(&rd);
return err;
}
if ((err = detect_valid_formats(&rd, valid_wave_format, dev_raw,
AUDCLNT_SHAREMODE_EXCLUSIVE)))
{
deinit_refresh_devices(&rd);
return err;
}
if (rd.wave_format) if (rd.wave_format)
CoTaskMemFree(rd.wave_format); CoTaskMemFree(rd.wave_format);
if (FAILED(hr = IAudioClient_GetMixFormat(dev_w_shared->audio_client, (WAVEFORMATEX**)&rd.wave_format))) { if (FAILED(hr = IAudioClient_GetMixFormat(dev_w_shared->audio_client, (WAVEFORMATEX**)&rd.wave_format))) {
@ -395,17 +621,24 @@ static int refresh_devices(SoundIoPrivate *si) {
return SoundIoErrorOpeningDevice; return SoundIoErrorOpeningDevice;
} }
rd.device_shared->sample_rate_current = rd.wave_format->Format.nSamplesPerSec; rd.device_shared->sample_rate_current = rd.wave_format->Format.nSamplesPerSec;
rd.device_shared->current_format = from_wave_format_format(rd.wave_format);
// WASAPI performs resampling in shared mode, so any value is valid. // WASAPI performs resampling in shared mode, so any value is valid.
// Let's pick some reasonable min and max values. // Let's pick some reasonable min and max values.
rd.device_shared->sample_rate_count = 1; rd.device_shared->sample_rate_count = 1;
rd.device_shared->sample_rates = &dev_shared->prealloc_sample_rate_range; rd.device_shared->sample_rates = &dev_shared->prealloc_sample_rate_range;
rd.device_shared->sample_rates[0].min = min(SOUNDIO_MIN_SAMPLE_RATE, rd.device_shared->sample_rate_current); rd.device_shared->sample_rates[0].min = min(SOUNDIO_MIN_SAMPLE_RATE,
rd.device_shared->sample_rates[0].max = max(SOUNDIO_MAX_SAMPLE_RATE, rd.device_shared->sample_rate_current); rd.device_shared->sample_rate_current);
rd.device_shared->sample_rates[0].max = max(SOUNDIO_MAX_SAMPLE_RATE,
rd.device_shared->sample_rate_current);
rd.device_shared->current_format = from_wave_format_format(rd.wave_format); if ((err = detect_valid_formats(&rd, rd.wave_format, dev_shared,
rd.device_shared->format_count = 1; AUDCLNT_SHAREMODE_SHARED)))
rd.device_shared->formats = &dev_shared->prealloc_format; {
rd.device_shared->formats[0] = rd.device_shared->current_format; deinit_refresh_devices(&rd);
return err;
}
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;

View file

@ -11,6 +11,7 @@
#include "soundio/soundio.h" #include "soundio/soundio.h"
#include "soundio/os.h" #include "soundio/os.h"
#include "atomics.hpp" #include "atomics.hpp"
#include "list.hpp"
struct IMMDeviceEnumerator; struct IMMDeviceEnumerator;
struct IAudioClient; struct IAudioClient;
@ -18,6 +19,7 @@ int soundio_wasapi_init(struct SoundIoPrivate *si);
struct SoundIoDeviceWasapi { struct SoundIoDeviceWasapi {
IAudioClient *audio_client; IAudioClient *audio_client;
SoundIoList<SoundIoSampleRateRange> sample_rates;
}; };
struct SoundIoWasapi { struct SoundIoWasapi {