FLV 封装格式详解
- FLV 封装格式详解
- 简介
- FLV 格式
- FLV header
- FLV body
- back-pointer
- FLV tag
- FLV tag header
- FLV tag data:audio tag
- FLV tag data:video tag
- FLV tag data:script tag
- 总结:FLV 层次结构
- 实例:flvAnalyser 解析 FLV 文件
- 解析 FLV 文件的 C 语言代码
- 参考
FLV 封装格式详解
简介
FLV(Flash Video)是 Adobe 公司设计开发的一种流行的流媒体格式,由于其视频文件体积轻巧、封装简单等特点,使其很适合在互联网上进行应用。此外,FLV可以使用 Flash Player 进行播放,而 Flash Player 插件已经安装在全世界绝大部分浏览器上,这使得通过网页播放 FLV 视频十分容易。目前主流的视频网站如优酷网,土豆网,乐视网等网站无一例外地使用了 FLV 格式。FLV 封装格式的文件后缀通常为“.flv”。直播场景下拉流比较常见的是 http-flv 直播流,具有延时低,易传输等特点。
FLV 格式
总体上看,FLV 由文件头(file header)和(file body)组成,其中 FLV body 由一对对的 Tag 和 Previous Tag Size 组成。
其中,Previous Tag Size 占用4个字节,记录前一个 Tag 的大小,用于逆向读取处理。
FLV header 后的第一个 Previous Tag Size 为 0。
Tag 由 Tag Header 和 Tag Body 组成。Tag Header 占 11 字节。
而 Tag Body 一般分为三种类型:脚本(Script)数据、视频数据、音频数据。其中 Video Tag 和 Audio Tag 由 Tag header 和 Tag Data 组成;Script Tag 只有 Tag Data。
FLV 数据以大端序进行存储,在解析时需要注意。
FLV 文件的详细内容结构如下:
FLV header
从上图中可以看到,FLV 头占用 9 个字节,用来标识文件类型为 FLV 类型,以及后续的音视频标识。一个 FLV 文件,每种类型的 tag 都属于一个流,也就是一个 flv 文件最多只有一个音频流,一个视频流,不存在多个独立的音视频流在一个文件的情况。FLV 头的结构如下:
上面的 UI 表示无符号整型,后面跟着的数字代表其长度是多少位;UB 表示位域,表示一个字节的多少位。可以参考C语言中的结构体位域。
所有 FLV 格式文件都以 FLV Header 开头。FLV Header 类型是 FLVHEADER,FLVHEADER 定义如下:
typedef struct {
UI8 Signature; // 'F' (0x46)
UI8 Signature; // 'L' (0x4C)
UI8 Signature; // 'V' (0x56)
UI8 Version; // FLV 版本。例如,0x01 表示 FLV 版本 1
UI8 TypeFlags; // b0 是否存在视频流,b2 是否存在音频流,其他字段保留,值为 0
UI32 DataOffset; // FLV Header 长度,单位:字节
} FLVHEADER;
在 FLV 版本 1 中,“数据偏移”字段值为 9。在 FLV 未来版本中,此字段可兼容更大尺寸的 FLV Header。
FLV body
FLV Header之后,就是 FLV File Body。FLV File Body 是由一连串的 back-pointers + tags 构成。
FLV Body 类型是 FLVBODY,FLVBODY 定义如下:
typedef struct {
UI32 PreviousTagSize0;
FLVTAG Tag1;
UI32 PreviousTagSize1;
FLVTAG Tag2;
...
UI32 PreviousTagSizeN-1;
FLVTAG TagN;
UI32 PreviousTagSizeN;
} FLVBODY;
back-pointer
back-pointer 表示 Previous Tag Size(前一个 tag 的字节数据长度),占4个字节,第一个 back-pointer 数据为 0。
FLV tag
tag 表示音视频数据,每一个 tag 由两部分组成:tag header 和 tag data。
FLV Tag 类型是 FLVTAG,FLVTAG 定义如下:
typedef struct {
UB[2] Reserved; // 用于 FMS 的保留字段, 值为 0
UB[1] Filter; // 指示 packet 是否需要预处理。0 = 不需要预处理。1 = packet 在渲染前需要预处理。未加密文件中此值为0,加密文件中此值为1。
UB[5] TagType; // 8 = 音频,9 = 视频,18 = 脚本数据
UI24 DataSize; // tag data 的长度。等于 tag 总长度 – 11
UI24 Timestamp; // 当前tag的解码时间戳 (DTS),单位是ms。第一个tag的DTS总是0
UI8 TimestampExtended; // 和Timestamp字段一起构成一个32位值, 此字段为高8位。单位是ms
UI24 StreamID; // 总为 0
IF TagType == 8
AudioTagHeader Header;
IF TagType == 9
VideoTagHeader Header;
IF TagType == 8
AUDIODATA Data;
IF TagType == 9
VIDEODATA Data;
IF TagType == 18
SCRIPTDATA Data;
} FLVTAG;
上面 Timestamp 和 TimestampExtended 两个字段拼成一个 32 位的时间戳,是当前 Tag 的解码时间戳 (DTS)。对于音频帧来说,PTS 和 DTS 相同。对于视频帧来说,若含 B 帧,则 PTS 和 DTS 不同,H264 视频帧 PTS = DTS + CTS,CTS 就是 CompositionTime 字段,表示 PTS 与 DTS 的时间偏移值,单位 ms。
FLV tag header
存放当前 tag 的类型、数据区长度、时间戳等信息。tag header 一般占 11 个字节。
FLV tag 的类型可以是视频、音频和 Script(脚本类型)。
FLV tag data:audio tag
audio tag 包含 audio tag header 和 audio tag body 两部分。
其中,audio tag header 占 1 字节,包含了音频数据的参数信息,从第 2 个字节开始为音频流数据。
前 4bit 表示音频格式;第 5、6bit 表示采样率;第 7bit 表示采样精度;第 8bit 表示音频声道。
这里着重强调一下格式10格式:AAC。声音类型应为 1 (立体声) 且采样率应为 3 (44 kHz)。这并不表示 FLV 中的 AAC 音频总是立体声、44 kHz的数据。实际上,Flash 播放器会忽略这两个值,而从已编码的 AAC 位流中提取出声道数和采样率信息。
第二个字节开始为音频数据:
如果音频格式为 10,即是 AAC 格式。AudioTagHeader 中会多出一个字节 AACPacketType,这个字段来表示 AACAUDIODATA 的类型:0 = AAC sequence header,1 = AAC raw。
- AAC sequence header 也就是包含了 AudioSpecificConfig,里面有一些更加详细音频的信息。
- AAC raw 这种包含的就是音频 ES 流了,也就是音频负载(audio payload)。
AudioTagHeader 定义如下:
typedef struct {
UB [4] SoundFormat;
UB [2] SoundRate;
UB [1] SoundSize;
UB [1] SoundType;
IF SoundFormat == 10
UI8 AACPacketType;
} AudioTagHeader;
AudioTagBody/AUDIODATA 定义如下:
typedef struct {
IF Encrypted
EncryptedBody Body
else
AudioTagBody Body;
} AudioTagBody;
AUDIODATA 包含 Body 字段。如果采用了加密,Body 的类型是 EncryptedBody,可参考规范文档“附件 F. FLV 加密”章节获得详细信息,此处略。如果未采用加密,则 Body 的类型是 AudioTagBody,下面详述。
AudioTagBody 定义如下:
typedef struct {
IF SoundFormat == 10
AACAUDIODATA SoundData;
ELSE
Varies by format
} AudioTagBody;
Flash 播放器 9.0.115.0 及以上版本支持 AAC 格式。AACAUDIODATA 定义如下:
typedef struct {
IF AACPacketType == 0
AudioSpecificConfig;
ELSE IF AACPacketType == 1
Raw AAC frame data in UI8;
} AACAUDIODATA;
FLV tag data:video tag
video tag 包含 video tag header 和 video tag body 两部分。video tag data 开始的第一个字节包含视频数据的参数信息,从第二个字节开始为视频流数据。结构如下:
第一个字节包含视频信息:前4位表示帧类型;后4位表示编码类型。
第 2 个字节开始为视频数据。
如果 CodecID = 7,视频的格式是 AVC(H.264)的话,VideoTagHeader 会多出 4 个字节的信息,AVCPacketType(1 字节) 和 CompositionTime(3 字节),所以是 H.264 编码的情况下 VideoHeader 长度是 5 个字节。
AVC VIDEO PACKET 结构如下:
VideoTagBody/VIDEODATA 定义如下:
typedef struct {
IF Encrypted
EncryptedBody Body
else
VideoTagBody Body;
} VIDEODATA;
VIDEODATA 包含 Body 字段。如果采用了加密,Body 的类型是 EncryptedBody,可参考规范文档“附件 F. FLV 加密”章节获得详细信息,此处略。如果未采用加密,则 Body 的类型是 VideoTagBody,下面详述。
VideoTagBody 包含视频帧净荷数据。VideoTagBody 定义如下:
typedef struct {
IF FrameType == 5
UI8 VideoData;
ELSE (
IF CodecID == 2
H263VIDEOPACKET VideoData;
IF CodecID == 3
SCREENVIDEOPACKET VideoData;
IF CodecID == 4
VP6FLVVIDEOPACKET VideoData;
IF CodecID == 5
VP6FLVALPHAVIDEOPACKET VideoData;
IF CodecID == 6
SCREENV2VIDEOPACKET VideoData;
IF CodecID == 7
AVCVIDEOPACKET VideoData;
)
} VideoTagBody;
AVCVIDEOPACKET 包含 AVC(H264)视频净荷数据。AVCVIDEOPACKET 定义如下:
typedef struct {
IF AVCPacketType == 0
AVCDecoderConfigurationRecord Data;
IF AVCPacketType == 1
One or more NALUs
} AVCVIDEOPACKET;
AVCDecoderConfigurationRecord 包含着 H.264 解码相关比较重要的 sps 和 pps 信息,再给 AVC 解码器送数据流之前一定要把 sps 和 pps 信息送出,否则的话解码器不能正常解码。而且在解码器 stop 之后再次 start 之前,如 seek、快进快退状态切换等,都需要重新送一遍 sps 和 pps 的信息。
AVCDecoderConfigurationRecord 在 FLV 文件中一般情况也是出现 1 次,也就是第一个 video tag。
FLV tag data:script tag
script tag 又通常被称为 metadata tag,会放一些关于FLV视频和音频的元数据信息如:duration、width、height等。通常该类型 tag 会跟在 FLV header 后面作为第一个 tag 出现,而且只有一个。
script tag 没有 script tag header,只有script tag body。一般来说,script tag 包含两个 AMF 包。结构如下所示:
第一个 AMF 包(封装字符串类型数据):
- 第 1 个字节表示 AMF 包类型,一般总是 0x02,表示字符串;
- 第 2-3 个字节为 UI16 类型值,表示字符串的长度,一般总是 0x000A(“onMetaData”长度);
- 后面字节为字符串数据,一般总为 “onMetaData”(6F,6E,4D,65,74,61,44,61,74,61)。
第二个 AMF 包(封装一个数组类型,这个数组中包含了音视频信息项的名称和值):
- 第 1 个字节表示 AMF 包类型,一般总是 0x08,表示数组。
- 第 2-5 个字节为 UI32 类型值,表示数组元素的个数。
后面即为各数组元素的封装,数组元素为元素名称和值组成的对。表示方法如下:第 1-2 个字节表示元素名称的长度,假设为 L。后面跟着为长度为 L 的字符串。第 L+3 个字节表示元素值的类型。后面跟着为对应值,占用字节数取决于值的类型。
常见的数组元素表示如下表:
AMF:Action Message Format,是 Adobe 设计的一种通用数据封装格式,在 Adobe 的很多产品中应用,简单来说,AMF 将不同类型的数据用统一的格式来描述。
👇下面是有关 script data 更详细的解释👇。
ScriptTagBody/SCRIPTDATA 定义如下:
typedef struct {
IF Encrypted
EncryptedBody Body
else
ScriptTagBody Body;
} SCRIPTDATA;
ScriptTagBody 包含以 AMF 编码的 SCRIPTDATA。AMF 是一种紧凑二进制格式,用于序列化 ActionScript 对象图。ScriptTagBody 定义如下:
typedef struct {
SCRIPTDATAVALUE Name;
SCRIPTDATAVALUE Value;
} ScriptTagBody;
这里的 Name 就是上面提到的 script tag 中的方法名,Value 是此方法的一组参数。
一个 SCRIPTDATAVALUE 记录包含一个特定类型的 ActionScript 值。
SCRIPTDATAVALUE 定义如下:
typedef struct {
UI8 Type;
IF Type == 0
DOUBLE ScriptDataValue;
IF Type == 1
UI8 ScriptDataValue;
IF Type == 2
SCRIPTDATASTRING ScriptDataValue;
IF Type == 3
SCRIPTDATAOBJECT ScriptDataValue;
IF Type == 7
UI16 ScriptDataValue;
IF Type == 8
SCRIPTDATAECMAARRAY ScriptDataValue;
IF Type == 10
SCRIPTDATASTRICTARRAY ScriptDataValue;
IF Type == 11
SCRIPTDATADATE ScriptDataValue;
IF Type == 12
SCRIPTDATALONGSTRING ScriptDataValue;
} SCRIPTDATAVALUE;
SCRIPTDATAVALUE 的两个字段,Type 是类型,ScriptDataValue 是值。Type 的值确定 ScriptDataValue 的类型。
因为 ScriptDataValue 的类型是动态的,由运行时解析得到的 Type 的值确定,所以这里类型和值用了两个字段。如果是静态类型,显然只用一个字段就可以了。
第一个 AMF(Name) 的 Type = 2,类型为 SCRIPTDATASTRING,定义如下:
typedef struct {
UI16 StringLength; // StringData 字段的长度,单位字节
STRING StringData; // 字符串实际数据,注意不带结束符 NUL
} SCRIPTDATASTRING;
第二个 AMF(Value) 的 Type = 8,类型为 SCRIPTDATAECMAARRAY,定义如下:
typedef struct {
UI32 ECMAArrayLength; // ECMA 数组元素数量
SCRIPTDATAOBJECTPROPERTY[] Variables; // 变量名和变量值的列表,即 ECMA 数组
SCRIPTDATAOBJECTEND ListTerminator; // 列表终止符
} SCRIPTDATAECMAARRAY;
其中,SCRIPTDATAOBJECTPROPERTY 类型定义了 ActionScript 对象或关联数组变量的对象属性。
SCRIPTDATAOBJECTPROPERTY 定义如下:
typedef struct {
SCRIPTDATASTRING PropertyName;
SCRIPTDATAVALUE PropertyData;
} SCRIPTDATAOBJECTPROPERTY;
常见的数组元素表示如下表:
实例:onMetaData 对象
FLV 元数据对象应在名为 onMetadata 的 SCRIPTDATA 标签中携带。各种属性对通过 NetStream.onMetaData 属性运行的 ActionScript 程序有效。可用的属性根据创建 FLV 文件的软件而有所不同。
onMetaData 标签通常会成为 FLV Body 中的第一个标签,紧跟在 FLV Header 之后。onMetaData 标签中存储的是一些视频、音频及文件相关的元数据信息:如视频帧率,音频采样率、文件长度等。
onMetaData 标签的 Name 字段主要就是存储 “onMetaData” 字符串。具体为:第 1 个字节值是 0x02,表示 Name 字段是字符串类型。第 2-3 个字节为 UI16 类型值,标识字符串的长度,值为 0x000A (“onMetaData” 这个字符串的长度)。后面跟着的数据为具体的字符串,值为 “onMetaData”。
onMetaData 标签的 Value 字段存储上表所示的各属性键值对。具体为:第 1 个字节值是 0x08,表示 Value 字段是数组类型。第 2-5 个字节为UI32类型值,表示数组元素个数。后面紧跟着数组,数组元素为属性名称和值组成的对(键值对)。最后是数组的结束符。
ScriptTagBody onMetaData;
onMetaData.Name.Type == 0x02
onMetaData.Name.ScriptDataValue.StringLength == 0x000A
onMetaData.Name.ScriptDataValue.StringData == "onMetaData"
onMetaData.Value.Type == 0x08
onMetaData.Value.ScriptDataValue.ECMAArrayLength ==
onMetaData.Value.ScriptDataValue.Variables[0].PropertyName == {0x0005, "width"} // SCRIPTDATASTRING 类型
onMetaData.Value.ScriptDataValue.Variables[0].PropertyData == {0x00, 1280.0} // SCRIPTDATAVALUE 类型
onMetaData.Value.ScriptDataValue.Variables[1].PropertyName == {0x0005, "height"} // SCRIPTDATASTRING 类型
onMetaData.Value.ScriptDataValue.Variables[1].PropertyData == {0x00, 720.0} // SCRIPTDATAVALUE 类型
...
PropertyName 的类型是 SCRIPTDATASTRING,第一个参数是字符串长度,第二个参数是字符串。
PropertyData 的类型是 SCRIPTDATAVALUE,第一个参数是 Type,为 0 时,第二个参数是一个 Double 类型的变量。
总结:FLV 层次结构
在 C 语言中定义 FLV 文件结构,一目了然:
/*
* @brief flv file header 9 bytes
*/
typedef struct flv_header {
uint8_t signature[3];
uint8_t version;
uint8_t type_flags;
uint32_t data_offset; // header size, always 9
} __attribute__((__packed__)) flv_header_t;
/*
* @brief flv tag general header 11 bytes
*/
typedef struct flv_tag {
uint8_t tag_type;
uint32_t data_size;
uint32_t timestamp;
uint8_t timestamp_ext;
uint32_t stream_id;
void *data; // will point to an audio_tag or video_tag
} flv_tag_t;
typedef struct audio_tag {
uint8_t sound_format; // 0 - raw, 1 - ADPCM, 2 - MP3, 4 - Nellymoser 16 KHz mono, 5 - Nellymoser 8 KHz mono, 10 - AAC, 11 - Speex
uint8_t sound_rate; // 0 - 5.5 KHz, 1 - 11 KHz, 2 - 22 KHz, 3 - 44 KHz
uint8_t sound_size; // 0 - 8 bit, 1 - 16 bit
uint8_t sound_type; // 0 - mono, 1 - stereo
void *data;
} audio_tag_t;
typedef struct video_tag {
uint8_t frame_type;
uint8_t codec_id;
void *data;
} video_tag_t;
typedef struct avc_video_tag {
uint8_t avc_packet_type; // 0x00 - AVC sequence header, 0x01 - AVC NALU
uint32_t composition_time;
uint32_t nalu_len;
void *data;
} avc_video_tag_t;
实例:flvAnalyser 解析 FLV 文件
flvAnalyser是一款FLV分析工具。
下载地址:https://github.com/zymill/flvAnalyser
打开一个 FLV 文件,概述如下所示:
文件结构如下所示:
先来看看 FLV header:
这里可以看到,flag中,第0位与第2位都是1,表示既有视频也有音频。
紧跟着flv header之后的是Script Data Tag,一般来讲Script Data Tag只会出现一次,用来提供音视频数据的属性和配置信息,如分辨率、帧率、码率等。这些信息在音视频数据传输过程中通常保持不变,因此通常只需要在开始时发送一次。然而,在某些特殊情况下,Script Data Tag 可能会发送多次。例如,当音视频数据的属性或配置信息发生变化时(如动态调整分辨率、帧率等),发送端可能会发送新的 Script Data Tag 来更新元数据信息。接收端在收到新的 Script Data Tag 后,可以根据其中的信息进行相应的调整。
Video tag 的 tag header中也包括负载类型和负载长度,此外还有Time stamp和Time stamp ext时间戳,tag body中则是具体的媒体信息,下图展示了一个负载类型为H.264编码的Video Tag:
Audio tag 中的信息与Video tag中几乎没有差别,下图是下图展示了一个负载类型为ADPCM编码的Audio Tag:
解析 FLV 文件的 C 语言代码
详见于:最简单的 FLV 封装格式解析程序
参考
- https://blog.csdn.net/leixiaohua1020/article/details/17934487
- https://blog.csdn.net/leixiaohua1020/article/details/50535082
- https://www.jianshu.com/p/651deb57a6b4
- https://cloud.tencent.com/developer/article/1415235
- https://zhuanlan.zhihu.com/p/654246696
- https://cloud.tencent.com/developer/article/2344723