output/pipewire: call pw_stream_flush() only if really draining

If draining was not requested, generate silence instead if there is no
data in the ring buffer.

The problem is that pw_stream_flush() appears to disable the stream
permanently, even though there is no state_changed callback - the
stream state remains at PW_STREAM_STATE_STREAMING, but the stream is
defunct.  I have no idea why and I havn't found any documentation
about it.

Closes https://github.com/MusicPlayerDaemon/MPD/issues/1219
This commit is contained in:
Max Kellermann 2021-08-09 19:43:26 +02:00
parent d33aa01000
commit 8a243e6e28
1 changed files with 34 additions and 2 deletions

View File

@ -22,6 +22,11 @@
#include "../OutputAPI.hxx" #include "../OutputAPI.hxx"
#include "../Error.hxx" #include "../Error.hxx"
#include "mixer/plugins/PipeWireMixerPlugin.hxx" #include "mixer/plugins/PipeWireMixerPlugin.hxx"
#include "pcm/Silence.hxx"
#include "util/Domain.hxx"
#include "util/ScopeExit.hxx"
#include "util/WritableBuffer.hxx"
#include "Log.hxx"
#ifdef __GNUC__ #ifdef __GNUC__
#pragma GCC diagnostic push #pragma GCC diagnostic push
@ -43,6 +48,8 @@
#include <stdexcept> #include <stdexcept>
static constexpr Domain pipewire_output_domain("pipewire_output");
class PipeWireOutput final : AudioOutput { class PipeWireOutput final : AudioOutput {
const char *const name; const char *const name;
@ -60,6 +67,11 @@ class PipeWireOutput final : AudioOutput {
float volume = 1.0; float volume = 1.0;
/**
* The active sample format, needed for PcmSilence().
*/
SampleFormat sample_format;
bool disconnected; bool disconnected;
/** /**
@ -72,6 +84,14 @@ class PipeWireOutput final : AudioOutput {
bool interrupted; bool interrupted;
bool paused; bool paused;
/**
* Has Drain() been called? This causes Process() to invoke
* pw_stream_flush() to drain PipeWire as soon as the
* #ring_buffer has been drained.
*/
bool drain_requested;
bool drained; bool drained;
explicit PipeWireOutput(const ConfigBlock &block); explicit PipeWireOutput(const ConfigBlock &block);
@ -313,6 +333,7 @@ PipeWireOutput::Open(AudioFormat &audio_format)
disconnected = false; disconnected = false;
restore_volume = true; restore_volume = true;
paused = false; paused = false;
drain_requested = false;
drained = true; drained = true;
auto props = pw_properties_new(PW_KEY_MEDIA_TYPE, "Audio", auto props = pw_properties_new(PW_KEY_MEDIA_TYPE, "Audio",
@ -335,6 +356,7 @@ PipeWireOutput::Open(AudioFormat &audio_format)
auto raw = ToPipeWireAudioFormat(audio_format); auto raw = ToPipeWireAudioFormat(audio_format);
frame_size = audio_format.GetFrameSize(); frame_size = audio_format.GetFrameSize();
sample_format = audio_format.format;
interrupted = false; interrupted = false;
/* allocate a ring buffer of 1 second */ /* allocate a ring buffer of 1 second */
@ -409,10 +431,17 @@ PipeWireOutput::Process() noexcept
size_t nbytes = ring_buffer->pop(dest, max_size); size_t nbytes = ring_buffer->pop(dest, max_size);
if (nbytes == 0) { if (nbytes == 0) {
if (drain_requested) {
pw_stream_flush(stream, true); pw_stream_flush(stream, true);
return; return;
} }
/* buffer underrun: generate some silence */
PcmSilence({dest, max_size}, sample_format);
LogWarning(pipewire_output_domain, "Decoder is too slow; playing silence to avoid xrun");
}
buf->datas[0].chunk->offset = 0; buf->datas[0].chunk->offset = 0;
buf->datas[0].chunk->stride = frame_size; buf->datas[0].chunk->stride = frame_size;
buf->datas[0].chunk->size = nbytes; buf->datas[0].chunk->size = nbytes;
@ -454,6 +483,9 @@ PipeWireOutput::Drain()
{ {
const PipeWire::ThreadLoopLock lock(thread_loop); const PipeWire::ThreadLoopLock lock(thread_loop);
drain_requested = true;
AtScopeExit(this) { drain_requested = false; };
while (!drained && !interrupted) { while (!drained && !interrupted) {
CheckThrowError(); CheckThrowError();
pw_thread_loop_wait(thread_loop); pw_thread_loop_wait(thread_loop);