ALSA: pcm: rewrite snd_pcm_playback_silence()
authorOswald Buddenhagen <oswald.buddenhagen@gmx.de>
Thu, 20 Apr 2023 11:33:23 +0000 (13:33 +0200)
committerTakashi Iwai <tiwai@suse.de>
Fri, 21 Apr 2023 10:21:04 +0000 (12:21 +0200)
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: Oswald Buddenhagen <oswald.buddenhagen@gmx.de>
Reviewed-by: Jaroslav Kysela <perex@perex.cz>
Link: https://lore.kernel.org/r/20230420113324.877164-1-oswald.buddenhagen@gmx.de
Signed-off-by: Takashi Iwai <tiwai@suse.de>
Documentation/sound/kernel-api/writing-an-alsa-driver.rst
include/sound/pcm.h
include/uapi/sound/asound.h
sound/core/pcm_lib.c
sound/core/pcm_local.h
sound/core/pcm_native.c

index a368529e8ed3fbe34e6f2f765ccadecff9b68ce4..e37d9dba320d8098348e8e092a34151a5d3bcc4b 100644 (file)
@@ -1577,14 +1577,19 @@ are the contents of this file:
           unsigned int period_step;
           unsigned int sleep_min;              /* min ticks to sleep */
           snd_pcm_uframes_t start_threshold;
-          snd_pcm_uframes_t stop_threshold;
-          snd_pcm_uframes_t silence_threshold; /* Silence filling happens when
-                                                  noise is nearest than this */
-          snd_pcm_uframes_t silence_size;      /* Silence filling size */
+          /*
+           * The following two thresholds alleviate playback buffer underruns; when
+           * 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 silenced_start;
-          snd_pcm_uframes_t silenced_size;
+          /* internal data of auto-silencer */
+          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 */
   
index 27040b472a4f69dec49c3c2bf68e3f840b67d349..19f564606ac42edc8c437b7e458ca1e935204182 100644 (file)
@@ -378,18 +378,18 @@ struct snd_pcm_runtime {
        unsigned int rate_den;
        unsigned int no_period_wakeup: 1;
 
-       /* -- SW params -- */
-       int tstamp_mode;                /* mmap timestamp is updated */
+       /* -- SW params; see struct snd_pcm_sw_params for comments -- */
+       int tstamp_mode;
        unsigned int period_step;
        snd_pcm_uframes_t start_threshold;
        snd_pcm_uframes_t stop_threshold;
-       snd_pcm_uframes_t silence_threshold; /* Silence filling happens when
-                                               noise is nearest than this */
-       snd_pcm_uframes_t silence_size; /* Silence filling size */
-       snd_pcm_uframes_t boundary;     /* pointers wrap point */
+       snd_pcm_uframes_t silence_threshold;
+       snd_pcm_uframes_t silence_size;
+       snd_pcm_uframes_t boundary;
 
+       /* internal data of auto-silencer */
        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 */
 
index 7eecc99ddef7d3782496fd2f4e931154c61f46df..0aa955aa82463a63872a037dbd6ef7d3f08dfec1 100644 (file)
@@ -429,9 +429,14 @@ struct snd_pcm_sw_params {
        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 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 */
-       snd_pcm_uframes_t silence_size;         /* silence block size */
+       /*
+        * The following two thresholds alleviate playback buffer underruns; when
+        * 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 */
        unsigned int proto;                     /* protocol version */
        unsigned int tstamp_type;               /* timestamp type (req. proto >= 2.0.12) */
index af1eb136feb0976ac3b35ba0e3b0018302f35bbe..d21c73944efdeecaca7ddc86dd9fd0facd283638 100644 (file)
@@ -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
  */
-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;
-       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;
 
+       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) {
-               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;
+               if (frames <= 0)
+                       return;
                if (frames > runtime->silence_size)
                        frames = runtime->silence_size;
        } else {
-               if (new_hw_ptr == ULONG_MAX) {  /* initialization */
-                       snd_pcm_sframes_t avail = snd_pcm_playback_hw_avail(runtime);
-                       if (avail > runtime->buffer_size)
-                               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;
+               frames = runtime->buffer_size - noise_dist;
+               if (frames <= 0)
+                       return;
        }
+
        if (snd_BUG_ON(frames > runtime->buffer_size))
                return;
-       if (frames == 0)
-               return;
-       ofs = runtime->silence_start % runtime->buffer_size;
-       while (frames > 0) {
+       ofs = (runtime->silence_start + runtime->silence_filled) % runtime->buffer_size;
+       do {
                transfer = ofs + frames > runtime->buffer_size ? runtime->buffer_size - ofs : frames;
                err = fill_silence_frames(substream, ofs, transfer);
                snd_BUG_ON(err < 0);
                runtime->silence_filled += transfer;
                frames -= transfer;
                ofs = 0;
-       }
+       } while (frames > 0);
        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,
                return 0;
        }
 
-       if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK &&
-           runtime->silence_size > 0)
-               snd_pcm_playback_silence(substream, new_hw_ptr);
-
        if (in_interrupt) {
                delta = new_hw_ptr - runtime->hw_ptr_interrupt;
                if (delta < 0)
@@ -460,6 +442,10 @@ static int snd_pcm_update_hw_ptr0(struct snd_pcm_substream *substream,
                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);
 
        return snd_pcm_update_state(substream, runtime);
index ecb21697ae3a4d66931d85fc13d6eac89b32ddec..42fe3a4e9154f20395a6424dd3c09c2478e96c2a 100644 (file)
@@ -29,8 +29,7 @@ int snd_pcm_update_state(struct snd_pcm_substream *substream,
                         struct snd_pcm_runtime *runtime);
 int snd_pcm_update_hw_ptr(struct snd_pcm_substream *substream);
 
-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);
 
 static inline snd_pcm_uframes_t
 snd_pcm_avail(struct snd_pcm_substream *substream)
index 94185267a7b9212e07b5e7aaf21434a179523b5b..3d0c4a5b701b18b2f4519dbc6fdd30506509cc97 100644 (file)
@@ -958,7 +958,7 @@ static int snd_pcm_sw_params(struct snd_pcm_substream *substream,
        if (snd_pcm_running(substream)) {
                if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK &&
                    runtime->silence_size > 0)
-                       snd_pcm_playback_silence(substream, ULONG_MAX);
+                       snd_pcm_playback_silence(substream);
                err = snd_pcm_update_state(substream, runtime);
        }
        snd_pcm_stream_unlock_irq(substream);
@@ -1455,7 +1455,7 @@ static void snd_pcm_post_start(struct snd_pcm_substream *substream,
        __snd_pcm_set_state(runtime, state);
        if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK &&
            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);
 }
 
@@ -1916,7 +1916,7 @@ static void snd_pcm_post_reset(struct snd_pcm_substream *substream,
        runtime->control->appl_ptr = runtime->status->hw_ptr;
        if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK &&
            runtime->silence_size > 0)
-               snd_pcm_playback_silence(substream, ULONG_MAX);
+               snd_pcm_playback_silence(substream);
        snd_pcm_stream_unlock_irq(substream);
 }