Android直播開發之旅(2):深度解析H.264編碼原理
(碼字不易,轉載請申明出處:http://blog.csdn.net/andrexpert/article/details/71774230 )
前 言
在學習H.264編碼之前,我們先了解一下在視訊直播的過程中,如果Camera採集的YUV影象不做任何處理進行傳輸,那麼每秒鐘需要傳輸的資料量是多少?Camera採集的YUV影象通常為YUV420,根據YUV420的取樣結構,YUV影象中的一個畫素中Y、U、V分量所佔比例為1:1/4:1/4,而一個Y分量佔1個位元組,也就是說對於YUV影象,它的一個畫素的大小為(1+1/4+1/4)*Y=3/2個位元組。如果直播時的幀率設定為30fps,當解析度為1280x720,那麼每秒需要傳輸的資料量為1280*720(畫素)*30(幀)*3/2(位元組)=39.5MB;當解析度為1920x720,那麼每秒需要傳輸的資料量接近60MB,而在現實網路中,這麼高的上行寬頻一般是很難達到的,因此,我們就必須在傳輸之前對採集的視訊資料進行壓縮編碼。
1. H.264簡介
H.264是MPEG-4的第十部分,是由VCEG和MPEG聯合提出的高度壓縮數字視訊編碼器標準,目前在多媒體開發應用中非常廣泛。H.264具有低位元速率、高壓縮、高質量的影象、容錯能力強、網路適應性強等特點,它最大的優勢擁有很高的資料壓縮比率,在同等影象質量的條件下,H.264的壓縮比是MPEG-2的兩倍以上。
(1) H.264編碼原理
在H.264協議裡定義了三種幀,完整編碼的幀叫I幀(關鍵幀),參考之前的I幀生成的只包含差異部分編碼的幀叫P幀,還有一種參考前後的幀編碼的幀叫B幀。H.264編碼採用的核心演算法是幀內壓縮和幀間壓縮。其中,幀內壓縮是生成I幀的演算法,它的原理是當壓縮一幀影象時,僅考慮本幀的資料而不用考慮相鄰幀之間的冗餘資訊,由於幀內壓縮是編碼一個完整的影象,所以可以獨立的解碼顯示;幀間壓縮是生成P、B幀的演算法,它的原理是通過對比相鄰兩幀之間的資料進行壓縮,進一步提高壓縮量,減少壓縮比。通俗的來說,H.264編碼的就是對於一段變化不大影象畫面,我們可以先編碼出一個完整的影象幀A,隨後的B幀就不編碼全部影象,只寫入與A幀的差別,這樣B幀的大小就只有完整幀的1/10或更小。B幀之後的C幀如果變化不大,我們可以繼續以參考B的方式編碼C幀,這樣迴圈下去。
H.264編碼框架分兩層:
VCL(Video Coding Layer):負責高效的視訊內容表示;
NAL(Network Abstraction Layer):負責以網路所要求的恰當的方式對資料進行打包和傳送;
(2) IDR(I幀)、SPS、PPS、SES、P/B幀
a) IDR(Instantaneous Decoding Refresh):即時解碼重新整理。一個序列的第一個影象叫做IDR 影象(立即重新整理影象),IDR 影象都是I 幀影象(關鍵幀)。H.264引入 IDR 影象是為了解碼的重同步,當解碼器解碼到IDR 影象時,立即將參考幀佇列清空,將已解碼的資料全部輸出或拋棄,重新查詢引數集,開始一個新的序列。這樣,如果前一個序列出現重大錯誤,在這裡可以獲得重新同步的機會。IDR影象之後的影象永遠不會使用IDR之前的影象的資料來解碼。
b) SPS(Sequence Parameter Sets):序列引數集,作用於一系列連續的編碼影象。
c) PPS(Picture Parameter Set):影象引數集,作用於編碼視訊序列中一個或多個獨立的影象。
d) SEI(Supplemental Enhancement Information):附加增強資訊,包含了視訊畫面定時等資訊,一般放在主編碼影象資料之前,在某些應用中,它可以被省略掉。
e) P幀:前向預測編碼幀。P幀表示的是這一幀跟之前的一個關鍵幀(或P幀)的差別,它P幀是I幀後面相隔1~2幀的編碼幀,其沒有完整畫面資料,只有與前一幀的、畫面差別的資料。
f) B幀:雙向預測內插編碼幀。B幀記錄的是本幀與前後幀的差別,它是由前面的I或P幀和後面的P幀來進行預測的。
(3) H.264與X.264區別
H.264是需要付費的編碼格式,而x264是符合H.264標準的一個開源專案,是免費的,也就是H264的一個簡化版,不支援某些高階特性。但x264非常優秀,並不比H264的商業編碼器差。H264採用的核心演算法是幀內壓縮和幀間壓縮,幀內壓縮是生成I幀的演算法,幀間壓縮是生成B幀和P幀的演算法。
2. H.264資料組織形式
通常,資料的組織形式從大到小排序是:序列(sequence)、影象(frame/field-picture)、片組(slicegroup)、片(slice)、巨集塊(macroblock)、塊(block)、子塊(sub-block)、畫素(pixel)。在H.264碼流中影象是以序列為單位進行組織的,一個序列是由多幀影象被編碼後的資料流,以I幀開始,到下一個I幀結束;一幀影象可以分成一個或多個片(slice),片由巨集塊組成,巨集塊是編碼處理的基本單位,當片編碼之後會被打包進一個NALU,也就是一幀影象對應於一個NALU。NALU是H.264編碼資料儲存或傳輸的基本單位,它除了容納片還可以容納其他資料,如SPS、PPS、SEI等。
根據H.264編碼原理可知,一個序列是一段內容差異不太大的影象編碼生成的一串資料流。當運動變化比較少時,一個序列可以很長,這是由於運動變化少就代表影象畫面的內容變動就很小,所有就可以編一個I幀,然後後面一直P幀、B幀;當運動變化較大,可能這個序列就比較短,因為影象畫面的內容變動大,所以P幀、B幀就相對減少。總之,一個序列總是以I幀為開始,到下一個I幀結束,序列包含的影象幀的數量與畫面變化情況有關。
3. H.264中的NAL技術
從H.264的介紹可知,NAL是H.264/AVC編碼框架中的網路抽象層,即NetworkAbstract Layer,它主要負責格式化資料並提供頭資訊,以保證資料適合各種通道和儲存介質上的有效傳輸。由於實際中的資訊傳輸系統可靠性、封裝方式、服務質量等特徵的多樣化,NAL提供了一個視訊編碼器和傳輸系統的友好介面,使得編碼後的視訊資料能夠有效地在各種不同的網路環境中傳輸。
在NAL層中,NALU(Network Abstract Layer Unit,網路抽象層單元)是H.264編碼儲存或傳輸的基本單位,在H.264碼流中每一幀資料就是一個NALU(注:SPS、PPS、SEI不屬於幀)。每個NALU都包含一個頭結構,這個頭結構佔1個位元組(8位),它標明瞭該NAL單元的是否可丟棄、重要性指示和NALU型別,結構如下:
其中:
(1) 禁止位:當網路發現NALU存在錯誤時,該位將被設定為1以方便接收方丟棄該NALU;
(2) 重要性指示:用於標誌該NALU用於重建時的重要性,其值越大表示越重要;
(3) NALU型別:用於判斷該NALU是否為PPS、SPS、I(關鍵)/P/B幀等,一般H.264碼流
最開始的兩個NALU是SPS和PPS,第三個NALU是IDR(I幀)。NALU型別是判斷幀型別重要工具,至於如何去利用它來實現SPS、PPS和I/P/B幀等資訊的檢測,我們接下來會舉例詳細介紹,以下是相關值與NALU型別的對映關係圖:
從上圖可知,當NALU型別=5時,說明該NALU是關鍵幀(I幀);當NALU型別=6時,說明該NALU是附加增強資訊;當NALU型別=7時,說明該NALU是序列引數集(SPS);當NALU型別=8時,說明該NALU是影象引數集(PPS),依次類推。
4. H.264中SPS、PPS、I/P/B幀檢測與解析
(1) H.264碼流分層結構
在分析SPS、PPS、I/P/B幀之前,我們先了解下H.264碼流分層的結構。從外往裡看,H.264碼流實際是由多個NALU組成的碼流序列集合(如第一層所示),而一個序列是以I幀開始,以下一個I幀結束。NALU是H.264編碼儲存或傳輸的基本單元,NALU由NALU 頭部和NALU主體組成(如第二層所示),其中,NALU頭部佔1個位元組,H.264中的SPS、PPS、I/P/B幀的檢測正是通過NALU頭部中的NALU型別來實現的。
H.264碼流分層結構如下圖所示:
(2) H.264檔案解析
一般來說,編碼器編出的首幀資料為SPS與PPS,接著為I幀(關鍵幀),再後面就是P幀、B幀…。而對於H.264碼流而言,每幀影象的界定符為0x00000001、0x000001,也稱起始碼,它們分別佔4個位元組或3個位元組,而起始碼的後的一個位元組便是NALU頭,通過這個位元組我們就可以很簡單的找到所需的SPS、PPS、I/P/B幀。這裡,我們通過分析一個H.264檔案來進行講解,使用H.264 Video ES Viewer工具開啟一個test.264檔案,至於h264檔案的生成,我將再下一篇博文進行詳細介紹,H264碼流結構如下圖:
從上圖可知,每一行表示一幀影象(除SPS、PPS除外),每一行包括四列,其中第一列為該幀影象儲存的邏輯地址;第二列為該幀影象資料所佔位元組長度,由於H264的編碼原理可以知道,H264碼流中的每一幀影象並不是實際上的一幀影象,而是多幀影象的集合;第三列表示影象幀的起始碼,均為0x00000001;第四列表示的是NAL型別,由圖可知,H264編碼器編出的首幀資料為SPS與PPS,接著為I幀(關鍵幀),再後面就是P幀或B幀(非I幀)…
(3) SPS、PPS、I/P/B幀檢測
有了前面的理論和分析基礎,再來判斷H.264碼流中的(3) SPS、PPS、I/P/B幀,那就顯得非常容易了。我們知道,H.264碼流中的一幀資料總是以0x00000001或0x000001開始的,起始碼的下一位就是NALU頭,比如第一幀資料NALU頭為0x67,我們擷取碼流中的前幾幀資料進行分析:
第一幀:0000 00 01 67 42 80 1F DA 02 D0 28 68 ….(佔17個位元組)
第二幀:0000 00 01 68 CE 06 E2 (佔8個位元組)
第三幀:0000 00 01 65 B8 40 F7 8F FC EB 04 …. (佔31872個位元組)
第四幀:0000 00 01 41 E2 01 10 EA 4E 9F … (佔3408位元組)
第五幀:0000 00 01 41 E4 01 10 EC 7B DF 13 … (佔2096個位元組)
由於NALU型別由NALU頭的後五位決定的,即位元組下標的3-7位,我們只需要得到這五位的十進位制值,再與NAL型別對照表進行比較就可以知道該幀影象是否為SPS、PPS或I幀(關鍵幀)等。在編碼過程中,我們可以通過將每幀資料起始碼的下一個位元組與0x1F相與取得NAL頭的後五位即可得到該幀的型別,比如:
0x67& 0x1F = (0110 0111) & (0001 1111) =(0000 0111)=7(十進位制)–> SPS
0x68& 0x1F = (0110 1000) & (0001 1111) =(0010 1000)=8(十進位制)–> PPS
0x65& 0x1F = (0110 0101) & (0001 1111) =(0000 0101)=5(十進位制)–> 關鍵幀(I幀)
0x41 & 0x1F = (01000001) & (0001 1111) =(0000 0001)=1(十進位制) –> 非關鍵幀(I幀)
碼字不易,轉載請申明出處:http://blog.csdn.net/andrexpert/article/details/71774230
參考: