FLV视频文件格式分析

廖雪峰 / 编程 / ... / Reads: 9950

FLV视频格式是Adobe推出的Flash可直接播放的视频流。需要注意的概念是编码格式和文件封装格式。编码格式是指编码器输出的“裸”的视频流或音频流,常见的视频编码格式就是H.264,常见的音频编码格式是AAC和MP3。FLV是一种文件封装格式,它可以封装H264和AAC,其他常见的文件封装格式还有MP4、TS、MKV等等。不同的文件封装格式可以相互转换,只要把一种文件封装格式拆包,解出“裸”的视频流和音频流,再按另一种文件封装格式打包,就可以完成转换,不需要重新编码,因此速度非常快。

本文讨论FLV的文件封装格式,FLV的文件格式定义最权威的就是Adobe的官方文档:

该文档的Annex E列出了FLV的详细封装格式。FLV采用网络字节序(高字节在前),无符号整数。

我们先找一个FLV文件,包含H264视频和AAC音频:

464c 5601 0500 0000 0900 0000 0012 0000
3f00 0000 0000 0000 0200 0a6f 6e4d 6574
6144 6174 6108 0000 0002 0008 6475 7261
7469 6f6e 0040 27c8 b439 5810 6200 0c76
6964 656f 636f 6465 6369 6400 4000 0000
0000 0000 0000 0900 0000 4a08 0000 0400
0000 0000 0000 af00 1208 0000 000f 0900
0043 0000 0000 0000 0017 0000 0000 0142
001f 0301 002f 6742 801f 9652 0283 f602
a100 0003 0001 0000 0300 32e0 6003 0d40
0046 30ff 18e3 0300 186a 0002 3187 f8c7
0ed0 a152 4001 0004 68cb 8d48 0000 004e
0900 0d1c 0000 0000 0000 0017 0100 029d 0000 0d13 6588 8040 0db1 185c 0008 2d1f 7893 de24 f789 f785 c2c4 f8a6 d3e2 43fa f177 85ea f377 a930 f991 ea7c 4f2a f0b9

FLV的封装格式比较简单,文件开头3个标识字节“FLV”标识文件类型,紧跟一个version字节,当前版本始终是0x01,然后紧跟的一个字节0x05从低位开始第一位表示是否有视频流,第三位表示是否有音频流,最高5位保留,因此,判断是否有视频和音频:

has_video = (b & 0x01)==1
has_audio = (b & 0x04)==4

上例中0x05说明该文件既含有视频又含有音频。随后4个字节表示FLV文件头的长度,一般是9,因为从文件头到此正好是9个字节,表示FLV文件头结束。

剩下的部分全部是FLV Body内容。FLV Body由一个一个的Tag构成,格式为:

Tag0的长度 | Tag1 | tag1的长度 | tag2 | tag2的长度 | ... | tagN | tagN的长度

第0个Tag也就是Tag0不存在,因此长度总是0,然后是Tag1的内容,紧跟Tag1的长度……Tag长度为4字节无符号整数。

tag有3类,ScriptTag = 0x12,又称Metadata Tag,存放视频元数据,如高、宽和关键帧等信息,VideoTag = 0x09,存放Video,AudioTag = 0x08,存放Audio。

Tag的结构如下:

Field: Type
Reseved: UB(2)
Filter: UB(1)
TagType: UB(5)
DataSize: UI24
Timestamp: UI24
TimestampExtended: UI8
StreamID: UI24
if TagType==8:
    AudioTagHeader: 不定长
if TagType==9:
    VideoTagHeader: 不定长
if Filter==1:
    EncryptionTagHeader: 不定长
Data: 不定长

Adobe的规范中,UB表示Unsigned Bit,UB(2)表示2个bit,UI表示Unsigned Integer,UI24表示24位整数,也就是3个Byte。

第一个字节的高2位bit保留,filter bit通常为0,低5位表示TagType,判断TagType的代码就是:

tagType = b & 0x1f

紧跟的3个byte是Tag数据的长度,即StreamID后面的数据长度,正好等于Tag的总长度减去11字节。

紧跟的3个byte是时间戳,后面再接一个byte的扩展时间戳。计算时间按如下公式计算:

扩展时间戳 << 24 + 时间戳

单位是毫秒。

紧跟的3个byte是StreamID,当前规范中始终为0。

根据TagType,紧跟的是AudioTagHeader或VideoTagHeader,剩下的就是真正的数据了。

解析FLV的Python代码如下:

def parse_flv(reader):
    if reader.read_bytes(3)!='FLV':
        raise StandardError('Bad FLV header')
    version = reader.read_byte() # should be 0x01
    b = reader.read_byte()
    has_video = (b & 0x01)==0x01
    has_audio = (b & 0x04)==0x04
    reader.skip(4) # skip length
    reader.skip(4) # skip tag 0's length
    while not reader.eof():
        tag_type, timestamp, data = parse_tag(reader)
        if tag_type==0x12:
            parse_script_tag(timestamp, data)
        elif tag_type==0x08:
            parse_audio_tag(timestamp, data)
        elif tag_type==0x09:
            parse_video_tag(timestamp, data)
        else:
            raise StandardError('Bad tag type')

def parse_tag(reader):
    tag_type = reader.read_byte() & 0x1f
    data_size = reader.read_int24()
    timestamp = reader.read_int24()
    timestamp_ext = reader.read_int8()
    reader.skip(3) # skip stream id
    data = reader.read(data_size)
    reader.read_int32() # tag size, should be data_size + 11
    return tag_type, (timestamp_ext << 24) + timestamp, data

Comments

Make a comment

Author: 廖雪峰

Publish at: ...

关于作者

关注公众号不定期领红包:

关注微博获取实时动态: