MP3と心理音響 — 「聞こえない音」の科学
MP3が捨てているのは「無い音」ではなく「有るのに聞こえない音」です。最小可聴閾・臨界帯域・マスキングの広がりという耳の穴を測る心理音響モデルを式まで降りて解説し、SMRからビット割り当て、MP3エンコーダの二重ループとビットリザーバまでつなげます。
要点
- 昼に星が見えないのは太陽が明るすぎるからで、MP3が捨てるのは「有るのに聞こえない音」である。
- 耳の穴は、静寂時最小可聴閾・同時マスキング・継時マスキングの3種類で、再現性を持って測れる。
- 臨界帯域は20Hz〜20kHzを24〜25本で覆い、MP3のscalefactor bandは長ブロックで21本である。
- 量子化のビットを1つ増やすと雑音は約6dB下がり、帯域のビット数はSMRを6.02で割って求める。
- MP3は二重ループとビットリザーバで予算に合わせ、main_data_beginの9ビットで最大511バイトさかのぼる。
昼の空にも星は出ている
昼に星が見えないのは、星が消えているからではありません。太陽が明るすぎて、そこにある光が知覚に届かないだけです。耳でも同じことが起きます。これがマスキングで、MP3が捨てているのは「無い音」ではなく、有るのに聞こえない音です。
前の記事音声圧縮を1からでは、変換(MDCT)→量子化→エントロピー符号化という骨格を追いました。この記事はその真ん中、「どの周波数帯をどれだけ雑に丸めてよいか」を毎フレーム決めている心理音響モデルだけを取り出します。「128kbps」という一行の設定が、実は無数の判定の平均値でしかないと見えてきます。
耳は穴だらけの測定器
人間の聴覚は感度こそ高性能ですが、測定器としては欠陥だらけです。肝心なのは、その欠陥が再現性を持って測れるという一点。穴は3種類あります。
- 静寂時最小可聴閾(絶対閾) — 小さすぎる音は聞こえない。しかもその基準は周波数で桁違いに違う
- 同時マスキング — 大きな音が鳴っている最中、周波数の近い小さな音が聞こえない
- 継時マスキング — 大きな音の直後(とわずかに直前)の小さな音が聞こえない
1つ目から。静かな部屋で音量を上げていくと、ある大きさで初めて音が知覚されます。この境界を周波数ごとに描くとU字型になり、3〜4kHz付近で最も低く(=最も敏感で)、両側に急に立ち上がります。谷がここに来るのは、外耳道が長さ2.5cmほどの片側が閉じた管として共鳴するから。悲鳴がこの帯域に集中しているのは偶然ではありません。
この曲線はTerhardtの近似式としてよく使われます。
は周波数(Hz)、 はその周波数で聞こえ始める音圧レベルです。つまり3つの部品の足し算で、第1項が低音側の壁、第2項が3.3kHz付近をえぐる谷、第3項が高音側の壁。この曲線より下の成分は、まるごと捨てても誰も気づきません。
臨界帯域 — 耳が持っている周波数の物差し
内耳の蝸牛に巻かれた基底膜は、入口側が硬く狭く、奥ほど柔らかく広い。高い音は入口を、低い音は奥を振動させる——位置が周波数に対応する分析器です。
ただし分解能は有限で、膜の一点はある幅を持って反応します。つまり耳は帯域通過フィルタが並んだフィルタバンクで、その1本の幅が臨界帯域です。幅は500Hz以下でおよそ100Hz、それより上では中心周波数のおよそ20%。20Hz〜20kHzが24〜25本で覆いきれてしまいます。「20kHzまで聞こえる」と言っても、区別できる升目は25個ぶんです。
この升目を1目盛りとする座標がBark尺度で、ZwickerとTerhardtの近似式が標準的です。
が周波数 のBark値で、 が1増えると臨界帯域ちょうど1本ぶんです。要するに、ヘルツという物理の物差しを臨界帯域という耳の物差しに貼り替える変換で、低音側は引き伸ばされ高音側は圧縮されます。この軸の上ではマスキングの広がりがほぼ直線になります。
コーデックが「帯域ごとに丸めの粗さを変える」と言うときの帯域は、この臨界帯域に沿って切られています。MP3ではscalefactor bandと呼ばれ、長ブロックで21本です。
同じ発想は画像にもあります。JPEGの量子化テーブルは、いわば視覚版の心理音響モデルを固定表に焼き込んだもの。紙の上で音は鳴らせないので、「丸めを粗くすると何が起きるか」はまず目で確かめてください。
マスクは隣にこぼれる — 拡散関数
大きな音(マスカー)が鳴ると、周りに「これより下は聞こえない」という傘がかかります。傘は両隣にもこぼれ、形は強く非対称です。Bark軸で見ると低い側は1Barkあたり約27dBと急峻に落ち、高い側は6〜15dB程度となだらか。しかもマスカーが大きいほど傾きが寝て、遠くまで届きます。つまり大きな音は自分より高い音を広く覆う一方、低い音はほとんど守れません。低音の厚い曲ほどビットを食うのはこのためです。
非対称はもう1つ、マスカーの性質にあります。雑音が純音を隠すのは得意で、純音が雑音より約5dB低ければもう聞こえません。逆に純音が雑音を隠すには20dB以上の差が要ります。許容できる量子化雑音がマスカー次第で20dB近く変わるので、この判定を誤ると音が壊れます。MPEGの心理音響モデル2は予測できなさ(unpredictability)で測ります。直前2フレームの振幅と位相から今を外挿し、当たれば純音的、外れれば雑音的。持続するトーンは当たり、打楽器は外れます。
最終的な閾値は、全マスカーの傘と絶対閾をパワーで足し合わせた包絡線です。dBのまま足さないのが肝心で、いったん でパワーに戻してから足します。
SMRから、ビットの枚数へ
帯域ごとに信号の強さと閾値の差を取ります。これが SMR(signal-to-mask ratio) です。SMRが大きい帯域は雑音を隠す余裕がなく、小さい帯域は傘の下に埋もれていて雑に扱えます。
ここで効くのが、量子化のビットを1つ増やすと雑音は約6dB下がるという関係です。感覚の物差し(dB)とビット数が同じ刻みで直結しているので、必要なビット数は割り算で出ます。
は 番目の帯域に配るビット数です。つまり「隠しきれない余分な30dBは、5ビット払って潰せ」。信号がまるごと閾値の下に沈んだ帯域は0ビット=丸ごと捨てる。低ビットレートのMP3で高域がばっさり無くなるのは、この判定の積み重ねです。
全帯域の を足した値をJohnstonは知覚エントロピーと呼びました。その一瞬を元と区別がつかず符号化するのに要るビット数の見積もりで、急な立ち上がりや複雑な和音では跳ね上がります。VBRの根拠はここです。
MP3エンコーダの二重ループ
ここまでが理屈で、実装には障害があります。量子化してハフマン符号化するまで、実際に何ビットになるか分からないのです。だからエンコーダは解を一発で求めず、入れ子の2つのループで探索します。量子化自体も非線形で、係数はべき乗則 を通してから丸められます。大きな係数ほど圧縮され、SN比が揃いやすくなる工夫です。
- 内側(rate loop): 粗さを決める
global_gainを上げながら量子化とハフマン符号化を試し、ビット数が予算に収まるまで繰り返す - 外側(noise control loop): 実際に生じた雑音を帯域ごとに測り、閾値を超える帯域があればその
scalefactorを上げて(そこだけ細かく量子化して)内側へ戻す
ループは「全帯域が閾値以下」「scalefactorが上限」「これ以上削れない」のいずれかで終わります。CBRで音が悪くなるのは、たいてい後ろ2つで妥協したときです。
さらにビットリザーバがあります。フレーム長は固定でも、余ったバイトを次のフレームが借りられる仕組みで、main_data_begin という9ビットのポインタで最大511バイトさかのぼれます。静かな小節で貯めた余剰を、シンバルが入った瞬間に注ぎ込む。「128kbps固定」でも瞬間のビット配分は可変です。
時間の穴 — プリエコーが生まれる場所
3つ目の穴も見ておきます。大きな音が止んだ後、耳は50〜200ミリ秒ほど小さな音を拾えません(後方マスキング)。音が始まる前にもマスキングは働きますが、こちらはおおむね2〜5ミリ秒しかありません。この非対称が変換コーデック最大の弱点を生みます。MP3の1グラニュールは576サンプル=44.1kHzで約13ミリ秒。鋭い立ち上がりがブロック後半に来ると、量子化雑音はブロック全体に均等にばらまかれ、アタックの10ミリ秒以上前の静寂にも乗ります。前方マスキングは数ミリ秒しか効かないので隠しきれず、「シュワッ」と聞こえる——プリエコーです。心理音響モデルはアタック検出器も兼ねていて、知覚エントロピーの急増を見つけると短ブロックへ切り替え、雑音がばらまかれる時間を縮めます。
コードで書く心理音響モデル
骨格だけならnumpyで数十行です。
import numpy as np
def bark(f): # Hz → Bark(耳の物差し)
return 13*np.arctan(0.00076*f) + 3.5*np.arctan((f/7500)**2)
def ath(f): # 静寂時最小可聴閾 [dB SPL]
k = f/1000
return 3.64*k**-0.8 - 6.5*np.exp(-0.6*(k-3.3)**2) + 1e-3*k**4
def spread(dz, level): # マスクの傘 [dB] dz = 対象 - マスカー [Bark]
upper = -(6 + 0.4*level) # 高域側はゆるく、大きい音ほど遠くへ
lower = -27.0 # 低域側は急
return np.where(dz >= 0, upper*dz, lower*(-dz))
def threshold(f, spl): # 全マスカーの傘と絶対閾をパワーで合成
z = bark(f)
umbrella = spl[:, None] + spread(z[None, :] - z[:, None], spl[:, None])
power = np.sum(10**(umbrella/10), axis=0) + 10**(ath(f)/10)
return 10*np.log10(power)
def bit_allocation(f, spl): # SMR → 帯域ごとのビット数
return np.maximum(0, np.round((spl - threshold(f, spl))/6.02))
実際のエンコーダはトーナリティ補正や平滑化、前節の二重ループを重ねますが、中心にあるのは「傘を重ねて包絡を作り、信号との差をビットに換算する」この流れです。
現場ではこう使う
誰が・いつ: 配信サービスのエンコード担当が音質ティアを決めるとき。ゲーム開発者が限られた容量にBGMと効果音を詰めるとき。そして音声認識や異常音検知のMLエンジニアが前処理を設計するとき——最後は見落とされがちで、かつ致命的です。
触るもの: モデルの内部定数を直接いじることはまずなく、次を通じて間接的に効かせます。
ffmpeg -i in.wav -c:a libmp3lame -q:a 2 out.mp3 # VBR品質指定(0が最高、推奨は2前後)
lame --preset standard -q 0 in.wav out.mp3 # -q は探索の丁寧さ(0が最も丁寧で遅い)
lame --lowpass 16 in.wav out.mp3 # 心理音響に任せず高域を明示的に切る
LAMEの -q は音質そのものではなく、二重ループをどれだけ粘り強く回すかの指定です。-q 0 は同じビットレートでより良い解を探しますが遅い。--lowpass はモデルの高域配分が不満なときの手動介入で、低ビットレートで効きます。
事故になる落とし穴:
- 機械学習の前処理でMP3を通す。モデルの基準は人間の耳であって、あなたの分類器ではありません。異常音検知や故障診断で、判別に効く微弱な高域が「聞こえないから」と削られます。解析用データはWAV/FLACで保持してください。
- 無音区間が無音でない。閾値は信号から計算されるので、信号が消えれば閾値は絶対閾まで下がり、隠れていた量子化雑音が露出します。低ビットレートで曲の切れ目に「シュルシュル」と乗るのはこれです。
- 超音波トーンや透かしが消える。18kHz以上に埋めた制御信号やビーコンは真っ先に捨てられます。MP3/AACを経由する経路には載せられません。
- モデルは「平均的な耳」の近似。閾値には個人差があり、加齢で高域は鈍ります。「自分には聞こえないから大丈夫」は検証になりません。ABXテストで統計的に判定を。
面接で問われたら: 「MP3は捨てる音をどう決めるのか」は、①絶対閾より下は捨てる ②臨界帯域ごとに傘を重ねて閾値を作る ③SMRを6dBで割る ④符号化して超過分をループで詰める、の4段で答えます。消しているのではなく雑音を閾値の下に隠している、まで言えると正確です。
まとめ
- 心理音響モデルが測っているのは音ではなく、耳の穴の位置(絶対閾・同時マスキング・継時マスキング)
- 臨界帯域(Bark尺度)は耳の周波数分解能そのもので、コーデックの帯域分割はこれに沿う
- 傘は高域側に広くこぼれ、雑音的なマスカーのほうが純音より強く隠す
- SMRを6.02dBで割ればビット数。MP3はそれを二重ループとビットリザーバで予算に合わせ込む
丸めた係数を最後にどう詰めるかはエントロピー符号化を1から、波形を周波数に分解する仕組みそのものはFFTを1から理解するへ続きます。
コメント
コメントにはログインが必要です