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 ## 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 WASAPI (Windows) backend, get examples working
0. implement ASIO (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. Do we really want `period_duration` in the API?
0. Integrate into libgroove and test with Groove Basin 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 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, "\n");
} }
fprintf(stderr, " min sample rate: %d\n", device->sample_rate_min); fprintf(stderr, " sample rates:\n");
fprintf(stderr, " max sample rate: %d\n", device->sample_rate_max); 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) if (device->sample_rate_current)
fprintf(stderr, " current sample rate: %d\n", device->sample_rate_current); fprintf(stderr, " current sample rate: %d\n", device->sample_rate_current);
fprintf(stderr, " formats: "); fprintf(stderr, " formats: ");

View file

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

View file

@ -238,7 +238,13 @@ struct SoundIoChannelLayout {
enum SoundIoChannelId channels[SOUNDIO_MAX_CHANNELS]; 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 { struct SoundIoChannelArea {
// Base address of buffer. // Base address of buffer.
char *ptr; char *ptr;
@ -336,10 +342,11 @@ struct SoundIoDevice {
// Sample rate is the number of frames per second. // Sample rate is the number of frames per second.
// Sample rate is handled very similar to sample format; see those docs. // 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 // If sample rate information is missing due to a probe error, the field
// will be set to zero. // will be set to 0 or NULL.
// Devices are guaranteed to have at least 1 sample rate available. // Devices which have `probe_error` set to `SoundIoErrorNone` are
int sample_rate_min; // guaranteed to have at least 1 sample rate available.
int sample_rate_max; struct SoundIoSampleRateRange *sample_rates;
int sample_rate_count;
int sample_rate_current; int sample_rate_current;
// Buffer duration in seconds. If `buffer_duration_current` is unknown or // 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, bool soundio_device_supports_layout(struct SoundIoDevice *device,
const struct SoundIoChannelLayout *layout); 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 // 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, static int probe_open_device(SoundIoDevice *device, snd_pcm_t *handle, int resample,
int *out_channels_min, int *out_channels_max) int *out_channels_min, int *out_channels_max)
{ {
SoundIoDevicePrivate *dev = (SoundIoDevicePrivate *)device;
int err; int err;
snd_pcm_hw_params_t *hwparams; 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) if ((err = snd_pcm_hw_params_set_rate_last(handle, hwparams, &rate_max, nullptr)) < 0)
return SoundIoErrorOpeningDevice; return SoundIoErrorOpeningDevice;
device->sample_rate_min = rate_min; device->sample_rate_count = 1;
device->sample_rate_max = rate_max; 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; 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; maps = nullptr;
if (!device->is_raw) { if (!device->is_raw) {
if (device->sample_rate_min == device->sample_rate_max) if (device->sample_rates[0].min == device->sample_rates[0].max)
device->sample_rate_current = device->sample_rate_min; device->sample_rate_current = device->sample_rates[0].min;
if (device->buffer_duration_min == device->buffer_duration_max) if (device->buffer_duration_min == device->buffer_duration_max)
device->buffer_duration_current = device->buffer_duration_min; device->buffer_duration_current = device->buffer_duration_min;

View file

@ -642,6 +642,14 @@ static int refresh_devices(struct SoundIoPrivate *si) {
} }
rd.device->sample_rate_current = (int)floored_value; rd.device->sample_rate_current = (int)floored_value;
// If you try to open an input stream with anything but the current
// nominal sample rate, AudioUnitRender returns an error.
if (aim == SoundIoDeviceAimInput) {
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.mSelector = kAudioDevicePropertyAvailableNominalSampleRates;
prop_address.mScope = aim_to_scope(aim); prop_address.mScope = aim_to_scope(aim);
prop_address.mElement = kAudioObjectPropertyElementMaster; prop_address.mElement = kAudioObjectPropertyElementMaster;
@ -666,20 +674,26 @@ static int refresh_devices(struct SoundIoPrivate *si) {
return SoundIoErrorOpeningDevice; 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) { for (int i = 0; i < avr_array_len; i += 1) {
AudioValueRange *avr = &rd.avr_array[i]; AudioValueRange *avr = &rd.avr_array[i];
int min_val = ceil(avr->mMinimum); int min_val = ceil(avr->mMinimum);
int max_val = floor(avr->mMaximum); int max_val = floor(avr->mMaximum);
if (rd.device->sample_rate_min == 0 || min_val < rd.device->sample_rate_min) device->sample_rates[i].min = min_val;
rd.device->sample_rate_min = min_val; device->sample_rates[i].max = max_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;
} }
prop_address.mSelector = kAudioDevicePropertyBufferFrameSize; prop_address.mSelector = kAudioDevicePropertyBufferFrameSize;

View file

@ -363,6 +363,14 @@ static int set_all_device_formats(SoundIoDevice *device) {
return 0; 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) { static int set_all_device_channel_layouts(SoundIoDevice *device) {
device->layout_count = soundio_channel_layout_builtin_count(); device->layout_count = soundio_channel_layout_builtin_count();
device->layouts = allocate<SoundIoChannelLayout>(device->layout_count); device->layouts = allocate<SoundIoChannelLayout>(device->layout_count);
@ -429,12 +437,11 @@ int soundio_dummy_init(SoundIoPrivate *si) {
destroy_dummy(si); destroy_dummy(si);
return err; return err;
} }
set_all_device_sample_rates(device);
device->buffer_duration_min = 0.01; device->buffer_duration_min = 0.01;
device->buffer_duration_max = 4; device->buffer_duration_max = 4;
device->buffer_duration_current = 0.1; device->buffer_duration_current = 0.1;
device->sample_rate_min = 2;
device->sample_rate_max = 5644800;
device->sample_rate_current = 48000; device->sample_rate_current = 48000;
device->period_duration_min = 0.01; device->period_duration_min = 0.01;
device->period_duration_max = 2; device->period_duration_max = 2;
@ -479,11 +486,10 @@ int soundio_dummy_init(SoundIoPrivate *si) {
destroy_dummy(si); destroy_dummy(si);
return err; return err;
} }
set_all_device_sample_rates(device);
device->buffer_duration_min = 0.01; device->buffer_duration_min = 0.01;
device->buffer_duration_max = 4; device->buffer_duration_max = 4;
device->buffer_duration_current = 0.1; device->buffer_duration_current = 0.1;
device->sample_rate_min = 2;
device->sample_rate_max = 5644800;
device->sample_rate_current = 48000; device->sample_rate_current = 48000;
device->period_duration_min = 0.01; device->period_duration_min = 0.01;
device->period_duration_max = 2; device->period_duration_max = 2;

View file

@ -192,8 +192,10 @@ static int refresh_devices_bare(SoundIoPrivate *si) {
device->format_count = 1; device->format_count = 1;
device->formats = allocate<SoundIoFormat>(1); device->formats = allocate<SoundIoFormat>(1);
device->current_format = SoundIoFormatFloat32NE; device->current_format = SoundIoFormatFloat32NE;
device->sample_rate_min = sij->sample_rate; device->sample_rate_count = 1;
device->sample_rate_max = sij->sample_rate; 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->sample_rate_current = sij->sample_rate;
device->period_duration_min = sij->period_size / (double) sij->sample_rate; device->period_duration_min = sij->period_size / (double) sij->sample_rate;
device->period_duration_max = device->period_duration_min; 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; device->sample_rate_current = info->sample_spec.rate;
// PulseAudio performs resampling, so any value is valid. Let's pick // PulseAudio performs resampling, so any value is valid. Let's pick
// some reasonable min and max values. // some reasonable min and max values.
device->sample_rate_min = min(8000, device->sample_rate_current); device->sample_rate_count = 1;
device->sample_rate_max = max(5644800, device->sample_rate_current); 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); device->current_format = from_pulseaudio_format(info->sample_spec);
// PulseAudio performs sample format conversion, so any PulseAudio // 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; device->sample_rate_current = info->sample_spec.rate;
// PulseAudio performs resampling, so any value is valid. Let's pick // PulseAudio performs resampling, so any value is valid. Let's pick
// some reasonable min and max values. // some reasonable min and max values.
device->sample_rate_min = min(8000, device->sample_rate_current); device->sample_rate_count = 1;
device->sample_rate_max = max(5644800, device->sample_rate_current); 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); device->current_format = from_pulseaudio_format(info->sample_spec);
// PulseAudio performs sample format conversion, so any PulseAudio // 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->formats);
free(device->layouts); free(device->layouts);
if (device->sample_rates != &dev->prealloc_sample_rate_range)
free(device->sample_rates);
free(dev); free(dev);
} }
} }
@ -451,7 +454,7 @@ int soundio_outstream_open(struct SoundIoOutStream *outstream) {
} }
if (!outstream->sample_rate) 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) if (!outstream->name)
outstream->name = "SoundIoOutStream"; outstream->name = "SoundIoOutStream";
@ -537,7 +540,7 @@ int soundio_instream_open(struct SoundIoInStream *instream) {
} }
if (!instream->sample_rate) 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) if (!instream->name)
instream->name = "SoundIoInStream"; instream->name = "SoundIoInStream";
@ -687,3 +690,32 @@ bool soundio_device_supports_layout(struct SoundIoDevice *device,
} }
return false; 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; SoundIoDevice pub;
SoundIoDeviceBackendData backend_data; SoundIoDeviceBackendData backend_data;
void (*destruct)(SoundIoDevicePrivate *); void (*destruct)(SoundIoDevicePrivate *);
SoundIoSampleRateRange prealloc_sample_rate_range;
}; };
void soundio_destroy_devices_info(struct SoundIoDevicesInfo *devices_info); void soundio_destroy_devices_info(struct SoundIoDevicesInfo *devices_info);