From 1e4d87e6082624c192acc85f70879e8963c6616c Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 13 Jul 2015 21:30:37 -0700 Subject: [PATCH] ALSA: open output device with parameters --- README.md | 3 + example/microphone.c | 4 +- example/sine.c | 9 +- src/alsa.cpp | 263 ++++++++++++++++++++++++++++++++++++++----- src/dummy.cpp | 63 ++++++----- src/os.cpp | 1 + src/pulseaudio.cpp | 29 +++-- src/soundio.cpp | 42 ++++++- src/soundio.h | 68 +++++++++-- src/soundio.hpp | 4 +- src/util.hpp | 5 + test/unit_tests.cpp | 2 +- test/valgrind.supp | 17 +++ 13 files changed, 418 insertions(+), 92 deletions(-) diff --git a/README.md b/README.md index 48a3747..ecd2186 100644 --- a/README.md +++ b/README.md @@ -15,10 +15,12 @@ exposed. * [PortAudio](http://www.portaudio.com/) - It does not support [PulseAudio](http://www.freedesktop.org/wiki/Software/PulseAudio/). - It logs messages to stdio and you can't turn that off. + - It does not support channel layouts / channel maps. - It is not written by me. * [rtaudio](https://www.music.mcgill.ca/~gary/rtaudio/) - It is not a C library. - It uses [exceptions](http://stackoverflow.com/questions/1736146/why-is-exception-handling-bad). + - It does not support channel layouts / channel maps. - It is not written by me. * [SDL](https://www.libsdl.org/) - It comes with a bunch of other baggage - display, windowing, input @@ -26,6 +28,7 @@ exposed. - It is not designed with real-time low latency audio in mind. - Listing audio devices is [broken](https://github.com/andrewrk/node-groove/issues/13). - It does not support recording devices. + - It does not support channel layouts / channel maps. - It is not written by me. ## How It Works diff --git a/example/microphone.c b/example/microphone.c index 36efd88..f3983b4 100644 --- a/example/microphone.c +++ b/example/microphone.c @@ -121,7 +121,7 @@ int main(int argc, char **argv) { instream->format = *fmt; instream->sample_rate = sample_rate; instream->layout = *layout; - instream->latency = 0.1; + instream->buffer_duration = 0.1; instream->read_callback = read_callback; if ((err = soundio_instream_open(instream))) @@ -133,7 +133,7 @@ int main(int argc, char **argv) { outstream->format = *fmt; outstream->sample_rate = sample_rate; outstream->layout = *layout; - outstream->latency = 0.1; + outstream->buffer_duration = 0.1; outstream->write_callback = write_callback; outstream->underrun_callback = underrun_callback; diff --git a/example/sine.c b/example/sine.c index e6e800f..4f88cb5 100644 --- a/example/sine.c +++ b/example/sine.c @@ -27,8 +27,6 @@ static void panic(const char *format, ...) { static const float PI = 3.1415926535f; static float seconds_offset = 0.0f; -static int target_sample_rate = 48000; - static void write_callback(struct SoundIoOutStream *outstream, int requested_frame_count) { float float_sample_rate = outstream->sample_rate; float seconds_per_frame = 1.0f / float_sample_rate; @@ -45,7 +43,6 @@ static void write_callback(struct SoundIoOutStream *outstream, int requested_fra float *ptr = (float *)data; - // 69 is A 440 float pitch = 440.0f; float radians_per_second = pitch * 2.0f * PI; for (int frame = 0; frame < frame_count; frame += 1) { @@ -83,16 +80,12 @@ int main(int argc, char **argv) { struct SoundIoDevice *device = soundio_get_output_device(soundio, default_out_device_index); if (!device) - panic("could not get output device: out of memory"); + panic("out of memory"); fprintf(stderr, "Output device: %s: %s\n", device->name, device->description); struct SoundIoOutStream *outstream = soundio_outstream_create(device); outstream->format = SoundIoFormatFloat32NE; - outstream->sample_rate = (device->sample_rate_min <= target_sample_rate && - target_sample_rate <= device->sample_rate_max) ? target_sample_rate : device->sample_rate_max; - outstream->layout = device->layouts[0]; - outstream->latency = 0.1; outstream->write_callback = write_callback; outstream->underrun_callback = underrun_callback; diff --git a/src/alsa.cpp b/src/alsa.cpp index a4167ef..8a861cc 100644 --- a/src/alsa.cpp +++ b/src/alsa.cpp @@ -12,6 +12,7 @@ #include #include +#include static snd_pcm_stream_t stream_types[] = {SND_PCM_STREAM_PLAYBACK, SND_PCM_STREAM_CAPTURE}; @@ -31,7 +32,9 @@ struct SoundIoAlsa { }; struct SoundIoOutStreamAlsa { - + snd_pcm_t *handle; + snd_pcm_chmap_t *chmap; + int chmap_size; }; static void wakeup_device_poll(SoundIoAlsa *sia) { @@ -136,6 +139,47 @@ static SoundIoChannelId from_alsa_chmap_pos(unsigned int pos) { return SoundIoChannelIdInvalid; } +static int to_alsa_chmap_pos(SoundIoChannelId channel_id) { + switch (channel_id) { + case SoundIoChannelIdFrontLeft: return SND_CHMAP_FL; + case SoundIoChannelIdFrontRight: return SND_CHMAP_FR; + case SoundIoChannelIdBackLeft: return SND_CHMAP_RL; + case SoundIoChannelIdBackRight: return SND_CHMAP_RR; + case SoundIoChannelIdFrontCenter: return SND_CHMAP_FC; + case SoundIoChannelIdLfe: return SND_CHMAP_LFE; + case SoundIoChannelIdSideLeft: return SND_CHMAP_SL; + case SoundIoChannelIdSideRight: return SND_CHMAP_SR; + case SoundIoChannelIdBackCenter: return SND_CHMAP_RC; + case SoundIoChannelIdFrontLeftCenter: return SND_CHMAP_FLC; + case SoundIoChannelIdFrontRightCenter: return SND_CHMAP_FRC; + case SoundIoChannelIdBackLeftCenter: return SND_CHMAP_RLC; + case SoundIoChannelIdBackRightCenter: return SND_CHMAP_RRC; + case SoundIoChannelIdFrontLeftWide: return SND_CHMAP_FLW; + case SoundIoChannelIdFrontRightWide: return SND_CHMAP_FRW; + case SoundIoChannelIdFrontLeftHigh: return SND_CHMAP_FLH; + case SoundIoChannelIdFrontCenterHigh: return SND_CHMAP_FCH; + case SoundIoChannelIdFrontRightHigh: return SND_CHMAP_FRH; + case SoundIoChannelIdTopCenter: return SND_CHMAP_TC; + case SoundIoChannelIdTopFrontLeft: return SND_CHMAP_TFL; + case SoundIoChannelIdTopFrontRight: return SND_CHMAP_TFR; + case SoundIoChannelIdTopFrontCenter: return SND_CHMAP_TFC; + case SoundIoChannelIdTopBackLeft: return SND_CHMAP_TRL; + case SoundIoChannelIdTopBackRight: return SND_CHMAP_TRR; + case SoundIoChannelIdTopBackCenter: return SND_CHMAP_TRC; + case SoundIoChannelIdTopFrontLeftCenter: return SND_CHMAP_TFLC; + case SoundIoChannelIdTopFrontRightCenter: return SND_CHMAP_TFRC; + case SoundIoChannelIdTopSideLeft: return SND_CHMAP_TSL; + case SoundIoChannelIdTopSideRight: return SND_CHMAP_TSR; + case SoundIoChannelIdLeftLfe: return SND_CHMAP_LLFE; + case SoundIoChannelIdRightLfe: return SND_CHMAP_RLFE; + case SoundIoChannelIdBottomCenter: return SND_CHMAP_BC; + case SoundIoChannelIdBottomLeftCenter: return SND_CHMAP_BLC; + case SoundIoChannelIdBottomRightCenter: return SND_CHMAP_BRC; + case SoundIoChannelIdInvalid: return SND_CHMAP_UNKNOWN; + } + return SND_CHMAP_UNKNOWN; +} + static void get_channel_layout(SoundIoChannelLayout *dest, snd_pcm_chmap_t *chmap) { int channel_count = min((unsigned int)SOUNDIO_MAX_CHANNELS, chmap->channels); dest->channel_count = channel_count; @@ -209,15 +253,6 @@ static void test_fmt_mask(SoundIoDevice *device, const snd_pcm_format_mask_t *fm } } -// TODO: look at http://www.alsa-project.org/alsa-doc/alsa-lib/_2test_2pcm_8c-example.html#a27 -// deterimine what do do about: -// * hw buffer size -// * hw period time -// * sw start threshold -// * sw avail min -// TODO: device->default_latency - - // this function does not override device->formats, so if you want it to, deallocate and set it to NULL static int probe_open_device(SoundIoDevice *device, snd_pcm_t *handle, snd_pcm_hw_params_t *hwparams, int resample) @@ -237,20 +272,67 @@ static int probe_open_device(SoundIoDevice *device, snd_pcm_t *handle, if ((err = snd_pcm_hw_params_set_channels_last(handle, hwparams, &channel_count)) < 0) return SoundIoErrorOpeningDevice; - unsigned int min_sample_rate; - unsigned int max_sample_rate; - int min_dir; - int max_dir; + unsigned int num; + unsigned int den; - if ((err = snd_pcm_hw_params_get_rate_max(hwparams, &max_sample_rate, &max_dir)) < 0) + int dir = 0; + if ((err = snd_pcm_hw_params_set_rate_first(handle, hwparams, &num, &dir)) < 0) return SoundIoErrorOpeningDevice; - if (max_dir < 0) - max_sample_rate -= 1; - if ((err = snd_pcm_hw_params_get_rate_min(hwparams, &min_sample_rate, &min_dir)) < 0) + if ((err = snd_pcm_hw_params_get_rate_numden(hwparams, &num, &den)) < 0) return SoundIoErrorOpeningDevice; - if (min_dir > 0) - min_sample_rate += 1; + + if (den != 1) + return SoundIoErrorOpeningDevice; + + device->sample_rate_min = num; + + dir = 0; + if ((err = snd_pcm_hw_params_set_rate_last(handle, hwparams, &num, &dir)) < 0) + return SoundIoErrorOpeningDevice; + + if ((err = snd_pcm_hw_params_get_rate_numden(hwparams, &num, &den)) < 0) + return SoundIoErrorOpeningDevice; + + if (den != 1) + return SoundIoErrorOpeningDevice; + + device->sample_rate_max = num; + + // Purposefully leave the parameters with the highest rate, highest channel count. + + snd_pcm_uframes_t min_frames; + snd_pcm_uframes_t max_frames; + if ((err = snd_pcm_hw_params_get_buffer_size_min(hwparams, &min_frames)) < 0) + return SoundIoErrorOpeningDevice; + if ((err = snd_pcm_hw_params_get_buffer_size_max(hwparams, &max_frames)) < 0) + return SoundIoErrorOpeningDevice; + + // divide the frame counts by the max sample rate + device->buffer_duration_min = ((double)min_frames) / (double)device->sample_rate_max; + device->buffer_duration_max = ((double)max_frames) / (double)device->sample_rate_max; + + if ((err = snd_pcm_hw_params_set_periods_integer(handle, hwparams)) < 0) + return SoundIoErrorOpeningDevice; + + dir = 0; + if ((err = snd_pcm_hw_params_get_periods_min(hwparams, &num, &dir)) < 0) + return SoundIoErrorOpeningDevice; + + if (dir != 0) + return SoundIoErrorOpeningDevice; + + device->period_count_min = num; + + dir = 0; + if ((err = snd_pcm_hw_params_get_periods_max(hwparams, &num, &dir)) < 0) + return SoundIoErrorOpeningDevice; + + if (dir != 0) + return SoundIoErrorOpeningDevice; + + device->period_count_max = num; + snd_pcm_format_mask_t *fmt_mask; snd_pcm_format_mask_alloca(&fmt_mask); @@ -304,9 +386,6 @@ static int probe_open_device(SoundIoDevice *device, snd_pcm_t *handle, test_fmt_mask(device, fmt_mask, SoundIoFormatFloat64BE); } - device->sample_rate_min = min_sample_rate; - device->sample_rate_max = max_sample_rate; - return 0; } @@ -347,8 +426,15 @@ static int probe_device(SoundIoDevice *device, snd_pcm_chmap_query_t **maps) { } maps = nullptr; - if (device->sample_rate_min == device->sample_rate_max && !device->is_raw) { - device->sample_rate_current = device->sample_rate_min; + if (!device->is_raw) { + if (device->sample_rate_min == device->sample_rate_max) + device->sample_rate_current = device->sample_rate_min; + + if (device->buffer_duration_min == device->buffer_duration_max) + device->buffer_duration_current = device->buffer_duration_min; + + if (device->period_count_min == device->period_count_max) + device->period_count_current = device->period_count_min; // now say that resampling is OK and see what the real min and max is. if ((err = probe_open_device(device, handle, hwparams, 1)) < 0) { @@ -754,11 +840,17 @@ static void outstream_destroy_alsa(SoundIoPrivate *si, SoundIoOutStreamPrivate * if (!osa) return; + deallocate(osa->chmap, osa->chmap_size); + + if (osa->handle) + snd_pcm_close(osa->handle); + destroy(osa); os->backend_data = nullptr; } -static int outstream_init_alsa(SoundIoPrivate *si, SoundIoOutStreamPrivate *os) { +static int outstream_open_alsa(SoundIoPrivate *si, SoundIoOutStreamPrivate *os) { + SoundIoOutStream *outstream = &os->pub; SoundIoOutStreamAlsa *osa = create(); if (!osa) { outstream_destroy_alsa(si, os); @@ -766,6 +858,119 @@ static int outstream_init_alsa(SoundIoPrivate *si, SoundIoOutStreamPrivate *os) } os->backend_data = osa; + osa->chmap_size = sizeof(int) + sizeof(int) * outstream->layout.channel_count; + osa->chmap = (snd_pcm_chmap_t *)allocate(osa->chmap_size); + if (!osa->chmap) { + outstream_destroy_alsa(si, os); + return SoundIoErrorNoMem; + } + + int err; + + snd_pcm_hw_params_t *hwparams; + snd_pcm_hw_params_alloca(&hwparams); + + snd_pcm_stream_t stream = purpose_to_stream(outstream->device->purpose); + + if ((err = snd_pcm_open(&osa->handle, outstream->device->name, stream, 0)) < 0) { + outstream_destroy_alsa(si, os); + return SoundIoErrorOpeningDevice; + } + + if ((err = snd_pcm_hw_params_any(osa->handle, hwparams)) < 0) { + outstream_destroy_alsa(si, os); + return SoundIoErrorOpeningDevice; + } + + int want_resample = !outstream->device->is_raw; + if ((err = snd_pcm_hw_params_set_rate_resample(osa->handle, hwparams, want_resample)) < 0) { + outstream_destroy_alsa(si, os); + return SoundIoErrorOpeningDevice; + } + + if ((err = snd_pcm_hw_params_set_access(osa->handle, hwparams, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) { + outstream_destroy_alsa(si, os); + return SoundIoErrorOpeningDevice; + } + + if ((err = snd_pcm_hw_params_set_channels(osa->handle, hwparams, outstream->layout.channel_count)) < 0) { + outstream_destroy_alsa(si, os); + return SoundIoErrorOpeningDevice; + } + + if ((err = snd_pcm_hw_params_set_rate(osa->handle, hwparams, outstream->sample_rate, 0)) < 0) { + outstream_destroy_alsa(si, os); + return SoundIoErrorOpeningDevice; + } + + if ((err = snd_pcm_hw_params_set_format(osa->handle, hwparams, to_alsa_fmt(outstream->format))) < 0) { + outstream_destroy_alsa(si, os); + return SoundIoErrorOpeningDevice; + } + + + snd_pcm_uframes_t buffer_size_frames = ceil(outstream->buffer_duration * (double)outstream->sample_rate); + if ((err = snd_pcm_hw_params_set_buffer_size_near(osa->handle, hwparams, &buffer_size_frames)) < 0) { + outstream_destroy_alsa(si, os); + return SoundIoErrorOpeningDevice; + } + outstream->buffer_duration = ((double)buffer_size_frames) / (double)outstream->sample_rate; + + unsigned int actual_periods_count = outstream->period_count; + if ((err = snd_pcm_hw_params_set_periods_near(osa->handle, hwparams, &actual_periods_count, nullptr)) < 0) { + outstream_destroy_alsa(si, os); + return SoundIoErrorOpeningDevice; + } + outstream->period_count = actual_periods_count; + + + snd_pcm_uframes_t period_size; + if ((snd_pcm_hw_params_get_period_size(hwparams, &period_size, nullptr)) < 0) { + outstream_destroy_alsa(si, os); + return SoundIoErrorOpeningDevice; + } + + // write the hardware parameters to device + if ((err = snd_pcm_hw_params(osa->handle, hwparams)) < 0) { + outstream_destroy_alsa(si, os); + return SoundIoErrorOpeningDevice; + } + + // set channel map + osa->chmap->channels = outstream->layout.channel_count; + for (int i = 0; i < outstream->layout.channel_count; i += 1) { + osa->chmap->pos[i] = to_alsa_chmap_pos(outstream->layout.channels[i]); + } + if (snd_pcm_set_chmap(osa->handle, osa->chmap) < 0) { + outstream_destroy_alsa(si, os); + return SoundIoErrorOpeningDevice; + } + + // get current swparams + snd_pcm_sw_params_t *swparams; + snd_pcm_sw_params_alloca(&swparams); + + if ((err = snd_pcm_sw_params_current(osa->handle, swparams)) < 0) { + outstream_destroy_alsa(si, os); + return SoundIoErrorOpeningDevice; + } + + if ((err = snd_pcm_sw_params_set_start_threshold(osa->handle, swparams, buffer_size_frames)) < 0) { + outstream_destroy_alsa(si, os); + return SoundIoErrorOpeningDevice; + } + + if ((err = snd_pcm_sw_params_set_avail_min(osa->handle, swparams, period_size)) < 0) { + outstream_destroy_alsa(si, os); + return SoundIoErrorOpeningDevice; + } + + // write the software parameters to device + if ((err = snd_pcm_sw_params(osa->handle, swparams)) < 0) { + outstream_destroy_alsa(si, os); + return SoundIoErrorOpeningDevice; + } + return 0; } @@ -795,7 +1000,7 @@ static void outstream_clear_buffer_alsa(SoundIoPrivate *si, soundio_panic("TODO"); } -static int instream_init_alsa(SoundIoPrivate *si, SoundIoInStreamPrivate *is) { +static int instream_open_alsa(SoundIoPrivate *si, SoundIoInStreamPrivate *is) { soundio_panic("TODO"); } @@ -900,7 +1105,7 @@ int soundio_alsa_init(SoundIoPrivate *si) { si->wait_events = wait_events; si->wakeup = wakeup; - si->outstream_init = outstream_init_alsa; + si->outstream_open = outstream_open_alsa; si->outstream_destroy = outstream_destroy_alsa; si->outstream_start = outstream_start_alsa; si->outstream_free_count = outstream_free_count_alsa; @@ -908,7 +1113,7 @@ int soundio_alsa_init(SoundIoPrivate *si) { si->outstream_write = outstream_write_alsa; si->outstream_clear_buffer = outstream_clear_buffer_alsa; - si->instream_init = instream_init_alsa; + si->instream_open = instream_open_alsa; si->instream_destroy = instream_destroy_alsa; si->instream_start = instream_start_alsa; si->instream_peek = instream_peek_alsa; diff --git a/src/dummy.cpp b/src/dummy.cpp index e26fae7..6b3f43c 100644 --- a/src/dummy.cpp +++ b/src/dummy.cpp @@ -131,7 +131,7 @@ static void outstream_destroy_dummy(SoundIoPrivate *si, SoundIoOutStreamPrivate os->backend_data = nullptr; } -static int outstream_init_dummy(SoundIoPrivate *si, SoundIoOutStreamPrivate *os) { +static int outstream_open_dummy(SoundIoPrivate *si, SoundIoOutStreamPrivate *os) { SoundIoOutStream *outstream = &os->pub; SoundIoOutStreamDummy *osd = create(); if (!osd) { @@ -140,9 +140,8 @@ static int outstream_init_dummy(SoundIoPrivate *si, SoundIoOutStreamPrivate *os) } os->backend_data = osd; - int buffer_frame_count = outstream->latency * outstream->sample_rate; - osd->buffer_size = outstream->bytes_per_frame * buffer_frame_count; - osd->period = outstream->latency * 0.5; + osd->buffer_size = outstream->bytes_per_frame * outstream->buffer_duration; + osd->period = outstream->buffer_duration / (double)outstream->period_count; soundio_ring_buffer_init(&osd->ring_buffer, osd->buffer_size); @@ -205,7 +204,7 @@ static void outstream_clear_buffer_dummy(SoundIoPrivate *si, SoundIoOutStreamPri soundio_ring_buffer_clear(&osd->ring_buffer); } -static int instream_init_dummy(SoundIoPrivate *si, SoundIoInStreamPrivate *is) { +static int instream_open_dummy(SoundIoPrivate *si, SoundIoInStreamPrivate *is) { soundio_panic("TODO"); } @@ -237,24 +236,24 @@ static int set_all_device_formats(SoundIoDevice *device) { if (!device->formats) return SoundIoErrorNoMem; - device->formats[0] = SoundIoFormatS8; - device->formats[1] = SoundIoFormatU8; - device->formats[2] = SoundIoFormatS16LE; - device->formats[3] = SoundIoFormatS16BE; - device->formats[4] = SoundIoFormatU16LE; - device->formats[5] = SoundIoFormatU16BE; - device->formats[6] = SoundIoFormatS24LE; - device->formats[7] = SoundIoFormatS24BE; - device->formats[8] = SoundIoFormatU24LE; - device->formats[9] = SoundIoFormatU24BE; - device->formats[10] = SoundIoFormatS32LE; - device->formats[11] = SoundIoFormatS32BE; - device->formats[12] = SoundIoFormatU32LE; - device->formats[13] = SoundIoFormatU32BE; - device->formats[14] = SoundIoFormatFloat32LE; - device->formats[15] = SoundIoFormatFloat32BE; - device->formats[16] = SoundIoFormatFloat64LE; - device->formats[17] = SoundIoFormatFloat64BE; + device->formats[0] = SoundIoFormatFloat32NE; + device->formats[1] = SoundIoFormatFloat32FE; + device->formats[2] = SoundIoFormatS32NE; + device->formats[3] = SoundIoFormatS32FE; + device->formats[4] = SoundIoFormatU32NE; + device->formats[5] = SoundIoFormatU32FE; + device->formats[6] = SoundIoFormatS24NE; + device->formats[7] = SoundIoFormatS24FE; + device->formats[8] = SoundIoFormatU24NE; + device->formats[9] = SoundIoFormatU24FE; + device->formats[10] = SoundIoFormatFloat64NE; + device->formats[11] = SoundIoFormatFloat64FE; + device->formats[12] = SoundIoFormatS16NE; + device->formats[13] = SoundIoFormatS16FE; + device->formats[14] = SoundIoFormatU16NE; + device->formats[15] = SoundIoFormatU16FE; + device->formats[16] = SoundIoFormatS8; + device->formats[17] = SoundIoFormatU8; return 0; } @@ -324,10 +323,15 @@ int soundio_dummy_init(SoundIoPrivate *si) { return err; } - device->default_latency = 0.01; + 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_count_min = 1; + device->period_count_max = 16; + device->period_count_current = 2; device->purpose = SoundIoDevicePurposeOutput; if (si->safe_devices_info->output_devices.append(device)) { @@ -370,10 +374,15 @@ int soundio_dummy_init(SoundIoPrivate *si) { destroy_dummy(si); return err; } - device->default_latency = 0.01; + 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_count_min = 1; + device->period_count_max = 16; + device->period_count_current = 2; device->purpose = SoundIoDevicePurposeInput; if (si->safe_devices_info->input_devices.append(device)) { @@ -389,7 +398,7 @@ int soundio_dummy_init(SoundIoPrivate *si) { si->wait_events = wait_events; si->wakeup = wakeup; - si->outstream_init = outstream_init_dummy; + si->outstream_open = outstream_open_dummy; si->outstream_destroy = outstream_destroy_dummy; si->outstream_start = outstream_start_dummy; si->outstream_free_count = outstream_free_count_dummy; @@ -397,7 +406,7 @@ int soundio_dummy_init(SoundIoPrivate *si) { si->outstream_write = outstream_write_dummy; si->outstream_clear_buffer = outstream_clear_buffer_dummy; - si->instream_init = instream_init_dummy; + si->instream_open = instream_open_dummy; si->instream_destroy = instream_destroy_dummy; si->instream_start = instream_start_dummy; si->instream_peek = instream_peek_dummy; diff --git a/src/os.cpp b/src/os.cpp index fb5eca2..f0134d3 100644 --- a/src/os.cpp +++ b/src/os.cpp @@ -695,6 +695,7 @@ void soundio_os_destroy_mirrored_memory(char *address, size_t capacity) { if (!address) return; #if defined(SOUNDIO_OS_WINDOWS) + // TODO #else int err = munmap(address, 2 * capacity); assert(!err); diff --git a/src/pulseaudio.cpp b/src/pulseaudio.cpp index d950fdd..25cdd7f 100644 --- a/src/pulseaudio.cpp +++ b/src/pulseaudio.cpp @@ -134,9 +134,11 @@ static void destroy_pa(SoundIoPrivate *si) { si->backend_data = nullptr; } +/* TODO static double usec_to_sec(pa_usec_t usec) { return (double)usec / (double)PA_USEC_PER_SEC; } +*/ static SoundIoFormat format_from_pulseaudio(pa_sample_spec sample_spec) { @@ -162,9 +164,11 @@ static SoundIoFormat format_from_pulseaudio(pa_sample_spec sample_spec) { return SoundIoFormatInvalid; } +/* TODO static int sample_rate_from_pulseaudio(pa_sample_spec sample_spec) { return sample_spec.rate; } +*/ /* TODO static SoundIoChannelId from_pulseaudio_channel_pos(pa_channel_position_t pos) { @@ -287,8 +291,10 @@ static void sink_info_callback(pa_context *pulse_context, const pa_sink_info *in // TODO determine the channel layouts supported //TODO set_from_pulseaudio_channel_map(info->channel_map, &device->channel_layout); device->current_format = format_from_pulseaudio(info->sample_spec); - device->default_latency = usec_to_sec(info->configured_latency); - device->sample_rate_current = sample_rate_from_pulseaudio(info->sample_spec); + // TODO set min, max, current latency + //device->default_latency = usec_to_sec(info->configured_latency); + // TODO set min, max, current sample rate + //device->sample_rate_current = sample_rate_from_pulseaudio(info->sample_spec); device->purpose = SoundIoDevicePurposeOutput; if (sipa->current_devices_info->output_devices.append(device)) @@ -319,8 +325,10 @@ static void source_info_callback(pa_context *pulse_context, const pa_source_info // TODO determine the channel layouts supported // TODO set_from_pulseaudio_channel_map(info->channel_map, &device->channel_layout); device->current_format = format_from_pulseaudio(info->sample_spec); - device->default_latency = usec_to_sec(info->configured_latency); - device->sample_rate_current = sample_rate_from_pulseaudio(info->sample_spec); + // TODO set min, max, current latency + //device->default_latency = usec_to_sec(info->configured_latency); + // TODO set min, max, current sample rate + //device->sample_rate_current = sample_rate_from_pulseaudio(info->sample_spec); device->purpose = SoundIoDevicePurposeInput; if (sipa->current_devices_info->input_devices.append(device)) @@ -590,8 +598,9 @@ static void outstream_destroy_pa(SoundIoPrivate *si, SoundIoOutStreamPrivate *os os->backend_data = nullptr; } -static int outstream_init_pa(SoundIoPrivate *si, SoundIoOutStreamPrivate *os) { +static int outstream_open_pa(SoundIoPrivate *si, SoundIoOutStreamPrivate *os) { SoundIoOutStream *outstream = &os->pub; + SoundIoOutStreamPulseAudio *ospa = create(); if (!ospa) { outstream_destroy_pa(si, os); @@ -626,7 +635,7 @@ static int outstream_init_pa(SoundIoPrivate *si, SoundIoOutStreamPrivate *os) { int bytes_per_second = outstream->bytes_per_frame * outstream->sample_rate; int buffer_length = outstream->bytes_per_frame * - ceil(outstream->latency * bytes_per_second / (double)outstream->bytes_per_frame); + ceil(outstream->buffer_duration * bytes_per_second / (double)outstream->bytes_per_frame); ospa->buffer_attr.maxlength = buffer_length; ospa->buffer_attr.tlength = buffer_length; @@ -757,7 +766,7 @@ static void instream_destroy_pa(SoundIoPrivate *si, SoundIoInStreamPrivate *inst } } -static int instream_init_pa(SoundIoPrivate *si, SoundIoInStreamPrivate *is) { +static int instream_open_pa(SoundIoPrivate *si, SoundIoInStreamPrivate *is) { SoundIoInStream *instream = &is->pub; SoundIoInStreamPulseAudio *ispa = create(); if (!ispa) { @@ -793,7 +802,7 @@ static int instream_init_pa(SoundIoPrivate *si, SoundIoInStreamPrivate *is) { int bytes_per_second = instream->bytes_per_frame * instream->sample_rate; int buffer_length = instream->bytes_per_frame * - ceil(instream->latency * bytes_per_second / (double)instream->bytes_per_frame); + ceil(instream->buffer_duration * bytes_per_second / (double)instream->bytes_per_frame); ispa->buffer_attr.maxlength = UINT32_MAX; ispa->buffer_attr.tlength = UINT32_MAX; @@ -937,7 +946,7 @@ int soundio_pulseaudio_init(SoundIoPrivate *si) { si->wait_events = wait_events; si->wakeup = wakeup; - si->outstream_init = outstream_init_pa; + si->outstream_open = outstream_open_pa; si->outstream_destroy = outstream_destroy_pa; si->outstream_start = outstream_start_pa; si->outstream_free_count = outstream_free_count_pa; @@ -945,7 +954,7 @@ int soundio_pulseaudio_init(SoundIoPrivate *si) { si->outstream_write = outstream_write_pa; si->outstream_clear_buffer = outstream_clear_buffer_pa; - si->instream_init = instream_init_pa; + si->instream_open = instream_open_pa; si->instream_destroy = instream_destroy_pa; si->instream_start = instream_start_pa; si->instream_peek = instream_peek_pa; diff --git a/src/soundio.cpp b/src/soundio.cpp index f74cc4e..d853187 100644 --- a/src/soundio.cpp +++ b/src/soundio.cpp @@ -208,7 +208,7 @@ void soundio_disconnect(struct SoundIo *soundio) { si->wait_events = nullptr; si->wakeup = nullptr; - si->outstream_init = nullptr; + si->outstream_open = nullptr; si->outstream_destroy = nullptr; si->outstream_start = nullptr; si->outstream_free_count = nullptr; @@ -216,7 +216,7 @@ void soundio_disconnect(struct SoundIo *soundio) { si->outstream_write = nullptr; si->outstream_clear_buffer = nullptr; - si->instream_init = nullptr; + si->instream_open = nullptr; si->instream_destroy = nullptr; si->instream_start = nullptr; si->instream_peek = nullptr; @@ -304,6 +304,7 @@ void soundio_device_unref(struct SoundIoDevice *device) { free(device->name); free(device->description); deallocate(device->formats, device->format_count); + deallocate(device->layouts, device->layout_count); destroy(device); } } @@ -369,18 +370,28 @@ struct SoundIoOutStream *soundio_outstream_create(struct SoundIoDevice *device) outstream->device = device; soundio_device_ref(device); - // TODO set defaults + const SoundIoChannelLayout *stereo = soundio_channel_layout_get_builtin(SoundIoChannelLayoutIdStereo); + + outstream->format = soundio_device_supports_format(device, SoundIoFormatFloat32NE) ? + SoundIoFormatFloat32NE : device->formats[0]; + outstream->layout = soundio_device_supports_layout(device, stereo) ? *stereo : device->layouts[0]; + outstream->sample_rate = clamp(device->sample_rate_min, 48000, device->sample_rate_max); + outstream->buffer_duration = clamp(device->buffer_duration_min, 1.0, device->buffer_duration_max); + outstream->period_count = clamp(device->period_count_min, 2, device->period_count_max); return outstream; } int soundio_outstream_open(struct SoundIoOutStream *outstream) { + if (outstream->format <= SoundIoFormatInvalid) + return SoundIoErrorInvalid; + SoundIoOutStreamPrivate *os = (SoundIoOutStreamPrivate *)outstream; outstream->bytes_per_frame = soundio_get_bytes_per_frame(outstream->format, outstream->layout.channel_count); SoundIo *soundio = outstream->device->soundio; SoundIoPrivate *si = (SoundIoPrivate *)soundio; - return si->outstream_init(si, os); + return si->outstream_open(si, os); } void soundio_outstream_destroy(SoundIoOutStream *outstream) { @@ -414,17 +425,26 @@ struct SoundIoInStream *soundio_instream_create(struct SoundIoDevice *device) { instream->device = device; soundio_device_ref(device); - // TODO set defaults + const SoundIoChannelLayout *stereo = soundio_channel_layout_get_builtin(SoundIoChannelLayoutIdStereo); + + instream->format = soundio_device_supports_format(device, SoundIoFormatFloat32NE) ? + SoundIoFormatFloat32NE : device->formats[0]; + instream->layout = soundio_device_supports_layout(device, stereo) ? *stereo : device->layouts[0]; + instream->sample_rate = clamp(device->sample_rate_min, 48000, device->sample_rate_max); + instream->buffer_duration = clamp(device->buffer_duration_min, 1.0, device->buffer_duration_max); + instream->period_count = clamp(device->period_count_min, 8, device->period_count_max); return instream; } int soundio_instream_open(struct SoundIoInStream *instream) { + if (instream->format <= SoundIoFormatInvalid) + return SoundIoErrorInvalid; instream->bytes_per_frame = soundio_get_bytes_per_frame(instream->format, instream->layout.channel_count); SoundIo *soundio = instream->device->soundio; SoundIoPrivate *si = (SoundIoPrivate *)soundio; SoundIoInStreamPrivate *is = (SoundIoInStreamPrivate *)instream; - return si->instream_init(si, is); + return si->instream_open(si, is); } int soundio_instream_start(struct SoundIoInStream *instream) { @@ -543,3 +563,13 @@ bool soundio_device_supports_format(struct SoundIoDevice *device, enum SoundIoFo } return false; } + +bool soundio_device_supports_layout(struct SoundIoDevice *device, + const struct SoundIoChannelLayout *layout) +{ + for (int i = 0; i < device->layout_count; i += 1) { + if (soundio_channel_layout_equal(&device->layouts[i], layout)) + return true; + } + return false; +} diff --git a/src/soundio.h b/src/soundio.h index 8c5408a..a54ef70 100644 --- a/src/soundio.h +++ b/src/soundio.h @@ -185,6 +185,7 @@ struct SoundIoChannelLayout { enum SoundIoChannelId channels[SOUNDIO_MAX_CHANNELS]; }; + // The size of this struct is not part of the API or ABI. struct SoundIoDevice { // Read-only. Set automatically. @@ -232,7 +233,15 @@ struct SoundIoDevice { int sample_rate_max; int sample_rate_current; - double default_latency; + // Buffer duration in seconds. + double buffer_duration_min; + double buffer_duration_max; + double buffer_duration_current; + + // How many slices it is possible to cut the buffer into. + int period_count_min; + int period_count_max; + int period_count_current; // Tells whether this device is an input device or an output device. enum SoundIoDevicePurpose purpose; @@ -258,12 +267,34 @@ struct SoundIoDevice { // The size of this struct is not part of the API or ABI. struct SoundIoOutStream { + // Populated automatically when you call soundio_outstream_create. struct SoundIoDevice *device; - enum SoundIoFormat format; - int sample_rate; - struct SoundIoChannelLayout layout; - double latency; + // Defaults to SoundIoFormatFloat32NE, followed by the first one supported. + enum SoundIoFormat format; + + // Defaults to 48000 (and then clamped into range). + int sample_rate; + + // Defaults to Stereo, if available, followed by the first layout supported. + struct SoundIoChannelLayout layout; + + // Buffer duration in seconds. + // (buffer_duration / period_count) is the latency; how much time it takes + // for a sample put in the buffer to get played. + // After you call soundio_outstream_open this value is replaced with the + // actual duration, as near to this value as possible. + // Defaults to 1 second (and then clamped into range). + double buffer_duration; + + // How many slices the buffer is cut into. The IRQ will happen every + // (buffer_frame_count / period_count) frames. + // After you call soundio_outstream_open this value is replaced with the + // actual period count, as near to this value as possible. + // Defaults to 2 (and then clamped into range). + int period_count; + + // Defaults to NULL. void *userdata; void (*underrun_callback)(struct SoundIoOutStream *); void (*write_callback)(struct SoundIoOutStream *, int frame_count); @@ -274,11 +305,28 @@ struct SoundIoOutStream { // The size of this struct is not part of the API or ABI. struct SoundIoInStream { + // Populated automatically when you call soundio_outstream_create. struct SoundIoDevice *device; + + // Defaults to SoundIoFormatFloat32NE, followed by the first one supported. enum SoundIoFormat format; + + // Defaults to max(sample_rate_min, min(sample_rate_max, 48000)) int sample_rate; + + // Defaults to Stereo, if available, followed by the first layout supported. struct SoundIoChannelLayout layout; - double latency; + + // Buffer duration in seconds. If the captured audio frames exceeds this + // before they are read, a buffer overrun occurs and the frames are lost. + // Defaults to 1 second (and then clamped into range). + double buffer_duration; + + // How many slices the buffer is cut into. The IRQ will happen every + // (buffer_duration / period_count) seconds, and that is the latency of the + // captured audio. This value must be a power of 2. + // Defaults to 8. + int period_count; void *userdata; void (*read_callback)(struct SoundIoInStream *); @@ -413,7 +461,13 @@ enum SoundIoDevicePurpose soundio_device_purpose(const struct SoundIoDevice *dev void soundio_device_sort_channel_layouts(struct SoundIoDevice *device); // Returns whether `format` is included in the devices supported formats. -bool soundio_device_supports_format(struct SoundIoDevice *device, enum SoundIoFormat format); +bool soundio_device_supports_format(struct SoundIoDevice *device, + enum SoundIoFormat format); + +// Returns whether `layout` is included in the devices supported channel +// layouts. +bool soundio_device_supports_layout(struct SoundIoDevice *device, + const struct SoundIoChannelLayout *layout); diff --git a/src/soundio.hpp b/src/soundio.hpp index 6d425f9..1cbf48c 100644 --- a/src/soundio.hpp +++ b/src/soundio.hpp @@ -43,7 +43,7 @@ struct SoundIoPrivate { void (*wait_events)(struct SoundIoPrivate *); void (*wakeup)(struct SoundIoPrivate *); - int (*outstream_init)(struct SoundIoPrivate *, struct SoundIoOutStreamPrivate *); + int (*outstream_open)(struct SoundIoPrivate *, struct SoundIoOutStreamPrivate *); void (*outstream_destroy)(struct SoundIoPrivate *, struct SoundIoOutStreamPrivate *); int (*outstream_start)(struct SoundIoPrivate *, struct SoundIoOutStreamPrivate *); int (*outstream_free_count)(struct SoundIoPrivate *, struct SoundIoOutStreamPrivate *); @@ -54,7 +54,7 @@ struct SoundIoPrivate { void (*outstream_clear_buffer)(struct SoundIoPrivate *, struct SoundIoOutStreamPrivate *); - int (*instream_init)(struct SoundIoPrivate *, struct SoundIoInStreamPrivate *); + int (*instream_open)(struct SoundIoPrivate *, struct SoundIoInStreamPrivate *); void (*instream_destroy)(struct SoundIoPrivate *, struct SoundIoInStreamPrivate *); int (*instream_start)(struct SoundIoPrivate *, struct SoundIoInStreamPrivate *); void (*instream_peek)(struct SoundIoPrivate *, struct SoundIoInStreamPrivate *, diff --git a/src/util.hpp b/src/util.hpp index 2f64051..62cb7c8 100644 --- a/src/util.hpp +++ b/src/util.hpp @@ -103,4 +103,9 @@ template static inline T min(T a, T b) { return (a <= b) ? a : b; } + +template +static inline T clamp(T min_value, T value, T max_value) { + return max(min(value, max_value), min_value); +} #endif diff --git a/test/unit_tests.cpp b/test/unit_tests.cpp index f876326..5ec7d92 100644 --- a/test/unit_tests.cpp +++ b/test/unit_tests.cpp @@ -38,7 +38,7 @@ static void test_create_outstream(void) { outstream->format = SoundIoFormatFloat32NE; outstream->sample_rate = 48000; outstream->layout = device->layouts[0]; - outstream->latency = 0.1; + outstream->buffer_duration = 0.1; outstream->write_callback = write_callback; outstream->underrun_callback = underrun_callback; diff --git a/test/valgrind.supp b/test/valgrind.supp index fde83b6..f57f18d 100644 --- a/test/valgrind.supp +++ b/test/valgrind.supp @@ -94,3 +94,20 @@ ... fun:snd1_dlobj_cache_get } +{ + + Memcheck:Leak + match-leak-kinds: definite + fun:malloc + fun:snd_pcm_hw_get_chmap + fun:snd_pcm_get_chmap + fun:snd1_pcm_direct_get_chmap + fun:snd_pcm_get_chmap + fun:snd1_pcm_generic_get_chmap + fun:snd_pcm_get_chmap + fun:snd1_pcm_generic_get_chmap + fun:snd_pcm_get_chmap + fun:snd1_pcm_generic_get_chmap + fun:snd_pcm_get_chmap + fun:snd_pcm_set_chmap +}