FLV文件解析

FLV(Flash Video)文件格式本质上是一个包含 Header(文件头) 和 Body(文件体) 的二进制文件。

与 RTMP 流不同,RTMP 是 Chunk 流式传输,而 FLV 是为了存储将这些 Chunk 按顺序"铺平"并加上文件头和索引信息,存放在硬盘上。

FLV 文件由三大部分组成:

  1. FLV Header(文件头)
  2. FLV Body(文件体,由无数个 Tag 组成)
  3. 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 结构:

  1. Previous Tag Size (4 bytes) : 记录前一个 Tag 的大小。注意:第一个 Tag 前面的这个值全为 0。
  2. Tag Header (11 bytes): 描述这个 Tag 的类型、时间戳和大小。
  3. Tag Data (变长): 真正的音视频数据。
Tag Header 详细解析 (11 字节)
  • TagType (1 byte) :
    • 8: Audio
    • 9: Video
    • 18: 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... 视频数据 序列参数集(某些流会周期性插入)。

关键点解析:

  1. SPS/PPS 位置 :
    • 在 FLV 中,SPS/PPS 通常存放在第一个视频 Tag 中(PacketType=0)。
    • 上面的例子展示了 PacketType=1(实际画面),但在某些推流配置(如 AnnexB 模式)下,SPS/PPS 也可能作为 Unit 跟随在 IDR 帧后面发送。标准的 FLV 容器格式倾向于将 SPS/PPS 放在 AVCDecoderConfigurationRecord (PacketType=0) 中,而不是每个 I 帧里都重复发。
  2. 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. 其他键值对(快速识别)
复制代码
键值对流程:
[键长度][键名][值类型][值]

从你的数据中可以看到清晰的模式:

  1. duration (偏移 0x30-0x3F)

  2. 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
  3. height (偏移 0x50-0x5F) - 1080像素

  4. videodatarate (偏移 0x60-0x6F) - 视频码率

  5. framerate (偏移 0x70-0x7F) - 帧率(30fps)

  6. videocodecid (偏移 0x80-0x8F) - 视频编码ID(7=H.264)

  7. audiodatarate (偏移 0x90-0x9F) - 音频码率

  8. audiosamplerate (偏移 0xA0-0xAF) - 采样率(44100Hz)

  9. audiosamplesize (偏移 0xB0-0xBF) - 采样大小(16bit)

  10. stereo (偏移 0xC0-0xC3) - 立体声(true=1)

    复制代码
    00 06 73 74 65 72 65 6f 01 01
                    ^^ ^^
                 Boolean true(1)
  11. audiocodecid (偏移 0xC4-0xD3) - 音频编码ID(10=AAC)

  12. 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类型                                              ║
╚══════════════════════════════════════════════════════════╝

总结要点

  1. FLV头固定以"FLV"开头
  2. PreviousTagSize总是跟在每个Tag后面(第一个是0)
  3. Tag Header固定11字节,包含类型、长度、时间戳
  4. Script Tag使用AMF格式,包含字符串、数组、键值对
  5. 每个键值对 = 长度键名类型值

这就是为什么你能看到 00 0a 表示长度10,然后跟着10个ASCII字符 "onMetaData",接着是 08 表示数组开始,00 00 00 15 表示21个元素。

这个Script Tag之后,文件会继续跟着Video Tag和Audio Tag,每个都有类似的Tag Header结构,但Data部分包含的是编码后的音视频数据。

相关推荐
揽秀亭长9 小时前
视频转脚本有哪些方法?5种方案技术拆解
人工智能·音视频
Android系统攻城狮11 小时前
Linux Gstreamer深度解析之gst_audio_sink_get_type调用流程与实战(六十五)
linux·运维·服务器·音视频·gstreamer音视频·音视频进阶·gstreamer音视频进阶
程序猿追12 小时前
HarmonyOS 6 音视频实战:用 AVPlayer 做一个本地音乐播放器
华为·音视频·harmonyos
weixin_6906547414 小时前
龙迅#LT9611EX 现MIPI转HDMI1.4功能主推型号,分辨率高达4K30HZ,IC内置I2C无需外部12C控制。
嵌入式硬件·音视频·信号处理
Light6017 小时前
谁在给大模型出题、卖题、判卷:一门 300 亿估值的生意,和它正在漏掉的部分
音视频
甜到心里的蛋糕18 小时前
云主机部署图片/视频管理系统的实践经验
python·音视频
可乐鸡翅yeah_18 小时前
HLS 业务 Referer‑Policy 网页元标签引发播放异常排错
前端·javascript·网络·ffmpeg·php·音视频
YEGE学AI算法18 小时前
KWS语音唤醒系统完整链路:音频前处理、Fbank、阈值与冷却逻辑
python·音视频·语音唤醒·kws·fbank
EasyDSS19 小时前
基于WebRTC的视频会议系统:私有化视频会议平台EasyDSS视频会议到底能干什么
音视频·webrtc·easydss
mont evergreen20 小时前
RTMP 连接单元测试之旅
单元测试·音视频·源码解读