H5直播系列八 FLV文件格式

如何看待哔哩哔哩的开源 HTML5 播放器内核 flv.js?中,flv.js作者有这样一段回复:
一些人问我为什么不直接采用 MP4 格式,并表示对 FLV 格式的厌恶。这个问题一方面是历史遗留问题,由于视频网站前期完全依赖 Flash 播放而选择 FLV 格式;另一方面,如果仔细研究过 FLV/MP4 封装格式,你会发现 FLV 格式非常简洁,而 MP4 内部 box 种类繁杂,结构复杂固实而又有太多冗余数据。FLV 天生具备流式特征适合网络流传输,而 MP4 这种使用最广泛的存储格式,设计却并不一定优雅。

关于MP4格式,可以参考VillainHR 学好 MP4,让直播更给力。本文来学习一下FLV格式。

参考
FLV文件格式详解
flv格式详解+实例剖析
FLV 实例分析

在集体挺进HTML5的时代,来讨论Adobe Flash相关的话题似乎有点过时,但现如今还是有很多的视频网站采用的是Flash播放器,播放的文件也依然还有很多是FLV格式,而且仅从一个文件格式的角度去了解和分析FLV应该也还说的过去的。FLV(Flash Video)是Adobe的一个免费开放的音视频格式,babala~~ 省略若干字的介绍,要看,到官网看吧,这里不赘述,我们主要来讨论下FLV文件格式的细节,整体上,FLV分为HeaderBody两大块。

Header: 记录FLV的类型,版本,当前文件类型等信息,这些信息可以让我们对当前FLV文件有个概括的了解。

Body: FLV的Body是Flv的数据区域,这些是FLV的具体内容,因为FLV中的内容有多种,并可同时存在,因此,Body也不是一整块的数据,而是由更细分的块来组成,这个细分的块叫Tag。


image.png

先来一张图,这是《东风破》——周杰伦(下载)的一个MV视频。我使用的是Binary Viewer的二进制查看工具。

image.png

一、Header

头部分由一下几部分组成,Signature(3 Byte)+Version(1 Byte)+Flags(1 Bypte)+DataOffset(4 Byte),共9字节。
1.signature
46 4C 56 正是FLV这三个字符的ASCII编码,这个是固定标识,表示它是FLV文件。
2.version
版本号0x01
3.Flags
0x05,对应二进制00000101,前面一个1表示有音频数据,后面一个1表示有视频数据。
4.DataOffset
此4字节共同组成一个无符号32位整数(使用大头序),表示文件从FLV Header开始到Flv Body的字节数,当前版本固定为9(0x00,0x00,0x00,0x09)

二、Body

1.Previous Tag Size
这个比较好理解,就是前一个Tag的大小,这里同样存的是无符号32位整型数值。因为第一个Previous Tag Size是紧接着FLV Header的,因此,其值也是固定为0(0x00,0x00,0x00,0x00)。

2.TAG
FLV中的TAG不止一种,当前版本共有3种类型组成:音频(audio),视频(video),脚本数据(script data),这三种类型会在Tag内进行标志区分。其中:Audio Tag是音频数据,Video Tag是视频数据,Script Data存放的是关于FLV视频和音频的一些参数信息(亦称为Metadata Tag),通常该Tag会在FLV File Header后面作为第一个Tag出现,并且一个文件仅有一个Script Data Tag。

为了在Tag内存放不同的数据,并且能够方便区分,每个Tag被定义了Tag Header和Tag Data两部分,他们的结构如下:

-------------------------
|       Tag Header      |
-------------------------
|       Tag  Data       |
-------------------------
 
 
 
                -------------------------
                |       Tag Header      |
                -------------------------
                 /                    \
--------------------------------------------------------
| 08 | 00 | 00 | 18 | 00 | 00 | 00 | 00 | 00 | 00 | 00 |
--------------------------------------------------------

Tag Header由11字节组成:

  • 第1字节type:标志当前Tag的类型,音频(0x08),视频(0x09),Script Data(0x12),除此之外,其他值非法;
  • 第2-4字节tag data size:表示一个无符号24位整型数值,表示当前Tag Data的大小;
  • 第5-7字节Timestreamp:无符号24位整型数值(UI24),当前Tag的时间戳(单位为ms),第一个Tag的时间戳总为0;
  • 第8字节TimestampExtended:为时间戳的扩展字节,当前24位不够用时,该字节作为最高位,将时间戳扩展为32位无符号整数(UI32)
  • 第9-11字节stream id:UI24类型,表示Stream ID,总是0

看一下上述实例,第一个TAG:

type=0x12=18,是一个Script Data。
tag data size=0x000125=293。长度为293。
timestreamp=0x000000。这里是scripts,所以为0
TimestampExtended =0x00。
stream id =0x000000

