From 44569708a0688090ef76c1a6745262342f2ae9bc Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 29 Jul 2015 20:55:28 -0700 Subject: [PATCH] Delete the concept of prebuffering --- README.md | 9 +---- example/microphone.c | 6 +-- src/alsa.cpp | 20 ++++++---- src/dummy.cpp | 90 ++++++++++++++++---------------------------- src/dummy.hpp | 4 -- src/jack.cpp | 3 -- src/pulseaudio.cpp | 12 +----- src/soundio.cpp | 2 - src/soundio.h | 25 ++++++------ 9 files changed, 64 insertions(+), 107 deletions(-) diff --git a/README.md b/README.md index a652c6c..da59e14 100644 --- a/README.md +++ b/README.md @@ -20,10 +20,10 @@ behavior on every platform. ## Features and Limitations * Supported backends: + - [JACK](http://jackaudio.org/) - [PulseAudio](http://www.freedesktop.org/wiki/Software/PulseAudio/) - [ALSA](http://www.alsa-project.org/) - Dummy (silence) - - [JACK](http://jackaudio.org/) - (planned) [CoreAudio](https://developer.apple.com/library/mac/documentation/MusicAudio/Conceptual/CoreAudioOverview/Introduction/Introduction.html) - (planned) [WASAPI](https://msdn.microsoft.com/en-us/library/windows/desktop/dd371455%28v=vs.85%29.aspx) - (planned) [ASIO](http://www.asio4all.com/) @@ -245,19 +245,15 @@ view `coverage/index.html` in a browser. 0. implement CoreAudio (OSX) backend, get examples working 0. implement WASAPI (Windows) backend, get examples working 0. implement ASIO (Windows) backend, get examples working - 0. JACK: implement prebuffering ...or delete prebuffering as a concept 0. Avoid calling `soundio_panic` in PulseAudio. - 0. Figure out a way to test prebuf. I suspect prebuf not working for ALSA - which is why we have to pre-fill the ring buffer with silence for - the microphone example. 0. PulseAudio: when prebuf gets set to 0 need to pass `PA_STREAM_START_CORKED`. + 0. clear buffer maybe could take an argument to say how many frames to not clear 0. In ALSA do we need to wake up the poll when destroying the in or out stream? 0. Verify that JACK xrun callback context is the same as process callback. If not, might need to hav xrun callback set a flag and have process callback call the underflow callback. 0. Detect PulseAudio server going offline and emit `on_backend_disconnect`. 0. Instead fo open(), start(), pause(), open() starts it and it starts paused. - 0. Create a test for clearing the playback buffer and prebuffering. 0. Create a test for pausing and resuming input and output streams. 0. Create a test for the latency / synchronization API. - Input is an audio file and some events indexed at particular frame - when @@ -290,7 +286,6 @@ view `coverage/index.html` in a browser. 0. make rtprio warning a callback and have existing behavior be the default callback 0. write detailed docs on buffer underflows explaining when they occur, what state changes are related to them, and how to recover from them. - 0. API to trigger playback even if prebuf condition isn't met yet. 0. Consider testing on FreeBSD ## Planned Uses for libsoundio diff --git a/example/microphone.c b/example/microphone.c index be4da6a..77a8740 100644 --- a/example/microphone.c +++ b/example/microphone.c @@ -61,7 +61,6 @@ static void read_callback(struct SoundIoInStream *instream, int available_frame_ int write_count = min_int(available_frame_count, free_count); int frames_left = write_count; - for (;;) { int frame_count = frames_left; @@ -148,7 +147,7 @@ static void underflow_callback(struct SoundIoOutStream *outstream) { } static int usage(char *exe) { - fprintf(stderr, "Usage: %s [--dummy] [--alsa] [--pulseaudio] [--in-device name] [--out-device name]\n", exe); + fprintf(stderr, "Usage: %s [--dummy] [--alsa] [--pulseaudio] [--jack] [--in-device name] [--out-device name]\n", exe); return 1; } @@ -165,6 +164,8 @@ int main(int argc, char **argv) { backend = SoundIoBackendAlsa; } else if (strcmp("--pulseaudio", arg) == 0) { backend = SoundIoBackendPulseAudio; + } else if (strcmp("--jack", arg) == 0) { + backend = SoundIoBackendJack; } else if (strcmp("--in-device", arg) == 0) { if (++i >= argc) { return usage(exe); @@ -290,7 +291,6 @@ int main(int argc, char **argv) { outstream->period_duration = 0.1; outstream->write_callback = write_callback; outstream->underflow_callback = underflow_callback; - outstream->prebuf_duration = 0.0; if ((err = soundio_outstream_open(outstream))) panic("unable to open output stream: %s", soundio_strerror(err)); diff --git a/src/alsa.cpp b/src/alsa.cpp index d3a43b9..4783bae 100644 --- a/src/alsa.cpp +++ b/src/alsa.cpp @@ -1086,8 +1086,6 @@ static int outstream_open_alsa(SoundIoPrivate *si, SoundIoOutStreamPrivate *os) outstream->period_duration = clamp(device->period_duration_min, outstream->buffer_duration / 2.0, device->period_duration_max); } - if (outstream->prebuf_duration == -1.0) - outstream->prebuf_duration = outstream->buffer_duration; int ch_count = outstream->layout.channel_count; @@ -1196,8 +1194,7 @@ static int outstream_open_alsa(SoundIoPrivate *si, SoundIoOutStreamPrivate *os) return SoundIoErrorOpeningDevice; } - snd_pcm_uframes_t prebuf_frames = ceil(outstream->prebuf_duration * (double)outstream->sample_rate); - if ((err = snd_pcm_sw_params_set_start_threshold(osa->handle, swparams, prebuf_frames)) < 0) { + if ((err = snd_pcm_sw_params_set_start_threshold(osa->handle, swparams, 0)) < 0) { outstream_destroy_alsa(si, os); return SoundIoErrorOpeningDevice; } @@ -1243,16 +1240,23 @@ static int outstream_open_alsa(SoundIoPrivate *si, SoundIoOutStreamPrivate *os) } static int outstream_start_alsa(SoundIoPrivate *si, SoundIoOutStreamPrivate *os) { + SoundIoOutStream *outstream = &os->pub; SoundIoOutStreamAlsa *osa = &os->backend_data.alsa; assert(!osa->thread); - osa->thread_exit_flag.test_and_set(); + // Give the API user a chance to fill the buffer before playback commences. + snd_pcm_sframes_t avail = snd_pcm_avail_update(osa->handle); int err; - if ((err = soundio_os_thread_create(outstream_thread_run, os, true, &osa->thread))) { - outstream_destroy_alsa(si, os); - return err; + if (avail < 0) { + if ((err = xrun_recovery(os, avail)) < 0) + return SoundIoErrorStreaming; } + outstream->write_callback(outstream, avail); + + osa->thread_exit_flag.test_and_set(); + if ((err = soundio_os_thread_create(outstream_thread_run, os, true, &osa->thread))) + return err; return 0; } diff --git a/src/dummy.cpp b/src/dummy.cpp index d6474ec..ae43ef5 100644 --- a/src/dummy.cpp +++ b/src/dummy.cpp @@ -17,15 +17,12 @@ static void playback_thread_run(void *arg) { SoundIoOutStream *outstream = &os->pub; SoundIoOutStreamDummy *osd = &os->backend_data.dummy; - double start_time = soundio_os_get_time(); - osd->playback_start_time = start_time; - osd->frames_consumed = 0; int fill_bytes = soundio_ring_buffer_fill_count(&osd->ring_buffer); int free_bytes = soundio_ring_buffer_capacity(&osd->ring_buffer) - fill_bytes; - int fill_frames = fill_bytes / outstream->bytes_per_frame; int free_frames = free_bytes / outstream->bytes_per_frame; - osd->prebuf_frames_left = osd->prebuf_frame_count - fill_frames; outstream->write_callback(outstream, free_frames); + double start_time = soundio_os_get_time(); + long frames_consumed = 0; while (osd->abort_flag.test_and_set()) { double now = soundio_os_get_time(); @@ -39,22 +36,20 @@ static void playback_thread_run(void *arg) { int free_bytes = soundio_ring_buffer_capacity(&osd->ring_buffer) - fill_bytes; int fill_frames = fill_bytes / outstream->bytes_per_frame; int free_frames = free_bytes / outstream->bytes_per_frame; - if (osd->prebuf_frames_left > 0) { - outstream->write_callback(outstream, free_frames); - continue; - } - double total_time = soundio_os_get_time() - osd->playback_start_time; + double total_time = soundio_os_get_time() - start_time; long total_frames = total_time * outstream->sample_rate; - int frames_to_kill = total_frames - osd->frames_consumed; + int frames_to_kill = total_frames - frames_consumed; int read_count = min(frames_to_kill, fill_frames); int byte_count = read_count * outstream->bytes_per_frame; soundio_ring_buffer_advance_read_ptr(&osd->ring_buffer, byte_count); - osd->frames_consumed += read_count; + frames_consumed += read_count; - if (frames_to_kill > read_count) { - osd->prebuf_frames_left = osd->prebuf_frame_count; + if (frames_to_kill > fill_frames) { outstream->underflow_callback(outstream); + outstream->write_callback(outstream, free_frames); + frames_consumed = 0; + start_time = soundio_os_get_time(); } else if (free_frames > 0) { outstream->write_callback(outstream, free_frames); } @@ -70,26 +65,33 @@ static void capture_thread_run(void *arg) { double start_time = soundio_os_get_time(); while (isd->abort_flag.test_and_set()) { double now = soundio_os_get_time(); - double total_time = now - start_time; - long total_frames = total_time * instream->sample_rate; - int frames_to_kill = total_frames - frames_consumed; - int free_bytes = soundio_ring_buffer_free_count(&isd->ring_buffer); - int free_frames = free_bytes / instream->bytes_per_frame; - int write_count = min(frames_to_kill, free_frames); - int frames_left = max(frames_to_kill - write_count, 0); - int byte_count = write_count * instream->bytes_per_frame; - soundio_ring_buffer_advance_write_ptr(&isd->ring_buffer, byte_count); - frames_consumed += write_count; - - isd->hole_size += frames_left; - if (write_count > 0) - instream->read_callback(instream, write_count); - now = soundio_os_get_time(); double time_passed = now - start_time; double next_period = start_time + ceil(time_passed / instream->period_duration) * instream->period_duration; double relative_time = next_period - now; soundio_os_cond_timed_wait(isd->cond, nullptr, relative_time); + + int fill_bytes = soundio_ring_buffer_fill_count(&isd->ring_buffer); + int free_bytes = soundio_ring_buffer_capacity(&isd->ring_buffer) - fill_bytes; + int fill_frames = fill_bytes / instream->bytes_per_frame; + int free_frames = free_bytes / instream->bytes_per_frame; + + double total_time = soundio_os_get_time() - start_time; + long total_frames = total_time * instream->sample_rate; + int frames_to_kill = total_frames - frames_consumed; + int write_count = min(frames_to_kill, free_frames); + int byte_count = write_count * instream->bytes_per_frame; + soundio_ring_buffer_advance_write_ptr(&isd->ring_buffer, byte_count); + frames_consumed += write_count; + + if (frames_to_kill > free_frames) { + // TODO overflow callback + frames_consumed = 0; + start_time = soundio_os_get_time(); + } + if (fill_frames > 0) { + instream->read_callback(instream, fill_frames); + } } } @@ -160,11 +162,6 @@ static int outstream_open_dummy(SoundIoPrivate *si, SoundIoOutStreamPrivate *os) osd->buffer_frame_count = actual_capacity / outstream->bytes_per_frame; outstream->buffer_duration = osd->buffer_frame_count / (double) outstream->sample_rate; - if (outstream->prebuf_duration == -1.0) - outstream->prebuf_duration = outstream->buffer_duration; - outstream->prebuf_duration = min(outstream->prebuf_duration, outstream->buffer_duration); - osd->prebuf_frame_count = ceil(outstream->prebuf_duration * (double) outstream->sample_rate); - osd->cond = soundio_os_cond_create(); if (!osd->cond) { outstream_destroy_dummy(si, os); @@ -231,20 +228,12 @@ static int outstream_end_write_dummy(SoundIoPrivate *si, SoundIoOutStreamPrivate SoundIoOutStream *outstream = &os->pub; int byte_count = frame_count * outstream->bytes_per_frame; soundio_ring_buffer_advance_write_ptr(&osd->ring_buffer, byte_count); - if (osd->prebuf_frames_left > 0) { - osd->prebuf_frames_left -= frame_count; - if (osd->prebuf_frames_left <= 0) { - osd->playback_start_time = soundio_os_get_time(); - osd->frames_consumed = 0; - } - } return 0; } static int outstream_clear_buffer_dummy(SoundIoPrivate *si, SoundIoOutStreamPrivate *os) { SoundIoOutStreamDummy *osd = &os->backend_data.dummy; soundio_ring_buffer_clear(&osd->ring_buffer); - osd->prebuf_frames_left = osd->prebuf_frame_count; return 0; } @@ -329,13 +318,6 @@ static int instream_begin_read_dummy(SoundIoPrivate *si, assert(*frame_count >= 0); assert(*frame_count <= isd->buffer_frame_count); - if (isd->hole_size > 0) { - *out_areas = nullptr; - isd->read_frame_count = min(isd->hole_size, *frame_count); - *frame_count = isd->read_frame_count; - return 0; - } - int fill_byte_count = soundio_ring_buffer_fill_count(&isd->ring_buffer); int fill_frame_count = fill_byte_count / instream->bytes_per_frame; isd->read_frame_count = min(*frame_count, fill_frame_count); @@ -357,14 +339,8 @@ static int instream_begin_read_dummy(SoundIoPrivate *si, static int instream_end_read_dummy(SoundIoPrivate *si, SoundIoInStreamPrivate *is) { SoundIoInStreamDummy *isd = &is->backend_data.dummy; SoundIoInStream *instream = &is->pub; - - if (isd->hole_size > 0) { - isd->hole_size -= isd->read_frame_count; - return 0; - } - int byte_count = isd->read_frame_count * instream->bytes_per_frame; - soundio_ring_buffer_advance_write_ptr(&isd->ring_buffer, byte_count); + soundio_ring_buffer_advance_read_ptr(&isd->ring_buffer, byte_count); return 0; } @@ -493,7 +469,7 @@ int soundio_dummy_init(SoundIoPrivate *si) { device->ref_count = 1; device->soundio = soundio; device->name = strdup("dummy-in"); - device->description = strdup("Dummy input device"); + device->description = strdup("Dummy Input Device"); if (!device->name || !device->description) { soundio_device_unref(device); destroy_dummy(si); diff --git a/src/dummy.hpp b/src/dummy.hpp index c1f2aae..1a1f899 100644 --- a/src/dummy.hpp +++ b/src/dummy.hpp @@ -31,9 +31,6 @@ struct SoundIoOutStreamDummy { atomic_flag abort_flag; int buffer_frame_count; struct SoundIoRingBuffer ring_buffer; - int prebuf_frame_count; - int prebuf_frames_left; - long frames_consumed; double playback_start_time; SoundIoChannelArea areas[SOUNDIO_MAX_CHANNELS]; }; @@ -45,7 +42,6 @@ struct SoundIoInStreamDummy { int read_frame_count; int buffer_frame_count; struct SoundIoRingBuffer ring_buffer; - int hole_size; SoundIoChannelArea areas[SOUNDIO_MAX_CHANNELS]; }; diff --git a/src/jack.cpp b/src/jack.cpp index 50760b7..a368383 100644 --- a/src/jack.cpp +++ b/src/jack.cpp @@ -428,7 +428,6 @@ static int outstream_open_jack(struct SoundIoPrivate *si, struct SoundIoOutStrea outstream->buffer_duration = 0.0; outstream->period_duration = device->period_duration_current; - outstream->prebuf_duration = 0.0; osj->period_size = sij->period_size; jack_status_t status; @@ -573,8 +572,6 @@ static int outstream_end_write_jack(struct SoundIoPrivate *si, struct SoundIoOut } static int outstream_clear_buffer_jack(struct SoundIoPrivate *si, struct SoundIoOutStreamPrivate *os) { - // JACK does not support `prebuf` which is the same as a `prebuf` value of 0, - // which means that clearing the buffer is always successful and does nothing. return 0; } diff --git a/src/pulseaudio.cpp b/src/pulseaudio.cpp index 7dca8fb..e8ca94c 100644 --- a/src/pulseaudio.cpp +++ b/src/pulseaudio.cpp @@ -637,7 +637,7 @@ static int outstream_open_pa(SoundIoPrivate *si, SoundIoOutStreamPrivate *os) { ospa->buffer_attr.maxlength = UINT32_MAX; ospa->buffer_attr.tlength = UINT32_MAX; - ospa->buffer_attr.prebuf = UINT32_MAX; + ospa->buffer_attr.prebuf = 0; ospa->buffer_attr.minreq = UINT32_MAX; ospa->buffer_attr.fragsize = UINT32_MAX; @@ -649,12 +649,6 @@ static int outstream_open_pa(SoundIoPrivate *si, SoundIoOutStreamPrivate *os) { ospa->buffer_attr.maxlength = buffer_length; ospa->buffer_attr.tlength = buffer_length; } - if (outstream->prebuf_duration >= 0.0) { - int prebuf_length = outstream->bytes_per_frame * - ceil(outstream->prebuf_duration * bytes_per_second / (double)outstream->bytes_per_frame); - - ospa->buffer_attr.prebuf = prebuf_length; - } pa_threaded_mainloop_unlock(sipa->main_loop); @@ -684,8 +678,6 @@ static int outstream_start_pa(SoundIoPrivate *si, SoundIoOutStreamPrivate *os) { const pa_buffer_attr *attr = pa_stream_get_buffer_attr(ospa->stream); outstream->buffer_duration = (attr->maxlength / (double)outstream->bytes_per_frame) / (double)outstream->sample_rate; - outstream->prebuf_duration = (attr->prebuf / - (double)outstream->bytes_per_frame) / (double)outstream->sample_rate; pa_threaded_mainloop_unlock(sipa->main_loop); @@ -842,7 +834,7 @@ static int instream_open_pa(SoundIoPrivate *si, SoundIoInStreamPrivate *is) { ispa->buffer_attr.maxlength = UINT32_MAX; ispa->buffer_attr.tlength = UINT32_MAX; - ispa->buffer_attr.prebuf = UINT32_MAX; + ispa->buffer_attr.prebuf = 0; ispa->buffer_attr.minreq = UINT32_MAX; ispa->buffer_attr.fragsize = UINT32_MAX; diff --git a/src/soundio.cpp b/src/soundio.cpp index f5674f0..6f48549 100644 --- a/src/soundio.cpp +++ b/src/soundio.cpp @@ -359,8 +359,6 @@ struct SoundIoOutStream *soundio_outstream_create(struct SoundIoDevice *device) outstream->error_callback = default_outstream_error_callback; outstream->underflow_callback = default_underflow_callback; - outstream->prebuf_duration = -1.0; - return outstream; } diff --git a/src/soundio.h b/src/soundio.h index 3c3688a..b4bc500 100644 --- a/src/soundio.h +++ b/src/soundio.h @@ -357,14 +357,6 @@ struct SoundIoOutStream { // sets `PA_STREAM_ADJUST_LATENCY` and is the value used for `fragsize`. double period_duration; - // How many seconds need to be in the buffer before playback will commence. - // If a buffer underflow occurs, this prebuffering will be again enabled. - // This value defaults to being the same as `buffer_duration`. - // After you call `soundio_outstream_open` this value is replaced with the - // actual `prebuf_duration`, as near to this value as possible. - // JACK does not support prebuffering; `prebuf_duration` is effectively 0. - double prebuf_duration; - // Defaults to NULL. Put whatever you want here. void *userdata; // In this callback, you call `soundio_outstream_begin_write` and @@ -619,12 +611,15 @@ bool soundio_device_supports_layout(struct SoundIoDevice *device, // Allocates memory and sets defaults. Next you should fill out the struct fields // and then call `soundio_outstream_open`. struct SoundIoOutStream *soundio_outstream_create(struct SoundIoDevice *device); - -int soundio_outstream_open(struct SoundIoOutStream *outstream); - // You may not call this function from the `write_callback` thread context. void soundio_outstream_destroy(struct SoundIoOutStream *outstream); +// After you call this function, `buffer_duration` and `period_duration` are +// set to the correct values, if available. +// The next thing to do is call `soundio_instream_start`. +int soundio_outstream_open(struct SoundIoOutStream *outstream); + +// After you call this function, `write_callback` will be called. int soundio_outstream_start(struct SoundIoOutStream *outstream); // Call this function when you are ready to begin writing to the device buffer. @@ -646,7 +641,7 @@ int soundio_outstream_begin_write(struct SoundIoOutStream *outstream, // You must call this function only from the `write_callback` thread context. int soundio_outstream_end_write(struct SoundIoOutStream *outstream, int frame_count); -// Clears the output stream buffer and the stream goes into prebuffering mode. +// Clears the output stream buffer. // You must call this function only from the `write_callback` thread context. int soundio_outstream_clear_buffer(struct SoundIoOutStream *outstream); @@ -663,11 +658,15 @@ int soundio_outstream_pause(struct SoundIoOutStream *outstream, bool pause); // Allocates memory and sets defaults. Next you should fill out the struct fields // and then call `soundio_instream_open`. struct SoundIoInStream *soundio_instream_create(struct SoundIoDevice *device); -// You must not call this function from `read_callback`. +// You may not call this function from `read_callback`. void soundio_instream_destroy(struct SoundIoInStream *instream); +// After you call this function, `buffer_duration` and `period_duration` are +// set to the correct values, if available. +// The next thing to do is call `soundio_instream_start`. int soundio_instream_open(struct SoundIoInStream *instream); +// After you call this function, `read_callback` will be called. int soundio_instream_start(struct SoundIoInStream *instream); // Call this function when you are ready to begin reading from the device