ID3v2的header中的扩展标头(Extended Header),其Size字段如何计算整个ID3的长度?
首先,要明确一点:扩展标头(Extended Header)的Extended Header Size长度和整个ID3标签的Size长度是完全不同的两个概念,它们计算的对象和方式都不同。
问题核心是“当扩展标头存在且不同步(Unsynchronisation)即值为1时,header的Size字段如何计算整个ID3的长度”。这个问题的答案其实是:它不直接计算整个ID3的长度。整个ID3标签的长度是由主标头(Main Header)中的Size字段来定义的。
下面我们分步解释,并说明扩展标头和不同步标志在其中扮演的角色。
■1. 主标头(Main Header)的Size字段(计算整个ID3长度)
ID3v2标签的最开始10个字节是主标头,其结构如下:
| 字段 | 长度 | 说明 |
| 文件标识 | 3 bytes | 恒为`ID3` |
| 版本 | 2 bytes | 例如 `0x03 0x00` 代表 ID3v2.3.0 |
| 标志 | 1 byte | 第6位表示是否存在扩展标头,第7位表示是否启用不同步 |
| Size | 4 bytes | 这是关键!它定义了整个ID3标签(不包括主标头10字节)的大小 |
主标头Size的计算方法(Synchsafe整数):
这个4字节的Size字段是一个“synchsafe”整数。它的最高位(bit 7)总是0,以防止与MPEG等音频帧的同步字冲突。因此,每个字节实际只有7位有效数据。
计算公式为:
`总大小 = (byte1 & 0x7F) * 0x200000 + (byte2 & 0x7F) * 0x400 + (byte3 & 0x7F) * 0x80 + (byte4 & 0x7F)`
简单来说:这个Size的值 = 扩展标头(如果有)的大小 + 所有帧(Frame)+填充块(Padding)(如果有)的大小之和。 整个ID3标签的绝对字节数等于 10(主标头) + 这个Size值。
无论是否启用扩展标头或不同步,整个标签的长度都由主标头的这个Size字段决定。
■2. 扩展标头(Extended Header)的Size字段
扩展标头是可选的。它的存在与否由主标头Flags字节的第6位(bit 5,从0开始数)指示。
扩展标头也有自己的Size字段,但这个Size字段只表示扩展标头本身的大小。
* 在 ID3v2.3 中,扩展标头通常为6或10字节(包括它自己的Size字段)。它的Size字段是32位(4字节)的普通整数(不是synchsafe)。
* 在 ID3v2.4 中,扩展标头的Size字段是32位的synchsafe整数。
重要关系:
主标头中的Size = 扩展标头的Size + 所有帧(Frame)+填充块(Padding)的大小之和。
所以,扩展标头的Size只是主标头Size的一部分,它绝不代表整个ID3标签的长度。
■3. 不同步(Unsynchronisation)标志与Size计算
不同步标志位于主标头Flags字节的第7位(bit 7)。当它为`1`时,表示标签的数据部分(即扩展标头之后的所有帧数据)使用了“不同步”方案。
* 目的:防止某些旧的软件/硬件播放器将标签数据中的`0xFF 0xXX`错误地识别为MPEG音频帧的同步信号,从而导致播放问题。
* 操作:在不同步方案下,数据中所有的`0xFF 0x00`序列(以及在某些情况下单独的`0xFF`后跟一个类似同步字的字节)都会被编码。编码方式是在`0xFF 0x00`或`0xFF`之后插入一个`0x00`字节。例如:
* `0xFF 0x00` -> 被存储为 `0xFF 0x00 0x00`
* `0xFF 0xE0` -> 被存储为 `0xFF 0x00 0xE0` (因为 `0xE0`的高位是1,看起来像同步字的一部分)
这对Size计算的关键影响:
1. 主标头Size是“解码前”的大小:主标头中的Size字段(synchsafe integer)所表示的数值,指的是数据部分(扩展标头+所有帧+填充块)在经过不同步解码(即移除所有插入的`0x00`字节)之后的大小。
2. 文件实际存储是“编码后”的大小:标签在文件中实际存储时,数据部分是经过不同步编码的(插入了额外的`0x00`字节),所以它在磁盘上占用的空间大于主标头Size字段所指示的大小。
计算流程总结(当不同步标志为1时):
1. 读取主标头10字节。
2. 从主标头的Size字段(4字节synchsafe)计算出数据部分的解码后大小,我们称之为 `decoded_data_size`。
3. 从文件的第11字节开始,读取 `decoded_data_size` 个字节的数据。但是,在读的过程中,必须实时进行不同步解码(遇到`0xFF 0x00`就丢弃后面的`0x00`)。解码后得到的数据长度正好就是 `decoded_data_size`。
4. 这 `decoded_data_size` 字节的解码后数据,其开头的部分(如果存在)是扩展标头,剩余部分是所有的帧(Frames)以及填充块(Padding)。
■结论
* 扩展标头的Size字段只计算扩展标头自身的长度,与整个ID3标签长度无关。
* 整个ID3标签的长度始终由主标头的Size字段(10字节后的4字节synchsafe整数)定义。
* 当不同步(Unsynchronisation)值为1时,主标头的Size字段表示的是解码后的数据部分(扩展标头+所有帧)的长度。你要用这个值作为目标,去读取并解码后续的数据,解码后的数据量会刚好等于这个值。文件实际存储的(编码后的)数据长度会比这个值大。
简而言之:算总长,只看主标头的Size,并用它加上10。不同步标志只影响如何解析这Size字节的数据,不影响Size值本身的意义。