这里来找一下第一个TAG在哪里结束,Tag Header本身是11个字节,第2-4字节tag data size现在已经知道是293字节,合计第一个TAB长度是11+293=304也就是16进制的130。那么下一个TAG的Previous Tag Size应该就是0x 00 00 01 30,见下图红线


image.png

可以在图片上数一下,第一行的12 00 01是3个,中间共18行,每行16字节,最后划红线00 00 01那行有13字节,合计是3+18*16+13=304,确认无误。

3.TAG DATA
Tag Data由Tag Header标志后,就被分成音频,视频,Script Data三类

4.Script Data
脚本Tag一般只有一个,是flv的第一个Tag,用于存放flv的信息,比如duration、audiodatarate、creator、width等。

首先介绍下脚本的数据类型。所有数据都是以数据类型+(数据长度)+数据的格式出现的,数据类型占1byte,数据长度看数据类型是否存在,后面才是数据。

image.png

image.png

image.png

string类型会先用uint16 标识出数据长度

image.png

image.png

一般来说,该Tag Data结构包含两个AMF包。AMF(Action Message Format)是Adobe设计的一种通用数据封装格式,在Adobe的很多产品中应用,简单来说,AMF将不同类型的数据用统一的格式来描述。第一个AMF包封装字符串类型数据,用来装入一个“onMetaData”标志,这个标志与Adobe的一些API调用有,在此不细述。第二个AMF包封装一个数组类型,这个数组中包含了音视频信息项的名称和值。具体说明如下,大家可以参照图片上的数据进行理解。

(1)先看第一个AMF包,从02 00 0A 6F往后读
第一个域是Name。Name又是SCRIPTDATAVALUE类型
type = 0x 02 对照上表是SCRIPTDATASTRING
SCRIPTDATASTRING类型会先用uint16标识出数据长度
size = 0x 00 0A ,说明长度为10
value=onMeta Data= 0x 6F 6E 4D 65 74 61 44 61 74 61对应的ASCII码正是onMetaData,见下图红线。

image.png

(2)然后看第二个AMF
type = 0x 08 对照上表是数组,后面Length UI32,即4个字节为数组的个数
size = 0x 00 00 00 0D = 13,说明数组长度为13,后面有13个SCRIPTDATAOBJECTPROPERTY。
对照上图,SCRIPTDATAOBJECTPROPERTY由PropertyName(SCRIPTDATASTRING类型)和PropertyData(SCRIPTDATAVAULE类型)组成

(3)第一个键值对
PropertyName 是SCRIPTDATASTRING类型
string length = 0x 00 08 说明长度为8
string data= 0x 64 75 72 61 74 69 6F 6E,正是ASCII码duration

PropertyData是一个SCRIPTDATAVAULE类型。用的是UI8标识type
type = 0x 00 是个double数值(8字节)
value = 0x 40 73 A7 85 1E B8 51 EC

(4)第二个键值对
PropertyName 是SCRIPTDATASTRING类型
string length = 0x 00 05 说明长度为5
string data = 0x 77 69 64 74 68,正是ASCII码width

PropertyData是一个SCRIPTDATAVAULE类型。用的是UI8标识type
type = 0x 00 是个double数值(8字节)
value = 0x 40 76 00 00 00 00 00 00

(5)第三个键值对
PropertyName
string length = 0x 00 06 长度为6
string data = 68 65 69 64 74 68 即width

PropertyData
type = 0x 00
value = 0x 40 76 00 00 00 00 00 00

后面的属性同理

5.video tag


image.png

type=0x09=9。这里应该是一个video。
size=0x000030=48。长度为48。
timestreamp=0x000000。
TimestampExtended =0x00。
stream id =0x000000

(1)接着StreamID字段之后的就是VideoTAagHeader


image.png

特殊情况
视频的格式(CodecID)是AVC(H.264)的话,VideoTagHeader会多出4个字节的信息,AVCPacketType 和CompositionTime。

AVCPacketType 占1个字节

类型
0 AVCDecoderConfigurationRecord(AVC sequence header)
1 AVC NALU
2 AVC end of sequence (lower level NALU sequence ender is not required or supported)

AVCDecoderConfigurationRecord.包含着是H.264解码相关比较重要的sps和pps信息,再给AVC解码器送数据流之前一定要把sps和pps信息送出,否则的话解码器不能正常解码。而且在解码器stop之后再次start之前,如seek、快进快退状态切换等,都需要重新送一遍sps和pps的信息.AVCDecoderConfigurationRecord在FLV文件中一般情况也是出现1次,也就是第一个video tag.

CompositionTime 占3个字节

条件
AVCPacketType ==1 Composition time offset
AVCPacketType !=1 0

