Draining will always playback somewhat beyond the end of the filled buffer. This would produce artifacts if the user did not set up the auto-silencing machinery, which is an extremely easy mistake to make, as the API strongly suggests convenient fire-and-forget semantics. This patch makes it work out of the box.
Signed-off-by: Oswald Buddenhagen oswald.buddenhagen@gmx.de
--- you are NOT expected to apply this. i just needed it for my testing (it's easier to deploy as i'm hacking on the kernel anyway) and wanted to post it for posterity.
v3: - rebased to updated silencing code - intro period alignment to reduce redundant fill - cut down commit message again, as it's disabled by default now v2: - fill only up to two periods, to avoid undue load with big buffers - added discussion to commit message --- sound/core/pcm_lib.c | 35 ++++++++++++++++++++++++++++++++++- sound/core/pcm_native.c | 3 ++- 2 files changed, 36 insertions(+), 2 deletions(-)
diff --git a/sound/core/pcm_lib.c b/sound/core/pcm_lib.c index 9c121a921b04..46e63e653789 100644 --- a/sound/core/pcm_lib.c +++ b/sound/core/pcm_lib.c @@ -67,6 +67,11 @@ void snd_pcm_playback_silence(struct snd_pcm_substream *substream, snd_pcm_ufram snd_pcm_uframes_t frames, ofs, transfer; int err;
+ if (runtime->silence_size == 0 && + (runtime->state != SNDRV_PCM_STATE_DRAINING || + (runtime->info & SNDRV_PCM_HW_PARAMS_NO_DRAIN_SILENCE) || + (runtime->hw.info & SNDRV_PCM_INFO_PERFECT_DRAIN))) + return; if (runtime->silence_size < runtime->boundary) { snd_pcm_sframes_t noise_dist; snd_pcm_uframes_t appl_ptr = READ_ONCE(runtime->control->appl_ptr); @@ -80,6 +85,33 @@ void snd_pcm_playback_silence(struct snd_pcm_substream *substream, snd_pcm_ufram noise_dist += runtime->boundary; /* total noise distance */ noise_dist += runtime->silence_filled; + if (runtime->state == SNDRV_PCM_STATE_DRAINING) { + snd_pcm_uframes_t slack = runtime->rate / 10; + snd_pcm_sframes_t threshold; + snd_pcm_uframes_t ps = runtime->period_size; + snd_pcm_uframes_t silence_size = ps; + // Round down to start of next period. This is disabled + // if the period count is not integer. + if (runtime->periods * ps == runtime->buffer_size) + silence_size = ps - (appl_ptr + ps - 1) % ps - 1; + // Add overshoot to accomodate FIFOs and IRQ delays. + // The default 1/10th secs is very generous. But more than one + // period doesn't make sense; the driver would set the minimum + // period size accordingly. + slack = min(slack, ps); + silence_size += slack; + // This catches the periods == 1 case. + silence_size = min(silence_size, runtime->buffer_size); + + threshold = ps + slack; + if (noise_dist >= threshold) + return; + frames = threshold - noise_dist; + if (frames > silence_size) + frames = silence_size; + + goto avoid_reindent; + } if (noise_dist >= (snd_pcm_sframes_t) runtime->silence_threshold) return; frames = runtime->silence_threshold - noise_dist; @@ -118,6 +150,7 @@ void snd_pcm_playback_silence(struct snd_pcm_substream *substream, snd_pcm_ufram */ frames = runtime->buffer_size - runtime->silence_filled; } +avoid_reindent: if (snd_BUG_ON(frames > runtime->buffer_size)) return; if (frames == 0) @@ -465,7 +498,7 @@ static int snd_pcm_update_hw_ptr0(struct snd_pcm_substream *substream, }
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK && - runtime->silence_size > 0) + (runtime->silence_size > 0 || runtime->state == SNDRV_PCM_STATE_DRAINING)) snd_pcm_playback_silence(substream, new_hw_ptr);
if (in_interrupt) { diff --git a/sound/core/pcm_native.c b/sound/core/pcm_native.c index 39a65d1415ab..913dae449ba0 100644 --- a/sound/core/pcm_native.c +++ b/sound/core/pcm_native.c @@ -1454,7 +1454,7 @@ static void snd_pcm_post_start(struct snd_pcm_substream *substream, runtime->rate; __snd_pcm_set_state(runtime, state); if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK && - runtime->silence_size > 0) + (runtime->silence_size > 0 || state == SNDRV_PCM_STATE_DRAINING)) snd_pcm_playback_silence(substream, ULONG_MAX); snd_pcm_timer_notify(substream, SNDRV_TIMER_EVENT_MSTART); } @@ -2045,6 +2045,7 @@ static int snd_pcm_do_drain_init(struct snd_pcm_substream *substream, break; case SNDRV_PCM_STATE_RUNNING: __snd_pcm_set_state(runtime, SNDRV_PCM_STATE_DRAINING); + snd_pcm_playback_silence(substream, ULONG_MAX); break; case SNDRV_PCM_STATE_XRUN: __snd_pcm_set_state(runtime, SNDRV_PCM_STATE_SETUP);