PulseAudio: opening out stream populates buffer_duration

This commit is contained in:
Andrew Kelley 2015-08-04 13:09:45 -07:00
parent a378cac92d
commit 8da5ae0798
3 changed files with 35 additions and 26 deletions

View file

@ -289,12 +289,6 @@ view `coverage/index.html` in a browser.
0. write detailed docs on buffer underflows explaining when they occur, what state 0. write detailed docs on buffer underflows explaining when they occur, what state
changes are related to them, and how to recover from them. changes are related to them, and how to recover from them.
0. Consider testing on FreeBSD 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. 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`. 0. Detect PulseAudio server going offline and emit `on_backend_disconnect`.

View file

@ -403,8 +403,7 @@ struct SoundIoOutStream {
// still set this, but you might not get the value you requested. If you // still set this, but you might not get the value you requested. If you
// set this and the backend is PulseAudio, it sets // set this and the backend is PulseAudio, it sets
// `PA_STREAM_ADJUST_LATENCY` and is the value used for `maxlength` and // `PA_STREAM_ADJUST_LATENCY` and is the value used for `maxlength` and
// `tlength`. With PulseAudio, this value is not replaced with the actual // `tlength`.
// 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

View file

@ -680,7 +680,7 @@ static int outstream_open_pa(SoundIoPrivate *si, SoundIoOutStreamPrivate *os) {
return SoundIoErrorIncompatibleBackend; return SoundIoErrorIncompatibleBackend;
SoundIoPulseAudio *sipa = &si->backend_data.pulseaudio; SoundIoPulseAudio *sipa = &si->backend_data.pulseaudio;
ospa->stream_ready = false; ospa->stream_ready.store(false);
assert(sipa->pulse_context); assert(sipa->pulse_context);
@ -700,8 +700,6 @@ static int outstream_open_pa(SoundIoPrivate *si, SoundIoOutStreamPrivate *os) {
return SoundIoErrorNoMem; return SoundIoErrorNoMem;
} }
pa_stream_set_state_callback(ospa->stream, playback_stream_state_callback, os); 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.maxlength = UINT32_MAX;
ospa->buffer_attr.tlength = 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; 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); pa_threaded_mainloop_unlock(sipa->main_loop);
return 0; return 0;
@ -730,22 +746,18 @@ static int outstream_start_pa(SoundIoPrivate *si, SoundIoOutStreamPrivate *os) {
pa_threaded_mainloop_lock(sipa->main_loop); 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, pa_operation *op = pa_stream_cork(ospa->stream, false, nullptr, nullptr);
outstream->device->id, &ospa->buffer_attr, if (!op) {
flags, nullptr, nullptr);
if (err) {
pa_threaded_mainloop_unlock(sipa->main_loop); pa_threaded_mainloop_unlock(sipa->main_loop);
return SoundIoErrorOpeningDevice; return SoundIoErrorStreaming;
} }
pa_operation_unref(op);
while (!ospa->stream_ready) pa_stream_set_write_callback(ospa->stream, playback_stream_write_callback, os);
pa_threaded_mainloop_wait(sipa->main_loop); pa_stream_set_underflow_callback(ospa->stream, playback_stream_underflow_callback, outstream);
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_threaded_mainloop_unlock(sipa->main_loop); 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_stream *stream = ospa->stream;
pa_threaded_mainloop_lock(sipa->main_loop); pa_threaded_mainloop_lock(sipa->main_loop);
pa_operation *op = pa_stream_flush(stream, NULL, NULL); pa_operation *op = pa_stream_flush(stream, NULL, NULL);
if (!op) if (!op) {
pa_threaded_mainloop_unlock(sipa->main_loop);
return SoundIoErrorStreaming; return SoundIoErrorStreaming;
}
pa_operation_unref(op); pa_operation_unref(op);
pa_threaded_mainloop_unlock(sipa->main_loop); pa_threaded_mainloop_unlock(sipa->main_loop);
return 0; return 0;
@ -815,8 +829,10 @@ static int outstream_pause_pa(SoundIoPrivate *si, SoundIoOutStreamPrivate *os, b
if (pause != pa_stream_is_corked(ospa->stream)) { if (pause != pa_stream_is_corked(ospa->stream)) {
pa_operation *op = pa_stream_cork(ospa->stream, pause, NULL, NULL); pa_operation *op = pa_stream_cork(ospa->stream, pause, NULL, NULL);
if (!op) if (!op) {
pa_threaded_mainloop_unlock(sipa->main_loop);
return SoundIoErrorStreaming; return SoundIoErrorStreaming;
}
pa_operation_unref(op); pa_operation_unref(op);
} }