PR #25064 opened by Niklas Haas (haasn)
URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/25064
Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/25064.patch

The old peak-based detection mode works well for digital silence, but basically 
nothing else. The slightest rustle, wind noise or other transient will disturb 
the detection.

Using a BS.1770-gated loudness average gives much more accurate results at 
distinguishing between real content (e.g. speech) and real silence across a 
diverse set of test samples.


>From 0135d3bfd6f537629d3c176ff589e162e0693e17 Mon Sep 17 00:00:00 2001
From: Niklas Haas <[email protected]>
Date: Mon, 14 Sep 2026 15:28:19 +0200
Subject: [PATCH 1/4] Revert "avfilter/ebur128: Remove unused functions"

This partially reverts commit ba874ad904042d0f90e392f45560b170e05512a6.
Needed for the upcoming commits.

Signed-off-by: Niklas Haas <[email protected]>
---
 libavfilter/ebur128.c | 32 ++++++++++++++++++++++++++++----
 libavfilter/ebur128.h | 43 ++++++++++++++++++++++++++++++++++++++++++-
 2 files changed, 70 insertions(+), 5 deletions(-)

diff --git a/libavfilter/ebur128.c b/libavfilter/ebur128.c
index 062099e206..1cb6309b71 100644
--- a/libavfilter/ebur128.c
+++ b/libavfilter/ebur128.c
@@ -363,6 +363,9 @@ static void ebur128_filter_##type(FFEBUR128State* st, const 
type** srcs,
         st->d->v[ci][1] = fabs(st->d->v[ci][1]) < DBL_MIN ? 0.0 : 
st->d->v[ci][1]; \
     }                                                                          
    \
 }
+EBUR128_FILTER(short, -((double)SHRT_MIN))
+EBUR128_FILTER(int, -((double)INT_MIN))
+EBUR128_FILTER(float,  1.0)
 EBUR128_FILTER(double, 1.0)
 
 static double ebur128_energy_to_loudness(double energy)
@@ -457,8 +460,8 @@ int ff_ebur128_set_channel(FFEBUR128State * st,
 }
 
 static int ebur128_energy_shortterm(FFEBUR128State * st, double *out);
-#define EBUR128_ADD_FRAMES_PLANAR(type)                                        
        \
-static void ebur128_add_frames_planar_##type(FFEBUR128State* st, const type** 
srcs,    \
+#define FF_EBUR128_ADD_FRAMES_PLANAR(type)                                     
        \
+void ff_ebur128_add_frames_planar_##type(FFEBUR128State* st, const type** 
srcs,        \
                                  size_t frames, int stride) {                  
        \
     size_t src_index = 0;                                                      
        \
     while (frames > 0) {                                                       
        \
@@ -500,7 +503,10 @@ static void 
ebur128_add_frames_planar_##type(FFEBUR128State* st, const type** sr
         }                                                                      
        \
     }                                                                          
        \
 }
-EBUR128_ADD_FRAMES_PLANAR(double)
+FF_EBUR128_ADD_FRAMES_PLANAR(short)
+FF_EBUR128_ADD_FRAMES_PLANAR(int)
+FF_EBUR128_ADD_FRAMES_PLANAR(float)
+FF_EBUR128_ADD_FRAMES_PLANAR(double)
 #define FF_EBUR128_ADD_FRAMES(type)                                            
\
 void ff_ebur128_add_frames_##type(FFEBUR128State* st, const type* src,         
\
                                     size_t frames) {                           
\
@@ -508,8 +514,11 @@ void ff_ebur128_add_frames_##type(FFEBUR128State* st, 
const type* src,         \
   const type **buf = (const type**)st->d->data_ptrs;                           
\
   for (i = 0; i < st->channels; i++)                                           
\
     buf[i] = src + i;                                                          
\
-  ebur128_add_frames_planar_##type(st, buf, frames, st->channels);             
\
+  ff_ebur128_add_frames_planar_##type(st, buf, frames, st->channels);          
\
 }
+FF_EBUR128_ADD_FRAMES(short)
+FF_EBUR128_ADD_FRAMES(int)
+FF_EBUR128_ADD_FRAMES(float)
 FF_EBUR128_ADD_FRAMES(double)
 
 static int ebur128_calc_relative_threshold(FFEBUR128State **sts, size_t size,
@@ -614,6 +623,21 @@ static int ebur128_energy_shortterm(FFEBUR128State * st, 
double *out)
                                       out);
 }
 
