一文读懂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文件的信息有很多方式,个人比较推荐的:

  1. https://www.onlinemp4parser.com/
  2. http://www.bento4.com/
  3. SteamAnalyzer

MP4 Box


这里就借用一个网图,上述图片来自:https://www.jianshu.com/p/529c3729f357

一个box由两部分组成:box headerbox 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:tkhdmdia

一些重要的字段:
视频:
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

  1. vmhd
  • graphics mode:视频合成模式,为0时拷贝原始图像,否则与opcolor进行合成。
  • opcolor: 一组(red,green,blue),graphics modes使用。
  1. 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包括:

  1. stsd:sample description box,样本的描述信息。
  2. stts:time to sample box,sample解码时间的压缩表。
  3. ctts:composition time to sample box,sample的CTS与DTS的时间差的压缩表。
  4. stss:sync sample box,针对视频,关键帧的序号。
  5. stsz:sample size box,每个sample的字节大小。
  6. stsc:sample to chunk box,sample-chunk映射表。
  7. 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在文件中的位置:

  1. 那么5.21s在视频时间轴上就是5.21*1000000 = 5210000,其实说白了就是微秒us
  2. 查表ctts,根据上边的公式,CT(n)=DT(n)+CTTS(n),计算出DT,就是dts,这里是5210000-200000 = 5010000
  3. 查表stts,从Decoding Time to Sample表中将所有sample_count * sample_delta 累加,要<=5010000,找到这个最大的sample的num。这里是第150个,再加1,也就是第151个sample
  4. 查表stsc,从sample-to-chunk table查找到对应的chunk number。

    151-5 = 146,所以这里对应的是第147个chunk!
  5. 查表stco,从chunk offset 表中查找到对应chunk在文件中的起始偏移量。为2830074
  6. 最后查 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封装格式的主要内容,如果未能解决你的问题,请参考以下文章

一文读懂充电宝usb接口电路及制作原理详细

Java你真的懂封装吗?一文读懂封装-----建议收藏

夯实Java基础系列23:一文读懂继承封装多态的底层实现原理

一文读懂PE格式

FastDFS 一文读懂

FastDFS 一文读懂