【H264】碼流結構詳解


無論是解析視頻文件或者通過網絡傳輸,其實都是一串字節序列。H264 碼流就是按照一定的規則組織排列的字節串。


一、碼流的組織形式

在 H264 中完全沒有 I 幀、P 幀、B 幀、IDR 幀的概念,之所以沿用這些說法是為了表明數據的編碼模式。H264 碼流的組織形式從大到小排序是:視頻序列(video sequence)、圖像(frame/field-picture)、片組(slice group)、片(slice)、宏塊(macroblock)、子塊(sub-block)、像素(pixel)。
H264_Struct_A.png


二、碼流功能的角度

從碼流功能的角度可以分為兩層:視頻編碼層(VCL)和網絡提取層(NAL)

  • VCL:進行視頻編解碼,包括預測(幀內預測和幀間預測),DCT 變化和量化,熵編碼和切分數據等功能,是為了實現更高的視頻壓縮比。
  • NAL:負責以網絡所要求的恰當的方式對 VCL 數據進行打包和傳送。

VCL 是管理 H264 的視頻數據層,是為了實現更高的視頻壓縮比,那 VCL 究竟是怎么管理 H264 視頻數據的呢?拋開 H264 壓縮算法細節來看就 3 步:

  1. 壓縮:預測(幀內預測和幀間預測)-> DCT 變化和量化 -> 比特流編碼;
  2. 切分數據,主要為了第三步。這里一點,網上看到的“切片(slice)”、“宏塊(macroblock)”是在VCL 中的概念,一方面提高編碼效率和降低誤碼率、另一方面提高網絡傳輸的靈活性。
  3. 壓縮切分后的 VCL 數據會包裝成為 NAL 中的一部分。

下面要重點講解下 NAL。


三、網絡提取層(NAL)

NAL,英文全稱為 Network Abstraction Layer,這塊和 H264 壓縮算法無關,涉設計出 NAL 的目的就是為了獲得 “network-friendly”,即為了實現良好的網絡親和性,即可適用於各種傳輸網絡。

終於要講 NAL 了,但是,我們需要先看 NAL的組成單元 - NALU


3.1 NAL單元 - NALU

NALU 的格式如下圖(引用H264 PDF)所示:

FFmpeg_H264_NALU.png


很明顯,NALU 由身體兩個部分組成:

  • 頭:一般存儲標志信息,譬如 NALU 的類型。前面講過 NAL 會打包 VCL 數據,但是這並不意味着所有的 NALU 負載的都是 VCL,也有一些 NALU 僅僅存儲了和編解碼信息相關的數據;
  • 身體:存儲了真正的數據。但實際上,這塊也會相對比較復雜,前面其實也提到過 H264 的一個目的是“網絡友好性”,說白了就是能夠很好地適配各種傳輸格式。所以根據實際采用數據傳輸流格式,也會對這部分數據格式再進行處理。

3.2 NALU Header

首先,NALU Header只占 1 個字節,即 8 位,其組成如下圖所示:

H264_Struct_B.jpg


  • forbidden_zero_bit

    在網絡傳輸中發生錯誤時,會被置為 1,告訴接收方丟掉該單元;否則為 0。

  • nal_ref_idc
    用於表示當前NALU的重要性,值越大,越重要。
    解碼器在解碼處理不過來的時候,可以丟掉重要性為 0 的 NALU。

  • nal_unit_type
    表示 NALU 數據的類型,有以下幾種:

H264_Struct_C.png


其中比較注意的應該是以下幾個:

  • 1-4:I/P/B幀,如果 nal_ref_idc 為 0,則表示 I 幀,不為 0 則為 P/B 幀。
  • 5:IDR幀,I 幀的一種,告訴解碼器,之前依賴的解碼參數集合(接下來要出現的 SPS\PPS 等)可以被刷新了。
  • 6:SEI,英文全稱 Supplemental Enhancement Information,翻譯為“補充增強信息”,提供了向視頻碼流中加入額外信息的方法。
  • 7:SPS,全稱 Sequence Paramater Set,翻譯為“序列參數集”。SPS 中保存了一組編碼視頻序列(Coded Video Sequence)的全局參數。因此該類型保存的是和編碼序列相關的參數。
  • 8: PPS,全稱 Picture Paramater Set,翻譯為“圖像參數集”。該類型保存了整體圖像相關的參數。
  • 9:AU 分隔符,AU 全稱 Access Unit,它是一個或者多個 NALU 的集合,代表了一個完整的幀,有時候用於解碼中的幀邊界識別。

特殊的 NALU 類型:SPS和PPS

SPSPPS 存儲了編解碼需要一些圖像參數,SPS,PPS 需要在 I 幀前出現,不然解碼器沒法解碼。而 SPS,PPS 出現的頻率也跟不同應用場景有關,對於一個本地 h264 流,可能只要在第一個 I 幀前面出現一次就可以,但對於直播流,每個 I 幀前面都應該插入 sps 或 pps,因為直播時客戶端進入的時間是不確定的。


3.3 NALU Payload

很少有資料會稱身體部分為 Payload,絕大部分資料對 NALU 組成的定義是這樣子的:

