diff --git a/README.md b/README.md index bdbb51e..e99eb44 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,10 @@ behavior on every platform. - 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/) + * Supports optimal usage of each supported backend. The same API does the + right thing whether the backend has a fixed buffer size, such as on JACK and + CoreAudio, or whether it allows directly managing the buffer, such as on + ALSA or PulseAudio. * C library. Depends only on the respective backend API libraries and libc. Does *not* depend on libstdc++, and does *not* have exceptions, run-time type information, or [setjmp](http://latentcontent.net/2007/12/05/libpng-worst-api-ever/). diff --git a/example/sio_microphone.c b/example/sio_microphone.c index 4549d27..6fc4c4e 100644 --- a/example/sio_microphone.c +++ b/example/sio_microphone.c @@ -15,6 +15,8 @@ static const double microphone_latency = 0.2; // seconds +struct SoundIoRingBuffer *ring_buffer = NULL; + static enum SoundIoFormat prioritized_formats[] = { SoundIoFormatFloat32NE, SoundIoFormatFloat32FE, @@ -37,6 +39,7 @@ static enum SoundIoFormat prioritized_formats[] = { SoundIoFormatInvalid, }; + __attribute__ ((cold)) __attribute__ ((noreturn)) __attribute__ ((format (printf, 1, 2))) @@ -49,22 +52,26 @@ static void panic(const char *format, ...) { abort(); } -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) { +static void read_callback(struct SoundIoInStream *instream, int frame_count_min, int frame_count_max) { 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; - //fprintf(stderr, "read_callback %d free %d\n", available_frame_count, free_count); - - if (available_frame_count > free_count) + if (frame_count_min > free_count) panic("ring buffer overflow"); + int write_frames = min_int(free_count, frame_count_max); + int frames_left = write_frames; + for (;;) { + int frame_count = frames_left; + if ((err = soundio_instream_begin_read(instream, &areas, &frame_count))) panic("begin read error: %s", soundio_strerror(err)); @@ -88,13 +95,17 @@ 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 = available_frame_count * instream->bytes_per_frame; + int advance_bytes = write_frames * instream->bytes_per_frame; soundio_ring_buffer_advance_write_ptr(ring_buffer, advance_bytes); } -static void write_callback(struct SoundIoOutStream *outstream, int requested_frame_count) { +static void write_callback(struct SoundIoOutStream *outstream, int frame_count_min, int frame_count_max) { struct SoundIoChannelArea *areas; int frame_count; int err; @@ -103,9 +114,7 @@ static void write_callback(struct SoundIoOutStream *outstream, int requested_fra int fill_bytes = soundio_ring_buffer_fill_count(ring_buffer); int fill_count = fill_bytes / outstream->bytes_per_frame; - //fprintf(stderr, "write_callback %d fill %d\n", requested_frame_count, fill_count); - - if (requested_frame_count > fill_count) { + if (frame_count_min > fill_count) { // Ring buffer does not have enough data, fill with zeroes. for (;;) { if ((err = soundio_outstream_begin_write(outstream, &areas, &frame_count))) @@ -123,7 +132,12 @@ static void write_callback(struct SoundIoOutStream *outstream, int requested_fra } } - for (;;) { + int read_count = min_int(frame_count_max, fill_count); + int frames_left = read_count; + + while (frames_left > 0) { + int frame_count = frames_left; + if ((err = soundio_outstream_begin_write(outstream, &areas, &frame_count))) panic("begin write error: %s", soundio_strerror(err)); @@ -140,9 +154,11 @@ static void write_callback(struct SoundIoOutStream *outstream, int requested_fra 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, requested_frame_count * outstream->bytes_per_frame); + soundio_ring_buffer_advance_read_ptr(ring_buffer, read_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 ee421e8..56775ed 100644 --- a/example/sio_sine.c +++ b/example/sio_sine.c @@ -32,15 +32,16 @@ static int usage(char *exe) { static const float PI = 3.1415926535f; static float seconds_offset = 0.0f; -static void write_callback(struct SoundIoOutStream *outstream, int requested_frame_count) { +static void write_callback(struct SoundIoOutStream *outstream, int frame_count_min, int frame_count_max) { float float_sample_rate = outstream->sample_rate; float seconds_per_frame = 1.0f / float_sample_rate; - int frame_count; + struct SoundIoChannelArea *areas; int err; + int frames_left = frame_count_max; for (;;) { - struct SoundIoChannelArea *areas; + int frame_count = frames_left; if ((err = soundio_outstream_begin_write(outstream, &areas, &frame_count))) panic("%s", soundio_strerror(err)); @@ -65,6 +66,10 @@ static void write_callback(struct SoundIoOutStream *outstream, int requested_fra return; panic("%s", soundio_strerror(err)); } + + frames_left -= frame_count; + if (frames_left <= 0) + break; } } diff --git a/soundio/soundio.h b/soundio/soundio.h index ca93083..89473f9 100644 --- a/soundio/soundio.h +++ b/soundio/soundio.h @@ -419,11 +419,15 @@ struct SoundIoOutStream { void *userdata; // In this callback, you call `soundio_outstream_begin_write` and // `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); + // at minimum `frame_count_min` frames and at maximum `frame_count_max` + // frames. `frame_count_max` will always be greater than 0. Note that you + // should write as many frames as you can; `frame_count_min` might be 0 and + // you can still get a buffer underflow if you always write + // `frame_count_min` frames. See `sio_sine.c` for example. + // For Dummy, ALSA, and PulseAudio, `frame_count_min` will be 0. For JACK + // and CoreAudio `frame_count_min` will be equal to `frame_count_max`. + void (*write_callback)(struct SoundIoOutStream *, + int frame_count_min, int frame_count_max); // This optional callback happens when the sound device runs out of buffered // audio data to play. After this occurs, the outstream waits until the // buffer is full to resume playback. @@ -500,8 +504,12 @@ struct SoundIoInStream { // Defaults to NULL. Put whatever you want here. void *userdata; // In this function call `soundio_instream_begin_read` and - // `soundio_instream_end_read`. - void (*read_callback)(struct SoundIoInStream *, int available_frame_count); + // `soundio_instream_end_read` as many times as necessary to read at + // minimum `frame_count_min` frames and at maximum `frame_count_max` + // frames. If you return from `read_callback` without having read + // `frame_count_min`, the frames will be dropped. `frame_count_max` is how + // many frames are available to read. + void (*read_callback)(struct SoundIoInStream *, int frame_count_min, int frame_count_max); // Optional callback. `err` is always SoundIoErrorStreaming. // SoundIoErrorStreaming is an unrecoverable error. The stream is in an // invalid state and must be destroyed. @@ -718,13 +726,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` - (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. +// * `frame_count` - (in/out) Provide the number of frames you want to write. +// Returned will be the number of frames you can actually write, which is +// also the number of frames that will be written when you call +// `soundio_outstream_end_write`. The value returned will always be less +// than or equal to the value provided. +// It is your responsibility to call this function exactly as many times as +// necessary to meet the `frame_count_min` and `frame_count_max` criteria from +// `write_callback`. See `sio_sine.c` for 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`. +// If you call this function with `frame_count` less than the `frame_count_min` +// parameter from `write_callback` it returns SoundIoErrorInvalid. +// If this function returns an error, do not call `soundio_outstream_end_write`. int soundio_outstream_begin_write(struct SoundIoOutStream *outstream, struct SoundIoChannelArea **areas, int *frame_count); @@ -768,11 +783,15 @@ int soundio_instream_start(struct SoundIoInStream *instream); // 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. +// * `frame_count` - (in/out) - Provide the number of frames you want to read; +// returns the number of frames you can actually read. The returned value +// will always be less than or equal to the provided value. If the provided +// value is less than `frame_count_min` from `read_callback` this function +// returns with SoundIoErrorInvalid. // 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. +// correct number of times according to the `frame_count_min` and +// `frame_count_max` criteria from `read_callback`. See sio_microphone.c for an +// example. // You must call this function only from the `read_callback` thread context. // After calling this function, read data from `areas` and then use // `soundio_instream_end_read` to actually remove the data from the buffer diff --git a/src/alsa.cpp b/src/alsa.cpp index 4ce0fea..576ed6d 100644 --- a/src/alsa.cpp +++ b/src/alsa.cpp @@ -1011,8 +1011,7 @@ void outstream_thread_run(void *arg) { continue; } - osa->frames_left = avail; - outstream->write_callback(outstream, avail); + outstream->write_callback(outstream, 0, avail); continue; } case SND_PCM_STATE_XRUN: @@ -1079,8 +1078,7 @@ static void instream_thread_run(void *arg) { continue; } - isa->frames_left = avail; - instream->read_callback(instream, avail); + instream->read_callback(instream, 0, avail); continue; } case SND_PCM_STATE_XRUN: @@ -1283,7 +1281,7 @@ static int outstream_start_alsa(SoundIoPrivate *si, SoundIoOutStreamPrivate *os) } int outstream_begin_write_alsa(SoundIoPrivate *si, SoundIoOutStreamPrivate *os, - struct SoundIoChannelArea **out_areas, int *out_frame_count) + struct SoundIoChannelArea **out_areas, int *frame_count) { *out_areas = nullptr; SoundIoOutStreamAlsa *osa = &os->backend_data.alsa; @@ -1295,19 +1293,19 @@ int outstream_begin_write_alsa(SoundIoPrivate *si, SoundIoOutStreamPrivate *os, osa->areas[ch].step = outstream->bytes_per_frame; } - osa->frames_to_write = min(osa->frames_left, osa->period_size); - *out_frame_count = osa->frames_to_write; + osa->write_frame_count = min(*frame_count, osa->period_size); + *frame_count = osa->write_frame_count; } 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; } - osa->frames_to_write = min(osa->frames_left, osa->period_size); - *out_frame_count = osa->frames_to_write; + osa->write_frame_count = min(*frame_count, osa->period_size); + *frame_count = osa->write_frame_count; } else { const snd_pcm_channel_area_t *areas; - snd_pcm_uframes_t frames = osa->frames_left; + snd_pcm_uframes_t frames = *frame_count; int err; if ((err = snd_pcm_mmap_begin(osa->handle, &areas, &osa->offset, &frames)) < 0) { @@ -1325,8 +1323,8 @@ int outstream_begin_write_alsa(SoundIoPrivate *si, SoundIoOutStreamPrivate *os, (osa->areas[ch].step * osa->offset); } - osa->frames_to_write = frames; - *out_frame_count = osa->frames_to_write; + osa->write_frame_count = frames; + *frame_count = osa->write_frame_count; } *out_areas = osa->areas; @@ -1339,27 +1337,24 @@ static int outstream_end_write_alsa(SoundIoPrivate *si, SoundIoOutStreamPrivate snd_pcm_sframes_t commitres; if (osa->access == SND_PCM_ACCESS_RW_INTERLEAVED) { - commitres = snd_pcm_writei(osa->handle, osa->sample_buffer, osa->frames_to_write); + commitres = snd_pcm_writei(osa->handle, osa->sample_buffer, osa->write_frame_count); } 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, osa->frames_to_write); + commitres = snd_pcm_writen(osa->handle, (void**)ptrs, osa->write_frame_count); } else { - commitres = snd_pcm_mmap_commit(osa->handle, osa->offset, osa->frames_to_write); + commitres = snd_pcm_mmap_commit(osa->handle, osa->offset, osa->write_frame_count); } - if (commitres < 0 || commitres != osa->frames_to_write) { + if (commitres < 0 || commitres != osa->write_frame_count) { 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; } @@ -1565,7 +1560,7 @@ static int instream_start_alsa(SoundIoPrivate *si, SoundIoInStreamPrivate *is) { } static int instream_begin_read_alsa(SoundIoPrivate *si, - SoundIoInStreamPrivate *is, SoundIoChannelArea **out_areas, int *out_frame_count) + SoundIoInStreamPrivate *is, SoundIoChannelArea **out_areas, int *frame_count) { *out_areas = nullptr; SoundIoInStreamAlsa *isa = &is->backend_data.alsa; @@ -1577,8 +1572,8 @@ static int instream_begin_read_alsa(SoundIoPrivate *si, isa->areas[ch].step = instream->bytes_per_frame; } - isa->read_frame_count = min(isa->frames_left, isa->period_size); - *out_frame_count = isa->read_frame_count; + isa->read_frame_count = min(*frame_count, isa->period_size); + *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 != isa->read_frame_count) { @@ -1594,8 +1589,8 @@ static int instream_begin_read_alsa(SoundIoPrivate *si, ptrs[ch] = isa->areas[ch].ptr; } - isa->read_frame_count = min(isa->frames_left, isa->period_size); - *out_frame_count = isa->read_frame_count; + isa->read_frame_count = min(*frame_count, isa->period_size); + *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 != isa->read_frame_count) { @@ -1605,7 +1600,7 @@ static int instream_begin_read_alsa(SoundIoPrivate *si, } } else { const snd_pcm_channel_area_t *areas; - snd_pcm_uframes_t frames = isa->frames_left; + snd_pcm_uframes_t frames = *frame_count; int err; if ((err = snd_pcm_mmap_begin(isa->handle, &areas, &isa->offset, &frames)) < 0) { @@ -1622,7 +1617,7 @@ static int instream_begin_read_alsa(SoundIoPrivate *si, } isa->read_frame_count = frames; - *out_frame_count = isa->read_frame_count; + *frame_count = isa->read_frame_count; } *out_areas = isa->areas; @@ -1645,8 +1640,6 @@ 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 32bfdba..baf7009 100644 --- a/src/alsa.hpp +++ b/src/alsa.hpp @@ -49,8 +49,7 @@ struct SoundIoOutStreamAlsa { SoundIoOsThread *thread; atomic_flag thread_exit_flag; int period_size; - int frames_left; - int frames_to_write; + int write_frame_count; SoundIoChannelArea areas[SOUNDIO_MAX_CHANNELS]; }; @@ -67,7 +66,6 @@ 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 17d052d..7282591 100644 --- a/src/coreaudio.cpp +++ b/src/coreaudio.cpp @@ -857,7 +857,8 @@ static OSStatus write_callback_ca(void *userdata, AudioUnitRenderActionFlags *io osca->io_data = io_data; osca->buffer_index = 0; - outstream->write_callback(outstream, in_number_frames); + osca->frames_left = osca->callback_frames; + outstream->write_callback(outstream, osca->frames_left, osca->frames_left); osca->io_data = nullptr; return noErr; @@ -951,31 +952,35 @@ static int outstream_start_ca(struct SoundIoPrivate *si, struct SoundIoOutStream } static int outstream_begin_write_ca(struct SoundIoPrivate *si, struct SoundIoOutStreamPrivate *os, - SoundIoChannelArea **out_areas, int *out_frame_count) + SoundIoChannelArea **out_areas, int *frame_count) { SoundIoOutStream *outstream = &os->pub; SoundIoOutStreamCoreAudio *osca = &os->backend_data.coreaudio; - if (osca->buffer_index < osca->io_data->mNumberBuffers) { - AudioBuffer *audio_buffer = &osca->io_data->mBuffers[osca->buffer_index]; - assert(audio_buffer->mNumberChannels == outstream->layout.channel_count); - *out_frame_count = audio_buffer->mDataByteSize / outstream->bytes_per_frame; - assert((audio_buffer->mDataByteSize % outstream->bytes_per_frame) == 0); - for (int ch = 0; ch < outstream->layout.channel_count; ch += 1) { - osca->areas[ch].ptr = ((char*)audio_buffer->mData) + outstream->bytes_per_sample * ch; - osca->areas[ch].step = outstream->bytes_per_frame; - } - *out_areas = osca->areas; - } else { - *out_areas = nullptr; - *out_frame_count = 0; + if (osca->buffer_index >= ocsa->io_data->mNumberBuffers) + return SoundIoErrorInvalid; + + if (*frame_count != osca->frames_left) + return SoundIoErrorInvalid; + + AudioBuffer *audio_buffer = &osca->io_data->mBuffers[osca->buffer_index]; + assert(audio_buffer->mNumberChannels == outstream->layout.channel_count); + osca->write_frame_count = audio_buffer->mDataByteSize / outstream->bytes_per_frame; + *frame_count = osca->write_frame_count; + assert((audio_buffer->mDataByteSize % outstream->bytes_per_frame) == 0); + for (int ch = 0; ch < outstream->layout.channel_count; ch += 1) { + osca->areas[ch].ptr = ((char*)audio_buffer->mData) + outstream->bytes_per_sample * ch; + osca->areas[ch].step = outstream->bytes_per_frame; } + *out_areas = osca->areas; return 0; } static int outstream_end_write_ca(struct SoundIoPrivate *si, struct SoundIoOutStreamPrivate *os) { SoundIoOutStreamCoreAudio *osca = &os->backend_data.coreaudio; osca->buffer_index += 1; + osca->frames_left -= osca->write_frame_count; + assert(osca->frames_left >= 0); return 0; } diff --git a/src/coreaudio.hpp b/src/coreaudio.hpp index ab108fb..2d5af29 100644 --- a/src/coreaudio.hpp +++ b/src/coreaudio.hpp @@ -45,6 +45,8 @@ struct SoundIoOutStreamCoreAudio { AudioComponentInstance output_instance; AudioBufferList *io_data; int buffer_index; + int frames_left; + int write_frame_count; SoundIoChannelArea areas[SOUNDIO_MAX_CHANNELS]; }; diff --git a/src/dummy.cpp b/src/dummy.cpp index 5158236..0534664 100644 --- a/src/dummy.cpp +++ b/src/dummy.cpp @@ -21,7 +21,7 @@ static void playback_thread_run(void *arg) { 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); + outstream->write_callback(outstream, 0, free_frames); double start_time = soundio_os_get_time(); long frames_consumed = 0; @@ -49,12 +49,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); + outstream->write_callback(outstream, 0, 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); + outstream->write_callback(outstream, 0, free_frames); } } } @@ -94,7 +94,7 @@ static void capture_thread_run(void *arg) { } if (fill_frames > 0) { isd->frames_left = fill_frames; - instream->read_callback(instream, fill_frames); + instream->read_callback(instream, 0, fill_frames); } } } @@ -201,18 +201,20 @@ static int outstream_start_dummy(SoundIoPrivate *si, SoundIoOutStreamPrivate *os } static int outstream_begin_write_dummy(SoundIoPrivate *si, - SoundIoOutStreamPrivate *os, SoundIoChannelArea **out_areas, int *out_frame_count) + SoundIoOutStreamPrivate *os, SoundIoChannelArea **out_areas, int *frame_count) { SoundIoOutStream *outstream = &os->pub; SoundIoOutStreamDummy *osd = &os->backend_data.dummy; + assert(*frame_count <= osd->frames_left); + 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; + osd->write_frame_count = *frame_count; *out_areas = osd->areas; return 0; } @@ -220,9 +222,9 @@ static int outstream_begin_write_dummy(SoundIoPrivate *si, static int outstream_end_write_dummy(SoundIoPrivate *si, SoundIoOutStreamPrivate *os) { SoundIoOutStreamDummy *osd = &os->backend_data.dummy; SoundIoOutStream *outstream = &os->pub; - int byte_count = osd->frames_left * outstream->bytes_per_frame; + int byte_count = osd->write_frame_count * outstream->bytes_per_frame; soundio_ring_buffer_advance_write_ptr(&osd->ring_buffer, byte_count); - osd->frames_left = 0; + osd->frames_left -= osd->write_frame_count; return 0; } @@ -305,18 +307,20 @@ static int instream_start_dummy(SoundIoPrivate *si, SoundIoInStreamPrivate *is) } static int instream_begin_read_dummy(SoundIoPrivate *si, - SoundIoInStreamPrivate *is, SoundIoChannelArea **out_areas, int *out_frame_count) + SoundIoInStreamPrivate *is, SoundIoChannelArea **out_areas, int *frame_count) { SoundIoInStream *instream = &is->pub; SoundIoInStreamDummy *isd = &is->backend_data.dummy; + assert(*frame_count <= isd->frames_left); + 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; + isd->read_frame_count = *frame_count; *out_areas = isd->areas; return 0; @@ -325,9 +329,9 @@ 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; - int byte_count = isd->frames_left * instream->bytes_per_frame; + int byte_count = isd->read_frame_count * instream->bytes_per_frame; soundio_ring_buffer_advance_read_ptr(&isd->ring_buffer, byte_count); - isd->frames_left = 0; + isd->frames_left -= isd->read_frame_count; return 0; } diff --git a/src/dummy.hpp b/src/dummy.hpp index 163723c..280d676 100644 --- a/src/dummy.hpp +++ b/src/dummy.hpp @@ -31,6 +31,7 @@ struct SoundIoOutStreamDummy { atomic_flag abort_flag; int buffer_frame_count; int frames_left; + int write_frame_count; struct SoundIoRingBuffer ring_buffer; double playback_start_time; SoundIoChannelArea areas[SOUNDIO_MAX_CHANNELS]; @@ -41,6 +42,7 @@ struct SoundIoInStreamDummy { struct SoundIoOsCond *cond; atomic_flag abort_flag; int frames_left; + int read_frame_count; int buffer_frame_count; struct SoundIoRingBuffer ring_buffer; SoundIoChannelArea areas[SOUNDIO_MAX_CHANNELS]; diff --git a/src/jack.cpp b/src/jack.cpp index 4418e8e..8f51750 100644 --- a/src/jack.cpp +++ b/src/jack.cpp @@ -345,7 +345,7 @@ static int outstream_process_callback(jack_nframes_t nframes, void *arg) { 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); + outstream->write_callback(outstream, osj->frames_left, osj->frames_left); return 0; } @@ -526,11 +526,13 @@ 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 *out_frame_count) + SoundIoChannelArea **out_areas, int *frame_count) { SoundIoOutStreamJack *osj = &os->backend_data.jack; - *out_frame_count = osj->frames_left; + if (*frame_count != osj->frames_left) + return SoundIoErrorInvalid; + *out_areas = osj->areas; return 0; @@ -601,7 +603,7 @@ static int instream_process_callback(jack_nframes_t nframes, void *arg) { 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); + instream->read_callback(instream, isj->frames_left, isj->frames_left); return 0; } @@ -726,11 +728,13 @@ 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 *out_frame_count) + SoundIoChannelArea **out_areas, int *frame_count) { SoundIoInStreamJack *isj = &is->backend_data.jack; - *out_frame_count = isj->frames_left; + if (*frame_count != isj->frames_left) + return SoundIoErrorInvalid; + *out_areas = isj->areas; return 0; diff --git a/src/pulseaudio.cpp b/src/pulseaudio.cpp index b41d84a..25dea91 100644 --- a/src/pulseaudio.cpp +++ b/src/pulseaudio.cpp @@ -642,11 +642,9 @@ 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) { 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); + int frame_count = nbytes / outstream->bytes_per_frame; + outstream->write_callback(outstream, 0, frame_count); } static void outstream_destroy_pa(SoundIoPrivate *si, SoundIoOutStreamPrivate *os) { @@ -746,9 +744,9 @@ static int outstream_start_pa(SoundIoPrivate *si, SoundIoOutStreamPrivate *os) { pa_threaded_mainloop_lock(sipa->main_loop); - ospa->bytes_left = pa_stream_writable_size(ospa->stream); - int frame_count = ospa->bytes_left / outstream->bytes_per_frame; - outstream->write_callback(outstream, frame_count); + ospa->write_byte_count = pa_stream_writable_size(ospa->stream); + int frame_count = ospa->write_byte_count / outstream->bytes_per_frame; + outstream->write_callback(outstream, 0, frame_count); pa_operation *op = pa_stream_cork(ospa->stream, false, nullptr, nullptr); if (!op) { @@ -765,23 +763,14 @@ static int outstream_start_pa(SoundIoPrivate *si, SoundIoOutStreamPrivate *os) { } static int outstream_begin_write_pa(SoundIoPrivate *si, - SoundIoOutStreamPrivate *os, SoundIoChannelArea **out_areas, int *out_frame_count) + SoundIoOutStreamPrivate *os, SoundIoChannelArea **out_areas, int *frame_count) { SoundIoOutStream *outstream = &os->pub; SoundIoOutStreamPulseAudio *ospa = &os->backend_data.pulseaudio; pa_stream *stream = ospa->stream; - if (ospa->bytes_left <= 0) { - *out_frame_count = 0; - *out_areas = nullptr; - return 0; - } - - // 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)) + ospa->write_byte_count = *frame_count * outstream->bytes_per_frame; + if (pa_stream_begin_write(stream, (void**)&ospa->write_ptr, &ospa->write_byte_count)) return SoundIoErrorStreaming; for (int ch = 0; ch < outstream->layout.channel_count; ch += 1) { @@ -789,7 +778,7 @@ static int outstream_begin_write_pa(SoundIoPrivate *si, ospa->areas[ch].step = outstream->bytes_per_frame; } - *out_frame_count = ospa->byte_count / outstream->bytes_per_frame; + *frame_count = ospa->write_byte_count / outstream->bytes_per_frame; *out_areas = ospa->areas; return 0; @@ -798,9 +787,8 @@ static int outstream_begin_write_pa(SoundIoPrivate *si, static int outstream_end_write_pa(SoundIoPrivate *si, SoundIoOutStreamPrivate *os) { SoundIoOutStreamPulseAudio *ospa = &os->backend_data.pulseaudio; pa_stream *stream = ospa->stream; - if (pa_stream_write(stream, ospa->write_ptr, ospa->byte_count, nullptr, 0, PA_SEEK_RELATIVE)) + if (pa_stream_write(stream, ospa->write_ptr, ospa->write_byte_count, nullptr, 0, PA_SEEK_RELATIVE)) return SoundIoErrorStreaming; - ospa->bytes_left -= ospa->byte_count; return 0; } @@ -866,12 +854,10 @@ 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); - ispa->bytes_left = nbytes; - int available_frame_count = ispa->bytes_left / instream->bytes_per_frame; - instream->read_callback(instream, available_frame_count); + int available_frame_count = nbytes / instream->bytes_per_frame; + instream->read_callback(instream, 0, available_frame_count); } static void instream_destroy_pa(SoundIoPrivate *si, SoundIoInStreamPrivate *is) { @@ -980,7 +966,7 @@ static int instream_start_pa(SoundIoPrivate *si, SoundIoInStreamPrivate *is) { } static int instream_begin_read_pa(SoundIoPrivate *si, - SoundIoInStreamPrivate *is, SoundIoChannelArea **out_areas, int *out_frame_count) + SoundIoInStreamPrivate *is, SoundIoChannelArea **out_areas, int *frame_count) { SoundIoInStream *instream = &is->pub; SoundIoInStreamPulseAudio *ispa = &is->backend_data.pulseaudio; @@ -988,42 +974,56 @@ static int instream_begin_read_pa(SoundIoPrivate *si, assert(ispa->stream_ready); - if (ispa->bytes_left <= 0) { - *out_frame_count = 0; - *out_areas = nullptr; - 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; + if (!ispa->peek_buf) { + if (pa_stream_peek(stream, (const void **)&ispa->peek_buf, &ispa->peek_buf_size)) { + soundio_panic("TODO"); } - *out_areas = ispa->areas; - } else { - *out_areas = nullptr; + ispa->peek_buf_frames_left = ispa->peek_buf_size / instream->bytes_per_frame; + ispa->peek_buf_index = 0; + + // hole + if (!ispa->peek_buf) { + *frame_count = ispa->peek_buf_frames_left; + *out_areas = nullptr; + return 0; + } } + ispa->read_frame_count = min(*frame_count, ispa->peek_buf_frames_left); + *frame_count = ispa->read_frame_count; + for (int ch = 0; ch < instream->layout.channel_count; ch += 1) { + ispa->areas[ch].ptr = ispa->peek_buf + ispa->peek_buf_index + instream->bytes_per_sample * ch; + ispa->areas[ch].step = instream->bytes_per_frame; + } + + *out_areas = ispa->areas; + return 0; } static int instream_end_read_pa(SoundIoPrivate *si, SoundIoInStreamPrivate *is) { + SoundIoInStream *instream = &is->pub; SoundIoInStreamPulseAudio *ispa = &is->backend_data.pulseaudio; pa_stream *stream = ispa->stream; - if (pa_stream_drop(stream)) - return SoundIoErrorStreaming; - ispa->bytes_left -= ispa->byte_count; + + // hole + if (!ispa->peek_buf) { + if (pa_stream_drop(stream)) + return SoundIoErrorStreaming; + return 0; + } + + size_t advance_bytes = ispa->read_frame_count * instream->bytes_per_frame; + ispa->peek_buf_index += advance_bytes; + ispa->peek_buf_frames_left -= ispa->read_frame_count; + + if (ispa->peek_buf_index >= ispa->peek_buf_size) { + if (pa_stream_drop(stream)) + return SoundIoErrorStreaming; + ispa->peek_buf = nullptr; + } + return 0; } diff --git a/src/pulseaudio.hpp b/src/pulseaudio.hpp index 22a4def..8ea0c4a 100644 --- a/src/pulseaudio.hpp +++ b/src/pulseaudio.hpp @@ -48,8 +48,7 @@ struct SoundIoOutStreamPulseAudio { atomic_bool stream_ready; pa_buffer_attr buffer_attr; char *write_ptr; - size_t byte_count; - int bytes_left; + size_t write_byte_count; SoundIoChannelArea areas[SOUNDIO_MAX_CHANNELS]; }; @@ -57,8 +56,11 @@ struct SoundIoInStreamPulseAudio { pa_stream *stream; atomic_bool stream_ready; pa_buffer_attr buffer_attr; - size_t byte_count; - int bytes_left; + char *peek_buf; + size_t peek_buf_index; + size_t peek_buf_size; + int peek_buf_frames_left; + int read_frame_count; SoundIoChannelArea areas[SOUNDIO_MAX_CHANNELS]; }; diff --git a/src/soundio.cpp b/src/soundio.cpp index 90cd407..ac01d17 100644 --- a/src/soundio.cpp +++ b/src/soundio.cpp @@ -393,6 +393,7 @@ 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); } diff --git a/test/underflow.c b/test/underflow.c index 2cf41a6..9886a49 100644 --- a/test/underflow.c +++ b/test/underflow.c @@ -38,11 +38,10 @@ static struct SoundIoOsCond *cond = NULL; static struct SoundIo *soundio = NULL; static float seconds_end = 9.0f; -static void write_callback(struct SoundIoOutStream *outstream, int requested_frame_count) { +static void write_callback(struct SoundIoOutStream *outstream, int frame_count_min, int frame_count_max) { 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) { @@ -55,7 +54,10 @@ static void write_callback(struct SoundIoOutStream *outstream, int requested_fra return; } + int frames_left = frame_count_max; + for (;;) { + int frame_count = frames_left; if ((err = soundio_outstream_begin_write(outstream, &areas, &frame_count))) panic("%s", soundio_strerror(err)); @@ -80,6 +82,10 @@ static void write_callback(struct SoundIoOutStream *outstream, int requested_fra return; panic("%s", soundio_strerror(err)); } + + frames_left -= frame_count; + if (frames_left <= 0) + break; } } diff --git a/test/unit_tests.cpp b/test/unit_tests.cpp index 2484a04..f87f29d 100644 --- a/test/unit_tests.cpp +++ b/test/unit_tests.cpp @@ -23,7 +23,7 @@ static void test_os_get_time(void) { } } -static void write_callback(struct SoundIoOutStream *device, int frame_count) { } +static void write_callback(struct SoundIoOutStream *device, int frame_count_min, int frame_count_max) { } static void error_callback(struct SoundIoOutStream *device, int err) { } static void test_create_outstream(void) {