第2章介绍过 FFmpeg 的功能分为媒体格式转封装、音视频编解码、传输协议转换、支持 Filter 处理等。本章将重点介绍如何使用 FFmpeg 进行媒体格式的转封装,前面已介绍过 FFmpeg 支持的媒体封装格式的多样性与全面性,本章就不一一介绍了,而是着重针对常见的媒体封装格式进行详细的介绍。
本章介绍的内容主要如下。
在互联网常见的格式中,跨平台最好的应该是 MP4 文件,因为 MP4 文件既可以在 PC 平台的 Flashplayer 中播放,又可以在移动平台的 Android、iOS 等平台中进行播放,而且使用系统默认的播放器即可播放,因此我们说 MP4 格式是最常见的多媒体文件格式。
本章首先重点介绍 MP4 封装的基本格式。
MP4 格式标准为 ISO-14496 Part 12、ISO-14496 Part 14 ,标准内容并不是特别多,下面就来着重介绍一些重要的信息。
如果要了解 MP4 的格式信息,首先要清楚几个概念,具体如下。
当 size 等于 0 时,代表这个 Box 是文件的最后一个 Box。当 size 等于 1 时,说明 Box 长度需要更多的位来描述,在后面会定义一个 64 位的 largesize 用来描述 Box 的长度。当 Type 为 uuid 时,说明这个 Box 中的数据是用户自定义扩展类型
MP4 文件中 Box 的组成可以用表 3-1 所示的列表进行排列,表 3-1 中标记“√”的 Box 为必要 Box ,否则为可选 Box。
在 MP4 文件中, Box 的结构与表 3-1 所描述的一般没有太大的差别,当然,因为MP4 的标准中描述的 moov(音视频数据的metadata信息) 与 mdat(media数据容器) 的存放位置前后并没有进行强制要求,所以有些时候 moov 这个 Box 在 mdat 的后面,有些时候 moov 被存放在 mdat 的前面。在互联网的视频点播中,如果希望 MP4 文件被快速打开, 则需要将 moov 存放 mdat 的前面;如果放在后面, 则需要将 MP4 文件下载完成后才可以进行播放。
解析 MP4 多媒体文件时需要一些关键的信息,下面就来介绍一下主要的信息。
表 3-1 中已经介绍过,moov 容器定义了一个 MP4 文件中的数据信息,类型是 moov,是一个容器 Atom,其至少必须包含以下三种 Atom 中的一种:
也可以包含其他容器信息,例如影片剪辑信息 Clipping atom(clip)、一个或几个 trakAtom(trak)、一个Color Table Atom(ctab)和一个 User Data Atom(udta)。
其中, mvhd 中定义了多媒体文件的 time scale、duration 以及 display characteristics。而 trak 中定义了多媒体文件中的一个 track 的信息, track 是多媒体文件中可以独立操作的媒体单位,例如一个音频流就是一个 track、一个视频流就是一个 track。
使用二进制查看工具打开一个 MP4 文件查看其内容,可以了解前面所讲到的 MP4文件容器信息:
关于读取这个 moov 容器的方式,可以参考表 3-2。
通过解析该 moov 容器的字节长度,可以看到,该容器共包含 0x000022bb (8891)字节,容器的类型为 moov;
(以下是我测试,与正文无关)
我用xxd命令打开一个MP4文件,有一行是这个,000022bb6d6f6f760000006c6d766864,解析器是怎么知道moov 容器的大小是8891字节的?
这个问题涉及到MP4文件的格式和结构。MP4文件是由多个“box”或“atom”组成的,每个box都有一个头部,其中包含了长度和类型。我们可以使用xxd命令将二进制文件转换为十六进制表示,然后分析其内容。在你给出的例子中,“000022bb6d6f6f760000006c6d766864” 是十六进制编码。这个字符串可以分成几个部分:
“000022bb”: 这是 box 的大小。在这里,它是一个32位的整数,用16进制表示。将这个值从16进制转换为10进制,得到8891。这就是moov
box的大小,单位是字节。“6d6f6f76”: 这是 box 的类型。如果我们将每个16进制值转换为ASCII字符,就会得到’moov’,这是一个常见的MP4 box类型。
“0000006c6d766864”: 这部分是 ‘moov’ box 内部的内容,具体代表什么需要根据’moov’ box的内部结构来解析。
所以,解析器通过读取每个box的头部信息,可以知道box的大小和类型。
解析器怎么知道容器的大小是从00 0022bb获取?而不是从其他地方
解析器知道容器的大小是从"000022bb"获取,因为它遵循了MP4文件格式的规定。在MP4文件格式中,每个容器(也被称为“box”或“atom”)都以一个头部开始,该头部包含两个信息:
- 一个四字节(32位)的长度字段,这代表了整个容器(包括头部和数据)的大小。
- 一个四字节的类型字段,表示容器的类型,例如’moov’、‘trak’、'mdia’等。
所以,当解析器读取到一个MP4文件时,它会首先读取前四个字节(即8个16进制数字),并将其转换为十进制来得到容器的长度。然后,它再读取接下来的四个字节来确定容器的类型。在你给出的例子中,“000022bb”就是moov容器的长度,而“6d6f6f76”则代表了容器的类型(即’moov’)。
(测试结束)
接着继续在这个 moov 容器中往下解析,下一个容器的大小0x0000006c (108)字节,类型为 mvhd ;然后继续在 moov 容器中往下解析:
上一个容器的地址是00000028(0000006c的地址),可以定位到下一个容器的地址为00000028 + 0000006c = 00000094
分析完 mvhd 之后,从上面的输出中可以看到下一个 moov 中的容器是 trak 标签,这个 trak 容器的大小是 0x000011de ( 4574 )字节,类型是 trak。
解析完该 trak 之后,又进入到 moov 容器中解析下一个 trak ,下一个 trak 的解析方式与这个 trak 的解析方式相同,可以看到下面文件内容的 trak 的大小为 0x00001007 (4103)字节:
地址:00000094 + 000011de = 00001272
解析完这个音频的 trak 之后,接下来可以看到还有一个 moov 容器中的子容器,就是udta 容器,这个 udta 容器的解析方式与前面解析 trak 的方式基本相同,可以从下面的文件数据中看到, udta 大小为 0x00000062 ( 98 )字节:
地址:00001272 + 00001007 = 00002279
根据前面描述过的信息可以得知, udta+视频 trak+音频 trak+mvhd+moov 描述大小之后得出来的总大小,刚好为 8891 字节,与前面得出来的 moov 的大小相等。
前面描述了针对 moov 容器下面的子容器的解析,接下来继续解析 moov 子容器中的子容器。
从文件内容中可以看到, mvhd 容器的大小为 0x0000006c 字节, mvhd 的解析方式如表 3-3 所示。
按照表 3-3 所示的方式对文件数据解析出来的 mvhd 的内容所对应的信息如表 3-4 所示。
解析 mvhd 之后,可以看到下一个 track ID 为 0x00000003 ,接下来就开始解析 trak,解析 trak 的时候同样也包含了多个子容器。
trak 容器中定义了媒体文件中的 一个 track 的信息, 一个媒体文件中可以包含多个 trak ,每个 trak 是独立的,具有自己的时间和空间占用的信息,每个 trak 容器都有与它关联的 media 容器描述信息。trak 容器的主要使用目的具体如下。
hint track 和 modifier track 必须保证完整性,同时要与至少一个 media track 一起存在。
一个 trak 容器中要求必须要有一个 Track Header Atom(tkhd)、一个 Media Atom(mdia), 其他 Atom 都是可选的,例如如下的 atom 选项。
解析的方式如表 3-5 所示。
参考表 3-5 的占用情况,然后打开 MP4 文件查看文件中的二进制数据,如下:
地址:0000009c + 0000005c = 000000f8
地址:000000f8 + 00000030 = 00000128
从文件的数据内容中可以看到,这个 trak 的大小为 0x000011de ( 4574 )字节,下面的子容器的大小为 0x0000005c ( 92 )字节,这个子容器的类型为 tkhd ;跳过 92 字节后,接下来读到的 trak 子容器的大小为 0x00000030 ( 48 )字节,这个子容器的类型为 edts ;跳过 48 字节后,接下来读到的 trak 子容器的大小为 0x0000114a ( 4426 )字节,这个子容器的类型为 mdia ;通过分析可以得到 trak+tkhd+edts+mdia 子容器的大小加起来刚好为 4574字节, trak 读取完毕。
解析 tkhd 容器的方式请参考表 3-6。
下面具体看一个 tkhd 的内容,然后根据表 3-6 的内容做个信息的对应,这个 tkhd 对应的值如表 3-7 所示。
表 3-7 为解析视频 trak 容器的 tkhd ,下面再分析一个音频的 tkhd:
地址:00000094 + 000011de = 00001272
解析 trak 的方法前面已经讲过,现在重点解析音频 tkhd ,并用表格的形式将数据表示出来,具体见表 3-8。
从上述两个例子中可以看出,音频与视频的 trak 的 tkhd 大小相同,里面的内容会随着音视频 trak 类型的不同而有所不同。至此 trak 的 tkhd 解析完毕。
解析完 tkhd 之后,接下来就可以分析一下 trak 容器的子容器了。Media Atom 类型是 mdia ,其必须包含如下容器。
这个容器的解析方式如表 3-9 所示。
下面先来参考一下 MP4 文件的数据:
edts地址:0000009c + 0000005c = 000000f8
mdia地址:000000f8 + 00000030 = 00000128
mdhd地址:00000130
hdlr地址:00000130 + 00000020 = 00000150
minf地址:00000150 + 0000002d = 0000017d
vmhd地址:00000185
从文件的内容可以看到这个 mdia 容器的大小为 0x0000114a(4426) 字节, mdia 容器下面包含了三大子容器,分别为 mdhd、hdlr、minf,其中 mdhd 的大小为 0x00000020(32)字节; hdlr 大小为 0x0000002d (45)字节; minf 大小为 0x000010f5 (4341)字节;mdia 容器信息 + mdhd + hdlr + minf 容器刚好为 4426 字节;至此 mdia容器解析完毕。
mdhd 容器被包含在各个 track 中,描述 Media 的 Header ,其包含的信息如表 3-10 所示。
根据 IS014496-Partl2 标准中的描述可以知道,当版本字段为 0 时,解析与当前版本字段为 1 时的解析稍微有所不同,这里介绍的为常见的解析方式。
下面根据表格的解析方式将对应的数据解析出来:
从打开文件的内容中可以将对应的数据逐一解析出来,具体见表 3-11。
从表 3-11 可以看出这个 Media Header 的大小 32 字节,类型是 mdhd ,版本为0,生成时间与媒体修改时间都为0,计算单位时间是25 000 ,媒体时间戳长度为 250 000, 语言编码是 0x55C4 (具体代表的语言可以参考标准 ISO 639-2/T ),至此 mdhd 标签解析完毕。
hdlr 容器中描述了媒体流的播放过程,该容器中包含的内容如表 3-12 所示:
根据表 3-12 的读取方式,读取示例文件中的内容数据,数据如下:
根据文件内容看到的信息,可以将内容读取出来,对应的值如表 3-13 所示。
从表 3-13 中解析出来的对应值可以看出来,这是一个视频的 track 对应的数据,对应组件的名称为 VideoHandler 和一个 0x00 结尾, hdlr 容器解析完毕。
minf 容器中包含了很多重要的子容器,例如音视频采样等信息相关的容器, minf 容器中的信息将作为音视频数据的映射存在,其内容信息具体如下。
前面已经介绍过解析 minf 的方式,下面就来详细介绍一下解析 vmhd、smhd、dinf 以及 stbl 容器的方式。
vmhd 容器内容的格式如表 3-14 所示。
根据这个表格读取容器中的内容进行解析,其数据如下:
根据文件中的内容将数据解析出来,对应的值如表 3-15 所示。
表 3-15 所示为视频的 Header 的解析,下面就来看一下音频的 Header 的解析。
smhd 容器的格式如表 3-16 所示。
根据表 3-16 解析文件中的音频对应的数据,解析数据如下:
根据文件内容将数据解析出来之后,对应的值如表 3-17 所示。
dinf 容器是一个用于描述数据信息的容器,其定义的是音视频数据的信息,这是一个容器, 它包含子容器 dref。下面就来列举一个解析 dinf 及其子容器 dref 的例子, dref 的解析方式如表 3-18 所示。
stbl 容器又称为采样参数列表的容器(Sample Table Atom),该容器包含转化媒体时间到实际 sample 的信息,也说明了解释 sample 的信息,例如,视频数据是否需要解压缩、解压缩算法是什么等信息。其所包含的子容器具体如下。
stbl 包含 track 中 media sample 的所有时间和数据索引,利用这个容器中的 sample 信息,就可以定位 sample 的媒体时间,决定其类型、大小,以及如何在其他容器中找到紧邻的 sample。如果 Sample Table Atom 所在的 track 没有引用任何数据,那么它就不是一个有用 media track,不需要包含任何子 Atom。
如果 Sample Table Atom 所在的 track 引用了数据,那么其必须包含以下子 Atom。
所有的子表都有相同的 sample 数目。
stbl 是必不可少的一个 Atom ,而且必须包含至少一个条目,因为它包含了数据引用 Atom 检索 media sample 的目录信息。没有 sample description,就不可能计算出 media sample 存储的位置。Sync Sample Atom 是可选的,如果没有,则表明所有的 sample 都是sync sample。
edts 容器定义了创建 Movie 媒体文件中 track 的一部分媒体, 所有的 edts 数据都在一个表里,包括每一部分的时间偏移量和长度,如果没有该表,那么这个 track 就会立即开始播放,一个空的 edts 数据用来定位到 track 的起始时间偏移
位置,如表 3-19 所示。
Trak 中的 edts 数据如下:
这个 Edts Atom 的大小为 0x00000030 ( 48 )字节,类型为 edts ;其中包含了 elst 子容器, elst 子容器的大小为 0x00000028 ( 40 )字节, edts 容器+elst 子容器的大小为 48 字节,至此, edts 容器解析完毕。
至此, MP4 文件的格式解析标准已经介绍完毕,按照以上的解析方式,读者将会根据对应的解析方式解析 MP4 文件,然后读取 MP4 中的音视频数据和对应的媒体信息。由于使用二进制查看工具解析 MP4 文件需要逐字节地解析,比较耗费时间和精力,我们可以借助分析工具来进行辅助解析。接下来将介绍 MP4 文件常用的查看工具以及 FFmpegMP4 文件的支持情况。
可用来分析 MP4 封装格式的工具比较多,除了 FFmpeg 之外,还有一些常用的工具,Elecard StreamEye、mp4box、mp4info 等;下面简要介绍一下这几款常见的工具。
Elecard StreamEye 一款非常强大的视频信息查看工具,能够查看帧的排列信息,将I帧、P帧、B帧以不同颜色的柱状展现出来,而且柱的长短将根据帧的大小展示;还能够通过 Elecard StreamEye 分析 MP4 的封装的内容信息,包括流的信息、宏块的信息、文件头的信息、图像的信息以及文件的信息等;还能够根据每一帧的顺序逐帧查看,可以看到
每一帧的详细信息与状态, Elecard StreamEye 查看 MP4 的内容信息如图 3-1 所示。
Mp4box 是 GPAC 项目中的一个组件,可以通过 mp4box 针对媒体文件进行合成、拆解等操作,其操作信息大概如下:
从以上的帮助信息中可以看到, mp4box 还有很多子帮助项,例如 DASH 切片、编码、 metadata、BIFS 流、 ISMA、SWF 相关帮助信息等。下面就来使用 mp4box 分析一下 output.mp4 的信息,内容如下:
从输出的内容中可以看到,对应的解析信息如 Timescale、Duration 等,与前面介绍的 MP4 原理一节中所看到的解析的 MP4 文件所得到的数据相同。
mp4info 也是一个不错的 MP4 分析工具,而且是可视化的工具(见图 3-2 ),可以将 MP4 文件中的各 Box 解析出来,并将其中的数据展现出来,分析 MP4 文件内容时使用 mp4info 将会更方便。
如图 3-2 所示,通过 Mp4info 可以解析 MP4 文件容器,解析到 Atom 的格式可以直接展现出来,相关的 Atom 解析信息比之前逐字节读取解析相对方便、易用很多。
根据前面介绍过的查看 FFmpeg 的 MP4 文件的 Demuxer 的方法,使用命令行 ffmpeg -h demuxer=mp4
查看 MP4 文件的 Demuxer 信息:
如输出内容所示,通过查看 Fmpeg 的 help 信息,可以看到 MP4 的 Demux 与 mov、3gp、m4a、3g2、mj2 的 Demuxer 相同,解析 Mp4 文件的参数如表 3-20 所示。
在解析 MP4 文件时,通过 FFmpeg 解析时也可以通过参数 ignore_editlist 忽略EditList Atom 对 MP4 进行解析;关于 MP4 的 Demuxer 操作通常使用默认配置即可,这里将不会做过多的解释与举例说明。
3.1.3 节中提到过, MP4 与 mov 、3gp、 m4a 、3g2、 mj2 的 Demuxer 相同,它们的 Muxer 也差别不大,但是是不同的 Muxer ,尽管在 ffmpeg 中使用的都是同一套 format 进行的封装与解封装。 MP4 的封装相对解封装来说稍微复杂一些,因为要封装的时候可选数多一些,可以通过表 3-21 来了解相关的参数。
从参数的列表中可以看到, MP4 的 muxer 支持的参数比较复杂,例如支持在视频关键帧处切片 、支持设置 moov 容器大小的最大值、支持设置 encrypt 加密等。 下面就对常见的参数进行举例说明。
正常情况下 ffmpeg 生成 moov 是在 mdat 写完成之后再写入,可以通过参数 faststart 将 moov 容器移动至 mdat 的前面,下面参考一个例子:
./ffmpeg -i input.flv -c copy -f mp4 output.mp4
然后使用 mp4info 查看 output.mp4 的容器出现顺序,如图 3-3 所示。
从图 3-3 中可以看到 moov 容器是在mdat 的下面,如果使用参数 faststart 就会在生成完上述的结构之后将 moov 移动到 mdat 的前面:
./ffmpeg -i input.flv -c copy -f mp4 -movflags faststart output.mp4
然后使用 mp4info 查看 MP4 的容器顺序,可以看到 moov 被移动到了 mdat 的前面,如图 3-4 所示。
当使用生成 DASH 格式的时候,里面使用的一种特殊的 MP4 格式,可以通过 dash 参数来生成:
./ffmpeg -i input.flv -c copy -f mp4 -movflags dash output.mp4
使用 mp4info 查看容器的格式信息,稍微有些特殊,具体的信息已在前面有过详细介绍,如图 3-5 所示。
从图 3-5 可以看到,这个 DASH 格式的 MP4 文件存储的容器信息与常规的 MP4 格式有些差别,其主要以三种容器为主:sidx、moof 和 mdat。
ISMV 为微软发布的一个流媒体格式,通过参数 isml 可以发布 ISML 直播流,将 ISMV 推流至 IIS 服务器,可以通过参数 isml 进行发布:
./ffmpeg -re -i input.mp4 -c copy -movflags isml+frag_keyframe -f ismv Stream
观察 stream 的格式,大致如下:
生成的文件格式的原理类似于 HLS ,使用 XML 格式进行索引,索引内容中主要包含了音频流的关键信息,例如视频宽、高以及码率等关键信息,然后刷新切片内容进行直播。
在网络的直播与点播场景中,FLV 也是一种常见的格式,FLV 是 Adobe 发布的一种可以作为直播也可以作为点播的封装格式,其封装格式非常简单,均以 FLVTAG 的形式存在,并且每一个 TAG 都是独立存在的,接下来就来详细介绍一下 FLV 标准。