[PATCH v3] ALSA: pcm: auto-fill buffer with silence when draining playback

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



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@xxxxxx>

---
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);
-- 
2.40.0.152.g15d061e6df




[Index of Archives]     [ALSA User]     [Linux Audio Users]     [Pulse Audio]     [Kernel Archive]     [Asterisk PBX]     [Photo Sharing]     [Linux Sound]     [Video 4 Linux]     [Gimp]     [Yosemite News]

  Powered by Linux