FLV(Flash Video)文件格式本质上是一个包含 Header(文件头) 和 Body(文件体) 的二进制文件。
与 RTMP 流不同,RTMP 是 Chunk 流式传输,而 FLV 是为了存储将这些 Chunk 按顺序"铺平"并加上文件头和索引信息,存放在硬盘上。
FLV 文件由三大部分组成:
- FLV Header(文件头)
- FLV Body(文件体,由无数个 Tag 组成)
- Previous Tag Size(前置 Tag 大小,位于每个 Tag 之前,用于回读)
1. FLV Header(文件头)
FLV 文件的开头是固定的 9 字节(或扩展后的 13 字节)。
- Signature (3 bytes) : 固定为
FLV(0x46 0x4C 0x56)。 - Version (1 byte) : 通常为
1(表示 FLV 版本 1)。 - Flags (1 byte) : 标志位,说明文件里有什么。
- 第 5 位 (0x04): 是否有 Audio。
- 第 6 位 (0x01): 是否有 Video。
- Header Length (4 bytes) : 整个 Header 的长度,通常为
9(0x00 00 00 09)。
2. FLV Body(文件体)与 Tag 结构
Header 之后,就是连续的 Tag 序列。每一个 Tag 包含一段音频、一段视频或一段脚本数据。
通用 Tag 结构:
- Previous Tag Size (4 bytes) : 记录前一个 Tag 的大小。注意:第一个 Tag 前面的这个值全为 0。
- Tag Header (11 bytes): 描述这个 Tag 的类型、时间戳和大小。
- Tag Data (变长): 真正的音视频数据。
Tag Header 详细解析 (11 字节)
- TagType (1 byte) :
8: Audio9: Video18: Script Data (元数据,如时长、分辨率、宽度、高度等)
- DataSize (3 bytes): Tag Data 的长度。
- Timestamp (3 bytes): 时间戳,单位毫秒。
- TimestampExtended (1 byte): 时间戳扩展,凑成 4 字节整数。当 Timestamp 超过 0xFFFFFF 时使用。
- StreamID (3 bytes) : 总是
0。
3. 结合音视频包的详细数据示例
假设我们截取了一个 FLV 文件的中间一段,这段包含了一个 AAC 音频包 和一个 H.264 关键帧。
场景一:AAC 音频 Tag
假设我们抓取到一个音频 Tag 的数据如下:
| Hex 数据 | 含义 | 详细说明 |
|---|---|---|
00 00 00 12 |
Previous Tag Size | 前一个 Tag 大小为 18 字节。 |
08 |
Tag Type | 0x08 = Audio。 |
00 00 05 |
Data Size | 数据长度为 5 字节。 |
00 00 40 |
Timestamp | 时间戳 = 64 ms。 |
00 |
Timestamp Extended | 扩展时间 = 0。总时间 64ms。 |
00 00 00 |
Stream ID | 流 ID = 0。 |
| Tag Data 开始 | ||
AF |
Sound Format | 参考前文 RTMP 说明:AAC, 44kHz, Stereo, 16-bit。 |
00 |
AAC Packet Type | 0x00 = AudioSpecificConfig (解码器配置)。 |
11 90 |
Data | AAC 配置数据的开始(Sampling Index 等)。 |
56 E5 |
Data | 配置数据的其余部分。 |
关键点 :如果是 Raw Data(真实歌声),第二个字节会是 01,后面跟的都是压缩后的音频流。
场景二:H.264 视频关键帧
视频 Tag 通常比音频大得多。这是一个 IDR 关键帧的示例:
| Hex 数据 | 含义 | 详细说明 |
|---|---|---|
00 01 23 45 |
Previous Tag Size | 前一个 Tag 大小很大(约 74k 字节,视频数据量大)。 |
09 |
Tag Type | 0x09 = Video。 |
00 10 00 |
Data Size | 数据长度为 4096 字节(假设)。 |
00 04 00 |
Timestamp | 时间戳 = 1024 ms。 |
00 |
Timestamp Extended | 扩展时间 = 0。总时间 1024ms。 |
00 00 00 |
Stream ID | 流 ID = 0。 |
| Tag Data 开始 | ||
17 |
Frame & CodecID | 关键帧 (1) + AVC (7)。 |
01 |
AVCPacketType | 0x01 = NALU (这是真正的画面数据)。 |
00 00 1D |
CompositionTime | CTS = 29ms (DTS与PTS的差值)。 |
00 00 01 65 |
NALU Unit 1 | [长度4字节] (0x01) + [类型65] (IDR Slice)。 |
...Raw Data... |
视频数据 | 第一帧画面数据。 |
00 00 01 67 |
NALU Unit 2 | [长度4字节] (0x01) + [类型67] (SPS)。 |
...SPS Data... |
视频数据 | 序列参数集(某些流会周期性插入)。 |
关键点解析:
- SPS/PPS 位置 :
- 在 FLV 中,SPS/PPS 通常存放在第一个视频 Tag 中(PacketType=0)。
- 上面的例子展示了 PacketType=1(实际画面),但在某些推流配置(如 AnnexB 模式)下,SPS/PPS 也可能作为 Unit 跟随在 IDR 帧后面发送。标准的 FLV 容器格式倾向于将 SPS/PPS 放在
AVCDecoderConfigurationRecord(PacketType=0) 中,而不是每个 I 帧里都重复发。
- Composition Time :
- 这对于正确播放视频至关重要。因为 H.264 编码后的 DTS(解码时间戳)和 PTS(显示时间戳)通常不一致。
CompositionTime = PTS - DTS。
4. Script Tag (元数据)
FLV 文件的第一个 Tag 通常是一个 Script Tag(onMetaData),它告诉播放器这个视频的基本信息。
- Tag Type :
18 - Data Format : AMF (Action Message Format)。
- AMF 0: String "onMetaData"
- AMF 3: ECMA Array (包含键值对)
典型数据结构:
String: "onMetaData"
Object: {
"duration": 120.5, // 总时长
"width": 1920, // 宽度
"height": 1080, // 高度
"videodatarate": 2500,// 视频码率
"framerate": 30, // 帧率
"audiodatarate": 128, // 音频码率
"videosamplerate": 44100 // 音频采样率
}
5. 总结:FLV 文件的物理布局图
[ FLV Header (9 bytes) ]
|
+--- Previous Tag Size (4 bytes, value = 0)
|
+--- [ Tag 1: Script Data (onMetaData) ]
| \__ PrevTagSize (4 bytes)
|
+--- [ Tag 2: Audio Header (AAC Config) ]
| \__ PrevTagSize (4 bytes)
|
+--- [ Tag 3: Video Header (SPS/PPS) ]
| \__ PrevTagSize (4 bytes)
|
+--- [ Tag 4: Audio Frame (Raw Data) ] (Timestamp: 30ms)
| \__ PrevTagSize (4 bytes)
|
+--- [ Tag 5: Video Frame (I-Frame) ] (Timestamp: 0ms, Low Latency)
| \__ PrevTagSize (4 bytes)
|
+--- [ Tag 6: Audio Frame ... ]
|
...
核心区别总结:
- RTMP : 发送的是
Chunk。 - FLV : 保存的是
Tag。 - 实际上,RTMP 的 Chunk Header 去掉后,剩下的 Body 拼起来就是完整的 FLV Tag Payload。这也是为什么录屏软件录制 RTMP 直播流非常高效,只需要把收到的流按顺序写进文件,加上 File Header 即可。
====================================================================
我提取了一段flv文件十六进制数据如下:
00000000 46 4c 56 01 05 00 00 00 09 00 00 00 00 12 00 03 |FLV.............|
00000010 11 00 00 00 00 00 00 00 02 00 0a 6f 6e 4d 65 74 |...........onMet|
00000020 61 44 61 74 61 08 00 00 00 15 00 08 64 75 72 61 |aData.......dura|
00000030 74 69 6f 6e 00 40 4e 07 2b 02 0c 49 ba 00 05 77 |tion.@N.+..I...w|
00000040 69 64 74 68 00 40 84 00 00 00 00 00 00 00 06 68 |idth.@.........h|
00000050 65 69 67 68 74 00 40 7e 00 00 00 00 00 00 00 0d |eight.@~........|
00000060 76 69 64 65 6f 64 61 74 61 72 61 74 65 00 40 7e |videodatarate.@~|
00000070 84 80 00 00 00 00 00 09 66 72 61 6d 65 72 61 74 |........framerat|
00000080 65 00 40 3e 00 00 00 00 00 00 00 0c 76 69 64 65 |e.@>........vide|
00000090 6f 63 6f 64 65 63 69 64 00 40 1c 00 00 00 00 00 |ocodecid.@......|
000000a0 00 00 0d 61 75 64 69 6f 64 61 74 61 72 61 74 65 |...audiodatarate|
000000b0 00 40 4f 40 00 00 00 00 00 00 0f 61 75 64 69 6f |.@O@.......audio|
000000c0 73 61 6d 70 6c 65 72 61 74 65 00 40 e5 88 80 00 |samplerate.@....|
000000d0 00 00 00 00 0f 61 75 64 69 6f 73 61 6d 70 6c 65 |.....audiosample|
000000e0 73 69 7a 65 00 40 30 00 00 00 00 00 00 00 06 73 |size.@0........s|
000000f0 74 65 72 65 6f 01 01 00 0c 61 75 64 69 6f 63 6f |tereo....audioco|
00000100 64 65 63 69 64 00 40 24 00 00 00 00 00 00 00 0b |decid.@$........|
00000110 6d 61 6a 6f 72 5f 62 72 61 6e 64 02 00 04 69 73 |major_brand...is|
FLV 格式总览
FLV 文件结构 = Header + PreviousTagSize0 + Tags × N
每个 Tag 结构 = Tag Header + Tag Data + PreviousTagSize
第一部分:FLV 文件头 (0x00-0x08)
00000000 46 4c 56 01 05 00 00 00 09
|--------| |-------| |----| |-------|
签名 版本 标志位 头部长度
| 偏移 | Hex | 字段 | 二进制/ASCII | 说明 |
|---|---|---|---|---|
| 0x00 | 46 4c 56 | 签名 | "FLV" | 固定标识符 |
| 0x03 | 01 | 版本 | 1 | FLV 版本 1 |
| 0x04 | 05 | 标志位 | 00000101 | 第0位保留(0)<br>第1位保留(0)<br>第2位有音频(1)<br>第3位有视频(1)<br>第4-7位保留(0) |
| 0x05-0x08 | 00 00 00 09 | 头部长度 | 9 | 固定为9字节 |
第二部分:第一个 PreviousTagSize (0x09-0x0C)
00000000 09 00 00 00 00
|------------|
PreviousTagSize0
| 偏移 | Hex | 字段 | 值 | 说明 |
|---|---|---|---|---|
| 0x09-0x0C | 00 00 00 00 | PreviousTagSize0 | 0 | 第一个Tag前一定是0 |
第三部分:第一个 Tag - Script Tag
Tag Header (0x0D-0x17) - 共11字节
00000000 12 00 03 11 00 00 00 00 00 00 00
|------| |----------| |-------|
长度 时间戳 StreamID
| 偏移 | Hex | 字段 | 值 | 说明 |
|---|---|---|---|---|
| 0x0D | 12 | TagType | 0x12 | Script Data (脚本数据) |
| 0x0E-0x10 | 00 03 11 | DataSize | 0x000311 = 785 | Tag数据体长度 |
| 0x11-0x14 | 00 00 00 00 | Timestamp | 0 | 时间戳0,单位ms |
| 0x15-0x17 | 00 00 00 | StreamID | 0 | 总是0 |
Tag Data - onMetaData 脚本数据
从 0x18 开始是 AMF 数据:
1. AMF String "onMetaData" (0x18-0x25)
00000010 02 00 0a 6f 6e 4d 65 74 61 44 61 74 61
|---| |-----| |----------------------------|
String 长度(10) "onMetaData"(10字符)
| 偏移 | Hex | AMF类型 | 值 | 说明 |
|---|---|---|---|---|
| 0x18 | 02 | String | 字符串类型 | AMF String 类型标识 |
| 0x19-0x1A | 00 0a | Length | 10 | 字符串长度10字节 |
| 0x1B-0x25 | 6f 6e... | Value | "onMetaData" | 元数据标识符 |
2. ECMA Array 开始 (0x26-0x29)
00000020 08 00 00 00 15
|--| |----------|
ECMA数组 数组元素数(21)
| 偏移 | Hex | AMF类型 | 值 | 说明 |
|---|---|---|---|---|
| 0x26 | 08 | ECMA Array | 混合数组 | 开始键值对数组 |
| 0x27-0x2A | 00 00 00 15 | Count | 21 | 包含21个元素 |
3. 键值对示例 - duration
让我们详细解析第一个键值对:
00000020 00 08 64 75 72 61 74 69 6f 6e 00 40 4e 07 2b 02 0c 49 ba
|-----| |----------------------------| |----------------------|
键长度(8) 键名"duration" Number值(60.503秒)
| 结构 | 字节 | 解释 |
|---|---|---|
| 键名长度 | 00 08 | 8字节长的键名 |
| 键名 | 64 75 72 61 74 69 6f 6e | ASCII "duration" |
| 值类型 | 00 | AMF Number 类型 |
| 值 | 40 4e 07 2b 02 0c 49 ba | Double 浮点数 (60.503秒) |
4. 其他键值对(快速识别)
键值对流程:
[键长度][键名][值类型][值]
从你的数据中可以看到清晰的模式:
-
duration (偏移 0x30-0x3F)
-
width (偏移 0x40-0x4F) - 1920像素
00 05 77 69 64 74 68 00 40 84 00 00 00 00 00 00 ^^ ^^ ^^^^^^^^^^ ^^ ^^^^^^^^^^^^^^^^^^ 长5 "width" Number 1920.0 -
height (偏移 0x50-0x5F) - 1080像素
-
videodatarate (偏移 0x60-0x6F) - 视频码率
-
framerate (偏移 0x70-0x7F) - 帧率(30fps)
-
videocodecid (偏移 0x80-0x8F) - 视频编码ID(7=H.264)
-
audiodatarate (偏移 0x90-0x9F) - 音频码率
-
audiosamplerate (偏移 0xA0-0xAF) - 采样率(44100Hz)
-
audiosamplesize (偏移 0xB0-0xBF) - 采样大小(16bit)
-
stereo (偏移 0xC0-0xC3) - 立体声(true=1)
00 06 73 74 65 72 65 6f 01 01 ^^ ^^ Boolean true(1) -
audiocodecid (偏移 0xC4-0xD3) - 音频编码ID(10=AAC)
-
major_brand (偏移 0xD4-...) - 字符串"is"
可视化对应关系
FLV 文件结构图示:
╔══════════════════════════════════════════════════════════╗
║ FLV 头 (9字节) ║
║ 46 4c 56 01 05 00 00 00 09 ║
╠══════════════════════════════════════════════════════════╣
║ PreviousTagSize0 (4字节) ║
║ 00 00 00 00 ║
╠══════════════════════════════════════════════════════════╣
║ Tag1 Header (11字节) ║
║ 12 00 03 11 00 00 00 00 00 00 00 ║
║ │ └───┬───┘ └─────┬─────┘ └─┬─┘ ║
║ │ 数据长度785 时间戳0 流ID0 ║
║ Script Tag ║
╠══════════════════════════════════════════════════════════╣
║ Tag1 Data (785字节) ║
║ 02 00 0a 6f 6e 4d 65 74 61 44 61 74 61 08 00 00 00 15...║
║ │ │ └─────┬─────┘ │ └─────┬─────┘ ║
║ │ 长度10 "onMetaData" ECMA数组 21个键值对 ║
║ String类型 ║
╚══════════════════════════════════════════════════════════╝
总结要点
- FLV头固定以"FLV"开头
- PreviousTagSize总是跟在每个Tag后面(第一个是0)
- Tag Header固定11字节,包含类型、长度、时间戳
- Script Tag使用AMF格式,包含字符串、数组、键值对
- 每个键值对 = [长度][键名][类型][值]
这就是为什么你能看到 00 0a 表示长度10,然后跟着10个ASCII字符 "onMetaData",接着是 08 表示数组开始,00 00 00 15 表示21个元素。
这个Script Tag之后,文件会继续跟着Video Tag和Audio Tag,每个都有类似的Tag Header结构,但Data部分包含的是编码后的音视频数据。