百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术文章 > 正文

视频封装格式:MP4格式详解(mp4 封装格式)

zhezhongyun 2025-04-06 00:27 39 浏览

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等

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。

领音视频开发学习资料→C/C++音视频开发学习路线+资料 - 知乎

子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:

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 的形式。

    相关推荐

    Python入门学习记录之一:变量_python怎么用变量

    写这个,主要是对自己学习python知识的一个总结,也是加深自己的印象。变量(英文:variable),也叫标识符。在python中,变量的命名规则有以下三点:>变量名只能包含字母、数字和下划线...

    python变量命名规则——来自小白的总结

    python是一个动态编译类编程语言,所以程序在运行前不需要如C语言的先行编译动作,因此也只有在程序运行过程中才能发现程序的问题。基于此,python的变量就有一定的命名规范。python作为当前热门...

    Python入门学习教程:第 2 章 变量与数据类型

    2.1什么是变量?在编程中,变量就像一个存放数据的容器,它可以存储各种信息,并且这些信息可以被读取和修改。想象一下,变量就如同我们生活中的盒子,你可以把东西放进去,也可以随时拿出来看看,甚至可以换成...

    绘制学术论文中的“三线表”具体指导

    在科研过程中,大家用到最多的可能就是“三线表”。“三线表”,一般主要由三条横线构成,当然在变量名栏里也可以拆分单元格,出现更多的线。更重要的是,“三线表”也是一种数据记录规范,以“三线表”形式记录的数...

    Python基础语法知识--变量和数据类型

    学习Python中的变量和数据类型至关重要,因为它们构成了Python编程的基石。以下是帮助您了解Python中的变量和数据类型的分步指南:1.变量:变量在Python中用于存储数据值。它们充...

    一文搞懂 Python 中的所有标点符号

    反引号`无任何作用。传说Python3中它被移除是因为和单引号字符'太相似。波浪号~(按位取反符号)~被称为取反或补码运算符。它放在我们想要取反的对象前面。如果放在一个整数n...

    Python变量类型和运算符_python中变量的含义

    别再被小名词坑哭了:Python新手常犯的那些隐蔽错误,我用同事的真实bug拆给你看我记得有一次和同事张姐一起追查一个看似随机崩溃的脚本,最后发现罪魁祸首竟然是她把变量命名成了list。说实话...

    从零开始:深入剖析 Spring Boot3 中配置文件的加载顺序

    在当今的互联网软件开发领域,SpringBoot无疑是最为热门和广泛应用的框架之一。它以其强大的功能、便捷的开发体验,极大地提升了开发效率,成为众多开发者构建Web应用程序的首选。而在Spr...

    Python中下划线 ‘_’ 的用法,你知道几种

    Python中下划线()是一个有特殊含义和用途的符号,它可以用来表示以下几种情况:1在解释器中,下划线(_)表示上一个表达式的值,可以用来进行快速计算或测试。例如:>>>2+...

    解锁Shell编程:变量_shell $变量

    引言:开启Shell编程大门Shell作为用户与Linux内核之间的桥梁,为我们提供了强大的命令行交互方式。它不仅能执行简单的文件操作、进程管理,还能通过编写脚本实现复杂的自动化任务。无论是...

    一文学会Python的变量命名规则!_python的变量命名有哪些要求

    目录1.变量的命名原则3.内置函数尽量不要做变量4.删除变量和垃圾回收机制5.结语1.变量的命名原则①由英文字母、_(下划线)、或中文开头②变量名称只能由英文字母、数字、下画线或中文字所组成。③英文字...

    更可靠的Rust-语法篇-区分语句/表达式,略览if/loop/while/for

    src/main.rs://函数定义fnadd(a:i32,b:i32)->i32{a+b//末尾表达式}fnmain(){leta:i3...

    C++第五课:变量的命名规则_c++中变量的命名规则

    变量的命名不是想怎么起就怎么起的,而是有一套固定的规则的。具体规则:1.名字要合法:变量名必须是由字母、数字或下划线组成。例如:a,a1,a_1。2.开头不能是数字。例如:可以a1,但不能起1a。3....

    Rust编程-核心篇-不安全编程_rust安全性

    Unsafe的必要性Rust的所有权系统和类型系统为我们提供了强大的安全保障,但在某些情况下,我们需要突破这些限制来:与C代码交互实现底层系统编程优化性能关键代码实现某些编译器无法验证的安全操作Rus...

    探秘 Python 内存管理:背后的神奇机制

    在编程的世界里,内存管理就如同幕后的精密操控者,确保程序的高效运行。Python作为一种广泛使用的编程语言,其内存管理机制既巧妙又复杂,为开发者们提供了便利的同时,也展现了强大的底层控制能力。一、P...