From 511fcafc3b31bdb7205f626eeecd5dfa23ba4e34 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 23 Jul 2015 16:49:44 -0700 Subject: [PATCH] dummy: implement prebuffering --- README.md | 5 +-- example/sine.c | 6 ++++ src/alsa.cpp | 5 ++- src/dummy.cpp | 80 +++++++++++++++++++++++++++++----------------- src/pulseaudio.cpp | 14 +++++--- src/soundio.cpp | 2 ++ src/soundio.h | 5 +++ 7 files changed, 78 insertions(+), 39 deletions(-) diff --git a/README.md b/README.md index d001934..0953864 100644 --- a/README.md +++ b/README.md @@ -231,7 +231,6 @@ view `coverage/index.html` in a browser. ## Roadmap - 0. pipe record to playback example working with dummy osx, windows 0. pipe record to playback example working with ALSA linux 0. expose prebuf 0. why does pulseaudio microphone use up all the CPU? @@ -248,9 +247,7 @@ view `coverage/index.html` in a browser. 0. Integrate into libgroove and test with Groove Basin 0. look at microphone example and determine if fewer memcpys can be done with the audio data - - pulseaudio has peek() drop() which sucks, but what if libsoundio lets you - specify how much to peek() and if you don't peek all of it, save the - unused to a buffer for you. + - test that sending the frame count to begin read works with PulseAudio 0. add len arguments to APIs that have char * 0. Test in an app that needs to synchronize video to test the latency/synchronization API. diff --git a/example/sine.c b/example/sine.c index b477bac..3b12ab6 100644 --- a/example/sine.c +++ b/example/sine.c @@ -70,6 +70,11 @@ static void write_callback(struct SoundIoOutStream *outstream, int requested_fra } } +static void underflow_callback(struct SoundIoOutStream *outstream) { + static int count = 0; + fprintf(stderr, "underflow %d\n", count++); +} + int main(int argc, char **argv) { char *exe = argv[0]; enum SoundIoBackend backend = SoundIoBackendNone; @@ -109,6 +114,7 @@ int main(int argc, char **argv) { struct SoundIoOutStream *outstream = soundio_outstream_create(device); outstream->format = SoundIoFormatFloat32NE; outstream->write_callback = write_callback; + outstream->underflow_callback = underflow_callback; if ((err = soundio_outstream_open(outstream))) panic("unable to open device: %s", soundio_strerror(err)); diff --git a/src/alsa.cpp b/src/alsa.cpp index 3468b3b..d9bdbf8 100644 --- a/src/alsa.cpp +++ b/src/alsa.cpp @@ -1029,6 +1029,8 @@ 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; SoundIoOutStreamAlsa *osa = create(); if (!osa) { @@ -1144,7 +1146,8 @@ static int outstream_open_alsa(SoundIoPrivate *si, SoundIoOutStreamPrivate *os) return SoundIoErrorOpeningDevice; } - if ((err = snd_pcm_sw_params_set_start_threshold(osa->handle, swparams, buffer_size_frames)) < 0) { + 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) { outstream_destroy_alsa(si, os); return SoundIoErrorOpeningDevice; } diff --git a/src/dummy.cpp b/src/dummy.cpp index 1f96e39..1b8a64b 100644 --- a/src/dummy.cpp +++ b/src/dummy.cpp @@ -21,6 +21,10 @@ 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]; }; @@ -46,35 +50,47 @@ static void playback_thread_run(void *arg) { SoundIoOutStream *outstream = &os->pub; SoundIoOutStreamDummy *osd = (SoundIoOutStreamDummy *)os->backend_data; - long frames_consumed = 0; 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); + while (osd->abort_flag.test_and_set()) { double now = soundio_os_get_time(); - double total_time = now - start_time; - long total_frames = total_time * outstream->sample_rate; - int frames_to_kill = total_frames - frames_consumed; - int fill_count = soundio_ring_buffer_fill_count(&osd->ring_buffer); - int frames_in_buffer = fill_count / outstream->bytes_per_frame; - int read_count = min(frames_to_kill, frames_in_buffer); - int frames_left = frames_to_kill - read_count; - int byte_count = read_count * outstream->bytes_per_frame; - soundio_ring_buffer_advance_read_ptr(&osd->ring_buffer, byte_count); - frames_consumed += read_count; - - if (frames_left > 0) { - outstream->underflow_callback(outstream); - // TODO delete this and simulate prebuf - soundio_ring_buffer_advance_write_ptr(&osd->ring_buffer, - soundio_ring_buffer_free_count(&osd->ring_buffer)); - } else if (read_count > 0) { - outstream->write_callback(outstream, read_count); - } - now = soundio_os_get_time(); double time_passed = now - start_time; double next_period = start_time + ceil(time_passed / outstream->period_duration) * outstream->period_duration; double relative_time = next_period - now; soundio_os_cond_timed_wait(osd->cond, nullptr, relative_time); + + 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; + if (osd->prebuf_frames_left > 0) { + outstream->write_callback(outstream, free_frames); + continue; + } + + double total_time = soundio_os_get_time() - osd->playback_start_time; + long total_frames = total_time * outstream->sample_rate; + int frames_to_kill = total_frames - osd->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; + + if (frames_to_kill > read_count) { + osd->prebuf_frames_left = osd->prebuf_frame_count; + outstream->underflow_callback(outstream); + } else if (free_frames > 0) { + outstream->write_callback(outstream, free_frames); + } } } @@ -93,13 +109,12 @@ static void capture_thread_run(void *arg) { 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 = frames_to_kill - write_count; + 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; - if (frames_left > 0) - isd->hole_size += frames_left; + isd->hole_size += frames_left; if (write_count > 0) instream->read_callback(instream, write_count); now = soundio_os_get_time(); @@ -194,6 +209,11 @@ 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); @@ -225,11 +245,6 @@ static int outstream_pause_dummy(struct SoundIoPrivate *si, struct SoundIoOutStr } static int outstream_start_dummy(SoundIoPrivate *si, SoundIoOutStreamPrivate *os) { - SoundIoOutStreamDummy *osd = (SoundIoOutStreamDummy *)os->backend_data; - - // TODO delete this and simulate prebuf - soundio_ring_buffer_advance_write_ptr(&osd->ring_buffer, soundio_ring_buffer_free_count(&osd->ring_buffer)); - return outstream_pause_dummy(si, os, false); } @@ -265,6 +280,13 @@ 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; } diff --git a/src/pulseaudio.cpp b/src/pulseaudio.cpp index 5009300..f2ec933 100644 --- a/src/pulseaudio.cpp +++ b/src/pulseaudio.cpp @@ -696,14 +696,20 @@ static int outstream_open_pa(SoundIoPrivate *si, SoundIoOutStreamPrivate *os) { ospa->buffer_attr.minreq = UINT32_MAX; ospa->buffer_attr.fragsize = UINT32_MAX; + int bytes_per_second = outstream->bytes_per_frame * outstream->sample_rate; 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; } + 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); @@ -733,6 +739,8 @@ 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); @@ -903,10 +911,6 @@ static int instream_open_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->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; } diff --git a/src/soundio.cpp b/src/soundio.cpp index 45ba252..cea6e34 100644 --- a/src/soundio.cpp +++ b/src/soundio.cpp @@ -351,6 +351,8 @@ 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 9823a97..5264127 100644 --- a/src/soundio.h +++ b/src/soundio.h @@ -327,6 +327,11 @@ 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`. + double prebuf_duration; + // Defaults to NULL. Put whatever you want here. void *userdata; // In this callback, you call `soundio_outstream_begin_write` and