Commit 9f656705 authored by Oswald Buddenhagen's avatar Oswald Buddenhagen Committed by Takashi Iwai

ALSA: pcm: rewrite snd_pcm_playback_silence()

The auto-silencer supports two modes: "thresholded" to fill up "just
enough", and "top-up" to fill up "as much as possible". The two modes
used rather distinct code paths, which this patch unifies. The only
remaining distinction is how much we actually want to fill.

This fixes a bug in thresholded mode, where we failed to use new_hw_ptr,
resulting in under-fill.

Top-up mode is now more well-behaved and much easier to understand in
corner cases.

This also updates comments in the proximity of silencing-related data
structures.
Signed-off-by: default avatarOswald Buddenhagen <oswald.buddenhagen@gmx.de>
Reviewed-by: default avatarJaroslav Kysela <perex@perex.cz>
Link: https://lore.kernel.org/r/20230420113324.877164-1-oswald.buddenhagen@gmx.deSigned-off-by: default avatarTakashi Iwai <tiwai@suse.de>
parent 0d19bd4d
...@@ -1577,14 +1577,19 @@ are the contents of this file: ...@@ -1577,14 +1577,19 @@ are the contents of this file:
unsigned int period_step; unsigned int period_step;
unsigned int sleep_min; /* min ticks to sleep */ unsigned int sleep_min; /* min ticks to sleep */
snd_pcm_uframes_t start_threshold; snd_pcm_uframes_t start_threshold;
snd_pcm_uframes_t stop_threshold; /*
snd_pcm_uframes_t silence_threshold; /* Silence filling happens when * The following two thresholds alleviate playback buffer underruns; when
noise is nearest than this */ * hw_avail drops below the threshold, the respective action is triggered:
snd_pcm_uframes_t silence_size; /* Silence filling size */ */
snd_pcm_uframes_t stop_threshold; /* - stop playback */
snd_pcm_uframes_t silence_threshold; /* - pre-fill buffer with silence */
snd_pcm_uframes_t silence_size; /* max size of silence pre-fill; when >= boundary,
* fill played area with silence immediately */
snd_pcm_uframes_t boundary; /* pointers wrap point */ snd_pcm_uframes_t boundary; /* pointers wrap point */
snd_pcm_uframes_t silenced_start; /* internal data of auto-silencer */
snd_pcm_uframes_t silenced_size; snd_pcm_uframes_t silence_start; /* starting pointer to silence area */
snd_pcm_uframes_t silence_filled; /* size filled with silence */
snd_pcm_sync_id_t sync; /* hardware synchronization ID */ snd_pcm_sync_id_t sync; /* hardware synchronization ID */
......
...@@ -378,18 +378,18 @@ struct snd_pcm_runtime { ...@@ -378,18 +378,18 @@ struct snd_pcm_runtime {
unsigned int rate_den; unsigned int rate_den;
unsigned int no_period_wakeup: 1; unsigned int no_period_wakeup: 1;
/* -- SW params -- */ /* -- SW params; see struct snd_pcm_sw_params for comments -- */
int tstamp_mode; /* mmap timestamp is updated */ int tstamp_mode;
unsigned int period_step; unsigned int period_step;
snd_pcm_uframes_t start_threshold; snd_pcm_uframes_t start_threshold;
snd_pcm_uframes_t stop_threshold; snd_pcm_uframes_t stop_threshold;
snd_pcm_uframes_t silence_threshold; /* Silence filling happens when snd_pcm_uframes_t silence_threshold;
noise is nearest than this */ snd_pcm_uframes_t silence_size;
snd_pcm_uframes_t silence_size; /* Silence filling size */ snd_pcm_uframes_t boundary;
snd_pcm_uframes_t boundary; /* pointers wrap point */
/* internal data of auto-silencer */
snd_pcm_uframes_t silence_start; /* starting pointer to silence area */ snd_pcm_uframes_t silence_start; /* starting pointer to silence area */
snd_pcm_uframes_t silence_filled; /* size filled with silence */ snd_pcm_uframes_t silence_filled; /* already filled part of silence area */
union snd_pcm_sync_id sync; /* hardware synchronization ID */ union snd_pcm_sync_id sync; /* hardware synchronization ID */
......
...@@ -429,9 +429,14 @@ struct snd_pcm_sw_params { ...@@ -429,9 +429,14 @@ struct snd_pcm_sw_params {
snd_pcm_uframes_t avail_min; /* min avail frames for wakeup */ snd_pcm_uframes_t avail_min; /* min avail frames for wakeup */
snd_pcm_uframes_t xfer_align; /* obsolete: xfer size need to be a multiple */ snd_pcm_uframes_t xfer_align; /* obsolete: xfer size need to be a multiple */
snd_pcm_uframes_t start_threshold; /* min hw_avail frames for automatic start */ snd_pcm_uframes_t start_threshold; /* min hw_avail frames for automatic start */
snd_pcm_uframes_t stop_threshold; /* min avail frames for automatic stop */ /*
snd_pcm_uframes_t silence_threshold; /* min distance from noise for silence filling */ * The following two thresholds alleviate playback buffer underruns; when
snd_pcm_uframes_t silence_size; /* silence block size */ * hw_avail drops below the threshold, the respective action is triggered:
*/
snd_pcm_uframes_t stop_threshold; /* - stop playback */
snd_pcm_uframes_t silence_threshold; /* - pre-fill buffer with silence */
snd_pcm_uframes_t silence_size; /* max size of silence pre-fill; when >= boundary,
* fill played area with silence immediately */
snd_pcm_uframes_t boundary; /* pointers wrap point */ snd_pcm_uframes_t boundary; /* pointers wrap point */
unsigned int proto; /* protocol version */ unsigned int proto; /* protocol version */
unsigned int tstamp_type; /* timestamp type (req. proto >= 2.0.12) */ unsigned int tstamp_type; /* timestamp type (req. proto >= 2.0.12) */
......
...@@ -42,70 +42,56 @@ static int fill_silence_frames(struct snd_pcm_substream *substream, ...@@ -42,70 +42,56 @@ static int fill_silence_frames(struct snd_pcm_substream *substream,
* *
* when runtime->silence_size >= runtime->boundary - fill processed area with silence immediately * when runtime->silence_size >= runtime->boundary - fill processed area with silence immediately
*/ */
void snd_pcm_playback_silence(struct snd_pcm_substream *substream, snd_pcm_uframes_t new_hw_ptr) void snd_pcm_playback_silence(struct snd_pcm_substream *substream)
{ {
struct snd_pcm_runtime *runtime = substream->runtime; struct snd_pcm_runtime *runtime = substream->runtime;
snd_pcm_uframes_t frames, ofs, transfer; snd_pcm_uframes_t appl_ptr = READ_ONCE(runtime->control->appl_ptr);
snd_pcm_sframes_t added, hw_avail, frames;
snd_pcm_uframes_t noise_dist, ofs, transfer;
int err; int err;
added = appl_ptr - runtime->silence_start;
if (added) {
if (added < 0)
added += runtime->boundary;
if (added < runtime->silence_filled)
runtime->silence_filled -= added;
else
runtime->silence_filled = 0;
runtime->silence_start = appl_ptr;
}
// This will "legitimately" turn negative on underrun, and will be mangled
// into a huge number by the boundary crossing handling. The initial state
// might also be not quite sane. The code below MUST account for these cases.
hw_avail = appl_ptr - runtime->status->hw_ptr;
if (hw_avail < 0)
hw_avail += runtime->boundary;
noise_dist = hw_avail + runtime->silence_filled;
if (runtime->silence_size < runtime->boundary) { if (runtime->silence_size < runtime->boundary) {
snd_pcm_sframes_t noise_dist, n;
snd_pcm_uframes_t appl_ptr = READ_ONCE(runtime->control->appl_ptr);
if (runtime->silence_start != appl_ptr) {
n = appl_ptr - runtime->silence_start;
if (n < 0)
n += runtime->boundary;
if ((snd_pcm_uframes_t)n < runtime->silence_filled)
runtime->silence_filled -= n;
else
runtime->silence_filled = 0;
runtime->silence_start = appl_ptr;
}
if (runtime->silence_filled >= runtime->buffer_size)
return;
noise_dist = snd_pcm_playback_hw_avail(runtime) + runtime->silence_filled;
if (noise_dist >= (snd_pcm_sframes_t) runtime->silence_threshold)
return;
frames = runtime->silence_threshold - noise_dist; frames = runtime->silence_threshold - noise_dist;
if (frames <= 0)
return;
if (frames > runtime->silence_size) if (frames > runtime->silence_size)
frames = runtime->silence_size; frames = runtime->silence_size;
} else { } else {
if (new_hw_ptr == ULONG_MAX) { /* initialization */ frames = runtime->buffer_size - noise_dist;
snd_pcm_sframes_t avail = snd_pcm_playback_hw_avail(runtime); if (frames <= 0)
if (avail > runtime->buffer_size) return;
avail = runtime->buffer_size;
runtime->silence_filled = avail > 0 ? avail : 0;
runtime->silence_start = (runtime->status->hw_ptr +
runtime->silence_filled) %
runtime->boundary;
} else {
ofs = runtime->status->hw_ptr;
frames = new_hw_ptr - ofs;
if ((snd_pcm_sframes_t)frames < 0)
frames += runtime->boundary;
runtime->silence_filled -= frames;
if ((snd_pcm_sframes_t)runtime->silence_filled < 0) {
runtime->silence_filled = 0;
runtime->silence_start = new_hw_ptr;
} else {
runtime->silence_start = ofs;
}
}
frames = runtime->buffer_size - runtime->silence_filled;
} }
if (snd_BUG_ON(frames > runtime->buffer_size)) if (snd_BUG_ON(frames > runtime->buffer_size))
return; return;
if (frames == 0) ofs = (runtime->silence_start + runtime->silence_filled) % runtime->buffer_size;
return; do {
ofs = runtime->silence_start % runtime->buffer_size;
while (frames > 0) {
transfer = ofs + frames > runtime->buffer_size ? runtime->buffer_size - ofs : frames; transfer = ofs + frames > runtime->buffer_size ? runtime->buffer_size - ofs : frames;
err = fill_silence_frames(substream, ofs, transfer); err = fill_silence_frames(substream, ofs, transfer);
snd_BUG_ON(err < 0); snd_BUG_ON(err < 0);
runtime->silence_filled += transfer; runtime->silence_filled += transfer;
frames -= transfer; frames -= transfer;
ofs = 0; ofs = 0;
} } while (frames > 0);
snd_pcm_dma_buffer_sync(substream, SNDRV_DMA_SYNC_DEVICE); snd_pcm_dma_buffer_sync(substream, SNDRV_DMA_SYNC_DEVICE);
} }
...@@ -439,10 +425,6 @@ static int snd_pcm_update_hw_ptr0(struct snd_pcm_substream *substream, ...@@ -439,10 +425,6 @@ static int snd_pcm_update_hw_ptr0(struct snd_pcm_substream *substream,
return 0; return 0;
} }
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK &&
runtime->silence_size > 0)
snd_pcm_playback_silence(substream, new_hw_ptr);
if (in_interrupt) { if (in_interrupt) {
delta = new_hw_ptr - runtime->hw_ptr_interrupt; delta = new_hw_ptr - runtime->hw_ptr_interrupt;
if (delta < 0) if (delta < 0)
...@@ -460,6 +442,10 @@ static int snd_pcm_update_hw_ptr0(struct snd_pcm_substream *substream, ...@@ -460,6 +442,10 @@ static int snd_pcm_update_hw_ptr0(struct snd_pcm_substream *substream,
runtime->hw_ptr_wrap += runtime->boundary; runtime->hw_ptr_wrap += runtime->boundary;
} }
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK &&
runtime->silence_size > 0)
snd_pcm_playback_silence(substream);
update_audio_tstamp(substream, &curr_tstamp, &audio_tstamp); update_audio_tstamp(substream, &curr_tstamp, &audio_tstamp);
return snd_pcm_update_state(substream, runtime); return snd_pcm_update_state(substream, runtime);
......
...@@ -29,8 +29,7 @@ int snd_pcm_update_state(struct snd_pcm_substream *substream, ...@@ -29,8 +29,7 @@ int snd_pcm_update_state(struct snd_pcm_substream *substream,
struct snd_pcm_runtime *runtime); struct snd_pcm_runtime *runtime);
int snd_pcm_update_hw_ptr(struct snd_pcm_substream *substream); int snd_pcm_update_hw_ptr(struct snd_pcm_substream *substream);
void snd_pcm_playback_silence(struct snd_pcm_substream *substream, void snd_pcm_playback_silence(struct snd_pcm_substream *substream);
snd_pcm_uframes_t new_hw_ptr);
static inline snd_pcm_uframes_t static inline snd_pcm_uframes_t
snd_pcm_avail(struct snd_pcm_substream *substream) snd_pcm_avail(struct snd_pcm_substream *substream)
......
...@@ -958,7 +958,7 @@ static int snd_pcm_sw_params(struct snd_pcm_substream *substream, ...@@ -958,7 +958,7 @@ static int snd_pcm_sw_params(struct snd_pcm_substream *substream,
if (snd_pcm_running(substream)) { if (snd_pcm_running(substream)) {
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK && if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK &&
runtime->silence_size > 0) runtime->silence_size > 0)
snd_pcm_playback_silence(substream, ULONG_MAX); snd_pcm_playback_silence(substream);
err = snd_pcm_update_state(substream, runtime); err = snd_pcm_update_state(substream, runtime);
} }
snd_pcm_stream_unlock_irq(substream); snd_pcm_stream_unlock_irq(substream);
...@@ -1455,7 +1455,7 @@ static void snd_pcm_post_start(struct snd_pcm_substream *substream, ...@@ -1455,7 +1455,7 @@ static void snd_pcm_post_start(struct snd_pcm_substream *substream,
__snd_pcm_set_state(runtime, state); __snd_pcm_set_state(runtime, state);
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK && if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK &&
runtime->silence_size > 0) runtime->silence_size > 0)
snd_pcm_playback_silence(substream, ULONG_MAX); snd_pcm_playback_silence(substream);
snd_pcm_timer_notify(substream, SNDRV_TIMER_EVENT_MSTART); snd_pcm_timer_notify(substream, SNDRV_TIMER_EVENT_MSTART);
} }
...@@ -1916,7 +1916,7 @@ static void snd_pcm_post_reset(struct snd_pcm_substream *substream, ...@@ -1916,7 +1916,7 @@ static void snd_pcm_post_reset(struct snd_pcm_substream *substream,
runtime->control->appl_ptr = runtime->status->hw_ptr; runtime->control->appl_ptr = runtime->status->hw_ptr;
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK && if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK &&
runtime->silence_size > 0) runtime->silence_size > 0)
snd_pcm_playback_silence(substream, ULONG_MAX); snd_pcm_playback_silence(substream);
snd_pcm_stream_unlock_irq(substream); snd_pcm_stream_unlock_irq(substream);
} }
......
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