参考链接
- FFmpeg中mp4的demuxer(mov.c)代码阅读 - 简书
- mp4文件格式解析 - 简书
- mp4封装格式各box类型讲解及IBP帧计算_青丶空゛的博客-CSDN博客
- 5分钟入门MP4文件格式 - 程序猿小卡 - 博客园
- 关于M4A文件的随机访问 - 云+社区 - 腾讯云
MP4文件格式相关内容
- MP4文件由许多box组成,每个box包含不同的信息, 这些box以树形结构的方式组织。
- 以下是主要box的简要说明:
- 根节点之下,主要包含三个节点:ftyp、moov、mdat。
- ftyp:文件类型。描述遵从的规范的版本。
- moov box:媒体的metadata信息,保存了音视频数据的时空信息。
- mdat:具体的媒体数据。
- 说明:在 mp4 中默认写入字节序是 Big-Endian的。
2. mp4文件基本信息
分析mp4文件的工具:
- mp4box.js:一个在线解析mp4的工具。
- bento4:包含mp4dump、mp4edit、mp4encrypt等工具。
- MP4Box:类似于bento4,包含很全面的工具。
- mp4info.exe: windows平台图形界面展示mp4基本信息的工具。
- mvhd针对整个影片,tkhd针对单个track,mdhd针对媒体,vmhd针对视频,smhd针对音频,可以认为是从 宽泛 > 具体,前者一般是从后者推导出来的。
mp4文件基本信息
- audio信息:
- smplrate:sample rate(采样率)。
- channel:通道个数。
- bitrate:比特率。
- audiosamplenum:音频sample的个数。
- video信息:
- width、height:视频的宽/高。
- bitrate:比特率(码率),秒为单位。等于视频总的大小/时长。
- frames:视频帧数。
- fps:帧率(frame per second)。
- total_time:时间长度,ms为单位。等于duration/timescale。
- timescale:时间的粒度,1000表示1000个单位为1s。
- duration:时间粒度的个数。
- videosamplenum:视频sample的个数。
3. 封装格式重要概念
box
- mp4文件由若干个box组成。下面是box结构的一个示意图。
- box由header和body组成,header指明box的size和type。size是包含box header的整个box的大小。
- box type,通常是4个ASCII码的字符如“ftyp”、“moov”等,这些box type都是已经预定义好的,表示固定的含义。如果是“uuid”,表示该box为用户自定义扩展类型,如果box type是未定义的,应该将其忽略。
- 如果header中的size为1,则表示box长度需要更多的bits位来描述,在后面会有一个64bits位的largesize用来描述box的长度。如果size为0,表示该box为文件的最后一个box,文件结尾(同样只存在于“mdat”类型的box中)。
- 只有“mdat”类型的box才可能会用到large size
- size后面紧跟着的32位为box type,一般是4个字符,如“ftyp”、“moov”等,这些box type都是预定义好的,分别表示固定的意义。如果是“uuid”,表示box为用户的扩展类型,如果未定义box type 需要将其忽略
- box中可以包含box,这种box称为container box。
- box分为两种,Box和Fullbox。FullBox 是 Box 的扩展,Header 中增加了version 和 flags字段,分别定义如下:
aligned(8) class Box (unsigned int(32) boxtype,
optional unsigned int(8)[16] extended_type) {
unsigned int(32) size;
unsigned int(32) type = boxtype;
if (size==1) {
unsigned int(64) largesize;
} else if (size==0) {
// box extends to end of file
}
if (boxtype==‘uuid’) {
unsigned int(8)[16] usertype = extended_type;
}
}
- FullBox有version和flags字段,
aligned(8) class FullBox(unsigned int(32) boxtype, unsigned int(8) v, bit(24) f)
extends Box(boxtype) {
unsigned int(8) version = v;
bit(24) flags = f;
}
MP4box
ftyp box
- 该box有且只有1个,并且只能被包含在文件层,而不能被其他box包含。该box应该被放在文件的最开始,指示该MP4文件应用的相关信息。
- “ftyp” body依次包括1个32位的major brand(4个字符),1个32位的minor version(整数)和1个以32位(4个字符)为单位元素的数组compatible brands。这些都是用来指示文件应用级别的信息。
- major_brand:比如常见的 isom、mp41、mp42、avc1、qt等。它表示“最好”基于哪种格式来解析当前的文件。举例,major_brand 是 A,compatible_brands 是 A1,当解码器同时支持 A、A1 规范时,最好使用A规范来解码当前媒体文件,如果不支持A规范,但支持A1规范,那么,可以使用A1规范来解码;
- minor_version:提供 major_brand 的说明信息,比如版本号,不得用来判断媒体文件是否符合某个标准/规范;
- compatible_brands:文件兼容的brand列表。比如 mp41 的兼容 brand 为 isom。通过兼容列表里的 brand 规范,可以将文件 部分(或全部)解码出来;
- 在实际使用中,不能把 isom 做为 major_brand,而是需要使用具体的brand(比如mp41),因此,对于 isom,没有定义具体的文件扩展名、mime type。
- 下面是常见的几种brand,以及对应的文件扩展名、mime type,更多brand可以参考 这里 。
补充
关于AVC/AVC1
- 在讨论 MP4 规范时,提到AVC,有的时候指的是“AVC文件格式”,有的时候指的是"AVC压缩标准(H.264)",这里简单做下区分。
- AVC文件格式:基于 ISO基础文件格式 衍生的,使用的是AVC压缩标准,可以认为是MP4的扩展格式,对应的brand 通常是 avc1,在MPEG-4 PART 15 中定义。
- AVC压缩标准(H.264):在MPEG-4 Part 10中定义。
- ISO基础文件格式(Base Media File Format) 在 MPEG-4 Part 12 中定义。
FREE(可选的)
- free是可选的,如果存在,则通常出现在moov与mdat之间,即moov-free-mdat。
- free中的数据通常为全0,其作用相当于占位符,在实时拍摄视频,moov数据增多时分配给moov使用。
- 因为设备录制视频时并不能预先知道视频数据大小,如果moov在mdat之前,随着拍摄mdat的数据会增加,moov数据也会增多,如果没有free预留的空间,则要不停的向后移动mdat数据以腾出moov空间。
-
“free”中的内容是无关紧要的,可以被忽略。该box被删除后,不会对播放产生任何影响。
moov box
- moov box 是一个 container box 该box包含了文件媒体的元数据信息,具体内容信息由子box诠释。同File Type Box一样,该box有且只有一个,且只被包含在文件层。一般情况下,“moov”会紧随“ftyp”出现。
- 可以看到这个demo 中有 mvhd、trak、udta 三种 box 一般情况下 “moov”中会包含1个“mvhd”和若干个“trak”。其中“mvhd”为header box,一般作为“moov”的第一个子box出现。“trak”包含了一条音、视频轨/流/track的相关信息,也是一个container box。
- 该box是解析MP4文件里面最重要的一个box,它包含了音视频数据的编码格式、音视频数据样本,chunks的大小、存储位置也即偏移offset、时间戳单位、DTS,CTS(PTS),解码时间、显示时间等等…
- moov box中记录的每帧音视频数据位置信息,实际上都在mdat box中,通过解析moov box来获取到每帧音视频数据具体位置后,使得播放器能方便的拖拉进度条。
mvhd box (Movie Header Box)
- mvhd 描述了与具体音频或视频流无关的文件整体信息,其中的duration/timescale的值即为单位为秒的媒体时长。
- 创建时间、修改时间、时间度量标尺、可播放时长等信息
字段 | 字节数 | 意义 |
box size | 4 | box大小 |
box type | 4 | box类型 |
version | 1 | box版本,0或1,一般为0。(以下字节数均按version=0) |
flags | 3 | |
creation time | 4 | 创建时间(相对于UTC时间1904-01-01零点的秒数) |
modification time | 4 | 修改时间 |
time scale | 4 | 文件媒体在1秒时间内的刻度值,可以理解为1秒长度的时间单元数 |
duration | 4 | 该 track的时间长度,用duration和time scale值可以计算track时长,比如audio track的time scale = 8000, duration = 560128,时长为70.016,video track的time scale = 600, duration = 42000,时长为70 |
rate | 4 | 推荐播放速率,高16位和低16位分别为小数点整数部分和小数部分,即[16.16] 格式,该值为1.0(0x00010000)表示正常前向播放 |
volume | 2 | 与rate类似,[8.8] 格式,1.0(0x0100)表示最大音量 |
reserved | 10 | 保留位 |
matrix | 36 | 视频变换矩阵,一般忽略不计 |
pre-defined | 24 | |
next track id | 4 | 下一个track使用的id号 |
补充
- timescale:一秒包含的时间单位(整数)。举个例子,如果timescale等于1000,那么,一秒包含1000个时间单位(后面track等的时间,都要用这个来换算,比如track的duration为10,000,那么,track的实际时长为10,000/1000=10s);
- next_track_ID:32位整数,非0,一般可以忽略不计。当要添加一个新的track到这个影片时,可以使用的track id,必须比当前已经使用的track id要大。也就是说,添加新的track时,需要遍历所有track,确认可用的track id;
trak box (Track Box)
- trak也是一个container box,其子box包含了该track的媒体数据引用和描述。一个MP4文件中的媒体可以包含多个track,且至少有一个track,这些track之间彼此独立,有自己的时间和空间信息。“trak”必须包含一个“tkhd”和一个“mdia”,此外还有很多可选的box(略)
- track表示一些sample集合,对于媒体数据来说,track表示一个视频或者音频序列
- 一系列子box描述了每个媒体轨道的具体信息
- hint track并不包含媒体数据,而是包含将一些其他数据track打包成流媒体的指示信息
- sample对于非hint track来说,video sample 表示视频帧,或者一组连续视频帧,audio sample即为一段连续的压缩音频,统称为sample,对于hint track,sample定义了一个或者多个流媒体的格式
- sample table指明sample的时序和物理布局的表
- chunk 一个track的几个sample组成的单元
- MP4文件中 媒体内容在moov的box中,一个moov包含多个track,每个track就是一个随时间变化的媒体序列,track里每个时间单位是一个sample,sample是按照时间顺序排列。注意,一帧音频可以分解为多个音频sample,所以音频一般用sample作为单位,而不用帧
tkhd(track header box)
- tkhd 描述的该track的,如果是视频会有宽、高信息、 还有文件创建时间、修改时间等。
字段 | 字节数 | 意义 |
box size | 4 | box大小 |
box type | 4 | box类型 |
version | 1 | box版本,0或1,一般为0。(以下字节数均按version=0) |
flags | 3 | 按位或操作结果值,预定义如下:0x000001 track_enabled,否则该track不被播放;0x000002 track_in_movie,表示该track在播放中被引用;0x000004 track_in_preview,表示该track在预览时被引用。一般该值为7,如果一个媒体所有track均未设置track_in_movie和track_in_preview,将被理解为所有track均设置了这两项;对于hint track,该值为0 |
creation time | 4 | 创建时间(相对于UTC时间1904-01-01零点的秒数) |
modification time | 4 | 修改时间 |
track id | 4 | id号,不能重复且不能为0 |
reserved | 4 | 保留位 |
duration | 4 | track的时间长度;当前track的完整时长(需要除以timescale得到具体秒数) |
reserved | 8 | 保留位 |
layer | 2 | 视频层,默认为0,值小的在上层;视频轨道的叠加顺序,数字越小越靠近观看者,比如1比2靠上,0比1靠上 |
alternate group | 2 | track分组信息,默认为0表示该track未与其他track有群组关系;当前track的分组ID,alternate_group值相同的track在同一个分组里面。同个分组里的track,同一时间只能有一个track处于播放状态。当alternate_group为0时,表示当前track没有跟其他track处于同个分组。一个分组里面,也可以只有一个track |
volume | 2 | [8.8] 格式,如果为音频track,1.0(0x0100)表示最大音量;否则为0 |
reserved | 2 | 保留位 |
matrix | 36 | 视频变换矩阵 |
width | 4 | 宽 |
height | 4 | 高,均为 [16.16] 格式值,与sample描述中的实际画面大小比值,用于播放时的展示宽高 |
补充
- flags:按位或操作获得,默认值是7(0x000001 | 0x000002 | 0x000004),表示这个track是启用的、用于播放的 且 用于预览的。
- Track_enabled:值为0x000001,表示这个track是启用的,当值为0x000000,表示这个track没有启用;
- Track_in_movie:值为0x000002,表示当前track在播放时会用到;
- Track_in_preview:值为0x000004,表示当前track用于预览模式;
mdia (Track Media Structure)
- mdia box 描述了这条音视频轨/流(trak)的媒体数据样本的主要信息,对播放器来说是一个很重要的box
- “mdia”也是个container box,其子box的结构和种类还是比较复杂的。先来看一个“mdia”的实例结构树图。
- 总 体来说,“mdia”定义了track媒体类型以及sample数据,描述sample信息。一般“mdia”包含一个“mdhd”,一个“hdlr”和 一个“minf”,其中“mdhd”为media header box,“hdlr”为handler reference box,“minf”为media information box。下面依次看一下这几个box的结构。
mdhd (Media Header Box)
- 当前音/视频轨/流(trak)的总体信息, 该box中有duration字段和timescale字段,duration/timescale的值即为当前流的时长。
- hdlr box用来指定该流的类型
字段 | 字节数 | 意义 |
box size | 4 | box大小 |
box type | 4 | box类型 |
version | 1 | box版本,0或1,一般为0。(以下字节数均按version=0) |
flags | 3 | |
creation time | 4 | 创建时间(相对于UTC时间1904-01-01零点的秒数) |
modification time | 4 | 修改时间 |
time scale | 4 | 同前表 |
duration | 4 | track的时间长度 |
language | 2 | 媒体语言码。最高位为0,后面15位为3个字符(见ISO 639-2/T标准中定义) |
pre-defined | 2 |
Handler Reference Box(hdlr)
- “hdlr”解释了媒体的播放过程信息,该box也可以被包含在meta box(meta)中。“hdlr”结构如下表。
字段 | 字节数 | 意义 |
box size | 4 | box大小 |
box type | 4 | box类型 |
version | 1 | box版本,0或1,一般为0。(以下字节数均按version=0) |
flags | 3 | |
pre-defined | 4 | |
handler type | 4 | 在media box中,该值为4个字符:“vide”— video track“soun”— audio track“hint”— hint track |
reserved | 12 | |
name | 不定 | track type name,以‘\0’结尾的字符串 |
- handler_type的取值包括:
- vide(0x76 69 64 65),video track;
- soun(0x73 6f 75 6e),audio track;
- hint(0x68 69 6e 74),hint track;
- name为utf8字符串,对handler进行描述,比如 L-SMASH Video Handler(参考 这里)。
- “hdlr”的字节实例如下图,各字段已经用颜色区分开:
- stsd box的子box用于保存该流的编码类型
- avcC box指定了该流的编码类型为H264,储了解码所需的SPS、PPS信息。
- stsc stsz stco三个box用于保存每帧视频或音频数据在文件中的保存位置。
- stts stss ctts三个box用于保存媒体数据和时间戳的对应关系。
- 在同级的stbl的样本表box里面可以查到对应的样本 描述信息(stsd),时序信息(stts),样本的大小信息(stsz),样本到chunk的映射信息(stsc),chunk的位置信息(stco)等等
Media Information Box(minf)
- “minf” 存储了解释track媒体数据的handler-specific信息,media handler用这些信息将媒体时间映射到媒体数据并进行处理。“minf”中的信息格式和内容与媒体类型以及解释媒体数据的media handler密切相关,其他media handler不知道如何解释这些信息。“minf”是一个container box,其实际内容由子box说明。
- 一 般情况下,“minf”包含一个header box,一个“dinf”和一个“stbl”,其中,header box根据track type(即media handler type)分为“vmhd”、“smhd”、“hmhd”和“nmhd”,“dinf”为data information box,“stbl”为sample table box。下面分别介绍。
- 下图为“minf”部分字节实例,其中红色为box header,蓝色为“smhd”,绿色为“dinf”,黄色为一部分“stbl”。
Media Information Header Box(vmhd、smhd、hmhd、nmhd)
- Video Media Header Box(vmhd)
字段 | 字节数 | 意义 |
box size | 4 | box大小 |
box type | 4 | box类型 |
version | 1 | box版本,0或1,一般为0。(以下字节数均按version=0) |
flags | 3 | |
graphics mode | 4 | 视频合成模式,为0时拷贝原始图像,否则与opcolor进行合成 |
opcolor | 2×3 | {red,green,blue} |
- Sound Media Header Box(smhd)
字段 | 字节数 | 意义 |
box size | 4 | box大小 |
box type | 4 | box类型 |
version | 1 | box版本,0或1,一般为0。(以下字节数均按version=0) |
flags | 3 | |
balance | 2 | 立体声平衡,[8.8] 格式值,一般为0,-1.0表示全部左声道,1.0表示全部右声道 |
reserved | 2 |
- Hint Media Header Box(hmhd) 略
- Null Media Header Box(nmhd) 非视音频媒体使用该box,略
Data Information Box(dinf)
- “dinf”解释如何定位媒体信息,是一个container box。“dinf”一般包含一个“dref”,即data reference box;“dref”下会包含若干个“url”或“urn”,这些box组成一个表,用来定位track数据。简单的说,track可以被分成若干段,每 一段都可以根据“url”或“urn”指向的地址来获取数据,sample描述中会用这些片段的序号将这些片段组成一个完整的track。一般情况下,当 数据被完全包含在文件中时,“url”或“urn”中的定位字符串是空的。
- “dref”的字节结构如下表。
字段 | 字节数 | 意义 |
box size | 4 | box大小 |
box type | 4 | box类型 |
version | 1 | box版本,0或1,一般为0。(以下字节数均按version=0) |
flags | 3 | |
entry count | 4 | “url”或“urn”表的元素个数 |
“url”或“urn”列表 | 不定 |
- “url”或“urn”都是box,“url”的内容为字符串(location string),“urn”的内容为一对字符串(name string and location string)。当“url”或“urn”的box flag为1时,字符串均为空。
- 下 面是一个“dinf”的字节实例图。其中黄色为“dinf”的box header,由红色部分我们知道包含的“url”或“urn”个数为1,红色后面为“url”box的内容。紫色为“url”的box header(根据box type我们知道是个“url”),绿色为box flag,值为1,说明“url”中的字符串为空,表示track数据已包含在文件中。
Sample Table Box(stbl)
- “stbl”几乎是普通的MP4文件中最复杂的一个box了,首先需要回忆一下sample的概念。sample是媒体数据存储的单位,存储在media的chunk中,chunk和sample的长度均可互不相同,如下图所示。
- “stbl” 包含了关于track中sample所有时间和位置的信息,以及sample的编解码等信息。利用这个表,可以解释sample的时序、类型、大小以及在各自存储容器中的位置。“stbl”是一个container box,其子box包括:sample description box(stsd)、time to sample box(stts)、sample size box(stsz或stz2)、sample to chunk box(stsc)、chunk offset box(stco或co64)、composition time to sample box(ctts)、sync sample box(stss)等。
- “stsd”必不可少,且至少包含一个条目,该box包含了data reference box进行sample数据检索的信息。没有“stsd”就无法计算media sample的存储位置。“stsd”包含了编码的信息,其存储的信息随媒体类型不同而不同。
Sample Description Box(stsd)
- 给出视频、音频的编码、宽高、音量等信息,以及每个sample中包含多少个frame
- 存储了编码类型和初始化解码器需要的信息。有与特定的track-type相关的信息,相同的track-type也会存在不同信息的情况如使用不一样的编码标准。
- 结构如下:
- box header和version字段后会有一个entry count字段,根据entry的个数,每个entry会有type信息,如“vide”、“sund”等,根据type不同sample description会提供不同的信息,例如对于video track,会有“VisualSampleEntry”类型信息,对于audio track会有“AudioSampleEntry”类型信息。
- 视频的编码类型、宽高、长度,音频的声道、采样等信息都会出现在这个box中。
Time To Sample Box(stts)
- 结构如下:
- “stts” 存储了sample的duration,描述了sample时序的映射方法,我们通过它可以找到任何时间的sample。“stts”可以包含一个压缩的 表来映射时间和sample序号,用其他的表来提供每个sample的长度和指针。表中每个条目提供了在同一个时间偏移量里面连续的sample序号,以 及samples的偏移量。递增这些偏移量,就可以建立一个完整的time to sample表。
Sample Size Box(stsz)
- 每个sample的size(单位是字节) ,根据 sample_size 字段,可以知道当前track包含了多少个sample(或帧)。
- 结构如下:
- “stsz” 定义了每个sample的大小,包含了媒体中全部sample的数目和一张给出每个sample大小的表。这个box相对来说体积是比较大的。
- 有两种不同的box类型,stsz、stz2。
stsz:
- sample_size:默认的sample大小(单位是byte),通常为0。如果sample_size不为0,那么,所有的sample都是同样的大小。如果sample_size为0,那么,sample的大小可能不一样。
- sample_count:当前track里面的sample数目。如果 sample_size==0,那么,sample_count 等于下面entry的条目;
- entry_size:单个sample的大小(如果sample_size==0的话)
stz2:
- field_size:entry表中,每个entry_size占据的位数(bit),可选的值为4、8、16。4比较特殊,当field_size等于4时,一个字节上包含两个entry,高4位为entry[i],低4位为entry[i+1];
- sample_count:等于下面entry的条目;
- entry_size:sample的大小。
Sample To Chunk Box(stsc)
- 结构如下:
- 用chunk组织sample可以方便优化数据获取,一个thunk包含一个或多个sample。“stsc”中用一个表描述了sample与chunk的映射关系,查看这张表就可以找到包含指定sample的thunk,从而找到这个sample。
Sync Sample Box(stss)
- 结构如下:
- “stss” 确定media中的关键帧。对于压缩媒体数据,关键帧是一系列压缩序列的开始帧,其解压缩时不依赖以前的帧,而后续帧的解压缩将依赖于这个关键帧。 “stss”可以非常紧凑的标记媒体内的随机存取点,它包含一个sample序号表,表内的每一项严格按照sample的序号排列,说明了媒体中的哪一个 sample是关键帧。如果此表不存在,说明每一个sample都是一个关键帧,是一个随机存取点。
Chunk Offset Box(stco)
- thunk在文件中的偏移
- 结构如下:
- “stco” 定义了每个thunk在媒体流中的位置。位置有两种可能,32位的和64位的,后者对非常大的电影很有用。在一个表中只会有一种可能,这个位置是在整个文 件中的,而不是在任何box中的,这样做就可以直接在文件中找到媒体数据,而不用解释box。需要注意的是一旦前面的box有了任何改变,这张表都要重新 建立,因为位置信息已经改变了。
- 针对小文件、大文件,有两种不同的box类型,分别是stco、co64,它们的结构是一样的,只是字段长度不同。
- chunk_offset 指的是在文件本身中的 offset,而不是某个box内部的偏移。
- 在构建mp4文件的时候,需要特别注意 moov 所处的位置,它对于chunk_offset 的值是有影响的。有一些MP4文件的 moov 在文件末尾,为了优化首帧速度,需要将 moov 移到文件前面,此时,需要对 chunk_offset 进行改写。
PTS和DTS的计算
I P B 帧的概念
- 在音视频中,为了提高压缩效率,会将每帧画面压缩为不同类型的视频帧数据。
- I帧表示关键帧,包含有一帧画面的完整信息,解码时只需要本帧数据就可以解码出完整的一帧画面。
- P帧表示前向参考帧,它保存了本帧与上一帧的差异信息,它不能单独解码,需要根据上一帧的画面加上本帧保存的差值来获取本帧的完整画面。
- B帧为双向参考帧,它解码时需要依赖它之前和之后的帧来获取最终的画面
- 因为B帧需要依赖它后面的帧来进行解码,所以它的解码顺序就必然和显示顺序不能保持一致,这时就需要解码时间戳(DTS)和显示时间戳(PTS)来共同决定一帧视频数据何时解码,然后何时显示了。
- 举个例子
- 一小段视频帧序列如下 :
- type : I — B — B — P — B — B — P
- PTS : 0.33 0.67 1.00 1.33 1.67 2.00 2.33
- DTS : 0.00 0.67 1.00 0.33 1.67 2.00 1.33
- PTS >= DTS
- 根据mp4 stts和ctts 可以得到DTS和PTS
stts(Decoding Time to Sample Box)
- stts 可以计算出每个sample的dts,其中sample_delta为该sample的dts相对于上一个smaple的差值,
- stts包含了DTS到sample number的映射表,主要用来推导每个帧(sample)的时长。
- 那么此样本数据的dts为 : 0 1000 2000 3000 4000 ···
- entry_count:stts 中包含的entry条目数;
- sample_count:单个entry中,具有相同时长(duration 或 sample_delta)的连续sample的个数。
- sample_delta:sample的时长(以timescale为计量)
ctts(Composition Time to Sample Box)
- Composition Time 构成时间目前我直接理解的PTS。。
- ctts 有每个sample的构成时间(Composition Time)和解码时间(DTS)之间的差值(CTTS)即图中的composition_offset。
- 如果不存在ctts,则代表该流不存在B帧,那么PTS就直接等于DTS。
- 帧解码到渲染的时间差值,通常用在B帧的场景,对于存在B帧的视频来说,ctts就需要存在了。当PTS、DTS不相等时,就需要ctts了,公式为 CT(n) = DT(n) + CTTS(n) 。
-
对于只有I帧、P帧的视频来说,解码顺序、渲染顺序是一致的,此时,ctts没必要存在。
timescale
- 最后就是关于单位,你可以看到图中样本的单位都是以1000为单位浮动,实际上真实DTS和PTS时间是需要除以mdia/mdhd中的timescale。这里是30000。
- 有了这些,我们就可以在ctts里面计算出pts了 :
else if (box_type_equa(uint32_to_str(bh.type, sbuffer), "ctts")) {
uint32_t version = 0;
read_net_bytes_to_host_uint32(&box[8], &version);
if(version != 0) {
LOG_E("ctts unsupport version :%d ", version)
return;
}
uint32_t entry_cnt = 0;
read_net_bytes_to_host_uint32(&box[12], &entry_cnt);
char buf[128] = {0};
tree_childs_insert_with_val(tree, "version", uint32_to_ascii(version, buf));
tree_childs_insert_with_val(tree, "entry_cnt", uint32_to_ascii(entry_cnt, buf));
uint32_t i = 0, j = 0, num = 0, pos = 16;
for (i = 0; i < entry_cnt; i++) {
uint32_t sample_cnt;
read_net_bytes_to_host_uint32(&box[pos], &sample_cnt);
pos += 4;
uint32_t sample_offset;
read_net_bytes_to_host_uint32(&box[pos], &sample_offset);
pos += 4;
for (j = 0; j < sample_cnt; j++) {
PushBack_Array(pts_array, At_Array(dts_array, num++) + sample_offset);
float dt, pt = 0.0;
printf("dts : %9.3f ms | pts : %9.3f ms | \n", At_Array(dts_array, num - 1) / (mdhd_time_scale * 1.0), At_Array(pts_array, num - 1) / (mdhd_time_scale * 1.0));
}
stss (Sync Sample Box)
- stss 里面存放了关键帧的序号(I帧),跳转时,需要从关键帧开始解码,否则会花屏。
- 哪些sample是关键帧
- mp4文件中,关键帧所在的sample序号。如果没有stss的话,所有的sample中都是关键帧。
- entry_count:entry的条目数,可以认为是关键帧的数目;
- sample_number:关键帧对应的sample的序号;(从1开始计算)
![](https://i-blog.csdnimg.cn/blog_migrate/d41931d681b57e698bab44af5c000693.png)
stsz (Sample Size Boxes):
- 顾名思义,样本大小.
stsc (Sample To Chunk Box):
- 媒体数据的样本是被打包进chunks(块)的,chunks和样本(samples)的大小不固定,该box用于说明chunks关联样本的信息。
- 每个thunk中包含几个sample
- entry_count:有多少个表项(每个表项,包含first_chunk、samples_per_chunk、sample_description_index信息);
- first_chunk 该入口第一个chunks的索引(index).
- samples_per_chunk 样本数量/chunks.
- sample_description_index:指向 stsd 中 sample description 的索引值(参考stsd小节);
-
sample 以 chunk 为单位分成多个组。chunk的size可以是不同的,chunk里面的sample的size也可以是不同的
前面描述比较抽象,这里看个例子,这里表示的是:
- 序号1~15的chunk,每个chunk包含15个sample;
- 序号16的chunk,包含30个sample;
- 序号17以及之后的chunk,每个chunk包含28个sample;
- 以上所有chunk中的sample,对应的sample description的索引都是1;
first_chunk | samples_per_chunk | sample_description_index |
---|---|---|
1 | 15 | 1 |
16 | 30 | 1 |
17 | 28 | 1 |
stco (Chunk Offset Box)
- 描述每个chunks相对文件的偏移量。
- 如图 第一个chunks即前10个样本(此例), samples.1起始地址为 423257, samples.1的地址则为 423257 + 140798 = 564055, 依此类推…
- 有了这些即可计算出音视频的时间和空间信息了
![](https://i-blog.csdnimg.cn/blog_migrate/7722d6d3b0686963be1563e7833809fe.png)
mdat box
- Meida Data Box 媒体数据box 位于顶层,定义是一个字节数组,用来存储媒体数据。该box数量可以为0个,也可以有多个(当媒体数据全部为外部文件引用时),数据直接跟在box type字段后面,具体数据结构的意义需要参考metadata(主要在sample table中描述)。
- 实际媒体数据。我们最终解码播放的数据都在这里面
- 该box包含于文件层,可以有多个,也可以没有(当媒体数据全部为外部文件引用时),用来存储媒体数据。数据直接跟在box type字段后面,具体数据结构的意义需要参考metadata(主要在sample table中描述)。
mehd(Movie Extends Header Box)
- mehd是可选的,用来声明影片的完整时长(fragment_duration)。如果不存在,则需要遍历所有的fragment,来获得完整的时长。对于fmp4的场景,fragment_duration一般没办法提前预知。
trex(Track Extends Box)
- 用来给 fMP4 的 sample 设置各种默认值,比如时长、大小等
-
字段含义如下:
- track_id:对应的 track 的 ID,比如video track、audio track 的ID;
- default_sample_description_index:sample description 的默认 index(指向stsd);
- default_sample_duration:sample 默认时长,一般为0;
- default_sample_size:sample 默认大小,一般为0;
- default_sample_flags:sample 的默认flag,一般为0;
- 老版本规范里,前6位都是保留位,新版规范里,只有前4位是保留位。is_leading 含义不是很直观,下一小节会专门讲解下。
- reserved:4 bits,保留位;
- is_leading:2 bits,是否 leading sample,可能的取值包括:
- 0:当前 sample 不确定是否 leading sample;(一般设为这个值)
- 1:当前 sample 是 leading sample,并依赖于 referenced I frame 前面的 sample,因此无法被解码;
- 2:当前 sample 不是 leading sample;
- 3:当前 sample 是 leading sample,不依赖于 referenced I frame 前面的 sample,因此可以被解码;
- sample_depends_on:2 bits,是否依赖其他sample,可能的取值包括:
- 0:不清楚是否依赖其他sample;
- 1:依赖其他sample(不是I帧);
- 2:不依赖其他sample(I帧);
- 3:保留值;
- sample_is_depended_on:2 bits,是否被其他sample依赖,可能的取值包括:
- 0:不清楚是否有其他sample依赖当前sample;
- 1:其他sample可能依赖当前sample;
- 2:其他sample不依赖当前sample;
- 3:保留值;
- sample_has_redundancy:2 bits,是否有冗余编码,可能的取值包括:
- 0:不清楚是否存在冗余编码;
- 1:存在冗余编码;
- 2:不存在冗余编码;
- 3:保留值;
- sample_padding_value:3 bits,填充值;
- sample_is_non_sync_sample:1 bits,不是关键帧;
- sample_degradation_priority:16 bits,降级处理的优先级(一般针对如流传过程中出现的问题);
is_leading
- 为方便讲解,下面的 leading frame 对应 leading sample,referenced frame 对应 referenced samle。
- 以 H264编码 为例,H264 中存在 I帧、P帧、B帧。由于 B帧 的存在,视频帧的 解码顺序、渲染顺序 可能不一致。
- mp4文件的特点之一,就是支持随机位置播放。比如,在视频网站上,可以拖动进度条快进。
- 很多时候,进度条定位的那个时刻,对应的不一定是 I帧。为了能够顺利播放,需要往前查找最近的一个 I帧,如果可能的话,从最近的 I帧 开始解码播放(也就是说,不一定能从前面最近的I帧播放)。
- 将上面描述的此刻定位到的帧,称作 leading frame。leading frame 前面最近的一个 I 帧,叫做 referenced frame。
- 回顾下 is_leading 为 1 或 3 的情况,同样都是 leading frame,什么时候可以解码(decodable),什么时候不能解码(not decodable)?
- 我没看懂
1、is_leading 为 1 的例子: 如下所示,帧2(leading frame) 解码依赖 帧1、帧3(referenced frame)。在视频流里,从 帧2 往前查找,最近的 I帧 是 帧3。哪怕已经解码了 帧3,帧2 也解不出来。
2、is_leading 为 3 的例子: 如下所示,此时,帧2(leading frame)可以解码出来。
moof
- moof是个container box,相关 metadata 在内嵌box里,比如 mfhd、 tfhd、trun 等。
mfhd(Movie Fragment Header Box)
- 结构比较简单,sequence_number 为 movie fragment 的序列号。根据 movie fragment 产生的顺序,从1开始递增。
traf(Track Fragment Box)
- 对 fmp4 来说,数据被氛围多个 movie fragment。一个 movie fragment 可包含多个track fragment(每个 track 包含0或多个 track fragment)。
- 每个 track fragment 中,可以包含多个该 track 的 sample。 每个 track fragment 中,包含多个 track run,每个 track run 代表一组连续的 sample。
tfhd(Track Fragment Header Box)
- tfhd 用来设置 track fragment 中 的 sample 的 metadata 的默认值。
- sample_description_index、default_sample_duration、default_sample_size 没什么好讲的,这里只讲解下 tf_flags、base_data_offset。
- 首先是 tf_flags,不同 flag 的值如下(同样是求按位求或) :
- 0x000001 base‐data‐offset‐present:存在 base_data_offset 字段,表示 数据位置 相对于整个文件的 基础偏移量。
- 0x000002 sample‐description‐index‐present:存在 sample_description_index 字段;
- 0x000008 default‐sample‐duration‐present:存在 default_sample_duration 字段;
- 0x000010 default‐sample‐size‐present:存在 default_sample_size 字段;
- 0x000020 default‐sample‐flags‐present:存在 default_sample_flags 字段;
- 0x010000 duration‐is‐empty:表示当前时间段不存在sample,default_sample_duration 如果存在则为0 ,;
- 0x020000 default‐base‐is‐moof:如果 base‐data‐offset‐present 为1,则忽略这个flag。如果 base‐data‐offset‐present 为0,则当前 track fragment 的 base_data_offset 是从 moof 的第一个字节开始计算;
- sample 位置计算公式为 base_data_offset + data_offset,其中,data_offset 每个 sample 单独定义。如果未显式提供 base_data_offset,则 sample 的位置的通常是基于 moof 的相对位置。
- 举个例子,比如 tf_flags 等于 57,表示 存在 base_data_offset、default_sample_duration、default_sample_flags。
- base_data_offset 为 1263 (ftyp、moov 的size 之和为 1263)。
trun(Track Fragment Run Box)
- 前面听过,track run 表示一组连续的 sample,其中:
- sample_count:sample 的数目;
- data_offset:数据部分的偏移量;
- first_sample_flags:可选,针对当前 track run中 第一个 sample 的设置;
- tr_flags 如下,大同小异:
- 0x000001 data‐offset‐present:存在 data_offset 字段;
- 0x000004 first‐sample‐flags‐present:存在 first_sample_flags 字段,这个字段的值,只会覆盖第一个 sample 的flag设置;当 first_sample_flags 存在时,sample_flags 则不存在;
- 0x000100 sample‐duration‐present:每个 sample 都有自己的 sample_duration,否则使用默认值;
- 0x000200 sample‐size‐present:每个 sample 都有自己的 sample_size,否则使用默认值;
- 0x000400 sample‐flags‐present:每个 sample 都有自己的 sample_flags,否则使用默认值;
- 0x000800 sample‐composition‐time‐offsets‐present:每个 sample 都有自己的 sample_composition_time_offset;
- 0x000004 first‐sample‐flags‐present,覆盖第一个sample的设置,这样就可以把一组sample中的第一个帧设置为关键帧,其他的设置为非关键帧;
- 举例如下,tr_flags 为 2565。此时,存在 data_offset 、first_sample_flags、sample_size、sample_composition_time_offset。
补充
- moofbox,这个box是视频分片的描述信息。并不是MP4文件必须的部分,但在我们常见的可在线播放的MP4格式文件中(例如Silverlight Smooth Streaming中的ismv文件)确是重中之重。
- mfra box,一般在文件末尾,媒体的索引文件,可通过查询直接定位所需时间点的媒体数据。
- 附:Smooth Streaming中ismv文件结构,文件分为了多个Fragments,每个Fragment中包含moof和mdat。这样的结构符合渐进式播放需求。(mdat及其描述信息逐步传输,收齐一个Fragment便可播放其中的mdat)。
- mp4和fmp4的区别
- fMP4 跟普通 mp4 基本文件结构是一样的。普通mp4用于点播场景,fmp4通常用于直播场景。
- 它们有以下差别:
- 普通mp4的时长、内容通常是固定的。fMP4 时长、内容通常不固定,可以边生成边播放;
- 普通mp4完整的metadata都在moov里,需要加载完moov box后,才能对mdat中的媒体数据进行解码渲染;
- fMP4中,媒体数据的metadata在moof box中,moof 跟 mdat (通常)结对出现。moof 中包含了sample duration、sample size等信息,因此,fMP4可以边生成边播放;
- 怎么判断mp4文件是普通mp4,还是fMP4呢?一般可以看下是否存在存在mvex(Movie Extends Box)。
- 当存在mvex时,表示当前文件是fmp4(非严谨)。此时,sample相关的metadata不在moov里,需要通过解析moof box来获得。
- 它们有以下差别:
- sample:
- video sample 即为一帧或者一组连续的视频帧
- audio sample 即为一段连续的音频
- sample table 指明sample时序和物理布局的表