这是一篇絮絮叨叨自言自语的笔记,请不要抱有过度期待,观看时请保持室内明亮
Video File Format Specification Version 10
视音频编解码学习工程:FLV 封装格式分析器
一张图看懂 FLV 文件格式
本帖示例工程里有一个 example.flv
用二进制表示xxd -b example.flv
第一个字节是01000110
,表示为 8 位 unsigned int 是 70
,在 ASCII 中代表字符 F
。
第二个字节是01000110
,表示为 8 位 unsigned int 是 76
,在 ASCII 中代表字符 L
。
第三个字节是01010110
,表示为 8 位 unsigned int 是 86
,在 ASCII 中代表字符 V
。
第四个字节是00000001
,表示为 8 位 unsigned int 是 1
,表示 FLV 格式的版本号。
第五个字节是00000101
,前五位是 00000
,在 FLV 版本 1 中是保留位没有作用,必须是 0。第六位是 1
,表示为 boolean 是 true
,代表 FLV 文件存在音频 tag。第七位是 0
,保留位,必须是 0。第八位是 1
,表示为 boolean 是 true
,代表 FLV 文件存在视频 tag。
第六到九个字节是 00000000000000000000000000001001
,表示 32 位 unsigned int 是 9
,这是 FLV header 最后一部分,代表 FLV header 一共有 9 字节。
第十到第十三个字节是 00000000000000000000000000000000
代表前一个 tag 的长度,因为没有前一个 tag ,这个部分总是 0。
第十四个字节是 00010010
,表示为 8 位 unsigned int 是 18
,代表这个 tag 的类型是 script 。
第十五到第十七个字节是 000000000000000101110100
,表示为 24 位 unsigned int 是 372
,代表这个 tag 的 data 的长度。
第十八到第二十个字节是 000000000000000000000000
,第二十一个字节是 00000000
,代表相对于第一个 tag 时间戳,单位毫秒,貌似是这一个 tag 播放的开始时间?(并没有搞明白),第一个 tag,所以是零。
第二十二到二十四个字节是 000000000000000000000000
,表示为 24 位 unsigned int 是 0
,代表 StreamID,总是 0,不是道有什么用。
第 25 到第 396 个字节是这个 tag 的 data,396 = 25 + 372 - 1。
第 397 到第 400 个字节表示为 32 位 unsigned int 是 383
,,代表前一个 tag 的 长度,因为 tag header 总是 11 个字节,所以这里 383 = 11 + 372。
第 401 个字节表示为 8 位 unsigned int 是 9
,从这里开始是第二个 tag ,9 代表这个 tag 的类型是视频。
第 412 个字节是 00010111
,前四位是 0001
,表示为十进制是 1
,代表这个 tag 的 data 是关键帧 (keyframe)。后四位是 0111
,表示为十进制是 7
,代表这个 tag 的 data 是 AVC 编码。
...
第 465 个字节表示为 8 位 unsigned int 是 8
,从这里开始是第三个 tag ,8 代表这个 tag 的类型是音频。
第 476 个字节是 10101111
,前四位是 1010
,表示为十进制是 10
,代表这个 tag 的 data 是 AAC 编码。第五位第六位是 11
,表示为十进制是 3
,代表音频采样率是 44kHz。第七位是 1
,表示音频采样精度是 16Bit。第八位是 1
,代表音频是立体声 (sndStereo)。
回到 Script tag。FLV 的 Script data 格式是 Action Message Format。
第 25 个字节是 00000010
,表示为 8 位 unsigned int 是 2
,代表第一个 packet 是 String 类型。
第 26 到第 27 个字节是 0000000000001010
,表示为 16 位 unsigned int 是 10
,代表第一个 packet 的长度是 10 字节。
第 28 到第 37 共 10 个字节表示为 8 位 unsigned int 是 [111, 110, 77, 101, 116, 97, 68, 97, 116, 97]
,在 ASCII 中代表字符 onMetaData
。
第 38 个字节表示为 8 位 unsigned int 是 8
,代表第二个 packet 是 ECMA Array 类型。
第 39 到 42 个字节表示为 8 位 unsigned int 是 16
,代表第二个 packet 包含了 16 个键值对,键是 String 类型。
第 43 到 44 个字节表示为 16 位 unsigned int 是 8
,代表键的长度。
第 45 到 52 共 8 个字节表示为 ASCII 是 duration
。代表视频时长,单位是秒。
第 53 个字节表示为 8 位 unsigned int 是 0
,代表第一个键值对的值是 DOUBLE 类型。
第 54 到第 61 个字节表示为 64 位浮点数是 2.378。ffmpeg -i example.flv -hide_banner
给出的 duration 是 00:00:02.38,mediainfo example.flv
给出的 duration 是 2 s 192 ms,为啥不一样呢?
第 62 到 63 个字节表示为 16 位 unsigned int 是 5
,代表键第二个的长度。
第 64 到 68 共 5 个字节表示为 ASCII 是 width
。代表视频分辨率宽度。
第 69 个字节表示为 8 位 unsigned int 是 0
,代表第一个键值对的值是 DOUBLE 类型。
第 70 到第 77 个字节表示为 64 位浮点数是 640。
...
第 240 到 245 共 6 个字节是第十个键名 stereo
。
第 246 个字节表示为 8 位 unsigned int 是 1
,代表第十个键值对的值是 Boolean 类型。
第 247 个字节表示为 8 位 unsigned int 是 1
,是 true
,代表 FLV 是立体声 (stereo)。
...
第 394 到 396 个字节表示为 8 位 unsigned int 是 [0, 0, 9]
,是 ECMA Array 的结束标记。到这里也是 Script data 的结尾。
安装 http-server 作为本地服务器
npm install http-server -g
http-server -a 127.0.0.1 -p 9999
浏览器打开,http://127.0.0.1:9999/index.html,打开开发者工具,点 Run 按钮,会在 console 中输出 FLV 信息。
用浏览器中用 getReader()
读取二进制数据
fetch(req).then(response => {
const reader = response.body.getReader()
reader.read().then(result => {
const chunk = result.value
parse(chunk)
})
})
console 中的输出是:Uint8Array(26192)
,文件就是 26192 字节。
ls -l example.flv
-rw-r--r--@ 1 nikoniko staff 26192 12 23 14:35 example.flv
下面的代码将 Uint8Array 转换成二进制字符串,从这里抄的 用 ArrayBuffer 来截取二进制数据中的字符,文章里有说明
Array.from(uint8Array, (item) => item.toString(2).padStart(8, '0')).join('')
FLV 文件最后 4 个字节是 PreviousTagSize,所以每个 PreviousTagSize 的值加上每个 PreviousTagSize 本身固定的 4 个字节,在加上 FLV header 固定的 9 个字节,结果应该等于文件大小
let sumPreTagSizes = chunk => {
let sum = 0
let preTagEnd = chunk.length
while (preTagEnd >= (9 + 4)) {
let preTag = chunk.slice(preTagEnd - 4, preTagEnd)
let preTagSize = new DataView(preTag.buffer).getUint32()
sum = sum + preTagSize
sum = sum + 4 // PreviousTagSize 本身占 4 字节
preTagEnd = preTagEnd - 4 - preTagSize
}
return sum
}