mirror of
https://github.com/Ryujinx/libsoundio.git
synced 2024-12-22 17:35:32 +00:00
sample rate is exposed as a list of min/max pairs
This commit is contained in:
parent
eb7308d992
commit
7238d29666
10
README.md
10
README.md
|
@ -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
|
||||
|
|
|
@ -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: ");
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
11
src/alsa.cpp
11
src/alsa.cpp
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Reference in a new issue