↑ページトップへ
表示調整
閉じる
挿絵表示切替ボタン
▼配色
▼行間
▼文字サイズ
▼メニューバー
×閉じる

ブックマークに追加しました

設定
0/400
設定を保存しました
エラーが発生しました
※文字以内
ブックマークを解除しました。

エラーが発生しました。

エラーの原因がわからない場合はヘルプセンターをご確認ください。

ブックマーク機能を使うにはログインしてください。
PR
47/50

エピ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の違いとなります。

 挿絵(By みてみん)


 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 に調整しました。

 すると、

 挿絵(By みてみん)

 lame3x は振幅と差分の相関が消えました! mpg123g と同じ感じです。


 RMSレベルは100倍も改善して、次の様になりました!


  8.003e-08 ---> 7.936938e-08 : mpg123g (real; generic)

  7.897e-06 ---> 7.942814e-08 : lame3x (real; default)


 内部データの波形も確認します。dist10 との差分です。

 挿絵(By みてみん)

 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倍の改善ですが、...うーん。

 差分の分布を見てみます。

 挿絵(By みてみん)

 概ね、-0.1〜+0.1 の範囲に入っていますが。-0.1〜-0.05 に出ている部分が邪魔ですね。

 差分の波形を見てみます。

 挿絵(By みてみん)


 周期的でノイズみたいな信号です。下位ビットを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 微推敲




評価をするにはログインしてください。
ブックマークに追加
ブックマーク機能を使うにはログインしてください。
― 新着の感想 ―
このエピソードに感想はまだ書かれていません。
感想一覧
+注意+

特に記載なき場合、掲載されている作品はすべてフィクションであり実在の人物・団体等とは一切関係ありません。
特に記載なき場合、掲載されている作品の著作権は作者にあります(一部作品除く)。
作者以外の方による作品の引用を超える無断転載は禁止しており、行った場合、著作権法の違反となります。