From 50e6950fa8bc2bda92698e399bab082383ec2804 Mon Sep 17 00:00:00 2001 From: borine <32966433+borine@users.noreply.github.com> Date: Thu, 30 Nov 2023 11:25:43 +0000 Subject: [PATCH 1/2] output/alsa: handle recoverable errors in DrainInternal() It is possible that an underrun may occur in the ALSA output device while MPD is draining its own internal buffer. If this happens then MPD stops playback, reporting the error EPIPE. This commit attempts to recover the ALSA device instead of stopping playback, so that the drain can complete and the next song in the play queue is played. --- src/output/plugins/AlsaOutputPlugin.cxx | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/output/plugins/AlsaOutputPlugin.cxx b/src/output/plugins/AlsaOutputPlugin.cxx index 257f1cd5a..c37428855 100644 --- a/src/output/plugins/AlsaOutputPlugin.cxx +++ b/src/output/plugins/AlsaOutputPlugin.cxx @@ -1002,14 +1002,19 @@ AlsaOutput::DrainInternal() period_buffer.FillWithSilence(silence, out_frame_size); /* drain period_buffer */ - if (!period_buffer.IsDrained()) { + unsigned int retry_count = 0; + while (!period_buffer.IsDrained() && retry_count <= 1) { auto frames_written = WriteFromPeriodBuffer(); if (frames_written < 0) { - if (frames_written == -EAGAIN) + if (frames_written == -EAGAIN || frames_written == -EINTR) return false; - throw Alsa::MakeError(frames_written, - "snd_pcm_writei() failed"); + if (Recover(frames_written) < 0) + throw Alsa::MakeError(frames_written, + "snd_pcm_writei() failed"); + + retry_count++; + continue; } /* need to call CopyRingToPeriodBuffer() and From b1f28d6003d394a05c9f4bbcf8524509334121c1 Mon Sep 17 00:00:00 2001 From: borine <32966433+borine@users.noreply.github.com> Date: Fri, 22 Dec 2023 12:02:33 +0000 Subject: [PATCH 2/2] output/alsa: Fix (theoretical error in) ALSA device recovery It is not possible to prepare an ALSA device when it is is state SND_PCM_STATE_OPEN; it is necessary to set the hardware parameters first. This pedantic commit corrects that error. Note that in practice this code path cannot be encountered because MPD always sets the hardware parameters before attempting to start playback. --- src/output/plugins/AlsaOutputPlugin.cxx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/output/plugins/AlsaOutputPlugin.cxx b/src/output/plugins/AlsaOutputPlugin.cxx index c37428855..15a51b758 100644 --- a/src/output/plugins/AlsaOutputPlugin.cxx +++ b/src/output/plugins/AlsaOutputPlugin.cxx @@ -910,7 +910,6 @@ AlsaOutput::Recover(int err) noexcept /* fall-through to snd_pcm_prepare: */ [[fallthrough]]; - case SND_PCM_STATE_OPEN: case SND_PCM_STATE_SETUP: case SND_PCM_STATE_XRUN: period_buffer.Rewind(); @@ -918,6 +917,7 @@ AlsaOutput::Recover(int err) noexcept err = snd_pcm_prepare(pcm); break; + case SND_PCM_STATE_OPEN: case SND_PCM_STATE_DISCONNECTED: case SND_PCM_STATE_DRAINING: /* can't play in this state; throw the error */