音視頻學習(六、初識h264 NALU)

深入淺出理解視頻編碼H264結構(內涵福利)
H264編碼原理以及I幀、B和P幀詳解, H264碼流結構分析
H264編碼之GOP含義

簡單的推流拉流已經實現了,我們之前推流的數據是通過讀取flv視頻文件的,正式的應用是不會這麼推流的,正式的應用是採集電腦屏幕數據,然後進行編碼,傳輸,接收端接收到數據之後再解碼然後顯示,這是的編碼就是把YUV格式的視頻數據編碼成h264的,這個h264的結構還是比較重要的,雖然網上也有很多人總結,並且總結的還不錯,但是總感覺看了幾遍還是記不住,這個就是所謂的好記性不如爛筆頭把,所以我這裏借鑑了網上的一些大神的總結,然後再加上自己的總結,這樣印象深刻一點。

6.1 h264介紹

首先來一段大家都熟悉的官方話來介紹一下 H.264

H.264: H.264/AVC項目的目的是爲了創建一個比以前的視頻壓縮標準,在更低的比特率的情況下依然能夠提供良好視頻質量的標準(如,一半或者更少於MPEG-2,H.263,或者MPEG-4 Part2 )。同時,還要不會太大的增加設計的複雜性。
優勢:
1)網絡親和性,即可適用於各種傳輸網絡
2)高的視頻壓縮比,當初提出的指標是比 H.263,MPEG-4,約爲它們的 2 倍,現在都已基 實現;

那麼很明顯,什麼時候需要到壓縮呢?當然是文件體積太大的時候啦,我們想想,所謂的視頻,就是像小時候的連環畫一樣,在一秒內翻過 24 張以上的圖片,就感覺圖像是連續的了,這就是視頻的原理。但是大家有沒有想過,一張圖片有多大呢?我們的屏幕分辨率按 1280 * 720 算的話,一秒鐘的視頻大概就 2.64 MB 了,大家想想,我們大部分的小夥伴爲了下載個小嗨片省吃儉用纔開了個 1M 的網線,然後連個直播都看不了是什麼感覺。那肯定不能這樣了,所以我們要進行壓縮,而 H.264 不僅壓縮比比較高,對網絡的兼容性也非常好,所以大多數人做直播也就選擇了 H.264 作爲編碼格式了。

6.1.1 編碼流程

那麼 H.264 其編解碼流程是怎麼樣的呢?其實可以主要分爲 5 部分: 幀間和幀內預測(Estimation)、變換(Transform)和反變換、量化(Quantization)和反量化、環路濾波(Loop Filter)、熵編碼(Entropy Coding)。
看起來很高深的樣子,實際上也是很高深的樣子,因爲這裏麪包含着許許多多的算法和專業知識,這裏我們就不做過多的講解,有興趣的同學可以上網翻翻,夠你看到睡覺的了。H.264詳細文檔

6.2 I幀 P幀 B幀

H264是新一代的編碼標準,以高壓縮高質量和支持多種網絡的流媒體傳輸著稱,在編碼方面,我理解的理論依據是:參照一段時間內圖像的統計結果表明,在相鄰幾幅圖像畫面中,一般有差別的像素只有10%以內的點,亮度差值變化不超過2%,而色度差值的變化只有1%以內。所以對於一段變化不大圖像的畫面,我們可以先編碼出一個完整的圖像幀A,隨後的B幀就不編碼全部圖像,只寫入與A幀的差別,這樣B幀的大小就只有完整幀的1/10或更小!B幀之後的C幀如果變化不大,我們可以繼續以參考B的方式編碼C幀,這樣循環下去。這段圖像我們稱爲一個序列(序列就是有相同特點的一段數據),當某個圖像與之前的圖像變化很大,無法參考前面的幀來生成,那我們就結束上一個序列,開始下一段序列,也就是對這個圖像生成一個完整幀A1,隨後的圖像就參考A1生成,只寫入與A1的差別內容。
在H264協議裏定義了三種幀,完整編碼的幀叫I幀,參考之前的I幀生成的只包含差異部分編碼的幀叫P幀,還有一種參考前後的幀編碼的幀叫B幀。
H264採用的核心算法是幀內壓縮和幀間壓縮,幀內壓縮是生成I幀的算法,幀間壓縮是生成B幀和P幀的算法。

