summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMax Kellermann <max@musicpd.org>2021-08-09 19:43:26 +0200
committerMax Kellermann <max@musicpd.org>2021-08-10 06:30:58 +0200
commit8a243e6e28bd949e9aa7aeddccaf8df10ec892fb (patch)
tree5320729983f9981754c9fa356a0172a7b6e83fb1
parentd33aa01000c5b121f1d41b516e6354dd64aa29a6 (diff)
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
-rw-r--r--src/output/plugins/PipeWireOutputPlugin.cxx36
1 files changed, 34 insertions, 2 deletions
diff --git a/src/output/plugins/PipeWireOutputPlugin.cxx b/src/output/plugins/PipeWireOutputPlugin.cxx
index 7fd691ee0..3170f488d 100644
--- a/src/output/plugins/PipeWireOutputPlugin.cxx
+++ b/src/output/plugins/PipeWireOutputPlugin.cxx
@@ -22,6 +22,11 @@
#include "../OutputAPI.hxx"
#include "../Error.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__
#pragma GCC diagnostic push
@@ -43,6 +48,8 @@
#include <stdexcept>
+static constexpr Domain pipewire_output_domain("pipewire_output");
+
class PipeWireOutput final : AudioOutput {
const char *const name;
@@ -60,6 +67,11 @@ class PipeWireOutput final : AudioOutput {
float volume = 1.0;
+ /**
+ * The active sample format, needed for PcmSilence().
+ */
+ SampleFormat sample_format;
+
bool disconnected;
/**
@@ -72,6 +84,14 @@ class PipeWireOutput final : AudioOutput {
bool interrupted;
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;
explicit PipeWireOutput(const ConfigBlock &block);
@@ -313,6 +333,7 @@ PipeWireOutput::Open(AudioFormat &audio_format)
disconnected = false;
restore_volume = true;
paused = false;
+ drain_requested = false;
drained = true;
auto props = pw_properties_new(PW_KEY_MEDIA_TYPE, "Audio",
@@ -335,6 +356,7 @@ PipeWireOutput::Open(AudioFormat &audio_format)
auto raw = ToPipeWireAudioFormat(audio_format);
frame_size = audio_format.GetFrameSize();
+ sample_format = audio_format.format;
interrupted = false;
/* allocate a ring buffer of 1 second */
@@ -409,8 +431,15 @@ PipeWireOutput::Process() noexcept
size_t nbytes = ring_buffer->pop(dest, max_size);
if (nbytes == 0) {
- pw_stream_flush(stream, true);
- return;
+ if (drain_requested) {
+ pw_stream_flush(stream, true);
+ 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;
@@ -454,6 +483,9 @@ PipeWireOutput::Drain()
{
const PipeWire::ThreadLoopLock lock(thread_loop);
+ drain_requested = true;
+ AtScopeExit(this) { drain_requested = false; };
+
while (!drained && !interrupted) {
CheckThrowError();
pw_thread_loop_wait(thread_loop);