一文读懂MP4封装格式
Posted 木大白易
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了一文读懂MP4封装格式相关的知识,希望对你有一定的参考价值。
简介
MP4或称MPEG-4第14部分(MPEG-4 Part 14)是一种标准的数字多媒体容器格式。扩展名为.mp4
。
虽然被官方标准定义的唯一扩展名是.mp4
,但第三方通常会使用各种扩展名来指示文件的内容:
- 同时拥有音频视频的MPEG-4文件通常使用标准扩展名
.mp4
- 仅有音频的MPEG-4文件会使用
.m4a
扩展名
大部分数据可以通过专用数据流嵌入到MP4文件中,因此MP4文件中包含了一个单独的用于存储流信息的轨道。目前得到广泛支持的编解码器或数据流格式有:
- 视频格式:H.264/AVC、H.265/HEVC、VP8/9等
- 音频格式:AAC、MP3、Opus等
文件格式
MP4文件由多个box组成,每个box存储不同的信息,且box之间是树状结构:
随便看一个MP4文件:
查看MP4文件的信息有很多方式,个人比较推荐的:
- https://www.onlinemp4parser.com/
- http://www.bento4.com/
- SteamAnalyzer
MP4 Box
这里就借用一个网图,上述图片来自:https://www.jianshu.com/p/529c3729f357
一个box由两部分组成:box header
、box body
。
box header
:box的元数据,比如box type、box size。box body
:box的数据部分,实际存储的内容跟box类型有关,比如mdat中body部分存储的媒体数据。
box header中,只有type、size是必选字段。当size==1
时,存在largesize字段。如果size==0
,表示该box为文件的最后一个box。在部分box中,还存在version、flags字段,这样的box叫做Full Box
。当box body中嵌套其他box时,这样的box叫做container box
。
我们在最开始展示了一个MP4文件box的图,根结构下一般有4个box(ftyp,free,mdat,moov),下面我们就来了解一下这几个box
ftyp(File Type Box)
一般在文件的开始位置,描述的文件的版本、兼容协议等。
isom(ISO Base Media file)是在 MPEG-4 Part 12 中定义的一种基础文件格式。MP4 文件可能遵循的规范有mp41、mp42,而mp41、mp42又是基于isom衍生出来的。
- major_brand:比如常见的 isom、mp41、mp42、avc1、qt等。它表示“最好”基于哪种格式来解析当前的文件。
- minor_version:提供 major_brand 的说明信息,比如版本号,不得用来判断媒体文件是否符合某个标准/规范;
- compatible_brands:文件兼容的brand列表。
free(Free Space Box)
free box(有的也叫skip),这个可以不用关心,去掉它也不影响解封装。
mdat(Media Data Box)
存放具体的媒体数据。数据结构的意义需要参考metadata(主要在sample table中描述)
moov(Movie Box)
存储mp4文件的metadata信息,“moov”是一个container box,它包含mvhd、track和udta这三个子box。
mvhd(Movie Header Box)
记录整个媒体文件的描述信息,如创建时间、修改时间、时间度量标尺、播放时长等。
一些重要的字段:
creation_time
:文件创建时间;modification_time
:文件修改时间;timescale
:一秒包含的时间单位(整数)。举个例子,如果timescale等于1000,那么,一秒包含1000个时间单位(比如track的duration为10,000,那么,track的实际时长为10,000/1000=10s);duration
:影片时长(整数),根据文件中的track的信息推导出来,等于时间最长的track的duration;rate
:推荐的播放速率,32位整数,高16位、低16位分别代表整数部分、小数部分([16.16]),举例 0x0001 0000 代表1.0,正常播放速度;volume
:播放音量,16位整数,高8位、低8位分别代表整数部分、小数部分([8.8]),举例 0x01 00 表示 1.0,即最大音量;matrix
:视频的转换矩阵,一般可以忽略不计;next_track_ID
:32位整数,非0,一般可以忽略不计。当要添加一个新的track到这个影片时,可以使用的track id,必须比当前已经使用的track id要大。也就是说,添加新的track时,需要遍历所有track,确认可用的track id;
track (Track Box)
记录媒体流信息,文件中可以存在一个或多个 track,它们之间是相互独立的。一般的包含一个视频track和一个音频track。
track是container box,至少包含两个子box:tkhd
和mdia
。
一些重要的字段:
视频:
packets
:包含的packets的数量
steam type
: 流的类型,比如AVC/H.264 、 AAC
profile
: 编码器的profile,如High、Base等
level
: 编码器的level
width
: 宽
height
: 高
aspect ratio
: 纵横比, 5:9
音频:
sample rate
: 采样率
channels
: 声道数
tkhd(Track Header Box)
包含关于媒体流的头信息,跟track中的更细化了!
一些重要的字段:
version
:tkhd box的版本;
flags
:按位或操作获得,默认值是7(0x000001 | 0x000002 | 0x000004),表示这个track是启用的、用于播放的 且 用于预览的。
- Track_enabled:值为0x000001,表示这个track是启用的,当值为0x000000,表示这个track没有启用;
- Track_in_movie:值为0x000002,表示当前track在播放时会用到;
- Track_in_preview:值为0x000004,表示当前track用于预览模式;
creation_time
:当前track的创建时间;
modification_time
:当前track的最近修改时间;
track_ID
:当前track的唯一标识,不能为0,不能重复;
duration
:当前track的完整时长(需要除以timescale得到具体秒数);
layer
:视频轨道的叠加顺序,数字越小越靠近观看者,比如1比2靠上,0比1靠上;
alternate_group
:当前track的分组ID,alternate_group值相同的track在同一个分组里面。同个分组里的track,同一时间只能有一个track处于播放状态。当alternate_group为0时,表示当前track没有跟其他track处于同个分组。一个分组里面,也可以只有一个track;
volume
:audio track的音量,介于0.0~1.0之间;
matrix
:视频的变换矩阵;
width、height
:视频的宽高;
edts(Edit Box)
它下边有一个elst(Edit List Box),它的作用是使某个track的时间戳产生偏移。
看一下一些字段:
-
segment_duration
: 表示该edit段的时长,以Movie Header Box(mvhd)中的timescale为单位,即 segment_duration/timescale = 实际时长(单位s) -
media_time
: 表示该edit段的起始时间,以track中Media Header Box(mdhd)中的timescale为单位。如果值为-1(FFFFFF),表示是空edit,一个track中最后一个edit不能为空。 -
media_rate
: edit段的速率为0的话,edit段相当于一个”dwell”,即画面停止。画面会在media_time点上停止segment_duration时间。否则这个值始终为1。
需要注意的问题:
为使PTS从0开始,media_time字段一般设置为第一个CTTS的值,计算PTS和DTS的时候,他们分别都减去media_time字段的值就可以将PTS调整为从0开始的值
如果media_time是从一个比较大的值,则表示要求PTS值大于该值时画面才进行显示,这时应该将第一个大于或等于该值的PTS设置为0,其他的PTS和DTS也相应做调整
mdia(Media Box)
定义了track媒体类型以及sample数据,描述sample信息。
它是一个container box,它必须包含mdhd,hdlr 和 minf。
mdhd(Media Header Atom Box)
mdhd 和 tkhd ,内容大致都是一样的。不过tkhd 通常是对指定的 track 设定相关属性和内容。而 mdhd 是针对于独立的 media 来设置的。
一些重要的字段:
creation_time
:当前track的创建时间;
modification_time
:当前track的最近修改时间;
timescale
: 同mvhd中的timescale,但是需要注意虽然意义相同,但是值有可能不同,下边stts,ctts等时间戳的计算都是以mdhd中的timescale
duration
: track的时间长度。
language
: 媒体语言码。最高位为0,后面15位为3个字符
hdlr(Handler Reference Box)
主要解释了媒体的播放过程信息。声明当前track的类型,以及对应的处理器(handler)。
handler_type的取值包括:
- vide(0x76 69 64 65):video track
- soun(0x73 6f 75 6e):audio track
- hint(0x68 69 6e 74):hint track
- meta(0x6d 65 74 61):Timed Metadata track
- auxv(0x61 75 78 76):Auxiliary Video track
minf(Media Information box)
解释 track 媒体数据的 handler-specific 信息,media handler用这些信息将媒体时间映射到媒体数据并进行处理。minf 同样是个 container box,其内部需要关注的内容是 stbl,这也是 moov 中最复杂的部分。
一般情况下,“minf”包含一个header box,一个“dinf”和一个“stbl”,其中,header box根据track type(即media handler type)分为“vmhd”、“smhd”、“hmhd”和“nmhd”,“dinf”为data information box,“stbl”为sample table box。
*mhd (Media Info Header Box)
可分为“vmhd”、“smhd”、“hmhd”和“nmhd”,比如视频类型则为vmhd,音频类型为smhd
- vmhd
- graphics mode:视频合成模式,为0时拷贝原始图像,否则与opcolor进行合成。
- opcolor: 一组(red,green,blue),graphics modes使用。
- smhd
- balance:立体声平衡,[8.8] 格式值,一般为0表示中间,-1.0表示全部左声道,1.0表示全部右声道。
dinf(Data Information Box)
描述了如何定位媒体信息,是一个container box。
“dinf”一般包含一个**“dref”(data reference box)**。
“dref”下会包含若干个“url”或“urn”,这些box组成一个表,用来定位track数据。简单的说,track可以被分成若干段,每一段都可以根据“url”或“urn”指向的地址来获取数据,sample描述中会用这些片段的序号将这些片段组成一个完整的track。一般情况下,当数据被完全包含在文件中时,“url”或“urn”中的定位字符串是空的。
stbl(Sample Table Box)
MP4文件的媒体数据部分在mdat box里,而stbl则包含了这些媒体数据的索引以及时间信息。在解析 stbl 前,我们需要了解chunk和sample的概念:
- Sample: video sample即为一帧或者一组连续视频帧,audio sample即为一段连续的音频。
- Chunk: 一个视频或者音频track中一个或多个sample的组合
stbl 是一个container box,其子box包括:
- stsd:sample description box,样本的描述信息。
- stts:time to sample box,sample解码时间的压缩表。
- ctts:composition time to sample box,sample的CTS与DTS的时间差的压缩表。
- stss:sync sample box,针对视频,关键帧的序号。
- stsz:sample size box,每个sample的字节大小。
- stsc:sample to chunk box,sample-chunk映射表。
- stco/co64:chunk offset box,chunk在文件中的偏移。
① stsd(Sample Description Box)
主要存储了编码类型和初始化解码器需要的信息。
这里以视频为例,包含子box:avc1
,表示是h.264的视频
data_reference_index
:利用这个索引可以检索与当前sample description关联的数据。数据引用存储在data reference box。width、height
:像素宽高。horizresolution、vertresolution
:每英寸的像素值(dpi),[16.16]格式的数据。frame_count
:每个sample中的视频帧数,默认是1。可以是一个sample中有多帧数据。depth
: 位图的深度信息,比如 0x0018(24),表示不带alpha通道的图片;
然后,avc1 box下还有一个avcc,里边会有 sps,pps 等解码必要信息。
② stts(Decoding Time to Sample Box)
包含了一个压缩版本的表,通过这个表可以从解码时间映射到sample序号。表中的每一项是连续相同的编码时间增量(Decode Delta)的个数和编码时间增量。通过把时间增量累加就可以建立一个完整的time to sample表。
重要的字段:
entry_count
:表中条目的个数。sample_count
: 连续相同时间长度的sample个数。sample_delta
:以timescale为单位的时间长度。
持续时间相同的连续的 Sample 可以放到一个 entry 里面,以达到节省空间的目的。
③ ctts(composition time to sample box)
这个box提供了decoding time到composition time的offset的表,用于计算pts。
如果一个视频只有I帧和P帧,则ctts这个表就不需要了,因为解码顺序和显示顺序是一致的,但是如果视频中存在B帧,则需要ctts!
这个表在Decoding time和composition time不一样的情况下时必须的。
如果box的version等于0,decoding time必须小于等于composition time,因而差值用一个无符号的数字表示。
有以下公式:CT(n)=DT(n)+CTTS(n)
注:CTTS(n)是未压缩的表的第n个sample对应的offset。
重要字段:
sample_count
:连续相同的offset的个数。sample_offset
:CT和DT之间的offset。
④ stss(Sync Sample Box)
它包含media中的关键帧的sample表。关键帧是为了支持随机访问。如果此表不存在,说明每一个sample都是一个关键帧。
重要字段:
sample_number
:媒体流中同步sample的序号。
⑤ stsz(Sample Size Box)
包含sample的数量和每个sample的字节大小,这个box相对来说体积比较大的。
重要字段:
sample_size
:指定默认的sample字节大小,如果所有sample的大小不一样,这个字段为0。sample_count
:track中sample的数量。entry_size
:每个sample的字节大小。
⑥ stsc(Sample To Chunk Box)
media中的sample被分为组成chunk。chunk可以有不同的大小,chunk内的sample可以有不同的大小。
通过stsc中的sample-chunk映射表可以找到包含指定sample的chunk,从而找到这个sample。结构相同的chunk可以聚集在一起形成一个entry,把一组相同结构的chunk放在一起进行管理,是为了压缩文件大小。这个entry就是stsc映射表的表项。
一些重要的字段解析:
- first_chunk:一组chunk的第一个chunk的序号。
chunk的编号从1开始。 - samples_per_chunk:每个chunk有多少个sample。
- sample_desc_idx:stsd 中sample desc信息的索引,即stsd 表项序号。
⑦ stco/co64(Chunk Offset Box)
Chunk Offset表存储了每个chunk在文件中的位置,这样就可以直接在文件中找到媒体数据,而不用解析box。
需要注意的是一旦前面的box有了任何改变,这张表都要重新建立。
重要字段:
chunk_offset
:chunk在文件中的位置。
stco 有两种形式,如果你的视频过大的话,就有可能造成 chunkoffset 超过 32bit 的限制。所以,这里针对大 Video 额外创建了一个 co64 的 Box。它的功效等价于 stco,也是用来表示 sample 在 mdat box 中的位置。只是,里面 chunk_offset 是 64bit 的。
需要注意,这里 stco 只是指定的每个 Chunk 在文件中的偏移位置,并没有给出每个 Sample 在文件中的偏移。想要获得每个 Sample 的偏移位置,需要结合 Sample Size box 和 Sample-To-Chunk 计算后取得。
udta(User Data Box)
用户自定义数据
如何计算一个sample在文件中的偏移?
注意:上边提过了这个的timesacle是mdia/mdhd中的timescale,这里等于1000000
另外也先不考虑edts中偏移的影响!
比如,想要获取时间戳在5.21s的sample在文件中的位置:
- 那么5.21s在视频时间轴上就是5.21*1000000 = 5210000,其实说白了就是微秒us
- 查表ctts,根据上边的公式,
CT(n)=DT(n)+CTTS(n)
,计算出DT,就是dts,这里是5210000-200000 = 5010000 - 查表stts,从Decoding Time to Sample表中将所有sample_count * sample_delta 累加,要<=5010000,找到这个最大的sample的num。这里是第150个,再加1,也就是第151个sample
- 查表stsc,从sample-to-chunk table查找到对应的chunk number。
151-5 = 146,所以这里对应的是第147个chunk! - 查表stco,从chunk offset 表中查找到对应chunk在文件中的起始偏移量。为2830074
- 最后查 stsz ,得到第 151 个 Sample 的 size 为 31523,因为sample_per_chunk=1,这个chunk里只有这一个sample,所以该chunk中需要读取的sample在文件中的起始偏移量 = 2830074!如果sample_per_chunk不为1,则需要手动加起来!
当然要解码,还需要找到视频的解码数据如sps、pps。
使用FFMpeg将moov前置
为了优化首帧速度,需要将 moov 移到文件前面,此时,需要对 chunk_offset 进行改写。
使用ffmpeg将moov前移很简单,在写文件前设置这个flag即可:
AVDictionary *opt = nullptr;
av_dict_set(&opt, "movflags", "faststart", AV_DICT_MULTIKEY);
以上是关于一文读懂MP4封装格式的主要内容,如果未能解决你的问题,请参考以下文章