跳转至

MP3 文件格式详解

一、概述

MPEG(Moving Picture Experts Group,活动图像专家组)音频文件是 MPEG-1 标准中的声音部分,根据压缩质量和编码复杂程度划分为三层:

层级 文件扩展名 压缩率 复杂度
Layer 1 .mp1 4:1
Layer 2 .mp2 6:1-8:1
Layer 3 .mp3 10:1-12:1

MP3 压缩原理

MP3 采用 有损压缩 方式,使用 感官编码技术

  1. 频谱分析:对音频文件进行频谱分析
  2. 噪声过滤:滤掉人耳不敏感的噪音电平
  3. 量化编码:将剩余数据量化并重新排列
  4. 高压缩比:达到 10:1-12:1 的压缩率

示例:1 分钟 CD 音质音乐(44.1kHz/16bit/立体声)

  • 未压缩:约 10MB
  • MP3 压缩:约 1MB

二、MP3 文件整体结构

┌─────────────────────┐
│     ID3V2 标签      │  ← 文件开始,长度可变
├─────────────────────┤
│                     │
│   音频数据帧序列    │  ← 文件主体,多个帧组成
│                     │
├─────────────────────┤
│     ID3V1 标签      │  ← 文件结尾,128字节固定
└─────────────────────┘
  1. ID3V2:位于文件开始,存储详细的元数据(作者、专辑等),长度可变
  2. 音频数据帧:文件主体,包含实际的音频数据,每个帧独立编码
  3. ID3V1:位于文件结尾,存储基本元数据,128字节固定长度

三、ID3V2 标签详解

3.1 标签头结构(10 字节)

偏移 长度 字段 描述 示例值
0 3 Header 必须为 "ID3" 49 44 33
3 1 Ver 版本号(ID3v2.3 = 3) 03
4 1 Revision 副版本号(通常为 0) 00
5 1 Flag 标志字节 00
6 4 Size 标签大小(含帧头) 见下文

标志字节详解(Flag):

a b c 0 0 0 0 0
├─┬─┬─┴───────── 保留位(总为0)
│ │ └─ c:测试标签标志(通常0)
│ └─ b:扩展头标志(通常0)
└─ a:Unsynchronisation标志(通常0)

标签大小计算(Size):

四个字节每个只使用低 7 位,最高位恒为 0:

Size[0] 0xxxxxxx
Size[1] 0xxxxxxx
Size[2] 0xxxxxxx
Size[3] 0xxxxxxx

计算公式:

total_size = (Size[0] & 0x7F) << 21 |
             (Size[1] & 0x7F) << 14 |
             (Size[2] & 0x7F) << 7  |
             (Size[3] & 0x7F);

示例计算

Size[0..3] = 00 00 2F 76
total_size = (0x00 & 0x7F) * 0x200000 + 
             (0x00 & 0x7F) * 0x400 + 
             (0x2F & 0x7F) * 0x80 + 
             (0x76 & 0x7F)
           = 0x872 = 2162 字节

3.2 标签帧结构

每个标签帧由 10 字节帧头 + 数据内容 组成:

偏移 长度 字段 描述
0 4 Frame ID 帧标识符(如 "TIT2")
4 4 Size 数据内容大小
8 2 Flags 帧标志

常用帧标识符:

标识符 含义 说明
TIT2 标题 歌曲标题
TPE1 艺术家 表演者/艺术家
TALB 专辑 所属专辑
TRCK 音轨号 格式:"N/M"(第N首/共M首)
TYER 年份 发行年份
TCON 流派 音乐类型
COMM 注释 格式:"语言代码\0注释内容"

帧大小计算:

frame_size = Size[0] << 24 |
             Size[1] << 16 |
             Size[2] << 8  |
             Size[3];

帧标志(Flags):

a b c 0 0 0 0 0 i j k 0 0 0 0 0
├─┬─┬─┴─────────┼─┬─┬─┴───────── 保留位
│ │ │           │ │ └─ k:组标志
│ │ │           │ └─ j:加密标志
│ │ │           └─ i:压缩标志
│ │ └─ c:只读标志
│ └─ b:文件保护标志
└─ a:标签保护标志

四、音频数据帧详解

4.1 帧头结构(4 字节)

每个音频帧以 4 字节帧头开始:

typedef struct {
    unsigned int sync:11;            // 同步字(全1)
    unsigned int version:2;          // MPEG版本
    unsigned int layer:2;            // 层级
    unsigned int crc_protect:1;      // CRC保护标志
    unsigned int bitrate_index:4;    // 比特率索引
    unsigned int sampling_rate:2;    // 采样率索引
    unsigned int padding:1;          // 填充位
    unsigned int private_bit:1;      // 私有位
    unsigned int channel_mode:2;     // 声道模式
    unsigned int mode_ext:2;         // 模式扩展
    unsigned int copyright:1;        // 版权标志
    unsigned int original:1;         // 原版标志
    unsigned int emphasis:2;         // 强调方式
} MP3FrameHeader;

4.2 帧头解析示例

