🎵

mp3を読む ~バイナリからID3v2タグを見てみよう~

2022/05/05に公開

mp3にはメタデータ(楽曲情報など)の埋め込みに「ID3タグ」というものを用いている。
ID3タグは末尾128バイトに情報を記録するID3v1と先頭から最大256MBのメタデータを記録できるID3v2がある。情報を記録できる量が多いので最近はID3v2の方が使われているようだ。
細かい説明はWikipediaに譲る。
https://ja.wikipedia.org/wiki/ID3タグ

今回はバイナリエディタを使ってmp3の中身、特にID3v2タグを覗いてみる。
なお、対象とするファイルはCDをWindows Media Playerで取り込んだものである。

用いたCDはこちら。
https://www.tanocstore.net/shopdetail/000000001906/

ID3v2ヘッダ

ヘッダのサイズは10バイトあり、中身は以下のようになっている。

オフセット 長さ 内容 備考
0 3 ファイル識別子 ID3という固定のマジックナンバーが入っている
3 2 バージョン 02 00ならv2.2、03 00ならv2.3、04 00ならv2.4
5 1 フラグ 非同期化・拡張ヘッダ・実験中などの情報を表す
6 4 サイズ 単位はバイト。各バイトの最上位ビットは無効

実際のバイナリの最初の10バイトは、以下のようになっている。

49 44 33 03 00 00 00 00 21 76

先頭3バイト49 44 33はASCIIコードでID3を表している。
続くバージョンは03 00なので、このタグはID3v2.3であることがわかる。ID3v2.3には、最近では多くのプレイヤーが対応している。もっと新しいv2.4もあるが、そちらの普及度合いはいまひとつのようだ。
フラグは00なので、ここでは無視する。
サイズは00 00 21 76。数値のある2バイトを2進数にすると0010 0001 0111 0110となるが、各バイトの最上位(左から1桁目)のビットは無効であるため、実際の数値は01 0000 1111 0110。すなわち10進数で4342バイトということになる。ID3v1が裸足で逃げ出す大容量だが、今回見ているmp3ファイルのID3v2ヘッダは0x0000016E以降が全て0で埋まっていた。

ちなみに、各バイトの最上位ビットを無効として整数を扱う方式をSyncsafe Integerという。

ID3v2フレーム

フレームは実際にデータが格納されている、いわば本体とも言えるところだ。
任意の個数のフレームがあり、各フレームは10バイトのヘッダーと任意の長さのデータ部からなる。
ID3v2.3におけるフレームの構造は以下の通りである。

オフセット 長さ 内容 備考
0 4 フレームID 0-9の数字とA-Zの大文字が使用可能。Tで始まるやつはデータの中身が文字列
4 4 フレームサイズ 正確には「フレーム総サイズからヘッダサイズ(10バイト)を引いたもの」。v2.3においてはただの32bit整数でありSyncsafeではない
8 2 フラグ 書き換え時のフレーム保持の可否や暗号化、圧縮などの情報がある
10 可変長 データ本体 1バイト目でどのエンコード方式を使っているのかがわかる

実際のバイナリを見てみる。

54 41 4C 42 00 00 00 17 00 00

フレームIDは54 41 4C 42、ASCIIコードでTALBとなる。これはアルバム名を表すフレームIDだ。
フレームサイズは00 00 00 17。これはそのまま解釈して良いので、10進数で23バイトとなる。ヘッダサイズも含めたこのフレームのサイズは33バイトだ。
フラグは00 00なので、何もないと考えて良い。

全ての定義されたフレームIDはこちら。
http://takaaki.info/wp-content/uploads/2013/01/ID3v2.3.0J.html#sec4

では、次の23バイトを切り出してみる。

00 4C 69 62 65 72 61 74 65 20 74 68 65 20 4D 65 74 61 76 65 72 73 65

1バイト目はテキストエンコーディングの種別。
00ならISO-8859-1を、01ならBOMありUTF-16を表す(v2.4ではBOMなしUTF-16とUTF-8も追加されている)。
今回はISO-8859-1である。読んでみると、アルバム名「Liberate the Metaverse」が出てくる。
ちなみに、どうも行儀の悪い奴らがISO-8859-1と称してShift-JISを詰め込む例があるらしい。そう、Windows Media Playerとか……

次のフレームに移ろう。
先頭10バイトは以下の通りだ。

54 49 54 32 00 00 00 22 00 00

フレームIDはTIT2。"Title/Songname/Content description"、すなわち曲のタイトルである。
サイズは34バイトということなので、次の34バイトを切り出す。

00 8B C6 20 2D 91 50 82 C8 82 E9 90 5F 82 C6 82 B1 82 CC 90 A2 82 CC 88 AB 82 C9 82 C2 82 A2 82 C4 2D

テキストエンコーディングはISO-8859-1。ではあとの33バイトをISO-8859-1形式で読んでみよう。

‹Æ -‘P‚È‚é_‚Æ‚±‚̐¢‚̈«‚ɂ‚¢‚Ä-

