FLV(Flash Video)是 Adobe Flash 平台使用的一种媒体容器格式,曾主宰互联网视频(如 YouTube、优酷等早期在线视频)。它以简单、流式友好著称,至今仍在直播和点播领域广泛使用(尤其在 RTMP 推流中)。下面从文件结构、标签解析、编解码器封装、工程实践等方面深入分析。
1. FLV 设计哲学:极简流式传输
FLV 的设计目标非常明确:在网络带宽受限的环境下,实现低延迟的音视频同步播放。因此,它采用了头部固定 + 时间戳标签的结构,每个媒体单元(音频帧、视频帧、脚本数据)都封装为一个独立标签,按时间顺序写入文件或流中。这种结构使得解封装器可以边下载边解析,无需索引表,天然适合流式传输。
2. FLV 文件整体结构
FLV 文件由一个头部和一个标签序列组成,结构如下:
FLV Header
Tag #1 (音频/视频/脚本)
Tag #2 (音频/视频/脚本)
...
Tag #n
每个标签都包含自己的长度、时间戳和类型,播放器按标签顺序读取,根据时间戳呈现即可。没有索引表,因此随机访问能力较弱(需要从头部开始扫描或构建内存索引)。
3. FLV Header 详解
FLV 头部固定 9 字节(或更长,但通常为 9 字节),结构如下:
| 字段 | 大小 (字节) | 说明 |
|---|---|---|
| Signature | 3 | 固定为 'F' 'L' 'V'(0x46 0x4C 0x56) |
| Version | 1 | FLV 版本号,目前为 1 |
| Flags | 1 | 位掩码,第0位表示是否包含视频标签(1=有),第2位表示是否包含音频标签(4=有) |
| Header Length | 4 | 头部总长度,通常为 9(后续版本可能包含扩展头部) |
示例:46 4C 56 01 05 00 00 00 09 表示 FLV 1.0,包含音频和视频(flags=0x05),头部长度 9 字节。
4. FLV 标签(Tag)结构
标签是 FLV 的核心单元,每个标签由标签头部和标签数据组成。标签紧跟在头部之后,无填充。
4.1 标签头部(Tag Header)
每个标签头部固定 11 字节:
| 字段 | 大小 (字节) | 说明 |
|---|---|---|
| Tag Type | 1 | 8=音频,9=视频,18=脚本数据 |
| Data Size | 3 | 标签数据部分的长度(不包括头部) |
| Timestamp | 3 | 相对时间戳(毫秒),对于视频流,低 24 位存储时间戳 |
| Timestamp Extended | 1 | 时间戳扩展字节,与前三字节组成 32 位有符号整数(实际为无符号) |
| Stream ID | 3 | 总是 0(FLV 设计为单流,但后续扩展可以支持多流,但实际很少用) |
时间戳计算:timestamp = (TimestampExtended << 24) | (24-bit Timestamp),范围 0~2^32-1 毫秒。
4.2 标签数据(Tag Data)
根据 Tag Type 不同,数据格式不同。
4.2.1 音频标签(Tag Type = 8)
音频标签数据格式如下(前 1 字节为音频参数,后跟音频帧数据):
| 位 | 字段 | 说明 |
|---|---|---|
| 0-3 | SoundFormat | 音频编码格式(0=Linear PCM,1=ADPCM,2=MP3,3=Linear PCM LE,4=Nellymoser 16kHz,5=Nellymoser 8kHz,6=Nellymoser,7=G.711 A-law,8=G.711 μ-law,9=Reserved,10=AAC,11=Speex,14=MP3 8kHz,15=Device-specific sound) |
| 4-5 | SoundRate | 采样率(0=5.5kHz,1=11kHz,2=22kHz,3=44kHz)。对于 AAC,固定为 3(44kHz) |
| 6 | SoundSize | 位深(0=8-bit,1=16-bit)。对于 AAC,固定为 1 |
| 7 | SoundType | 声道类型(0=单声道,1=立体声)。对于 AAC,固定为 1 |
对于 AAC(SoundFormat=10),音频数据的前 1 字节为 AACPacketType:
0:AAC sequence header(存放 AudioSpecificConfig)1:AAC raw frame(AAC 音频帧)
其他格式(如 MP3)直接存放音频帧数据。
4.2.2 视频标签(Tag Type = 9)
视频标签数据格式如下(前 1 字节为视频参数,后跟视频帧数据):
| 位 | 字段 | 说明 |
|---|---|---|
| 0-3 | FrameType | 帧类型(1=关键帧,2=非关键帧,3=视频信息帧,4=保留,5=视频信息帧(仅用于 H.264)) |
| 4-7 | CodecID | 视频编码格式(1=JPEG,2=Sorenson H.263,3=Screen video,4=On2 VP6,5=On2 VP6 with alpha,6=Screen video v2,7=AVC/H.264,8=HEVC/H.265(非标准,但某些实现支持)) |
对于 AVC(CodecID=7),视频数据的前 1 字节为 AVCPacketType:
0:AVC sequence header(存放 SPS、PPS)1:AVC NALU(一个或多个 NAL 单元,前 4 字节表示 NALU 长度)2:AVC end of sequence
紧随其后是 CompositionTime(3 字节,有符号整数),用于 B 帧的 PTS 偏移。对于普通帧(无 B 帧),该值为 0。计算公式:pts = timestamp + composition_time,其中 timestamp 来自标签头部。
AVC sequence header 通常只在流开始和参数变化时发送一次,包含 AVCDecoderConfigurationRecord(类似 MP4 的 avcC)。对于 H.265,类似处理。
4.2.3 脚本数据标签(Tag Type = 18)
脚本数据标签用于存放元数据(如时长、分辨率、编码信息)或自定义事件,使用 AMF(Action Message Format) 编码。最常见的是 onMetaData 事件,其中包含一组键值对。典型内容如下:
{
"duration": 120.0,
"width": 1920,
"height": 1080,
"videocodecid": 7, // AVC
"audiocodecid": 10, // AAC
"framerate": 25.0,
"filesize": 12345678
}
解析时需要实现 AMF0(或 AMF3)解码器。FFmpeg 内部通过 ff_amf_parse 实现。
5. FLV 与 H.264 / AAC 的封装细节
5.1 H.264 封装
对于 H.264 视频流,FLV 的封装方式如下:
-
发送 AVC sequence header(AVCPacketType=0):
- 数据部分为
AVCDecoderConfigurationRecord(包含 SPS 和 PPS 的完整 NAL 单元)。 - 通常紧随 FLV 头部,作为第一个视频标签。
- 数据部分为
-
发送视频帧(AVCPacketType=1):
- 视频帧数据由多个 NAL 单元组成,每个 NAL 单元前加 4 字节长度(大端序),顺序拼接。
- 帧类型(FrameType)必须正确:关键帧(IDR 或非 IDR I 帧)标记为 1,非关键帧标记为 2。
- 对于 B 帧,
CompositionTime用于调整显示时间。
-
发送 AVC end of sequence(AVCPacketType=2,可选)。
关键点:FLV 不直接存储 PPS/SPS 与帧数据混排的 Annex B 格式(00 00 00 01 start code),而是使用长度前缀 + 配置记录,这与 MP4 的存储方式一致。
5.2 AAC 封装
AAC 音频封装类似:
- AAC sequence header(AACPacketType=0):发送
AudioSpecificConfig(1-2 字节),包含采样率、声道数等信息。 - AAC raw frame(AACPacketType=1):发送 ADTS 帧去除 ADTS 头部后的原始 AAC 数据(即 raw AAC frame)。
6. FLV 的流式传输特性
FLV 天然支持流式传输,主要得益于:
- 无索引依赖:播放器只需按顺序解析标签,无需文件末尾的索引。
- 独立标签:每个标签包含完整的时间戳,即使丢包也能恢复后续播放。
- 头部前置:所有元数据(编码格式、参数等)均在文件开头(sequence header),播放器可快速初始化解码器。
RTMP 与 FLV:RTMP(Real-Time Messaging Protocol)的媒体流封装就是 FLV,将 FLV 标签封装在 RTMP 消息中传输。推流端(如 OBS)将音视频编码为 H.264/AAC,打包成 FLV 标签,再通过 RTMP 发送到服务器。
7. 局限性
尽管 FLV 流行多年,但它存在一些先天不足:
| 问题 | 说明 |
|---|---|
| B 帧 PTS/DTS 处理繁琐 | 通过 CompositionTime 字段修正,但并非所有解码器都支持,且容易出错。 |
| 不支持多音频/视频流 | 每个 FLV 只能包含一个音频流和一个视频流(理论上可扩展,但极少支持)。 |
| 不支持字幕、章节等 | 只能通过脚本标签传递简单信息,无标准化字幕支持。 |
| 比特率自适应弱 | 无内置多比特率切换机制(HLS 替代方案)。 |
| 时间戳精度 | 时间戳单位为毫秒,对于高帧率视频(如 120fps)精度不够,且 32 位时间戳最长约 49 天,对于长直播可能溢出(需额外处理)。 |
| 安全性 | 无内置加密或 DRM,需依赖传输层(如 RTMPS)。 |
随着 Flash 的淘汰,FLV 在 Web 端已让位给 HLS、MPEG-DASH 和 MP4。但在直播推流(RTMP)和某些嵌入式设备中,FLV 仍被广泛使用。
8. 工程实践与 FFmpeg 使用
8.1 FFmpeg 中的 FLV 支持
FFmpeg 通过 libavformat/flvdec.c 和 libavformat/flvenc.c 提供解复用和复用功能。常用命令:
# 查看 FLV 信息
ffprobe input.flv
# 将 MP4 转封装为 FLV(不重新编码)
ffmpeg -i input.mp4 -c copy -f flv output.flv
# 从 RTMP 拉流并保存为 FLV
ffmpeg -i rtmp://example.com/live/stream -c copy output.flv
# 将 FLV 推送到 RTMP 服务器
ffmpeg -i input.flv -c copy -f flv rtmp://example.com/live/stream
8.2 工具与库
- flvparser:简单的 Python/JavaScript 解析器,用于调试 FLV 结构。
- flvtool2:旧版 FLV 元数据注入工具。
- FFmpeg 自带 flv 解复用器:生产环境中最常用的实现。
8.3 自定义解析
若需自行解析 FLV,流程如下:
- 读取 FLV 头部,验证签名、版本,获取 flags 判断包含的流类型。
- 循环读取标签:
- 读取 11 字节标签头,获取类型、长度、时间戳。
- 根据类型读取标签数据。
- 如果是音频/视频,解析头部并提取编码参数和帧数据。
- 如果是脚本,解析 AMF 并提取元数据。
- 处理完所有标签即完成解封装。
9. 总结
FLV 是专为网络流媒体设计的容器,其简洁的头部 + 标签结构、无索引依赖的设计,使其成为 RTMP 传输的基础。尽管随着 Flash 退出历史舞台,FLV 在 Web 播放端不再主流,但它在直播推流、老旧系统兼容、以及某些专用领域仍不可或缺。理解 FLV 的封装细节(特别是 H.264/AAC 的 sequence header 和帧封装)对音视频开发人员而言依然重要,因为 RTMP 推流和许多嵌入式设备仍广泛使用 FLV。