BGMをWAVからMP3にすると音は何ミリ秒遅れるか|MMDの音ズレとファイルサイズ
WAVは扱いやすい反面、3分の曲で30MB前後になります。MP3にすれば10分の1ほどになりますが、MP3のエンコーダは曲の頭に短い無音を足すので、モーションと合わせて再生すると音がわずかに遅れます。このサイトのアップロードで使っているWAV→MP3の変換で、どれだけ遅れるのかを測りました。
サイズは10分の1ほどになる
非圧縮のWAV(44.1kHz・16bit・ステレオ)は1秒で約176KBです。128kbpsのMP3は1秒で16KBなので、11分の1ほどになります。このサイトに実際にアップロードされたWAVを変換したときの結果は次のとおりです。
| 元のWAV | MP3(128kbps) | 減った割合 |
|---|---|---|
| 30.53MB | 2.54MB | 91.7%(変換に12秒) |
| 0.63MB | 0.23MB | 63.5% |
| 10秒の試験音(44.1kHz・ステレオ) 1.76MB | 0.16MB | 90.9% |
このサイトでは、WAVをアップロードすると、送る前にブラウザの中でMP3へ変換します。変換できない形式のときや、変換するとかえって大きくなるときは、元のWAVのまま保存して、画面にその旨を出します。
MP3にすると音が遅れる
MP3のエンコーダは、曲の頭に短い無音を足します。フレームの境目をまたいで計算するための「助走」で、LAME系のエンコーダでは足される長さが決まっています。測ってみると、どのサンプリング周波数でも、ちょうど1,105サンプル遅れていました。 長さはサンプル数で決まるので、秒に直すとサンプリング周波数によって変わります。
| サンプリング周波数 | 音の遅れ | 30fpsのフレーム数 | 曲全体の長さの増え方 |
|---|---|---|---|
| 48kHz | 23.0ms | 0.69フレーム | +32.0ms |
| 44.1kHz | 25.1ms | 0.75フレーム | +31.0ms |
| 32kHz | 34.5ms | 1.04フレーム | +44.0ms |
| 22.05kHz | 50.1ms | 1.50フレーム | +57.1ms |
測り方: 1・5・9秒の位置に5msのクリック音を置いた10秒のWAVを作り、MP3に変換しました。WAVとMP3をそれぞれChromiumの decodeAudioData() で波形に戻し、クリックの前後で2つの波形が最もよく重なるずれを探しています。3か所のクリックで同じずれになったので、遅れは曲の途中で変わらず、頭に足された無音の分だけです。曲の終わりにも無音が足されるので、曲全体は遅れより少し長くなります。
44.1kHz・48kHzの曲なら、遅れは1フレームに満たないので、目で見て分かるずれにはまずなりません。22.05kHzのような低いサンプリング周波数では1.5フレームになり、拍に合わせた振付ではずれが見える場合があります。
このサイトの変換も、この遅れを補正していません。44.1kHz・48kHzでは1フレーム未満のためです。ずれが気になるときは、モーションの側を1〜2フレームずらすと合わせられます(立ち位置合わせと音ズレの直し方)。
無音を取り除く情報はあるか
MP3には、先頭のフレームに「頭と終わりの無音が何サンプルか」を書いておく方法があります(Xing / Info ヘッダとLAMEタグ)。これがあれば、対応している再生ソフトは無音を取り除けます。ただし、今回の変換で作ったMP3には Xing も Info もありませんでした(ファイルの中に LAME の文字はありますが、エンコーダの名前が書かれているだけです)。
Chromiumで <audio> に読ませたときの長さ(duration)も、decodeAudioData() で解読した長さも、足された無音を含んだまま(44.1kHzで10.031秒)でした。少なくともこの変換で作ったMP3では、ブラウザ側で無音が取り除かれることはありません。
MP3の形式はサンプリング周波数で変わる
32kHz以上はMPEG-1、24kHz以下はMPEG-2という別の規格で書かれます。今回のファイルでも、先頭の4バイトが32〜48kHzでは FF FB、22.05kHzでは FF F3 で始まっていました。違いは1フレームのサンプル数で、MPEG-1は1,152、MPEG-2は576です。
MP3のフレームを数えて長さを確かめるような道具を自分で書くときは、両方に対応させてください。MPEG-1しか知らないと、22.05kHzのファイルで「フレームがほとんど無い」と読み違えます(このサイトでも一度そう誤読しかけました)。
このサイトの変換(lamejs)が受け付けるのは、8・11.025・12・16・22.05・24・32・44.1・48kHzの9通りです。96kHzのようなハイレゾのWAVは変換せず、WAVのまま保存します。
WAVを自分で読むときの落とし穴
WAV(RIFF)は「4バイトの名前 + 4バイトの長さ + 中身」の塊(チャンク)を並べた形式です。よく見かける説明では「44バイトのヘッダの後ろが音のデータ」とされていますが、それはチャンクが fmt と data の2つだけのときに限ります。
fmtとdataのあいだに別のチャンクが入ることがある。 このサイトに上がったWAVには、曲名などを入れるLISTチャンク(178バイト)が挟まっていました。決め打ちで44バイト目から読むと、LISTの中身を音として読んでしまいます。チャンクは先頭から順にたどってください。- チャンクの長さが奇数なら、後ろに1バイトの詰め物が入る。 これを数えないと、次のチャンクの位置が1バイトずれます。
dataチャンクの長さが実際のファイルより長いことがある。 実ファイルの長さで頭打ちにすると安全です。- ビット深度は16bitだけではない。 8bit(このときだけ符号なし)・24bit・32bit、32bitの浮動小数点もあります。拡張形式(フォーマット番号 0xFFFE)では、本当の形式がfmtチャンクの後ろのほうに書かれています。
このサイトの変換は、ブラウザの decodeAudioData() を使わず、WAVを自分で読んでいます。すでにアップロードされたWAVをあとからNode.jsで変換し直すときも、アップロード時と同じコードを使えるようにするためです。