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閱讀 230,527評論 6 544
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,687評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,640評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,957評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,682評論 6 413
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 56,011評論 1 329
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,009評論 3 449
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,183評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,714評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,435評論 3 359
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,665評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,148評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,838評論 3 350
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,251評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,588評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,379評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,627評論 2 380

推薦閱讀更多精彩內容