原文地址:https://www.cnblogs.com/moonwalk/p/16244932.html
解析工具:
- https://gpac.github.io/mp4box.js/test/filereader.html (mp4box)
1. 概述
mp4 容器格式相较于 flv、ts 容器格式来说,其定义较为复杂,本篇文章主要记录自己的学习理解。
2. mp4 结构概览
一个 mp4 文件可以看作有两个大类:
- 记录媒体信息相关的部分
- 媒体负载部分
典型结构如下:
+-+-+-+-+-+-+-+-+-+-+-+-+-+
| ftyp | moov | mdat |
+-+-+-+-+-+-+-+-+-+-+-+-+-+
其中:
- ftyp(file type box),文件头,记录一些兼容性信息
- moov(movie box),记录媒体信息
- mdat(media data),媒体负载
mp4box 图示如下:
编辑
添加图片注释,不超过 140 字(可选)
3. box 结构
mp4 封装格式采用称为 box 的结构来组织数据。 结构如下:
+-+-+-+-+-+-+-+-+-+-+
| header | body |
+-+-+-+-+-+-+-+-+-+-+
其它所有 box 都在语法上继承自此基本 box 结构。
3.1 box header
box 分为普通 box 和 fullbox。 普通 box header 结构如下:
字段 | 类型 | 描述 |
---|---|---|
size | 4 Bytes | 包含 box header 的整个 box 的大小 |
type | 4 Bytes | 4 个 ascii 值,如果是 “uuid”,则表示此 box 为用户自定义,可忽略 |
large size | 8 Bytes | size=1 时才有的字段,用于扩展,例如 mdat box 会需要此字段 |
fullbox 在上面的基础上新增了 2 个字段:
字段 | 类型 | 描述 |
---|---|---|
version | 1 Bytes | 版本号 |
flags | 3 Bytes | 标识 |
3.2 box body
一个 box 可能会包含其它多个 box,此种 box 称为 container box。 因此 box body 可能是一种具体 box 类型,也有可能是其它 box。
4. ftyp(File Type Box)
ftyp 一般出现在文件的开头,用来指示该 mp4 文件使用的标准规范:
字段 | 类型 | 描述 |
---|---|---|
major_brand | 4 bytes | 主版本 |
minor_version | 4 bytes | 次版本 |
compatible_brands[] | 4 bytes | 指定兼容的版本,注意此字段是一个 list,即可以包含多个 4 bytes 版本号 |
一个示例如下:
编辑
添加图片注释,不超过 140 字(可选)
5. moov(Movie Box)
moov 是一个 container box,一个文件只有一个,其包含的所有 box 用于描述媒体信息。 moov 的位置可以紧随着 ftyp 出现,也可以出现在文件末尾. 由于是一个 container box,所以除了 box header,其 box body 就是其它的 box。 一个示例如下:
编辑
添加图片注释,不超过 140 字(可选)
这些 box 并列出现,其中:
- mvhd(moov header),用于简单描述一些所有媒体共享的信息
- trak,即 track,轨道,用于描述音频流或视频流信息,可以有多个轨道,如上出现了 2 次,分别表示一路音频和一路视频流
- udta(userdata),用户自定义,可忽略
5.1 mvhd(Movie Header Box)
mvhd 作为媒体信息的 header 出现(注意此 header 不是 box header,而是 moov 媒体信息的 header),用于描述一些所有媒体共享的基本信息。 mvhd 语法继承自 fullbox,注意下述示例出现的 version 和 flags 字段属于 fullbox header。
字段 | 类型 | 描述 |
---|---|---|
version | 8 bit | 取 0 或 1,一般取 0 |
flags | 24 bit | |
creation_time | 64/32 bit | 创建时间,当 version=0 时取 32bit |
modification_time | 64/32 bit | 修改时间,当 version=0 时取 32bit |
timescale | 32 bit | 时间基 |
duration | 64/32 bit | 文件时长,当 version=0 时取 32bit |
rate | 32 bit | 播放速率,默认取 0x00010000,即 1.0 |
volume | 16 bit | 音量,默认取 0x0100,即 1.0 |
reserved | 16 bit | 0 |
reserved | 2 x 32 bit | 0 |
matrix | 9 x 32 bit | 可忽略 |
pre_defined | 6 x 32 bit | 0 |
next_track_ID | 32 bit | 下一个紧邻的 track box id |
6. trak
trak box 是一个 container box,其子 box 包含了该 track 的媒体信息。 一个 mp4 文件可以包含多个 track,track之间是独立的,trak box 用于描述每一路媒体流。 一个示例如下:
编辑
添加图片注释,不超过 140 字(可选)
其中:
- tkhd(track header box),用于简单描述该路媒体流的信息
- edts(),
- mdia(media box),用于详细描述该路媒体流的信息
6.1 tkhd(track header box)
tkhd 作为媒体信息的 header 出现(注意此 header 不是 box header,而是 track 媒体信息的 header),用于描述一些该 track 的基本信息。 tkhd 语法继承自 fullbox,注意下述示例出现的 version 和 flags 字段属于 fullbox header。
字段 | 类型 | 描述 |
---|---|---|
version | 8 bit | 取 0 或 1,一般取 0 |
flags | 24 bit | |
creation_time | 64/32 bit | 创建时间,当 version=0 时取 32bit |
modification_time | 64/32 bit | 修改时间,当 version=0 时取 32bit |
track_ID | 32 bit | 本 track id |
reserved | 32 bit | 0 |
duration | 64/32 bit | 本 track 时长,当 version=0 时取 32bit |
reserved | 2 x 32 bit | 0 |
layer | 16 bit | |
alternate_group | 16 bit | |
volume | 16 bit | 音量,如果是 audio track,则为 0x0100,即 1.0,否则取 0 |
reserved | 16 bit | 0 |
matrix | 9 x 32 bit | 可忽略 |
width | 32 bit | 宽,[16.16] 格式,不必与sample的像素尺寸一致,用于播放时的展示宽高 |
height | 32 bit | 高,[16.16] 格式 ,不必与sample的像素尺寸一致,用于播放时的展示宽高 |
6.2 edts
6.3 mdia(media box)
mdia box 是一个 container box,其子 box 描述 track 更详细的媒体信息。 一个示例如下:
编辑
添加图片注释,不超过 140 字(可选)
其中:
- mdhd(Media Header Box),用于简单描述该路媒体流的信息
- hdlr(Handler Reference Box),主要定义了 track 类型
- stbl(Media Information Box),用于描述该路媒体流的解码相关信息和音视频位置等信息
6.3.1 mdhd(Media Header Box)
mdhd 作为媒体信息的 header 出现(注意此 header 不是 box header,而是 media 媒体信息的 header),用于描述一些该 track 的基本信息。 mdhd 语法继承自 fullbox,注意下述示例出现的 version 和 flags 字段属于 fullbox header。
字段 | 类型 | 描述 |
---|---|---|
version | 8 bit | 取 0 或 1,一般取 0 |
flags | 24 bit | |
creation_time | 64/32 bit | 创建时间,当 version=0 时取 32bit |
modification_time | 64/32 bit | 修改时间,当 version=0 时取 32bit |
timescale | 32 bit | 时间基,同 mvhd 中 timescale |
duration | 64/32 bit | 本 track 时长,当 version=0 时取 32bit |
pad | 1 bit | 0 |
language | 3 x 5 bit | 最高位为0,后面15位为3个字符(见ISO 639-2/T标准中定义) |
pre_defined | 16 bit | 0 |
6.3.2 hdlr(Handler Reference Box)
hdlr 主要定义了 track 类型。 hdlr 语法继承自 fullbox,注意下述示例出现的 version 和 flags 字段属于 fullbox header。
字段 | 类型 | 描述 |
---|---|---|
version | 8 bit | 取 0 或 1,一般取 0 |
flags | 24 bit | |
pre_defined | 32 bit | 0 |
handler_type | 32 bit | 取值为 vide:Video track,soun:Audio track,hint:Hint track,meta:Timed Metadata track,auxv:Auxiliary Video track |
reserved | 3 x 32 bit | 0 |
name | 不定长度 | 自定义名称,以 \0 结尾 |
7. 音视频 frame 与 sample、chunk 的关系
在继续介绍 stbl box 之前,需要先介绍一下 mp4 中定义的 sample 与 chunk:
- sample,ISO/IEC 14496-12 中定义 samples 之间不能共享同一个时间戳,因此,在音视频 track 中,一个 sample 代表一个视频或音频帧
- chunk,多个 sample 的集合,实际上音视频 track 中,chunk 与 sample 一一对应
8. stbl(Media Information Box)
stbl box 是一个 container box,是整个 track 中最重要的一个 box,其子 box 描述了该路媒体流的解码相关信息、音视频位置信息、时间戳信息等。 一个示例如下:
编辑
添加图片注释,不超过 140 字(可选)
其中:
- stsd(sample description box),存储了编码类型和初始化解码器需要的信息,并与具体编解码器类型有关
- stts(time to sample box),存储了该 track 每个 sample 到 dts 的时间映射关系
- stss(sync sample box),针对视频 track,关键帧所属sample 的序号
- ctts(composition time to sample box),存储了该 track 中,每个 sample 的 cts 与 dts 的时间差
- stsc/stz2(sample to chunk box),存储了该 track 中每个 sample 与 chunk 的映射关系
- stsz(sample size box),存储了该 track 中每个 sample 的字节大小
- stco/co64(chunk offset box),存储了该 track 中每个 chunk 在文件中的偏移
8.1 stsd(sample description box)
stsd 是个 container box,其存储了编码类型和初始化解码器需要的信息,并与具体编解码器类型有关:
字段 | 类型 | 描述 |
---|---|---|
version | 8 bit | 取 0 或 1,一般取 0 |
flags | 24 bit | |
entry_count | 32 bit | entry 个数 |
开始循环 | ||
AudioSampleEntry() | 不定大小 | 子 box,当 handler_type=‘soun’ 时才有 |
VisualSampleEntry() | 不定大小 | 子 box,当 handler_type=‘vide’ 时才有 |
HintSampleEntry() | 不定大小 | 子 box,当 handler_type=‘hint’ 时才有 |
MetadataSampleEntry() | 不定大小 | 子 box,当 handler_type=‘meta’ 时才有 |
结束循环 |
8.1.1 h264 stsd
对于 h264 视频,典型结构如下:
编辑
添加图片注释,不超过 140 字(可选)
其上(只列出 avc1 与 avcC,其余 box 可忽略):
- avc1,是 avc/h264/mpeg-4 part 10 视频编解码格式的代称,是一个 container box,但是 box body 也携带自身的信息
字段 | 类型 | 描述 |
---|---|---|
reserved | 6 x 8 bit | 0 |
data_reference_index | 16 bit | |
pre_defined | 16 bit | 0 |
reserved | 16 bit | 0 |
pre_defined | 3 x 32 bit | 0 |
width | 16 bit | 像素宽度 |
height | 16 bit | 像素高度 |
horizresolution | 32 bit | 每英寸的像素值(dpi),[16.16]格式的数据,固定为 0x00480000,72 dpi |
vertresolution | 32 bit | 每英寸的像素值(dpi),[16.16]格式的数据,固定为 0x00480000,72 dpi |
reserved | 32 bit | 0 |
frame_count | 16 bit | 每个 sample 中的视频帧数,固定为 1 |
compressorname | 32 bit | 0 |
depth | 16 bit | 0x0018,rgb24 位深 |
pre_defined | 16 bit | -1 |
- avcC(AVC Video Stream Definition Box),存储 sps && pps,即在 ISO/IEC 14496-15 中定义的 AVCDecoderConfigurationRecord 结构
字段 | 类型 | 描述 |
---|---|---|
configurationVersion | 8 bit | 固定 1 |
AVCProfileIndication | 8 bit | 编码时设置的 profile,例如 base、main、high 等,具体参见 ISO_IEC_14496-10-AVC-2003.pdf, page 45 |
profile_compatibility | 8 bit | |
AVCLevelIndication | 8 bit | 编码时设置的 level |
reserved | 6 bit | '111111’b |
lengthSizeMinusOne | 2 bit | |
reserved | 3 bit | '111’b |
numOfSequenceParameterSets | 8 bit | sps 个数,一般为 1 |
sps 循环开始 | ||
sequenceParameterSetLength | 16 bit | |
sps | 8 * sequenceParameterSetLength | sps |
sps 循环结束 | ||
numOfPictureParameterSets | 8 bit | pps 个数,一般为 1 |
pps 循环开始 | ||
pictureParameterSetLength | 16 bit | |
pps | 8 * pictureParameterSetLength | pps |
pps 循环结束 |
在 srs 中,解析 avcc/AVCDecoderConfigurationRecord 结构解析参见 srs/trunk/src/kernel/srs_kerner_codec.cpp::SrsFormat::avc_demux_sps_pps() 函数。
8.1.2 aac stsd
对于 aac 音频,典型结构如下:
编辑
添加图片注释,不超过 140 字(可选)
可以看到,aac stsd 结构比较复杂,box 众多。实际上,在 ISO/IEC 14496-3 中,定义了 AudioSpecificConfig 类型,aac stsd 结构主要信息就来自 AudioSpecificConfig。 具体不做分析,可以参看 srs 中:
- 解析 AudioSpecificConfig 结构的 srs/trunk/src/kernel/srs_kerner_codec.cpp::SrsFormat::audio_aac_sequence_header_demux() 函数
- 封装 aac stsd 结构的 srs/trunk/src/kernel/srs_kernel_mp4.cpp::SrsMp4Encoder::flush() 函数
8.2 stts(time to sample box)
存储了该 track 每个 sample 到 dts 的时间映射关系:
字段 | 类型 | 描述 |
---|---|---|
version | 8 bit | 取 0 或 1,一般取 0 |
flags | 24 bit | |
entry_count | 32 bit | 条目个数 |
开始循环 | ||
sample_count | 32 bit | 播放时长为 sample_delta 的连续 sample 个数 |
sample_delta | 32 bit | 单个 sample 的播放时长,单位为 timescale |
结束循环 |
这里为了节约条目的个数,采用了压缩存储的方式,即 sample_count 个连续的 sample 如果 sample_delta 时长一样,那么用一个条目就能表示了。 一个音频 track 的示例如下:
编辑
添加图片注释,不超过 140 字(可选)
一个视频 track 的示例如下:
编辑
添加图片注释,不超过 140 字(可选)
8.3 ctts(composition time to sample box)
存储了该 track 中,每个 sample 的 pts 与 dts 时间差(cts = pts - dts):
字段 | 类型 | 描述 |
---|---|---|
version | 8 bit | 取 0 或 1,一般取 0 |
flags | 24 bit | |
entry_count | 32 bit | 条目个数 |
开始循环 | ||
sample_count | 32 bit | cts 为 sample_offset 的连续 sample 个数 |
sample_offset | signed/unsigned 32 bit | 单个 sample 的 cts,单位为 timescale。支持负数,当 version=1 时为 signed |
结束循环 |
注意:
- 此 box 在 dts 和 pts 不一样的情况下必须存在,如果一样,不用包含此 box
- 如果 box 的 version=0,意味着所有 sample 都满足 pts >= dts,因而差值用一个无符号的数字表示。只要存在一个 pts < dts,那么必须使用 version=1、有符号差值来表示
- 关于 ctts 的生成,可以参看 srs/trunk/src/kernel/srs_kernel_mp4.cpp::SrsMp4SampleManager::write_track() 函数 pts、dts、cts 满足公式:pts - dts = cts
8.4 stss(sync sample box)
针对视频 track,关键帧所属sample 的序号。
字段 | 类型 | 描述 |
---|---|---|
version | 8 bit | 取 0 或 1,一般取 0 |
flags | 24 bit | |
entry_count | 32 bit | 条目个数 |
开始循环 | ||
sample_number | 32 bit | sample 计数,从 1 开始 |
结束循环 |
一个视频示例如下:
编辑切换为居中
添加图片注释,不超过 140 字(可选)
8.5 stsc/stz2(sample to chunk box)
存储了该 track 中每个 sample 与 chunk 的映射关系。
字段 | 类型 | 描述 |
---|---|---|
version | 8 bit | 取 0 或 1,一般取 0 |
flags | 24 bit | |
entry_count | 32 bit | 条目个数 |
开始循环 | ||
first_chunk | 32 bit | 一组 chunk 的第一个 chunk 的序号,chunk 的编号从 1 开始 |
samples_per_chunk | 32 bit | 每个 chunk 有多少个 sample |
sample_description_index | 32 bit | stsd box 中 sample desc 信息的索引 |
结束循环 |
一个音频示例如下:
编辑切换为居中
添加图片注释,不超过 140 字(可选)
- 第一组 chunk 的 first_chunk 序号为 1,每个 chunk 的 sample 个数为 1,因为第二组 chunk 的 first_chunk 序号为 2,可知第一组 chunk 中只有一个 chunk
- 第二组 chunk 的 first_chunk 序号为 2,每个 chunk 的 sample 个数为 2,因为第三组 chunk 的 first_chunk 序号为 24,可知第二组 chunk 中有 22 个 chunk,有 44 个 sample
8.6 stsz(sample size box)
存储了该 track 中每个 sample 的字节大小。
字段 | 类型 | 描述 |
---|---|---|
version | 8 bit | 取 0 或 1,一般取 0 |
flags | 24 bit | |
sample_size | 32 bit | 指定默认的 sample 字节大小,如果所有 sample 的大小不一样,这个字段为 0 |
sample_count | 32 bit | track 中 sample 的数量 |
开始循环 | ||
entry_size | 32 bit | 每个 sample 的字节大小 |
结束循环 |
8.7 stco/co64(chunk offset box)
存储了该 track 中每个 chunk 在文件中的偏移。
字段 | 类型 | 描述 |
---|---|---|
version | 8 bit | 取 0 或 1,一般取 0 |
flags | 24 bit | |
entry_count | 32 bit | |
开始循环 | ||
chunk_offset | 32 bit | chunk 在文件中的位置 |
结束循环 |
9. mdat
mdat 也是一个 box,拥有 box header 和 box body。 mdat 可以引用外部的数据,参见 moov --> udta --> meta,这里不讨论,只讨论数据存储在本文件中的形式。 对于 box body 部分,采用一个一个 samples 的形式进行存储,即一个一个音频帧或视频帧的形式进行存储。 码流组织方式采用 avcc 格式,即 AUD + slice size + slice 的形式。
9.1 通过时间进行定位寻址/随机访问
参考 https://www.jianshu.com/p/529c3729f357 6.2 节
>>> 音视频开发 视频教程: https://ke.qq.com/course/3202131?flowToken=1031864
>>> 音视频开发学习资料、教学视频,免费分享有需要的可以自行添加学习交流群: 739729163 领取