Commit 4cdcaa86 authored by Max Kellermann's avatar Max Kellermann

output/alsa: don't call snd_pcm_drain() if nothing was written

Works around a problem where MPD goes into a busy loop because snd_pcm_drain() always returns `-EAGAIN` without making any progress (fixes #425). This problem was triggered by snd_pcm_drain() after snd_pcm_cancel() and snd_pcm_prepare(), but without submitting any data with snd_pcm_writei(). I believe this is a kernel bug: in non-blocking mode, the kernel's snd_pcm_drain() function returns early. In this mode, it only checks whether snd_pcm_drain_done() has been called already, but snd_pcm_drain_done() is never called if no data was submitted. In blocking mode, the following `for` loop detects this condition, so snd_pcm_drain_done() is not necessary, but without this extra check, we get `-EAGAIN` forever.
parent 04f63229
...@@ -147,6 +147,16 @@ class AlsaOutput final ...@@ -147,6 +147,16 @@ class AlsaOutput final
*/ */
bool must_prepare; bool must_prepare;
/**
* Has snd_pcm_writei() been called successfully at least once
* since the PCM was prepared?
*
* This is necessary to work around a kernel bug which causes
* snd_pcm_drain() to return -EAGAIN forever in non-blocking
* mode if snd_pcm_writei() was never called.
*/
bool written;
bool drain; bool drain;
/** /**
...@@ -305,9 +315,11 @@ private: ...@@ -305,9 +315,11 @@ private:
auto frames_written = snd_pcm_writei(pcm, period_buffer.GetHead(), auto frames_written = snd_pcm_writei(pcm, period_buffer.GetHead(),
period_buffer.GetFrames(out_frame_size)); period_buffer.GetFrames(out_frame_size));
if (frames_written > 0) if (frames_written > 0) {
written = true;
period_buffer.ConsumeFrames(frames_written, period_buffer.ConsumeFrames(frames_written,
out_frame_size); out_frame_size);
}
return frames_written; return frames_written;
} }
...@@ -673,6 +685,7 @@ AlsaOutput::Open(AudioFormat &audio_format) ...@@ -673,6 +685,7 @@ AlsaOutput::Open(AudioFormat &audio_format)
active = false; active = false;
must_prepare = false; must_prepare = false;
written = false;
error = {}; error = {};
} }
...@@ -705,6 +718,7 @@ AlsaOutput::Recover(int err) noexcept ...@@ -705,6 +718,7 @@ AlsaOutput::Recover(int err) noexcept
case SND_PCM_STATE_SETUP: case SND_PCM_STATE_SETUP:
case SND_PCM_STATE_XRUN: case SND_PCM_STATE_XRUN:
period_buffer.Rewind(); period_buffer.Rewind();
written = false;
err = snd_pcm_prepare(pcm); err = snd_pcm_prepare(pcm);
break; break;
case SND_PCM_STATE_DISCONNECTED: case SND_PCM_STATE_DISCONNECTED:
...@@ -755,6 +769,11 @@ AlsaOutput::DrainInternal() ...@@ -755,6 +769,11 @@ AlsaOutput::DrainInternal()
return period_buffer.IsEmpty(); return period_buffer.IsEmpty();
} }
if (!written)
/* if nothing has ever been written to the PCM, we
don't need to drain it */
return true;
/* .. and finally drain the ALSA hardware buffer */ /* .. and finally drain the ALSA hardware buffer */
int result; int result;
...@@ -914,6 +933,7 @@ try { ...@@ -914,6 +933,7 @@ try {
if (must_prepare) { if (must_prepare) {
must_prepare = false; must_prepare = false;
written = false;
int err = snd_pcm_prepare(pcm); int err = snd_pcm_prepare(pcm);
if (err < 0) if (err < 0)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment