LV060-FLV封装简介
其实相比视频编码和传输,音视频封装应该是非常简单的知识点了。而且我们前面还学习过 RTP 打包,RTP 打包音视频数据其实一定程度上也可以算是一种封装。
音视频封装其实就是将一帧帧视频和音频数据按照对应封装的标准有组织地存放在一个文件里面,并且再存放一些额外的基础信息,比如说分辨率、采样率等信息。那到底怎么组织这些基础信息还有音视频数据呢?我们接下来先看看 FLV 是怎么做的。
一、FLV 简介
FLV(Flash Video)是 Adobe 公司推出的一种流媒体格式,它的出现有效地解决了视频文件导入 Flash 后,使导出的 SWF 文件体积庞大,不能在网络上很好的使用等缺点。由于其视频文件体积轻、封装播放简单等优点,使得其非常合适在网络上传输,目前主流的视频网站无一例外支持 FLV 流媒体格式进行视频播放。
2020 年 12 月 31 日,Chrome 作为最后一个宣布将不再支持使用 Flash 的应用程序浏览器,这给 FLV 格式带来了巨大的冲击 。因为 FLV 格式与 Flash Player 紧密相关,随着 Flash Player 逐渐退出历史舞台,FLV 视频在浏览器中的播放支持大幅减少 。除了一些特定的视频网站(如 BiliBili、优酷等可能通过其他技术手段继续支持 FLV 播放),许多网站均停止使用 FLV 作为视频格式 。这使得 FLV 格式在网页端的应用受到了极大的限制 。
随着视频技术的发展,视频消费逐渐向 HTML5 和其他现代格式转移 。FLV 格式并不被许多新设备和现代浏览器原生支持 。例如,在一些移动设备和新型浏览器中,无法直接播放 FLV 视频,需要额外的插件或转换才能实现播放 。这使得 FLV 格式在新的技术环境下,兼容性方面的劣势逐渐凸显,其应用范围也因此受到了进一步的压缩 。
二、封装格式
FLV 是一种非常常见的音视频封装,尤其是在流媒体场景中经常用到。FLV 封装也是比较简单的封装格式,它是由一个个 Tag 组成的。Tag 又分为视频 Tag、音频 Tag 和 Script Tag,分别用来存放视频数据、音频数据和 MetaData 数据。
下图就是 FLV 的总体结构图:
其总体格式图如下:
| 字段 | 字节数 | 含义 | ||
| FLV Header | Signature | 3 | 标识符, 值为 "FLV"(0x46 0x4c 0x66) | |
| Version | 1 | 版本号 | ||
| Flags | 1 | 第 6 位表示音频是不是存在, 第 8 位表示视频是不是存在, 其余为 0 | ||
| Header size | 4 | 有些地方也被描述为 Data offset,表示头部大小, 一般为 9 字节 | ||
| FLV Body | Previous Tag size | 4 | 前一个 Tag 的大小 | |
| Tag1 | - | Tag Header | Type: 1 字节, 表示 Tag 的类型, 音频是 0x08, 视频是 0x09, Script 是 0x12 | |
| Data Size : 3 字节, 表示 Tag Data 的大小 | ||||
| Timestamp : 3 字节, 时间戳 | ||||
| TimestampEx : 1 字节, 扩展时间戳, Timestamp 不够的时间使用 | ||||
| StreamId : 3 字节, 值为 0 | ||||
| Tag Data | 不同类型 Tag 的 Data 部分结构各不相同,但是 Header 的结构相同。 | |||
| Previous Tag size | 4 | 前一个 Tag 的大小 | ||
| ...... | ...... | ...... | ||
| Previous Tag size | 4 | 前一个 Tag 的大小 | ||
1. FLV Header
其中,FLV Header 占用 9 个字节。前 3 个字节是文件的标识,固定是 FLV。之后的 1 个字节表示版本。在之后的 1 个字节中的第 6 位表示是否存在音频数据,第 8 位表示是否存在视频数据,其他位都为 0。最后的 4 个字节表示从文件开头到 FLV Body 开始的长度,一般就是等于 9。
| 字段 | 字节数 | 含义 | ||
| FLV Header | Signature | 3 | 标识符, 值为 "FLV"(0x46 0x4c 0x66) | |
| Version | 1 | 版本号 | ||
| Flags | 1 | 第 6 位表示音频是不是存在, 第 8 位表示视频是不是存在, 其余为 0 | ||
| Header size | 4 | 有些地方也被描述为 Data offset,表示头部大小, 一般为 9 字节 | ||
2. FLV Body
在 FLV Header 之后就是 FLV Body 了,这就是存放主要数据的地方,放置着一个个 Tag。在每一个 Tag 前面都有一个 4 字节的 Previous Tag Size,表示前一个 Tag 的大小,方便往回倒。再之后就是具体的 Tag 了。
FLV Tag 用于承载音视频数据和元数据(Metadata)。FLV 格式包含三种类型的标签:音频标签(Audio Tag)、视频标签(Video Tag)和脚本数据标签(Script Data Tag)。
- 音频标签(Audio Tag):音频标签用于承载音频数据,如 AAC、MP3 等。音频标签包含一个音频头部(Audio Header)和一个音频负载(Audio Payload)。音频头部用于描述音频负载的编码格式、采样率、声道数等信息。
- 视频标签(Video Tag):视频标签用于承载视频数据,如 H.264、VP6 等。视频标签包含一个视频头部(Video Header)和一个视频负载(Video Payload)。视频头部用于描述视频负载的编码格式、帧类型等信息。
- 脚本数据标签(Script Data Tag):脚本数据标签用于承载元数据(Metadata),如分辨率、帧率、码率等。脚本数据标签包含一个脚本数据头部(Script Data Header)和一个脚本数据负载(Script Data Payload)。脚本数据头部用于描述脚本数据负载的类型和长度。
对于不同类型的 Tag,“Tag Header”(标签头)的格式都是相同的 。Tag Header 主要包含一些通用信息,比如 TagType(1 字节,指示本 TAG 包含的数据的类型,0x08 代表音频数据,0x09 代表视频数据,0x18 代表脚本数据)、DataSize(3 字节,指示 FLV TAG 总 Data 字段的数据长度,这个长度是不包含 tag head 的长度)、Timestamp(3 字节,指示该 tag 的时间戳,单位毫秒)、TimestampExtended(1 字节,24 位不够时,本字节作为高位,时间戳变成 32 位)、StreamID(3 字节,总是 0) 。
而 “Tag Body”(标签体)的格式则会因 Tag 类型的不同而有所差异 。需要注意的是时间戳的时间是 dts 解码时间。
2.1 Tag Header
Tag 是由 Tag Header 和 Tag Data 组成,其中 Tag Header 占用 11 个字节:
| Tag Header | Type: 1 字节, 表示 Tag 的类型, 音频是 0x08, 视频是 0x09, Script 是 0x12 |
| Data Size : 3 字节, 表示 Tag Data 的大小, FLV TAG 总 Data 字段的数据长度,这个长度是不包含 tag head 的长度 | |
| Timestamp : 3 字节, 时间戳 | |
| TimestampEx : 1 字节, 扩展时间戳, Timestamp 不够的时间使用,24 位不够时,本字节作为高位,时间戳变成 32 位 | |
| StreamId : 3 字节, 值为 0 |
2.2 Tag Data
接下来就是 Tag Data 数据了。Tag Data 有 Script、音频和视频。
2.2.1 Script Tag Data
首先来看一下 Script Tag 的 Data。这个 Tag 存放的是 MetaData 数据,主要包括宽、高、时长、采样率等基础信息。这些信息就像视频的名片,方便对视频进行分类、搜索和管理 。同时,脚本信息则可以实现一些简单的交互操作,比如在视频播放过程中触发特定的动作等 。它为视频增添了更多的信息维度和交互性,提升了用户与视频的互动体验 。
Script Data 使用 2 个 AMF 包来存放信息:
第一个 AMF 包是 onMetaData 包。第 1 个字节表示的是 AMF 包的类型,一般是字符串类型,值是 0x02,之后是 2 字节的长度,一般长度总是 10,值是 0x000A。之后就是 10 字节长度字符串了,值是 onMetaData。
第二个 AMF 包的第一个字节是数组类型,值是 0x08,紧接着 4 个字节为数组元素的个数。后面即为各数组元素的封装,数组元素为元素名称和值组成的对。常见的数组元素如下表所示:
| 元素类型 | 元素的意义 |
| duration | 时长 |
| file size | 文件大小 |
| width | 视频的宽 |
| height | 视频的高 |
| video data rate | 视频码率 |
| frame rate | 视频帧率 |
| video codec id | 视频编码类型 |
| audio sample rate | 音频采样率 |
| audio sample size | 音频的位宽 |
| audio data rate | 音频编码码率 |
| stereo | 是不是立体声 |
| audio codec id | 音频编码类型 |
音频 Tag Data 的第一个字节表示音频的编码方式、采样率和位宽等信息,如下所示。之后就是音频数据了。
| 字段 | 占用大小 | 说明 |
| 音频编码类型 | 4bits | 2 表示 mp3、10 表示 aac |
| 采样率 | 2bits | 0 表示 5.5kHz、1 表示 11kHz、2 表示 22kHz、3 表示 44kHz |
| 位宽 | 1bit | 0 表示 8 位、1 表示 16 位 |
| 声道数 | 1bit | 0 表示单声道、1 表示双声道 |
第二个字节开始为音频数据。
| 字段 | 占用大小 | 说明 |
| 音频数据 | n 字节 | AudioTagBody 存储的数据根据当前格式不同而不同。如果是 PCM 线性数据,存储的时候每个按 16bit 小端存储,有符号。如果是 AAC,则存储的数据是 AAC AUDIO DATA,否则为线性数组 |
2.2.3 Video Tag Data
视频 Tag 的第 1 个字节包含了这个 Tag 的视频帧类型和视频编码方式,格式如下图:
| 字段 | 占用大小 | 说明 |
| 视频帧类型 | 4bits | 1 表示关键帧、2 表示非关键帧 |
| 视频编码类型 | 4bits | 7 表示 H264(AVC) |
对于 H264 数据,紧接着会有 4 字节的 AVC Packet Type 格式,如下图所示:
| 字段 | 占用大小 | 说明 |
| AVC 包类型 | 1 字节 | 0 表示序列头(包含 SPS、PPS)、1 表示 NALU |
| CTS | 3 字节 | 如果包类型是 0 则为 0。如果包类型是 1 则表示 cts,cts = pts-dts |
pts:显示时间,也就是接收方在显示器显示这帧的时间。单位为 1/90000 秒。
dts:解码时间,也就是 rtp 包中传输的时间戳,表明解码的顺序。单位单位为 1/90000 秒。——根据后面的理解,pts 就是标准中的 CompositionTime
cts 偏移:cts = (pts - dts) / 90 。cts 的单位是毫秒。
pts 和 dts 的时间不一样,应该只出现在含有 B 帧的情况下,也就是 profile main 以上。baseline 是没有这个问题的,baseline 的 pts 和 dts 一直相同,所以 cts 一直为 0。因为 H264 有 B 帧这种类型,涉及到显示时间戳 PTS 和解码时间戳 DTS。前面 Tag Header 里的时间戳就是 DTS,PTS 等于 DTS + CTS,这个需要注意一下。接下来就是存放具体的视频数据。
如果 AVC 包类型是 0,则数据格式如下图所示:
| 字段 | 占用大小 | 说明 |
| 版本号 | 1字节 | 版本号为1 |
| AVC Profile Indication | 1字节 | H264 Profile |
| Profile Compatibility | 1字节 | |
| AVC Level Indication | 1字节 | H264 Level |
| Length Size Minus One | 1字节 | |
| SPS的个数 | 1字节 | |
| SPS的大小 | 2字节 | |
| SPS | 具体大小前面计算得到 | |
| PPS的个数 | 1字节 | |
| PPS的大小 | 2字节 | |
| PPS | 具体大小前面计算得到 |
如果 AVC 包类型为 1,则数据格式如下图所示:
| 字段 | 占用大小 |
| NALU 长度 | 4 字节 |
| NALU 数据 | 具体大小由前面计算得到 |
参考资料:
封装格式之 FLV 格式 — MyNote release 文档
(5 封私信) 音视频基础:FLV 封装格式介绍及解析 - 知乎