Normalize audio loudness with FFmpeg
loudnorm brings a file to a loudness target measured the way streaming platforms measure it (EBU R128, in LUFS). The commands below were run on a test file sitting at -21.8 LUFS and each result was re-measured with ebur128.
Verified with FFmpeg 9.0.1 on 26 August 2026. Search intent: ffmpeg normalize audio.
ffmpeg -i input.mp4 -af "loudnorm=I=-16:TP=-1.5:LRA=11,aresample=48000" -c:v copy -c:a aac -b:a 160k output.mp4One pass to -16 LUFS integrated with a true-peak ceiling of -1.5 dBTP, video copied untouched. The test file went from -21.8 LUFS to -16.0 LUFS. aresample=48000 matters: without it loudnorm handed the encoder 96 kHz audio.
Flags, explained
- loudnorm=I=-16
- Target integrated loudness in LUFS. -14 for streaming platforms, -16 for podcasts, -23 for broadcast.
- TP=-1.5
- True-peak ceiling in dBTP. Keeps inter-sample peaks below -1.5 dB so lossy encoders do not clip.
- LRA=11
- Target loudness range in LU. Only matters in dynamic mode; a short synthetic file measures 0.
- aresample=48000
- Bring the audio back to 48 kHz. loudnorm works internally at a high sample rate and, on this build, output 96 kHz without it.
- linear=true + measured_*
- Second-pass mode: apply one constant gain computed from the first-pass measurements instead of adapting over time.
- -c:v copy
- Video is not touched, so the job costs only the audio decode and encode.
- volume=3dB
- Fixed gain. Accepts dB or a linear factor (volume=1.4).
Variants
Two passes: measure first, then apply a linear gain
ffmpeg -i input.mp4 -af "loudnorm=I=-16:TP=-1.5:LRA=11:print_format=json" -f null -The first pass prints a JSON block (input_i, input_tp, input_lra, input_thresh, target_offset). Feed those numbers back with linear=true so the whole file gets one constant gain instead of a gain that moves over time. On the test file: input_i -21.75, input_tp -16.32, input_lra 0.00, input_thresh -31.75, target_offset 0.04.
Second pass with the measured values
ffmpeg -i input.mp4 -af "loudnorm=I=-16:TP=-1.5:LRA=11:measured_I=-21.75:measured_TP=-16.32:measured_LRA=0:measured_thresh=-31.75:offset=0.04:linear=true,aresample=48000" -c:v copy -c:a aac -b:a 160k output.mp4Result: -16.0 LUFS integrated, peak -10.6 dBFS, 48 kHz. Same loudness as the one-pass run, but the dynamics of the source are preserved exactly.
Podcast or music file to the streaming target (-14 LUFS)
ffmpeg -i input.mp3 -af "loudnorm=I=-14:TP=-1:LRA=11,aresample=44100" -c:a libmp3lame -q:a 2 output.mp3A very quiet test file (-40.1 LUFS) came out at -13.7 LUFS. Streaming services generally normalise around -14 LUFS, podcasts around -16, broadcast at -23.
Plain gain when you only need a few dB
ffmpeg -i input.mp4 -af "volume=3dB" -c:v copy -c:a aac -b:a 160k output.mp4No measurement, no limiter: +3 dB moved the file from -21.8 to -18.8 LUFS and the peak from -16.3 to -13.5 dBFS. Check the peak first (next variant) so the gain does not clip.
Measure loudness and peak without writing a file
ffmpeg -i input.mp4 -af "ebur128=peak=true" -f null -Prints integrated loudness (I), loudness range (LRA) and true peak. The test file reported I: -21.8 LUFS, Peak: -16.3 dBFS. volumedetect is the older alternative and reports mean_volume and max_volume in dB.
Pitfalls we hit
- Without aresample the output sample rate changes: the one-pass command produced 96 kHz AAC (1,126 frames for 12 s instead of 564). Most players cope, some hardware does not, and the file is larger for nothing.
- One-pass loudnorm is dynamic (the JSON says normalization_type: dynamic): the gain moves during the file. For music or anything mastered, run two passes with linear=true.
- Normalising loudness and normalising peaks are different things. A file peaking at -0.1 dBFS can still be quiet; volume= only shifts the peak, loudnorm targets perceived loudness and limits the peak.
- Loudness range on short or synthetic material comes out as 0 LU; the LRA target then does nothing. Measure real speech or music before judging the settings.
- volume=3dB on a file that already peaks near 0 dBFS clips. Read max_volume with volumedetect or Peak with ebur128 first.
How we verified it
Run with FFmpeg 9.0.1 on 26 August 2026 against a 12 s H.264/AAC file measured at -21.8 LUFS, peak -16.3 dBFS. One-pass loudnorm: -16.0 LUFS, 96 kHz without aresample, 48 kHz with it. Two-pass with the printed measurements and linear=true: -16.0 LUFS, peak -10.6 dBFS. Quiet MP3 at -40.1 LUFS with I=-14: -13.7 LUFS. volume=3dB: -18.8 LUFS, peak -13.5 dBFS.
The same edit as a typed operation
normalize_audio takes a named target (streaming -14 LUFS, podcast -16, broadcast -23), measures and applies the gain in one job, keeps the sample rate and the video stream, and returns a signed URL. The target is validated before anything renders.
{
"inputs": [{ "id": "main", "url": "https://example.com/video.mp4" }],
"options": { "target": "streaming" }
}FAQ
What is the difference between peak normalization and loudness normalization?
Peak normalization scales the file so its loudest sample hits a ceiling; two files can end up at the same peak and sound very different. Loudness normalization (loudnorm, LUFS) targets how loud the file is perceived over time, which is what streaming platforms use.
Why did the output sample rate become 96 kHz?
loudnorm processes at a higher internal rate and passes that rate downstream. Append aresample=48000 (or 44100 for MP3) to the filter chain to restore the original rate.
Should I use one pass or two?
One pass is fine for voice recordings and quick jobs. Two passes with linear=true give a single constant gain, so dynamics are preserved; use them for music, mastered content or anything you will publish.
Which loudness target should I pick?
Around -14 LUFS for streaming and social platforms, -16 LUFS for podcasts, -23 LUFS for broadcast deliverables. Match the platform you deliver to rather than pushing louder.
Related commands
- ffmpeg extract audioExtract audio from a video with FFmpegRead
- ffmpeg remove silenceRemove silence from audio with FFmpegRead
- ffmpeg silencedetectDetect silence with FFmpegRead