6.2.1 I幀

幀內編碼幀,I幀表示關鍵幀,你可以理解爲這一幀畫面的完整保留;解碼時只需要本幀數據就可以完成(因爲包含完整畫面)
I幀特點:
1)它是一個全幀壓縮編碼幀。它將全幀圖像信息進行JPEG壓縮編碼及傳輸;
2)解碼時僅用I幀的數據就可重構完整圖像;
3)I幀描述了圖像背景和運動主體的詳情;
4)I幀不需要參考其他畫面而生成;
5)I幀是P幀和B幀的參考幀(其質量直接影響到同組中以後各幀的質量);
6)I幀是幀組GOP的基礎幀(第一幀),在一組中只有一個I幀;
7)I幀不需要考慮運動矢量;
8)I幀所佔數據的信息量比較大。

6.2.2 P幀

前向預測編碼幀。P幀表示的是這一幀跟之前的一個關鍵幀(或P幀)的差別,解碼時需要用之前緩存的畫面疊加上本幀定義的差別,生成最終畫面。(也就是差別幀,P幀沒有完整畫面數據,只有與前一幀的畫面差別的數據)
P幀的預測與重構:P幀是以I幀爲參考幀,在I幀中找出P幀“某點”的預測值和運動矢量,取預測差值和運動矢量一起傳送。在接收端根據運動矢量從I幀中找出P幀“某點”的預測值並與差值相加以得到P幀“某點”樣值,從而可得到完整的P幀。
P幀特點:
1)P幀是I幀後面相隔1~2幀的編碼幀;
2)P幀採用運動補償的方法傳送它與前面的I或P幀的差值及運動矢量(預測誤差);
3)解碼時必須將I幀中的預測值與預測誤差求和後才能重構完整的P幀圖像;
4)P幀屬於前向預測的幀間編碼。它只參考前面最靠近它的I幀或P幀;
5)P幀可以是其後面P幀的參考幀,也可以是其前後的B幀的參考幀;
6)由於P幀是參考幀,它可能造成解碼錯誤的擴散;
7)由於是差值傳送,P幀的壓縮比較高。

6.2.3 B幀

雙向預測內插編碼幀。B幀是雙向差別幀,也就是B幀記錄的是本幀與前後幀的差別(具體比較複雜,有4種情況,但我這樣說簡單些),換言之,要解碼B幀,不僅要取得之前的緩存畫面,還要解碼之後的畫面,通過前後畫面的與本幀數據的疊加取得最終的畫面。B幀壓縮率高,但是解碼時CPU會比較累。
B幀的預測與重構
B幀以前面的I或P幀和後面的P幀爲參考幀,“找出”B幀“某點”的預測值和兩個運動矢量,並取預測差值和運動矢量傳送。接收端根據運動矢量在兩個參考幀中“找出(算出)”預測值並與差值求和,得到B幀“某點”樣值,從而可得到完整的B幀。
B幀特點
1)B幀是由前面的I或P幀和後面的P幀來進行預測的;
2)B幀傳送的是它與前面的I或P幀和後面的P幀之間的預測誤差及運動矢量;
3)B幀是雙向預測編碼幀;
4)B幀壓縮比最高,因爲它只反映丙參考幀間運動主體的變化情況,預測比較準確;
5)B幀不是參考幀,不會造成解碼錯誤的擴散。

注:I、B、P各幀是根據壓縮算法的需要,是人爲定義的,它們都是實實在在的物理幀。一般來說,I幀的壓縮率是7(跟JPG差不多),P幀是20,B幀可以達到50。可見使用B幀能節省大量空間,節省出來的空間可以用來保存多一些I幀,這樣在相同碼率下,可以提供更好的畫質。

6.3 NALU

H.264 原始碼流(又稱爲裸流),是有一個接一個的 NALU 組成的,而它的功能分爲兩層:

  • 視頻編碼層(VCL, Video Coding Layer):包括核心壓縮引擎和塊,宏塊和片,設計目標是儘可能的獨立於網絡進行高效的編碼。

  • 網絡提取層(NAL, Network Abstraction Layer):負責將VCL產生的比特字符串適配到各種各樣的網絡和多元環境中,覆蓋所有片級以上的語法級別。

