How lossless MP3 and AAC volume normalization works

Updated · mp3rgain v3.16.2

In short

Every MP3 and AAC frame carries a small number, global_gain, that tells the decoder how loud to play that frame. mp3gain, aacgain, and mp3rgain change volume by editing that number and nothing else. The compressed audio is not decoded and encoded again, so no quality is lost, the new volume plays on every device, and writing the old number back undoes it exactly. The price is that volume moves in steps of 1.5 dB.

Three ways to make files the same loudness

MethodWhat changesQualityWorks on players that ignore tagsTools
ReplayGain tagsA tag says how much to turn the file up or down; the player does itUntouchedNorsgain, loudgain, foobar2000
Re-encodingThe audio is decoded, scaled, and compressed againA lossy generation is addedYesffmpeg loudnorm, most audio editors
Gain field rewriteThe global_gain number in each frameUntouchedYesmp3rgain, mp3gain, aacgain, foobar2000

mp3rgain does the third and the first together: it rewrites the gain field and writes standard ReplayGain tags describing what is left over, so a player that reads tags and one that does not end up at the same loudness. Each tool compared.

The gain field

MP3 and AAC do not store samples. They store a compact description of the sound's frequency content, plus scale information the decoder uses to turn that description back into samples. Part of the scale information is global_gain, an 8-bit value (0 to 255) held in every MP3 granule and every AAC channel element.

The decoder scales its output in proportion to 2global_gain/4. Raising the value by one therefore multiplies the output by the fourth root of two, about 1.19, which is 1.505 dB. That is where the step size comes from: it is a property of the formats, not a choice made by the tool.

Steps (-g)ChangeAmplitude
+1+1.5 dB× 1.19
+2+3.0 dB× 1.41
+4+6.0 dB× 2.00
-2−3.0 dB× 0.71
-4−6.0 dB× 0.50

Why this is lossless

Applying gain to an MP3 does not decode any audio. mp3rgain finds each frame, reads its side information, and changes the global_gain value of each granule and channel. AAC needs more parsing, because finding every global_gain means walking the bitstream element by element, but the principle is the same. The bits that describe the sound itself are copied through unchanged, so there is no second round of lossy compression and nothing to add up over repeated changes.

You can check this. After a gain change and an undo, a file is byte-identical to the original:

shasum -a 256 song.mp3
mp3rgain -g 2 song.mp3     # +3.0 dB
mp3rgain -u song.mp3       # undo
shasum -a 256 song.mp3     # same hash as before

For MP3, the frames mp3rgain writes are also byte-identical to the ones mp3gain writes for the same change; the compatibility report shows the comparison.

Measuring: ReplayGain

Before changing anything, mp3rgain has to know how loud a file is. That is the one step that decodes audio, and it only reads. Three measurements are available:

ModeFlagReferenceMatches
ReplayGain 1.0default89 dBmp3gain, aacgain
ReplayGain 2.0--rg2−18 LUFSfoobar2000, rsgain, loudgain
EBU R128--r128−23 LUFSffmpeg ebur128, broadcast tools

Track gain (-r) measures and corrects each file on its own. Album gain (-a) measures an album as a whole and applies one correction to all of its tracks, which keeps their relative levels. The measured correction is then rounded to whole steps, so the audio lands within about 0.75 dB of the target.

Tags that describe what is left

After applying gain, mp3rgain writes the standard REPLAYGAIN_TRACK_GAIN and REPLAYGAIN_ALBUM_GAIN tags with the residual: the small difference between the step it applied and the exact value it measured. This is mp3gain's convention. A player that honours ReplayGain applies the residual and reaches the exact target; a player that ignores tags is already within 0.75 dB of it. Neither applies the correction twice.

If you would rather not change the audio at all, --tags-only writes the full values as tags and leaves every frame as it was.

Undo

Each change is recorded in an undo tag: mp3gain's own MP3GAIN_UNDO on MP3, and an mp3rgain_undo atom on AAC. mp3rgain -u reads it and writes every frame's original value back. Undo is cumulative, so several changes in a row are reversed in one go, and it works on files that mp3gain adjusted. Its limits:

  • A frame whose value would pass 0 or 255 is clamped there and cannot be restored exactly. mp3rgain warns when this happens.
  • An MP3 that already had an ID3v2 tag keeps it after undo, rewritten as ID3v2.4, so the audio is restored exactly but the file is not byte-identical. A file without one is (since v3.16.2).
  • -s s writes no tags, including the undo tag, so that change cannot be undone.

Clipping

Turning a file up can push its peaks past full scale. mp3rgain measures the peak during analysis and warns when the gain it is about to apply would clip. -k limits the gain so that it does not; the desktop app has this on by default.

Safe writes

Every change is written to a temporary file, flushed to disk, and renamed over the original, so an interrupted or failed write leaves the original file as it was. A read-only file is refused, not replaced. -n previews a run without writing, and -p keeps each file's modification time.

What this method cannot do

  • Hit an exact loudness in the audio. Steps are 1.5 dB. For an exact value, write tags (--tags-only) or re-encode with ffmpeg loudnorm.
  • HE-AAC. global_gain reaches only the AAC core, not the SBR high band, so mp3rgain skips HE-AAC files.
  • FLAC, Ogg Vorbis, Opus, WAV, ALAC. mp3rgain does not handle them; a tag-based tool such as rsgain is the right choice.
  • Change dynamics. This is gain only: no compression and no limiter. Quiet passages stay quiet relative to loud ones.

Next