……お分かりだとは思うが念のため説明しておくと、先程も述べた通り、Windows Media PlayerはISO-8859-1と称してShift-JISを詰め込む悪い奴である。これのせいで、世の中の音楽プレイヤーはISO-8859-1の場合にShift-JISであることを疑わなければいけなくなった。なんてことをしてくれたんだ……。

Shift-JISで読み込むと、本来のタイトルである「業 -善なる神とこの世の悪について-」が出てくる。文字化けしたやつをよく見るとASCIIの範囲内にあるスペースとハイフンが生き残っている。

次のフレームに移ろう。
先頭10バイトは以下の通りだ。

4D 43 44 49 00 00 00 90 00 00

フレームIDはMCDI。Music CD Identifierがこの後に144バイト続いているらしい。
MCDIはCDDB(Gracenoteなど)でCDを識別するのに使用する。バイナリが大きいのと本題から逸れるのでMCDI自体の解説は見送るが、たしかに同じアルバムの曲は皆同じバイナリになっていた。

次のフレームに移ろう。
先頭10バイトは以下の通りだ。

54 52 43 4B 00 00 00 03 00 00

フレームIDはTRCK。サイズはわずか3バイト。
TRCKはトラック番号で、曲のトラック番号/CDの曲の総数という形式の文字列になっている。
ではその3バイトの中身は何かというと、以下の通りである。

00 31 32

文字列なので当然先頭1バイトはエンコーディング。続く2バイトはそれぞれ12。この曲はトラック12ということになる。しかし、どこにも/が見当たらない。
念のため、Exact Audio Copyで取り込んだ別のCDのデータを覗いてみたところ、01/18という文字列が確認できた。Windows Media Player、またお前か!!!
扱うファイルを間違えたかもしれない。

気を取り直して、次のフレームに移ろう。
先頭10バイトは以下の通りだ。

54 43 4F 4E 00 00 00 05 00 00

フレームIDはTCON。ジャンルを表す。
ID3v1では予め定義された数字とジャンルの対応表から1つ数字を選んでそれを記録するという形式になったが、v2からは文字列になった。一応v1の番号を参照しつつ(41)Bassみたいに書くこともできるようだ。

00 46 69 6C 6D

設定されていたジャンル名は「Film」だった。

次のフレームに移ろう。
先頭10バイトは以下の通りだ。

50 52 49 56 00 00 00 0E 00 00

フレームIDはPRIV。どうやら、ソフトウェア開発者が他のフレームに収まらない情報を格納するためのフレームらしい。所有者識別文字列とバイナリデータから構成される、とされ、EメールアドレスもしくはEメールアドレスにアクセスできるリンクを含んでいる必要があるらしいのだが、先に述べておくと、(例によって)それらしい記述は見つからなかった。
次の14バイトは以下の通りだ。

50 65 61 6B 56 61 6C 75 65 00 A1 7E 00 00

前から9バイトはPeakValueという文字列に読める。
続く3バイトは10進数にすると41342となる。一般にCDはサンプリングレートが44100Hz(44.1kHz)で、この値はそれに近いので、おそらくこの音声ファイルにある一番高い周波数が書いてあるのだろう。

さて、次のフレームもなんとPRIVである。

50 52 49 56 00 00 00 11 00 00

中身17バイトは以下の通りだ。

41 76 65 72 61 67 65 4C 65 76 65 6C 00 7B 1D 00 00

先頭12バイトは文字列AverageLevelのようだ。最後の2バイトは先ほどと同じく無視してよいと考えると、肝心の意味を持っている値は00 7B 1Dだろう。
10進数での値が31517なのを考えると、ファイルの平均周波数だろうか?

ちなみにPRIVフレームについては、こんな記述がID3.orgのInformal standardにある。

The tag may contain more than one "PRIV" frame but only with different contents. It is recommended to keep the number of "PRIV" frames as low as possible.

PRIVフレームは複数含めることができるが、互いに違う内容でなければならず、また、可能な限り数は減らすようにとのことだ。

次のフレームに移ろう。
先頭10バイトは以下の通りだ。

54 50 45 31 00 00 00 13 00 00

フレームIDはTPE1。主なアーティストなどを格納するところらしい。
続く19バイトは以下の通りだ。

00 8C F5 8B 67 96 D2 8F 43 56 53 8C 8A 8E 52 91 E5 95 E3

フレームIDがT始まりなのでテキストだ。始まりが00なのでISO-8859-1…ではなく、Shift-JIS。
テキストの内容は「光吉猛修VS穴山大輔」。この曲のアーティスト名だ。

最後のフレームに移ろう。
先頭10バイトは以下の通りだ。

54 4C 45 4E 00 00 00 08 00 00

フレームIDはTLEN。曲の長さが数値文字列で格納されている。単位はミリ秒。

00 31 36 35 32 35 33 00

165253ms。2分45秒ほどということだ。

まとめ

普段何気なく聴いているmp3にはこんな情報が入っていたんだなあ、と感心した。
機会があったら他のファイル形式も見てみたい。

あとWindows Media Playerは反省しろ。

参考資料

Discussion