解碼H264文件的一些基礎知識


這段時間一直在進行編寫H264文件的解析類,因此對於H264文件的格式有了初步的了解,官方文檔也看了個大概。這篇文章主要是總結了一些為解碼H264文件而需要的一些前期知識,話不多說,下面是干貨,有些是自己在wiki上翻譯過來的,有些是看官方文檔后發現的一些關鍵部分。 
首先了解一下視頻文件中的一些知識:


Video compression picture types(視頻壓縮圖像類型)

1. 視頻幀的壓縮使用了不同的算法,這些應用於視頻幀的不同算法被稱為圖像類型或者幀類型。
    a. I - 該幀可壓縮程度最低,也不需要通過其他視頻幀解碼。
    b. P  - 該幀可以引用前面的幀的數據來解壓縮並且相對於I幀來說,該幀可以壓縮程度更高。
    c. B - 該幀可以引用前面的幀和后面的幀的數據,從而壓縮程度最高。

這里寫圖片描述 
(圖為一段視頻幀,由2個關鍵幀(I-frame),1個向前預測幀(P-frame)和1個雙向預測幀(B-frame)構成)

這里共有三種類型的幀被用在視頻壓縮技術中:I-幀,P-幀 和 B-幀。 
I-幀是一種“內編碼圖片”,實際上是一種信息十分詳細的圖片,跟傳統的靜態圖片文件相似。P-幀 和 B-幀 只存儲了部分的圖片信息,因此相比於I-幀來說,他們需要更少的存儲空間,也因此而提高了壓縮速率。 
P-幀(“預測圖片”)只保存圖片中與前一幀的不同的地方。比如,在一個場景中,汽車行駛在靜止的背景中,只有汽車的運動需要被編碼。編碼器無需存儲未改變的背景像素信息到P-幀中,因此而節省了空間,P-幀 也同樣被稱為 delta-幀。 
B-幀(“雙向預測圖片”)通過比較現在的幀和前后的幀的不同來指定具體存儲的內容,從而節省了更多的空間。


Slices(條帶)

一個條帶是一個幀中比較獨特的區域,不同於該幀中的其他區域。在最新的國際標准中,這里已經存在 I-slices(I-條帶),P-slices(P-條帶)和 B-slices(B-條帶)。


Macroblocks(宏塊)

普遍來說,圖片(幀)被分割成許多宏塊和個體預測類型能夠在宏塊層被挑選出來,並且整個圖片(幀)中的宏塊與宏塊並不相同,如下: 
1. I-幀 只能包含內部宏塊 
2. P-幀 既能包含內部宏塊,也能包含預測宏塊 
3. B-幀 能包含內部宏塊,預測宏塊和雙向預測宏塊 
進一步說,在視頻編解碼H.264中,幀能夠被分割成宏塊序列,該宏塊序列被稱為條帶(slices)。編解碼過程不是使用I,B和P 幀類型作為選集,而是通過每個條帶明確無誤的選擇出預測類型。在H.264文件中也能找到幾個額外的 幀/條帶 類型: 
1. SI-幀/條帶(Switching I);使編碼流之間的轉換變得更加容易;包含了 SI-宏塊(一種內部編碼宏塊的特殊類型) 
2. SP-幀/條帶(Switching P);使編碼流之間的轉換變得更加容易;包含了 P-宏塊 和/或 I-宏塊。 
3. muti-幀 動態預測(包括16個引用幀,或32個引用域)


Intra coded frames/slices(I-frames/slices or Key frames)(內編碼幀/條帶(I幀/條帶 或者 關鍵幀))

1. I-幀 編碼不依賴於其他幀
2. 可能被編碼器生成並創建於一個隨機訪問點(來允許解碼器在圖片的該位置開始解碼)
3. 當區分圖片細節時也可能為了避免產生有效的 P-幀 或 B-幀 而生成
4. 普遍來說比其他類型的幀需要更多的位數來編碼

