[client] audio/pw: flush playback buffers before stopping

This stops the end of the playback from being truncated. It also prevents
an audible glitch when playback next starts due to the truncated data being
left behind in the ring buffer.
This commit is contained in:
Chris Spencer 2022-01-10 20:45:05 +00:00 committed by Geoffrey McRae
parent b9c646074d
commit 4c389a9274

View file

@ -29,6 +29,16 @@
#include "common/ringbuffer.h" #include "common/ringbuffer.h"
#include "common/util.h" #include "common/util.h"
typedef enum
{
STREAM_STATE_INACTIVE,
STREAM_STATE_ACTIVE,
STREAM_STATE_FLUSHING,
STREAM_STATE_DRAINING,
STREAM_STATE_RESTARTING
}
StreamState;
struct PipeWire struct PipeWire
{ {
struct pw_loop * loop; struct pw_loop * loop;
@ -43,7 +53,7 @@ struct PipeWire
int stride; int stride;
RingBuffer buffer; RingBuffer buffer;
bool active; StreamState state;
} }
playback; playback;
@ -68,7 +78,17 @@ static void pipewire_onPlaybackProcess(void * userdata)
struct pw_buffer * pbuf; struct pw_buffer * pbuf;
if (!ringbuffer_getCount(pw.playback.buffer)) if (!ringbuffer_getCount(pw.playback.buffer))
{
if (pw.playback.state == STREAM_STATE_FLUSHING)
{
pw_thread_loop_lock(pw.thread);
pw_stream_flush(pw.playback.stream, true);
pw.playback.state = STREAM_STATE_DRAINING;
pw_thread_loop_unlock(pw.thread);
}
return; return;
}
if (!(pbuf = pw_stream_dequeue_buffer(pw.playback.stream))) if (!(pbuf = pw_stream_dequeue_buffer(pw.playback.stream)))
{ {
@ -93,6 +113,26 @@ static void pipewire_onPlaybackProcess(void * userdata)
pw_stream_queue_buffer(pw.playback.stream, pbuf); pw_stream_queue_buffer(pw.playback.stream, pbuf);
} }
static void pipewire_onPlaybackDrained(void * userdata)
{
pw_thread_loop_lock(pw.thread);
if (pw.playback.state == STREAM_STATE_RESTARTING)
{
// A play command was received while we were in the middle of stopping;
// switch straight back into playing
pw_stream_set_active(pw.playback.stream, true);
pw.playback.state = STREAM_STATE_ACTIVE;
}
else
{
pw_stream_set_active(pw.playback.stream, false);
pw.playback.state = STREAM_STATE_INACTIVE;
}
pw_thread_loop_unlock(pw.thread);
}
static bool pipewire_init(void) static bool pipewire_init(void)
{ {
pw_init(NULL, NULL); pw_init(NULL, NULL);
@ -138,7 +178,6 @@ static void pipewire_playbackStopStream(void)
return; return;
pw_thread_loop_lock(pw.thread); pw_thread_loop_lock(pw.thread);
pw_stream_flush(pw.playback.stream, true);
pw_stream_destroy(pw.playback.stream); pw_stream_destroy(pw.playback.stream);
pw.playback.stream = NULL; pw.playback.stream = NULL;
pw_thread_loop_unlock(pw.thread); pw_thread_loop_unlock(pw.thread);
@ -152,7 +191,8 @@ static void pipewire_playbackStart(int channels, int sampleRate)
static const struct pw_stream_events events = static const struct pw_stream_events events =
{ {
.version = PW_VERSION_STREAM_EVENTS, .version = PW_VERSION_STREAM_EVENTS,
.process = pipewire_onPlaybackProcess .process = pipewire_onPlaybackProcess,
.drained = pipewire_onPlaybackDrained
}; };
if (pw.playback.stream && if (pw.playback.stream &&
@ -217,23 +257,61 @@ static void pipewire_playbackPlay(uint8_t * data, size_t size)
ringbuffer_append(pw.playback.buffer, data, size / pw.playback.stride); ringbuffer_append(pw.playback.buffer, data, size / pw.playback.stride);
if (!pw.playback.active) if (pw.playback.state != STREAM_STATE_ACTIVE &&
pw.playback.state != STREAM_STATE_RESTARTING)
{ {
pw_thread_loop_lock(pw.thread); pw_thread_loop_lock(pw.thread);
switch (pw.playback.state) {
case STREAM_STATE_INACTIVE:
pw_stream_set_active(pw.playback.stream, true); pw_stream_set_active(pw.playback.stream, true);
pw.playback.active = true; pw.playback.state = STREAM_STATE_ACTIVE;
break;
case STREAM_STATE_FLUSHING:
// We were preparing to stop; just carry on as if nothing happened
pw.playback.state = STREAM_STATE_ACTIVE;
break;
case STREAM_STATE_DRAINING:
// We are in the middle of draining the PipeWire buffers; we will need
// to reactivate the stream once this has completed
pw.playback.state = STREAM_STATE_RESTARTING;
break;
default:
DEBUG_UNREACHABLE();
}
pw_thread_loop_unlock(pw.thread); pw_thread_loop_unlock(pw.thread);
} }
} }
static void pipewire_playbackStop(void) static void pipewire_playbackStop(void)
{ {
if (!pw.playback.active) if (pw.playback.state != STREAM_STATE_ACTIVE &&
pw.playback.state != STREAM_STATE_RESTARTING)
return; return;
pw_thread_loop_lock(pw.thread); pw_thread_loop_lock(pw.thread);
pw_stream_set_active(pw.playback.stream, false);
pw.playback.active = false; switch (pw.playback.state)
{
case STREAM_STATE_ACTIVE:
pw.playback.state = STREAM_STATE_FLUSHING;
break;
case STREAM_STATE_RESTARTING:
// A stop was requested, and then a start while PipeWire was draining, and
// now another stop. PipeWire hasn't finished draining yet so just switch
// the state back
pw.playback.state = STREAM_STATE_DRAINING;
break;
default:
DEBUG_UNREACHABLE();
}
pw_thread_loop_unlock(pw.thread); pw_thread_loop_unlock(pw.thread);
} }
@ -265,7 +343,6 @@ static void pipewire_recordStopStream(void)
return; return;
pw_thread_loop_lock(pw.thread); pw_thread_loop_lock(pw.thread);
pw_stream_flush(pw.record.stream, true);
pw_stream_destroy(pw.record.stream); pw_stream_destroy(pw.record.stream);
pw.record.stream = NULL; pw.record.stream = NULL;
pw_thread_loop_unlock(pw.thread); pw_thread_loop_unlock(pw.thread);