在如何看待嗶哩嗶哩的開源 HTML5 播放器內(nèi)核 flv.js?中,flv.js作者有這樣一段回復(fù):
一些人問我為什么不直接采用 MP4 格式,并表示對 FLV 格式的厭惡。這個問題一方面是歷史遺留問題,由于視頻網(wǎng)站前期完全依賴 Flash 播放而選擇 FLV 格式;另一方面,如果仔細(xì)研究過 FLV/MP4 封裝格式,你會發(fā)現(xiàn) FLV 格式非常簡潔,而 MP4 內(nèi)部 box 種類繁雜,結(jié)構(gòu)復(fù)雜固實而又有太多冗余數(shù)據(jù)。FLV 天生具備流式特征適合網(wǎng)絡(luò)流傳輸,而 MP4 這種使用最廣泛的存儲格式,設(shè)計卻并不一定優(yōu)雅。
關(guān)于MP4格式,可以參考VillainHR 學(xué)好 MP4,讓直播更給力。本文來學(xué)習(xí)一下FLV格式。
參考
FLV文件格式詳解
flv格式詳解+實例剖析
FLV 實例分析
在集體挺進(jìn)HTML5的時代,來討論Adobe Flash相關(guān)的話題似乎有點過時,但現(xiàn)如今還是有很多的視頻網(wǎng)站采用的是Flash播放器,播放的文件也依然還有很多是FLV格式,而且僅從一個文件格式的角度去了解和分析FLV應(yīng)該也還說的過去的。FLV(Flash Video)是Adobe的一個免費(fèi)開放的音視頻格式,babala~~ 省略若干字的介紹,要看,到官網(wǎng)看吧,這里不贅述,我們主要來討論下FLV文件格式的細(xì)節(jié),整體上,F(xiàn)LV分為Header和Body兩大塊。
Header: 記錄FLV的類型,版本,當(dāng)前文件類型等信息,這些信息可以讓我們對當(dāng)前FLV文件有個概括的了解。
Body: FLV的Body是Flv的數(shù)據(jù)區(qū)域,這些是FLV的具體內(nèi)容,因為FLV中的內(nèi)容有多種,并可同時存在,因此,Body也不是一整塊的數(shù)據(jù),而是由更細(xì)分的塊來組成,這個細(xì)分的塊叫Tag。

先來一張圖,這是《東風(fēng)破》——周杰倫(下載)的一個MV視頻。我使用的是Binary Viewer的二進(jìn)制查看工具。

一、Header
頭部分由一下幾部分組成,Signature(3 Byte)+Version(1 Byte)+Flags(1 Bypte)+DataOffset(4 Byte),共9字節(jié)。
1.signature
46 4C 56 正是FLV這三個字符的ASCII編碼,這個是固定標(biāo)識,表示它是FLV文件。
2.version
版本號0x01
3.Flags
0x05,對應(yīng)二進(jìn)制00000101,前面一個1表示有音頻數(shù)據(jù),后面一個1表示有視頻數(shù)據(jù)。
4.DataOffset
此4字節(jié)共同組成一個無符號32位整數(shù)(使用大頭序),表示文件從FLV Header開始到Flv Body的字節(jié)數(shù),當(dāng)前版本固定為9(0x00,0x00,0x00,0x09)
二、Body
1.Previous Tag Size
這個比較好理解,就是前一個Tag的大小,這里同樣存的是無符號32位整型數(shù)值。因為第一個Previous Tag Size是緊接著FLV Header的,因此,其值也是固定為0(0x00,0x00,0x00,0x00)。
2.TAG
FLV中的TAG不止一種,當(dāng)前版本共有3種類型組成:音頻(audio),視頻(video),腳本數(shù)據(jù)(script data),這三種類型會在Tag內(nèi)進(jìn)行標(biāo)志區(qū)分。其中:Audio Tag是音頻數(shù)據(jù),Video Tag是視頻數(shù)據(jù),Script Data存放的是關(guān)于FLV視頻和音頻的一些參數(shù)信息(亦稱為Metadata Tag),通常該Tag會在FLV File Header后面作為第一個Tag出現(xiàn),并且一個文件僅有一個Script Data Tag。
為了在Tag內(nèi)存放不同的數(shù)據(jù),并且能夠方便區(qū)分,每個Tag被定義了Tag Header和Tag Data兩部分,他們的結(jié)構(gòu)如下:
-------------------------
| Tag Header |
-------------------------
| Tag Data |
-------------------------
-------------------------
| Tag Header |
-------------------------
/ \
--------------------------------------------------------
| 08 | 00 | 00 | 18 | 00 | 00 | 00 | 00 | 00 | 00 | 00 |
--------------------------------------------------------
Tag Header由11字節(jié)組成:
- 第1字節(jié)type:標(biāo)志當(dāng)前Tag的類型,音頻(0x08),視頻(0x09),Script Data(0x12),除此之外,其他值非法;
- 第2-4字節(jié)tag data size:表示一個無符號24位整型數(shù)值,表示當(dāng)前Tag Data的大小;
- 第5-7字節(jié)Timestreamp:無符號24位整型數(shù)值(UI24),當(dāng)前Tag的時間戳(單位為ms),第一個Tag的時間戳總為0;
- 第8字節(jié)TimestampExtended:為時間戳的擴(kuò)展字節(jié),當(dāng)前24位不夠用時,該字節(jié)作為最高位,將時間戳擴(kuò)展為32位無符號整數(shù)(UI32)
- 第9-11字節(jié)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在哪里結(jié)束,Tag Header本身是11個字節(jié),第2-4字節(jié)tag data size現(xiàn)在已經(jīng)知道是293字節(jié),合計第一個TAB長度是11+293=304也就是16進(jìn)制的130。那么下一個TAG的Previous Tag Size應(yīng)該就是0x 00 00 01 30,見下圖紅線

