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.
This commit is contained in:
parent
04f632296f
commit
4cdcaa8630
@ -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:
|
|||||||
|
|
||||||
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)
|
|||||||
|
|
||||||
active = false;
|
active = false;
|
||||||
must_prepare = false;
|
must_prepare = false;
|
||||||
|
written = false;
|
||||||
error = {};
|
error = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -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()
|
|||||||
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 {
|
|||||||
|
|
||||||
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)
|
||||||
|
Loading…
Reference in New Issue
Block a user