通常,I-幀 被用來隨機訪問和作為其他圖片(幀)解碼時的引用。內部刷新周期通常為半秒(在數字電視廣播和DVD存儲介質中很常見)。在一些環境下,也可能需要更長的時間周期。比如,在視頻會議系統中,通常會間歇的發送I-幀(不頻繁的發送)。


Predicted frames/slices(P-frames/slices)(預測幀/條帶(P-幀/條帶))

1. 為了解碼的需要,要優先解碼一些其他圖片(幀)
2. 可能包含圖像數據或運動向量偏移量或者兩者的組合
3. 能夠按照解碼順序引用前面的圖片
4. 舊的設計標准在解碼時只使用一張前面的解碼圖片作為引用,並且需要該圖片也要在P-幀圖片之前顯示。
5. 在H.264文件中,在解碼過程中能夠使用多個先前解碼的圖片作為引用,並且相對於被用來做預測的圖片幀,該幀能夠以任意順序顯示。
6. 通常,相對於編碼I-幀來說,編碼預測幀需要更少的比特位數。

Bi-directional predicted frames/slices(B-frames/slices)(雙向預測幀/條帶(B-幀/條帶))

1. 為了解碼的需要,要優先解碼一些其他圖片(幀)
2. 可能包含圖像數據或運動向量偏移量或者兩者的組合
    a. 舊的標准有一個關於整個幀的全局運動補償向量
    b. 一些標准關於每個宏塊都有一個運動補償向量
3. 一些標准允許每個宏塊使用兩個運動補償向量(雙向預測)
4. 在舊的設計標准中(比如MPEG-2),B-幀從不用來作為其他圖片(幀)的預測引用。因此,B-幀可以用一個稍低質量的編碼(編碼量少的)來完成,因為一些細節的丟失不會對預測后續圖片(幀)的質量帶來影響。
5. 在H.264文件中,有可能被作為解碼其他圖片(幀)的引用(由編碼器決定)
6. 在舊的設計標准中(比如MPEG-2),在解碼過程中會使用前面的2幀解碼圖片作為引用,並且需要其中一幀在B-幀之前顯示,另外一幀在B-幀之后顯示。
7. 在H.264文件中,能夠使用1,2或者更多先前解碼的圖片(幀)作為解碼過程中的引用,並且相對於被用來做預測的圖片幀,該幀能夠以任意順序顯示。
8. 通常,相對於編碼I-幀 和 P-幀來說,編碼該幀需要更少的比特位數。

有了已上的基本知識以后,我們已經了解到I-幀是關鍵幀,並且是占空間最多的幀,因為很多數據都是內編碼的,而P-幀是預測幀,需要借助前面的幀來作為引用,從而得到完整的數據,而B-幀是雙向預測幀,需要借助前面和后面的幀來作為引用。因此一般來說占用空間排序:I-幀 > P-幀 > B-幀 (I-幀占用空間最多)。

對於如何解析幀類型(I,P還是B幀等等),需要用到nal_unit_type這一字段,在Nalu中它占據第一個字節的3~8位,Nalu的第一個字節(忽略起始碼startcode:0x000001 or 0x00000001)如下: 
forbidden_zero_bit: 1 bit 
nal_ref_idc: 2 bit 
nal_unit_type: 5 bit 
官方文檔也給出了語義解析,以偽代碼的形式給出的: 
這里寫圖片描述
后面的f(1),u(2),u(5)是代表要使用的解析方法,前面加深的粗體forbidden_zero_bit,nal_ref_idc,nal_unit_type稱為syntax_element,語法元素,即:解析這些語法元素要用到后面的f(1),u(2),u(5)方法,下面是官方文檔中給出的解析方法: 
這里寫圖片描述
這里寫圖片描述
這里面的解析方法,個人感覺只有Exp-Golomb-coded的解析有些麻煩(解析具體是什么幀需要用到該方法),其他的用簡單的幾個位操作就能輕松應對了吧,下面講講指數哥倫布編碼的相關信息:


