公開

flacのバイナリを読む

この記事は苦行またはメロンパン Advent Calendar 2024の12日目の記事です。

11日目は小川さんの「【緊急苦行】メロンパンと金沢大学を歩こう」でした。 苦行orメロンパンではなく苦行andメロンパンだ。

こんにちは、Yちゃんです。 遅刻して1日遅れです…。 なぜかflacというオーディオコーデックのバイナリを読んで、デコーダを実装することになりました。 なぜflacのバイナリを読んでいるかというと、UEC Advent Calendar 2024の2日目の記事である「Web Audio APIを使ってみた」に概ね書いてあるプロジェクトに取り組んでいるからです。

ここでも説明すると、私の所属する電通大にはイノベイティブ総合コミュニケーションデザイン1・2(iCCD1/iCCD2)という授業があり、その授業はハッカソン的にプロジェクトに取り組んで発表することで単位を得られます。 その中で、music-streamというプロジェクトに取り組んでおり、その中でflacのエンコーダ・デコーダをRustで再実装することになりました。 車輪の再発明なんてなんぼやってもいいですからね。

というわけで読んでいきましょう。 今回読んでいくflacのバイナリは、2024年10月の音楽系即売会M3でAstral Skyというサークルから頒布された、BRAVE [MY] WORLDというCDの5曲目の「next to you (Full Version)」という曲のデータです。 いい曲なのでぜひ聞いて下さい。EUROBEATはいいぞ。

ちなみにnext to youは、2018年にArcaeaという音楽ゲームに収録された曲で、なかなかExtend版が出ないなぁと待ち遠しにしていた作品だったので、めちゃめちゃ聴き込んでいます。

Music Center for PCでCDをflac形式で取り込むことでflacファイルを作成しました。

www.youtube.com

arcaea.lowiro.com

knowledge.support.sony.jp

シグネチャ

flacのバイナリは、まずシグネチャがあります。

66 4c 61 43  # fLaC (asciiシグネチャ)

このファイルがflacであることを示しています。

メタデータブロック

メタデータです。ストリーム情報や、タイトル、アーティスト名などが含まれています。

ストリーム情報

サンプリングレートやチャンネル数、ビット深度などのメタデータが含まれています。

00  # メタデータ続きあり(0b0)、ストリーム情報(0b0000000)
00 00 22  # ストリーム情報長(34)
10 00  # 最小ブロックサイズ: 4096
10 00  # 最大ブロックサイズ: 4096、固定ブロックサイズである
00 00 0e  # 最小フレームサイズ: 14
00 39 0a  # 最大フレームサイズ: 14602
0a c4 4  # サンプリングレート: 44100Hz
2 f  # 0b001 01111(3bit/5bitに分割) チャンネル数: 2、ビット数: 16
0 00 8c a1 38  # 1チャンネルのサンプル数: 9216312 (3分28秒)
57 ee 61 e1 3a 4d 25 b4 94 39 8e d3 bb 30 f3 85  # MD5チェックサム

チェックサムは、フレームの音声をMD5でハッシュ化したものです。

Vorbisコメント

タイトルやアーティスト名、アルバム名などが含まれています。 Vorbisコメントについては、以下にドキュメントがあります。 なお、Vorbisコメントの中身はUTF-8でエンコードされており、系列長などについてはリトルエンディアン(LE)でエンコードされています。

picard-docs.musicbrainz.org

