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_i と input_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=true | mp3rgain --rg2 --true-peak | プロセス内 (3.6) |
|---|---|---|---|---|---|
| MP3 320 kbps, 8.9 min | 0.42 | 14.3 | 2.2 | 0.92 | 0.93 |
| MP3 256 kbps, 5.7 min | 0.48 | 9.5 | 1.4 | 0.59 | 0.59 |
| MP3 228 kbps VBR, 19.3 min | 0.89 | 31.3 | 4.7 | 2.0 | 2.0 |
| MP3 128 kbps, 15.3 min | 0.60 | 24.5 | 3.7 | 1.5 | 1.5 |
| MP3 128 kbps, 74 min mix | 3.0 | 116 | 18.0 | 9.9 | 7.3 |
| MP3 16 kbps mono 16 kHz, 82 min | 0.83 | 66.2 | 9.5 | 1.6 | 1.5 |
| WAV 24-bit, 4.6 min | 0.05 | 7.3 | 1.0 | n/a | 0.34 |
| FLAC, 4.5 min | 0.07 | 7.2 | 1.0 | n/a | 0.45 |
| AAC-LC 128 kbps, 5.9 min | 0.19 | 9.4 | 1.4 | 0.58 | 0.56 |
| 上の MP3 6 本を一括 (8 スレッド) | 124 | 3.7 | 8.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_i と input_tp だけなら、出力を -f null に捨てるノーマライザの代金を払っていることになります。
置き換えたもの
- デコード: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 / TP | 3.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.62 | 2 ステップ下げ、両方 |
| MP3 228 kbps, 19.3 min | -8.32 / +3.28 | -8.29 / +3.37 | 3 ステップ下げ、両方 |
| MP3 320 kbps, 8.9 min | -11.25 / +0.85 | -11.21 / +0.86 | 1 ステップ下げ、両方 |
| MP3 128 kbps, 15.3 min | -19.68 / -0.41 | -19.63 / -0.41 | 1 ステップ下げ、両方 |
| MP3 16 kbps mono, 82 min | -18.48 / -2.75 | -18.36 / -2.75 | 1 ステップ上げ、両方 |
| 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.25 | 2 ステップ下げ、両方 |
- 統合ラウドネスは 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=1とametadata=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。
計測の全記録と設計は issue #129、実装は PR #130、BS.1770 の実装は mp3rgain の bs1770.rs にあります。どちらも MIT です。
クレジット
デコードは Philip Deljanov と貢献者による Symphonia。True Peak メーターの設計は Jan Kokemüller の libebur128 に従っています。ffmpeg は baken の中で音声を書き出す仕事をすべて引き続き担っています。報告と検証をしてくれた Alex2Code に感謝します。