If some of your videos are noticeably louder than others, the fix is loudness normalisation rather than volume adjustment. They are different operations: volume multiplies the signal, normalisation measures how loud the material actually sounds and adjusts it to a target.
That distinction matters because a volume increase applied to already-loud audio just clips it.
The measurement is not peak level
Peak level tells you the loudest single instant. It says almost nothing about how loud something sounds, because a quiet recording with one door slam has the same peak as a consistently loud one.
Loudness is measured in LUFS, over time, weighted for how hearing actually works. Two files normalised to the same LUFS sound equally loud; two files with the same peak do not.
Single-pass normalisation
ffmpeg -i input.mp4 -af loudnorm=I=-16:TP=-1.5:LRA=11 \ -c:v copy -c:a aac -b:a 192k output.mp4
I=-16 is the target loudness, TP=-1.5 the maximum true peak, and LRA=11 the loudness range.
-c:v copy matters: you are only changing audio, so the video is copied untouched and the whole operation runs at disk speed rather than encode speed.
Two-pass is meaningfully better
Single-pass adjusts as it goes and is approximate. Two-pass measures the whole file first, then applies exact correction.
ffmpeg -i input.mp4 -af loudnorm=I=-16:TP=-1.5:LRA=11:print_format=json -f null -
That prints measured values. Feed them back:
ffmpeg -i input.mp4 -af loudnorm=I=-16:TP=-1.5:LRA=11:measured_I=-23.1:measured_TP=-5.2:measured_LRA=6.4:measured_thresh=-33.8:offset=0.4:linear=true \ -c:v copy -c:a aac -b:a 192k output.mp4
Use two-pass for anything published. Single-pass is fine for a quick check.
Which target
-16 LUFS suits web video and podcasts.
-14 LUFS is roughly what several streaming platforms normalise to.
-23 LUFS is the broadcast standard in much of the world.
The important thing is consistency across your own library rather than the exact number. A viewer moving between your videos should not reach for the volume control, and that is achieved by using one target everywhere.
Note that platforms which normalise on their side will adjust your audio anyway: mastering much louder than their target achieves nothing except being turned down.
Simple volume change, when that is all you need
ffmpeg -i input.mp4 -af "volume=1.5" -c:v copy -c:a aac output.mp4
Multiplies by 1.5. Useful for audio that is uniformly too quiet with headroom to spare, and wrong for anything already near the ceiling; it will clip, and clipping is not recoverable.
Removing or replacing audio
# No audio at all ffmpeg -i input.mp4 -an -c:v copy output.mp4 # Replace the track ffmpeg -i video.mp4 -i audio.mp3 -map 0:v -map 1:a -c:v copy -shortest output.mp4
-shortest stops at whichever input ends first, which prevents a long audio file leaving silent video on the end.
Batch normalising a library
for f in *.mp4; do
ffmpeg -i "$f" -af loudnorm=I=-16:TP=-1.5:LRA=11 \
-c:v copy -c:a aac -b:a 192k "normalised/${f}"
done
Quote the variable so filenames with spaces survive, and write to a different directory so an interrupted run cannot leave you with a mixture of processed and unprocessed files under the same names.
This is single-pass, which is the reasonable trade for a large batch. For a small number of important files, two-pass each.
Check the result by listening
Measure afterwards if you like, but the test that matters is playing two normalised files one after the other and not reaching for the volume.
Also listen for artefacts. Aggressive normalisation on material with a very wide dynamic range can make quiet passages sound pumped, and the LRA value is what controls how much the range is compressed.
For selecting, replacing and mixing the audio streams themselves, Working with Audio Tracks in FFmpeg explains the mapping.
Measure before changing anything
Half the files people normalise did not need it, and the measurement takes seconds.
ffmpeg -i input.mp4 -af ebur128 -f null - 2>&1 | tail -12 ffmpeg -i input.mp4 -af loudnorm=print_format=json -f null - 2>&1 | tail -14
Read three numbers from the summary. Integrated loudness is the overall level. Loudness range describes how much it varies between quiet and loud passages. True peak says how close the waveform comes to clipping.
A file already within a decibel of your target does not need processing, and running it anyway costs a re-encode and a small loss of quality for nothing.
True peak is the number that causes distortion
Sample peak and true peak are different, and the gap is where audible damage happens after encoding.
A waveform that never exceeds full scale at its sample points can still exceed it between them once reconstructed, and a lossy encoder makes that worse. The result is distortion that was not present in the source and does not appear in a simple level meter.
ffmpeg -i input.mp4 -af loudnorm=I=-16:TP=-1.5:LRA=11 -c:v copy -c:a aac -b:a 192k out.mp4
Leaving one and a half decibels of headroom is the usual protection. Normalising to a loud target with a peak ceiling at zero produces exactly the fault this is meant to avoid.
The target depends on where it is going
There is no single correct loudness, and matching the destination matters more than hitting a number you read somewhere.
Video platforms apply their own normalisation on playback, so delivering something much louder than their target simply gets turned down, with the dynamics you crushed to achieve it left crushed.
Speech content is usually delivered a little louder than music, because intelligibility on a phone speaker matters more than dynamic range. Broadcast has its own standard, and material intended for it is measured rather than judged.
Pick the target from the destination, apply it once, and keep the original. Normalising an already normalised file compounds the processing.
Do not re-encode the picture to fix the sound
The commonest waste here is a full video encode performed because the audio was two decibels quiet.
ffmpeg -i input.mp4 -c:v copy -af loudnorm=I=-16:TP=-1.5:LRA=11 -c:a aac -b:a 192k out.mp4 ffprobe -v error -select_streams v -show_entries stream=codec_name,bit_rate -of csv=p=0 out.mp4
Copying the video stream makes the operation take seconds instead of hours and leaves the picture bit for bit identical.
Check the result reports the same video codec and bitrate as the source. If it does not, the copy did not happen and something in the command forced a re-encode. Remuxing and stream copy goes into why that occasionally fails.
Keep the original alongside the processed copy
Loudness processing is not reversible, and a file normalised to the wrong target cannot be returned to where it started.
ffmpeg -i original.mp4 -af loudnorm=I=-16:TP=-1.5:LRA=11 -c:v copy processed.mp4 ls -la original.mp4 processed.mp4
Write to a new file rather than replacing the source, and keep the source until the result has been checked on the destination it is going to.
The case this protects against is a batch run with a wrong parameter. With the originals intact that is an afternoon of reprocessing; without them it is a library that has been quietly damaged and cannot be recovered.
Check a sample rather than trusting the whole run
A batch that reports success can still have produced files that are wrong in the same way.
for f in out/*.mp4; do printf '%-28s ' "$(basename "$f")" ffmpeg -i "$f" -af ebur128 -f null - 2>&1 | grep -m1 'I:' | tr -s ' ' done | head -10
Read the measured loudness of the results rather than assuming the requested target was reached. Files with unusual content, such as long silences or a single loud passage, frequently land some distance from where they were aimed.
Listen to two or three as well. The measurement confirms the number and it says nothing about whether the processing introduced pumping or made quiet passages unnaturally loud, which are the faults a listener notices immediately.