视频封装格式:MP4格式详解

1.MP4格式概述

1.1 简介

  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等

本文福利, 免费领取C++音视频学习资料包、技术视频/代码,内容包括(音视频开发,面试题,FFmpeg ,webRTC ,rtmp ,hls ,rtsp ,ffplay ,编解码,推拉流,srs)↓↓↓↓↓↓见下面↓↓文章底部点击免费领取↓↓

1.2 术语

  为了后面能比较规范的了解这种文件格式,这里需要了解下面几个概念和术语,这些概念和术语是理解好MP4媒体封装格式和其操作算法的关键。

(1)Box
  这个概念起源于QuickTime中的atom,也就是说MP4文件就是由一个个Box组成的,可以将其理解为一个数据块,它由Header+Data组成,Data 可以存储媒体元数据和实际的音视频码流数据。Box里面可以直接存储数据块但是也可以包含其它类型的Box,我们把这种Box又称为container box。

(2)Sample
  简单理解为采样,对于视频可以理解为一帧数据,音频一帧数据就是一段固定时间的音频数据,可以由多个Sample数据组成,简而言之:存储媒体数据的单位是sample。

(3)Track
  表示一些sample的集合,对于媒体数据而言就是一个视频序列或者音频序列,我们常说的音频轨和视频轨可以对照到这个概念上。当然除了Video Track和Audio Track还可以有非媒体数据,比如Hint Track,这种类型的Track就不包含媒体数据,可以包含一些将其他数据打包成媒体数据的指示信息或者字幕信息。简单来说:Track 就是电影中可以独立操作的媒体单位。

(4)Chunk
  一个track的连续几个sample组成的单元被称为chunk,每个chunk在文件中有一个偏移量,整个偏移量从文件头算起,在这个chunk内,sample是连续存储的。
  这样就可以理解为MP4文件里面有多个Track,一个Track又是由多个Chunk组成,每个Chunk里面包含着一组连续的Sample,正是因为定义了上述几个概念,MP4这种封装格式才容易实现灵活、高效、开放的特性,所以要仔细理解。

2.MP4整体结构

2.1 MP4结构概览

  MP4格式是一个box的格式,box容器套box子容器,box子容器再套box子容器。

一个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。
    mp4box 图示如下:

  • 其中:
  • ftyp(file type box):文件头,记录一些兼容性信息
  • moov(movie box):记录媒体信息
  • mdat(media data):媒体负载

完整的Box结构:

每个Box承载的数据内容如下:

2.2 Box结构

  mp4 封装格式采用称为 box 的结构来组织数据。结构如下:

  +-+-+-+-+-+-+-+-+-+-+
  |  header  |  body  |
  +-+-+-+-+-+-+-+-+-+-+

其它所有 box 都在语法上继承自此基本 box 结构。

2.2.1 box header

  box 分为普通 box 和 fullbox。
(1)普通 box header 结构如下:

字段

类型

描述

size

4 Bytes

包含 box header 的整个 box 的大小

type

4 Bytes

4 个 ascii 值,如果是 "uuid",则表示此 box 为用户自定义,可忽略

large size

8 Bytes

size=1 时才有的字段,用于扩展,例如 mdat box 会需要此字段

(2)fullbox 在上面的基础上新增了 2 个字段:

字段

类型

描述

version

1 Bytes

版本号

flags

3 Bytes

标识

2.2.2 box body

  一个box可能会包含其它多个box,此种box称为container box。因此box body可能是一种具体box类型,也有可能是其它box。
  虽然Box的类型非常多,大概有70多种,但是并不是都是必须的,一般的MP4文件都是含有必须的Box和个别非必须Box,我用MP4info这种工具分析了一首MP4的文件,具体的Box显示如下:

  通过上述工具分析出来的结果,我们大概可以总结出MP4以下几个特点:

  1. MP4文件就是由一个个Box组成,其中Box还可以相互嵌套,排列紧凑没有多的冗余数据;
  2. Box类型并没有很多,主要是由必须的ftyp、moov、mdat组成,还有free,udta非必须box组成即去掉这两种box对于播放音视频也没有啥影响。
  3. Moov一般存储媒体元数据,比较复杂嵌套层次比较深,后面会详细解释各个box的字段含义和组成。