帧头:FF FB 90 04(二进制:11111111 11111011 10010000 00000100

位域 位数 含义
0-10 11 11111111111 同步字
11-12 2 11 MPEG版本:MPEG-1
13-14 2 01 层级:Layer 3
15 1 1 CRC保护:无
16-19 4 1001 比特率索引:128 kbps
20-21 2 00 采样率索引:44.1 kHz
22 1 0 填充:无
23 1 0 私有位:未使用
24-25 2 00 声道模式:立体声
26-27 2 00 模式扩展:未使用
28 1 0 版权:无
29 1 1 原版:是
30-31 2 00 强调方式:无

4.3 帧长度计算

每帧采样数:

层级 MPEG-1 MPEG-2/LSF
Layer 1 384 384
Layer 2 1152 1152
Layer 3 1152 576

帧长度计算公式:

// Layer I
frame_length = ((采样数 / 8 × 比特率) / 采样率) + 填充 × 4

// Layer II & III
frame_length = ((采样数 / 8 × 比特率) / 采样率) + 填充

示例计算(MPEG-1 Layer III):

  • 比特率:128 kbps = 128,000 bps
  • 采样率:44.1 kHz = 44,100 Hz
  • 每帧采样数:1152
  • 填充:0
帧长度 = ((1152 / 8 × 128000) / 44100) + 0
       = (144 × 128000) / 44100
       = 417 字节(约)

4.4 帧持续时间

每帧时间(ms) = (每帧采样数 / 采样率) × 1000

示例

帧时间 = (1152 / 44100) × 1000 = 26.12 ms

4.5 音频数据帧组成

┌─────────────────────────────────┐
│      帧头(4字节)               │
├─────────────────────────────────┤
│    CRC校验(可选,2字节)         │ ← 仅当crc_protect=0时存在
├─────────────────────────────────┤
│    Side Info(通道信息)          │ ← Layer III:32字节(立体声)
├─────────────────────────────────┤
│    Scale Factor(增益因子)       │
├─────────────────────────────────┤
│    Huffman编码数据(主数据)      │
└─────────────────────────────────┘

4.6 CBR vs VBR

CBR(恒定比特率)

  • 所有帧使用相同比特率
  • 帧长度固定(除可能有填充外)
  • 易于计算文件总时长

VBR(可变比特率)

  • 根据音频复杂度动态调整比特率
  • 文件开头包含特殊帧标识:
  • XING Header:包含 "Xing" 或 "Info"
  • VBRI Header:包含 "VBRI"(较少见)

XING Header 位置:

条件 起始偏移
MPEG-1,非单声道 帧头后 36 字节
MPEG-1,单声道 帧头后 21 字节
MPEG-2,非单声道 帧头后 21 字节
MPEG-2,单声道 帧头后 13 字节

XING Header 包含:

  • 总帧数
  • 文件总大小
  • 100个时间段的帧索引(用于快速定位)
  • 编码器信息

五、ID3V1 标签详解

5.1 结构(128 字节固定)

typedef struct {
    char header[3];      // 必须为 "TAG"
    char title[30];      // 标题
    char artist[30];     // 艺术家
    char album[30];      // 专辑
    char year[4];        // 年份(YYYY)
    char comment[28];    // 注释
    char zero_byte;      // 保留(通常为0)
    char track;          // 音轨号(ID3v1.1新增)
    char genre;          // 流派编号
} ID3V1_Tag;

5.2 内存布局

字节  长度  内容
0-2    3    "TAG"(标识)
3-32   30   标题(不足补\0)
33-62  30   艺术家(不足补\0)
63-92  30   专辑(不足补\0)
93-96  4    年份(YYYY)
97-124 28   注释
125    1    保留(ID3v1.1:音轨号,0=无音轨号)
126    1    音轨号(ID3v1.1,0=无音轨号)
127    1    流派编号(0-147)

5.3 常用流派编号(部分)

编号 流派 编号 流派
0 Blues 1 Classic Rock
2 Country 3 Dance
4 Disco 5 Funk
6 Grunge 7 Hip-Hop
8 Jazz 9 Metal
10 New Age 11 Oldies
12 Other 13 Pop
14 R&B 15 Rap
16 Reggae 17 Rock
18 Techno 19 Industrial
20 Alternative ... ...

六、MP3 文件解析示例

6.1 文件结构分析

偏移       内容                说明
0x0000    ID3V2 标签头         "ID3"标识
0x000A    ID3V2 标签帧         多个帧组成
...       音频数据帧           从ID3V2后开始
文件尾-128 ID3V1 标签          固定128字节

6.2 音频帧识别流程

int find_mp3_frames(FILE *fp) {
    unsigned char header[4];

    // 查找第一个有效帧头(同步字11位全1)
    while (!feof(fp)) {
        fread(header, 1, 4, fp);

        if ((header[0] == 0xFF) && ((header[1] & 0xE0) == 0xE0)) {
            // 找到有效帧头
            return ftell(fp) - 4;
        }

        // 未找到,回退3字节继续搜索
        fseek(fp, -3, SEEK_CUR);
    }
    return -1;
}

6.3 帧类型判断

int get_mpeg_version(unsigned char byte2) {
    int version_bits = (byte2 >> 3) & 0x03;
    switch (version_bits) {
        case 0: return 2;  // MPEG-2.5
        case 2: return 2;  // MPEG-2
        case 3: return 1;  // MPEG-1
        default: return 0; // 保留
    }
}

int get_layer(unsigned char byte2) {
    int layer_bits = (byte2 >> 1) & 0x03;
    switch (layer_bits) {
        case 1: return 3;  // Layer III
        case 2: return 2;  // Layer II
        case 3: return 1;  // Layer I
        default: return 0; // 保留
    }
}