在如何看待嗶哩嗶哩的開源 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分為Header
和Body
兩大塊。
Header: 記錄FLV的類型,版本,當前文件類型等信息,這些信息可以讓我們對當前FLV文件有個概括的了解。
Body: FLV的Body是Flv的數據區域,這些是FLV的具體內容,因為FLV中的內容有多種,并可同時存在,因此,Body也不是一整塊的數據,而是由更細分的塊來組成,這個細分的塊叫Tag。
先來一張圖,這是《東風破》——周杰倫(下載)的一個MV視頻。我使用的是Binary Viewer的二進制查看工具。
一、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,見下圖紅線
可以在圖片上數一下,第一行的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,數據長度看數據類型是否存在,后面才是數據。
一般來說,該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,見下圖紅線。
(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
type=0x09=9。這里應該是一個video。
size=0x000030=48。長度為48。
timestreamp=0x000000。
TimestampExtended =0x00。
stream id =0x000000
(1)接著StreamID字段之后的就是VideoTAagHeader
特殊情況
視頻的格式(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數據。
下面為了復制截圖方便,引用了FLV 實例分析中的例子
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了
再往下:
numOfPictureParamterSets = 0x01
pictureParameterSetLength = 0x0004;
下圖選中的就是pps了
再后面四個字節是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
從這前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字節
接著是第二個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 等式成立。