2.3 ftyp(File Type Box)

  ftyp 一般出现在文件的开头,用来指示该 mp4 文件使用的标准规范:

字段

类型

描述

major_brand

4 bytes

主版本

minor_version

4 bytes

次版本

compatible_brands[]

4 bytes

指定兼容的版本,注意此字段是一个 list,即可以包含多个 4 bytes 版本号

一个示例如下:

2.4 moov(Movie Box)

  1. moov是一个container box,一个文件只有一个,其包含的所有box用于描述媒体信息(metadata)。
  2. moov的位置可以紧随着 ftyp 出现,也可以出现在文件末尾。
  3. 由于是一个 container box,所以除了 box header,其 box body 就是其它的 box。

子Box:

  • mvhd(moov header):用于简单描述一些所有媒体共享的信息。
  • trak:即 track,轨道,用于描述音频流或视频流信息,可以有多个轨道,如上出现了 2 次,分别表示一路音频和一路视频流。
  • udta(user data):用户自定义,可忽略。

一个示例如下:
(1)结构

(2)数据

(3)成分

子Box:mvhd
  用于简单描述一些所有媒体共享的信息。

子Box:trak
  track,轨道,用于描述音频流或视频流信息,可以有多个轨道,如上出现了 2 次,分别表示一路音频和一路视频流。

2.5 mvhd(Movie Header Box)

  mvhd 作为媒体信息的 header 出现(注意此header不是box header,而是moov媒体信息的header),用于描述一些所有媒体共享的基本信息。
  mvhd 语法继承自fullbox,注意下述示例出现的version和flags字段属于fullbox header。
Box Body:


2.6 trak(track)

  1. trak box 是一个 container box,其子 box 包含了该 track 的媒体信息。
  2. 一个 mp4 文件可以包含多个 track,track之间是独立的,trak box 用于描述每一路媒体流。
  3. 一般情况下有两个trak,分别对应音频流和视频流。

一个示例如下:


其中:

  • tkhd(track header box):用于简单描述该路媒体流的信息,如时长,宽度等。
  • mdia(media box):用于详细描述该路媒体流的信息
  • edts(edit Box):子Box为elst(Edit List Box),它的作用是使某个track的时间戳产生偏移。

2.7 tkhd(track header box)

  1. tkhd 作为媒体信息的header出现(注意此 header 不是 box header,而是 track 媒体信息的 header),用于描述一些该track的基本信息。
  2. tkhd语法继承自fullbox,注意下述示例出现的version和flags 字段属于fullbox header。
    Box Body:


2.8 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也相应做调整。



2.9 mdia(media box)

  1. 定义了track媒体类型以及sample数据,描述sample信息。
  2. 它是一个container box,它必须包含mdhd,hdlr 和 minf。

一个示例如下:



其中:

  • mdhd(Media Header Box):用于简单描述该路媒体流的信息。
  • hdlr(Handler Reference Box):主要定义了 track 类型。
  • stbl(Media Information Box):用于描述该路媒体流的解码相关信息和音视频位置等信息。

2.10 mdhd(Media Header Box)

  1. mdhd 作为媒体信息的header出现(注意此header不是box header,而是media媒体信息的header),用于描述一些该media的基本信息。
  2. mdhd和tkhd ,内容大致都是一样的。不过tkhd通常是对指定的track设定相关属性和内容。而mdhd 是针对于独立的media来设置的。
  3. mdhd语法继承自fullbox,注意下述示例出现的 version 和 flags 字段属于fullbox header。
    Box Body:


注:timescale同mvhd中的timescale,但是需要注意虽然意义相同,但是值有可能不同,下边stts,ctts等时间戳的计算都是以mdhd中的timescale。

