エピ35 mp3のデコーダを評価したけど中身が気になる
***
エピ35 mp3のデコーダを評価したけど中身が気になる
***
mp3デコーダを評価してみました。...、正式な評価ではなくて、それっぽい真似をしているだけですけどね。
dist10 との差分で求めたRMSレベルは、...エピ34の libmad を含めて、...現在のところ次のようになっています:
1.171e-10 --- distpy (real)
5.100e-08 --- libmadia (32bit fix-point; INTEL, for ACCURACY)
8.003e-08 --- mpg123g (real; generic)
7.897e-06 --- lame3x (real; default)
8.810e-06 --- {↑ISO's Fully Compliant}
8.846e-06 --- ffmpeg (int64_t; disable-x86asm)
1.410e-04 --- {↑ISO's Limited Accuracy}
*
mp3のデコードって、...mp3の仕様に従ってデジタルなデータをデジタルなデータに変換するデジタルな処理なのですけど。計算上の誤差?の扱いによって結果が違うようです。
libmad の場合だと、乗算用の関数がCPUやオプション指定によって違い、計算結果も違ってました。RMSレベルで3700倍ほどの違い。
とすると。mpg123g と比べると lame3x, ffmpeg は100倍ほど違いますがコレは本当に違うのかな。...、ffmpeg は気軽に x86asm を disable したけど大丈夫か? lame3x はデフォルトだけど、それでいいのか。
これって、気になりますね。16bit-PCM では±1の違いでしかなくて実用上の影響はないのですけど。
気になるのです。
***
評価は、...RMSレベルは次のようにして求めます。
まず、ソースコードの然るべきところに printf 文を挿入して内部データを出力します。エピ33でやりました。
取得したデータは +1.0 〜 -1.0 の範囲の実数にします。
dist10 のデータはそのままです(dist10 が基準だからですね)。他は 32768.0 で割ったり等してレベルを合わせます。
比較するデコーダのデータの配列を w1 と w2 とします。w1[i], w2[i] は w1, w2 の i 番目の値です。
差分(の配列)は、j = min(len(w1),len(w2)) として、
w_diff = [w1[i]-w2[i] for i in range(j)]
です。差分の二乗の平均のルート(RMS, Root-Mean-Sqare)は、
w_rms = math.sqrt(sum([d*d for d in w_diff])/len(w_diff))
です。sum() より math.fsum() を使った方が良いかもですけど。
***
ffmpeg のビルド条件は
./configure(--disable-x86asm なしで)すると ./ffbuild/config.mak と ./config.h と ./config.asm ができる。うーん、色々違うな。
なお、コンパイルは時間が掛かる。...、Zzzz。チラ?(まだか) ...、Zzzz、...。...。はッ。目覚めたら出来ていた。
x86asm を disable しないでビルドしても、出力のPCMファイルは同じだった。
*
lame3x は、./configure のオプションを見てみたけど、...特に気になるものは無かった。というか多々のオプションがあって、どれが関係するのか分からなかった、...orz
どこから調べたらよいのか?
***
**
*
エピ33で内部データ、...、16bit-PCM に変換する前の変数(real や int64_t など)を調べてみたとき、lame3x と ffmpeg には他と違う特徴がありました。
lame3x は dist10 との差分が、波形の振幅と(マイナスの)相関があること。ffmpeg は相関がないこと。...、です。
振幅と差分の関係を再確認しましょう。最初の9秒弱、...ピー音の部分です。
縦軸は振幅で +1.0〜-1.0 のスケールです。横軸は dist10 との差分を 32768.0 倍したものです。±1.0 が 16bit-PCM データの±1の違いとなります。
lame3x は、ほぼ直線でビックリですね。
ffmpeg もビックリで、振幅に関係なく -0.5〜+0.5 に分布しています。なお、エピ33にて ffmpeg のデータに加算したオフセット -1.447628e-05 は 32768.0 倍すると -0.47435874304 で、このオフセットを付けないと 0.0〜1.0 の分布です。
mpg123g と libmadia の2つは差分が ±0.01 程で明らかに違う。これがRMSレベルの違いとなっているのですが。...。これって?
*
lame3x の内部データは 32768.0 で割ってレベルを dist10 に合わせた、...つもりだったのですが、この乗数(除数?)が不正確だった可能性があります。
乗数をパラメータとして振って見ると、32766.999 でRMSレベルが最適になることが分かりました。mpg123g も 32767.999 に調整しました。
すると、
lame3x は振幅と差分の相関が消えました! mpg123g と同じ感じです。
RMSレベルは100倍も改善して、次の様になりました!
8.003e-08 ---> 7.936938e-08 : mpg123g (real; generic)
7.897e-06 ---> 7.942814e-08 : lame3x (real; default)
内部データの波形も確認します。dist10 との差分です。
lame3x の差分は mpg123g と(ほぼ)同じ波形になりました。
*
lame3x が mpg123g と同じなのは納得感があります。
lame3x のデコード処理は(./mpglib/READMEによると)mpg123 の mpglib 0.2a を元にして、改良してきたものだからです。
...、でも乗数が1違うのは何故だろう?
***
**
*
次は ffmpeg にです。
ffmpeg は内部では int64_t で処理しているのですが、...16bit-PCM のレベルで 0.0〜1.0 の差分が発生するのは不思議です。意図的に下位ビットに乱数を入れているのではないかと。
出力付近のコードを整理して簡略にしてみると、...(イメージだけよ)、
sum = dither_state
:
for ...
for ...
sum += w[i]*p[j]
sum1 = (sum >> OUT_SHIFT) // --- ★1
sum &= (1<<OUT_SHIFT)-1 // --- ★2
samples += [clip_int16(sum1)]
:
dither_state = sum
「★1」の行にて、変数 sum の下位ビットを切捨てて変数 sum1 に代入して、「★2」の行にて、sum の下位ビットを残して、上位ビットをマスクしています。
その後は sum1 は 16bit にクリップして変数 sample に蓄積です。そして、次のデータを sum に加算します(+=w[i]*p[j])。外側の for ループを抜けた後は、sum を変数 dither_state に保管して、次回の sum の初期値にする。
つまり、変数 sum の下位ビットは疑似的な乱数になっています。
下位ビットを乱数にすれば 16bit-PCM に変換する際に発生する±1のエラーは拡散されて、...、音質がまろやかになるのかな? 聴いて分かる人には分かるのかな? どうかな?
画像のディザリングなら見て分かるよ。昔の低解像度のドットインパクトプリンタなら必須な技術よね。
確認してみたら、mpg123 は 16bit-PCM 出力を dithering (noise shaping) するオプションを用意してました。そう言えば、変数 sum の出力用の printf() のすぐ後に ADD_DITHER() というマクロがあった。ソースコードを追うと...、[-0.5,+0.5] の疑似乱数を2個加算してTPDFにして、ハイパスフィルタで成形して、sum に加算している。
更にググってみたら、DAWでハイレゾ音源を(CD用などの)低解像度!に書き出すときに使う。...、って解説しているWEBを幾つか見つけた。ティザーについて説明しているようで、...どうなんだろう? DAWソフトにそういう機能があるのは分かったけど。Wiki にもティザーの解説があるよ。
*
ffmpeg のRMSレベルを評価するためには、変数 sum を(下位ビットも含めて)0クリアすればよさげです。それからオフセット 7.80804060e-07 を加算します(...?、怪しい雰囲気)。
そしたらRMSは、7.867e-07 となりました。約10倍の改善ですが、...うーん。
差分の分布を見てみます。
概ね、-0.1〜+0.1 の範囲に入っていますが。-0.1〜-0.05 に出ている部分が邪魔ですね。
差分の波形を見てみます。
周期的でノイズみたいな信号です。下位ビットを0クリアしたのだけど漏れがあるのか。mpg123g や lame3x と比べて差分が大きすぎ。
***
**
*
内部データ(変数sum)で評価した、今日の結果です。
1.095e-10 --- distpy (real)
5.100e-08 --- libmadia (32bit fix-point; INTEL, for ACCURACY)
7.937e-08 --- mpg123g (real; generic)
7.943e-08 --- lame3x (real; default)
7.867e-07 --- ffmpeg (int64_t; default)
8.810e-06 --- {↑ISO's Fully Compliant}
1.410e-04 --- {↑ISO's Limited Accuracy}
dist10 の波形とレベル合わせをするために乗数を調整しました。特に lame3x で効果大でした。
ffmpeg は dithering 処理を無効化するために変数 sum の初期値を 0 にしました。
これで全デコーダが Fully Compliant になったのですが。どうしよう?
***
間違いの指摘とか疑問とか、ご意見・ご感想とかありましたら、どうぞ感想欄に!
***
2026.9.8 微推敲
2026.9.11 微推敲