可以在圖片上數(shù)一下,第一行的12 00 01是3個,中間共18行,每行16字節(jié),最后劃紅線00 00 01那行有13字節(jié),合計是3+18*16+13=304,確認(rèn)無誤。
3.TAG DATA
Tag Data由Tag Header標(biāo)志后,就被分成音頻,視頻,Script Data三類
4.Script Data
腳本Tag一般只有一個,是flv的第一個Tag,用于存放flv的信息,比如duration、audiodatarate、creator、width等。
首先介紹下腳本的數(shù)據(jù)類型。所有數(shù)據(jù)都是以數(shù)據(jù)類型+(數(shù)據(jù)長度)+數(shù)據(jù)的格式出現(xiàn)的,數(shù)據(jù)類型占1byte,數(shù)據(jù)長度看數(shù)據(jù)類型是否存在,后面才是數(shù)據(jù)。






一般來說,該Tag Data結(jié)構(gòu)包含兩個AMF包。AMF(Action Message Format)是Adobe設(shè)計的一種通用數(shù)據(jù)封裝格式,在Adobe的很多產(chǎn)品中應(yīng)用,簡單來說,AMF將不同類型的數(shù)據(jù)用統(tǒng)一的格式來描述。第一個AMF包封裝字符串類型數(shù)據(jù),用來裝入一個“onMetaData”標(biāo)志,這個標(biāo)志與Adobe的一些API調(diào)用有,在此不細(xì)述。第二個AMF包封裝一個數(shù)組類型,這個數(shù)組中包含了音視頻信息項的名稱和值。具體說明如下,大家可以參照圖片上的數(shù)據(jù)進(jìn)行理解。
(1)先看第一個AMF包,從02 00 0A 6F往后讀
第一個域是Name。Name又是SCRIPTDATAVALUE類型
type = 0x 02 對照上表是SCRIPTDATASTRING
SCRIPTDATASTRING類型會先用uint16標(biāo)識出數(shù)據(jù)長度
size = 0x 00 0A ,說明長度為10
value=onMeta Data= 0x 6F 6E 4D 65 74 61 44 61 74 61對應(yīng)的ASCII碼正是onMetaData,見下圖紅線。

(2)然后看第二個AMF
type = 0x 08 對照上表是數(shù)組,后面Length UI32,即4個字節(jié)為數(shù)組的個數(shù)
size = 0x 00 00 00 0D = 13,說明數(shù)組長度為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標(biāo)識type
type = 0x 00 是個double數(shù)值(8字節(jié))
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標(biāo)識type
type = 0x 00 是個double數(shù)值(8字節(jié))
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

type=0x09=9。這里應(yīng)該是一個video。
size=0x000030=48。長度為48。
timestreamp=0x000000。
TimestampExtended =0x00。
stream id =0x000000
(1)接著StreamID字段之后的就是VideoTAagHeader

