金山云多媒体 SDK 团队在移动直播、短视频等项目中遇到了许多 FFmpeg 问题,特设立《 FFmpeg 从入门到出家》系列文稿,希望博君一笑的同时,能让大家对 FFmpeg 有更深入的了解。
FLV(FLASH VIDEO),是一种常用的文件封装格式,目前国内外大部分视频分享网站都是采用的这种格式。其标准定义为《 Adobe Flash Video File Format Specification 》。RTMP 协议也是基于 FLV 视频格式的。
FLV 的文件格式在该规范中已阐述清楚,本章节不再重复描述,而是结合下面的示例具体阐述如何分析 FLV 文件。
FLV 文件的分析工具有很多,这里给大家推荐 FLV Parser 这个小软件,通过它可以很容易的看到文件的组成结构。
3.1 文件结构
从整个文件上看,FLV 是由 Header 和 File Body 组成,如下图所示:
1.FLV Header - 长度为 9,其结构的标准定义参见标准定义见 E.2 The FLV header ;
以图 3. FLV 文件结构示例 1 为例分析整体结构:
1.位置 0x00000000 - 0x00000008, 共 9 个字节,为 FLV Header,其中:
◦0x00000000 - 0x00000002 : 0x46 0x4C 0x56 分别表示字符'F''L''V',用来标识这个文件是 FLV 格式的。在做格式探测的时候,如果发现前 3 个字节为“ FLV ”,就认为它是 FLV 文件;
◦0x00000003 : 0x01, 表示 FLV 版本号;
◦0x00000004 : 0x05, 转换为 2 进制是 0000 0101,其中第 0 位为 1,表示存在 video,第 2 位为 1,表示存在 audio ;
◦0x00000005 - 0x00000008 : 0x00 0x00 0x00 0x09,转十进制为 9,表示 FLV header 的长度,当 FLV 版本号为 1 时,该值通常为 9。
2.位置 0x00000009 - ,为 FLV File Body:
◦0x00000009 - 0x0000000C : 0x00 0x00 0x00 0x00 PreviousTagSize0,转十进制为 0,该值永远为 0 ;
◦0x0000000D - 0x00000209 : 0x12 ... 0x09,共 509 个字节,为 Tag1 的具体内容;
◦0x0000020A - 0x0000020D : 0x00 0x00 0x01 0xFD,转十进制为 509,表示它前面的 Tag,即 Tag1 的长度为 509 ;
◦0x0000020E - :按照 Tag + PreviousTagSize 的结构依次递推,此处不再举例说明。
3.2 Tag 定义
FLV File Body 是由一系列的 PreviousTagSize + Tag 组成,其中 PreviousTagSize 的长度为 4 个字节,用来表示前一个 Tag 的长度; Tag 里面的数据可能是 video、audio 或者 scripts,其定义参见 E.4.1 FLV Tag,结构如下:
以图 3. FLV 文件结构示例 1 为例分析 Tag 结构:
1.位置 0x0000020E : 0x08, 二进制为 0000 1000,第 5 位为 0, 表示为非加扰文件;低 5 位 01000 为 8,说明这个 Tag 包含的数据类型为 Audio ;
位置 0x0000020F - 0x00000211 : 0x00 0x00 0x04,转十进制为 4,说明 Tag 的内容长度为 4,与该 tag 后面的 previousTagSize(15) - 11 相同;
位置 0x00000212 - 0x00000214 : 0x00 0x00 0x00,转十进制为 0,说明当前 Audio 数据的时间戳为 0 ;
5.位置 0x00000215 : 0x00,扩展时间戳为 0,如果扩展时间戳不为 0,那么该 Tag 的时间戳应为:Timestamp | TimestampExtended<<24 ;
位置 0x00000216 - 0x00000218 : 0x00 0x00 0x00,StreamID,总是 0 ;
StreamID 之后的数据每种格式的情况都不一样,下面会依次进行详细解读。
3.3 Audio Tags
如果 TAG 包中的 TagType 等于 8,表示该 Tag 中包含的数据类型为 Audio。StreamID 之后的数据就是 AudioTagHeader,其定义详见 E.4.2.1 AUDIODATA。结构如下:
图 7. FLV Audio Tag 结构 需要说明的是,通常情况下 AudioTagHeader 之后跟着的就是 AUDIODATA 数据了,但有个特例,如果音频编码格式为 AAC,AudioTagHeader 中会多出 1 个字节的数据 AACPacketType,这个字段来表示 AACAUDIODATA 的类型:
• 0 = AAC sequence header
• 1 = AAC raw。
以图 3. FLV 文件结构示例为例分析 AudioTag 结构:
◦高 4 位为 1010,转十进制为 10,表示 Audio 的编码格式为 AAC ;
◦第 3、2 位为 11,转十进制为 3,表示该音频的采样率为 44KHZ ;
◦第 1 位为 1,表示该音频采样点位宽为 16bits ;
◦第 0 位为 1,表示该音频为立体声。
位置 0x0000021A : 0x00,十进制为 0,并且 Audio 的编码格式为 AAC,说明 AACAUDIODATA 中存放的是 AAC sequence header ;
位置 0x0000021B - 0x0000021C : AUDIODATA 数据,即 AAC sequence header。
3.3.1 AudioSpecificConfig
AAC sequence header 中存放的是 AudioSpecificConfig,该结构包含了更加详细的音频信息,《 ISO-14496-3 Audio 》中的 1.6.2.1 章节对此作了详细定义。
通常情况下,AAC sequence header 这种 Tag 在 FLV 文件中只出现 1 次,并且是第一个 Audio Tag,它存放了解码 AAC 音频所需要的详细信息。
有关 AudioSpecificConfig 结构的代码解析,可以参考 ffmpeg/libavcodec/mpeg4audio.c 中的 avpriv_mpeg4audio_get_config 方法。
为什么 AudioTagHeader 中定义了音频的相关参数,我们还需要传递 AudioSpecificConfig 呢?
因为当 SoundFormat 为 AAC 时,SoundType 须设置为 1 (立体声),SoundRate 须设置为 3 ( 44KHZ ),但这并不意味着 FLV 文件中 AAC 编码的音频必须是 44KHZ 的立体声。播放器在播放 AAC 音频时,应忽略 AudioTagHeader 中的参数,并根据 AudioSpecificConfig 来配置正确的解码参数。
3.4 Video Tag
如果 TAG 包中的 TagType 等于 9,表示该 Tag 中包含的数据类型为 Video。StreamID 之后的数据就是 VideoTagHeader,其定义详见 E.4.3.1 VIDEODATA,结构如下:
图 8. FLV Video Tag 结构 VideoTagHeader 之后跟着的就是 VIDEODATA 数据了,但是和 AAC 音频一样,它也存在一个特例,就是当视频编码格式为 H.264 的时候,VideoTagHeader 会多出 4 个字节的信息,AVCPacketType 和 CompositionTime。
• AVCPacketType 用来表示 VIDEODATA 的内容
• CompositonTime 相对时间戳,如果 AVCPacketType=0x01,为相对时间戳,其它均为 0 ;
以图 4. FLV 文件结构示例 2 为例分析 VideoTagHeader 结构:
◦高 4 位为 0001,转十进制为 1,表示当前帧为关键帧;
◦低 4 位为 0111,转十进制为 7,说明当前视频的编码格式为 AVC。
位置 0x0000022D : 0x00,十进制为 0,并且 Video 的编码格式为 AVC,说明 VideoTagBody 中存放的是 AVC sequence header ;
位置 0x0000022E - 0x00000230 : 转十进制为 0,表示相对时间戳为 0 ;
位置 0x00000231 - 0x0000021C : VIDEODATA 数据,即 AVC sequence header。
3.4.1 AVCDecoderConfigurationRecord
AVC sequence header 中存放的是 AVCDecoderConfigurationRecord,《 ISO-14496-15 AVC file format 》对此作了详细定义。它存放的是 AVC 的编码参数,解码时需设置给解码器后方可正确解码。
通常情况下,AVC sequence header 这种 Tag 在 FLV 文件中只出现 1 次,并且第一个 Video Tag。
有关 AVCDecoderConfigurationRecord 结构的代码解析,可以参考中的 ff_isom_write_avcc 方法。
3.4.2 CompositionTime(相对时间戳)
相对时间戳的概念需要和 PTS、DTS 一起理解:
DTS : Decode Time Stamp,解码时间戳,用于告知解码器该视频帧的解码时间;
PTS : Presentation Time Stamp,显示时间戳,用于告知播放器该视频帧的显示时间;
CTS : Composition Time Stamp,相对时间戳,用来表示 PTS 与 DTS 的差值。
如果视频里各帧的编码是按输入顺序依次进行的,则解码和显示时间相同,应该是一致的。但在编码后的视频类型中,如果存在 B 帧,输入顺序和编码顺序并不一致,所以才需要 PTS 和 DTS 这两种时间戳。视频帧的解码一定是发生在显示前,所以视频帧的 PTS,一定是大于等于 DTS 的,因此 CTS=PTS-DTS。
FLV Video Tag 中的 TimeStamp,不是 PTS,而是 DTS,视频帧的 PTS 需要我们通过 DTS + CTS 计算得到。
为什么 Audio Tag 不需要 CompositionTime 呢?
因为 Audio 的编码顺序和输入顺序一致,即 PTS=DTS,所以它没有 CompositionTime 的概念。
3.5 Script Data Tags
如果 TAG 包中的 TagType 等于 18,表示该 Tag 中包含的数据类型为 SCRIPT。
SCRIPTDATA 结构十分复杂,定义了很多格式类型,每个类型对应一种结构,详细可参考 E.4.4 Data Tags
onMetaData 是 SCRIPTDATA 中一个非常重要的信息,其结构定义可参考 E.5 onMetaData。它通常是 FLV 文件中的第一个 Tag,用来表示当前文件的一些基本信息: 比如视音频的编码类型 id、视频的宽和高、文件大小、视频长度、创建日期等。