04  # メタデータ続きあり(0b0)、Vorbisコメント(0b0000100)
00 01 b3  # Vorbisコメント長(435)
20 00 00 00  # ベンダ文字列長(LE: 32)
72 65 66 65 72 65 6e 63 65 20 6c 69 62 46 4c 41 43 20 31 2e 33 2e 34 20 32 30 32 32 30 32 32 30  # ベンダ文字列: reference libFLAC 1.3.4 20220220
0a 00 00 00  # テーブルサイズ(LE: 10)、Vorbisコメントの数
0d 00 00 00  # フィールドサイズ(LE: 13)
54 52 41 43 4b 4e 55 4d 42 45 52 3d 35  # TRACKNUMBER=5
20 00 00 00  # フィールドサイズ(LE: 32)
54 49 54 4c 45 3d 6e 65 78 74 20 74 6f 20 79 6f 75 20 28 46 75 6c 6c 20 56 65 72 73 69 6f 6e 29  # TITLE=next to you (Full Version)
58 00 00 00  # フィールドサイズ(LE: 88)
54 49 54 4c 45 53 4f 52 54 3d ef bd 8e ef bd 85 ef bd 98 ef bd 94 e3 80 80 ef bd 94 ef bd 8f e3 80 80 ef bd 99 ef bd 8f ef bd 95 e3 80 80 ef bc 88 ef bc a6 ef bd 95 ef bd 8c ef bd 8c e3 80 80 ef bc b6 ef bd 85 ef bd 92 ef bd 93 ef bd 89 ef bd 8f ef bd 8e ef bc 89  # タイトルの読み: TITLESORT=next to you (Full Version)
0f 00 00 00  # フィールドサイズ(LE: 15)
41 4c 42 55 4d 41 52 54 49 53 54 3d 75 6d 61  # ALBUMARTIST=uma
1a 00 00 00  # フィールドサイズ(LE: 26)
41 52 54 49 53 54 3d 75 6d 61 20 66 65 61 74 2e 20 e6 a9 98 e8 8a b1 e9 9f b3  # ARTIST=uma feat. 橘花音
16 00 00 00  # フィールドサイズ(LE: 22)
41 4c 42 55 4d 3d 42 52 41 56 45 20 5b 4d 59 5d 20 57 4f 52 4c 44  # ALBUM=BRAVE [MY] WORLD
0e 00 00 00  # フィールドサイズ(LE: 14)
54 4f 54 41 4c 54 52 41 43 4b 53 3d 31 33  # TOTALTRACKS=13
3e 00 00 00  # フィールドサイズ(LE: 62)
41 52 54 49 53 54 53 4f 52 54 3d ef bd 95 ef bd 8d ef bd 81 e3 80 80 ef bd 86 ef bd 85 ef bd 81 ef bd 94 ef bc 8e e3 80 80 e3 82 bf e3 83 81 e3 83 90 e3 83 8a e3 82 ab e3 83 8e e3 83 b3  # ARTISTSORT=uma feat. 橘花音
19 00 00 00  # フィールドサイズ(LE: 25)
41 4c 42 55 4d 41 52 54 49 53 54 53 4f 52 54 3d ef bd 95 ef bd 8d ef bd 81  # ALBUMARTISTSORT=uma
3a 00 00 00  # フィールドサイズ(LE: 58)
41 4c 42 55 4d 53 4f 52 54 3d ef bc a2 ef bc b2 ef bc a1 ef bc b6 ef bc a5 e3 80 80 ef bc bb ef bc ad ef bc b9 ef bc bd e3 80 80 ef bc b7 ef bc af ef bc b2 ef bc ac ef bc a4  # ALBUMSORT=BRAVE [MY] WORLD

UTF-8でエンコードされているので、説明が長くなってしまいます…。

写真

アルバムアートワークなどが含まれています。

06  # メタデータ続きあり(0b0)、写真(0b0000110)
01 64 df  # 写真系列長(91359)
00 00 00 03  # 写真タイプ: 3 (表紙アルバムアート)
00 00 00 0a  # メディアタイプ(MIMEタイプ)文字列長: 10
69 6d 61 67 65 2f 6a 70 65 67  # メディアタイプ: image/jpeg
00 00 00 00  # 写真説明文字列長: 0
00 00 00 00  # 写真幅: 0
00 00 00 00  # 写真高さ: 0
00 00 00 00  # 写真色深度: 0
00 00 00 00  # インデックスカラー(GIFなど向け): 0
00 01 64 B5  # 写真データ長: 91317
ff d8 ff e0 00 10 4a 46 49 46 00 ...  # JPEGデータ

アプリケーション

アプリケーション固有のメタデータが含まれています。 正直意味はわからんかった…。

02  # メタデータ続きあり(0b0)、アプリケーション(0b0000010)
00 24 8a  # アプリケーション系列長(9354)
53 4d 46 4d  # アプリケーションID: SMFM
47 42 50 4d 53 54 41 45 4d 4d ...  # アプリケーションデータ

そもそもアプリケーションIDは一意になるように登録制らしいのですが、このアプリケーションIDは登録されておらず、出どころが不明です。

パディング

メタデータブロックの中で、ファイルサイズを変えずにメタデータブロックに情報を追加できるようにパディングを設定できます。

81  # メタデータ続きなし(0b1)、パディング(0b0000001)
00 1b 4f  # パディング系列長(6991)
00 00 00 00 00 00 00 00 ...  # パディング

メタデータ続きなしのフラグが立っているので、これでメタデータブロックは終わりです。

フレーム: 定数サブフレーム

フレームは、音声となる情報が含まれています。 定数サブフレームを含むフレームはものすごく単純で、サンプル値がそのまま格納されているだけ(なはず)です。

フレームヘッダ

ff f8  # フレーム同期コード(0b1111 1111 1111 100)とブロック戦略ビット 0b0: 固定ブロックサイズ
c  # ブロックサイズ: 2 ^ 0x0c = 4096
9  # サンプリングレート: 44100Hz
1  # チャンネル数: 2(左右)
8  # ビット深度: 16bit/予約済みビット: 0
00  # UTF-8でエンコードされたフレームの開始番号(可変ブロックサイズならブロックの開始番号): 0
c2  # CRC-8チェックサム

サブフレーム(右チャンネル)

00  # 0b0(予約済み)/0b000000(定数サブフレーム)/0b0(無駄ビットなし)
0000  # 16ビットの定数サブフレーム: サンプル値が0

