From: realies via ffmpeg-devel <ffmpeg-devel@ffmpeg.org>
To: ffmpeg-devel@ffmpeg.org
Cc: realies <code@ffmpeg.org>
Subject: [FFmpeg-devel] [PR] avfilter/af_loudnorm: fix slow gain ramp after initial silence (PR #21449)
Date: Tue, 13 Jan 2026 15:27:25 -0000
Message-ID: <176831804580.25.9165504325125381743@4457048688e7> (raw)
PR #21449 opened by realies
URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/21449
Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/21449.patch
## Summary
- Fixes fade-in artifact when audio begins with silence (3+ seconds)
- When first 3s of audio is below `measured_thresh`, the filter set `above_threshold=0`
- This caused gain to ramp at only 0.5 dB/second instead of applying correct gain immediately
- Audio took 20+ seconds to reach steady state after silence ended
## Root Cause
In INNER_FRAME processing with `above_threshold=0`:
- Gain was increased via `prev_delta *= 1.0058` (~0.05 dB per 100ms)
- `above_threshold` only changed to 1 when OUTPUT reached target
- Created a catch-22: output couldn't reach target because gain was too low
## Fix
When input short-term loudness exceeds `measured_thresh`, immediately transition to normal gain mode by:
1. Setting `above_threshold = 1`
2. Reinitializing all `delta[]` values with the correct gain
This ensures proper gain is applied immediately when audio starts after silence.
## Test plan
- [x] Tested with 11 different silence durations (0s to 10s) followed by constant tone
- [x] All tests pass with fix (previously 5 failed with silence >= 3s)
- [x] LUFS compliance tests pass
- [x] True peak compliance tests pass
- [x] Limiter and gating tests pass
Fixes: https://github.com/slhck/ffmpeg-normalize/issues/146
>From 5f0547f2e61cd472fc12715d301b2d608d11a945 Mon Sep 17 00:00:00 2001
From: realies <ffmpeg@reali.es>
Date: Tue, 13 Jan 2026 16:48:14 +0200
Subject: [PATCH] avfilter/af_loudnorm: fix slow gain ramp after initial
silence
When audio begins with silence (below measured_thresh), the filter
would set above_threshold=0 and then slowly ramp up gain at only
0.5 dB/second when audio content started. This caused audible
fade-in artifacts taking 20+ seconds to reach steady state.
The bug was in the INNER_FRAME processing: when above_threshold=0,
gain was increased via prev_delta *= 1.0058 (~0.05 dB per 100ms),
and above_threshold only changed to 1 when OUTPUT reached target.
This created a catch-22: output couldn't reach target because gain
was too low.
Fix: When input short-term loudness exceeds measured_thresh,
immediately transition to normal gain mode by:
1. Setting above_threshold = 1
2. Reinitializing all delta[] values with the correct gain
This ensures proper gain is applied immediately when audio starts
after silence, rather than slowly ramping via Gaussian smoothing
of stale values.
Fixes fade-in artifacts reported in:
- https://github.com/slhck/ffmpeg-normalize/issues/146
---
libavfilter/af_loudnorm.c | 19 ++++++++++++-------
1 file changed, 12 insertions(+), 7 deletions(-)
diff --git a/libavfilter/af_loudnorm.c b/libavfilter/af_loudnorm.c
index 432b9710a5..5c047cb57d 100644
--- a/libavfilter/af_loudnorm.c
+++ b/libavfilter/af_loudnorm.c
@@ -543,14 +543,19 @@ static int filter_frame(AVFilterLink *inlink, AVFrame *in)
ff_ebur128_relative_threshold(s->r128_in, &relative_threshold);
if (s->above_threshold == 0) {
- double shortterm_out;
-
- if (shortterm > s->measured_thresh)
- s->prev_delta *= 1.0058;
-
- ff_ebur128_loudness_shortterm(s->r128_out, &shortterm_out);
- if (shortterm_out >= s->target_i)
+ if (shortterm > s->measured_thresh) {
+ /* Input has exceeded threshold, transition to normal gain mode.
+ * Reinitialize all delta values with the correct gain to ensure
+ * immediate proper gain application when audio starts after
+ * silence, rather than slowly ramping up via Gaussian smoothing
+ * of stale values. */
+ double env_st = s->target_i - shortterm;
+ double new_delta = pow(10., env_st / 20.);
+ for (int i = 0; i < 30; i++)
+ s->delta[i] = new_delta;
+ s->prev_delta = new_delta;
s->above_threshold = 1;
+ }
}
if (shortterm < relative_threshold || shortterm <= -70. || s->above_threshold == 0) {
--
2.49.1
_______________________________________________
ffmpeg-devel mailing list -- ffmpeg-devel@ffmpeg.org
To unsubscribe send an email to ffmpeg-devel-leave@ffmpeg.org
reply other threads:[~2026-01-13 15:27 UTC|newest]
Thread overview: [no followups] expand[flat|nested] mbox.gz Atom feed
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=176831804580.25.9165504325125381743@4457048688e7 \
--to=ffmpeg-devel@ffmpeg.org \
--cc=code@ffmpeg.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Git Inbox Mirror of the ffmpeg-devel mailing list - see https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
This inbox may be cloned and mirrored by anyone:
git clone --mirror https://master.gitmailbox.com/ffmpegdev/0 ffmpegdev/git/0.git
# If you have public-inbox 1.1+ installed, you may
# initialize and index your mirror using the following commands:
public-inbox-init -V2 ffmpegdev ffmpegdev/ https://master.gitmailbox.com/ffmpegdev \
ffmpegdev@gitmailbox.com
public-inbox-index ffmpegdev
Example config snippet for mirrors.
AGPL code for this site: git clone https://public-inbox.org/public-inbox.git