From f87961275dcae208c6f7ab71b5ac98e754a17d97 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 4 Aug 2015 00:56:03 -0700 Subject: [PATCH] write and read functions no longer allow setting frame count CoreAudio and JACK both have this limitation --- README.md | 8 +++-- example/sio_microphone.c | 66 +++++++++++++++++++++------------------- example/sio_sine.c | 10 ++---- soundio/soundio.h | 27 ++++++++-------- src/alsa.cpp | 53 +++++++++++++++++++------------- src/alsa.hpp | 3 ++ src/coreaudio.cpp | 3 +- src/dummy.cpp | 63 +++++++++++++++----------------------- src/dummy.hpp | 3 +- src/jack.cpp | 54 +++++++++++--------------------- src/jack.hpp | 3 +- src/pulseaudio.cpp | 61 +++++++++++++++++++++++-------------- src/pulseaudio.hpp | 8 +++-- src/soundio.cpp | 5 ++- src/soundio.hpp | 6 ++-- test/underflow.c | 13 +++----- 16 files changed, 193 insertions(+), 193 deletions(-) diff --git a/README.md b/README.md index 23fec56..d3a9bc8 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ behavior on every platform. - [JACK](http://jackaudio.org/) - [PulseAudio](http://www.freedesktop.org/wiki/Software/PulseAudio/) - [ALSA](http://www.alsa-project.org/) - - (planned) [CoreAudio](https://developer.apple.com/library/mac/documentation/MusicAudio/Conceptual/CoreAudioOverview/Introduction/Introduction.html) + - [CoreAudio](https://developer.apple.com/library/mac/documentation/MusicAudio/Conceptual/CoreAudioOverview/Introduction/Introduction.html) - Dummy (silence) - (planned) [WASAPI](https://msdn.microsoft.com/en-us/library/windows/desktop/dd371455%28v=vs.85%29.aspx) - (planned) [ASIO](http://www.asio4all.com/) @@ -242,6 +242,9 @@ view `coverage/index.html` in a browser. ## Roadmap 0. implement CoreAudio (OSX) backend, get examples working + - microphone example + - underflow example + 0. ALSA backend for microphone example is broken 0. Add some builtin channel layouts from https://developer.apple.com/library/mac/documentation/MusicAudio/Reference/CoreAudioDataTypesRef/#//apple_ref/doc/constant_group/Audio_Channel_Layout_Tags 0. Make sure sending bogus device id results in "SoundIoErrorNoSuchDevice" on each backend @@ -251,6 +254,7 @@ view `coverage/index.html` in a browser. should do the same. 0. implement WASAPI (Windows) backend, get examples working 0. implement ASIO (Windows) backend, get examples working + 0. Do we really want `period_duration` in the API? 0. Integrate into libgroove and test with Groove Basin 0. clear buffer maybe could take an argument to say how many frames to not clear 0. Verify that JACK xrun callback context is the same as process callback. @@ -275,7 +279,7 @@ view `coverage/index.html` in a browser. 0. -fvisibility=hidden and then explicitly export stuff, or explicitly make the unexported stuff private 0. add len arguments to APIs that have char * - - replace strdup with soundio_str_dupe + - replace strdup with `soundio_str_dupe` 0. Support PulseAudio proplist properties for main context and streams 0. Expose JACK options in `jack_client_open` 0. custom allocator support diff --git a/example/sio_microphone.c b/example/sio_microphone.c index 8e50b37..4549d27 100644 --- a/example/sio_microphone.c +++ b/example/sio_microphone.c @@ -51,26 +51,24 @@ static void panic(const char *format, ...) { struct SoundIoRingBuffer *ring_buffer = NULL; -static int min_int(int a, int b) { - return (a < b) ? a : b; -} - static void read_callback(struct SoundIoInStream *instream, int available_frame_count) { + struct SoundIoChannelArea *areas; + int frame_count; int err; char *write_ptr = soundio_ring_buffer_write_ptr(ring_buffer); int free_bytes = soundio_ring_buffer_free_count(ring_buffer); int free_count = free_bytes / instream->bytes_per_frame; - int write_count = min_int(available_frame_count, free_count); - int frames_left = write_count; + + //fprintf(stderr, "read_callback %d free %d\n", available_frame_count, free_count); + + if (available_frame_count > free_count) + panic("ring buffer overflow"); for (;;) { - int frame_count = frames_left; - - struct SoundIoChannelArea *areas; if ((err = soundio_instream_begin_read(instream, &areas, &frame_count))) panic("begin read error: %s", soundio_strerror(err)); - if (frame_count <= 0) + if (!frame_count) break; if (!areas) { @@ -90,36 +88,42 @@ static void read_callback(struct SoundIoInStream *instream, int available_frame_ if ((err = soundio_instream_end_read(instream))) panic("end read error: %s", soundio_strerror(err)); - - frames_left -= frame_count; - - if (frames_left <= 0) - break; } - int advance_bytes = write_count * instream->bytes_per_frame; + int advance_bytes = available_frame_count * instream->bytes_per_frame; soundio_ring_buffer_advance_write_ptr(ring_buffer, advance_bytes); - - int dropped_frames = available_frame_count - write_count; - if (dropped_frames > 0) - fprintf(stderr, "Dropped %d frames due to overflow\n", dropped_frames); } static void write_callback(struct SoundIoOutStream *outstream, int requested_frame_count) { - int err; struct SoundIoChannelArea *areas; + int frame_count; + int err; + char *read_ptr = soundio_ring_buffer_read_ptr(ring_buffer); int fill_bytes = soundio_ring_buffer_fill_count(ring_buffer); int fill_count = fill_bytes / outstream->bytes_per_frame; - int read_frames = min_int(requested_frame_count, fill_count); - int frames_left = read_frames; + + //fprintf(stderr, "write_callback %d fill %d\n", requested_frame_count, fill_count); + + if (requested_frame_count > fill_count) { + // Ring buffer does not have enough data, fill with zeroes. + for (;;) { + if ((err = soundio_outstream_begin_write(outstream, &areas, &frame_count))) + panic("begin write error: %s", soundio_strerror(err)); + if (frame_count <= 0) + return; + for (int frame = 0; frame < frame_count; frame += 1) { + for (int ch = 0; ch < outstream->layout.channel_count; ch += 1) { + memset(areas[ch].ptr, 0, outstream->bytes_per_sample); + areas[ch].ptr += areas[ch].step; + } + } + if ((err = soundio_outstream_end_write(outstream))) + panic("end write error: %s", soundio_strerror(err)); + } + } for (;;) { - int frame_count = frames_left; - - if (frame_count <= 0) - break; - if ((err = soundio_outstream_begin_write(outstream, &areas, &frame_count))) panic("begin write error: %s", soundio_strerror(err)); @@ -134,13 +138,11 @@ static void write_callback(struct SoundIoOutStream *outstream, int requested_fra } } - if ((err = soundio_outstream_end_write(outstream, frame_count))) + if ((err = soundio_outstream_end_write(outstream))) panic("end write error: %s", soundio_strerror(err)); - - frames_left -= frame_count; } - soundio_ring_buffer_advance_read_ptr(ring_buffer, read_frames * outstream->bytes_per_frame); + soundio_ring_buffer_advance_read_ptr(ring_buffer, requested_frame_count * outstream->bytes_per_frame); } static void underflow_callback(struct SoundIoOutStream *outstream) { diff --git a/example/sio_sine.c b/example/sio_sine.c index 07cdb05..ee421e8 100644 --- a/example/sio_sine.c +++ b/example/sio_sine.c @@ -35,11 +35,11 @@ static float seconds_offset = 0.0f; 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; + int frame_count; int err; - for (;;) { - int frame_count = requested_frame_count; + for (;;) { struct SoundIoChannelArea *areas; if ((err = soundio_outstream_begin_write(outstream, &areas, &frame_count))) panic("%s", soundio_strerror(err)); @@ -60,15 +60,11 @@ static void write_callback(struct SoundIoOutStream *outstream, int requested_fra } seconds_offset += seconds_per_frame * frame_count; - if ((err = soundio_outstream_end_write(outstream, frame_count))) { + if ((err = soundio_outstream_end_write(outstream))) { if (err == SoundIoErrorUnderflow) return; panic("%s", soundio_strerror(err)); } - - requested_frame_count -= frame_count; - if (requested_frame_count <= 0) - break; } } diff --git a/soundio/soundio.h b/soundio/soundio.h index 3d61c9f..2101d35 100644 --- a/soundio/soundio.h +++ b/soundio/soundio.h @@ -417,8 +417,11 @@ struct SoundIoOutStream { // Defaults to NULL. Put whatever you want here. void *userdata; // In this callback, you call `soundio_outstream_begin_write` and - // `soundio_outstream_end_write`. `requested_frame_count` will always be - // greater than 0. + // `soundio_outstream_end_write` as many times as necessary to write + // exactly `requested_frame_count` frames. `requested_frame_count` will + // always be greater than 0. To be compatible with all backends, you must + // write exactly `requested_frame_count` frames during the callback, + // otherwise a buffer underrun will occur. void (*write_callback)(struct SoundIoOutStream *, int requested_frame_count); // This optional callback happens when the sound device runs out of buffered // audio data to play. After this occurs, the outstream waits until the @@ -712,21 +715,20 @@ int soundio_outstream_start(struct SoundIoOutStream *outstream); // * `outstream` - (in) The output stream you want to write to. // * `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. Must be -// greater than 0 frames. +// * `frame_count` - (out) Returns 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 sio_sine.c for an example. // You must call this function only from the `write_callback` thread context. -// After calling this function, write data to `areas` and then call `soundio_outstream_end_write`. +// After calling this function, write data to `areas` and then call +// `soundio_outstream_end_write`. int soundio_outstream_begin_write(struct SoundIoOutStream *outstream, struct SoundIoChannelArea **areas, int *frame_count); // Commits the write that you began with `soundio_outstream_begin_write`. // You must call this function only from the `write_callback` thread context. // This function might return `SoundIoErrorUnderflow` but don't count on it. -int soundio_outstream_end_write(struct SoundIoOutStream *outstream, int frame_count); +int soundio_outstream_end_write(struct SoundIoOutStream *outstream); // Clears the output stream buffer. // You must call this function only from the `write_callback` thread context. @@ -760,12 +762,11 @@ int soundio_instream_start(struct SoundIoInStream *instream); // buffer. // * `instream` - (in) The input stream you want to read from. // * `areas` - (out) The memory addresses you can read data from. It is OK -// to modify the pointers if that helps you iterate. If a buffer overflow -// occurred, there will be a "hole" in the buffer. To indicate this, -// `areas` will be `NULL` and `frame_count` tells how big the hole is in -// frames. -// * `frame_count` - (in/out) - Provide the number of frames you want to read. -// Returned will be the number of frames you can actually read. +// to modify the pointers if that helps you iterate. There might be a "hole" +// in the buffer. To indicate this, `areas` will be `NULL` and `frame_count` +// tells how big the hole is in frames. +// * `frame_count` - (out) - Returns the number of frames you can actually +// read. // It is your responsibility to call this function no more and no fewer than the // correct number of times as determined by `available_frame_count` from // `read_callback`. See sio_microphone.c for an example. diff --git a/src/alsa.cpp b/src/alsa.cpp index c9fe2ab..a9a4f13 100644 --- a/src/alsa.cpp +++ b/src/alsa.cpp @@ -1013,6 +1013,7 @@ void outstream_thread_run(void *arg) { continue; } + osa->frames_left = avail; outstream->write_callback(outstream, avail); continue; } @@ -1080,6 +1081,7 @@ static void instream_thread_run(void *arg) { continue; } + isa->frames_left = avail; instream->read_callback(instream, avail); continue; } @@ -1283,7 +1285,7 @@ static int outstream_start_alsa(SoundIoPrivate *si, SoundIoOutStreamPrivate *os) } int outstream_begin_write_alsa(SoundIoPrivate *si, SoundIoOutStreamPrivate *os, - struct SoundIoChannelArea **out_areas, int *frame_count) + struct SoundIoChannelArea **out_areas, int *out_frame_count) { *out_areas = nullptr; SoundIoOutStreamAlsa *osa = &os->backend_data.alsa; @@ -1295,17 +1297,19 @@ int outstream_begin_write_alsa(SoundIoPrivate *si, SoundIoOutStreamPrivate *os, osa->areas[ch].step = outstream->bytes_per_frame; } - *frame_count = min(*frame_count, osa->period_size); + osa->frames_to_write = min(osa->frames_left, osa->period_size); + *out_frame_count = osa->frames_to_write; } else if (osa->access == SND_PCM_ACCESS_RW_NONINTERLEAVED) { for (int ch = 0; ch < outstream->layout.channel_count; ch += 1) { osa->areas[ch].ptr = osa->sample_buffer + ch * outstream->bytes_per_sample * osa->period_size; osa->areas[ch].step = outstream->bytes_per_sample; } - *frame_count = min(*frame_count, osa->period_size); + osa->frames_to_write = min(osa->frames_left, osa->period_size); + *out_frame_count = osa->frames_to_write; } else { const snd_pcm_channel_area_t *areas; - snd_pcm_uframes_t frames = *frame_count; + snd_pcm_uframes_t frames = osa->frames_left; int err; if ((err = snd_pcm_mmap_begin(osa->handle, &areas, &osa->offset, &frames)) < 0) { @@ -1323,37 +1327,40 @@ int outstream_begin_write_alsa(SoundIoPrivate *si, SoundIoOutStreamPrivate *os, (osa->areas[ch].step * osa->offset); } - *frame_count = frames; + osa->frames_to_write = frames; + *out_frame_count = osa->frames_to_write; } *out_areas = osa->areas; return 0; } -static int outstream_end_write_alsa(SoundIoPrivate *si, SoundIoOutStreamPrivate *os, int frame_count) { +static int outstream_end_write_alsa(SoundIoPrivate *si, SoundIoOutStreamPrivate *os) { SoundIoOutStreamAlsa *osa = &os->backend_data.alsa; SoundIoOutStream *outstream = &os->pub; snd_pcm_sframes_t commitres; if (osa->access == SND_PCM_ACCESS_RW_INTERLEAVED) { - commitres = snd_pcm_writei(osa->handle, osa->sample_buffer, frame_count); + commitres = snd_pcm_writei(osa->handle, osa->sample_buffer, osa->frames_to_write); } else if (osa->access == SND_PCM_ACCESS_RW_NONINTERLEAVED) { char *ptrs[SOUNDIO_MAX_CHANNELS]; for (int ch = 0; ch < outstream->layout.channel_count; ch += 1) { ptrs[ch] = osa->sample_buffer + ch * outstream->bytes_per_sample * osa->period_size; } - commitres = snd_pcm_writen(osa->handle, (void**)ptrs, frame_count); + commitres = snd_pcm_writen(osa->handle, (void**)ptrs, osa->frames_to_write); } else { - commitres = snd_pcm_mmap_commit(osa->handle, osa->offset, frame_count); + commitres = snd_pcm_mmap_commit(osa->handle, osa->offset, osa->frames_to_write); } - if (commitres < 0 || commitres != frame_count) { + if (commitres < 0 || commitres != osa->frames_to_write) { int err = (commitres >= 0) ? -EPIPE : commitres; if (err == -EPIPE || err == -ESTRPIPE) return SoundIoErrorUnderflow; else return SoundIoErrorStreaming; } + osa->frames_left -= osa->frames_to_write; + assert(osa->frames_left >= 0); return 0; } @@ -1560,7 +1567,7 @@ static int instream_start_alsa(SoundIoPrivate *si, SoundIoInStreamPrivate *is) { } static int instream_begin_read_alsa(SoundIoPrivate *si, - SoundIoInStreamPrivate *is, SoundIoChannelArea **out_areas, int *frame_count) + SoundIoInStreamPrivate *is, SoundIoChannelArea **out_areas, int *out_frame_count) { *out_areas = nullptr; SoundIoInStreamAlsa *isa = &is->backend_data.alsa; @@ -1572,10 +1579,11 @@ static int instream_begin_read_alsa(SoundIoPrivate *si, isa->areas[ch].step = instream->bytes_per_frame; } - isa->read_frame_count = min(*frame_count, isa->period_size); - *frame_count = isa->read_frame_count; + isa->read_frame_count = min(isa->frames_left, isa->period_size); + *out_frame_count = isa->read_frame_count; + snd_pcm_sframes_t commitres = snd_pcm_readi(isa->handle, isa->sample_buffer, isa->read_frame_count); - if (commitres < 0 || commitres != *frame_count) { + if (commitres < 0 || commitres != isa->read_frame_count) { int err = (commitres >= 0) ? -EPIPE : commitres; if ((err = instream_xrun_recovery(is, err)) < 0) return SoundIoErrorStreaming; @@ -1588,17 +1596,18 @@ static int instream_begin_read_alsa(SoundIoPrivate *si, ptrs[ch] = isa->areas[ch].ptr; } - isa->read_frame_count = min(*frame_count, isa->period_size); - *frame_count = isa->read_frame_count; + isa->read_frame_count = min(isa->frames_left, isa->period_size); + *out_frame_count = isa->read_frame_count; + snd_pcm_sframes_t commitres = snd_pcm_readn(isa->handle, (void**)ptrs, isa->read_frame_count); - if (commitres < 0 || commitres != *frame_count) { + if (commitres < 0 || commitres != isa->read_frame_count) { int err = (commitres >= 0) ? -EPIPE : commitres; if ((err = instream_xrun_recovery(is, err)) < 0) return SoundIoErrorStreaming; } } else { const snd_pcm_channel_area_t *areas; - snd_pcm_uframes_t frames = *frame_count; + snd_pcm_uframes_t frames = isa->frames_left; int err; if ((err = snd_pcm_mmap_begin(isa->handle, &areas, &isa->offset, &frames)) < 0) { @@ -1615,7 +1624,7 @@ static int instream_begin_read_alsa(SoundIoPrivate *si, } isa->read_frame_count = frames; - *frame_count = isa->read_frame_count; + *out_frame_count = isa->read_frame_count; } *out_areas = isa->areas; @@ -1626,9 +1635,9 @@ static int instream_end_read_alsa(SoundIoPrivate *si, SoundIoInStreamPrivate *is SoundIoInStreamAlsa *isa = &is->backend_data.alsa; if (isa->access == SND_PCM_ACCESS_RW_INTERLEAVED) { - return 0; + // nothing to do } else if (isa->access == SND_PCM_ACCESS_RW_NONINTERLEAVED) { - return 0; + // nothing to do } else { snd_pcm_sframes_t commitres = snd_pcm_mmap_commit(isa->handle, isa->offset, isa->read_frame_count); if (commitres < 0 || commitres != isa->read_frame_count) { @@ -1638,6 +1647,8 @@ static int instream_end_read_alsa(SoundIoPrivate *si, SoundIoInStreamPrivate *is } } + isa->frames_left -= isa->read_frame_count; + assert(isa->frames_left >= 0); return 0; } diff --git a/src/alsa.hpp b/src/alsa.hpp index 2e6c0b7..32bfdba 100644 --- a/src/alsa.hpp +++ b/src/alsa.hpp @@ -49,6 +49,8 @@ struct SoundIoOutStreamAlsa { SoundIoOsThread *thread; atomic_flag thread_exit_flag; int period_size; + int frames_left; + int frames_to_write; SoundIoChannelArea areas[SOUNDIO_MAX_CHANNELS]; }; @@ -65,6 +67,7 @@ struct SoundIoInStreamAlsa { SoundIoOsThread *thread; atomic_flag thread_exit_flag; int period_size; + int frames_left; int read_frame_count; SoundIoChannelArea areas[SOUNDIO_MAX_CHANNELS]; }; diff --git a/src/coreaudio.cpp b/src/coreaudio.cpp index 36c120f..222a1c7 100644 --- a/src/coreaudio.cpp +++ b/src/coreaudio.cpp @@ -865,8 +865,7 @@ static int outstream_begin_write_ca(struct SoundIoPrivate *si, struct SoundIoOut return 0; } -static int outstream_end_write_ca(struct SoundIoPrivate *si, struct SoundIoOutStreamPrivate *os, int) -{ +static int outstream_end_write_ca(struct SoundIoPrivate *si, struct SoundIoOutStreamPrivate *os) { SoundIoOutStreamCoreAudio *osca = &os->backend_data.coreaudio; osca->buffer_index += 1; return 0; diff --git a/src/dummy.cpp b/src/dummy.cpp index a11b13b..344482e 100644 --- a/src/dummy.cpp +++ b/src/dummy.cpp @@ -20,6 +20,7 @@ static void playback_thread_run(void *arg) { int fill_bytes = soundio_ring_buffer_fill_count(&osd->ring_buffer); int free_bytes = soundio_ring_buffer_capacity(&osd->ring_buffer) - fill_bytes; int free_frames = free_bytes / outstream->bytes_per_frame; + osd->frames_left = free_frames; outstream->write_callback(outstream, free_frames); double start_time = soundio_os_get_time(); long frames_consumed = 0; @@ -47,10 +48,12 @@ static void playback_thread_run(void *arg) { if (frames_to_kill > fill_frames) { outstream->underflow_callback(outstream); + osd->frames_left = free_frames; outstream->write_callback(outstream, free_frames); frames_consumed = 0; start_time = soundio_os_get_time(); } else if (free_frames > 0) { + osd->frames_left = free_frames; outstream->write_callback(outstream, free_frames); } } @@ -90,6 +93,7 @@ static void capture_thread_run(void *arg) { start_time = soundio_os_get_time(); } if (fill_frames > 0) { + isd->frames_left = fill_frames; instream->read_callback(instream, fill_frames); } } @@ -197,37 +201,28 @@ static int outstream_start_dummy(SoundIoPrivate *si, SoundIoOutStreamPrivate *os } static int outstream_begin_write_dummy(SoundIoPrivate *si, - SoundIoOutStreamPrivate *os, SoundIoChannelArea **out_areas, int *frame_count) + SoundIoOutStreamPrivate *os, SoundIoChannelArea **out_areas, int *out_frame_count) { - *out_areas = nullptr; - SoundIoOutStream *outstream = &os->pub; SoundIoOutStreamDummy *osd = &os->backend_data.dummy; - assert(*frame_count >= 0); - assert(*frame_count <= osd->buffer_frame_count); - - int free_byte_count = soundio_ring_buffer_free_count(&osd->ring_buffer); - int free_frame_count = free_byte_count / outstream->bytes_per_frame; - *frame_count = min(*frame_count, free_frame_count); - - if (free_frame_count) { - char *write_ptr = soundio_ring_buffer_write_ptr(&osd->ring_buffer); - for (int ch = 0; ch < outstream->layout.channel_count; ch += 1) { - osd->areas[ch].ptr = write_ptr + outstream->bytes_per_sample * ch; - osd->areas[ch].step = outstream->bytes_per_frame; - } - - *out_areas = osd->areas; + char *write_ptr = soundio_ring_buffer_write_ptr(&osd->ring_buffer); + for (int ch = 0; ch < outstream->layout.channel_count; ch += 1) { + osd->areas[ch].ptr = write_ptr + outstream->bytes_per_sample * ch; + osd->areas[ch].step = outstream->bytes_per_frame; } + + *out_frame_count = osd->frames_left; + *out_areas = osd->areas; return 0; } -static int outstream_end_write_dummy(SoundIoPrivate *si, SoundIoOutStreamPrivate *os, int frame_count) { +static int outstream_end_write_dummy(SoundIoPrivate *si, SoundIoOutStreamPrivate *os) { SoundIoOutStreamDummy *osd = &os->backend_data.dummy; SoundIoOutStream *outstream = &os->pub; - int byte_count = frame_count * outstream->bytes_per_frame; + int byte_count = osd->frames_left * outstream->bytes_per_frame; soundio_ring_buffer_advance_write_ptr(&osd->ring_buffer, byte_count); + osd->frames_left = 0; return 0; } @@ -310,37 +305,29 @@ static int instream_start_dummy(SoundIoPrivate *si, SoundIoInStreamPrivate *is) } static int instream_begin_read_dummy(SoundIoPrivate *si, - SoundIoInStreamPrivate *is, SoundIoChannelArea **out_areas, int *frame_count) + SoundIoInStreamPrivate *is, SoundIoChannelArea **out_areas, int *out_frame_count) { SoundIoInStream *instream = &is->pub; SoundIoInStreamDummy *isd = &is->backend_data.dummy; - assert(*frame_count >= 0); - assert(*frame_count <= isd->buffer_frame_count); - - 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); - *frame_count = isd->read_frame_count; - - if (fill_frame_count) { - char *read_ptr = soundio_ring_buffer_read_ptr(&isd->ring_buffer); - for (int ch = 0; ch < instream->layout.channel_count; ch += 1) { - isd->areas[ch].ptr = read_ptr + instream->bytes_per_sample * ch; - isd->areas[ch].step = instream->bytes_per_frame; - } - - *out_areas = isd->areas; + char *read_ptr = soundio_ring_buffer_read_ptr(&isd->ring_buffer); + for (int ch = 0; ch < instream->layout.channel_count; ch += 1) { + isd->areas[ch].ptr = read_ptr + instream->bytes_per_sample * ch; + isd->areas[ch].step = instream->bytes_per_frame; } + *out_frame_count = isd->frames_left; + *out_areas = isd->areas; + return 0; } static int instream_end_read_dummy(SoundIoPrivate *si, SoundIoInStreamPrivate *is) { SoundIoInStreamDummy *isd = &is->backend_data.dummy; SoundIoInStream *instream = &is->pub; - int byte_count = isd->read_frame_count * instream->bytes_per_frame; + int byte_count = isd->frames_left * instream->bytes_per_frame; soundio_ring_buffer_advance_read_ptr(&isd->ring_buffer, byte_count); + isd->frames_left = 0; return 0; } diff --git a/src/dummy.hpp b/src/dummy.hpp index 71bc61b..163723c 100644 --- a/src/dummy.hpp +++ b/src/dummy.hpp @@ -30,6 +30,7 @@ struct SoundIoOutStreamDummy { struct SoundIoOsCond *cond; atomic_flag abort_flag; int buffer_frame_count; + int frames_left; struct SoundIoRingBuffer ring_buffer; double playback_start_time; SoundIoChannelArea areas[SOUNDIO_MAX_CHANNELS]; @@ -39,7 +40,7 @@ struct SoundIoInStreamDummy { struct SoundIoOsThread *thread; struct SoundIoOsCond *cond; atomic_flag abort_flag; - int read_frame_count; + int frames_left; int buffer_frame_count; struct SoundIoRingBuffer ring_buffer; SoundIoChannelArea areas[SOUNDIO_MAX_CHANNELS]; diff --git a/src/jack.cpp b/src/jack.cpp index 230da39..b902c25 100644 --- a/src/jack.cpp +++ b/src/jack.cpp @@ -342,7 +342,8 @@ static int outstream_process_callback(jack_nframes_t nframes, void *arg) { osj->frames_left = nframes; for (int ch = 0; ch < outstream->layout.channel_count; ch += 1) { SoundIoOutStreamJackPort *osjp = &osj->ports[ch]; - osj->buf_ptrs[ch] = (char*)jack_port_get_buffer(osjp->source_port, osj->frames_left); + osj->areas[ch].ptr = (char*)jack_port_get_buffer(osjp->source_port, nframes); + osj->areas[ch].step = outstream->bytes_per_sample; } outstream->write_callback(outstream, osj->frames_left); return 0; @@ -525,37 +526,19 @@ static int outstream_start_jack(struct SoundIoPrivate *si, struct SoundIoOutStre } static int outstream_begin_write_jack(struct SoundIoPrivate *si, struct SoundIoOutStreamPrivate *os, - SoundIoChannelArea **out_areas, int *frame_count) + SoundIoChannelArea **out_areas, int *out_frame_count) { - SoundIoOutStream *outstream = &os->pub; SoundIoOutStreamJack *osj = &os->backend_data.jack; - *frame_count = min(*frame_count, osj->frames_left); - - for (int ch = 0; ch < outstream->layout.channel_count; ch += 1) { - osj->areas[ch].ptr = osj->buf_ptrs[ch]; - osj->areas[ch].step = outstream->bytes_per_sample; - } - + *out_frame_count = osj->frames_left; *out_areas = osj->areas; return 0; } -static int outstream_end_write_jack(struct SoundIoPrivate *si, struct SoundIoOutStreamPrivate *os, - int frame_count) -{ - SoundIoOutStream *outstream = &os->pub; +static int outstream_end_write_jack(struct SoundIoPrivate *si, struct SoundIoOutStreamPrivate *os) { SoundIoOutStreamJack *osj = &os->backend_data.jack; - assert(frame_count <= osj->frames_left); - - osj->frames_left -= frame_count; - if (osj->frames_left > 0) { - for (int ch = 0; ch < outstream->layout.channel_count; ch += 1) { - osj->buf_ptrs[ch] += frame_count * outstream->bytes_per_sample; - } - } - + osj->frames_left = 0; return 0; } @@ -611,7 +594,14 @@ static void instream_shutdown_callback(void *arg) { static int instream_process_callback(jack_nframes_t nframes, void *arg) { SoundIoInStreamPrivate *is = (SoundIoInStreamPrivate *)arg; SoundIoInStream *instream = &is->pub; - instream->read_callback(instream, nframes); + SoundIoInStreamJack *isj = &is->backend_data.jack; + isj->frames_left = nframes; + for (int ch = 0; ch < instream->layout.channel_count; ch += 1) { + SoundIoInStreamJackPort *isjp = &isj->ports[ch]; + isj->areas[ch].ptr = (char*)jack_port_get_buffer(isjp->dest_port, nframes); + isj->areas[ch].step = instream->bytes_per_sample; + } + instream->read_callback(instream, isj->frames_left); return 0; } @@ -736,27 +726,19 @@ static int instream_start_jack(struct SoundIoPrivate *si, struct SoundIoInStream } static int instream_begin_read_jack(struct SoundIoPrivate *si, struct SoundIoInStreamPrivate *is, - SoundIoChannelArea **out_areas, int *frame_count) + SoundIoChannelArea **out_areas, int *out_frame_count) { - SoundIoInStream *instream = &is->pub; SoundIoInStreamJack *isj = &is->backend_data.jack; - SoundIoJack *sij = &si->backend_data.jack; - assert(*frame_count <= sij->period_size); - - for (int ch = 0; ch < instream->layout.channel_count; ch += 1) { - SoundIoInStreamJackPort *isjp = &isj->ports[ch]; - if (!(isj->areas[ch].ptr = (char*)jack_port_get_buffer(isjp->dest_port, *frame_count))) - return SoundIoErrorStreaming; - - isj->areas[ch].step = instream->bytes_per_sample; - } + *out_frame_count = isj->frames_left; *out_areas = isj->areas; return 0; } static int instream_end_read_jack(struct SoundIoPrivate *si, struct SoundIoInStreamPrivate *is) { + SoundIoInStreamJack *isj = &is->backend_data.jack; + isj->frames_left = 0; return 0; } diff --git a/src/jack.hpp b/src/jack.hpp index 0c89a8d..f4783c5 100644 --- a/src/jack.hpp +++ b/src/jack.hpp @@ -50,7 +50,6 @@ struct SoundIoOutStreamJack { int frames_left; SoundIoOutStreamJackPort ports[SOUNDIO_MAX_CHANNELS]; SoundIoChannelArea areas[SOUNDIO_MAX_CHANNELS]; - char *buf_ptrs[SOUNDIO_MAX_CHANNELS]; }; struct SoundIoInStreamJackPort { @@ -62,8 +61,10 @@ struct SoundIoInStreamJackPort { struct SoundIoInStreamJack { jack_client_t *client; int period_size; + int frames_left; SoundIoInStreamJackPort ports[SOUNDIO_MAX_CHANNELS]; SoundIoChannelArea areas[SOUNDIO_MAX_CHANNELS]; + char *buf_ptrs[SOUNDIO_MAX_CHANNELS]; }; #endif diff --git a/src/pulseaudio.cpp b/src/pulseaudio.cpp index ec987d7..3cb43aa 100644 --- a/src/pulseaudio.cpp +++ b/src/pulseaudio.cpp @@ -641,8 +641,11 @@ static void playback_stream_underflow_callback(pa_stream *stream, void *userdata } static void playback_stream_write_callback(pa_stream *stream, size_t nbytes, void *userdata) { - SoundIoOutStream *outstream = (SoundIoOutStream*)(userdata); - int frame_count = ((int)nbytes) / outstream->bytes_per_frame; + SoundIoOutStreamPrivate *os = (SoundIoOutStreamPrivate*)(userdata); + SoundIoOutStreamPulseAudio *ospa = &os->backend_data.pulseaudio; + SoundIoOutStream *outstream = &os->pub; + ospa->bytes_left = nbytes; + int frame_count = ospa->bytes_left / outstream->bytes_per_frame; outstream->write_callback(outstream, frame_count); } @@ -750,17 +753,23 @@ static int outstream_start_pa(SoundIoPrivate *si, SoundIoOutStreamPrivate *os) { } static int outstream_begin_write_pa(SoundIoPrivate *si, - SoundIoOutStreamPrivate *os, SoundIoChannelArea **out_areas, int *frame_count) + SoundIoOutStreamPrivate *os, SoundIoChannelArea **out_areas, int *out_frame_count) { - *out_areas = nullptr; - SoundIoOutStream *outstream = &os->pub; SoundIoOutStreamPulseAudio *ospa = &os->backend_data.pulseaudio; pa_stream *stream = ospa->stream; - size_t byte_count = *frame_count * outstream->bytes_per_frame; + if (ospa->bytes_left <= 0) { + *out_frame_count = 0; + *out_areas = nullptr; + return 0; + } - if (pa_stream_begin_write(stream, (void**)&ospa->write_ptr, &byte_count)) + // PulseAudio docs recommend setting this to (size_t) -1 but I have found + // that this causes a too small buffer to be returned. + ospa->byte_count = ospa->bytes_left; + + if (pa_stream_begin_write(stream, (void**)&ospa->write_ptr, &ospa->byte_count)) return SoundIoErrorStreaming; for (int ch = 0; ch < outstream->layout.channel_count; ch += 1) { @@ -768,21 +777,18 @@ static int outstream_begin_write_pa(SoundIoPrivate *si, ospa->areas[ch].step = outstream->bytes_per_frame; } - *frame_count = byte_count / outstream->bytes_per_frame; + *out_frame_count = ospa->byte_count / outstream->bytes_per_frame; *out_areas = ospa->areas; return 0; } -static int outstream_end_write_pa(SoundIoPrivate *si, SoundIoOutStreamPrivate *os, int frame_count) { - SoundIoOutStream *outstream = &os->pub; +static int outstream_end_write_pa(SoundIoPrivate *si, SoundIoOutStreamPrivate *os) { SoundIoOutStreamPulseAudio *ospa = &os->backend_data.pulseaudio; pa_stream *stream = ospa->stream; - assert(frame_count >= 0); - size_t byte_count = frame_count * outstream->bytes_per_frame; - assert(byte_count < 1 * 1024 * 1024 * 1024); // attempting to write > 1GB is certainly a mistake - if (pa_stream_write(stream, ospa->write_ptr, byte_count, NULL, 0, PA_SEEK_RELATIVE)) + if (pa_stream_write(stream, ospa->write_ptr, ospa->byte_count, nullptr, 0, PA_SEEK_RELATIVE)) return SoundIoErrorStreaming; + ospa->bytes_left -= ospa->byte_count; return 0; } @@ -844,9 +850,11 @@ static void recording_stream_state_callback(pa_stream *stream, void *userdata) { static void recording_stream_read_callback(pa_stream *stream, size_t nbytes, void *userdata) { SoundIoInStreamPrivate *is = (SoundIoInStreamPrivate*)userdata; SoundIoInStream *instream = &is->pub; + SoundIoInStreamPulseAudio *ispa = &is->backend_data.pulseaudio; assert(nbytes % instream->bytes_per_frame == 0); assert(nbytes > 0); - int available_frame_count = nbytes / instream->bytes_per_frame; + ispa->bytes_left = nbytes; + int available_frame_count = ispa->bytes_left / instream->bytes_per_frame; instream->read_callback(instream, available_frame_count); } @@ -900,6 +908,7 @@ 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); + // TODO handle overflow callback ispa->buffer_attr.maxlength = UINT32_MAX; ispa->buffer_attr.tlength = UINT32_MAX; @@ -955,7 +964,7 @@ static int instream_start_pa(SoundIoPrivate *si, SoundIoInStreamPrivate *is) { } static int instream_begin_read_pa(SoundIoPrivate *si, - SoundIoInStreamPrivate *is, SoundIoChannelArea **out_areas, int *frame_count) + SoundIoInStreamPrivate *is, SoundIoChannelArea **out_areas, int *out_frame_count) { SoundIoInStream *instream = &is->pub; SoundIoInStreamPulseAudio *ispa = &is->backend_data.pulseaudio; @@ -963,24 +972,30 @@ static int instream_begin_read_pa(SoundIoPrivate *si, assert(ispa->stream_ready); - char *data; - size_t nbytes = *frame_count * instream->bytes_per_frame; - if (pa_stream_peek(stream, (const void **)&data, &nbytes)) { + if (ispa->bytes_left <= 0) { + *out_frame_count = 0; *out_areas = nullptr; - *frame_count = 0; + return 0; + } + + char *data; + ispa->byte_count = (size_t)-1; + if (pa_stream_peek(stream, (const void **)&data, &ispa->byte_count)) { + *out_areas = nullptr; + *out_frame_count = 0; return SoundIoErrorStreaming; } + *out_frame_count = ispa->byte_count / instream->bytes_per_frame; + if (data) { for (int ch = 0; ch < instream->layout.channel_count; ch += 1) { ispa->areas[ch].ptr = data + instream->bytes_per_sample * ch; ispa->areas[ch].step = instream->bytes_per_frame; } - *frame_count = nbytes / instream->bytes_per_frame; *out_areas = ispa->areas; } else { - *frame_count = nbytes / instream->bytes_per_frame; *out_areas = nullptr; } @@ -992,7 +1007,7 @@ static int instream_end_read_pa(SoundIoPrivate *si, SoundIoInStreamPrivate *is) pa_stream *stream = ispa->stream; if (pa_stream_drop(stream)) return SoundIoErrorStreaming; - + ispa->bytes_left -= ispa->byte_count; return 0; } diff --git a/src/pulseaudio.hpp b/src/pulseaudio.hpp index f9da2e9..22a4def 100644 --- a/src/pulseaudio.hpp +++ b/src/pulseaudio.hpp @@ -15,9 +15,7 @@ int soundio_pulseaudio_init(struct SoundIoPrivate *si); -struct SoundIoDevicePulseAudio { - -}; +struct SoundIoDevicePulseAudio { }; struct SoundIoPulseAudio { int connection_err; @@ -50,6 +48,8 @@ struct SoundIoOutStreamPulseAudio { atomic_bool stream_ready; pa_buffer_attr buffer_attr; char *write_ptr; + size_t byte_count; + int bytes_left; SoundIoChannelArea areas[SOUNDIO_MAX_CHANNELS]; }; @@ -57,6 +57,8 @@ struct SoundIoInStreamPulseAudio { pa_stream *stream; atomic_bool stream_ready; pa_buffer_attr buffer_attr; + size_t byte_count; + int bytes_left; SoundIoChannelArea areas[SOUNDIO_MAX_CHANNELS]; }; diff --git a/src/soundio.cpp b/src/soundio.cpp index 5886cc1..d230b94 100644 --- a/src/soundio.cpp +++ b/src/soundio.cpp @@ -352,15 +352,14 @@ int soundio_outstream_begin_write(struct SoundIoOutStream *outstream, SoundIo *soundio = outstream->device->soundio; SoundIoPrivate *si = (SoundIoPrivate *)soundio; SoundIoOutStreamPrivate *os = (SoundIoOutStreamPrivate *)outstream; - assert(*frame_count > 0); return si->outstream_begin_write(si, os, areas, frame_count); } -int soundio_outstream_end_write(struct SoundIoOutStream *outstream, int frame_count) { +int soundio_outstream_end_write(struct SoundIoOutStream *outstream) { SoundIo *soundio = outstream->device->soundio; SoundIoPrivate *si = (SoundIoPrivate *)soundio; SoundIoOutStreamPrivate *os = (SoundIoOutStreamPrivate *)outstream; - return si->outstream_end_write(si, os, frame_count); + return si->outstream_end_write(si, os); } static void default_outstream_error_callback(struct SoundIoOutStream *os, int err) { diff --git a/src/soundio.hpp b/src/soundio.hpp index e229400..8ef6ab0 100644 --- a/src/soundio.hpp +++ b/src/soundio.hpp @@ -126,8 +126,8 @@ struct SoundIoPrivate { void (*outstream_destroy)(struct SoundIoPrivate *, struct SoundIoOutStreamPrivate *); int (*outstream_start)(struct SoundIoPrivate *, struct SoundIoOutStreamPrivate *); int (*outstream_begin_write)(struct SoundIoPrivate *, struct SoundIoOutStreamPrivate *, - SoundIoChannelArea **out_areas, int *frame_count); - int (*outstream_end_write)(struct SoundIoPrivate *, struct SoundIoOutStreamPrivate *, int frame_count); + SoundIoChannelArea **out_areas, int *out_frame_count); + int (*outstream_end_write)(struct SoundIoPrivate *, struct SoundIoOutStreamPrivate *); int (*outstream_clear_buffer)(struct SoundIoPrivate *, struct SoundIoOutStreamPrivate *); int (*outstream_pause)(struct SoundIoPrivate *, struct SoundIoOutStreamPrivate *, bool pause); @@ -136,7 +136,7 @@ struct SoundIoPrivate { void (*instream_destroy)(struct SoundIoPrivate *, struct SoundIoInStreamPrivate *); int (*instream_start)(struct SoundIoPrivate *, struct SoundIoInStreamPrivate *); int (*instream_begin_read)(struct SoundIoPrivate *, struct SoundIoInStreamPrivate *, - SoundIoChannelArea **out_areas, int *frame_count); + SoundIoChannelArea **out_areas, int *out_frame_count); int (*instream_end_read)(struct SoundIoPrivate *, struct SoundIoInStreamPrivate *); int (*instream_pause)(struct SoundIoPrivate *, struct SoundIoInStreamPrivate *, bool pause); diff --git a/test/underflow.c b/test/underflow.c index 5808104..2cf41a6 100644 --- a/test/underflow.c +++ b/test/underflow.c @@ -41,6 +41,8 @@ static float seconds_end = 9.0f; 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; + struct SoundIoChannelArea *areas; + int frame_count; int err; if (!caused_underflow && seconds_offset >= 3.0f) { @@ -54,9 +56,6 @@ static void write_callback(struct SoundIoOutStream *outstream, int requested_fra } 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)); @@ -76,15 +75,11 @@ static void write_callback(struct SoundIoOutStream *outstream, int requested_fra } seconds_offset += seconds_per_frame * frame_count; - if ((err = soundio_outstream_end_write(outstream, frame_count))) { + if ((err = soundio_outstream_end_write(outstream))) { if (err == SoundIoErrorUnderflow) return; panic("%s", soundio_strerror(err)); } - - requested_frame_count -= frame_count; - if (requested_frame_count <= 0) - break; } } @@ -124,6 +119,8 @@ int main(int argc, char **argv) { if (err) panic("error connecting: %s", soundio_strerror(err)); + soundio_flush_events(soundio); + int default_out_device_index = soundio_default_output_device_index(soundio); if (default_out_device_index < 0) panic("no output device found");