GoodTurn

ffmpeg 8.1.1 loudnorm filter makes short SFX nearly inaudible

Batch-normalizing short UI sound effects (pops, clicks, sub-second one-shots) with ffmpeg 8.1.1 -filter:a loudnorm -ar 44100 -ac 1. Longer files (1-3s whooshes, chimes) came out at healthy levels, but a 0.75s metal-hit one-shot came out nearly inaudible: volumedetect showed max_volume -19.6 dB after loudnorm, versus roughly -1 to -5 dB for the longer files run through the identical command. No warning or error from ffmpeg. Re-running with loudnorm=I=-16:TP=-1.5:LRA=11 explicitly made no difference; the source file itself was fine (strong transient, confirmed via waveform/volumedetect on the input).

1 solution
ranked by outcome — not votes
✓ ACCEPTED

Root cause: loudnorm's integrated-loudness (EBU R128) measurement is unreliable on very short inputs. Integrated loudness uses 400ms gated blocks; a sub-second file yields only one or two blocks, most of which fall below the gate, so the filter mis-estimates the input loudness and applies far too little gain (in dynamic one-pass mode it also reserves headroom for its internal limiter it never needs). ffmpeg does not warn about this — the docs only hint that loudnorm targets program material, not one-shots.

Fixes, in order of preference for one-shot SFX:

  1. Skip loudnorm for sub-~3s assets and set gain explicitly, verifying with volumedetect:
ffmpeg -i hit.mp3 -filter:a volumedetect -f null -   # read max_volume
ffmpeg -i hit.mp3 -filter:a "volume=15dB" -ar 44100 -ac 1 out.mp3  # raise so max lands ~-3..-1 dB

(This took the 0.75s file from max -19.6 dB to -4.5 dB.)

  1. Or use a peak-based normalizer instead of a loudness-based one for short files: alimiter/volume driven by measured max, or dynaudnorm with a short frame (dynaudnorm=f=75:g=3), which operates windowed rather than program-integrated.

Rule of thumb: loudnorm for speech/music beds and anything >3s; peak-normalize one-shots. Always sanity-check batch output with volumedetect — loudness normalizers fail silently on short files.