flesh out buffer semantics

This commit is contained in:
Andrew Kelley 2015-07-29 21:50:12 -07:00
parent 44569708a0
commit 2900616e9b
6 changed files with 60 additions and 25 deletions

View file

@ -252,6 +252,11 @@ view `coverage/index.html` in a browser.
0. Verify that JACK xrun callback context is the same as process callback. 0. Verify that JACK xrun callback context is the same as process callback.
If not, might need to hav xrun callback set a flag and have process callback If not, might need to hav xrun callback set a flag and have process callback
call the underflow callback. call the underflow callback.
0. In PulseAudio, to get buffer duration and period duration, fill the buffer
with silence before starting, start the stream corked, and have the
callback be a callback that just provides silence. Once
`soundio_outstream_start` is called, switch to the real callback, then call
`pa_stream_flush`, then uncork the stream.
0. Detect PulseAudio server going offline and emit `on_backend_disconnect`. 0. Detect PulseAudio server going offline and emit `on_backend_disconnect`.
0. Instead fo open(), start(), pause(), open() starts it and it starts paused. 0. Instead fo open(), start(), pause(), open() starts it and it starts paused.
0. Create a test for pausing and resuming input and output streams. 0. Create a test for pausing and resuming input and output streams.

View file

@ -13,6 +13,8 @@
#include <string.h> #include <string.h>
#include <math.h> #include <math.h>
static const double microphone_latency = 0.2; // seconds
static enum SoundIoFormat prioritized_formats[] = { static enum SoundIoFormat prioritized_formats[] = {
SoundIoFormatFloat32NE, SoundIoFormatFloat32NE,
SoundIoFormatFloat32FE, SoundIoFormatFloat32FE,
@ -188,6 +190,9 @@ int main(int argc, char **argv) {
int err = (backend == SoundIoBackendNone) ? int err = (backend == SoundIoBackendNone) ?
soundio_connect(soundio) : soundio_connect_backend(soundio, backend); soundio_connect(soundio) : soundio_connect_backend(soundio, backend);
if (err)
panic("error connecting: %s", soundio_strerror(err));
int default_out_device_index = soundio_default_output_device_index(soundio); int default_out_device_index = soundio_default_output_device_index(soundio);
if (default_out_device_index < 0) if (default_out_device_index < 0)
@ -274,8 +279,7 @@ int main(int argc, char **argv) {
instream->format = *fmt; instream->format = *fmt;
instream->sample_rate = sample_rate; instream->sample_rate = sample_rate;
instream->layout = *layout; instream->layout = *layout;
instream->buffer_duration = 1.0; instream->period_duration = microphone_latency / 4.0;
instream->period_duration = 0.05;
instream->read_callback = read_callback; instream->read_callback = read_callback;
if ((err = soundio_instream_open(instream))) if ((err = soundio_instream_open(instream)))
@ -287,20 +291,19 @@ int main(int argc, char **argv) {
outstream->format = *fmt; outstream->format = *fmt;
outstream->sample_rate = sample_rate; outstream->sample_rate = sample_rate;
outstream->layout = *layout; outstream->layout = *layout;
outstream->buffer_duration = 0.2; outstream->buffer_duration = microphone_latency;
outstream->period_duration = 0.1;
outstream->write_callback = write_callback; outstream->write_callback = write_callback;
outstream->underflow_callback = underflow_callback; outstream->underflow_callback = underflow_callback;
if ((err = soundio_outstream_open(outstream))) if ((err = soundio_outstream_open(outstream)))
panic("unable to open output stream: %s", soundio_strerror(err)); panic("unable to open output stream: %s", soundio_strerror(err));
int capacity = fmax(1.0, instream->buffer_duration) * instream->sample_rate * instream->bytes_per_frame; int capacity = microphone_latency * 2 * instream->sample_rate * instream->bytes_per_frame;
ring_buffer = soundio_ring_buffer_create(soundio, capacity); ring_buffer = soundio_ring_buffer_create(soundio, capacity);
if (!ring_buffer) if (!ring_buffer)
panic("unable to create ring buffer: out of memory"); panic("unable to create ring buffer: out of memory");
char *buf = soundio_ring_buffer_write_ptr(ring_buffer); char *buf = soundio_ring_buffer_write_ptr(ring_buffer);
int fill_count = 0.2 * outstream->sample_rate * outstream->bytes_per_frame; int fill_count = microphone_latency * outstream->sample_rate * outstream->bytes_per_frame;
memset(buf, 0, fill_count); memset(buf, 0, fill_count);
soundio_ring_buffer_advance_write_ptr(ring_buffer, fill_count); soundio_ring_buffer_advance_write_ptr(ring_buffer, fill_count);

View file

@ -182,13 +182,10 @@ static int refresh_devices_bare(SoundIoPrivate *si) {
SoundIoDevice *device = &dev->pub; SoundIoDevice *device = &dev->pub;
SoundIoDeviceJack *dj = &dev->backend_data.jack; SoundIoDeviceJack *dj = &dev->backend_data.jack;
int description_len = client->name_len + 3 + 2 * client->port_count; int description_len = client->name_len + 3 + 2 * client->port_count;
jack_nframes_t max_buffer_frames = 0;
for (int port_index = 0; port_index < client->port_count; port_index += 1) { for (int port_index = 0; port_index < client->port_count; port_index += 1) {
SoundIoJackPort *port = &client->ports[port_index]; SoundIoJackPort *port = &client->ports[port_index];
description_len += port->name_len; description_len += port->name_len;
max_buffer_frames = max(max_buffer_frames, port->latency_range.max);
} }
dev->destruct = destruct_device; dev->destruct = destruct_device;
@ -210,9 +207,9 @@ static int refresh_devices_bare(SoundIoPrivate *si) {
device->period_duration_min = sij->period_size / (double) sij->sample_rate; device->period_duration_min = sij->period_size / (double) sij->sample_rate;
device->period_duration_max = device->period_duration_min; device->period_duration_max = device->period_duration_min;
device->period_duration_current = device->period_duration_min; device->period_duration_current = device->period_duration_min;
device->buffer_duration_min = max_buffer_frames / (double) sij->sample_rate; device->buffer_duration_min = device->period_duration_min;
device->buffer_duration_max = device->buffer_duration_min; device->buffer_duration_max = device->period_duration_min;
device->buffer_duration_current = device->buffer_duration_min; device->buffer_duration_current = device->period_duration_min;
dj->port_count = client->port_count; dj->port_count = client->port_count;
dj->ports = allocate<SoundIoDeviceJackPort>(dj->port_count); dj->ports = allocate<SoundIoDeviceJackPort>(dj->port_count);

View file

@ -838,8 +838,14 @@ static int instream_open_pa(SoundIoPrivate *si, SoundIoInStreamPrivate *is) {
ispa->buffer_attr.minreq = UINT32_MAX; ispa->buffer_attr.minreq = UINT32_MAX;
ispa->buffer_attr.fragsize = UINT32_MAX; ispa->buffer_attr.fragsize = UINT32_MAX;
int bytes_per_second = instream->bytes_per_frame * instream->sample_rate;
if (instream->buffer_duration > 0.0) {
int buffer_length = instream->bytes_per_frame *
ceil(instream->buffer_duration * bytes_per_second / (double)instream->bytes_per_frame);
ispa->buffer_attr.maxlength = buffer_length;
}
if (instream->period_duration > 0.0) { if (instream->period_duration > 0.0) {
int bytes_per_second = instream->bytes_per_frame * instream->sample_rate;
int buffer_length = instream->bytes_per_frame * int buffer_length = instream->bytes_per_frame *
ceil(instream->period_duration * bytes_per_second / (double)instream->bytes_per_frame); ceil(instream->period_duration * bytes_per_second / (double)instream->bytes_per_frame);
ispa->buffer_attr.fragsize = buffer_length; ispa->buffer_attr.fragsize = buffer_length;
@ -872,6 +878,8 @@ static int instream_start_pa(SoundIoPrivate *si, SoundIoInStreamPrivate *is) {
const pa_buffer_attr *attr = pa_stream_get_buffer_attr(ispa->stream); const pa_buffer_attr *attr = pa_stream_get_buffer_attr(ispa->stream);
instream->buffer_duration = (attr->maxlength / instream->buffer_duration = (attr->maxlength /
(double)instream->bytes_per_frame) / (double)instream->sample_rate; (double)instream->bytes_per_frame) / (double)instream->sample_rate;
instream->period_duration = (attr->fragsize /
(double)instream->bytes_per_frame) / (double)instream->sample_rate;
pa_threaded_mainloop_unlock(sipa->main_loop); pa_threaded_mainloop_unlock(sipa->main_loop);
return 0; return 0;

View file

@ -193,6 +193,7 @@ int soundio_connect_backend(SoundIo *soundio, SoundIoBackend backend) {
soundio_disconnect(soundio); soundio_disconnect(soundio);
return err; return err;
} }
soundio->current_backend = backend;
return 0; return 0;
} }
@ -229,11 +230,13 @@ void soundio_disconnect(struct SoundIo *soundio) {
} }
void soundio_flush_events(struct SoundIo *soundio) { void soundio_flush_events(struct SoundIo *soundio) {
assert(soundio->current_backend != SoundIoBackendNone);
SoundIoPrivate *si = (SoundIoPrivate *)soundio; SoundIoPrivate *si = (SoundIoPrivate *)soundio;
si->flush_events(si); si->flush_events(si);
} }
int soundio_input_device_count(struct SoundIo *soundio) { int soundio_input_device_count(struct SoundIo *soundio) {
assert(soundio->current_backend != SoundIoBackendNone);
SoundIoPrivate *si = (SoundIoPrivate *)soundio; SoundIoPrivate *si = (SoundIoPrivate *)soundio;
if (!si->safe_devices_info) if (!si->safe_devices_info)
soundio_flush_events(soundio); soundio_flush_events(soundio);
@ -242,6 +245,7 @@ int soundio_input_device_count(struct SoundIo *soundio) {
} }
int soundio_output_device_count(struct SoundIo *soundio) { int soundio_output_device_count(struct SoundIo *soundio) {
assert(soundio->current_backend != SoundIoBackendNone);
SoundIoPrivate *si = (SoundIoPrivate *)soundio; SoundIoPrivate *si = (SoundIoPrivate *)soundio;
if (!si->safe_devices_info) if (!si->safe_devices_info)
soundio_flush_events(soundio); soundio_flush_events(soundio);
@ -250,6 +254,7 @@ int soundio_output_device_count(struct SoundIo *soundio) {
} }
int soundio_default_input_device_index(struct SoundIo *soundio) { int soundio_default_input_device_index(struct SoundIo *soundio) {
assert(soundio->current_backend != SoundIoBackendNone);
SoundIoPrivate *si = (SoundIoPrivate *)soundio; SoundIoPrivate *si = (SoundIoPrivate *)soundio;
if (!si->safe_devices_info) if (!si->safe_devices_info)
soundio_flush_events(soundio); soundio_flush_events(soundio);
@ -258,6 +263,7 @@ int soundio_default_input_device_index(struct SoundIo *soundio) {
} }
int soundio_default_output_device_index(struct SoundIo *soundio) { int soundio_default_output_device_index(struct SoundIo *soundio) {
assert(soundio->current_backend != SoundIoBackendNone);
SoundIoPrivate *si = (SoundIoPrivate *)soundio; SoundIoPrivate *si = (SoundIoPrivate *)soundio;
if (!si->safe_devices_info) if (!si->safe_devices_info)
soundio_flush_events(soundio); soundio_flush_events(soundio);
@ -266,6 +272,7 @@ int soundio_default_output_device_index(struct SoundIo *soundio) {
} }
struct SoundIoDevice *soundio_get_input_device(struct SoundIo *soundio, int index) { struct SoundIoDevice *soundio_get_input_device(struct SoundIo *soundio, int index) {
assert(soundio->current_backend != SoundIoBackendNone);
SoundIoPrivate *si = (SoundIoPrivate *)soundio; SoundIoPrivate *si = (SoundIoPrivate *)soundio;
assert(si->safe_devices_info); assert(si->safe_devices_info);
assert(index >= 0); assert(index >= 0);
@ -276,6 +283,7 @@ struct SoundIoDevice *soundio_get_input_device(struct SoundIo *soundio, int inde
} }
struct SoundIoDevice *soundio_get_output_device(struct SoundIo *soundio, int index) { struct SoundIoDevice *soundio_get_output_device(struct SoundIo *soundio, int index) {
assert(soundio->current_backend != SoundIoBackendNone);
SoundIoPrivate *si = (SoundIoPrivate *)soundio; SoundIoPrivate *si = (SoundIoPrivate *)soundio;
assert(si->safe_devices_info); assert(si->safe_devices_info);
assert(index >= 0); assert(index >= 0);

View file

@ -287,15 +287,16 @@ struct SoundIoDevice {
int sample_rate_current; int sample_rate_current;
// Buffer duration in seconds. If any values are unknown, they are set to // Buffer duration in seconds. If any values are unknown, they are set to
// 0.0. These values are unknown for PulseAudio. These values are sometimes // 0.0. These values are unknown for PulseAudio. For JACK, buffer duration
// unknown for JACK. // and period duration are the same.
double buffer_duration_min; double buffer_duration_min;
double buffer_duration_max; double buffer_duration_max;
double buffer_duration_current; double buffer_duration_current;
// Period duration in seconds. After this much time passes, write_callback // Period duration in seconds. After this much time passes, write_callback
// is called. If values are unknown, they are set to 0.0. These values are // is called. If values are unknown, they are set to 0.0. These values are
// unknown for PulseAudio. // meaningless for PulseAudio. For JACK, buffer duration and period duration
// are the same.
double period_duration_min; double period_duration_min;
double period_duration_max; double period_duration_max;
double period_duration_current; double period_duration_current;
@ -338,13 +339,14 @@ struct SoundIoOutStream {
struct SoundIoChannelLayout layout; struct SoundIoChannelLayout layout;
// Buffer duration in seconds. // Buffer duration in seconds.
// After you call soundio_outstream_open this value is replaced with the // After you call `soundio_outstream_open` this value is replaced with the
// actual duration, as near to this value as possible. // actual duration, as near to this value as possible.
// Defaults to 1 second (and then clamped into range). // Defaults to 1 second (and then clamped into range).
// If the device has unknown buffer duration min and max values, you may // If the device has unknown buffer duration min and max values, you may
// still set this. If you set this and the backend is PulseAudio, it // still set this. If you set this and the backend is PulseAudio, it
// sets `PA_STREAM_ADJUST_LATENCY` and is the value used for `maxlength` // sets `PA_STREAM_ADJUST_LATENCY` and is the value used for `maxlength`
// and `tlength`. // and `tlength`. With PulseAudio, this value is not replaced with the
// actual duration until `soundio_outstream_start`.
double buffer_duration; double buffer_duration;
// `period_duration` is the latency; how much time it takes // `period_duration` is the latency; how much time it takes
@ -353,8 +355,7 @@ struct SoundIoOutStream {
// actual period duration, as near to this value as possible. // actual period duration, as near to this value as possible.
// Defaults to `buffer_duration / 2` (and then clamped into range). // Defaults to `buffer_duration / 2` (and then clamped into range).
// If the device has unknown period duration min and max values, you may // If the device has unknown period duration min and max values, you may
// still set this. If you set this and the backend is PulseAudio, it // still set this. This value is meaningless for PulseAudio.
// sets `PA_STREAM_ADJUST_LATENCY` and is the value used for `fragsize`.
double period_duration; double period_duration;
// Defaults to NULL. Put whatever you want here. // Defaults to NULL. Put whatever you want here.
@ -413,14 +414,27 @@ struct SoundIoInStream {
// Defaults to Stereo, if available, followed by the first layout supported. // Defaults to Stereo, if available, followed by the first layout supported.
struct SoundIoChannelLayout layout; struct SoundIoChannelLayout layout;
// Buffer duration in seconds. If the captured audio frames exceeds this // Buffer duration in seconds.
// before they are read, a buffer overrun occurs and the frames are lost. // After you call `soundio_instream_open` this value is replaced with the
// Defaults to 1 second (and then clamped into range). // actual duration, as near to this value as possible.
// If the captured audio frames exceeds this before they are read, a buffer
// overrun occurs and the frames are lost.
// Defaults to 1 second (and then clamped into range). For PulseAudio,
// defaults to PulseAudio's default value, usually large. If you set this
// and the backend is PulseAudio, it sets `PA_STREAM_ADJUST_LATENCY` and
// is the value used for `maxlength`. With PulseAudio, this value is not
// replaced with the actual duration until `soundio_instream_start`.
double buffer_duration; double buffer_duration;
// The latency of the captured audio. After this many seconds pass, // The latency of the captured audio.
// `read_callback` is called. // After you call `soundio_instream_open` this value is replaced with the
// actual duration, as near to this value as possible.
// After this many seconds pass, `read_callback` is called.
// Defaults to `buffer_duration / 8`. // Defaults to `buffer_duration / 8`.
// If you set this and the backend is PulseAudio, it sets
// `PA_STREAM_ADJUST_LATENCY` and is the value used for `fragsize`.
// With PulseAudio, this value is not replaced with the actual duration
// until `soundio_instream_start`.
double period_duration; double period_duration;
// Defaults to NULL. Put whatever you want here. // Defaults to NULL. Put whatever you want here.