2.11 hdlr(Handler Reference Box)

  1. 主要解释了媒体的播放过程信息。声明当前track的类型,以及对应的处理器(handler)。
  2. hdlr 语法继承自 fullbox,注意下述示例出现的 version 和 flags 字段属于 fullbox header。
    Box Body:

本文福利, 免费领取C++音视频学习资料包、技术视频/代码,内容包括(音视频开发,面试题,FFmpeg ,webRTC ,rtmp ,hls ,rtsp ,ffplay ,编解码,推拉流,srs)↓↓↓↓↓↓见下面↓↓文章底部点击免费领取↓↓



2.12 minf(Media Information box)

  1. 解释 track 媒体数据的 handler-specific 信息,media handler用这些信息将媒体时间映射到媒体数据并进行处理。minf 同样是个 container box,其内部需要关注的内容是 stbl,这也是 moov 中最复杂的部分。
  2. 一般情况下,“minf”包含一个header box,一个“dinf”和一个“stbl”,其中,header box根据track type(即media handler type)分为“vmhd”、“smhd”、“hmhd”和“nmhd”,“dinf”为data information box,“stbl”为sample table box。

2.13 *mhd (Media Info Header Box)

  可分为“vmhd”、“smhd”、“hmhd”和“nmhd”,比如视频类型则为vmhd,音频类型为smhd。
(1)vmhd

  • graphics mode:视频合成模式,为0时拷贝原始图像,否则与opcolor进行合成。
  • opcolor:一组(red,green,blue),graphics modes使用。
    (2)smhd
  • balance:立体声平衡,[8.8] 格式值,一般为0表示中间,-1.0表示全部左声道,1.0表示全部右声道。

2.14 dinf(Data Information Box)

  1. 描述了如何定位媒体信息,是一个container box。
  2. “dinf”一般包含一个“dref”(data reference box)
  3. “dref”下会包含若干个“url”或“urn”,这些box组成一个表,用来定位track数据。简单的说,track可以被分成若干段,每一段都可以根据“url”或“urn”指向的地址来获取数据,sample描述中会用这些片段的序号将这些片段组成一个完整的track。一般情况下,当数据被完全包含在文件中时,“url”或“urn”中的定位字符串是空的。

2.15 stbl(Sample Table Box)

  在介绍stbl box之前,需要先介绍一下mp4中定义的sample与chunk:

  • sample:ISO/IEC 14496-12 中定义 samples 之间不能共享同一个时间戳,因此,在音视频 track 中,一个 sample 代表一个视频或音频帧。
  • chunk:多个 sample 的集合,实际上音视频 track 中,chunk 与 sample 一一对应。



  stbl box是一个container box,是整个track中最重要的一个box,其子box描述了该路媒体流的解码相关信息、音视频位置信息、时间戳信息等。

  MP4文件的媒体数据部分在mdat box里,而stbl则包含了这些媒体数据的索引以及时间信息。
一个示例如下:

其中:

  • 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 在文件中的偏移。

2.16 stsd(sample description box)

  主要存储了编码类型和初始化解码器需要的信息。这里以视频为例,包含子box:avc1,表示是H264的视频。

2.16.1 h264 stsd

  对于h264视频,典型结构如下:

其上(只列出 avc1 与 avcC,其余 box 可忽略):

  • avc1,是 avc/h264/mpeg-4 part 10视频编解码格式的代称,是一个 container box,但是 box body 也携带自身的信息。
    Box Body:

avcC(AVC Video Stream Definition Box),存储 sps && pps,即在 ISO/IEC 14496-15 中定义的 AVCDecoderConfigurationRecord 结构


注:在 srs 中,解析 avcc/AVCDecoderConfigurationRecord 结构解析参见 srs/trunk/src/kernel/srs_kerner_codec.cpp::SrsFormat::avc_demux_sps_pps() 函数。

2.16.2 aac stsd

  对于 aac 音频,典型结构如下:


可以看到,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() 函数

