ffmpeg loudnorm をやめて解析が 15 倍速くなった話:BS.1770-4 をプロセス内で測る

baken headroom は 3.5 まで、ラウドネスと True Peak を ffmpeg の loudnorm フィルタで測っていました。解析時間の 97% はそのフィルタで、デコードは 3% でした。3.6 は symphonia でプロセス内にデコードし、mp3rgain の BS.1770-4 アナライザで測ります。8.9 分の 320 kbps MP3 が 14.3 秒から 0.9 秒に、6 曲のバッチが 124 秒から 8 秒になり、0 dBTP 以下の True Peak は前のエンジンと 0.01 dB まで一致します。このページはその計測記録です。2026-09-19、baken 3.6.0 で測定。

前提

Headroom は DJ ライブラリの全曲を ひとつの True Peak の天井に揃えます。必要なのはファイルごとに 2 つの数値、レポート用の統合ラウドネスと、ゲイン判定用の True Peak です。3.5 まではその両方を ffmpeg -i file -af loudnorm=print_format=json -f null - の stderr から input_iinput_tp を拾って得ていました。大きなライブラリを持つユーザーから 1000 曲に 10 分から 20 分かかるという報告があり、同じ作者の純 Rust ツール mp3rgain なら同じファイルを数秒で解析できると指摘されました。最初の仮説は「デコーダが遅い」でした。これは外れでした。

時間はどこに消えていたか

M3 MacBook Air (P コア 4 + E コア 4)、Homebrew の ffmpeg 9.0.1、mp3rgain 3.8.1、実在のトラックを 1 ファイルずつ、実時間の秒。

ファイルffmpeg デコードのみffmpeg loudnorm (3.5)ffmpeg ebur128=peak=truemp3rgain --rg2 --true-peakプロセス内 (3.6)
MP3 320 kbps, 8.9 min0.4214.32.20.920.93
MP3 256 kbps, 5.7 min0.489.51.40.590.59
MP3 228 kbps VBR, 19.3 min0.8931.34.72.02.0
MP3 128 kbps, 15.3 min0.6024.53.71.51.5
MP3 128 kbps, 74 min mix3.011618.09.97.3
MP3 16 kbps mono 16 kHz, 82 min0.8366.29.51.61.5
WAV 24-bit, 4.6 min0.057.31.0n/a0.34
FLAC, 4.5 min0.077.21.0n/a0.45
AAC-LC 128 kbps, 5.9 min0.199.41.40.580.56
上の MP3 6 本を一括 (8 スレッド)1243.78.1

デコードは loudnorm の時間の約 3% です。残りはフィルタ自身で、コーデックに関係なく音声 1 分あたり約 1.6 秒かかります。0.05 秒でデコードできる WAV でも 7.3 秒です。

loudnorm はメーターではなくノーマライザです。内部の True Peak 検出は 192 kHz でのサンプルピーク探索なので、libavfilter に 192 kHz の入力を要求し、その手前に aresample が自動挿入されます。元の 4.35 倍のサンプル数が、倍精度で、ゲーティング、ゲイン計算、リミッティングの全経路を通ります。print_format=json はその処理の副産物として測定値を印字するだけです。読むのが input_iinput_tp だけなら、出力を -f null に捨てるノーマライザの代金を払っていることになります。

ffmpeg 9.0.1 -loglevel verbose -af loudnorm
[Parsed_loudnorm_0] auto-inserting filter 'auto_aresample_0' between 'graph_-1_in_0:0' and 'Parsed_loudnorm_0'
[auto_aresample_0] ch:2 chl:stereo fmt:fltp r:44100Hz -> ch:2 chl:stereo fmt:dbl r:192000Hz
Output #0, null, to 'pipe:': Stream #0:0: Audio: pcm_s16le, 192000 Hz, stereo, s16, 6144 kb/s

置き換えたもの

  • デコード:symphonia(純 Rust、MPL-2.0)。コンテナを probe して既定の音声トラックをデコードします。MP3、AAC-LC(.m4a / .mp4 / ADTS)、FLAC、WAV、AIFF、ALAC。
  • ラウドネス:mp3rgain の BS.1770-4 アナライザ。デコードしたバッファを f64 のインターリーブとして取り出し、フレーム単位で渡します。K 特性(シェルフ + RLB ハイパス、係数はそのサンプルレート向けに解析的に導出)、400 ms のゲーティングブロックを 100 ms 刻みで、絶対ゲート -70 LUFS、相対ゲートは生き残ったブロックの平均から 10 LU 下。単体テストは EBU Tech 3341 の正弦波ケースを通ります。
  • True Peak:同じ crate の Annex 2 メーター。49 タップの Hann 窓 sinc によるポリフェーズ補間、4 倍オーバーサンプリング(88.2 kHz 以上では 2 倍)、libebur128 と同じ設計です。中央タップがひとつの位相にちょうど乗るので、サンプルピークを下回ることはありません。
  • コードは analyzer.rs の約 100 行。symphonia は mp3rgain の AAC 対応を通じて既にバイナリに入っていたので、新しく増えたのはロスレスのデコーダだけで、バイナリは 1.3 MB 増えました。
  • 旧 loudnorm はフォールバックとして残しています。symphonia が開けないもの(SBR 付き HE-AAC、リスト外のコンテナ、拡張子の違うファイル)は今までどおり ffmpeg で測るので、以前測れたファイルが測れなくなることはありません。

