callbacks supply min and max frame count parameters

This changes the semantics of the callbacks so that instead of
a single `requested_frame_count` or `available_frame_count`,
the callbacks get a minimum frame count and maximum frame count.

The callback must write at least the minimum or get an underflow.
The minimum will be 0 on ALSA, PulseAudio, and Dummy, and will
equal the maximum on CoreAudio and JACK.

This ensures optimal behavior regardless of buffer size.
This commit is contained in:
Andrew Kelley 2015-08-04 21:57:46 -07:00
parent 8da5ae0798
commit c381526205
16 changed files with 216 additions and 155 deletions

View file

@ -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/).

View file

@ -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) {

View file

@ -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;
}
}

View file

@ -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

View file

@ -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;
}

View file

@ -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];
};

View file

@ -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;
}

View file

@ -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];
};

View file

@ -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;
}

View file

@ -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];

View file

@ -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;

View file

@ -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;
}

View file

@ -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];
};

View file

@ -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);
}

View file

@ -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;
}
}

View file

@ -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) {