2.17 stts(time to sample box)

  1. 存储了该 track 每个 sample 到 dts 的时间映射关系。
  2. 包含了一个压缩版本的表,通过这个表可以从解码时间映射到sample序号。表中的每一项是连续相同的编码时间增量(Decode Delta)的个数和编码时间增量。通过把时间增量累加就可以建立一个完整的time to sample表。



  这里为了节约条目的个数,采用了压缩存储的方式,即 sample_count 个连续的 sample 如果 sample_delta 时长一样,那么用一个条目就能表示了。
一个音频 track 的示例如下:

一个视频 track 的示例如下:

2.18 ctts(composition time to sample box)

  1. 存储了该 track 中,每个 sample 的 pts 与 dts 时间差(cts = pts - dts):
  2. 如果一个视频只有I帧和P帧,则ctts这个表就不需要了,因为解码顺序和显示顺序是一致的,但是如果视频中存在B帧,则需要ctts。

注意:

  • 此 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。

2.19 stss(sync sample box)

  它包含media中的关键帧的sample表。关键帧是为了支持随机访问。如果此表不存在,说明每一个sample都是一个关键帧。


一个视频示例如下:

2.20 stsc/stz2(sample to chunk box)

  存储了该 track 中每个 sample 与 chunk 的映射关系。

一个音频示例如下:

  • 第一组 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。
  • 这个并不是说,这个视频流只有3个sample,也就是只有3帧,不可能的,而是第三,第四行省略了,也就是说,第三跟第四,等等,后面的chunk 里面都只有1个sample,跟第二个chunk一样。本视频流有239个chunk。因为本视频流一共240帧,第一个chunk里面有2帧,后面的都是1帧,所以计算出来只有239个chunk。

2.21 stsz(sample size box)

  包含sample的数量和每个sample的字节大小,这个box相对来说体积比较大的。表明视频帧或者音频帧大小,FFmpeg 里面的AVPacket 的size 数据大小,就是从这个box中来的。

2.22 stco/co64(chunk offset box)

  1. Chunk Offset表存储了每个chunk在文件中的位置,这样就可以直接在文件中找到媒体数据,而不用解析box。
  2. 需要注意的是一旦前面的box有了任何改变,这张表都要重新建立。

stco 有两种形式,如果你的视频过大的话,就有可能造成 chunkoffset 超过 32bit 的限制。所以,这里针对大 Video 额外创建了一个 co64 的 Box。它的功效等价于 stco,也是用来表示 sample 在 mdat box 中的位置。只是,里面 chunk_offset 是 64bit 的。

  • 需要注意,这里 stco 只是指定的每个 Chunk 在文件中的偏移位置,并没有给出每个 Sample 在文件中的偏移。想要获得每个 Sample 的偏移位置,需要结合 Sample Size box 和 Sample-To-Chunk 计算后取得。

2.23 udta(user data box)

  用户自定义数据。

2.24 free(free space box)

  1. “free”中的内容是无关紧要的,可以被忽略。该box被删除后,不会对播放产生任何影响。
  2. Ftyp可以是free或skip。

2.25 mdat(media data box)

  1. mdat就是具体的编码后的数据。
  2. mdat 也是一个 box,拥有 box header 和 box body。
  3. mdat 可以引用外部的数据,参见 moov --> udta --> meta,这里不讨论,只讨论数据存储在本文件中的形式。
  4. 对于 box body 部分,采用一个一个 samples 的形式进行存储,即一个一个音频帧或视频帧的形式进行存储。
  5. 码流组织方式采用 avcc 格式,即 AUD + slice size + slice 的形式。

本文福利, 免费领取C++音视频学习资料包、技术视频/代码,内容包括(音视频开发,面试题,FFmpeg ,webRTC ,rtmp ,hls ,rtsp ,ffplay ,编解码,推拉流,srs)↓↓↓↓↓↓见下面↓↓文章底部点击免费领取↓↓

猜你喜欢

转载自blog.csdn.net/m0_60259116/article/details/132714706