VCL 數據即編碼處理的輸出,它表示被壓縮編碼後的視頻數據序列。在 VCL 數據傳輸或存儲之前,這些編碼的 VCL 數據,先被映射或封裝進 NAL 單元(以下簡稱 NALU,Nal Unit) 中。
每個 NALU 包括一個原始字節序列負荷(RBSP, Raw Byte Sequence Payload)、一組 對應於視頻編碼的 NALU 頭部信息。
RBSP 的基本結構是:在原始編碼數據的後面填加了結尾 比特。一個 bit“1”若干比特“0”,以便字節對齊。
在這裏插入圖片描述
NAL 單元排列
上圖中的 NALU頭 + RBSP 就相當與一個 NALU (Nal Unit), 每個單元都按獨立的 NALU 傳送。 其實說白了,H.264 中的結構全部都是以 NALU 爲主的,理解了 NALU,就理解 H.264 的結構了。
還有另外一種結果startcode + NALU頭 + RBSP(我也是剛接觸,也不知道究竟是哪個)三部分組成,其中startcode用於標識這是一個NALU單元的開始,必須是“00 00 00 01”或“00 00 01”。

6.3.1 NALU頭解析

每個NAL單元是一個一定語法元素的可變長字節字符串,包括包含一個字節的頭信息(用來表示數據類型),以及若⼲整數字節的負荷數據。

NALU頭信息(一個字節):
在這裏插入圖片描述
forbidden_bit(F):禁止位,在h264規範中這一位必須爲0.
nal_reference_bit(R):當前NAL的優先級,值越大,該NAL越重要。如果00的話NALU解碼器可以丟棄它而不影響錄像的回放,比如SEI幀。
在這裏插入圖片描述
nal_unit_type (T):NAL類型,112由h264使用,2431由h264以爲的應用使用,詳情參見下表:
在這裏插入圖片描述
5是IDR幀 6是SEI 7是SPS 8是PPS

H264標準指出,當數據流是存儲在介質上時,在每個NALU前添加起始碼:0x000001或0x00000001,用來指示一個NALU的起始和終止位置:

  • 在這樣的機制下,在碼流中檢測起始碼,作爲一個NALU得起始標識,當檢測到下一個起始碼時,當前NALU結束。

  • 3字節的0x000001只有一種場合下使用,就是一個完整的幀被編爲多個slice(片)的時候,包含這些slice的NALU使用3字節起始碼。其餘場合都是4字節0x00000001的。

(上面遺留的問題就找到答案了,是存儲在介質中,才需要一個NALU一個開始,只有這樣隔離了才能知道一個NALU和一個NALU,如果是網絡傳輸的話,就不要,這個問題後面分析rtmp的時候再看看)

6.3.2 NALU流解析

上面講解了NALU的基本信息,還有NALU的頭信息,發現這個頭信息有好多種類型,所以這裏就先對h264碼流做一個講解,我們之前瞭解了I幀,P幀,B幀,並且瞭解到這些編碼後的VCL數據會封裝到NALU中,然後再上一節又瞭解了到SPS,PPS,這些信息是怎麼串聯起來的呢?我之前就是一直對這個模糊不理解,所以一直對h264結構比較迷糊,現在看到丹老師的一個圖片之後,瞬間醍醐灌頂,分享一下給大家看看:
在這裏插入圖片描述
多麼有意義的一幅圖,寫明瞭很多東西,還是介紹一下圖裏面的各種幀
SPS:序列參數集,SPS中保存了一組編碼視頻序列(Coded video sequence)的全局參數。
PPS:圖像參數集,對應的是一個序列中某一幅圖像或者某幾幅圖像的參數。
I幀:幀內編碼幀,可獨立解碼生成完整的圖片
P幀:前向預測編碼幀,需要參考前面一個I後者B來生成一張完整的圖片。
B幀:雙向預測內插編碼幀,則要參考器前一個I或者P幀及其後面一個P幀來生成一張完整圖片。

發I幀之前,至少要發一次SPS和PPS
現在是不是明星的感覺到了h264碼流是一個接一個的NALU組成的,把其中有效的信息都封裝進NALU中,這樣就組成了一個碼流。這裏是不是有人要問,爲什麼I幀和P幀會有兩個NALU,這個後面會介紹,這裏就先留一個疑問,有疑問纔有激情學習。

