diff --git a/README.md b/README.md index d6a0062..fc17343 100644 --- a/README.md +++ b/README.md @@ -66,8 +66,9 @@ static void write_callback(struct SoundIoOutStream *outstream, int requested_fra float seconds_per_frame = 1.0f / float_sample_rate; int err; - int frame_count = requested_frame_count; for (;;) { + int frame_count = requested_frame_count; + struct SoundIoChannelArea *areas; if ((err = soundio_outstream_begin_write(outstream, &areas, &frame_count))) panic("%s", soundio_strerror(err)); @@ -90,6 +91,10 @@ static void write_callback(struct SoundIoOutStream *outstream, int requested_fra if ((err = soundio_outstream_write(outstream, frame_count))) panic("%s", soundio_strerror(err)); + + requested_frame_count -= frame_count; + if (requested_frame_count <= 0) + break; } } @@ -107,7 +112,6 @@ int main(int argc, char **argv) { if (!soundio) panic("out of memory"); - int err; if ((err = soundio_connect(soundio))) panic("error connecting: %s", soundio_strerror(err)); @@ -236,7 +240,6 @@ view `coverage/index.html` in a browser. 0. implement ALSA (Linux) backend, get examples working 0. ALSA: poll instead of callback - 0. fix pulseaudio backend since I broke it 0. pipe record to playback example working with dummy linux, osx, windows 0. pipe record to playback example working with pulseaudio linux 0. implement CoreAudio (OSX) backend, get examples working @@ -259,6 +262,9 @@ view `coverage/index.html` in a browser. 0. add len arguments to APIs that have char * 0. custom allocator support 0. ALSA: support devices that don't support mmap access + 0. Test in an app that needs to synchronize video to test the + latency/synchronization API. + 0. Support PulseAudio proplist properties for main context and streams ## Planned Uses for libsoundio diff --git a/example/sine.c b/example/sine.c index be9a9e7..2093a5e 100644 --- a/example/sine.c +++ b/example/sine.c @@ -38,8 +38,9 @@ static void write_callback(struct SoundIoOutStream *outstream, int requested_fra float seconds_per_frame = 1.0f / float_sample_rate; int err; - int frame_count = requested_frame_count; for (;;) { + int frame_count = requested_frame_count; + struct SoundIoChannelArea *areas; if ((err = soundio_outstream_begin_write(outstream, &areas, &frame_count))) panic("%s", soundio_strerror(err)); @@ -62,6 +63,10 @@ static void write_callback(struct SoundIoOutStream *outstream, int requested_fra if ((err = soundio_outstream_write(outstream, frame_count))) panic("%s", soundio_strerror(err)); + + requested_frame_count -= frame_count; + if (requested_frame_count <= 0) + break; } } diff --git a/src/alsa.cpp b/src/alsa.cpp index 507c196..9765c10 100644 --- a/src/alsa.cpp +++ b/src/alsa.cpp @@ -929,6 +929,15 @@ static void async_direct_callback(snd_async_handler_t *ahandler) { static int outstream_open_alsa(SoundIoPrivate *si, SoundIoOutStreamPrivate *os) { SoundIoOutStream *outstream = &os->pub; + SoundIoDevice *device = outstream->device; + + if (outstream->buffer_duration == 0.0) + outstream->buffer_duration = clamp(device->buffer_duration_min, 1.0, device->buffer_duration_max); + if (outstream->period_duration == 0.0) { + outstream->period_duration = clamp(device->period_duration_min, + outstream->buffer_duration / 2.0, device->period_duration_max); + } + SoundIoOutStreamAlsa *osa = create(); if (!osa) { outstream_destroy_alsa(si, os); @@ -1133,6 +1142,7 @@ static int outstream_pause_alsa(struct SoundIoPrivate *si, struct SoundIoOutStre } static int instream_open_alsa(SoundIoPrivate *si, SoundIoInStreamPrivate *is) { + // TODO default buffer_duration and period_duration soundio_panic("TODO"); } diff --git a/src/dummy.cpp b/src/dummy.cpp index 4225972..02b5f0c 100644 --- a/src/dummy.cpp +++ b/src/dummy.cpp @@ -137,6 +137,15 @@ static void outstream_destroy_dummy(SoundIoPrivate *si, SoundIoOutStreamPrivate static int outstream_open_dummy(SoundIoPrivate *si, SoundIoOutStreamPrivate *os) { SoundIoOutStream *outstream = &os->pub; + SoundIoDevice *device = outstream->device; + + if (outstream->buffer_duration == 0.0) + outstream->buffer_duration = clamp(device->buffer_duration_min, 1.0, device->buffer_duration_max); + if (outstream->period_duration == 0.0) { + outstream->period_duration = clamp(device->period_duration_min, + outstream->buffer_duration / 2.0, device->period_duration_max); + } + SoundIoOutStreamDummy *osd = create(); if (!osd) { outstream_destroy_dummy(si, os); @@ -236,6 +245,15 @@ static void outstream_clear_buffer_dummy(SoundIoPrivate *si, SoundIoOutStreamPri } static int instream_open_dummy(SoundIoPrivate *si, SoundIoInStreamPrivate *is) { + /* TODO + instream->buffer_duration = clamp(device->buffer_duration_min, 1.0, device->buffer_duration_max); + instream->period_duration = -1.0; + if (instream->period_duration == -1.0) { + instream->period_duration = clamp(instream->device->period_duration_min, + instream->buffer_duration / 8.0, instream->device->period_duration_max); + } + */ + soundio_panic("TODO"); } @@ -293,6 +311,16 @@ static int set_all_device_formats(SoundIoDevice *device) { return 0; } +static int set_all_device_channel_layouts(SoundIoDevice *device) { + device->layout_count = soundio_channel_layout_builtin_count(); + device->layouts = allocate(device->layout_count); + if (!device->layouts) + return SoundIoErrorNoMem; + for (int i = 0; i < device->layout_count; i += 1) + device->layouts[i] = *soundio_channel_layout_get_builtin(i); + return 0; +} + int soundio_dummy_init(SoundIoPrivate *si) { SoundIo *soundio = &si->pub; assert(!si->backend_data); @@ -342,17 +370,13 @@ int soundio_dummy_init(SoundIoPrivate *si) { destroy_dummy(si); return SoundIoErrorNoMem; } - device->layout_count = soundio_channel_layout_builtin_count(); - device->layouts = allocate(device->layout_count); - if (!device->layouts) { - soundio_device_unref(device); - destroy_dummy(si); - return SoundIoErrorNoMem; - } - for (int i = 0; i < device->layout_count; i += 1) - device->layouts[i] = *soundio_channel_layout_get_builtin(i); int err; + if ((err = set_all_device_channel_layouts(device))) { + soundio_device_unref(device); + destroy_dummy(si); + return err; + } if ((err = set_all_device_formats(device))) { soundio_device_unref(device); destroy_dummy(si); @@ -395,17 +419,13 @@ int soundio_dummy_init(SoundIoPrivate *si) { return SoundIoErrorNoMem; } - device->layout_count = soundio_channel_layout_builtin_count(); - device->layouts = allocate(device->layout_count); - if (!device->layouts) { + int err; + if ((err = set_all_device_channel_layouts(device))) { soundio_device_unref(device); destroy_dummy(si); - return SoundIoErrorNoMem; + return err; } - for (int i = 0; i < device->layout_count; i += 1) - device->layouts[i] = *soundio_channel_layout_get_builtin(i); - int err; if ((err = set_all_device_formats(device))) { soundio_device_unref(device); destroy_dummy(si); diff --git a/src/pulseaudio.cpp b/src/pulseaudio.cpp index 6cff383..fd609c8 100644 --- a/src/pulseaudio.cpp +++ b/src/pulseaudio.cpp @@ -11,6 +11,7 @@ #include #include +#include #include @@ -135,14 +136,7 @@ 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) { +static SoundIoFormat from_pulseaudio_format(pa_sample_spec sample_spec) { switch (sample_spec.format) { case PA_SAMPLE_U8: return SoundIoFormatU8; case PA_SAMPLE_S16LE: return SoundIoFormatS16LE; @@ -165,13 +159,6 @@ 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) { switch (pos) { case PA_CHANNEL_POSITION_MONO: return SoundIoChannelIdFrontCenter; @@ -214,7 +201,33 @@ static void set_from_pulseaudio_channel_map(pa_channel_map channel_map, SoundIoC } } } -*/ + +static int set_all_device_channel_layouts(SoundIoDevice *device) { + device->layout_count = soundio_channel_layout_builtin_count(); + device->layouts = allocate(device->layout_count); + if (!device->layouts) + return SoundIoErrorNoMem; + for (int i = 0; i < device->layout_count; i += 1) + device->layouts[i] = *soundio_channel_layout_get_builtin(i); + return 0; +} + +static int set_all_device_formats(SoundIoDevice *device) { + device->format_count = 9; + device->formats = allocate(device->format_count); + if (!device->formats) + return SoundIoErrorNoMem; + device->formats[0] = SoundIoFormatU8; + device->formats[1] = SoundIoFormatS16LE; + device->formats[2] = SoundIoFormatS16BE; + device->formats[3] = SoundIoFormatFloat32LE; + device->formats[4] = SoundIoFormatFloat32BE; + device->formats[5] = SoundIoFormatS32LE; + device->formats[6] = SoundIoFormatS32BE; + device->formats[7] = SoundIoFormatS24LE; + device->formats[8] = SoundIoFormatS24BE; + return 0; +} static int perform_operation(SoundIoPrivate *si, pa_operation *op) { SoundIoPulseAudio *sipa = (SoundIoPulseAudio *)si->backend_data; @@ -274,6 +287,7 @@ static void sink_info_callback(pa_context *pulse_context, const pa_sink_info *in SoundIoPrivate *si = (SoundIoPrivate *)userdata; SoundIo *soundio = &si->pub; SoundIoPulseAudio *sipa = (SoundIoPulseAudio *)si->backend_data; + int err; if (eol) { sipa->have_sink_list = true; finish_device_query(si); @@ -288,15 +302,29 @@ static void sink_info_callback(pa_context *pulse_context, const pa_sink_info *in device->description = strdup(info->description); if (!device->name || !device->description) soundio_panic("out of memory"); - // TODO determine the list of supported formats and the min and max sample rate - // 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); - // 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); - // TODO set min, max, current period size + + 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->current_format = from_pulseaudio_format(info->sample_spec); + // PulseAudio performs sample format conversion, so any PulseAudio + // value is valid. + if ((err = set_all_device_formats(device))) + soundio_panic("out of memory"); + + set_from_pulseaudio_channel_map(info->channel_map, &device->current_layout); + // PulseAudio does channel layout remapping, so any channel layout is valid. + if ((err = set_all_device_channel_layouts(device))) + soundio_panic("out of memory"); + + device->buffer_duration_min = 0.10; + device->buffer_duration_max = 4.0; + + // "period" is not a recognized concept in PulseAudio. + device->purpose = SoundIoDevicePurposeOutput; if (sipa->current_devices_info->output_devices.append(device)) @@ -309,6 +337,7 @@ static void source_info_callback(pa_context *pulse_context, const pa_source_info SoundIoPrivate *si = (SoundIoPrivate *)userdata; SoundIo *soundio = &si->pub; SoundIoPulseAudio *sipa = (SoundIoPulseAudio *)si->backend_data; + int err; if (eol) { sipa->have_source_list = true; finish_device_query(si); @@ -323,15 +352,30 @@ static void source_info_callback(pa_context *pulse_context, const pa_source_info device->description = strdup(info->description); if (!device->name || !device->description) soundio_panic("out of memory"); - // TODO determine the list of supported formats and the min and max sample rate - // 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); - // 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); - // TODO set min, max, current period size + + 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->current_format = from_pulseaudio_format(info->sample_spec); + // PulseAudio performs sample format conversion, so any PulseAudio + // value is valid. + if ((err = set_all_device_formats(device))) + soundio_panic("out of memory"); + + set_from_pulseaudio_channel_map(info->channel_map, &device->current_layout); + // PulseAudio does channel layout remapping, so any channel layout is valid. + if ((err = set_all_device_channel_layouts(device))) + soundio_panic("out of memory"); + + device->buffer_duration_min = 0.10; + device->buffer_duration_max = 4.0; + device->buffer_duration_current = -1.0; + + // "period" is not a recognized concept in PulseAudio. + device->purpose = SoundIoDevicePurposeInput; if (sipa->current_devices_info->input_devices.append(device)) @@ -625,8 +669,6 @@ static int outstream_open_pa(SoundIoPrivate *si, SoundIoOutStreamPrivate *os) { sample_spec.channels = outstream->layout.channel_count; pa_channel_map channel_map = to_pulseaudio_channel_map(&outstream->layout); - // TODO handle period_duration - ospa->stream = pa_stream_new(sipa->pulse_context, outstream->name, &sample_spec, &channel_map); if (!ospa->stream) { pa_threaded_mainloop_unlock(sipa->main_loop); @@ -637,16 +679,21 @@ static int outstream_open_pa(SoundIoPrivate *si, SoundIoOutStreamPrivate *os) { pa_stream_set_write_callback(ospa->stream, playback_stream_write_callback, os); pa_stream_set_underflow_callback(ospa->stream, playback_stream_underflow_callback, outstream); - int bytes_per_second = outstream->bytes_per_frame * outstream->sample_rate; - int buffer_length = 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; - ospa->buffer_attr.prebuf = 0; + ospa->buffer_attr.maxlength = UINT32_MAX; + ospa->buffer_attr.tlength = UINT32_MAX; + ospa->buffer_attr.prebuf = UINT32_MAX; ospa->buffer_attr.minreq = UINT32_MAX; ospa->buffer_attr.fragsize = UINT32_MAX; + if (outstream->buffer_duration > 0.0) { + int bytes_per_second = outstream->bytes_per_frame * outstream->sample_rate; + int buffer_length = 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; + } + pa_threaded_mainloop_unlock(sipa->main_loop); return 0; @@ -659,10 +706,11 @@ static int outstream_start_pa(SoundIoPrivate *si, SoundIoOutStreamPrivate *os) { pa_threaded_mainloop_lock(sipa->main_loop); + pa_stream_flags_t flags = (outstream->buffer_duration > 0.0) ? PA_STREAM_ADJUST_LATENCY : PA_STREAM_NOFLAGS; int err = pa_stream_connect_playback(ospa->stream, outstream->device->name, &ospa->buffer_attr, - PA_STREAM_ADJUST_LATENCY, nullptr, nullptr); + flags, nullptr, nullptr); if (err) { pa_threaded_mainloop_unlock(sipa->main_loop); return SoundIoErrorOpeningDevice; @@ -671,8 +719,6 @@ static int outstream_start_pa(SoundIoPrivate *si, SoundIoOutStreamPrivate *os) { while (!ospa->stream_ready) pa_threaded_mainloop_wait(sipa->main_loop); - soundio_outstream_fill_with_silence(outstream); - pa_threaded_mainloop_unlock(sipa->main_loop); return 0; @@ -784,6 +830,7 @@ static void instream_destroy_pa(SoundIoPrivate *si, SoundIoInStreamPrivate *inst static int instream_open_pa(SoundIoPrivate *si, SoundIoInStreamPrivate *is) { SoundIoInStream *instream = &is->pub; + SoundIoInStreamPulseAudio *ispa = create(); if (!ispa) { instream_destroy_pa(si, is); @@ -803,8 +850,6 @@ static int instream_open_pa(SoundIoPrivate *si, SoundIoInStreamPrivate *is) { pa_channel_map channel_map = to_pulseaudio_channel_map(&instream->layout); - // TODO handle period_duration - ispa->stream = pa_stream_new(sipa->pulse_context, instream->name, &sample_spec, &channel_map); if (!ispa->stream) { pa_threaded_mainloop_unlock(sipa->main_loop); @@ -817,15 +862,22 @@ static int instream_open_pa(SoundIoPrivate *si, SoundIoInStreamPrivate *is) { pa_stream_set_state_callback(stream, recording_stream_state_callback, is); pa_stream_set_read_callback(stream, recording_stream_read_callback, is); - int bytes_per_second = instream->bytes_per_frame * instream->sample_rate; - int buffer_length = 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; - ispa->buffer_attr.prebuf = 0; + ispa->buffer_attr.prebuf = UINT32_MAX; ispa->buffer_attr.minreq = UINT32_MAX; - ispa->buffer_attr.fragsize = buffer_length; + ispa->buffer_attr.fragsize = UINT32_MAX; + + if (instream->period_duration > 0.0) { + int bytes_per_second = instream->bytes_per_frame * instream->sample_rate; + int buffer_length = instream->bytes_per_frame * + ceil(instream->period_duration * bytes_per_second / (double)instream->bytes_per_frame); + ispa->buffer_attr.maxlength = UINT32_MAX; + ispa->buffer_attr.tlength = UINT32_MAX; + ispa->buffer_attr.prebuf = UINT32_MAX; + ispa->buffer_attr.minreq = UINT32_MAX; + ispa->buffer_attr.fragsize = buffer_length; + } pa_threaded_mainloop_unlock(sipa->main_loop); @@ -838,9 +890,11 @@ static int instream_start_pa(SoundIoPrivate *si, SoundIoInStreamPrivate *is) { SoundIoPulseAudio *sipa = (SoundIoPulseAudio *)si->backend_data; pa_threaded_mainloop_lock(sipa->main_loop); + pa_stream_flags_t flags = (instream->period_duration > 0.0) ? PA_STREAM_ADJUST_LATENCY : PA_STREAM_NOFLAGS; + int err = pa_stream_connect_record(ispa->stream, instream->device->name, - &ispa->buffer_attr, PA_STREAM_ADJUST_LATENCY); + &ispa->buffer_attr, flags); if (err) { pa_threaded_mainloop_unlock(sipa->main_loop); return SoundIoErrorOpeningDevice; @@ -905,6 +959,8 @@ static int instream_pause_pa(SoundIoPrivate *si, SoundIoInStreamPrivate *is, boo } int soundio_pulseaudio_init(SoundIoPrivate *si) { + SoundIo *soundio = &si->pub; + assert(!si->backend_data); SoundIoPulseAudio *sipa = create(); if (!sipa) { @@ -932,12 +988,7 @@ int soundio_pulseaudio_init(SoundIoPrivate *si) { return SoundIoErrorNoMem; } - // TODO let the API specify this - pa_proplist_sets(sipa->props, PA_PROP_APPLICATION_NAME, "libsoundio"); - pa_proplist_sets(sipa->props, PA_PROP_APPLICATION_VERSION, SOUNDIO_VERSION_STRING); - pa_proplist_sets(sipa->props, PA_PROP_APPLICATION_ID, "me.andrewkelley.libsoundio"); - - sipa->pulse_context = pa_context_new_with_proplist(main_loop_api, "SoundIo", sipa->props); + sipa->pulse_context = pa_context_new_with_proplist(main_loop_api, soundio->app_name, sipa->props); if (!sipa->pulse_context) { destroy_pa(si); return SoundIoErrorNoMem; diff --git a/src/soundio.cpp b/src/soundio.cpp index 36d3476..f6427dd 100644 --- a/src/soundio.cpp +++ b/src/soundio.cpp @@ -134,6 +134,7 @@ struct SoundIo * soundio_create(void) { SoundIo *soundio = &si->pub; soundio->on_devices_change = default_on_devices_change; soundio->on_events_signal = default_on_events_signal; + soundio->app_name = "SoundIo"; return soundio; } @@ -387,8 +388,6 @@ struct SoundIoOutStream *soundio_outstream_create(struct SoundIoDevice *device) 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_duration = -1.0; outstream->name = "SoundIo"; return outstream; @@ -398,11 +397,6 @@ int soundio_outstream_open(struct SoundIoOutStream *outstream) { if (outstream->format <= SoundIoFormatInvalid) return SoundIoErrorInvalid; - if (outstream->period_duration == -1.0) { - outstream->period_duration = clamp(outstream->device->period_duration_min, - outstream->buffer_duration / 2.0, outstream->device->period_duration_max); - } - SoundIoOutStreamPrivate *os = (SoundIoOutStreamPrivate *)outstream; outstream->bytes_per_frame = soundio_get_bytes_per_frame(outstream->format, outstream->layout.channel_count); outstream->bytes_per_sample = soundio_get_bytes_per_sample(outstream->format); @@ -456,8 +450,6 @@ struct SoundIoInStream *soundio_instream_create(struct SoundIoDevice *device) { 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_duration = -1.0; instream->name = "SoundIo"; return instream; @@ -467,11 +459,6 @@ int soundio_instream_open(struct SoundIoInStream *instream) { if (instream->format <= SoundIoFormatInvalid) return SoundIoErrorInvalid; - if (instream->period_duration == -1.0) { - instream->period_duration = clamp(instream->device->period_duration_min, - instream->buffer_duration / 8.0, instream->device->period_duration_max); - } - instream->bytes_per_frame = soundio_get_bytes_per_frame(instream->format, instream->layout.channel_count); instream->bytes_per_sample = soundio_get_bytes_per_sample(instream->format); SoundIo *soundio = instream->device->soundio; diff --git a/src/soundio.h b/src/soundio.h index c836cfc..9053c73 100644 --- a/src/soundio.h +++ b/src/soundio.h @@ -205,6 +205,9 @@ struct SoundIo { // to call any soundio functions. You may use this to signal a condition // variable to wake up. Called when soundio_wait_events would be woken up. void (*on_events_signal)(struct SoundIo *); + + // Optional: Application name. Used by PulseAudio. Defaults to "SoundIo". + const char *app_name; }; // The size of this struct is not part of the API or ABI. @@ -254,13 +257,15 @@ struct SoundIoDevice { int sample_rate_max; int sample_rate_current; - // Buffer duration in seconds. + // Buffer duration in seconds. If any values are unknown, they are set to + // 0.0. These values are unknown for PulseAudio. double buffer_duration_min; double buffer_duration_max; double buffer_duration_current; // Period duration in seconds. After this much time passes, write_callback - // is called. + // is called. If values are unknown, they are set to 0.0. These values are + // unknown for PulseAudio. double period_duration_min; double period_duration_max; double period_duration_current; @@ -305,6 +310,10 @@ struct SoundIoOutStream { // 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). + // If the device has unknown buffer duration min and max values, you may + // still set this. If you set this and the backend is PulseAudio, it + // sets `PA_STREAM_ADJUST_LATENCY` and is the value used for `maxlength` + // and `tlength`. double buffer_duration; // `period_duration` is the latency; how much time it takes @@ -312,6 +321,9 @@ struct SoundIoOutStream { // After you call `soundio_outstream_open` this value is replaced with the // actual period duration, as near to this value as possible. // Defaults to `buffer_duration / 2` (and then clamped into range). + // If the device has unknown period duration min and max values, you may + // still set this. If you set this and the backend is PulseAudio, it + // sets `PA_STREAM_ADJUST_LATENCY` and is the value used for `fragsize`. double period_duration; // Defaults to NULL. Put whatever you want here. @@ -516,8 +528,10 @@ int soundio_outstream_free_count(struct SoundIoOutStream *outstream); // * `areas` - (out) The memory addresses you can write data to. It is OK to // modify the pointers if that helps you iterate. // * `frame_count` - (in/out) Provide the number of frames you want to write. -// Returned will be the number of frames you actually can write. If this -// number is greater than zero, you must call this function again. +// Returned will be the number of frames you actually can write. +// It is your responsibility to call this function no more and no fewer than the +// correct number of times as determined by `requested_frame_count` from +// `write_callback`. See sine.c for an example. int soundio_outstream_begin_write(struct SoundIoOutStream *outstream, struct SoundIoChannelArea **areas, int *frame_count);