a比特: 1:有音频; 0:无音频
v比特: 1:有视频; 0:无视频 如果音视频都有这个字节为0x05
‘F’|‘L’|‘V’|0x01|0b00000a0v| 0x00 00 00 09|
版本字节一般为0x01 头长度 固定为9
FLV Body由一系列的Tag组成,每个tag的结构如下:
{ 0x00000000| tag_header0| tag_data0 } | {tag_size0| tag_header1 | tag_data1 } | {tag_size1| tag_header2 | tag_data2 } |…
tag_size(n) = tag_data_size(n-1) + 11
解析完一个tag后,读取下4个字节,可以比较下看是不是前帧完全解码完毕。
除了可以验证码流外,还有什么样的好处促使FLV格式采用这种方式呢?不太清楚。
0x00000000| { tag_header0| tag_data0 | tag_size0} | { tag_header1 | tag_data1 | tag_size1} |...
Tag body也可以这样看,编码时按照: 数据头 数据体 数据大小 三部分来划分,更适合一些。下面各个tag都是按照这个结构来写的。
Tag header 11Bytes
|1Byte Tag类型 | 只支持3类, 0x08音频; 0x09视频;0x12脚本
|3Byte data_size数据区长度 | 纯数据长度,不包括头信息的15字节
|3Byte timestamp 时间戳 | 最终的时间戳 = (timestamp_ex<< 24) | timestamp
|1Byte timestamp_ex扩展时间戳 | 最终时间戳的高8位。不知道为什么定义成这样,可能是因为标准扩展
|3Byte StreamID | 总为0
Tag Data data_size字节
|Tag数据段,长度为data_size… | 音视频或者脚本数据。
|4Byte PreviousTagSize | 是前一个Tag数据长度,第一个Tag此数据段为0.
视频Tag
视频数据的第一个字节定义视频信息:
高4比特定义帧格式:1 关键帧; 2 内部帧(非IDR帧);3可丢弃内部帧(仅对H.263有用)
低4比特定义编码器:2:H.263, 4: VP6, 7:H264; …
对于264的视频FLV,这个字节一般为 0x17或者0x27.
音频tag
音频数据的第一个字节定义视频信息:
高4比特定义音频编码格式:0 Linear PCM, 1: ADPCM, 2: MP3, 10:AAC …
第4,3两比特定义采样率: 0 5.5KHz, 1 11KHz, 2 22KHz, 3 44KHz. 对于AAC该值总为3
第2比特定义采样大小: 0 8比特,1 16比特
第1比特定义声道数: 0 单声道,1 立体声
脚本tag
理论上不需要解析脚本Tag就可以解码FLV文件,它只是提供了一些信息记录。
脚本Tag里面的类型比较多,格式也不一样。
Number 类,记录一个8字节double数据。|0x00|8字节data|.
Boolean类,记录一个1字节布尔型数据。|0x01|1字节data|.
String类,记录一个变长字符串数据。 |0x02|2字节 字符串长度 N| N字节字符串内容|.
注意读取完字符串后加一个’\0’字符串结束符号。因此字符串申请的时候,长度为N+1.
ECMA array type, 记录一些数据对。
|0x08| 4字节 数组长度 N|2字节 字符串长度 m1 | m1字节字符串内容|1字节 data1 type | X字节 data1 | ...
|2字节 字符串长度 Nm| Nm字节字符串内容|1字节 dataN type| X字节 dataN|
有N个数据对 元素名 (长度|数据) 元素值(类型|数据)
一般用这个记录一些音视频信息,例如:
0x0008 "duration" 0x00 8字节double
0x0005 "width" 0x00 8字节double
0x0006 "stereo" 0x01 1字节boolean型
Strict array type类,记录一组数据。
|0x0A|4字节 数组长度 N| 1字节 data1 type| X字节 data1 | …
|1字节 dataN type| X字节 dataN|
有N个数据, 每个数据的类型 数据值
一般会用这个类记录关键帧的偏移地址和对应的pts值。类型都是 Number类,即X=8字节double型
Object type, 记录对象数据。一般用它来做keyframes的数据存储起始。
5.keyframes字段
FLV没有像mp4一样定义stbl,FLV文件如果做快进,seek等操作会比较麻烦。
业内通用做法是在脚本Tag里面增加keyfrmes object类。一般定义为:
|00 09| 9字节 “keyframes”|00 0D| 13字节 “filepositions”|0A| 4字节关键字数目 N|00|8字节 关键帧1偏移地址| …
|00|8字节 关键帧N偏移地址|
|00 05| 5字节 “times” |0A| 4字节关键字数目 N|00|8字节 关键帧1时间| …
|00|8字节 关键帧N时间|
有了各个关键帧的偏移地址和时间,做Seek操作的时候就方便的多。
读取8字节double型,这个牵扯浮点型的存储结构问题,蛮罗嗦,没看明白,但找到下面一段代码可以实现转换。
uint64_t v = get_8bytes();
if(v+v > 0xFFEULL<<52) return 0.0/0.0;
double x = ldexp(((v&((1LL<<52)-1)) + (1LL<<52)) * (v>>63|1), (v>>52&0x7FF)-1075);
FLV结构里面,数据长度有3字节和4字节限制,不会像Mp4一样出现8字节长度。
3字节最大表示16M数据,4字节最大是4G数据。
一个Video Tag里面的NALU长度不能超过16M字节。