浅析 ID3 v2.3 标签头结构

ID3 是一种用于存储音频文件元数据的标准,常用于 MP3 文件,但是 AAC、WAV 等文件也可以使用 ID3 标签。

ID3 标签有多个版本,ID3 v1 位于文件末尾,ID3 v2 位于文件开头。截止本文撰写时,最新的 ID3 版本是 v2.4,但是 v2.3 仍然是最常见的版本。因此,本文将介绍 ID3 v2.3 标签头结构。

ID3 标签

本文仅介绍最基本的 ID3 数据结构。简单的说,ID3 标签可以分为两部分:标签头和标签内容。

1
id3 = id3_header + (id3_tag * n)

下面是一个 ID3 数据的例子:

1
2
3
4
49 44 33 03 00 00 00 00 00 28 54 50 45 31 00 00  |ID3......(TPE1..|
00 0b 00 00 01 ff fe 7a 82 2f 67 b6 5b 00 00 54 |.......z./g.[..T|
49 54 32 00 00 00 09 00 00 01 ff fe 07 68 98 98 |IT2..........h..|
00 00 |..|

下载示例数据:id3_example.bin

ID3 长度编码

MSB 编码(Most Significant Bit Encoding) 是 ID3 中表示字段长度的编码方式。

MSB 编码方式为:

  1. 将长度表示为 int 类型 (4 字节)
  2. 将 int 类型的长度转换为二进制
  3. 舍弃最高 4 位,将剩余的 28 位分为 4 个 7 位的部分
  4. 对于每个 MSB 编码的字节,最高位为 0,低 7 位依次填入上一步的每部分(7 位)数据

MSB 编码映射

下面是一个用 Python 编写的 MSB 编码例程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
def msb_encoding(value: int, max_bytes = 4) -> None | bytes:
max_value = 0
counter = 0
encoded = 0
valid_bits = max_bytes * 8 - max_bytes
for i in range(max_bytes):
max_value |= 1 << i
max_value = max_value << valid_bits
if value > max_value:
return None # Overflow
for i in range(valid_bits):
if (counter + 1) % 8 == 0:
counter += 1
b = value & (1 << i)
b = b >> i
encoded = encoded | (b << counter)
counter += 1
return encoded.to_bytes(max_bytes, 'big')

ID3 v2.3 头部

格式

1
AA AA AA AA BB CC DD DD DD DD

例子

1
49 44 33 03 00 00 00 00 00 28
FieldLengthExampleDescription
A349 44 33ID3 标签头标识,固定为 ID3 的 ASCII 码
B203 00ID3 版本号,固定为 0x03 0x00
C100标志位,一般情况下为 0b00000000
D400 00 00 28MSB 编码的标签长度

长度计算

参与标签长度计算的元素为:

  • 对于每个帧
    1. + 10:帧头长度
      • + 4:帧类型
      • + 4:帧长度
      • + 2:标志位
    2. + frame.length:帧内容长度
1
2
3
4
5
6
7
8
9
10
# 计算 ID3 头部中的标签长度 (Python 伪代码)
FRAME_TYPE_LEN = 4
FRAME_SIZE_LEN = 4
FRAME_FLAG_LEN = 2
FRAME_HEADER_LEN = FRAME_TYPE_LEN + FRAME_SIZE_LEN + FRAME_FLAG_LEN

length = 0
for frame in frames:
length += FRAME_HEADER_LEN
length += frame.content_length

以示例数据为例,头部中的标签长度为 0x28 = 40 字节,计算如下:

1
2
3
4
5
6
7
8
9
10
11
12
[TPE1]TYPE          4
[TPE1]SIZE + 4
[TPE1]FLAG + 2
[TPE1]CONTENT + 11
---------------------
LENGTH 21
[TPE1]TYPE + 4
[TPE1]SIZE + 4
[TPE1]FLAG + 2
[TPE1]CONTENT + 9
---------------------
LENGTH 40

ID3 帧

格式

1
2
AA AA AA AA BB BB BB BB CC CC DD
EE ... [(length - 1) bytes]

例子

1
2
54 50 45 31 00 00 00 0B 00 00 01
FF FE 7A 82 2F 67 b6 5b 00 00 [10 bytes]
FieldLengthExampleDescription
A454 50 45 31ID3 帧类型,由 4 个大写字母和数字组成
B400 00 00 0BMSB 编码的帧长度(例子中为 0x0B = 11 字节)
C200 00标志位,一般情况下为 0b00000000 0b00000000
如果不是则接下来的结构不是本文所述
D101帧内容文字编码
0x00 为 ISO-8859-1,0x01 为 UTF-16
EvariantFF FE ... 00UTF-16 编码的帧内容
需要有 BOM 头 (FF FE),以 UTF-16 NULL 字符 (00 00) 结尾
F100帧内容的终止符,为 0x00

ID3 帧类型

ID3 帧类型由 4 个大写字母和数字组成,具体含义参见 官方定义表。例如:

  • TPE1:艺术家
  • TIT3:标题

在例子中,我们使用 艺术家 的 UTF-16 作为 TPE1 帧的内容;标题 的 UTF-16 作为 TIT3 帧的内容。

长度计算

参与帧长度计算的元素为:

  1. + 1:帧内容文字编码
  2. + frame_content:帧内容 E 的长度,包括:
    • frame_content_with_utf16bom_length:UTF-16 编码的帧内容长度,包括 BOM 头
    • 2:UTF-16 编码的帧内容的终止符
1
2
3
4
5
UTF16_ENDING     = b'\x00\x00'
ENCODING_LEN = 1
UTF16_ENDING_LEN = len(UTF16_ENDING)
content = frame_content.encode('utf-16') + UTF16_ENDING
length = ENCODING_LEN + len(content)

以示例数据为例,TPE1 帧的长度计算如下:

1
2
3
4
5
6
ENCODING      1
UTF16-BOM + 2
CONTENT + 6
UTF16-END + 2
----------------
LENGTH 11

参考资料

  1. ID3 标准文档:ID3v2.3.0 Informal Standard
  2. MSB 编码:Bit numbering - Wikipedia