前言
❝ 现在的MP4是在互联网中最常见的格式了,MP4的好处例如跨平台,不仅可以在PC播放还可以在Android,IOS等平台中播放,基本上系统自带的播放器都支持该视频格式的视频文件播放。 ❞
❝ 因为工作中需要在WEB端支持实时流的播放,提到WEB端现在比 较火的就是VUE了,既然使用VUE了,必然少不了HTML5,那就可以使用HTML5的video标签来做视频的播放,如何渲染就完全交给浏览器了。❞
❝ 想使用HTML5的video标签来播放视频,实时的数据需要是FMP4(Fragment MP4),想整明白FMP4的封装流程就需要知道MP4的封装格式和流程。所以本文的主要目的就是了解MP4的组成,以及MP4格式的分析手段。
粉丝福利,博主耗时2个月整理了一份详细的音视频开发学习教程,涵盖了音视频开发FFmmpeg、流媒体客户端、流媒体服务器、WebRTC、Android NDK开发、IOS音视频开发等等全栈技术栈,并提供了配套的免费领取C++音视频学习资料包、技术视频/代码,内容包括(FFmpeg ,WebRTC ,rtmp ,hls ,rtsp ,ffplay ,编解码,推拉流,srs流媒体服务器,音视频通话等等)↓↓↓↓↓↓见下面↓↓文章底部点击免费领取↓↓
MP4分析工具&资料
可视化媒体文件分析工具:
mediainfo:https://mediaarea.net/zh-CN/MediaInfo/Download
mp4box:https://gpac.github.io/mp4box.js/test/filereader.html
Bento4: https://www.bento4.com/downloads/
16进制编辑工具:
windows/linux/:macOS通用工具:Hexinator https://www.hexinator.com/
H264白皮书;
MP4标准;
MP4的组成
1、MP4的结构
mp4由N个box组成,每个Box都长的一样,都由size,type,data三部分组成,如下图 所示:
- 其中size和type是固定的,各占用4字节。
- size是整个box的大小,表示从存放size的第一个字节开始到data的最后一个字节的大小。
- type是用来描述该box的类型;
- data就是整个box的数据了;
更多情况下box和box是嵌套的,嵌套的box结构如下图所示:
2、MP4的box类型列表
❝
注:参考了众多翻译版的box列表,中和了一下做个记录对国人友好一些。
❞
在互联网的视频点播中,如果希望MP4文件被快速打开,则需要把moov放在mdat的前面,如果放在后面,需要将MP4文件下载完成后才可以播放。
在实际生成MP4文件时,通常无法预先得知moov中存放的一些关键信息,此时如果想把moov放到mdat前面。通常的做法是:在保存MP4结束时把moov移动到mdat前面。
「一级」 |
「二级」 |
「三级」 |
「四级」 |
「五级」 |
「六级」 |
「必选」 |
「描述」 |
type |
是 |
文件类型 |
|||||
pdin |
下载进度信息 |
||||||
moov |
是 |
音视频数据的元数据信息 |
|||||
mvhd |
是 |
文件头 |
|||||
trak |
是 |
流的track(轨道) |
|||||
tkhd |
是 |
流信息的track头 |
|||||
tref |
track参考容器 |
||||||
elst |
edit list 容器 |
||||||
mdia |
是 |
track里的media信息 |
|||||
mdhd |
是 |
media信息的头 |
|||||
hdlr |
是 |
media信息的句柄 |
|||||
minf |
是 |
media信息容器 |
|||||
vmhd |
视频media头(只存在于视频的track) |
||||||
smhd |
音频media头(只存在于音频的track) |
||||||
hmhd |
提示media头(只存在于提示的track) |
||||||
dinf |
是 |
数据信息容器 |
|||||
dref |
是 |
数据参考容器,track中media的参考信息 |
|||||
stbl |
是 |
采样表容器,track中media的参考信息 |
|||||
stsd |
是 |
采样表叔(codec类型与初始化信息) |
|||||
stts |
是 |
采样时间(decoding) |
|||||
ctts |
采样时间(composition) |
||||||
stsc |
是 |
chunk采样,数据片段信息 |
|||||
stsz |
采样大小 |
||||||
stz2 |
采样大小详细描述 |
||||||
stco |
是 |
chunk偏移信息,数据偏移信息 |
|||||
co64 |
64位chunk偏移信息 |
||||||
stts |
同步采样表 |
||||||
stsh |
采样同步表 |
||||||
padb |
采样padding |
||||||
stdp |
采样退化优先描述 |
||||||
sdtp |
独立于可支配采样描述 |
||||||
sdgp |
采样组 |
||||||
sgpd |
采样组描述 |
||||||
subs |
子采样信息 |
||||||
mvex |
视频扩展容器 |
||||||
mehd |
视频扩展容器头 |
||||||
trex |
是 |
track扩展信息 |
|||||
ipmc |
IPMP控制容器 |
||||||
moof |
视频分片 |
||||||
mfhd |
是 |
视频分片头 |
|||||
traf |
track分片 |
||||||
tfhd |
是 |
track分片头 |
|||||
trun |
track分片run信息 |
||||||
sdtp |
独立和可支配的采样 |
||||||
sbgp |
采样组 |
||||||
subs |
子采样信息 |
||||||
mfra |
视频分片访问控制信息 |
||||||
tfra |
track分片访问控制信息 |
||||||
mfro |
是 |
拼分片访问控制偏移量 |
|||||
mdat |
media数据容器 |
||||||
free |
空闲区域 |
||||||
skip |
空闲区域 |
||||||
udata |
用户数据 |
||||||
cprt |
copyright信息 |
||||||
meta |
元数据 |
||||||
hdlr |
是 |
定义元数据句柄 |
|||||
dinf |
数据信息容器 |
||||||
dref |
元数据的源参考信息 |
||||||
ipmc |
IPMP控制容器 |
||||||
iloc |
所在位置信息容器 |
||||||
ipro |
所在位置信息容器 |
||||||
sinf |
计划信息保护容器 |
||||||
frma |
原格式容器 |
||||||
imif |
IPMP信息容器 |
||||||
schm |
计划类型容器 |
||||||
schi |
计划信息容器 |
||||||
iinf |
容器所在项目信息 |
||||||
xml |
XML容器 |
||||||
bxml |
binary XML容器 |
||||||
pitm |
主要参考容器 |
||||||
fiin |
文件发送信息 |
||||||
paen |
partition入口 |
||||||
fpar |
文件片段容器 |
||||||
fecr |
FEC Reservoir |
||||||
segr |
文件发送session组信息 |
||||||
gitn |
组ID转名称信息 |
||||||
tsel |
track选择信息 |
||||||
meco |
追加的metadata信息 |
||||||
mere |
metabox关系 |
||||||
meta |
metadata(元数据) |
||||||
styp |
分段类型 |
||||||
sidx |
分段索引 |
||||||
ssix |
子分段索引 |
||||||
prft |
生产者参考时间 |
MP4重要组成格式解析
本次使用的一个MP4文件信息如下:
(base) zhenghui@zh-pc:~/视频$ ffprobe 1.mp4
Input #0, mov,mp4,m4a,3gp,3g2,mj2, from '1.mp4':
Metadata:
major_brand : mp42
minor_version : 0
compatible_brands: mp42mp41isomiso2
creation_time : 2023-03-26T04:28:11.000000Z
encoder : x264
Duration: 00:00:07.71, start: 0.000000, bitrate: 839 kb/s
Stream #0:0(und): Video: h264 (High 4:4:4 Predictive) (avc1 / 0x31637661), yuv444p(tv, bt709), 2560x1440 [SAR 1:1 DAR 16:9], 705 kb/s, 60 fps, 60 tbr, 6k tbn, 120 tbc (default)
Metadata:
creation_time : 2023-03-26T04:28:11.000000Z
handler_name : VideoHandler
vendor_id : [0][0][0][0]
Stream #0:1(und): Audio: mp3 (mp4a / 0x6134706D), 44100 Hz, mono, fltp, 129 kb/s (default)
Metadata:
creation_time : 2023-03-26T04:28:11.000000Z
handler_name : SoundHandler
vendor_id : [0][0][0][0]
(base) zhenghui@zh-pc:~/视频$
使用的二进制工具:
使用Hexinator时,建议拖动窗口,缩小或者放大到每行16个字节的大小,这样看起来比较好看,分析起来也比较好分析
moov解析
moov定义了MP4文件的音视频数据的元数据信息,从上表中可以看到,在整个MP4文件中,仅存在一个moov,而moov中又存在众多嵌套的子box.
例如下图用MP4Box.js的一个工具打开的mp4,呈现
下图是使用MP4 Reader软件打开的
可见,mp4文件的moov中包含mvhd、trak、udata、而trak中又包含tkhd、edts、mdia、udata。是一层套一层的包含着众多box,最终组成一个大的moov box。
使用MP4Box.js分析该Mp4后,点击moov时,可以看到结果如下:
- type: moov
- size:4690
- start:32
如果想弄懂怎么解析出来的,我们需要使用二进制工具打开:
如上图所示,我们再结合下图的结构图。
可知前4个字节是表示的整个box的size,第5-8个字节是type,然后后面就是data了。
看前8个字节:
00 00 12 52 6D 6F 6F 76
0x00001252(hex) = 4690(dec) 0x6D(hex) = 109(dec) = m(根据ascii码表可得知) 0x6F(hex) = 111(dec) = o(根据ascii码表可得知) 0x6F(hex) = 118(dec) = v(根据ascii码表可得知)
所以可分析出来这个box的一些信息:
- 大小有:4690个字节
- 类型为:moov
至于为什么start是32,因为这个box是从第32个字节开始的。
看下图更为清晰:
使用MP4 Reader可以显示的更友好:
根据解析的结果可以看到,该box的类型为moov,大小为4690字节。
都说了moov中嵌套了众多box,那么继续往下看:
紧接着的16进制数据是:
0000006C 6D766864
长度:0x0000006C(hex)=108(dec) 类型:0x6D766864(hex)=mvhd(ascii码)
「为什么是嵌套的,而不是并列的:」
❝
因为mvhd还在moov的4690字节范围内,所以是嵌套在moov内的一个box.
❞
所以moov中第一个box是mvhd
使用Hexinator可以用鼠标选中108个字节:
想都不用想108个字节后,肯定又是一个box;
可见108个字节过后的8个16进制数据为:
00 00 0A 74 74 72 61 6B
长度:0x00000A74(hex) = 2676(dec)字节 类型:0x7472616B(hex) = trak
「trck的类型可能为音频或者视频的trck,这里暂不分析,过后会仔细分析。」
可以从00 00 0A 74的第一个字节开始选中2676个字节:
可以看到2676字节过后的8个字节:
00 00 07 2D 74 72 61 6B
box大小:0x00 00 07 2D(1837)字节 box类型:0x74 72 61 6B(trck)
继续选中1837字节:
接下来的8字节为:
00 00 00 3D 75 64 74 61
box大小:0x00 00 00 3D(61) box类型:0x75 64 74 61(udta)
到此,我们已经分析得到了如下结果:moov中嵌套了一个mvhd box,两个trck box(可能是视频也可能是音频,过后再分析,一个udta box
8字节(moov的type和size)+108字节(mvhd)+2676字节(trck)+1837字节(trck)+61字节(udta)
刚好为前面得出的moov的总大小4690字节
mvhd解析
mvhd box是MPEG-4(MP4)容器格式中的一个存放音视频文件头(Movie Header)的部分,mvhd中包含了整个媒体文件的信息,包括媒体文件的持续时间、时间刻度、音频和视频轨道数量,播放速度等信息。
前面我们分析过,mvhd的大小为0x0000006C(108)字节,解析方式如下表所示:
mvhd参数表:
字段 |
长度(字节) |
描述 |
box大小 |
4 |
Movie Header box的字节数 |
box类型 |
4 |
mvhd |
版本 |
1 |
Movie Header box的版本,取值为0或1;取值决定了生成时间、修订时间、duration按照什么方式进行解析。为0时:按照32位/4字节的方式;为1时:按照64位/8字节的方式; |
标志 |
3 |
扩展的Movie Header box标致 |
生成时间 |
4/8 |
Movie box的起始时间。基准时间是:1904-1-1 0:00 AM |
修订时间 |
4/8 |
Movie box的修订时间。基准时间是:1904-1-1 0:00 AM |
timescale |
4 |
时间计算单位,这是一个全局的timescale。mp4也支持每个track单独的timescale。换算方式:duration/timescale=xx秒 |
duration |
4/8 |
记录整个文件的播放时长;在fMP4的时候,该值需为0; |
播放速度 |
4 |
1.0为正常播放速度(16.16的浮点表示) |
播放音量 |
2 |
1.0为最大音量(8.8的浮点表示) |
保留 |
10 |
|
矩阵结构 |
36 |
矩阵定义了该Movie中两个坐标空间的映射关系 |
预定义 |
24 |
|
下一个Track ID |
4 |
下一个待添加track的ID值。如果为0就不是一个有效的值。 |
按照mvhd参数表来填充解析:
「字段(所占字节数)」 |
「十六进制数据」 |
「转换后结果」 |
box大小(4) |
0x0000006C |
108 |
box类型(4) |
0x6D766864 |
mvhd |
版本(1) |
0x00 |
0 |
标志(3) |
0x000000 |
0 |
生成时间(4/8) |
0xE0457A5B |
3762649691 |
修订时间(4/8) |
0xE0457A65 |
3762649701 |
timescale(4) |
0x00001770 |
6000 |
duration(4/8) |
0x0000B49D |
46237 |
播放速度(4) |
0x00010000 |
1.0 |
播放音量(2) |
0x0100 |
1.0 |
保留(10) |
0x00000000000000000000 |
|
矩阵结构(36) |
0x000100000000000000000000000000000001000000000000000000000000000040000000 |
|
预定义(24) |
0x000000000000000000000000000000000000000000000000 |
|
下一个Track ID(4) |
0x00000003 |
3 |
说明:例如播放速度为1.0,那么怎么得到的呢,计算方式如下:播放速度是以16.16浮点来表示的,播放速度占4个字节, 0x00010000 转成 二进制为:0000 0000 0000 0001 0000 0000 0000 0000 再转成16.16的定点小数:0000 0000 0000 0001 . 0000 0000 0000 0000 再转成10进制小数:1.0
mvhd box的下一个box是trak box,下面开始解析trak box的解析。
trak解析
每个媒体文件中可包含多个track,每个track都是独立的,各自保存各自的媒体信息,例如音频track,视频track。
从mvhd box后面的8个字节,我们可以分析出来下一个box的大小和类型
00 00 0A 74 74 72 61 6B
长度:0x00000A74(hex) = 2676(dec)字节 类型:0x7472616B(hex) = trak
可知从00 00 0A 74开始的第2676字节全是该track box的数据
如下图所示,所标出的2676个字节就是第一个track的所有数据
继续往下读8字节:
00 00 00 5C 74 6B 68 64
前4个字节:0x0000005C(92) 后4个字节:0x746B6864(tkhd)
说明这又是一个box,类型为tkhd,该box的长度为92字节
下图所标的92字节就是整个tkhd box的数据:
跳过92字节后的8字节为:
00 00 00 24 65 64 74 73
前4个字节:0x00000024(36) 后4个字节:0x65647473(edts)
说明这又是一个box,长度为36,类型为edts的box。如下图,所标的36字节就是edts的全部数据:
继续跳过36字节,读取8字节:
00 00 09 93 6D 64 69 61
前4个字节:0x00000993(2451) 后4个字节:0x6D646961(mdia)
这是一个长度为2451,类型为mdia的box;标出2451后:
跳过2451字节后,我们再读取8字节数据:
00 00 00 59 75 64 74 61
前4个字节:0x00000059(89) 后4个字节:0x75647461(udta)
可见这是一个长度为89的udta box。
跳过89字节后,再读取8字节:
00 00 07 2D 74 72 61 6B
前4个字节:0x0000072D(1837) 后4个字节:0x7472616B(trak)
可知这是遇到的第2个trak box了,所以这里先忽略。所以到udta box就结束了第一个trak box中嵌套的所有三级box。
「总结一下:」
- 第1个trak box一共2676字节;
- tkhd占用92字节
- edts占用36字节
- mdia占用2451字节
- udta占用89字节
所以8(trak的size和type)+92(tkhd)+36(edts)+2451(mdia)+89(udta)=2676字节;
接下来我们来分析track中的第一个box :tkhd box
tkhd解析
tkhd是“Track Header”的缩写。
❝
每个track中必须包含tkhd,切只能而且必须包含1个tkhd box;tkhd用来表述这个轨道的一些基本信息参数,例如:
如果是视频的话就会包含宽和高,如果是音频的话就会包含采样率等信息。
❞
「tkhd参数表:」
字段 |
长度(字节) |
描述 |
box大小 |
4 |
该box的大小(字节) |
box类型 |
4 |
该box的类型 |
版本 |
1 |
该box的版本 |
标志 |
3 |
有效的标志如下: |
生成时间 |
4/8 |
Movie box的起始时间。基准时间是:1904-1-1 0:00 AM |
修订时间 |
4/8 |
Movie box的修订时间。基准时间是:1904-1-1 0:00 AM |
trackID |
4 |
该track的ID,唯一的 |
保留 |
4 |
|
duration |
4/8 |
trak的duration,在媒体文件的时间戳中,与trak的edts list的时间戳建立关联,然后进行时间戳计算,得到对应track的播放时间坐标。但是在fMP4时,该值需要设置为0 |
保留 |
8 |
|
layer |
2 |
视频曾,默认为0,值小的在上层 |
alternate group |
2 |
track分组信息,默认为0,表示该track未与其他track有群组关系 |
音量 |
2 |
播放此track的音量。1.0时为正常音量 |
保留 |
2 |
|
矩阵结构 |
36 |
该矩阵定义了该track中两个坐标空间的映射关系 |
宽度 |
4 |
如果该track是video track,该值表示视频图像的宽度(16.16浮点表示) |
高度 |
4 |
如果该track是video track,该值表示视频图像的高度(16.16浮点表示) |
视频trak->tkhd
前面我们分析过tkhd的长度为92字节
同样为了方便查看,继续标出来92字节:
然后按照字节数填表:
「视频tkhd解析表:」
「字段(所占字节数)」 |
「十六进制数据」 |
「转换后结果」 |
box大小(4) |
0x0000005C |
92 |
box类型(4) |
0x746B6864 |
tkhd |
版本(1) |
0x00 |
0 |
标志(3) |
0x000007 |
7 |
生成时间(4/8) |
0xE0457A5B |
3762649691 |
修订时间(4/8) |
0xE0457A65 |
3762649701 |
trackID(4) |
0x00000001 |
1 |
保留(4) |
0x00000000 |
|
duration(4/8) |
0x0000B478 |
46200 |
保留(8) |
0x0000000000000000 |
|
layer(2) |
0x0000 |
0 |
alternate group(2) |
0x0000 |
0 |
音量(2) |
0x0000 |
0 |
保留(2) |
0x0000 |
|
矩阵结构(36) |
0x000100000000000000000000000000000001000000000000000000000000000040000000 |
|
宽度(4) |
0x0A000000 |
2560.0 |
高度(4) |
0x05A00000 |
1440.0 |
重点说明:
「1、为什么标志是7(0x000007),代表是什么意思:」
在回顾下上面表格中介绍的:有效的标志如下:
- 0x0001: track生效,为0时该track不启用;
- 0x0002: track被用在Movie中;(播放时会用到)
- 0x0004: track被用在Movie预览中;(预览模式会用到)
- 0x0008: 表示宽度和高度字段不以像素单位表示
可以计算一下:0x00 00 07 = 0x0001 | 0x0002 | 0x0004;所以这三种是有效的。
「2、高度和宽度如何计算的:」
高度和宽度是16.16浮点运算的。拿宽度0x0A000000举例:
0x0A000000 (十六进制)= 转成二进制并用点分开16.16 --> 0x0000 1010 0000 0000 . 0000 0000 0000 0000 二进制转10进制:2560.0
❝
宽度和高度都是有效的,可见这是一个视频track。还记得我们这个分析中有2个trck吗?我们猜另一个应该就是音频trck了,再看下音频trck.
❞
音频trak->tkhd
本文所分析的mp4文件的第二个trak位于2676后的box就是第二个trak box
按照解析视频的方法,来解析音频:「音频tkhd解析表:」
「字段(所占字节数)」 | 「十六进制数据」 | 「转换后结果」 |
---|---|---|
box大小(4) | 0x0000005C | 92 |
box类型(4) | 0x746B6864 | tkhd |
版本(1) | 0x00 | 0 |
标志(3) | 0x000007 | 7 |
生成时间(4/8) | 0xE0457A5B | 3762649691 |
修订时间(4/8) | 0xE0457A65 | 3762649701 |
trackID(4) | 0x00000002 | 2 |
保留(4) | 0x00000000 | |
duration(4/8) | 0x0000B49D | 46237 |
保留(8) | 0x0000000000000000 | |
layer(2) | 0x0000 | 0 |
alternate group(2) | 0x0000 | 0 |
音量(2) | 0x0100 | 1.0 |
保留(2) | 0x0000 | |
矩阵结构(36) | 0x000100000000000000000000000000000001000000000000000000000000000040000000 | |
宽度(4) | 0x00000000 | 0.0 |
高度(4) | 0x00000000 | 0.0 |
可见,与视频tkhd不同的地方有:trackID、duration、音量、矩阵结构、宽度、高度
宽度和高度对于音频来说是无意义的,所以音频中为0.0
mdia解析
mdia box位于trak box内,按照前面所分析的结果来说,mdia在edts之后,所以我们跳过edts box直接到mdia box;
老样子,读取8字节数据:
00 00 09 93 6D 64 69 61
前四个字节:0x00000993(2451) 后四个字节:0x6D646961(mdia)
可见这是一个长度为2451字节的mdia box;
为什么这个box这么大,因为mdia box中包含的box特别多,例如下面这些是必须要包含的:
- mdhd:media信息头
- hdlr:media信息的句柄
- minf:media信息容器
mdia box解析:
如下图所示就是mdia box的开始位置:
取前8字节数据:
00 00 09 93 6D 64 69 61
前四位:0x00000993(2451) 后四位:0x6D646961(mdia)
说明我们找的没错,这个box就是长度为2451字节的mdia box;
接下来可以根据前面的经验继续往下找了:
mdhd:
00 00 00 20 6D 64 68 64
前四位:0x00000020(32) 后四位:0x6D646864(mdhd)
跳过32字节继续:
00 00 00 2D 68 64 6C 72
前四位:0x0000002D(45) 后四位:0x68646C72(hdlr)
跳过45字节继续:
00 00 09 3E 6D 69 6E 66
前四位:0x0000093E(2366) 后四位:0x6D696E66(minf)
跳过2366字节继续就到了udta,所以就上面这些就是mdia box的所有内容了。
8(mdia)+32(mdhd)+45(hdlr)+2366(minf)= 2451字节,计算结果和前面分析的是吻合的,所以是正确的。
mdhd解析
mdhd box是用来描述Media的Header,每个track都有一个mdhd。
「mdhd容器参数表:」
字段 |
长度(字节) |
描述 |
box大小 |
4 |
该box的大小(字节) |
box类型 |
4 |
该box的类型 |
版本 |
1 |
该box的版本 |
标志 |
3 |
该box的标志 |
生成时间 |
4 |
Movie box的起始时间。基准时间是:1904-1-1 0:00 AM |
修订时间 |
4 |
Movie box的修订时间。基准时间是:1904-1-1 0:00 AM |
timescale |
4 |
时间计算单位,这个地方的timescale是每个track单独的timescale |
duration |
4 |
该track的duration |
语言 |
2 |
媒体的语言码 |
质量 |
2 |
媒体的回放质量 |
上面已经得到了mdhd的长度为32。
所以就这一点:
按照前面的经验,继续填表法:
「字段(所占字节数)」 |
「十六进制数据」 |
「转换后结果」 |
box大小(4) |
0x00000020 |
32 |
box类型(4) |
0x6D646864 |
mdhd |
版本(1) |
0x00 |
0 |
标志(3) |
0x000000 |
0 |
生成时间(4/8) |
0xE0457A5B |
|
修订时间(4/8) |
0xE0457A65 |
|
timescale(4) |
0x00001770 |
6000 |
duration(4) |
0x0000B478 |
46200 |
语言(2) |
0x55C4 |
21956 |
质量(2) |
0x0000 |
0 |
根据这个表,我们可以计算出来整个视频track的时长,duration/timescale就是时长,单位为秒:46200/6000=7.7秒
hdlr解析
hdlr box中存放着该track的媒体类型,如果多个track的话,例如就可以区分出来哪个是音频track,哪个是视频track等。「hdlr容器参数表:」
字段 |
长度(字节) |
描述 |
box大小 |
4 |
该box的大小(字节) |
box类型 |
4 |
该box的类型 |
版本 |
1 |
该box的版本 |
标志 |
3 |
该box的标志 |
handle的预定义 |
4 |
handler的预定义 |
handle的子类型 |
4 |
用来描述media handler或data handler的类型。 |
如果component type是mhlr,这个字段定义了数据的类型,例如:video是vide类型的track,soun是sound的track类型。如果component type是dhlr,这个字段定义了数据引用的类型,例如:alis是文件的别名。 |
||
保留 |
12 |
保留字段,默认为0 |
component name |
可变 |
这个component的名字,就是生成此media的media handler。该字段的长度可以为0。 |
前面我们分析过,hdlr占用45字节的长度:
填表:
「字段(所占字节数)」 |
「十六进制数据」 |
「转换后结果」 |
box大小(4) |
0x0000002D |
45 |
box类型(4) |
0x68646C72 |
hdlr |
版本(1) |
0x00 |
0 |
标志(3) |
0x000000 |
0 |
handle的预定义(4) |
0x00000000 |
0 |
handle的子类型(4) |
0x76696465 |
vide |
保留(12) |
0x000000000000000000000000 |
0 |
component name(可变) |
0x566964656F48616E646C657200 |
VideoHandler'\0' |
可以看到这是一个video的track。对应的组件名称为:VideoHandler,并且以0x00结尾。
minf解析
前面我们分析过minf的长度有2366字节。在minf box中包含着很多与音视频采样等信息相关的重要容器。
例如重要的容器:
- vmhd::视频meida头(Video Media Information Header)
- smhd:音频media头(Sound Media Information Header)
- dinf:数据信息参考容器(Data Information)
- stbl:采样表容器(Sample Table)
下面逐步来解析完这些重要的容器。
vmhd解析
vmhd box中描述了一些与视频track编码无关的通用信息。
「vmhd参数表:」
字段 |
长度(字节) |
描述 |
box大小 |
4 |
该box的大小(字节) |
box类型 |
4 |
该box的类型 |
版本 |
1 |
该box的版本 |
标志 |
3 |
该box的标志 |
图形模式 |
2 |
传输模式,传输模式指定的布尔值 |
Opcolor |
6 |
颜色值,RGB颜色值 |
读取8字节:
00 00 00 14 76 6D 68 64
前四个字节:0x00000014(20) 后四个字节:0x766D6864(vmhd)
下图所标就是vmhd整个box的数据了:
填表解析:
「字段(所占字节数)」 |
「十六进制数据」 |
「转换后结果」 |
box大小(4) |
0x00000014 |
20 |
box类型(4) |
0x766D6864 |
vmhd |
版本(1) |
0x00 |
0 |
标志(3) |
0x000001 |
1 |
图形模式(2) |
0x0000 |
0 |
Opcolor(6) |
0x000000000000 |
0 |
smhd解析
smhd中描述了音频的Header的相关内容。
「smhd参数表:」
字段 |
长度(字节) |
描述 |
box大小 |
4 |
该box的大小(字节) |
box类型 |
4 |
该box的类型 |
版本 |
1 |
该box的版本 |
标志 |
3 |
该box的标志 |
均衡 |
2 |
音频的均衡用来控制计算的两个扬声器混合效果,一般是0 |
保留 |
2 |
保留字段,默认为0 |
smhd在第二个track中,所以需要你去另外的track中去找。
例如我的:
取出前8个字节分析:
00 00 00 10 73 6D 68 64
前4个字节:0x00000010(16) 后4个字节:0x736D6864(smhd)
所标处的16个字节就是smhd的数据
继续填表:
「字段(所占字节数)」 |
「十六进制数据」 |
「转换后结果」 |
box大小(4) |
0x00000010 |
16 |
box类型(4) |
0x736D6864 |
smhd |
版本(1) |
0x00 |
0 |
标志(3) |
0x000000 |
0 |
均衡(2) |
0x0000 |
0 |
保留(2) |
0x0000 |
0 |
好像vmhd和smhd中的内容也没有太多值得参考的价值。
dinf解析
dinf也是必须要包含的一个box,dinf中包含着一个dref box。dinf存放着音视频media的参考信息。
00 00 00 24 64 69 6E
前4个字节:0x00000024(36) 后4个字节:0x64696E66(dinf)
可知dinf box一共占了36个字节。
继续读取8字节:
00 00 00 1C 64 72 65 66
前4个字节:0x0000001C(28) 后4个字节:0x64726566(dref)
dref占用了28字节。
dref解析
「dref参考表:」
字段 |
长度(字节) |
描述 |
box大小 |
4 |
该box的大小(字节) |
box类型 |
4 |
该box的类型 |
版本 |
1 |
该box的版本 |
标志 |
3 |
该box的标志 |
条目数目 |
4 |
data reference(数据参考)的数目 |
数据参考 |
--- |
每个data reference就像容器的格式一样,包含下面的数据成员 |
尺寸 |
4 |
这个box字节数 |
类型 |
4 |
如url/urn等类型 |
版本 |
1 |
这个data reference的版本 |
数据 |
可变 |
data reference信息 |
dref的内容如下:
继续填表解析:
「字段(所占字节数)」 |
「十六进制数据」 |
「转换后结果」 |
box大小(4) |
0x0000001C |
28 |
box类型(4) |
0x64726566 |
dref |
版本(1) |
0x00 |
0 |
标志(3) |
0x000000 |
0 |
条目数目(4) |
0x00000001 |
1 |
数据参考(---) |
||
尺寸(4) |
0x0000000C |
12 |
类型(4) |
0x75726C20 |
url |
版本(1) |
0x00 |
0 |
数据(可变) |
0x000001 |
1 |
stbl解析
stbl称为采样列表Box(Sample Table Box)。
在这里需要首先了解下标准中的一些定义:
- track(轨道):表示一些sample的集合,对于媒体文件数据来说,用来表示一个视频或者音频数据序列。
- chunk(块):一个track中几个连续的sample组成的集合成为chunk(块);同一个chunk内的sample是连续的;在fMP4格式中,则使用run来表达类似的意思。
- sample(采样):与一个时间戳相关的所有数据。对于video的话,一般对应视频中的一个帧;对于audio的话,一般对应音频中的一段压缩的音频数据。
一般同一个track中不可能有多个sample具有相同的时间戳。
简洁一些:
- sample是一个媒体流的基本单元,例如视频流的一个sample代表实际的NAL数据。
- chunk是数据存储的基本单位,chunk是一系列sample数据的集合,一个chunk中包含多个sample。
先看看下都包含了哪些内容:
读取8字节:
00 00 08 FE 73 74 62 6C
前4个字节:0x000008FE(2302) 后4个字节:0x7374626C(stbl) 说明我们读取到了stbl box,大小为2302个字节。
显然整个stbl box有2302个字节,因为现在分析的这些都在stbl box内,所以继续读取8个字节:
00 00 00 D2 73 74 73 64
前4个字节:0x000000D2(210) 后4个字节:0x73747364(stsd) 说明我们读取到了stsd box,大小为210个字节。
跳过210字节继续读取8字节:
00 00 00 18 73 74 74 73
前4个字节:0x00000018(24) 后4个字节:0x73747473(stts) 说明我们读取到了stts box,大小为24个字节。
跳过24字节继续读取8字节:
00 00 00 14 73 74 73 73
前4个字节:0x00000014(20) 后4个字节:0x73747373(stss) 说明我们读取到了stss box,大小为20个字节。
跳过20字节继续读取8字节:
00 00 00 28 73 74 73 63
前4个字节:0x00000028(40) 后4个字节:0x773747363(stsc) 说明我们读取到了stsc box,大小为40个字节。
跳过40字节继续读取8字节:
00 00 07 4C 73 74 73 7A
前4个字节:0x0000074C(1868) 后4个字节:0x7374737A(stsz) 说明我们读取到了stsz box,大小为1868个字节。
跳过1868字节继续读取8字节:
00 00 00 84 73 74 63 6F
前4个字节:0x00000084(132) 后4个字节:0x7374636F(stco) 说明我们读取到了stco box,大小为132个字节。
跳过32字节继续读取8字节:
00 00 00 59 75 64 74 61
前4个字节:0x00000059(59) 后4个字节:0x75647461(udata)
发现我们去读的字节数已经超出了stbl的总大小,所以这个udata是外层的box,并不是stbl box内所包含的,所以忽略。
12.1. stsd解析
全称:Sample description
stbl box内,所以继续读取8个字节:
00 00 00 D2 73 74 73 64
前4个字节:0x000000D2(210) 后4个字节:0x73747364(stsd) 说明我们读取到了stsd box,大小为210个字节。
「stsd参考表:」
字段 |
长度(字节) |
描述 |
box大小 |
4 |
该box的大小(字节) |
box类型 |
4 |
stsd |
版本 |
1 |
该box的版本 |
标志 |
3 |
该box的标志 |
条目数目 |
4 |
data reference(数据参考)的数目 |
数据参考表 |
--- |
每个data reference就像容器的格式一样,包含下面的数据成员 |
数据参考表box大小 |
4 |
|
数据存储格式 |
4 |
例如我的就是avc1 |
保留 |
6 |
|
数据参考索引 |
2 |
|
版本 |
2 |
|
修订级别 |
2 |
|
厂商 |
4 |
用来描述生成压缩数据的压缩程序的开发人员 |
时间质量 |
4 |
描述时间压缩程度 |
空间质量 |
4 |
描述空间压缩程度 |
宽度 |
2 |
描述图像的宽度 |
高度 |
2 |
描述图像的高度 |
水平分辨率 |
4 |
描述图像的水平分辨率 |
垂直分辨率 |
4 |
描述体香的垂直分辨率 |
数据大小 |
4 |
|
Frame count |
2 |
描述每个样本中存储了多少帧压缩数据 |
压缩器名字大小 |
1 |
压缩器名字大小 |
填充 |
31 |
填充0x00 |
压缩器名字(Compressor name) |
4 |
描述图像的压缩格式的名字,例如"jpeg" |
深度 |
2 |
描述压缩图像的像素深度 |
颜色表ID |
2 |
描述使用的颜色表 |
我们读取到了stsd box,大小为210个字节。
读取到0x61766331时,发现又是一个box,类型为avc1.
继续填表解析:
「字段(所占字节数)」 |
「十六进制数据」 |
「转换后结果」 |
box大小(4) |
0x000000D2 |
210 |
box类型(4) |
0x73747364 |
stsd |
版本(1) |
0x00 |
0 |
标志(3) |
0x000000 |
0 |
条目数目(4) |
0x00000001 |
1 |
数据参考表(---) |
||
数据参考表大小(4) |
0x000000C2 |
194 |
数据存储格式 (4) |
0x61766331 |
avc1 |
保留(6) |
0x000000000000 |
0 |
数据参考索引(2) |
0x0001 |
1 |
版本(2) |
0x0000 |
|
修订级别(2) |
0x0000 |
|
厂商(4) |
0x00000000 |
|
时间质量(4) |
0x00000000 |
|
空间质量(4) |
0x00000000 |
|
宽度(2) |
0x0A00 |
2560 |
高度(2) |
0x05A0 |
1440 |
水平分辨率(4) |
0x00480000 |
|
垂直分辨率(4) |
0x00480000 |
|
数据大小(4) |
0x00000000 |
|
Frame count(2) |
0x0001 |
1 |
压缩器名字大小(1) |
0x00 |
0 |
填充(31) |
0x00000000000000000000000000000000000000000000000000000000000000 |
0 |
深度(2) |
0x0018 |
24 |
颜色表ID(2) |
0xFFFF |
65535 |
avcC:
「avcC参考表:」
字段 |
长度(字节) |
描述 |
box大小 |
4 |
该box的大小(字节) |
box类型 |
4 |
stsd |
版本 |
1 |
版本号 |
AVC解码器配置记录 |
---- |
配置AVC解码器的记录 |
avcC填表解析:继续填表解析:
「字段(所占字节数)」 |
「十六进制数据」 |
「转换后结果」 |
box大小(4) |
0x00000035 |
53 |
box类型(4) |
0x61766343 |
avcC |
版本(1) |
0x01 |
1 |
AVC解码器配置记录 |
0xF40033FFE1001 |
SPS,PPS |
AVC解码器参数解析:
1、由十六进制转成二进制:
1111010000000000001100111111111111100001000000000001110101100111111101000000000000110011100100011001011010000000001010000000000010110101101100000001011010100000001000000010000000101000000000000000000000000011000000000000100000000000000000000000001100000011110001000111100011000001100101010000000100000000000001010110100011001110000000101111000110010010
解析SPS和PPS需要运用H264的知识点啦:h264的sps,pps解析可以尝试去了解H.264白皮书《Rec. ITU-T H.264 (03/2010)》的44页这本标准
「sps数据的定义与解析:」
此处稍复杂,暂时放一下,之后单独来写。
12.2. stts解析
stts全程:Time-to-Sample
❝
存储sample与时间的映射表。
❞
跳过210字节继续读取8字节:
00 00 00 18 73 74 74 73
前4个字节:0x00000018(24) 后4个字节:0x73747473(stts) 说明我们读取到了stts box,大小为24个字节。
stts参数表:
字段 |
长度(字节) |
描述 |
box大小 |
4 |
该box的大小(字节) |
box类型 |
4 |
该box的类型 |
版本 |
1 |
该box的版本 |
标志 |
3 |
该box的标志 |
映射表数量 |
4 |
|
映射表 |
--- |
映射表的格式:
Field |
Bytes |
Sample count |
4 |
Sample Duration |
4 |
stts解析:
「字段(所占字节数)」 |
「十六进制数据」 |
「转换后结果」 |
box大小(4) |
0x00000018 |
24 |
box类型(4) |
0x73747473 |
stts |
版本(1) |
0x00 |
0 |
标志(3) |
0x000000 |
0 |
映射表数量(4) |
0x00000001 |
1个映射表 |
映射表(---) |
0x000001CE00000064 |
映射表的格式:
Sample count(4 bytes) |
Sample Duration(4 bytes) |
0x000001CE(462) |
0x00000064(100) |
12.3. stss解析
stss全称:Sync sample
❝
同步sample表,存放关键帧列表
❞
跳过24字节继续读取8字节:
00 00 00 14 73 74 73 73
前4个字节:0x00000014(20) 后4个字节:0x73747373(stss) 说明我们读取到了stss box,大小为20个字节。
stss参数表:
「字段」 |
「长度(字节)」 |
「描述」 |
box大小 |
4 |
该box的大小(字节) |
box类型 |
4 |
该box的类型 |
版本 |
1 |
该box的版本 |
标志 |
3 |
该box的标志 |
同步表数量 |
4 |
|
同步表 |
--- |
同步表结构:
「Entry Number」 |
Sample |
「Number」 |
Sample1 |
「Number」 |
Sample2 |
「Number」 |
Sample3 |
「Number」 |
Sample4 |
「Number」 |
Sample5 |
「Number」 |
Sample6 |
stss解析:
「字段(所占字节数)」 |
「十六进制数据」 |
「转换后结果」 |
box大小(4) |
0x00000014 |
20 |
box类型(4) |
0x73747373 |
stss |
版本(1) |
0x00 |
0 |
标志(3) |
0x000000 |
0 |
同步表数量(4) |
0x00000001 |
1个同步表,说明只有1个关键帧 |
同步表(---) |
0x00000001 |
同步表的格式:
「Entry Number」 |
Sample |
0x00000001(1) |
Sample1 |
12.4. stsc解析
stsc全称:Sample to chunk
❝
存储sample 与 chunk之间的映射关系。
❞
跳过20字节继续读取8字节:
00 00 00 28 73 74 73 63
前4个字节:0x00000028(40) 后4个字节:0x773747363(stsc) 说明我们读取到了stsc box,大小为40个字节。
stsc参数表:
字段 |
长度(字节) |
描述 |
box大小 |
4 |
该box的大小(字节) |
box类型 |
4 |
该box的类型 |
版本 |
1 |
该box的版本 |
标志 |
3 |
该box的标志 |
sample-to-chunk表数量 |
4 |
|
sample-to-chunk表 |
--- |
sample-to-chunk表结构:
字段 |
长度(字节) |
描述 |
First chunk |
4 |
第一个chunk |
Samples per chunk |
4 |
每块样本数 |
Sample description ID |
4 |
样本描述ID |
stsc解析表:
「字段(所占字节数)」 |
「十六进制数据」 |
「转换后结果」 |
box大小(4) |
0x00000028 |
40 |
box类型(4) |
0x73747363 |
stsc |
版本(1) |
0x00 |
0 |
标志(3) |
0x000000 |
0 |
sample-to-chunk表数量(4) |
0x00000002 |
2个 |
sample-to-chunk表(---) |
0x00000001 |
sample-to-chunk表:
First chunk |
Samples per chunk |
Sample description ID |
0x00000001(1) |
0x00000010(16) |
0x00000001(1) |
0x0000001D(29) |
0x0000000E(14) |
0x00000001(1) |
12.5. stsz解析
stsz全称:Sample size
❝
stsz box中存储了每个sample的大小。
❞
stsz参数表:
字段 |
长度(字节) |
描述 |
box大小 |
4 |
该box的大小(字节) |
box类型 |
4 |
该box的类型 |
版本 |
1 |
该box的版本 |
标志 |
3 |
该box的标志 |
样本个数 |
4 |
样本个数 |
sample-size表数量 |
4 |
|
sample-size表 |
--- |
sample-size表结构:
「Sample size」 |
「Sample」 |
Size |
Sample 1 |
Size |
Sample 2 |
Size |
Sample 3 |
跳过40字节继续读取8字节:
00 00 07 4C 73 74 73 7A
前4个字节:0x0000074C(1868) 后4个字节:0x7374737A(stsz) 说明我们读取到了stsz box,大小为1868个字节。
stsz解析表:
「字段(所占字节数)」 |
「十六进制数据」 |
「转换后结果」 |
box大小(4) |
0x0000074C |
1868 |
box类型(4) |
0x7374737A |
stsz |
版本(1) |
0x00 |
0 |
标志(3) |
0x000000 |
0 |
样本个数(4) |
0x00000000 |
0 |
sample-size表数量(4) |
0x000001CE |
462个 |
sample-size表(---) |
0x00000001 |
如何算出sample-size表的结束字符,或者说怎么算出sample-size表所占的字符大小:sample-size表数量=462
462*4=1848
我们从0x000001CE往后标1848个字节就是总体的sample-size表:
可以看出,标出来的结尾就到了stco了,说明我们计算的是正确的。
sample-size表解析:
「Sample size」 |
0x00094566(607590) |
0x00000091(145) |
0x00000011(17) |
0x00000011(17) |
.... |
0x00000011(17) |
12.6. stco解析
stco全称:Chunk offset atom
❝
stco box中通过offset定义了每个chunk到文件开头的位置。
❞
stco参数表:
字段 |
长度(字节) |
描述 |
box大小 |
4 |
该box的大小(字节) |
box类型 |
4 |
该box的类型 |
版本 |
1 |
该box的版本 |
标志 |
3 |
该box的标志 |
Chunk offset表数量 |
4 |
|
Chunk offset表 |
--- |
Chunk offset表结构:
「Chunk offset」 |
「Chunk」 |
「offset」 |
「Chunk」 1 |
「offset」 |
「Chunk」 2 |
「offset」 |
「Chunk」 3 |
跳过1868字节继续读取8字节:
00 00 00 84 73 74 63 6F
前4个字节:0x00000084(132) 后4个字节:0x7374636F(stco) 说明我们读取到了stco box,大小为132个字节。
stco解析表:
「字段(所占字节数)」 |
「十六进制数据」 |
「转换后结果」 |
box大小(4) |
0x00000084 |
132 |
box类型(4) |
0x7374636F |
stco |
版本(1) |
0x00 |
0 |
标志(3) |
0x000000 |
0 |
Chunk offset表数量(4) |
0x0000001D |
29个 |
Chunk offset表(---) |
0x00000001 |
我们计算下Chunk offset列表的大小:29*4=116字节,
Chunk offset表解析:
「Chunk offset」 |
「0x0000127A」 |
「0x0009798C」 |
「0x000989E2」 |
... |
... |
.. |
粉丝福利,博主耗时2个月整理了一份详细的音视频开发学习教程,涵盖了音视频开发FFmmpeg、流媒体客户端、流媒体服务器、WebRTC、Android NDK开发、IOS音视频开发等等全栈技术栈,并提供了配套的免费领取C++音视频学习资料包、技术视频/代码,内容包括(FFmpeg ,WebRTC ,rtmp ,hls ,rtsp ,ffplay ,编解码,推拉流,srs流媒体服务器,音视频通话等等)↓↓↓↓↓↓见下面↓↓文章底部点击免费领取↓↓