--------------------------------------------------------
簡書地址 : 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).