サブフレーム(左チャンネル)

00  # 0b0(予約済み)/0b000000(定数サブフレーム)/0b0(無駄ビットなし)
0000  # 16ビットの定数サブフレーム: サンプル値が0

フレームヘッダ

b8 ee  # CRC-16チェックサム

フレーム: 線形予測(LPC)サブフレーム

定数サブフレームを含むサンプルは、サンプル値がそのまま格納されているだけ(なはず)なので、単純でしたが、flacのサブフレームには定数サブレームのみならず、4つのサブフレームが存在します。

  • 定数サブフレーム
  • 逐語的(Verbatim)サブフレーム
  • 固定予測サブフレーム
  • 線形予測(LPC)サブフレーム

今読んでいるバイナリの中ではまだ定数サブフレームと線形予測サブフレームしか出てきていないので、この2つの紹介に留めます。

フレームヘッダ

ff f8  # フレーム同期コード(0b1111 1111 1111 100)とブロック戦略ビット 0b0: 固定ブロックサイズ
c  # ブロックサイズ: 2 ^ 0x0c = 4096
9  # サンプリングレート: 44100Hz
a  # チャンネル数: 2(ミッド/サイドステレオ、ミッド: (左 + 右) / 2、サイド: (左 - 右))
8  # ビット深度: 16bit/予約済みビット: 0
05  # UTF-8でエンコードされたフレームの開始番号: 5
96  # CRC-8チェックサム

初めてミッドサイドステレオが出てきたので、少し解説しておくと、ミッドサイドステレオは、左右の音声をミッドとサイドに分離するステレオ方式です。 ミッドは左右の音声を平均化した音声で、サイドは左右の音声の差分です。 これらを左右に復元する際には、サイドが奇数かどうかに注意する必要があります。

  • サイドが奇数
    • 左: (ミッド * 2 + 1 + サイド) / 2
    • 右: (ミッド * 2 + 1 - サイド) / 2
  • サイドが偶数
    • 左: (ミッド * 2 + サイド) / 2
    • 右: (ミッド * 2 - サイド) / 2

サブフレーム(ミッドチャンネル)

40  # 0b0(予約済み)/0b100000(1次線形予測サブフレーム)/0b0(無駄ビットなし)
0000  # 16ビットのウォームアップサンプル x 1次: サンプル値が0
b  # 4ビットの予測係数の精度: 0b1011 + 1 = 12
5 af 18  # 線形予測係数シフト(2の補数 5ビット): 0b0101 1 / 予測子係数(精度 x 予測次数 = 12 x 1 = 12ビット): 0b010 1111 0001 1 / ライスコード: 0b00
(8) 8  # パーティション次数: (0b0) 100 = 4
# この先2 ^ 4 = 16のパーティションが存在
(8) 1  # エンコードパラメータ: 0b0 000 = 0
# この先(4096 / (2 ^ 4)) - 1(予測子次数) = 255ビット分がエンコード残差
(1) ff  # 商0、あまり0(1が来るまで0のビットを数え、その後のビットをエンコードパラメータ分=0ビット数える)x9
ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff  # 商0、あまり0 x128
ff ff ff ff ff ff ff ff ff ff ff ff ff ff fc   # 商0、あまり0 x118、1パーティションの終わり
(c) 3  # 0b00 00: エンコードパラメータ
# この先(4096 / (2 ^ 4)) = 256ビット分がエンコード残差
(3)f ff  # 商0、あまり0 x14
...
(c) 3  # 0b00 00: エンコードパラメータ
# この先256サンプルのエンコード残差
(3)f ff ff ff ff ff ff ff ff ff ff ff ff ff ff  # 商0、あまり0 x118
d  # 0b1101: 商0、あまり0 x2、商1、あまり0 x1
9  # 0b1001: 商0、あまり0 x1、商2、あまり0 x1
7  # 0b0111: 商1、あまり0 x1、商0、あまり0 x2
e  # 0b111: 商0、あまり0 x3
(e) 5 0b0 0b1001  # 商2、あまり0 x1、商0、あまり0 x1、商2、あまり0 x1
....

奇数ビット区切りとか気が狂うからやめてくれ。 いやそもそも人間が読むものではないという指摘は受け付けないことにする。

なんか、曲の初めで音が0なので、0ばっかりになっています。でも定数フレームより小さそう。 それにしても1パーティション255ビットor256ビットもあるのを16パーティションx2チャンネルもあるので、人間が読むのは辛い、サイドチャンネルは省略します。

そもそも逐次的に読んでいかないとサイドチャンネルのサブフレームにもたどり着けないので、読めてない…

ちなみに、ここには書いていませんが、本来であれば線形予測サブフレームでは商・あまりを元に、更に計算を行ってサンプル値を求めます。 ここで手本を見せられるほど簡単ではない…。

まとめ

私たちは機械じゃない!人間なんだ……!

参考文献