FLV 封装格式详解

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 封装格式解析程序

参考

  1. https://blog.csdn.net/leixiaohua1020/article/details/17934487
  2. https://blog.csdn.net/leixiaohua1020/article/details/50535082
  3. https://www.jianshu.com/p/651deb57a6b4
  4. https://cloud.tencent.com/developer/article/1415235
  5. https://zhuanlan.zhihu.com/p/654246696
  6. https://cloud.tencent.com/developer/article/2344723

相关推荐

最近更新

  1. TCP协议是安全的吗?

    2024-04-10 09:44:01       16 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2024-04-10 09:44:01       16 阅读
  3. 【Python教程】压缩PDF文件大小

    2024-04-10 09:44:01       15 阅读
  4. 通过文章id递归查询所有评论(xml)

    2024-04-10 09:44:01       18 阅读

热门阅读

  1. 【蓝桥杯每日一题】4.9网络分析(代码详解版)

    2024-04-10 09:44:01       13 阅读
  2. vue 和 react 的区别

    2024-04-10 09:44:01       10 阅读
  3. 19、Lua 调试(Debug)

    2024-04-10 09:44:01       12 阅读
  4. 【御控物联】 1、物联网介绍

    2024-04-10 09:44:01       10 阅读
  5. Jeecg的Dict注解的用法

    2024-04-10 09:44:01       11 阅读
  6. SSH和交换机端口安全概述

    2024-04-10 09:44:01       10 阅读
  7. 抖音视频如何下载保存(方法分享)

    2024-04-10 09:44:01       11 阅读
  8. SQL查询:UNION用法详解

    2024-04-10 09:44:01       12 阅读
  9. 【C语言】共用体union

    2024-04-10 09:44:01       10 阅读