BGMをWAVからMP3にすると音は何ミリ秒遅れるか|MMDの音ズレとファイルサイズ

WAVは扱いやすい反面、3分の曲で30MB前後になります。MP3にすれば10分の1ほどになりますが、MP3のエンコーダは曲の頭に短い無音を足すので、モーションと合わせて再生すると音がわずかに遅れます。このサイトのアップロードで使っているWAV→MP3の変換で、どれだけ遅れるのかを測りました。

最終更新: 2026-10-07 · クリック音を1・5・9秒に置いた10秒のWAV(4種類のサンプリング周波数)を作り、このサイトの変換(lamejs、128kbps)でMP3にして、Chromiumで解読した波形を元のWAVと突き合わせて遅れを測った。サイズは本番にあったWAVを変換した実測

サイズは10分の1ほどになる

非圧縮のWAV(44.1kHz・16bit・ステレオ)は1秒で約176KBです。128kbpsのMP3は1秒で16KBなので、11分の1ほどになります。このサイトに実際にアップロードされたWAVを変換したときの結果は次のとおりです。

元のWAVMP3(128kbps)減った割合
30.53MB2.54MB91.7%(変換に12秒)
0.63MB0.23MB63.5%
10秒の試験音(44.1kHz・ステレオ) 1.76MB0.16MB90.9%

このサイトでは、WAVをアップロードすると、送る前にブラウザの中でMP3へ変換します。変換できない形式のときや、変換するとかえって大きくなるときは、元のWAVのまま保存して、画面にその旨を出します。

MP3にすると音が遅れる

MP3のエンコーダは、曲の頭に短い無音を足します。フレームの境目をまたいで計算するための「助走」で、LAME系のエンコーダでは足される長さが決まっています。測ってみると、どのサンプリング周波数でも、ちょうど1,105サンプル遅れていました。 長さはサンプル数で決まるので、秒に直すとサンプリング周波数によって変わります。

サンプリング周波数音の遅れ30fpsのフレーム数曲全体の長さの増え方
48kHz23.0ms0.69フレーム+32.0ms
44.1kHz25.1ms0.75フレーム+31.0ms
32kHz34.5ms1.04フレーム+44.0ms
22.05kHz50.1ms1.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で変換し直すときも、アップロード時と同じコードを使えるようにするためです。