sample rate is exposed as a list of min/max pairs

This commit is contained in:
Andrew Kelley 2015-08-08 14:44:31 -07:00
parent eb7308d992
commit 7238d29666
11 changed files with 162 additions and 73 deletions

View file

@ -249,16 +249,10 @@ view `coverage/index.html` in a browser.
## Roadmap
0. ALSA backend for microphone example is broken
0. Add some builtin channel layouts from
https://developer.apple.com/library/mac/documentation/MusicAudio/Reference/CoreAudioDataTypesRef/#//apple_ref/doc/constant_group/Audio_Channel_Layout_Tags
0. Make sure sending bogus device id results in "SoundIoErrorNoSuchDevice" on each backend
0. Make sure PulseAudio can handle refresh devices crashing before
block_until_have_devices
0. CoreAudio exposes a list of min/max pairs of supported sample rates. libsoundio
should do the same.
0. implement WASAPI (Windows) backend, get examples working
0. implement ASIO (Windows) backend, get examples working
0. Make sure PulseAudio can handle refresh devices crashing before
block_until_have_devices
0. Do we really want `period_duration` in the API?
0. Integrate into libgroove and test with Groove Basin
0. clear buffer maybe could take an argument to say how many frames to not clear

View file

@ -50,8 +50,12 @@ static void print_device(struct SoundIoDevice *device, bool is_default) {
fprintf(stderr, "\n");
}
fprintf(stderr, " min sample rate: %d\n", device->sample_rate_min);
fprintf(stderr, " max sample rate: %d\n", device->sample_rate_max);
fprintf(stderr, " sample rates:\n");
for (int i = 0; i < device->sample_rate_count; i += 1) {
struct SoundIoSampleRateRange *range = &device->sample_rates[i];
fprintf(stderr, " %d - %d\n", range->min, range->max);
}
if (device->sample_rate_current)
fprintf(stderr, " current sample rate: %d\n", device->sample_rate_current);
fprintf(stderr, " formats: ");

View file