(2)第一个字节是0x 17,即
FrameType= 0x01
CodecID = 0x07
因为codecID=7,所以后面有AVCPacketType1个字节=0,CompositionTime3个字节也是0。

(3)我们看第一个video tag,也就是前面那张图。我们看到AVCPacketType =0。而后面三个字节也是0。说明这个tag记录的是AVCDecoderConfigurationRecord。包含sps和pps数据。

image.png

下面为了复制截图方便,引用了FLV 实例分析中的例子
image.png

configurationVersion = 0x01
AVCProfileIndication = 0x4D (77) Main Profile
profile_compatibiltity = 0x40
AVCLevelIndication = 0x1F (31)
第五六字节是0xFFE1 ,写成二进制格式 ‘1111 1111 1110 0001’b
对应到AVCDecoderConfigurationRecord的语法定义
lengthSizeMinusOne = ‘11’b (3) 也就是NALUintLength字段会是4个字节
numOfSequenceParameterSets = ‘00001’b 有一个Sps结构
接下来16bits 是 sequenceParameterSetLength = 0x0019 (25 bytes)
下图选中部分就是Sps了
image.png

再往下:
numOfPictureParamterSets = 0x01
pictureParameterSetLength = 0x0004;
下图选中的就是pps了
image.png

再后面四个字节是PreviousTagSize= 0x00 00 00 38 (56) 等于这个Tag的 DataSize + 11 == (45) + 11。

6.audio tag
再下来又是一个新的FLVTAG了。
11个字节的头部先取出来
TagType = 8 (音频)
DataSize =0x000009 ( 9bytes)

(1)AudioTagHeader结构 0xAF
SoundFormat(4bits) = 0x0A (10 == AAC)
SoundRate(2bits) = ‘11’b (3 == 44kHz)
SoundSize(1bit) =’1’b (1 == 16-bit)
SoundType(1bit) = ‘1’b (1= Stereo)
注: 虽然这里SoundRate, SoundSize SoundType 都是 1 。但是这些都是定值,AAC格式的时候,不看这里的值,可以忽略掉。具体的真是值应该从后面的数据从获取
AACPacketType == 0x00 (AAC seqence header)
所以AACPacketType后面的就是AudioSpecificConfig了 0x13 90 56
AudioSpecificConfig.audioObjectType(5 bits) = 2 (AAC LC)
AudioSpecificConfig.samplingFrequencyIndex(4 bits) = 7
AudioSpecificConfig.channelConfiguration (4 bits)= 2
AudioSpecificConfig.GASpecificConfig.frameLengthFlag (1 bit) = 0
AudioSpecificConfig.GASpecificConfig.dependsOnCoreCoder : (1 bit) = 0
AudioSpecificConfig.GASpecificConfig.extensionFlag : (1 bit) = 1
剩下的四个字节就是extensionflag3的相关内容,这块还没有研究过。
到这一个audioTag结束。
后面的0x00000014是这个AudioTag的长度 等于 20 = 9 + 11。

7.再后面又是一个新的TAG


image.png

从这前11个字节知道的是这是一个视频Tag. DataSize = 0x00099F (2463)timeStamp == 0;
然后再往后看,是一个VideoTagHeader结构,可以得到的信息如下
FrameType = 1 (是一个Key Frame)
CodecID= 7 (AVC)
AVCPacketType = 1 ;是一个普通的AVC NALUint
CompositionTime = 0x000043 (67)
那么这个Key Frame包含多少个NALUint呢,我们再来一步步看下去吧。记得前面我们分析的吗?NALUint数据的开头的NALUintLength字段。由之前的分析可知。占四个字节,
NALUintLength = 0x00000222 (546 Bytes) 说明第一NALUint的长度是546字节


选中的546字节

接着是第二个NALUintLength = 0x00 00 05 F3 (1523 bytes) ,图片太长,详见原文
接下来又是一个NALUintLength = 0x00 00 01 0B (267 bytes) ,图略
下来又是一个NALUintLenth = 0x00 00 00 32 (50 bytes) ,图略
接下来还是一个NALUintLength = 0x00 00 00 34 (52 byte),图略

好了,到这里我们第一个KeyFrame的所有NALUint都已经取出来了 ,TagDataSize (2463 Bytes)= 1 (FrameType + CodecID) + 1 (AVCPacketType) + 3 (CompositionTime) + 4 + 546 +4 + 1523 + 4 + 267 + 4 + 50 + 4 + 52 等式成立。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,362评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,330评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,247评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,560评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,580评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,569评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,929评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,587评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,840评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,596评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,678评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,366评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,945评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,929评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,165评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,271评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,403评论 2 342

推荐阅读更多精彩内容