3、MP4关键元数据:Track、Sample、Chunk、Sample Table(stbl)详解

MP4文件的结构,说白了就是一棵树。你打开一个MP4文件,最先看到的是ftyp,然后是moov和mdat。但真正决定播放器能不能正确解码的,是moov里的那堆元数据。今天我们就来啃最核心的几个概念:Track、Sample、Chunk,以及那个让人又爱又恨的Sample Table(stbl)。

我个人习惯把MP4的元数据比作一本字典。你查一个字,得知道它在哪一页、哪一行。Track就是章节,Sample就是每个字,Chunk就是连续的几页,而stbl就是索引目录。没有这个目录,播放器就得从头到尾扫描整个文件——那体验,你想想看。

核心关系速览:

  • Track:一条独立的媒体流(视频、音频、字幕等)
  • Sample:一个独立的媒体单元(一帧视频、一段音频)
  • Chunk:连续存储的一组Sample
  • stbl:描述Sample如何映射到文件偏移的索引表

3.1 Track:媒体流的容器

一个MP4文件可以包含多个Track。最常见的,一个视频Track加一个音频Track。每个Track都有自己的tkhd(Track Header),里面记录了时长、宽高、音量等基本信息。

我记得有一次接手一个项目,播放器总是卡在开头几秒。排查了半天,发现是Track的duration字段写错了,导致播放器预加载了过多数据。嗯,这里要注意:tkhd里的duration是媒体时间单位,不是毫秒,也不是秒。你得结合mdhd里的timescale才能换算成真实时间。

Track类型 常见子类型 关键Box
视频 h264, hevc, av1 stsd(Sample Description)
音频 aac, mp3, opus stsd + mp4a/mp4v等
字幕 tx3g, wvtt stsd + 字体描述
元数据 meta, timed metadata stsd + 自定义描述

避坑指南:我曾经在解析一个多Track文件时,发现第二个Track的tkhd里track_ID居然和第一个重复了。虽然规范不允许,但有些编码器确实会犯这种错。解析时一定要做去重校验,否则后面查stbl会乱套。

3.2 Sample:最小的媒体单元

Sample是什么?对于视频,一帧就是一个Sample。对于音频,一段连续的音频数据(通常是1024或2048个采样点)就是一个Sample。Sample是不可再分的媒体数据单元。

你可能会问:那一个Sample在文件里占多少字节?这得看编码格式和编码参数。H.264的I帧可能几十KB,B帧可能只有几KB。所以,每个Sample的大小是不固定的。这就引出了一个问题:播放器怎么知道每个Sample从哪里开始、到哪里结束?

答案就在stbl里。stbl里有一个叫stsz(Sample Size)的Box,它记录着每个Sample的字节大小。如果所有Sample大小相同(比如某些音频流),stsz可以只写一个默认值,省空间。

注意:Sample和NAL单元(H.264)不是一回事。一个Sample可能包含多个NAL单元(比如一个IDR帧包含SPS、PPS和Slice数据)。解析时不要混淆。

3.3 Chunk:Sample的物理分组

Sample是逻辑单元,Chunk是物理存储单元。编码器在写文件时,会把连续的几个Sample打包成一个Chunk,然后写入mdat。为什么这么做?为了减少文件碎片,提高读写效率。

举个例子:一个视频Track有1000个Sample,编码器可能每10个Sample组成一个Chunk。这样在mdat里就只有100个Chunk,而不是1000个零散的数据块。播放器读取时,一次I/O操作就能拿到10个Sample,效率高得多。

Chunk的信息记录在stco(Chunk Offset)里。每个Chunk有一个起始文件偏移,以及包含的Sample数量。嗯,这里有个细节:stco记录的是Chunk在mdat里的绝对偏移,不是相对于moov的偏移。

Box名称 作用 关键字段
stco Chunk在文件中的偏移 chunk_offset(数组)
stsc Sample到Chunk的映射 first_chunk, samples_per_chunk, sample_description_index
stsz 每个Sample的大小 sample_size(统一值或数组)
stts Sample的时间戳 sample_delta(数组)

关键理解:Chunk是物理概念,Sample是逻辑概念。播放器通过stsc知道:第N个Chunk包含哪些Sample;通过stco知道:这个Chunk在文件哪里;通过stsz知道:每个Sample多大。三者结合,就能精确定位任意一个Sample的数据。

3.4 Sample Table(stbl):元数据的核心

