FLV 格式分析 笔记

0 阅读8分钟

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 字节),结构如下:

字段大小 (字节)说明
Signature3固定为 'F' 'L' 'V'(0x46 0x4C 0x56)
Version1FLV 版本号,目前为 1
Flags1位掩码,第0位表示是否包含视频标签(1=有),第2位表示是否包含音频标签(4=有)
Header Length4头部总长度,通常为 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 Type18=音频,9=视频,18=脚本数据
Data Size3标签数据部分的长度(不包括头部)
Timestamp3相对时间戳(毫秒),对于视频流,低 24 位存储时间戳
Timestamp Extended1时间戳扩展字节,与前三字节组成 32 位有符号整数(实际为无符号)
Stream ID3总是 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-3SoundFormat音频编码格式(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-5SoundRate采样率(0=5.5kHz,1=11kHz,2=22kHz,3=44kHz)。对于 AAC,固定为 3(44kHz)
6SoundSize位深(0=8-bit,1=16-bit)。对于 AAC,固定为 1
7SoundType声道类型(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-3FrameType帧类型(1=关键帧,2=非关键帧,3=视频信息帧,4=保留,5=视频信息帧(仅用于 H.264))
4-7CodecID视频编码格式(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 的封装方式如下:

  1. 发送 AVC sequence header(AVCPacketType=0):

    • 数据部分为 AVCDecoderConfigurationRecord(包含 SPS 和 PPS 的完整 NAL 单元)。
    • 通常紧随 FLV 头部,作为第一个视频标签。
  2. 发送视频帧(AVCPacketType=1):

    • 视频帧数据由多个 NAL 单元组成,每个 NAL 单元前加 4 字节长度(大端序),顺序拼接。
    • 帧类型(FrameType)必须正确:关键帧(IDR 或非 IDR I 帧)标记为 1,非关键帧标记为 2。
    • 对于 B 帧,CompositionTime 用于调整显示时间。
  3. 发送 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.clibavformat/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,流程如下:

  1. 读取 FLV 头部,验证签名、版本,获取 flags 判断包含的流类型。
  2. 循环读取标签:
    • 读取 11 字节标签头,获取类型、长度、时间戳。
    • 根据类型读取标签数据。
    • 如果是音频/视频,解析头部并提取编码参数和帧数据。
    • 如果是脚本,解析 AMF 并提取元数据。
  3. 处理完所有标签即完成解封装。

9. 总结

FLV 是专为网络流媒体设计的容器,其简洁的头部 + 标签结构、无索引依赖的设计,使其成为 RTMP 传输的基础。尽管随着 Flash 退出历史舞台,FLV 在 Web 播放端不再主流,但它在直播推流、老旧系统兼容、以及某些专用领域仍不可或缺。理解 FLV 的封装细节(特别是 H.264/AAC 的 sequence header 和帧封装)对音视频开发人员而言依然重要,因为 RTMP 推流和许多嵌入式设备仍广泛使用 FLV。