特殊情況
視頻的格式(CodecID)是AVC(H.264)的話,VideoTagHeader會多出4個字節(jié)的信息,AVCPacketType 和CompositionTime。
AVCPacketType 占1個字節(jié)
| 值 | 類型 |
|---|---|
| 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解碼相關(guān)比較重要的sps和pps信息,再給AVC解碼器送數(shù)據(jù)流之前一定要把sps和pps信息送出,否則的話解碼器不能正常解碼。而且在解碼器stop之后再次start之前,如seek、快進(jìn)快退狀態(tài)切換等,都需要重新送一遍sps和pps的信息.AVCDecoderConfigurationRecord在FLV文件中一般情況也是出現(xiàn)1次,也就是第一個video tag.
CompositionTime 占3個字節(jié)
| 條件 | 值 |
|---|---|
| AVCPacketType ==1 | Composition time offset |
| AVCPacketType !=1 | 0 |
(2)第一個字節(jié)是0x 17,即
FrameType= 0x01
CodecID = 0x07
因為codecID=7,所以后面有AVCPacketType1個字節(jié)=0,CompositionTime3個字節(jié)也是0。
(3)我們看第一個video tag,也就是前面那張圖。我們看到AVCPacketType =0。而后面三個字節(jié)也是0。說明這個tag記錄的是AVCDecoderConfigurationRecord。包含sps和pps數(shù)據(jù)。

下面為了復(fù)制截圖方便,引用了FLV 實例分析中的例子

configurationVersion = 0x01
AVCProfileIndication = 0x4D (77) Main Profile
profile_compatibiltity = 0x40
AVCLevelIndication = 0x1F (31)
第五六字節(jié)是0xFFE1 ,寫成二進(jìn)制格式 ‘1111 1111 1110 0001’b
對應(yīng)到AVCDecoderConfigurationRecord的語法定義
lengthSizeMinusOne = ‘11’b (3) 也就是NALUintLength字段會是4個字節(jié)
numOfSequenceParameterSets = ‘00001’b 有一個Sps結(jié)構(gòu)
接下來16bits 是 sequenceParameterSetLength = 0x0019 (25 bytes)
下圖選中部分就是Sps了

再往下:
numOfPictureParamterSets = 0x01
pictureParameterSetLength = 0x0004;
下圖選中的就是pps了

再后面四個字節(jié)是PreviousTagSize= 0x00 00 00 38 (56) 等于這個Tag的 DataSize + 11 == (45) + 11。
6.audio tag
再下來又是一個新的FLVTAG了。
11個字節(jié)的頭部先取出來
TagType = 8 (音頻)
DataSize =0x000009 ( 9bytes)
(1)AudioTagHeader結(jié)構(gòu) 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格式的時候,不看這里的值,可以忽略掉。具體的真是值應(yīng)該從后面的數(shù)據(jù)從獲取
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
剩下的四個字節(jié)就是extensionflag3的相關(guān)內(nèi)容,這塊還沒有研究過。
到這一個audioTag結(jié)束。
后面的0x00000014是這個AudioTag的長度 等于 20 = 9 + 11。
7.再后面又是一個新的TAG

從這前11個字節(jié)知道的是這是一個視頻Tag. DataSize = 0x00099F (2463)timeStamp == 0;
然后再往后看,是一個VideoTagHeader結(jié)構(gòu),可以得到的信息如下
FrameType = 1 (是一個Key Frame)
CodecID= 7 (AVC)
AVCPacketType = 1 ;是一個普通的AVC NALUint
CompositionTime = 0x000043 (67)
那么這個Key Frame包含多少個NALUint呢,我們再來一步步看下去吧。記得前面我們分析的嗎?NALUint數(shù)據(jù)的開頭的NALUintLength字段。由之前的分析可知。占四個字節(jié),
NALUintLength = 0x00000222 (546 Bytes) 說明第一NALUint的長度是546字節(jié)

接著是第二個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都已經(jīng)取出來了 ,TagDataSize (2463 Bytes)= 1 (FrameType + CodecID) + 1 (AVCPacketType) + 3 (CompositionTime) + 4 + 546 +4 + 1523 + 4 + 267 + 4 + 50 + 4 + 52 等式成立。