stbl是Track里最重要的Box。它下面有多个子Box,每个负责一类元数据。我把它叫做「五虎上将」:

  • stsd(Sample Description):描述Sample的编码格式、分辨率、采样率等。没有它,解码器不知道该怎么解码。
  • stts(Time-to-Sample):记录每个Sample的时长(delta)。播放器靠它计算时间戳。
  • stsc(Sample-to-Chunk):记录每个Chunk包含多少个Sample。这是连接逻辑和物理的桥梁。
  • stsz(Sample Size):记录每个Sample的字节大小。
  • stco(Chunk Offset):记录每个Chunk在文件中的起始偏移。

你想想看,播放器要播放第100帧视频,它需要做几步?

  1. 通过stts找到第100帧对应的Sample索引(因为时间戳可能不是均匀的)。
  2. 通过stsc找到这个Sample属于哪个Chunk。
  3. 通过stco找到这个Chunk的偏移。
  4. 通过stsz找到这个Sample在Chunk内的偏移和大小。
  5. 从mdat里读取数据,交给解码器。

每一步都离不开stbl。所以我说,stbl是MP4解析的「心脏」。我曾经在优化一个播放器时,发现随机seek特别慢。查到最后,是stco用了32位偏移,而文件超过4GB了。换成64位的co64后,问题解决。

大文件陷阱:如果文件超过4GB,stco必须使用64位版本(co64)。但有些老编码器会忽略这个限制,导致解析时偏移溢出。解析时一定要检查ftyp里的brand,如果是"isom"或"mp42",大概率支持64位。

3.5 整体结构图

下面这张图展示了Track、Sample、Chunk和stbl之间的关系。我建议你多看几遍,理解这个层级结构,后面解析代码写起来就顺了。

MP4 元数据核心结构 moov (Movie Box) trak (Track Box) tkhd (Track Header) mdia (Media Box) mdhd (Media Header) hdlr (Handler) minf (Media Information) stbl (Sample Table) stsd stts stsc stsz stco / co64 stbl 是 Track 元数据的核心,包含 5 个子 Box,共同描述 Sample 的编码、时间、大小和位置

3.6 实战:如何快速定位一个Sample

假设我们要读取视频Track的第50个Sample。伪代码如下:

// 1. 通过 stsz 获取第50个Sample的大小
sample_size = stsz.entry_sizes[49];  // 索引从0开始

// 2. 通过 stsc 找到第50个Sample属于哪个Chunk
// stsc 是一个分段表,需要遍历查找
chunk_index = find_chunk_for_sample(50, stsc.entries);

// 3. 通过 stco 获取该Chunk的文件偏移
chunk_offset = stco.chunk_offsets[chunk_index];

// 4. 计算Sample在Chunk内的偏移
// 需要知道该Chunk中前几个Sample的总大小
offset_within_chunk = 0;
for (i = 0; i < sample_index_within_chunk; i++) {
    offset_within_chunk += stsz.entry_sizes[first_sample_index + i];
}

// 5. 最终文件偏移
final_offset = chunk_offset + offset_within_chunk;

这段代码看起来简单,但实际项目中坑不少。比如stsc的first_chunk是从1开始计数的,不是0。还有stsz如果用了统一大小模式,entry_sizes数组可能为空,你得用sample_size字段代替。

我的习惯:在解析stbl时,我会先构建一个「Sample索引表」,把每个Sample的偏移和大小提前算好缓存起来。虽然占点内存,但seek时直接查表,速度飞快。对于4K视频,100万个Sample的索引表也就几十MB,值得。

3.7 常见问题与避坑

  • stco溢出:文件超过4GB时,必须用co64。解析时先检查stco的版本号,version=1就是64位。
  • stsc分段混乱:有些编码器写的stsc分段不合理,导致一个Chunk包含的Sample数量忽多忽少。解析时要做边界检查。
  • stts时间戳跳跃:如果stts里的delta为0,说明有连续多个Sample时间戳相同(比如B帧)。播放器要注意处理。
  • stsd多描述:一个Track可以有多个Sample Description(比如不同分辨率的视频)。解析时要根据stsc里的sample_description_index选择正确的描述。

嗯,今天就先聊到这里。Track、Sample、Chunk、stbl这四个概念,是MP4解析的基石。你把这套逻辑吃透了,后面讲moof(电影片段)和fragment(碎片化MP4)时,就会轻松很多。