MP4 是目前最通用的多媒体容器格式,其设计基于 ISO/IEC 14496-12(ISO Base Media File Format,ISOBMFF) 和 ISO/IEC 14496-14。理解 MP4 的结构,对于音视频封装、转封装、流媒体传输以及自定义媒体处理都至关重要。下面从数据组织、核心盒子、编解码配置以及实际解析几个方面展开。
1. MP4 的基本设计理念
MP4 是一种面向对象、分层嵌套的容器格式,所有数据都被包装在 盒子(Box) 中。每个盒子都有长度、类型和数据负载,可以包含子盒子。这种结构使得格式具有很强的扩展性,能够容纳各种编码格式的媒体流以及元数据。
文件扩展名:.mp4、.m4v、.m4a、.fmp4 等。
2. 盒子(Box)的通用结构
每个盒子由以下字段开头:
| 字段 | 字节数 | 描述 |
|---|---|---|
size | 4 | 盒子总大小(包括头部),若为 0 表示盒子延伸至文件末尾;若为 1 表示使用扩展大小(64 位)。 |
type | 4 | 盒子的类型,通常用 4 个 ASCII 字符表示(如 ftyp、moov)。 |
对于 size == 1 的情况,在 type 之后会跟随一个 8 字节的扩展大小字段。某些盒子(Full Box)还会包含 version(1 字节)和 flags(3 字节)字段。
3. MP4 文件的顶层结构
一个典型的 MP4 文件由以下几个顶层盒子构成:
+ ftyp
+ moov
+ mdat
+ [free / skip] (可选)
3.1 ftyp —— 文件类型盒
必须位于文件开头,用于标识文件格式和兼容性。
- 字段:
major_brand:主要品牌,如mp42、isom、avc1。minor_version:次要版本。compatible_brands:兼容品牌列表,表示该文件可以按哪些规范解析。
示例:mp42 + isom + avc1 表示这是一个兼容 MP4 v2、基本 ISO 以及 AVC 的视频文件。
3.2 moov —— 元数据盒
包含所有媒体元数据,是文件的大脑。它必须出现在 mdat 之前(对于普通 MP4),或者和 mdat 交替出现(对于分段 MP4)。moov 内部嵌套了多个子盒:
mvhd(电影头):全局信息,如时长、时间刻度、创建时间等。trak(轨道盒):每个音视频、字幕流对应一个trak。内部包含:tkhd(轨道头):轨道 ID、时长、宽高、变换矩阵等。mdia(媒体盒):描述轨道的具体媒体类型和数据引用。mdhd(媒体头):媒体时长、语言等。hdlr(处理器):声明媒体类型(vide、soun、text)。minf(媒体信息盒):包含实际数据如何存放的详细描述,其中最重要的子盒是stbl(Sample Table Box)。
3.3 mdat —— 媒体数据盒
存储实际的音视频帧数据(称为 sample)。数据按顺序连续存放,由 stbl 中的索引表描述每个 sample 的位置和大小。
3.4 free / skip —— 空闲盒
通常用于预留空间,或作为元数据更新时的占位符。
4. 深入 stbl —— 样本表(Sample Table)
stbl 是 minf 下的关键子盒,负责将 mdat 中的二进制数据与时间轴关联起来。它由多个表组成:
| 盒子 | 作用 |
|---|---|
stsd (Sample Description) | 样本描述,包含编解码器配置信息,如 avcC(H.264)、hvcC(H.265)、esds(AAC)等。 |
stts (Time to Sample) | 将解码时间(DTS)映射到样本序号。通过(样本数,每样本时长)的方式压缩存储。 |
stsc (Sample to Chunk) | 将样本映射到数据块(chunk)。一个 chunk 可能包含多个连续样本。 |
stsz (Sample Size) | 每个样本的字节大小。若所有样本大小相同,则使用 stz2 简化存储。 |
stco / co64 (Chunk Offset) | 每个 chunk 在文件中的偏移量(相对于文件开头)。stco 使用 32 位偏移,co64 用于大于 4GB 的文件。 |
stss (Sync Sample) | 标识哪些样本是关键帧(如 IDR 帧)。用于随机访问。 |
ctts (Composition Time to Sample) | 记录 PTS 与 DTS 的偏移量(仅当存在 B 帧时需要)。 |
寻址流程:给定一个时间点 → 通过 stts 找到对应样本序号 → 通过 stsc 找到所属 chunk → 通过 stco 找到 chunk 起始位置 → 通过 stsz 计算该样本在 chunk 内的偏移 → 读取数据。
5. 编解码器配置的存储方式
MP4 不直接存储 SPS 和 PPS 这样的原始 NAL 单元,而是将它们封装在特定的盒子中,存放在 stsd 内部。
5.1 H.264 / AVC
- 盒子名称:
avcC(AVC Configuration Box) - 位置:
moov.trak.mdia.minf.stbl.stsd.[avc1/hvc1].avcC - 内容:包含版本、档次、级别、长度字段大小、SPS 和 PPS 的完整 NAL 单元。
解码器必须先解析 avcC,才能正确解码后面的 mdat 中的 AVC 样本。
5.2 H.265 / HEVC
- 盒子名称:
hvcC(HEVC Configuration Box) - 存储:类似
avcC,但结构更复杂,支持更多参数集(VPS、SPS、PPS 等)。
5.3 AAC 音频
- 盒子名称:
esds(Elementary Stream Descriptor) - 位置:
stsd下的mp4a或enca中。 - 内容:包含音频对象类型、采样率、声道数以及解码器特定配置(如 AudioSpecificConfig)。
6. 分段 MP4(Fragmented MP4)
对于流媒体和自适应传输(如 DASH、HLS),传统 MP4 需要将整个 moov 放在文件头,导致必须解析完所有元数据才能播放。分段 MP4 将文件划分为多个片段,每个片段包含自己的 moof(Movie Fragment Box)和 mdat。
moof:包含该片段的样本表(类似于stbl的简化版),描述片段内的样本布局。mdat:存放该片段的媒体数据。
moov 中仍保留 mvex(Movie Extends Box)来描述全局信息,但不再需要完整的 stbl。分段 MP4 支持动态生成和增量播放,非常适合 HTTP 直播。
7. 实际分析 MP4 文件的工具与方法
7.1 使用 FFmpeg 查看结构
ffprobe -show_format -show_packets input.mp4
-show_format:显示格式信息(包括盒子结构概览)。-show_packets:显示每个 packet 的 PTS、DTS、大小等。
7.2 使用 MP4Box 分析
MP4Box 是 GPAC 项目的一部分,可详细打印盒子树:
mp4box -info input.mp4
mp4box -diso input.mp4 # 显示完整 ISO 结构
7.3 手动解析(Python 示例)
可以用简单的脚本解析 MP4 的盒子层次,例如:
import struct
def parse_box(f):
header = f.read(8)
if len(header) < 8:
return None
size, type = struct.unpack('>I4s', header)
type = type.decode('ascii')
data = f.read(size - 8) if size > 8 else b''
return (type, size, data)
with open('input.mp4', 'rb') as f:
while True:
box = parse_box(f)
if not box:
break
print(box[0], box[1])
# 根据需要递归处理 moov、trak 等
8. 常见问题与工程启示
- “moov 在文件末尾”:某些 MP4 文件(如从网络下载的)将
moov放在文件末尾,需要先下载整个文件才能播放。流媒体优化通常要求moov前置(faststart),可通过 FFmpeg 实现:ffmpeg -i input.mp4 -c copy -movflags +faststart output.mp4 - 解码器配置缺失:如果
avcC或esds损坏或丢失,解码器将无法初始化。这是很多转码工具容易出错的地方。 - 音视频同步:MP4 使用
mvhd中的时间刻度和stts来保证音视频轨道的同步播放。