diff --git a/README.md b/README.md index 70f73f0..bdbb51e 100644 --- a/README.md +++ b/README.md @@ -289,12 +289,6 @@ view `coverage/index.html` in a browser. 0. write detailed docs on buffer underflows explaining when they occur, what state changes are related to them, and how to recover from them. 0. Consider testing on FreeBSD - 0. PulseAudio idea: when prebuf gets set to 0 need to pass `PA_STREAM_START_CORKED`. - 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. In ALSA do we need to wake up the poll when destroying the in or out stream? 0. Detect PulseAudio server going offline and emit `on_backend_disconnect`. diff --git a/soundio/soundio.h b/soundio/soundio.h index f8585a8..ca93083 100644 --- a/soundio/soundio.h +++ b/soundio/soundio.h @@ -403,8 +403,7 @@ struct SoundIoOutStream { // still set this, but you might not get the value you requested. If you // set this and the backend is PulseAudio, it sets // `PA_STREAM_ADJUST_LATENCY` and is the value used for `maxlength` and - // `tlength`. With PulseAudio, this value is not replaced with the actual - // duration until `soundio_outstream_start`. + // `tlength`. double buffer_duration; // `period_duration` is the latency; how much time it takes diff --git a/src/pulseaudio.cpp b/src/pulseaudio.cpp index 75f8a27..b41d84a 100644 --- a/src/pulseaudio.cpp +++ b/src/pulseaudio.cpp @@ -680,7 +680,7 @@ static int outstream_open_pa(SoundIoPrivate *si, SoundIoOutStreamPrivate *os) { return SoundIoErrorIncompatibleBackend; SoundIoPulseAudio *sipa = &si->backend_data.pulseaudio; - ospa->stream_ready = false; + ospa->stream_ready.store(false); assert(sipa->pulse_context); @@ -700,8 +700,6 @@ static int outstream_open_pa(SoundIoPrivate *si, SoundIoOutStreamPrivate *os) { return SoundIoErrorNoMem; } pa_stream_set_state_callback(ospa->stream, playback_stream_state_callback, os); - pa_stream_set_write_callback(ospa->stream, playback_stream_write_callback, os); - pa_stream_set_underflow_callback(ospa->stream, playback_stream_underflow_callback, outstream); ospa->buffer_attr.maxlength = UINT32_MAX; ospa->buffer_attr.tlength = UINT32_MAX; @@ -718,6 +716,24 @@ static int outstream_open_pa(SoundIoPrivate *si, SoundIoOutStreamPrivate *os) { ospa->buffer_attr.tlength = buffer_length; } + pa_stream_flags_t flags = PA_STREAM_START_CORKED; + if (outstream->buffer_duration > 0.0) + flags = (pa_stream_flags_t) (flags | PA_STREAM_ADJUST_LATENCY); + + int err = pa_stream_connect_playback(ospa->stream, + outstream->device->id, &ospa->buffer_attr, + flags, nullptr, nullptr); + if (err) { + pa_threaded_mainloop_unlock(sipa->main_loop); + return SoundIoErrorOpeningDevice; + } + + while (!ospa->stream_ready.load()) + pa_threaded_mainloop_wait(sipa->main_loop); + + size_t writable_size = pa_stream_writable_size(ospa->stream); + outstream->buffer_duration = writable_size / bytes_per_second; + pa_threaded_mainloop_unlock(sipa->main_loop); return 0; @@ -730,22 +746,18 @@ static int outstream_start_pa(SoundIoPrivate *si, SoundIoOutStreamPrivate *os) { pa_threaded_mainloop_lock(sipa->main_loop); - pa_stream_flags_t flags = (outstream->buffer_duration > 0.0) ? PA_STREAM_ADJUST_LATENCY : PA_STREAM_NOFLAGS; + 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); - int err = pa_stream_connect_playback(ospa->stream, - outstream->device->id, &ospa->buffer_attr, - flags, nullptr, nullptr); - if (err) { + pa_operation *op = pa_stream_cork(ospa->stream, false, nullptr, nullptr); + if (!op) { pa_threaded_mainloop_unlock(sipa->main_loop); - return SoundIoErrorOpeningDevice; + return SoundIoErrorStreaming; } - - while (!ospa->stream_ready) - pa_threaded_mainloop_wait(sipa->main_loop); - - const pa_buffer_attr *attr = pa_stream_get_buffer_attr(ospa->stream); - outstream->buffer_duration = (attr->maxlength / - (double)outstream->bytes_per_frame) / (double)outstream->sample_rate; + pa_operation_unref(op); + pa_stream_set_write_callback(ospa->stream, playback_stream_write_callback, os); + pa_stream_set_underflow_callback(ospa->stream, playback_stream_underflow_callback, outstream); pa_threaded_mainloop_unlock(sipa->main_loop); @@ -800,8 +812,10 @@ static int outstream_clear_buffer_pa(SoundIoPrivate *si, pa_stream *stream = ospa->stream; pa_threaded_mainloop_lock(sipa->main_loop); pa_operation *op = pa_stream_flush(stream, NULL, NULL); - if (!op) + if (!op) { + pa_threaded_mainloop_unlock(sipa->main_loop); return SoundIoErrorStreaming; + } pa_operation_unref(op); pa_threaded_mainloop_unlock(sipa->main_loop); return 0; @@ -815,8 +829,10 @@ static int outstream_pause_pa(SoundIoPrivate *si, SoundIoOutStreamPrivate *os, b if (pause != pa_stream_is_corked(ospa->stream)) { pa_operation *op = pa_stream_cork(ospa->stream, pause, NULL, NULL); - if (!op) + if (!op) { + pa_threaded_mainloop_unlock(sipa->main_loop); return SoundIoErrorStreaming; + } pa_operation_unref(op); }