diff --git a/README.md b/README.md index da59e14..ef395a4 100644 --- a/README.md +++ b/README.md @@ -252,6 +252,11 @@ view `coverage/index.html` in a browser. 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 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. Instead fo open(), start(), pause(), open() starts it and it starts paused. 0. Create a test for pausing and resuming input and output streams. diff --git a/example/microphone.c b/example/microphone.c index 77a8740..a56667f 100644 --- a/example/microphone.c +++ b/example/microphone.c @@ -13,6 +13,8 @@ #include #include +static const double microphone_latency = 0.2; // seconds + static enum SoundIoFormat prioritized_formats[] = { SoundIoFormatFloat32NE, SoundIoFormatFloat32FE, @@ -188,6 +190,9 @@ int main(int argc, char **argv) { int err = (backend == SoundIoBackendNone) ? 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); if (default_out_device_index < 0) @@ -274,8 +279,7 @@ int main(int argc, char **argv) { instream->format = *fmt; instream->sample_rate = sample_rate; instream->layout = *layout; - instream->buffer_duration = 1.0; - instream->period_duration = 0.05; + instream->period_duration = microphone_latency / 4.0; instream->read_callback = read_callback; if ((err = soundio_instream_open(instream))) @@ -287,20 +291,19 @@ int main(int argc, char **argv) { outstream->format = *fmt; outstream->sample_rate = sample_rate; outstream->layout = *layout; - outstream->buffer_duration = 0.2; - outstream->period_duration = 0.1; + outstream->buffer_duration = microphone_latency; outstream->write_callback = write_callback; outstream->underflow_callback = underflow_callback; if ((err = soundio_outstream_open(outstream))) 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); if (!ring_buffer) panic("unable to create ring buffer: out of memory"); 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); soundio_ring_buffer_advance_write_ptr(ring_buffer, fill_count); diff --git a/src/jack.cpp b/src/jack.cpp index a368383..fc3e186 100644 --- a/src/jack.cpp +++ b/src/jack.cpp @@ -182,13 +182,10 @@ static int refresh_devices_bare(SoundIoPrivate *si) { SoundIoDevice *device = &dev->pub; SoundIoDeviceJack *dj = &dev->backend_data.jack; 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) { SoundIoJackPort *port = &client->ports[port_index]; description_len += port->name_len; - - max_buffer_frames = max(max_buffer_frames, port->latency_range.max); } 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_max = 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_max = device->buffer_duration_min; - device->buffer_duration_current = device->buffer_duration_min; + device->buffer_duration_min = device->period_duration_min; + device->buffer_duration_max = device->period_duration_min; + device->buffer_duration_current = device->period_duration_min; dj->port_count = client->port_count; dj->ports = allocate(dj->port_count); diff --git a/src/pulseaudio.cpp b/src/pulseaudio.cpp index e8ca94c..e993e93 100644 --- a/src/pulseaudio.cpp +++ b/src/pulseaudio.cpp @@ -838,8 +838,14 @@ static int instream_open_pa(SoundIoPrivate *si, SoundIoInStreamPrivate *is) { ispa->buffer_attr.minreq = 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) { - int bytes_per_second = instream->bytes_per_frame * instream->sample_rate; int buffer_length = instream->bytes_per_frame * ceil(instream->period_duration * bytes_per_second / (double)instream->bytes_per_frame); ispa->buffer_attr.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); instream->buffer_duration = (attr->maxlength / (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); return 0; diff --git a/src/soundio.cpp b/src/soundio.cpp index 6f48549..f2f3a28 100644 --- a/src/soundio.cpp +++ b/src/soundio.cpp @@ -193,6 +193,7 @@ int soundio_connect_backend(SoundIo *soundio, SoundIoBackend backend) { soundio_disconnect(soundio); return err; } + soundio->current_backend = backend; return 0; } @@ -229,11 +230,13 @@ void soundio_disconnect(struct SoundIo *soundio) { } void soundio_flush_events(struct SoundIo *soundio) { + assert(soundio->current_backend != SoundIoBackendNone); SoundIoPrivate *si = (SoundIoPrivate *)soundio; si->flush_events(si); } int soundio_input_device_count(struct SoundIo *soundio) { + assert(soundio->current_backend != SoundIoBackendNone); SoundIoPrivate *si = (SoundIoPrivate *)soundio; if (!si->safe_devices_info) soundio_flush_events(soundio); @@ -242,6 +245,7 @@ int soundio_input_device_count(struct SoundIo *soundio) { } int soundio_output_device_count(struct SoundIo *soundio) { + assert(soundio->current_backend != SoundIoBackendNone); SoundIoPrivate *si = (SoundIoPrivate *)soundio; if (!si->safe_devices_info) 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) { + assert(soundio->current_backend != SoundIoBackendNone); SoundIoPrivate *si = (SoundIoPrivate *)soundio; if (!si->safe_devices_info) 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) { + assert(soundio->current_backend != SoundIoBackendNone); SoundIoPrivate *si = (SoundIoPrivate *)soundio; if (!si->safe_devices_info) 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) { + assert(soundio->current_backend != SoundIoBackendNone); SoundIoPrivate *si = (SoundIoPrivate *)soundio; assert(si->safe_devices_info); 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) { + assert(soundio->current_backend != SoundIoBackendNone); SoundIoPrivate *si = (SoundIoPrivate *)soundio; assert(si->safe_devices_info); assert(index >= 0); diff --git a/src/soundio.h b/src/soundio.h index b4bc500..b930835 100644 --- a/src/soundio.h +++ b/src/soundio.h @@ -287,15 +287,16 @@ struct SoundIoDevice { int sample_rate_current; // 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 - // unknown for JACK. + // 0.0. These values are unknown for PulseAudio. For JACK, buffer duration + // and period duration are the same. double buffer_duration_min; double buffer_duration_max; double buffer_duration_current; // 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 - // unknown for PulseAudio. + // meaningless for PulseAudio. For JACK, buffer duration and period duration + // are the same. double period_duration_min; double period_duration_max; double period_duration_current; @@ -338,13 +339,14 @@ struct SoundIoOutStream { struct SoundIoChannelLayout layout; // 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. // Defaults to 1 second (and then clamped into range). // 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 // 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; // `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. // Defaults to `buffer_duration / 2` (and then clamped into range). // 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 - // sets `PA_STREAM_ADJUST_LATENCY` and is the value used for `fragsize`. + // still set this. This value is meaningless for PulseAudio. double period_duration; // 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. struct SoundIoChannelLayout layout; - // Buffer duration in seconds. 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). + // Buffer duration in seconds. + // After you call `soundio_instream_open` this value is replaced with the + // 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; - // The latency of the captured audio. After this many seconds pass, - // `read_callback` is called. + // The latency of the captured audio. + // 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`. + // 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; // Defaults to NULL. Put whatever you want here.