Exponential-Golomb(universal code):指數哥倫布編碼

以下是wiki上給出的解釋(給翻譯過來的,英文水平不好,請輕噴,thanks)
    1. (默認k=0的形式) a. 將原數加1寫成二進制形式 b. 將1中的二進制的位數減一並記下,添加這么多個0到1中二進制位數的前面 例子: 0 ⇒ 1 ⇒ 1 1 ⇒ 10 ⇒ 010 2 ⇒ 11 ⇒ 011 3 ⇒ 100 ⇒ 00100 4 ⇒ 101 ⇒ 00101 5 ⇒ 110 ⇒ 00110 6 ⇒ 111 ⇒ 00111 7 ⇒ 1000 ⇒ 0001000 8 ⇒ 1001 ⇒ 0001001 將其推廣到負數范圍表示,方法如下: (1) 非負整數x≤0映射到偶數-2x上 (2) 正整數x>0映射到奇數2x - 1上 例子: 0 ⇒ 0 ⇒ 1 ⇒ 1 1 ⇒ 1 ⇒ 10 ⇒ 010 −1 ⇒ 2 ⇒ 11 ⇒ 011 2 ⇒ 3 ⇒ 100 ⇒ 00100 −2 ⇒ 4 ⇒ 101 ⇒ 00101 3 ⇒ 5 ⇒ 110 ⇒ 00110 −3 ⇒ 6 ⇒ 111 ⇒ 00111 4 ⇒ 7 ⇒ 1000 ⇒ 0001000 −4 ⇒ 8 ⇒ 1001 ⇒ 0001001 2. 衍生到k階(k≥0)形式 a. 用於編碼更大的數,編碼后形成更少的二進制位(編碼比較小的數需要使用更多的二進制位數) b. 要以k階形式編碼一個非負整數x,方法如下: i. 以0階方式編碼⌊x/2^k ⌋ ii. 將x取模2^k 等價方式: i. 使用0階exp-Golomb編碼方式編碼x+2k−1 ii. 從編碼結構中刪除前面的k個0位

這里寫圖片描述


官方文檔有關於解析哥倫布指數編碼的方法,of course,仍然是以偽代碼的形式給出: ) 
這里寫圖片描述
這里寫圖片描述
這里寫圖片描述

再繼續說解析幀類型的方法… 
依然是官方文檔…. 
發現了這么一個東東 
這里寫圖片描述 
這應該就是我們要分析該Nalu是什么幀類型的關鍵吧!於是找找它在哪里出現過 
這里寫圖片描述
很明顯,第3行出現了,再找找slice_header出現的地方 
這里寫圖片描述
這里寫圖片描述
這里寫圖片描述

然后再繼續逆向查找它們分別出現在哪里,結果最后都在一張表中找到了 
這里寫圖片描述
於是開始分析:

    1. 對於slice_layer_without_partitioning_rbsp: 
      1. nal_unit_type = 1,5,19
    2. 對於slice_data_partition_a_layer_rbsp: 
      1. nal_unit_type = 2
    3. 對於slice_layer_extension_rbsp: 
      1. nal_unit_type = 20 
        於是下面就是具體分析nal_unit_type的值來進行相應處理的過程: 
        首先,在代碼中枚舉如下NAL單元類型(參考了下ffmpeg) 
        這里寫圖片描述
        相應的,枚舉如下Slice類型,根據Slice類型即能得出幀類型 
        這里寫圖片描述 
        (具體為什么要都定義2種,因為根據官方文檔來寫的,其實每個第二種類型數值-5即為第一種類型,沒有仔細研究) 
        具體的解析代碼在我的GitHub上,歡迎參觀:yijiazhen的GitHub 
        剛剛接觸H264文件解析不久,如有分析錯誤或是不到位的地方,懇請各路好漢批評指正,希望可以互相交流,共同提高。


免責聲明!

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



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