@ -39,6 +39,14 @@ static enum SoundIoFormat prioritized_formats[] = {
SoundIoFormatInvalid,
};
static int prioritized_sample_rates[] = {
48000,
44100,
96000,
24000,
0,
};
__attribute__ ((cold))
__attribute__ ((noreturn))
@ -274,11 +282,15 @@ int main(int argc, char **argv) {
if (!layout)
panic("channel layouts not compatible");
int sample_rate = 48000;
if (in_device->sample_rate_max < sample_rate) sample_rate = in_device->sample_rate_max;
if (out_device->sample_rate_max < sample_rate) sample_rate = out_device->sample_rate_max;
if (in_device->sample_rate_min > sample_rate || out_device->sample_rate_min > sample_rate)
int *sample_rate;
for (sample_rate = prioritized_sample_rates; *sample_rate; sample_rate += 1) {
if (soundio_device_supports_sample_rate(in_device, *sample_rate) &&
soundio_device_supports_sample_rate(out_device, *sample_rate))
{
break;
}
}
if (!*sample_rate)
panic("incompatible sample rates");
enum SoundIoFormat *fmt;
@ -296,7 +308,7 @@ int main(int argc, char **argv) {
if (!instream)
panic("out of memory");
instream->format = *fmt;
instream->sample_rate = sample_rate;
instream->sample_rate = *sample_rate;
instream->layout = *layout;
instream->period_duration = microphone_latency / 4.0;
instream->read_callback = read_callback;
@ -308,7 +320,7 @@ int main(int argc, char **argv) {
if (!outstream)
panic("out of memory");
outstream->format = *fmt;
outstream->sample_rate = sample_rate;
outstream->sample_rate = *sample_rate;
outstream->layout = *layout;
outstream->buffer_duration = microphone_latency;
outstream->write_callback = write_callback;

View file

@ -238,7 +238,13 @@ struct SoundIoChannelLayout {
enum SoundIoChannelId channels[SOUNDIO_MAX_CHANNELS];
};
// The size of this struct is not part of the API or ABI.
// The size of this struct is OK to use.
struct SoundIoSampleRateRange {
int min;
int max;
};
// The size of this struct is OK to use.
struct SoundIoChannelArea {
// Base address of buffer.
char *ptr;
@ -336,10 +342,11 @@ struct SoundIoDevice {
// Sample rate is the number of frames per second.
// Sample rate is handled very similar to sample format; see those docs.
// If sample rate information is missing due to a probe error, the field
// will be set to zero.
// Devices are guaranteed to have at least 1 sample rate available.
int sample_rate_min;
int sample_rate_max;
// will be set to 0 or NULL.
// Devices which have `probe_error` set to `SoundIoErrorNone` are
// guaranteed to have at least 1 sample rate available.
struct SoundIoSampleRateRange *sample_rates;
int sample_rate_count;
int sample_rate_current;
// Buffer duration in seconds. If `buffer_duration_current` is unknown or
@ -705,6 +712,16 @@ bool soundio_device_supports_format(struct SoundIoDevice *device,
bool soundio_device_supports_layout(struct SoundIoDevice *device,
const struct SoundIoChannelLayout *layout);
// Convenience function. Returns whether `sample_rate` is included in the
// device's supported sample rates.
bool soundio_device_supports_sample_rate(struct SoundIoDevice *device,
int sample_rate);
// Convenience function. Returns the available sample rate nearest to
// `sample_rate`, rounding up.
int soundio_device_nearest_sample_rate(struct SoundIoDevice *device,
int sample_rate);
// Output Streams

View file

@ -252,6 +252,7 @@ static int set_access(snd_pcm_t *handle, snd_pcm_hw_params_t *hwparams, snd_pcm_
static int probe_open_device(SoundIoDevice *device, snd_pcm_t *handle, int resample,
int *out_channels_min, int *out_channels_max)
{
SoundIoDevicePrivate *dev = (SoundIoDevicePrivate *)device;
int err;
snd_pcm_hw_params_t *hwparams;
@ -286,8 +287,10 @@ static int probe_open_device(SoundIoDevice *device, snd_pcm_t *handle, int resam
if ((err = snd_pcm_hw_params_set_rate_last(handle, hwparams, &rate_max, nullptr)) < 0)
return SoundIoErrorOpeningDevice;
device->sample_rate_min = rate_min;
device->sample_rate_max = rate_max;
device->sample_rate_count = 1;
device->sample_rates = &dev->prealloc_sample_rate_range;
device->sample_rates[0].min = rate_min;
device->sample_rates[0].max = rate_max;
double one_over_actual_rate = 1.0 / (double)rate_max;
@ -437,8 +440,8 @@ static int probe_device(SoundIoDevice *device, snd_pcm_chmap_query_t **maps) {
maps = nullptr;
if (!device->is_raw) {
if (device->sample_rate_min == device->sample_rate_max)
device->sample_rate_current = device->sample_rate_min;
if (device->sample_rates[0].min == device->sample_rates[0].max)
device->sample_rate_current = device->sample_rates[0].min;
if (device->buffer_duration_min == device->buffer_duration_max)
device->buffer_duration_current = device->buffer_duration_min;

View file

@ -642,44 +642,58 @@ static int refresh_devices(struct SoundIoPrivate *si) {
}
rd.device->sample_rate_current = (int)floored_value;
prop_address.mSelector = kAudioDevicePropertyAvailableNominalSampleRates;
prop_address.mScope = aim_to_scope(aim);
prop_address.mElement = kAudioObjectPropertyElementMaster;
if ((os_err = AudioObjectGetPropertyDataSize(device_id, &prop_address, 0, nullptr,
&io_size)))
{
deinit_refresh_devices(&rd);
return SoundIoErrorOpeningDevice;
}
int avr_array_len = io_size / sizeof(AudioValueRange);
rd.avr_array = (AudioValueRange*)allocate<char>(io_size);
if (!rd.avr_array) {
deinit_refresh_devices(&rd);
return SoundIoErrorNoMem;
}
if ((os_err = AudioObjectGetPropertyData(device_id, &prop_address, 0, nullptr,
&io_size, rd.avr_array)))
{
deinit_refresh_devices(&rd);
return SoundIoErrorOpeningDevice;
}
for (int i = 0; i < avr_array_len; i += 1) {
AudioValueRange *avr = &rd.avr_array[i];
int min_val = ceil(avr->mMinimum);
int max_val = floor(avr->mMaximum);
if (rd.device->sample_rate_min == 0 || min_val < rd.device->sample_rate_min)
rd.device->sample_rate_min = min_val;
if (rd.device->sample_rate_max == 0 || max_val > rd.device->sample_rate_max)
rd.device->sample_rate_max = max_val;
}
// If you try to open an input stream with anything but the current
// nominal sample rate, AudioUnitRender returns an error.
if (aim == SoundIoDeviceAimInput) {
rd.device->sample_rate_min = rd.device->sample_rate_current;
rd.device->sample_rate_max = rd.device->sample_rate_current;
device->sample_rate_count = 1;
device->sample_rates = &dev->prealloc_sample_rate_range;
device->sample_rates[0].min = rd.device->sample_rate_current;
device->sample_rates[0].max = rd.device->sample_rate_current;
} else {
prop_address.mSelector = kAudioDevicePropertyAvailableNominalSampleRates;
prop_address.mScope = aim_to_scope(aim);
prop_address.mElement = kAudioObjectPropertyElementMaster;
if ((os_err = AudioObjectGetPropertyDataSize(device_id, &prop_address, 0, nullptr,
&io_size)))
{
deinit_refresh_devices(&rd);
return SoundIoErrorOpeningDevice;
}
int avr_array_len = io_size / sizeof(AudioValueRange);
rd.avr_array = (AudioValueRange*)allocate<char>(io_size);
if (!rd.avr_array) {
deinit_refresh_devices(&rd);
return SoundIoErrorNoMem;
}
if ((os_err = AudioObjectGetPropertyData(device_id, &prop_address, 0, nullptr,
&io_size, rd.avr_array)))
{
deinit_refresh_devices(&rd);
return SoundIoErrorOpeningDevice;
}
if (avr_array_len == 1) {
device->sample_rate_count = 1;
device->sample_rates = &dev->prealloc_sample_rate_range;
device->sample_rates[0].min = ceil(avr->mMinimum);
device->sample_rates[0].max = floor(avr->mMaximum);
} else {
device->sample_rate_count = avr_array_len;
device->sample_rates = allocate<SoundIoSampleRateRange>(avr_array_len);
if (!device->sample_rates) {
deinit_refresh_devices(&rd);
return SoundIoErrorNoMem;
}
for (int i = 0; i < avr_array_len; i += 1) {
AudioValueRange *avr = &rd.avr_array[i];
int min_val = ceil(avr->mMinimum);
int max_val = floor(avr->mMaximum);
device->sample_rates[i].min = min_val;
device->sample_rates[i].max = max_val;
}
}
}
prop_address.mSelector = kAudioDevicePropertyBufferFrameSize;

View file

@ -363,6 +363,14 @@ static int set_all_device_formats(SoundIoDevice *device) {
return 0;
}
static void set_all_device_sample_rates(SoundIoDevice *device) {
SoundIoDevicePrivate *dev = (SoundIoDevicePrivate *)device;
device->sample_rate_count = 1;
device->sample_rates = &dev->prealloc_sample_rate_range;
device->sample_rates[0].min = 8000;
device->sample_rates[0].max = 5644800;
}
static int set_all_device_channel_layouts(SoundIoDevice *device) {
device->layout_count = soundio_channel_layout_builtin_count();
device->layouts = allocate<SoundIoChannelLayout>(device->layout_count);
@ -429,12 +437,11 @@ int soundio_dummy_init(SoundIoPrivate *si) {
destroy_dummy(si);
return err;
}
set_all_device_sample_rates(device);
device->buffer_duration_min = 0.01;
device->buffer_duration_max = 4;
device->buffer_duration_current = 0.1;
device->sample_rate_min = 2;
device->sample_rate_max = 5644800;
device->sample_rate_current = 48000;
device->period_duration_min = 0.01;
device->period_duration_max = 2;
@ -479,11 +486,10 @@ int soundio_dummy_init(SoundIoPrivate *si) {
destroy_dummy(si);
return err;
}
set_all_device_sample_rates(device);
device->buffer_duration_min = 0.01;
device->buffer_duration_max = 4;
device->buffer_duration_current = 0.1;
device->sample_rate_min = 2;
device->sample_rate_max = 5644800;
device->sample_rate_current = 48000;
device->period_duration_min = 0.01;
device->period_duration_max = 2;

View file

@ -192,8 +192,10 @@ static int refresh_devices_bare(SoundIoPrivate *si) {
device->format_count = 1;
device->formats = allocate<SoundIoFormat>(1);
device->current_format = SoundIoFormatFloat32NE;
device->sample_rate_min = sij->sample_rate;
device->sample_rate_max = sij->sample_rate;
device->sample_rate_count = 1;
device->sample_rates = &dev->prealloc_sample_rate_range;
device->sample_rates[0].min = sij->sample_rate;
device->sample_rates[0].max = sij->sample_rate;
device->sample_rate_current = sij->sample_rate;
device->period_duration_min = sij->period_size / (double) sij->sample_rate;
device->period_duration_max = device->period_duration_min;

View file

@ -294,8 +294,10 @@ static void sink_info_callback(pa_context *pulse_context, const pa_sink_info *in
device->sample_rate_current = info->sample_spec.rate;
// PulseAudio performs resampling, so any value is valid. Let's pick
// some reasonable min and max values.
device->sample_rate_min = min(8000, device->sample_rate_current);
device->sample_rate_max = max(5644800, device->sample_rate_current);
device->sample_rate_count = 1;
device->sample_rates = &dev->prealloc_sample_rate_range;
device->sample_rates[0].min = min(8000, device->sample_rate_current);
device->sample_rates[0].max = max(5644800, device->sample_rate_current);
device->current_format = from_pulseaudio_format(info->sample_spec);
// PulseAudio performs sample format conversion, so any PulseAudio
@ -364,8 +366,10 @@ static void source_info_callback(pa_context *pulse_context, const pa_source_info
device->sample_rate_current = info->sample_spec.rate;
// PulseAudio performs resampling, so any value is valid. Let's pick
// some reasonable min and max values.
device->sample_rate_min = min(8000, device->sample_rate_current);
device->sample_rate_max = max(5644800, device->sample_rate_current);
device->sample_rate_count = 1;
device->sample_rates = &dev->prealloc_sample_rate_range;
device->sample_rates[0].min = min(8000, device->sample_rate_current);
device->sample_rates[0].max = max(5644800, device->sample_rate_current);
device->current_format = from_pulseaudio_format(info->sample_spec);
// PulseAudio performs sample format conversion, so any PulseAudio

View file

@ -368,6 +368,9 @@ void soundio_device_unref(struct SoundIoDevice *device) {
free(device->formats);
free(device->layouts);
if (device->sample_rates != &dev->prealloc_sample_rate_range)
free(device->sample_rates);
free(dev);
}
}
@ -451,7 +454,7 @@ int soundio_outstream_open(struct SoundIoOutStream *outstream) {
}
if (!outstream->sample_rate)
outstream->sample_rate = clamp(device->sample_rate_min, 48000, device->sample_rate_max);
outstream->sample_rate = soundio_device_nearest_sample_rate(device, 48000);
if (!outstream->name)
outstream->name = "SoundIoOutStream";
@ -537,7 +540,7 @@ int soundio_instream_open(struct SoundIoInStream *instream) {
}
if (!instream->sample_rate)
instream->sample_rate = clamp(device->sample_rate_min, 48000, device->sample_rate_max);
instream->sample_rate = soundio_device_nearest_sample_rate(device, 48000);
if (!instream->name)
instream->name = "SoundIoInStream";
@ -687,3 +690,32 @@ bool soundio_device_supports_layout(struct SoundIoDevice *device,
}
return false;
}
bool soundio_device_supports_sample_rate(struct SoundIoDevice *device, int sample_rate) {
for (int i = 0; i < device->sample_rate_count; i += 1) {
SoundIoSampleRateRange *range = &device->sample_rates[i];
if (sample_rate >= range->min && sample_rate <= range->max)
return true;
}
return false;
}
int soundio_device_nearest_sample_rate(struct SoundIoDevice *device, int sample_rate) {
int best_rate = -1;
int best_delta = -1;
for (int i = 0; i < device->sample_rate_count; i += 1) {
SoundIoSampleRateRange *range = &device->sample_rates[i];
if (sample_rate < range->min) {
int delta = range->min - sample_rate;
if (best_delta == -1 || delta < best_delta) {
best_delta = delta;
best_rate = range->min;
}
} else if (best_rate == -1 && sample_rate > range->max) {
best_rate = range->max;
} else {
return sample_rate;
}
}
return best_rate;
}

View file

@ -147,6 +147,7 @@ struct SoundIoDevicePrivate {
SoundIoDevice pub;
SoundIoDeviceBackendData backend_data;
void (*destruct)(SoundIoDevicePrivate *);
SoundIoSampleRateRange prealloc_sample_rate_range;
};
void soundio_destroy_devices_info(struct SoundIoDevicesInfo *devices_info);