6.3.3 GOP

都講到碼流部分了,就不得不提一樣GOP的概念:
在H264中圖像以序列爲單位進行組織(GOP),一個序列是一段圖像編碼後的數據流,以I幀開始,到下一個I幀結束。(上一節的圖可以看成是一個GOP,一個GOP只有一個I幀)
一個序列的第一個圖像叫做 IDR 圖像(立即刷新圖像),IDR 圖像都是 I 幀圖像。H.264 引入 IDR 圖像是爲了解碼的重同步,當解碼器解碼到 IDR 圖像時,立即將參考幀隊列清空,將已解碼的數據全部輸出或拋棄,重新查找參數集,開始一個新的序列。這樣,如果前一個序列出現重大錯誤,在這裏可以獲得重新同步的機會。IDR圖像之後的圖像永遠不會使用IDR之前的圖像的數據來解碼。
一個序列就是一段內容差異不太大的圖像編碼後生成的一串數據流。當運動變化比較少時,一個序列可以很長,因爲運動變化少就代表圖像畫面的內容變動很小,所以就可以編一個I幀,然後一直P幀、B幀了。當運動變化多時,可能一個序列就比較短了,比如就包含一個I幀和3、4個P幀。

GOP說白了就是兩個I幀之間的間隔.比較說GOP爲120,如果是720p60的話,那就是2s一次I幀.
在視頻編碼序列中,GOP即Group of picture(圖像組),指兩個I幀之間的距離,Reference(參考週期)指兩個P幀之間的距離(如下圖3.1)。一個I幀所佔用的字節數大於一個P幀,一個P幀所佔用的字節數大於一個B幀(如下圖3.1所示)。
在這裏插入圖片描述
所以在碼率不變的前提下,GOP值越大,P、B幀的數量會越多,畫面細節更多,也就更容易獲取較好的圖像質量;Reference越大,B幀的數量越多,同理也更容易獲得較好的圖像質量。

需要說明的是,通過提高GOP值來提高圖像質量是有限度的,在遇到場景切換的情況時,H.264編碼器會自動強制插入一個I幀,此時實際的GOP值被縮短了。另一方面,在一個GOP中,P、B幀是由I幀預測得到的,當I幀的圖像質量比較差時,會影響到一個GOP中後續P、B幀的圖像質量,直到下一個GOP開始纔有可能得以恢復,所以GOP值也不宜設置過大。
同時,由於P、B幀的複雜度大於I幀,所以過多的P、B幀會影響編碼效率,使編碼效率降低。另外,過長的GOP還會影響Seek操作(找I幀)的響應速度,由於P、B幀是由前面的I或P幀預測得到的,所以Seek操作需要直接定位,解碼某一個P或B幀時,需要先解碼得到本GOP內的I幀及之前的N個預測幀纔可以,GOP值越長,需要解碼的預測幀就越多,seek響應的時間也越長。

從宏觀的角度看,我們的視頻流被h264分成好多個組(GOP),每一個組中,包含了一個SPS,PPS,I幀,P幀,B幀。然後NALU數據就是封裝上面各種幀的,所以接下來分析的是NALU的RBSP部分,看看怎麼把不同類型的幀封裝到NALU中的。

6.3.4 NALU的RBSP

上面兩小節講了其他部分,不過也需要講一講其他部分,畢竟上面的部分是從比較大的視角看h264碼流結構,接下來就從一個NALU分析,

究竟 NALU 是怎麼由一幀圖片變化而來的呀,H.264究竟爲什麼這麼神奇?

一幀圖片經過 H.264 編碼器之後,就被編碼爲一個或多個片(slice),而裝載着這些片(slice)的載體,就是 NALU 了,我們可以來看看 NALU 跟片的關係(slice)。
在這裏插入圖片描述
圖片編碼後

在這裏插入圖片描述
NALU 結構

小夥伴們要明白,片(slice)的概念不同與幀(frame),幀(frame)是用作描述一張圖片的,一幀(frame)對應一張圖片,而片(slice),是 H.264 中提出的新概念,是通過編碼圖片後切分通過高效的方式整合出來的概念,一張圖片至少有一個或多個片(slice)。
上圖中可以看出,片(slice)都是又 NALU 裝載並進行網絡傳輸的,但是這並不代表 NALU 內就一定是切片,這是充分不必要條件,因爲 NALU 還有可能裝載着其他用作描述視頻的信息。

