簡書地址 : H.264 Data Structure
博客地址 : H.264 Data Structure
掘金地址 : H.264 Data Structure
Why ?
相信在你的電腦里,一定存有一些已經下載好的視頻文件,如果你硬說沒有,那我相信你曾經總有吧?曾經也沒有?那我想對你說曾經免費的時候你不下載,直到電影都收費才后悔那些年錯過下載的大片。
好了,言歸正傳,在日常我們一定見過很多后綴為avi, mp4, rmvb, flv等格式的視頻文件。而很少有人真正挖掘這些文件到底是什么?其實以上格式都是封裝視頻的封裝格式。
什么是封裝格式 ?
把音頻數據和視頻數據打包成一個文件的規范。不用封裝格式差距不大, 各有千秋。
從視頻播放器播放一個互聯網上的視頻文件
需要經過以下幾個步驟:解協議,解封裝,解碼視音頻,視音頻同步。如果播放本地文件則不需要解協議,其他步驟相同。
- 解協議:流媒體協議的數據,解析為標準的相應的封裝格式數據
- 解封裝:將輸入的封裝格式的數據,分離成為音頻流壓縮編碼數據和視頻流壓縮編碼數據。
- 解碼:就是將視頻/音頻壓縮編碼數據,解碼成為非壓縮的視頻/音頻原始數據。
- 視音頻同步:就是根據解封裝模塊處理過程中獲取到的參數信息,同步解碼出來的視頻和音頻數據,并將視頻音頻數據送至系統的顯卡和聲卡播放出來。
為什么要對視頻數據進行編碼
視頻編碼的主要作用是將視頻像素數據(RGB,YUV等)壓縮成視頻碼流,從而降低視頻的數據量。舉個例子:比如當前手機的屏幕分辨率是1280 * 720(即我們平時在視頻軟件中可選的720P),假設一秒鐘30幀(即1秒鐘傳輸30張圖片),那么一秒鐘的數據為 1280 * 720(位像素)*30(張) / 8(1字節8位)(結果B)
,也就是一秒鐘的數據量為3.456M數據量,一分鐘就是207.36M,那么我們平常看一部電影就是大約18G的流量,試想下如果是這樣對于存儲即網絡傳輸是件多么恐怖的事情。
正是因為以上原因,我們需要對視頻數據進行編碼,以最小程序減小清晰度與最大程序降低數據量,而H264正是目前廣泛使用的一種編碼格式,下面我們將主要介紹下H264的碼流結構。
碼流結構
刷新圖像概念
在我們的印象中,一張圖片就是一張圖像,而在H264中圖像是個集合的概念。
幀、頂場、底場都可以稱為圖像。一幀通常就是一幅完整的圖像。當采集視頻信號時,如果采用逐行掃描,則每次掃描得到的信號就是一副圖像,也就是一幀。當采集視頻信號時,如果采用隔行掃描(奇、偶數行),則掃描下來的一幀圖像就被分為了兩個部分,這每一部分就稱為「場」,根據次序分為:「頂場」和「底場」。「幀」和「場」的概念又帶來了不同的編碼方式:幀編碼、場編碼。逐行掃描適合于運動圖像,所以對于運動圖像采用幀編碼更好;隔行掃描適合于非運動圖像,所以對于非運動圖像采用場編碼更好。
H264原始碼流
- 結構:由一個接一個的 NALU 組成的,而它的功能分為兩層,VCL(視頻編碼層)和 NAL(網絡提取層).
- VCL:包括核心壓縮引擎和塊,宏塊和片的語法級別定義,設計目標是盡可能地獨立于網絡進行高效的編碼。
- NAL:負責將VCL產生的比特字符串適配到各種各樣的網絡和多元環境中,覆蓋了所有片級以上的語法級別。
- 組成:NALU (Nal Unit) = NALU頭 + RBSP
在 VCL 數據傳輸或存儲之前,這些編碼的 VCL 數據,先被映射或封裝進 NAL 單元(以下簡稱 NALU,Nal Unit) 中。每個 NALU 包括一個原始字節序列負荷(RBSP, Raw Byte Sequence Payload)、一組 對應于視頻編碼的 NALU 頭部信息。RBSP 的基本結構是:在原始編碼數據的后面填加了結尾 比特。一個 bit“1”若干比特“0”,以便字節對齊。
一個原始的H.264 NALU 單元常由 [StartCode] [NALU Header] [NALU Payload] 三部分組成
StartCode : Start Code 用于標示這是一個NALU 單元的開始,必須是”00 00 00 01” 或”00 00 01”
NALU Header
下表為 NAL Header Type
例如:
00 00 00 01 06: SEI信息
00 00 00 01 07: SPS
00 00 00 01 08: PPS
00 00 00 01 05: IDR Slice
- RBSP :NAL包將其負載數據存儲在 RBSP(Raw Byte Sequence Payload) 中,RBSP 是一系列的 SODB(String Of Data Bits)。
- 一幀圖片跟NALU的關聯:
一幀圖片經過 H.264 編碼器之后,就被編碼為一個或多個片(slice),而裝載著這些片(slice)的載體,就是 NALU 了。
注意:片(slice)的概念不同與幀(frame),幀(frame)是用作描述一張圖片的,一幀(frame)對應一張圖片,而片(slice),是 H.264 中提出的新概念,是通過編碼圖片后切分通過高效的方式整合出來的概念,一張圖片至少有一個或多個片(slice)。片(slice)都是又 NALU 裝載并進行網絡傳輸的,但是這并不代表 NALU 內就一定是切片,這是充分不必要條件,因為 NALU 還有可能裝載著其他用作描述視頻的信息。
什么是切片(slice)?
片的主要作用是用作宏塊(Macroblock)的載體(ps:下面會介紹到宏塊的概念)。片之所以被創造出來,主要目的是為限制誤碼的擴散和傳輸。
那么片(slice)的具體結構,我們用一張圖來直觀說明吧:
上圖結構中,我們不難看出,每個分片也包含著頭和數據兩部分:
- 分片頭中包含著分片類型、分片中的宏塊類型、分片幀的數量、分片屬于那個圖像以及對應的幀的設置和參數等信息。
- 分片數據中則是宏塊,這里就是我們要找的存儲像素數據的地方。
什么是宏塊?
宏塊是視頻信息的主要承載者,它包含著每一個像素的亮度和色度信息。視頻解碼的主要工作則是提供高效的方式從碼流中獲得宏塊中的像素陣列。
宏塊的組成:一個宏塊由一個16*16亮度像素和附加的一個8 * 8Cb和一個8 * 8Cr彩色像素塊組成。每個圖像中,若干宏塊被排列成片的形式。
下面是宏塊的結構圖:
切片(slice)類型跟宏塊類型的關系
切片(slice)來講,分為以下幾種類型:
P-slice. Consists of P-macroblocks (each macro block is predicted using one reference frame) and / or I-macroblocks.
B-slice. Consists of B-macroblocks (each macroblock is predicted using one or two reference frames) and / or I-macroblocks.
I-slice. Contains only I-macroblocks. Each macroblock is predicted from previously coded blocks of the same slice.
SP-slice. Consists of P and / or I-macroblocks and lets you switch between encoded streams.
SI-slice. It consists of a special type of SI-macroblocks and lets you switch between encoded streams.
I片:只包 I宏塊,I 宏塊利用從當前片中已解碼的像素作為參考進行幀內預測(不能取其它片中的已解碼像素作為參考進行幀內預測)。
P片:可包 P和I宏塊,P 宏塊利用前面已編碼圖象作為參考圖象進行幀內預測,一個幀內編碼的宏塊可進一步作宏塊的分割:即 16×16、16×8、8×16 或 8×8 亮度像素塊(以及附帶的彩色像素);如果選了 8×8 的子宏塊,則可再分成各種子宏塊的分割,其尺寸為 8×8、8×4、4×8 或 4×4 亮度像素塊(以及附帶的彩色像素)。
B片:可包 B和I宏塊,B 宏塊則利用雙向的參考圖象(當前和 來的已編碼圖象幀)進行幀內預測。
SP片(切換P):用于不同編碼流之間的切換,包含 P 和/或 I 宏塊
SI片:擴展檔次中必須具有的切換,它包含了一種特殊類型的編碼宏塊,叫做 SI 宏塊,SI 也是擴展檔次中的必備功能。
整體結構
H.264的碼流結構并沒有那么復雜,編碼后視頻的每一組圖像(GOP,圖片組)都給予了傳輸的序列(PPS)和本身這個幀的圖像參數(SPS),所以,整體給夠如下
GOP 圖像組主要形容一個I幀到下一個I幀之間間隔了多少幀,增大圖片組能有效的減少編碼后視頻的體積,但是也會降低視頻質量,至于怎么取舍,得看需求。
NALU頭部的類型
enum nal_unit_type_e
{
NAL_UNKNOWN = 0, // 未使用
NAL_SLICE = 1, // 不分區、非 IDR 圖像的片(片的頭信息和數據)
NAL_SLICE_DPA = 2, // 片分區 A
NAL_SLICE_DPB = 3, // 片分區 B
NAL_SLICE_DPC = 4, // 片分區 C
NAL_SLICE_IDR = 5, / ref_idc != 0 / // IDR 圖像中的片
NAL_SEI = 6, / ref_idc == 0 / // 補充增強信息單元
-
參數集是 H.264 標準的一個新概念,是一種通過改進視頻碼流結構增強錯誤恢復能力的方法。
NAL_SPS = 7, // 序列參數集 (包括一個圖像序列的所有信息,即兩個 IDR 圖像間的所有圖像信息,如圖像尺寸、視頻格式等)
NAL_PPS = 8, // 圖像參數集 (包括一個圖像的所有分片的所有相關信息, 包括圖像類型、序列號等,解碼時某些序列號的丟失可用來檢驗信息包的丟失與否)
-
NAL_AUD = 9, // 分界符
NAL_FILLER = 12, // 填充(啞元數據,用于填充字節)
/ ref_idc == 0 for 6,9, 10 (表明下一圖像為 IDR 圖像),11(表明該流中已沒有圖像),12 /
};
ps: 以上括號()中的為類型描述
補充說明
- I,P,B 幀 與 pts / dts
I幀 | P幀 | B幀 |
---|---|---|
幀內編碼幀 | 前向預測編碼幀 | 雙向預測編碼幀 |
I幀通常是每個GOP的第一幀,經過適度壓縮,作為隨機訪問的參考點,可看成一個圖片經過壓縮后的產物 | 通過充分低于圖像序列中前面已編碼幀的時間冗余信息來壓縮傳輸數據編碼圖像,也叫預測幀 | 既考慮與源圖像序列前面已編碼幀,也顧及源圖像序列后面已編碼幀之間的時間冗余信息來壓縮傳輸數據量的編碼圖像,也叫雙向預測幀 |
I,P,B幀
- I frame : 自身可以通過視頻解壓算法解壓成一張單獨完整的圖片
- P frame : 需要參考其前面的一個I frame 或者B frame來生成一張完整圖片
- B frame : 既要參考其前一個I frame 或者 P frame以及其后一個P frame來生成一張完整的圖片。
DTS,PTS
- PTS :PTS主要用于度量解碼后的視頻什么時候被顯示
- DTS :DTS主要是標識內存中的Bit流什么時候開始送入解碼器進行解碼
GOP
GOP是畫面組,一個GOP是一組連續的畫面。
GOP一般有兩個數字,如M=3,N=12.M制定I幀與P幀之間的距離,N指定兩個I幀之間的距離。那么現在的GOP結構是
I BBP BBP BBP BB I
IDR
一個序列的第一個圖像叫做 IDR 圖像(立即刷新圖像),IDR 圖像都是 I 幀圖像。
I和IDR幀都使用幀內預測。I幀不用參考任何幀,但是之后的P幀和B幀是有可能參考這個I幀之前的幀的。IDR就不允許這樣。
- 核心作用 : H.264 引入 IDR 圖像是為了解碼的重同步,當解碼器解碼到 IDR 圖像時,立即將參考幀隊列清空,將已解碼的數據全部輸出或拋棄,重新查找參數集,開始一個新的序列。這樣,如果前一個序列出現重大錯誤,在這里可以獲得重新同步的機會。IDR圖像之后的圖像永遠不會使用IDR之前的圖像的數據來解碼。
幀內預測和幀間預測
- 幀內預測(也叫幀內壓縮)
我們可以通過第 1、2、3、4、5 塊的編碼來推測和計算第 6 塊的編碼,因此就不需要對第 6 塊進行編碼了,從而壓縮了第 6 塊,節省了空間
可以看到前后兩幀的差異其實是很小的,這時候用幀間壓縮就很有意義。
這里涉及到幾個重要的概念:塊匹配,殘差,運動搜索(運動估計),運動補償.
幀間壓縮最常用的方式就是塊匹配(Block Matching)。找找看前面已經編碼的幾幀里面,和我當前這個塊最類似的一個塊,這樣我不用編碼當前塊的內容了,只需要編碼當前塊和我找到的快的差異(殘差)即可。找最想的塊的過程叫運動搜索(Motion Search),又叫運動估計。用殘差和原來的塊就能推算出當前塊的過程叫運動補償(Motion Compensation).