同じものを測っているか

同じファイルを、旧エンジン (loudnorm) と 3.6 で。統合ラウドネス LUFS と True Peak dBTP。

ファイルloudnorm I / TP3.6 I / TP既定の天井での判定
MP3 128 kbps, 74 min-18.61 / -1.46-18.57 / -1.46変更なし、両方
MP3 256 kbps, 5.7 min-9.02 / +1.73-8.98 / +1.622 ステップ下げ、両方
MP3 228 kbps, 19.3 min-8.32 / +3.28-8.29 / +3.373 ステップ下げ、両方
MP3 320 kbps, 8.9 min-11.25 / +0.85-11.21 / +0.861 ステップ下げ、両方
MP3 128 kbps, 15.3 min-19.68 / -0.41-19.63 / -0.411 ステップ下げ、両方
MP3 16 kbps mono, 82 min-18.48 / -2.75-18.36 / -2.751 ステップ上げ、両方
WAV 24-bit-4.48 / +3.94-4.42 / +3.76正確な下げ:-4.44 dB と -4.26 dB
FLAC-6.24 / +1.21-6.19 / +1.20正確な下げ:-1.71 dB と -1.70 dB
AAC-LC 128 kbps-12.17 / +1.25-12.13 / +1.252 ステップ下げ、両方
  • 統合ラウドネスは 0.06 LU 以内で一致(16 kHz モノラルの変則ファイルで 0.12)。
  • 0 dBTP 以下のファイルでは True Peak が 0.01 dB まで一致。上げる判定と 0.05 dB のデッドゾーンが住んでいる領域です。3.5 で処理したライブラリが 3.6 で再提案されることはありません。
  • 天井を大きく超えたハードクリップのマスター(+1.5 dBTP 以上)は 0.1 から 0.2 dB 違います。

どちらも、サンプルの間で再構成波形がどこまで届くかの推定値です。loudnorm の推定は、libswresample が既定の窓付き sinc で 192 kHz に変換したあとのサンプルピーク。Annex 2 のメーターは通過帯域が規定された 4 倍のポリフェーズ FIR。普通の素材では最後の桁まで一致します。ハードクリップは事情が違います。クリップはナイキスト直下までエネルギーを置き、そこは補間フィルタの差がいちばん出る場所で、規格自身も 4 倍メーターがそうした信号で数分の 1 dB 低く読むことを認めています。どちらかが「本当の」True Peak なのではなく、両方が規格の言う許容差の中にあります。baken にとっては問題になりません。違いが出たファイルは天井より 1.5 から 4 dB 上にあり、1.5 dB 刻みで下げる対象で、そのステップ数は全テストファイルで一致しました。

ffmpeg で測っている実装者へ

  • 必要なのが I と True Peak だけなら、loudnorm は間違ったフィルタです。ebur128=peak=true は同じ I と True Peak を 6 分の 1 の時間で、ファイル本来のレートで測ります。サマリの印字は小数 1 桁ですが、ebur128=metadata=1ametadata=mode=print を組み合わせればフレームごとの値がフル精度で出ます(最後のフレームを取ります)。
  • バイナリを自分で握っているなら、デコーダと BS.1770 実装をプロセス内に持つ方がさらに 3 倍速く、stderr のパースも消えます。loudnorm の JSON は ID3 ダンプと同じストリームに流れてきて、波括弧を含む GEOB/PRIV フレームが括弧のマッチングを壊したことがあります(#10)。
  • ffmpeg の経路はフォールバックとして残す。純 Rust のデコーダは一般的なフォーマットを網羅しますが、ffmpeg は何でも開きます。
  • クリップしたマスターの 0.1 dB を追いかけない。補間器が違えば出る差で、規格の許容差の中です。

再現する

デコードだけ、loudnorm、ebur128、そして 192 kHz の証拠。最後の行は 3.6.0 以降の baken。

Terminal
$ time ffmpeg -nostdin -i track.mp3 -map 0:a:0 -f null -
$ time ffmpeg -nostdin -i track.mp3 -map 0:a:0 -af loudnorm=print_format=json -f null -
$ time ffmpeg -nostdin -i track.mp3 -map 0:a:0 -af ebur128=peak=true -f null -
$ ffmpeg -nostdin -loglevel verbose -i track.mp3 -af loudnorm -f null - 2>&1 | grep aresample
$ time baken headroom --analyze-only --no-report ~/Music/DJ

計測の全記録と設計は issue #129、実装は PR #130、BS.1770 の実装は mp3rgain の bs1770.rs にあります。どちらも MIT です。

クレジット

デコードは Philip Deljanov と貢献者による Symphonia。True Peak メーターの設計は Jan Kokemüller の libebur128 に従っています。ffmpeg は baken の中で音声を書き出す仕事をすべて引き続き担っています。報告と検証をしてくれた Alex2Code に感謝します。

Bake'n Deck のトップへ