+int ff_ebur128_loudness_momentary(FFEBUR128State * st, double *out)
+{
+    double energy;
+    int error = ebur128_energy_in_interval(st, st->d->samples_in_100ms * 4,
+                                           &energy);
+    if (error) {
+        return error;
+    } else if (energy <= 0.0) {
+        *out = -HUGE_VAL;
+        return 0;
+    }
+    *out = ebur128_energy_to_loudness(energy);
+    return 0;
+}
+
 int ff_ebur128_loudness_shortterm(FFEBUR128State * st, double *out)
 {
     double energy;
diff --git a/libavfilter/ebur128.h b/libavfilter/ebur128.h
index 8e7385e044..830919d1af 100644
--- a/libavfilter/ebur128.h
+++ b/libavfilter/ebur128.h
@@ -81,7 +81,7 @@ enum channel {
  *  modes that suit your needs, as performance will be better.
  */
 enum mode {
-  /** can resurrrect and call ff_ebur128_loudness_momentary */
+  /** can call ff_ebur128_loudness_momentary */
     FF_EBUR128_MODE_M = (1 << 0),
   /** can call ff_ebur128_loudness_shortterm */
     FF_EBUR128_MODE_S = (1 << 1) | FF_EBUR128_MODE_M,
@@ -151,9 +151,41 @@ int ff_ebur128_set_channel(FFEBUR128State * st,
  *  @param src array of source frames. Channels must be interleaved.
  *  @param frames number of frames. Not number of samples!
  */
+void ff_ebur128_add_frames_short(FFEBUR128State * st,
+                                 const short *src, size_t frames);
+/** \brief See \ref ebur128_add_frames_short */
+void ff_ebur128_add_frames_int(FFEBUR128State * st,
+                               const int *src, size_t frames);
+/** \brief See \ref ebur128_add_frames_short */
+void ff_ebur128_add_frames_float(FFEBUR128State * st,
+                                 const float *src, size_t frames);
+/** \brief See \ref ebur128_add_frames_short */
 void ff_ebur128_add_frames_double(FFEBUR128State * st,
                                   const double *src, size_t frames);
 
+/** \brief Add frames to be processed.
+ *
+ *  @param st library state.
+ *  @param srcs array of source frame channel data pointers
+ *  @param frames number of frames. Not number of samples!
+ *  @param stride number of samples to skip to for the next sample of the same 
channel
+ */
+void ff_ebur128_add_frames_planar_short(FFEBUR128State * st,
+                                        const short **srcs,
+                                        size_t frames, int stride);
+/** \brief See \ref ebur128_add_frames_planar_short */
+void ff_ebur128_add_frames_planar_int(FFEBUR128State * st,
+                                      const int **srcs,
+                                      size_t frames, int stride);
+/** \brief See \ref ebur128_add_frames_planar_short */
+void ff_ebur128_add_frames_planar_float(FFEBUR128State * st,
+                                        const float **srcs,
+                                        size_t frames, int stride);
+/** \brief See \ref ebur128_add_frames_planar_short */
+void ff_ebur128_add_frames_planar_double(FFEBUR128State * st,
+                                         const double **srcs,
+                                         size_t frames, int stride);
+
 /** \brief Get global integrated loudness in LUFS.
  *
  *  @param st library state.
@@ -165,6 +197,15 @@ void ff_ebur128_add_frames_double(FFEBUR128State * st,
  */
 int ff_ebur128_loudness_global(FFEBUR128State * st, double *out);
 
+/** \brief Get momentary loudness (last 400ms) in LUFS.
+ *
+ *  @param st library state.
+ *  @param out momentary loudness in LUFS. -HUGE_VAL if result is negative
+ *             infinity.
+ *  @return
+ *    - 0 on success.
+ */
+int ff_ebur128_loudness_momentary(FFEBUR128State * st, double *out);
 /** \brief Get short-term loudness (last 3s) in LUFS.
  *
  *  @param st library state.
-- 
2.52.0


>From 8c0bd33f891bdc708aca65be87092884e4a0108d Mon Sep 17 00:00:00 2001
From: Niklas Haas <[email protected]>
Date: Mon, 14 Sep 2026 16:06:52 +0200
Subject: [PATCH 2/4] avfilter/ebur128: make ff_ebur128_destroy() safe to call
 on NULL

Signed-off-by: Niklas Haas <[email protected]>
---
 libavfilter/ebur128.c | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/libavfilter/ebur128.c b/libavfilter/ebur128.c
index 1cb6309b71..8574364d4e 100644
--- a/libavfilter/ebur128.c
+++ b/libavfilter/ebur128.c
@@ -303,6 +303,8 @@ exit:
 
 void ff_ebur128_destroy(FFEBUR128State ** st)
 {
+    if (!(*st))
+        return;
     av_free((*st)->d->block_energy_histogram);
     av_free((*st)->d->short_term_block_energy_histogram);
     av_free((*st)->d->audio_data);
-- 
2.52.0


>From edffd35ecb8ef904548effd0a2095f61023b8754 Mon Sep 17 00:00:00 2001
From: Niklas Haas <[email protected]>
Date: Mon, 14 Sep 2026 16:17:17 +0200
Subject: [PATCH 3/4] avfilter/af_silencedetect: add BS.1770 loudness
 measurement mode

This can be a more reliable way to detect content silence than looking at
raw sample peaks alone, and will exclude things like isolated glitches
or occasional hiss that happens to be mastered above the chosen noise
threshold.

Because BS.1770 momentary loudness is updated only once every 100ms block,
we cache the result of the previous loudness measurement and report that
for every sample in the same 100ms window.

Signed-off-by: Niklas Haas <[email protected]>
---
 doc/filters.texi               |  18 ++++
 libavfilter/Makefile           |   2 +-
 libavfilter/af_silencedetect.c | 175 ++++++++++++++++++++++++++++-----
 3 files changed, 170 insertions(+), 25 deletions(-)

diff --git a/doc/filters.texi b/doc/filters.texi
index 04496d338f..81a2cda3b9 100644
--- a/doc/filters.texi
+++ b/doc/filters.texi
@@ -6506,6 +6506,18 @@ for the accepted syntax.
 
 @item mono, m
 Process each channel separately, instead of combined. By default is disabled.
+
+@item mode
+Affects how silence is classified. Accepts the following values:
+@table @samp
+@item peak
+Compare the absolute value of each sample to the noise tolerance. This is the
+default mode.
+
+@item loudness
+Compare the momentary loudness, as defined by ITU-R BS.1770, to the noise
+tolerance in LUFS.
+@end table
 @end table
 
 @subsection Examples
@@ -6517,6 +6529,12 @@ Detect 5 seconds of silence with -50dB noise tolerance:
 silencedetect=n=-50dB:d=5
 @end example
 
+@item
+Detect 2 seconds of silence, with a -35 LUFS noise tolerance:
+@example
+silencedetect=mode=loudness:n=-35dB:d=2
+@end example
+
 @item
 Complete example with @command{ffmpeg} to detect silence with 0.0001 noise
 tolerance in @file{silence.mp3}:
diff --git a/libavfilter/Makefile b/libavfilter/Makefile
index 15d0b23af9..6b1ad27ef2 100644
--- a/libavfilter/Makefile
+++ b/libavfilter/Makefile
@@ -161,7 +161,7 @@ OBJS-$(CONFIG_REPLAYGAIN_FILTER)             += 
af_replaygain.o
 OBJS-$(CONFIG_RUBBERBAND_FILTER)             += af_rubberband.o
 OBJS-$(CONFIG_SIDECHAINCOMPRESS_FILTER)      += af_sidechaincompress.o
 OBJS-$(CONFIG_SIDECHAINGATE_FILTER)          += af_agate.o
-OBJS-$(CONFIG_SILENCEDETECT_FILTER)          += af_silencedetect.o
+OBJS-$(CONFIG_SILENCEDETECT_FILTER)          += af_silencedetect.o ebur128.o
 OBJS-$(CONFIG_SILENCEREMOVE_FILTER)          += af_silenceremove.o
 OBJS-$(CONFIG_SOFALIZER_FILTER)              += af_sofalizer.o
 OBJS-$(CONFIG_SPEECHNORM_FILTER)             += af_speechnorm.o
diff --git a/libavfilter/af_silencedetect.c b/libavfilter/af_silencedetect.c
index bcdc90f8e3..4b0b8523f7 100644
--- a/libavfilter/af_silencedetect.c
+++ b/libavfilter/af_silencedetect.c
@@ -24,18 +24,29 @@
  */
 
 #include <float.h> /* DBL_MAX */
+#include <math.h>
 
 #include "libavutil/mem.h"
 #include "libavutil/opt.h"
 #include "libavutil/timestamp.h"
 #include "audio.h"
 #include "avfilter.h"
+#include "ebur128.h"
 #include "filters.h"
 
+enum SilenceDetectMode {
+    MODE_PEAK,
+    MODE_LOUDNESS,
+    MODE_NB,
+};
+
 typedef struct SilenceDetectContext {
     const AVClass *class;
+    FFEBUR128State **r128;
+
     double noise;               ///< noise amplitude ratio
     int64_t duration;           ///< minimum duration of silence until 
notification
+    int mode;                   ///< enum SilenceDetectMode
     int mono;                   ///< mono mode : check each channel separately 
(default = check when ALL channels are silent)
     int channels;               ///< number of channels
     int independent_channels;   ///< number of entries in following arrays 
(always 1 in mono mode)
@@ -44,6 +55,8 @@ typedef struct SilenceDetectContext {
     int64_t frame_end;          ///< pts of the end of the current frame (used 
to compute duration of silence at EOS)
     int last_sample_rate;       ///< last sample rate to check for sample rate 
changes
     AVRational time_base;       ///< time_base
+    int nb_pending_samples;     ///< number of samples until loudness is 
measured
+    int *is_silence;            ///< (array) result of previous loudness 
measurement
 
     void (*silencedetect)(AVFilterContext *ctx, AVFrame *insamples,
                           int nb_samples, int64_t nb_samples_notify,
@@ -51,9 +64,14 @@ typedef struct SilenceDetectContext {
 } SilenceDetectContext;
 
 #define MAX_DURATION (24*3600*1000000LL)
+#define LOUDNESS_STEP(sample_rate)   (((sample_rate) + 5) / 10)       /* 100 
ms */
+#define LOUDNESS_WINDOW(sample_rate) (4 * LOUDNESS_STEP(sample_rate)) /* 400 
ms */
 #define OFFSET(x) offsetof(SilenceDetectContext, x)
 #define FLAGS AV_OPT_FLAG_FILTERING_PARAM|AV_OPT_FLAG_AUDIO_PARAM
 static const AVOption silencedetect_options[] = {
+    { "mode",      "set how to detect silence",        OFFSET(mode),      
AV_OPT_TYPE_INT,    {.i64=MODE_PEAK},      0, MODE_NB-1,FLAGS, .unit = "mode" },
+    {   "peak",     "detect each sample peak",         0,                 
AV_OPT_TYPE_CONST,  {.i64=MODE_PEAK},      0, 0,        FLAGS, .unit = "mode" },
+    {   "loudness", "detect momentary loudness",       0,                 
AV_OPT_TYPE_CONST,  {.i64=MODE_LOUDNESS},  0, 0,        FLAGS, .unit = "mode" },
     { "n",         "set noise tolerance",              OFFSET(noise),     
AV_OPT_TYPE_DOUBLE, {.dbl=0.001},          0, DBL_MAX,  FLAGS },
     { "noise",     "set noise tolerance",              OFFSET(noise),     
AV_OPT_TYPE_DOUBLE, {.dbl=0.001},          0, DBL_MAX,  FLAGS },
     { "d",         "set minimum duration in seconds",  OFFSET(duration),  
AV_OPT_TYPE_DURATION, {.i64=2000000},      0, MAX_DURATION,FLAGS },
@@ -100,6 +118,14 @@ static av_always_inline void update(AVFilterContext *ctx, 
AVFrame *insamples,
             int64_t end_pts = insamples ? insamples->pts + 
av_rescale_q(current_sample / s->channels,
                     (AVRational){ 1, s->last_sample_rate }, time_base)
                     : s->frame_end;
+
+            /* momentary loudness lags the actual signal by up to the window
+             * size when it rises, so report the end of silence earlier to
+             * compensate, except at the end of the stream */
+            if (insamples && s->mode == MODE_LOUDNESS)
+                end_pts -= av_rescale_q(LOUDNESS_WINDOW(s->last_sample_rate),
+                                        (AVRational){ 1, s->last_sample_rate 
}, time_base);
+
             int64_t duration_ts = end_pts - s->start[channel];
             if (insamples) {
                 set_meta(insamples, s->mono ? channel + 1 : 0, "silence_end",
@@ -163,6 +189,70 @@ SILENCE_DETECT_PLANAR(fltp, float)
 SILENCE_DETECT_PLANAR(s32p, int32_t)
 SILENCE_DETECT_PLANAR(s16p, int16_t)
 
+static void update_loudness(SilenceDetectContext *s)
+{
+    /* measure the loudness every 100ms, per ITU-R BS.1770 */
+    if (--s->nb_pending_samples)
+        return;
+
+    s->nb_pending_samples = LOUDNESS_STEP(s->last_sample_rate);
+    for (int ch = 0; ch < s->channels; ch++) {
+        double loudness;
+        ff_ebur128_loudness_momentary(s->r128[ch], &loudness);
+        s->is_silence[ch] = loudness < s->noise;
+    }
+}
+
+#define SILENCE_DETECT_LOUDNESS(name, type)                                    
  \
+static void silencedetect_##name(AVFilterContext *ctx, AVFrame *insamples,     
  \
+                                 int nb_samples, int64_t nb_samples_notify,    
  \
+                                 AVRational time_base)                         
  \
+{                                                                              
  \
+    SilenceDetectContext *s = ctx->priv;                                       
  \
+    const int channels = insamples->ch_layout.nb_channels;                     
  \
+    const type *p = (const type *) insamples->data[0];                         
  \
+                                                                               
  \
+    nb_samples /= channels;                                                    
  \
+    for (int i = 0; i < nb_samples; i++) {                                     
  \
+        for (int ch = 0; ch < channels; ch++, p++) {                           
  \
+            update(ctx, insamples, s->is_silence[ch], channels * i + ch,       
  \
+                   nb_samples_notify, time_base);                              
  \
+            ff_ebur128_add_frames_##type(s->r128[ch], p, 1);                   
  \
+        }                                                                      
  \
+        update_loudness(s);                                                    
  \
+    }                                                                          
  \
+}
+
+#define SILENCE_DETECT_LOUDNESS_PLANAR(name, type)                             
  \
+static void silencedetect_##name(AVFilterContext *ctx, AVFrame *insamples,     
  \
+                                 int nb_samples, int64_t nb_samples_notify,    
  \
+                                 AVRational time_base)                         
  \
+{                                                                              
  \
+    SilenceDetectContext *s = ctx->priv;                                       
  \
+    const int channels = insamples->ch_layout.nb_channels;                     
  \
+                                                                               
  \
+    nb_samples /= channels;                                                    
  \
+    for (int i = 0; i < nb_samples; i++) {                                     
  \
+        for (int ch = 0; ch < channels; ch++) {                                
  \
+            const type *p = (const type *) insamples->extended_data[ch];       
  \
+            update(ctx, insamples, s->is_silence[ch], channels * i + ch,       
  \
+                   nb_samples_notify, time_base);                              
  \
+            ff_ebur128_add_frames_##type(s->r128[ch], &p[i], 1);               
  \
+        }                                                                      
  \
+        update_loudness(s);                                                    
  \
+    }                                                                          
  \
+}
+
+SILENCE_DETECT_LOUDNESS(dbl_loudness, double)
+SILENCE_DETECT_LOUDNESS(flt_loudness, float)
+SILENCE_DETECT_LOUDNESS(s32_loudness, int)
+SILENCE_DETECT_LOUDNESS(s16_loudness, short)
+
+SILENCE_DETECT_LOUDNESS_PLANAR(dblp_loudness, double)
+SILENCE_DETECT_LOUDNESS_PLANAR(fltp_loudness, float)
+SILENCE_DETECT_LOUDNESS_PLANAR(s32p_loudness, int)
+SILENCE_DETECT_LOUDNESS_PLANAR(s16p_loudness, short)
+
 static int config_input(AVFilterLink *inlink)
 {
     AVFilterContext *ctx = inlink->dst;
@@ -182,29 +272,61 @@ static int config_input(AVFilterLink *inlink)
     for (c = 0; c < s->independent_channels; c++)
         s->start[c] = INT64_MIN;
 
-    switch (inlink->format) {
-    case AV_SAMPLE_FMT_DBL: s->silencedetect = silencedetect_dbl; break;
-    case AV_SAMPLE_FMT_FLT: s->silencedetect = silencedetect_flt; break;
-    case AV_SAMPLE_FMT_S32:
-        s->noise *= INT32_MAX;
-        s->silencedetect = silencedetect_s32;
+    switch (s->mode) {
+    case MODE_PEAK:
+        switch (inlink->format) {
+        case AV_SAMPLE_FMT_DBL: s->silencedetect = silencedetect_dbl; break;
+        case AV_SAMPLE_FMT_FLT: s->silencedetect = silencedetect_flt; break;
+        case AV_SAMPLE_FMT_S32:
+            s->noise *= INT32_MAX;
+            s->silencedetect = silencedetect_s32;
+            break;
+        case AV_SAMPLE_FMT_S16:
+            s->noise *= INT16_MAX;
+            s->silencedetect = silencedetect_s16;
+            break;
+        case AV_SAMPLE_FMT_DBLP: s->silencedetect = silencedetect_dblp; break;
+        case AV_SAMPLE_FMT_FLTP: s->silencedetect = silencedetect_fltp; break;
+        case AV_SAMPLE_FMT_S32P:
+            s->noise *= INT32_MAX;
+            s->silencedetect = silencedetect_s32p;
+            break;
+        case AV_SAMPLE_FMT_S16P:
+            s->noise *= INT16_MAX;
+            s->silencedetect = silencedetect_s16p;
+            break;
+        default:
+            return AVERROR_BUG;
+        }
         break;
-    case AV_SAMPLE_FMT_S16:
-        s->noise *= INT16_MAX;
-        s->silencedetect = silencedetect_s16;
+
+    case MODE_LOUDNESS:
+        s->r128       = av_calloc(s->channels, sizeof(*s->r128));
+        s->is_silence = av_calloc(s->channels, sizeof(*s->is_silence));
+        if (!s->r128 || !s->is_silence)
+            return AVERROR(ENOMEM);
+        for (c = 0; c < s->channels; c++) {
+            s->r128[c] = ff_ebur128_init(1, inlink->sample_rate, 0, 
FF_EBUR128_MODE_M);
+            if (!s->r128[c])
+                return AVERROR(ENOMEM);
+        }
+        s->nb_pending_samples = LOUDNESS_STEP(inlink->sample_rate);
+        s->noise = 20 * log10(s->noise); /* convert to LUFS */
+        /* silence must outlast the window its end is moved back by, see 
update() */
+        s->duration += LOUDNESS_WINDOW(inlink->sample_rate);
+
+        switch (inlink->format) {
+        case AV_SAMPLE_FMT_DBL:  s->silencedetect = 
silencedetect_dbl_loudness;  break;
+        case AV_SAMPLE_FMT_FLT:  s->silencedetect = 
silencedetect_flt_loudness;  break;
+        case AV_SAMPLE_FMT_S32:  s->silencedetect = 
silencedetect_s32_loudness;  break;
+        case AV_SAMPLE_FMT_S16:  s->silencedetect = 
silencedetect_s16_loudness;  break;
+        case AV_SAMPLE_FMT_DBLP: s->silencedetect = 
silencedetect_dblp_loudness; break;
+        case AV_SAMPLE_FMT_FLTP: s->silencedetect = 
silencedetect_fltp_loudness; break;
+        case AV_SAMPLE_FMT_S32P: s->silencedetect = 
silencedetect_s32p_loudness; break;
+        case AV_SAMPLE_FMT_S16P: s->silencedetect = 
silencedetect_s16p_loudness; break;
+        default: return AVERROR_BUG;
+        }
         break;
-    case AV_SAMPLE_FMT_DBLP: s->silencedetect = silencedetect_dblp; break;
-    case AV_SAMPLE_FMT_FLTP: s->silencedetect = silencedetect_fltp; break;
-    case AV_SAMPLE_FMT_S32P:
-        s->noise *= INT32_MAX;
-        s->silencedetect = silencedetect_s32p;
-        break;
-    case AV_SAMPLE_FMT_S16P:
-        s->noise *= INT16_MAX;
-        s->silencedetect = silencedetect_s16p;
-        break;
-    default:
-        return AVERROR_BUG;
     }
 
     return 0;
@@ -221,10 +343,11 @@ static int filter_frame(AVFilterLink *inlink, AVFrame 
*insamples)
     int c;
 
     // scale number of null samples to the new sample rate
-    if (s->last_sample_rate && s->last_sample_rate != srate)
-        for (c = 0; c < s->independent_channels; c++) {
+    if (s->last_sample_rate && s->last_sample_rate != srate) {
+        for (c = 0; c < s->independent_channels; c++)
             s->nb_null_samples[c] = srate * s->nb_null_samples[c] / 
s->last_sample_rate;
-        }
+        s->nb_pending_samples = srate * s->nb_pending_samples / 
s->last_sample_rate;
+    }
     s->last_sample_rate = srate;
     s->time_base = inlink->time_base;
     s->frame_end = insamples->pts + av_rescale_q(insamples->nb_samples,
@@ -244,6 +367,10 @@ static av_cold void uninit(AVFilterContext *ctx)
     for (c = 0; c < s->independent_channels; c++)
         if (s->start[c] > INT64_MIN)
             update(ctx, NULL, 0, c, 0, s->time_base);
+    for (c = 0; s->r128 && c < s->channels; c++)
+        ff_ebur128_destroy(&s->r128[c]);
+    av_freep(&s->r128);
+    av_freep(&s->is_silence);
     av_freep(&s->nb_null_samples);
     av_freep(&s->start);
 }
-- 
2.52.0


>From 4e4aa0436088325b1fe3c00379ce7fa10919c884 Mon Sep 17 00:00:00 2001
From: Niklas Haas <[email protected]>
Date: Mon, 14 Sep 2026 17:31:50 +0200
Subject: [PATCH 4/4] tests/fate/filter-video: add af_silencedetect loudness
 mode test

Signed-off-by: Niklas Haas <[email protected]>
---
 tests/fate/filter-video.mak                          |  4 ++++
 .../ref/fate/filter-metadata-silencedetect-loudness  | 12 ++++++++++++
 2 files changed, 16 insertions(+)
 create mode 100644 tests/ref/fate/filter-metadata-silencedetect-loudness

diff --git a/tests/fate/filter-video.mak b/tests/fate/filter-video.mak
index ebbc0a7d1c..8551212890 100644
--- a/tests/fate/filter-video.mak
+++ b/tests/fate/filter-video.mak
@@ -918,6 +918,10 @@ FATE_METADATA_FILTER-$(call ALLYES, $(SILENCEDETECT_DEPS)) 
+= fate-filter-metada
 fate-filter-metadata-silencedetect: SRC = 
$(TARGET_SAMPLES)/lossless-audio/inside.tta
 fate-filter-metadata-silencedetect: CMD = run $(FILTER_METADATA_COMMAND) 
"amovie='$(SRC)',silencedetect=n=-33.5dB:d=0.2"
 
+FATE_METADATA_FILTER-$(call ALLYES, $(SILENCEDETECT_DEPS)) += 
fate-filter-metadata-silencedetect-loudness
+fate-filter-metadata-silencedetect-loudness: SRC = 
$(TARGET_SAMPLES)/lossless-audio/inside.tta
+fate-filter-metadata-silencedetect-loudness: CMD = run 
$(FILTER_METADATA_COMMAND) 
"amovie='$(SRC)',silencedetect=mode=loudness:n=-30dB:d=0.3"
+
 EBUR128_METADATA_DEPS = LAVFI_INDEV AMOVIE_FILTER FLAC_DEMUXER FLAC_DECODER 
ARESAMPLE_FILTER EBUR128_FILTER
 FATE_METADATA_FILTER-$(call ALLYES, $(EBUR128_METADATA_DEPS)) += 
fate-filter-metadata-ebur128
 fate-filter-metadata-ebur128: SRC = 
$(TARGET_SAMPLES)/filter/seq-3341-7_seq-3342-5-24bit.flac
diff --git a/tests/ref/fate/filter-metadata-silencedetect-loudness 
b/tests/ref/fate/filter-metadata-silencedetect-loudness
new file mode 100644
index 0000000000..11b35037eb
--- /dev/null
+++ b/tests/ref/fate/filter-metadata-silencedetect-loudness
@@ -0,0 +1,12 @@
+pts=0|tag:lavfi.silence_start=0.1
+pts=46080|tag:lavfi.silence_end=1.5|tag:lavfi.silence_duration=1.4
+pts=92160
+pts=138240
+pts=184320
+pts=230400
+pts=276480
+pts=322560
+pts=368640
+pts=414720
+pts=460800
+pts=506880
-- 
2.52.0

_______________________________________________
ffmpeg-devel mailing list -- [email protected]
To unsubscribe send an email to [email protected]

Reply via email to