From ccafe3f3cf361ff1c2862c0eb45d96501d67d6a7 Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Wed, 14 Nov 2018 11:06:59 +0100 Subject: [PATCH] output/alsa: don't generate silence if ALSA-PCM buffer has enough data If our `ring_buffer` is smaller than the ALSA-PCM buffer (if the latter has more than the 4 periods we allocate), it can happen that the start threshold is crossed and ALSA switches to `SND_PCM_STATE_RUNNING`, but the `ring_buffer` is empty. In this case, MPDD will generate silence, even though the ALSA-PCM buffer has enough data. This causes stuttering (#420). This commit amends an older workaround for a similar problem (commit e08598e7e2526b65fd8790a49794c1d4d00945d0) by adding a snd_pcm_avail() check, and only generate silence if there is less than one period of data in the ALSA-PCM buffer. Fixes #420 --- NEWS | 1 + src/output/plugins/AlsaOutputPlugin.cxx | 19 ++++++++++++++++++- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/NEWS b/NEWS index 2cfd2c025..a8bacdbc2 100644 --- a/NEWS +++ b/NEWS @@ -1,6 +1,7 @@ ver 0.21.3 (not yet released) * output - alsa: fix crash bug + - alsa: fix stuttering at start of playback ver 0.21.2 (2018/11/12) * protocol diff --git a/src/output/plugins/AlsaOutputPlugin.cxx b/src/output/plugins/AlsaOutputPlugin.cxx index c72cf4ea2..ee1487fb0 100644 --- a/src/output/plugins/AlsaOutputPlugin.cxx +++ b/src/output/plugins/AlsaOutputPlugin.cxx @@ -109,6 +109,13 @@ class AlsaOutput final */ snd_pcm_uframes_t period_frames; + /** + * If snd_pcm_avail() goes above this value and no more data + * is available in the #ring_buffer, we need to play some + * silence. + */ + snd_pcm_sframes_t max_avail_frames; + /** * Is this a buggy alsa-lib version, which needs a workaround * for the snd_pcm_drain() bug always returning -EAGAIN? See @@ -482,6 +489,10 @@ AlsaOutput::Setup(AudioFormat &audio_format, period_frames = alsa_period_size; + /* generate silence if there's less than once period of data + in the ALSA-PCM buffer */ + max_avail_frames = hw_result.buffer_size - hw_result.period_size; + silence = new uint8_t[snd_pcm_frames_to_bytes(pcm, alsa_period_size)]; snd_pcm_format_set_silence(hw_result.format, silence, alsa_period_size * audio_format.channels); @@ -933,7 +944,8 @@ try { CopyRingToPeriodBuffer(); if (period_buffer.IsEmpty()) { - if (snd_pcm_state(pcm) == SND_PCM_STATE_PREPARED) { + if (snd_pcm_state(pcm) == SND_PCM_STATE_PREPARED || + snd_pcm_avail(pcm) <= max_avail_frames) { /* at SND_PCM_STATE_PREPARED (not yet switched to SND_PCM_STATE_RUNNING), we have no pressure to fill the ALSA buffer, because @@ -943,6 +955,11 @@ try { monitoring the ALSA file descriptor, and let it be reactivated by Play()/Activate() whenever more data arrives */ + /* the same applies when there is still enough + data in the ALSA-PCM buffer (determined by + snd_pcm_avail()); this can happend at the + start of playback, when our ring_buffer is + smaller than the ALSA-PCM buffer */ { const std::lock_guard lock(mutex);