看到這裏,是不是就瞬間明白了,爲什麼上面的I幀包含了兩個NALU,就是因爲切片了,是不是還記得,一個幀之後,如果切片的NALU數據是通過0x000001來分割的,我們就拿二進制數據找一找
既然都要找了,就把之前的SPS和PPS的數據也找一下,其實雷神自己寫了一個軟件就是分析h264碼流的,在這裏我們也能清楚的看到h264碼流的編碼情況,不過還是爲了過癮,我們自己去找一下二進制的編碼,看看是不是真的就是這樣
在這裏插入圖片描述

SEI:
在這裏插入圖片描述
這是SEI幀:nal_unit_type 爲6,nal_ref_idc 爲0,是可以丟棄的

SPS、PPS?IDR
在這裏插入圖片描述
這幅圖都找齊了,0x67,nal_unit_type 爲7 就是SPS幀,0x68,nal_unit_type 爲8 就是PPS幀,0x65,nal_unit_type 爲5 就是I幀,算了算了,不找那種一幀數據分爲兩個NALU的數據了,太多二進制數了,不好找。

6.3.5 NALU的片

片的主要作用是用作宏塊(Macroblock)的載體(ps:下面會介紹到宏塊的概念)。片之所以被創造出來,主要目的是爲限制誤碼的擴散和傳輸。
如何限制誤碼的擴散和傳輸?
每個片(slice)都應該是互相獨立被傳輸的,某片的預測(片(slice)內預測和片(slice)間預測)不能以其它片中的宏塊(Macroblock)爲參考圖像。

那麼片(slice)的具體結構,我們用一張圖來直觀說明吧:
在這裏插入圖片描述
我們可以理解爲一 張/幀 圖片可以包含一個或多個分片(Slice),而每一個分片(Slice)包含整數個宏塊(Macroblock),即每片(slice)至少一個 宏塊(Macroblock),最多時每片包 整個圖像的宏塊。

上圖結構中,我們不難看出,每個分片也包含着頭和數據兩部分:
1、分片頭中包含着分片類型、分片中的宏塊類型、分片幀的數量、分片屬於那個圖像以及對應的幀的設置和參數等信息。
2、分片數據中則是宏塊,這裏就是我們要找的存儲像素數據的地方。

這個細節就不用找了,再找就找不下去了,知道就好。

6.3.5 NALU的宏塊

宏塊是視頻信息的主要承載者,因爲它包含着每一個像素的亮度和色度信息。視頻解碼最主要的工作則是提供高效的方式從碼流中獲得宏塊中的像素陣列。
組成部分:一個宏塊由一個16×16亮度像素和附加的一個8×8 Cb和一個 8×8 Cr 彩色像素塊組成。每個圖象中,若干宏塊被排列成片的形式。

我們先來看看宏塊的結構圖:
在這裏插入圖片描述
從上圖中,可以看到,宏塊中包含了宏塊類型、預測類型、Coded Block Pattern、Quantization Parameter、像素的亮度和色度數據集等等信息。

講了這裏是不是又看了熟悉東西,QP值,還有YUV的數據,我們獲取的視頻流本來就是YUV數據,然後經過h264層層加工,才形成我們最後的h264編碼

切片(slice)類型跟宏塊類型的關係:
對於切片(slice)來講,分爲以下幾種類型:
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 也是擴展檔次中的必備功能。

6.4 整體總結

通過剖析了這麼多個小零件,是時候個大家一個世界地圖了,
那麼我們的 NALU 整體結構可以呼之欲出了,以下就引用 H.264 文檔當中的一幅圖了
在這裏插入圖片描述
其實 H.264 的碼流結構並沒有大家想的那麼複雜,編碼後視頻的每一組圖像(GOP,圖像組)都給予了傳輸中的序列(PPS)和本身這個幀的圖像參數(SPS),所以,我們的整體結構,應該如此:
在這裏插入圖片描述

這一篇文章總體都是借鑑了網上各個大神的總結,再由我自己整理總結,把這些知識消化成自己的知識,總結了這一篇文章之後,終於對h264的結構有總體的認識了,所以這篇文章就寫成轉載把,參考的原文鏈接在開頭,想看的可以去看看。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章