Ever had one video clip that's whisper-quiet followed by another that blows out your speakers? Audio loudness inconsistency is one of the most common complaints in video production — and one of the most asked-about topics on r/ffmpeg and audio engineering forums.
FFmpeg's loudnorm filter normalizes audio to a target loudness level using the EBU R128 standard. The basic command is ffmpeg -i input.mp4 -af loudnorm=I=-16:TP=-1.5:LRA=11 output.mp4 — this brings your audio to -16 LUFS, which works for YouTube, Spotify, and most streaming platforms.
This guide covers everything from quick single-pass normalization to precise two-pass workflows, platform-specific loudness targets, and batch processing with ffmpeg-normalize.
What you'll learn
- How the loudnorm filter works and what each parameter means
- Single-pass vs. two-pass normalization and when to use each
- Loudness targets for YouTube, Spotify, Apple, TikTok, and broadcast
- How to batch-normalize with ffmpeg-normalize
- How loudnorm compares to volume, dynaudnorm, and compand
How the loudnorm Filter Works
The loudnorm filter measures perceived loudness using the ITU-R BS.1770 algorithm (K-weighted filtering with gating) and adjusts gain to hit a target level. Internally, it upsamples to 192 kHz for accurate true-peak detection, then applies a loudness-tuned AGC with a 100 ms lookahead true-peak limiter.
Single-pass normalization
The simplest way to normalize audio:
ffmpeg -i input.mp4 -af loudnorm=I=-16:TP=-1.5:LRA=11 output.mp4
What each parameter does:
| Parameter | Meaning | Range | Default |
|---|---|---|---|
I | Target integrated loudness (LUFS) | -70.0 to -5.0 | -24.0 |
TP | Maximum true peak (dBTP) | -9.0 to 0.0 | -2.0 |
LRA | Target loudness range (LU) | 1.0 to 50.0 | 7.0 |
Single-pass mode uses dynamic normalization — the AGC adjusts gain in real time as it processes the audio. This works well for quick jobs and live streaming, but the result may not be perfectly linear because the filter hasn't seen the entire file yet.
Measuring loudness before normalization
Before normalizing, it's useful to check the current loudness of your file:
ffmpeg -i input.mp4 -af loudnorm=I=-16:TP=-1.5:LRA=11:print_format=json -f null -
This outputs JSON with the measured values:
{
"input_i": "-27.61",
"input_tp": "-4.47",
"input_lra": "18.06",
"input_thresh": "-39.20",
"output_i": "-16.58",
"output_tp": "-1.50",
"output_lra": "14.78",
"output_thresh": "-27.71",
"normalization_type": "dynamic",
"target_offset": "0.58"
}
The input_i tells you how far off the source is from your target. The normalization_type field shows whether linear or dynamic normalization was applied.
Two-Pass Normalization for Precision
Two-pass normalization first measures the entire file, then applies the exact correction. This enables linear normalization — a constant gain applied uniformly, preserving the original dynamics without any AGC coloring.
Pass 1: Measure
ffmpeg -i input.mp4 -af loudnorm=I=-16:TP=-1.5:LRA=11:print_format=json -f null - 2>&1
Save the JSON output. You need input_i, input_tp, input_lra, input_thresh, and target_offset.
Pass 2: Apply
ffmpeg -i input.mp4 -af loudnorm=I=-16:TP=-1.5:LRA=11:measured_I=-27.61:measured_LRA=18.06:measured_TP=-4.47:measured_thresh=-39.20:offset=0.58:linear=true -ar 48000 output.mp4
The measured_* parameters feed the first-pass measurements back into the filter. Setting linear=true requests linear normalization — a single gain value applied to the entire file.
Automating two-pass with a shell script
Parsing JSON and running two commands manually gets tedious. Here's a script that automates it:
#!/bin/bash
# two-pass-normalize.sh — Normalize audio to target LUFS
INPUT="$1"
OUTPUT="$2"
TARGET_I=${3:--16}
TARGET_TP=${4:--1.5}
TARGET_LRA=${5:-11}
# Pass 1: measure
JSON=$(ffmpeg -i "$INPUT" -af "loudnorm=I=$TARGET_I:TP=$TARGET_TP:LRA=$TARGET_LRA:print_format=json" -f null - 2>&1 | sed -n '/{/,/}/p')
# Extract measured values
measured_I=$(echo "$JSON" | grep '"input_i"' | sed 's/.*: "//;s/".*//')
measured_TP=$(echo "$JSON" | grep '"input_tp"' | sed 's/.*: "//;s/".*//')
measured_LRA=$(echo "$JSON" | grep '"input_lra"' | sed 's/.*: "//;s/".*//')
measured_thresh=$(echo "$JSON" | grep '"input_thresh"' | sed 's/.*: "//;s/".*//')
offset=$(echo "$JSON" | grep '"target_offset"' | sed 's/.*: "//;s/".*//')
# Pass 2: apply
ffmpeg -i "$INPUT" -af "loudnorm=I=$TARGET_I:TP=$TARGET_TP:LRA=$TARGET_LRA:measured_I=$measured_I:measured_LRA=$measured_LRA:measured_TP=$measured_TP:measured_thresh=$measured_thresh:offset=$offset:linear=true" -ar 48000 "$OUTPUT"
Usage:
chmod +x two-pass-normalize.sh
./two-pass-normalize.sh input.mp4 output.mp4 -16 -1.5 11
For more advanced scripting with Python, see Automating FFmpeg with Python.
Platform-Specific Loudness Targets
Different platforms normalize audio to different targets. Delivering audio that already meets the target prevents the platform from applying its own normalization, which can introduce artifacts.
| Platform | Target LUFS | True Peak | Notes |
|---|---|---|---|
| YouTube | -14 LUFS | -1.5 dBTP | Applies normalization on playback |
| Spotify | -14 LUFS | -1 dBTP | Uses ReplayGain-style normalization |
| Apple Music | -16 LUFS | -1 dBTP | Sound Check feature |
| Apple Podcasts | -16 LUFS | -1 dBTP | Same as Apple Music |
| Amazon Music | -14 LUFS | -2 dBTP | Only turns volume down, never up |
| TikTok | ~-14 LUFS | -1 dBTP | No official spec; industry consensus |
| Instagram Reels | ~-14 LUFS | -1 dBTP | Meta manages via xHE-AAC |
| EBU R128 (broadcast) | -23 LUFS | -1 dBTP | European broadcast standard |
| ARIB TR-B32 (Japan) | -24 LKFS | per spec | Japanese broadcast standard (since 2012) |
Source: EBU Tech R128, platform developer documentation, and industry measurements.
Ready-to-use commands for common targets
YouTube / Spotify / TikTok (-14 LUFS):
ffmpeg -i input.mp4 -af loudnorm=I=-14:TP=-1.5:LRA=11 output.mp4
Apple Music / Podcasts (-16 LUFS):
ffmpeg -i input.mp4 -af loudnorm=I=-16:TP=-1:LRA=11 output.mp4
Broadcast (EBU R128, -23 LUFS):
ffmpeg -i input.mp4 -af loudnorm=I=-23:TP=-1:LRA=7 output.mp4
Batch Normalization with ffmpeg-normalize
For normalizing multiple files, ffmpeg-normalize is a Python CLI that wraps FFmpeg's loudnorm filter with sensible defaults and built-in two-pass support.
Installation
pip install ffmpeg-normalize
Basic usage
# Normalize a single file to -16 LUFS (two-pass, EBU R128)
ffmpeg-normalize input.mp4 -o output.mp4
# Normalize to YouTube's -14 LUFS
ffmpeg-normalize input.mp4 -o output.mp4 -t -14
# Normalize all MP4 files in a directory
ffmpeg-normalize *.mp4 -of normalized/ -ext mp4
Built-in presets (v1.36+)
# Podcast (AES streaming standard)
ffmpeg-normalize input.wav -o output.wav --preset podcast
# Music (RMS-based)
ffmpeg-normalize input.wav -o output.wav --preset music
# Streaming video
ffmpeg-normalize input.mp4 -o output.mp4 --preset streaming-video
Album normalization (v1.35+)
To keep relative loudness between tracks while bringing the album to a target level:
ffmpeg-normalize track1.wav track2.wav track3.wav -of album/ -ext wav --album
For a deeper dive into batch workflows, see Automating FFmpeg with Python.
Comparing Audio Filters: loudnorm vs. Others
"Should I use loudnorm or just volume?" — this question comes up constantly on Stack Overflow and audio production forums. FFmpeg has several audio adjustment filters. Here's when to use each:
| Filter | Method | Best for | Trade-off |
|---|---|---|---|
| loudnorm | EBU R128 perceptual loudness | Streaming, broadcast, podcasts | Most accurate; upsamples to 192 kHz internally |
| volume | Fixed gain multiplier | Simple adjustments ("make it louder") | No loudness awareness; can clip |
| dynaudnorm | Per-chunk dynamic gain | Conversation, interviews | ~4x faster than loudnorm; no standard compliance |
| compand | Dynamic range compression | Heavy compression effects | Can distort; complex syntax |
When to use volume instead of loudnorm
If you just need to bump the volume by a known amount, volume is simpler and faster:
# Increase by 6 dB
ffmpeg -i input.mp4 -af volume=6dB output.mp4
# Decrease by 3 dB
ffmpeg -i input.mp4 -af volume=-3dB output.mp4
Use volumedetect first to measure peak and mean volume:
ffmpeg -i input.mp4 -af volumedetect -f null -
For more basic FFmpeg audio commands, see the audio section in FFmpeg Commands: A Practical Guide.
When to use dynaudnorm
dynaudnorm adjusts gain on a per-chunk basis (default ~8 seconds). It's faster than loudnorm and good for making quiet speech audible, but it doesn't follow any standard:
ffmpeg -i input.mp4 -af dynaudnorm=f=200:g=5 output.mp4
f= frame length in milliseconds (default 500)g= Gaussian filter size (default 31)
Frequently Asked Questions
What's the difference between LUFS and LKFS?
They're the same measurement. LUFS (Loudness Units Full Scale) is the EBU term; LKFS (Loudness K-weighted Full Scale) is the ITU term. -14 LUFS = -14 LKFS.
Why does my normalized audio sound quieter than the original?
Loudness normalization targets perceived loudness, not peak level. A heavily compressed (dynamically) source may have a high peak but low perceived loudness. After normalization, the peak may be lower even though perceived loudness matches the target.
Should I normalize before or after compression/encoding?
Normalize before final encoding. If you normalize after lossy compression, you're applying gain to already-degraded audio. Normalize the highest quality source you have, then encode.
Can I normalize audio without re-encoding the video?
Yes. Copy the video stream and only re-encode audio:
ffmpeg -i input.mp4 -c:v copy -af loudnorm=I=-16:TP=-1.5:LRA=11 -c:a aac -b:a 192k output.mp4
What sample rate should I use for the output?
48 kHz is the standard for video. For music-only files, match the source sample rate. The loudnorm filter internally upsamples to 192 kHz regardless, so adding -ar 48000 to the output is recommended to avoid unexpected sample rate changes.
Does loudnorm work with surround sound (5.1/7.1)?
Yes. The filter handles multi-channel audio, but set dual_mono=true if your source is mono content in a stereo container, so the measurement accounts for the single-channel playback context.
How do I verify the result?
Run loudnorm in measurement mode on the output file:
ffmpeg -i output.mp4 -af loudnorm=print_format=json -f null -
Check that input_i matches your target. A deviation of ±0.5 LU is normal.
Wrapping Up
Here's a quick reference for the most common normalization tasks:
| Task | Command |
|---|---|
| Quick normalize to -16 LUFS | loudnorm=I=-16:TP=-1.5:LRA=11 |
| YouTube/Spotify (-14 LUFS) | loudnorm=I=-14:TP=-1.5:LRA=11 |
| Apple Podcasts (-16 LUFS) | loudnorm=I=-16:TP=-1:LRA=11 |
| Broadcast (EBU R128) | loudnorm=I=-23:TP=-1:LRA=7 |
| Measure only (no output) | loudnorm=print_format=json -f null - |
| Keep video, normalize audio | -c:v copy -af loudnorm=... -c:a aac |
| Batch normalize | ffmpeg-normalize *.mp4 -of out/ -ext mp4 |
The loudnorm filter handles most normalization needs out of the box. For batch workflows or advanced automation, ffmpeg-normalize saves significant time. And for basic volume adjustments that don't need loudness standards compliance, the simpler volume filter works fine.
Enterprise-grade cloud VPS with global data centers
- 13 data centers (US, EU, Asia, Middle East)
- Starting at $4/month for 1GB RAM — pay-as-you-go
- 30-day free trial available
Related articles: