Measure integrated loudness in LUFS, loudness range and true peak in C# with oneaudio’s ITU-R BS.1770-5 and EBU R 128 meter, for a whole file or live, block by block.
The input is game-mix.wav, the file Mix audio files writes.
using OneAudio; using OneAudio.Loudness; // One call: integrated loudness, loudness range and true peak. LoudnessResult r = Audio.Measure("game-mix.wav"); Console.WriteLine($"game-mix.wav: {r.IntegratedLufs:F1} LUFS, LRA {r.LoudnessRange:F1} LU, " + $"true peak {r.TruePeakDbtp:F1} dBTP"); Console.WriteLine($"max momentary {r.MaxMomentaryLufs:F1}, max short-term {r.MaxShortTermLufs:F1}"); // Skip true peak (4x oversampling) when you only need LUFS: it's the costly part. double lufs = Audio.Measure("game-mix.wav", truePeak: false).IntegratedLufs; Console.WriteLine($"without true peak: {lufs:F1} LUFS"); // A live meter: feed blocks as they come, and read momentary (400 ms) and // short-term (3 s) loudness at any time. using IAudioDecoder decoder = Audio.Open("game-mix.wav"); var meter = new LoudnessMeter(decoder.Format, truePeak: true, loudnessRange: true); int channels = decoder.Format.Channels; float[] block = new float[4800 * channels]; // 100 ms int read, n = 0; while ((read = decoder.Read(block)) > 0) { meter.Process(block.AsSpan(0, read * channels)); if (++n % 20 == 0) // every 2 s Console.WriteLine($"{n / 10,3} s momentary {meter.MomentaryLufs,6:F1}" + $" short-term {meter.ShortTermLufs,6:F1}"); } Console.WriteLine($"integrated {meter.IntegratedLufs:F1} LUFS, range {meter.LoudnessRange:F1} LU, " + $"true peak {meter.TruePeakDbtp:F1} dBTP");
game-mix.wav: -18.7 LUFS, LRA 2.6 LU, true peak -1.8 dBTP max momentary -14.1, max short-term -16.7 without true peak: -18.7 LUFS 2 s momentary -20.7 short-term -21.1 4 s momentary -20.8 short-term -19.4 6 s momentary -16.6 short-term -17.3 8 s momentary -18.3 short-term -17.6 10 s momentary -23.6 short-term -19.1 12 s momentary -20.8 short-term -19.9 14 s momentary -22.0 short-term -19.1 16 s momentary -21.0 short-term -20.2 integrated -18.7 LUFS, range 2.6 LU, true peak -1.8 dBTP
ffmpeg’s ebur128 filter gives the same integrated loudness (−18.7 LUFS) and true peak (−1.8 dBTP) for this file.
The readings
| Reading | What it is |
|---|---|
IntegratedLufs | The loudness of the whole program, gated: blocks below −70 LUFS, and blocks 10 LU below the ungated level, are left out |
MomentaryLufs, MaxMomentaryLufs | Over the last 400 ms |
ShortTermLufs, MaxShortTermLufs | Over the last 3 s |
LoudnessRange | The spread of short-term loudness in LU, per EBU Tech 3342 |
TruePeakDbtp | The highest peak between samples as well as on them, found by 4× oversampling, across all channels |
Before any audio, the readings are negative infinity and the range is 0.
Good to know
- The meter passes EBU Tech 3341 cases 1 to 23 and Tech 3342 cases 1 to 6 within their tolerances.
- It works at any sample rate from 4 kHz up, and the K-weighting filters are designed for the actual rate.
- Channels are weighted from the channel mask: 1.41 for surround and side channels, and the LFE channel is left out of the loudness but not the true peak.
- The results don’t depend on how the audio is split into blocks. Everything is allocated in the constructor, so a meter can run on a real-time thread.
- True peak is the expensive part. Pass
truePeak: falsewhen you only need LUFS. - The true-peak filter lags by 12 samples, so a peak in the last 12 frames shows only after more audio arrives.
- The mixer has the same meters built in, per bus. See Mix audio files.
To change a file’s loudness, see Normalize loudness.
A longer guide to EBU R 128 and streaming targets: Loudness normalization in C#.