NALU = NALU Header + SODB // 定義1
NALU = NALU Header + RBSP // 定義2
NALU = NALU Header + EBSP // 定義3

於是新的問題來了:SODB,RBSP和EBSP都是什么東西呢?這塊概念,在博客NALU詳解二(EBSP、RBSP與SODB)中介紹得非常清楚,總結來說就是:

SODB

英文全稱 String Of Data Bits,稱原始數據比特流,就是最原始的編碼/壓縮得到的數據。

RBSP

英文全稱 Raw Byte Sequence Payload,又稱原始字節序列載荷。和 SODB 關系如下:

RBSP = SODB + RBSP Trailing Bits(RBSP尾部補齊字節)

引入 RBSP Trailing Bits 做 8 位字節補齊。

EBSP

英文全稱 Encapsulated Byte Sequence Payload,稱為擴展字節序列載荷。和 RBSP 關系如下:

EBSP :RBSP插入防競爭字節(`0x03`)

這里說明下防止競爭字節(0x03):讀者可以先認為 H264 會插入一個叫做 StartCode 的字節串來分割 NALU,於是問題來了,如果 RBSP 中也包括了 StartCode(0x000001 或 0x00000001)怎么辦呢?所以,就有了防止競爭字節(0x03)

編碼時,掃描 RBSP,如果遇到連續兩個 0x00 字節,就在后面添加 防止競爭字節(0x03);解碼時,同樣掃描 EBSP,進行逆向操作即可。

最后,以一幅圖總結 NALU 這段內容:

H264_Struct_D.jpg


四、碼流解析的角度

H264 碼流實際可以理解為由一個一個的 NALU 單元組成。(下圖中的 RBSP 類似 NALU Payload)

H264_Struct_E.png


前面提到的一幀圖像(I 幀, P 幀, B 幀)就是一個 NALU 單元,NALU 單元除了代表圖像外還能包含其他類型的數據,如 PPS 和 SPS。


五、H264更詳細的分層結構

H264_Struct_F.png


  • 第一層:比特流。該層有兩種格式:Annexb 格式和 RTP 格式。
  • 第二層:NAL Unit 層。包含了 NAL Header 和 NAL Body 信息。
  • 第三層:Slice 層。一幀視頻圖像可編碼成一個或者多個片,每片包含整數個宏塊,即每片至少 一個宏塊,最多時包含整個圖像的宏塊。
  • 第四層:Slice data 層。Slice 由宏塊(macro block, MB)組成。宏塊是編碼處理的基本單元。
  • 第五層:PCM 類。
  • 第六層:殘差層。

片的目的:

為了限制誤碼的擴散和傳輸,使編碼片相互間保持獨立。片共有 5 種類型: I 片(只包含 I 宏塊)、P 片(P 和 I 宏塊)、B 片(B 和 I 宏塊)、SP 片(用於不同編碼流之 間的切換)和 SI 片(特殊類型的編碼宏塊)。


六、擴展:怎么區分 NALU 的邊界?

了解了 NALU 之后,關於 H264 格式,還有一個問題:解碼器怎么知道一個 NALU 要結束了?或者說它怎么區分 NALU 的邊界?

要回答這個問題,就必須了解 H264 的打包方式,通俗來說是H264 如何組織一連串的 NALU 為完整的 H264 碼流。目前 H264 主流的兩種格式:

  • Annex-B:本文關於 NALU 的很多細節介紹都是 Annex-B,它依靠前文提到的 Start Code 來分隔 NALU,打包方式如下:

    [start code]--[NALU]--[start code]--[NALU]...
    
  • AVCC:筆者對這個格式了解的不多,從網上找到很多資料知道以下幾點:

    • NALUextradata/sequence header 組成,由於在 extradata/sequence header 中存儲了 NALU 的長度,因此 NALU Payload 不需要做字節對齊,不過防競爭字節還是有的;

    • SPSPPS 被放在了 extradata/sequence header

    • 打包方式如下:

      [SIZE (4 bytes)]--[NAL]--[SIZE (4 bytes)]--[NAL]... // 請注意,SIZE一般為4字節,但是具體以實際為准
      

至於為什么要有這兩類格式,還需要查閱更多的資料。不過 StackOverflow 上關於Possible Locations for Sequence/Picture Parameter Set(s) for H.264 Stream的回答可以幫助深入了解這兩種格式,推薦閱讀。


下面是一個 H264 碼流,可以看到每個 NALU 前有一個 StartCode(0x000001 或 0x00000001),作為 NALU 的分割符:

H264_Struct_G.jpg


分析其中比較有代表性的3幀:

  • 00 00 00 01 67
    00 00 00 01 是一個 NALU 開始,67 是Header, 二進制為 0110 0111, nal_unit_type 為00111,即7為 SPS 幀。
  • 00 00 00 01 68
    68 二進制為 0110 1000,nal_unit_type 為00111,即 8 為 SPS 幀。
  • 00 00 00 01 65
    65 二進制為 0110 0101,nal_unit_type 為 00101, 即 5 為 IDR 幀。

參考:

H264系列--碼流組成和分層結構

知乎 - 視頻和視頻幀:H264編碼格式整理

(十三) h264標准解析



免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM