【音視頻】H264編碼基礎(chǔ)
點(diǎn)擊關(guān)注,與你共同成長!

H264編碼基礎(chǔ)
0x1 基本介紹
視頻是由一幀幀圖像組成,視頻為了不卡頓,一秒鐘至少要16幀畫面,但是圖片內(nèi)容太大,傳輸不現(xiàn)實(shí)。因此需要對(duì)他們編碼。
官方文檔:http://www.itu.int/rec/T-REC-H.264 翻譯文檔:http://www.itu.int/rec/T-REC-H.264-200503-S/en
0x11 過程
H264通過比較相鄰幾張圖像的像素差(編碼),來保存這幾張圖片的真實(shí)數(shù)據(jù),當(dāng)兩張圖片的像素點(diǎn)不大的時(shí)候則不去編碼來減小傳遞張數(shù)(壓縮)。來大大提高視頻質(zhì)量和傳輸速率。
例:
編碼得出一個(gè)完整的圖片A 將第二張圖片與第一張圖片A進(jìn)行比較,獲取的像素差,作為第二張圖片B 同上,獲取圖片C 當(dāng)此幀圖片與C想差很大時(shí),我們重新對(duì)此幀進(jìn)行編碼,獲取圖片D 同上

編碼的原理:
空間冗余:同一幅圖像的相鄰像素點(diǎn)具有連貫性。 時(shí)間冗余:一組連續(xù)的畫面之間存在著關(guān)聯(lián)性。 結(jié)構(gòu)冗余:某些場景中存在著明顯的圖像分布模式,有規(guī)律可循。比如:方格地板,蜂窩。 視覺冗余:人眼對(duì)圖像細(xì)節(jié)的分辨率有限,對(duì)亮度比對(duì)色度更敏感。 知識(shí)冗余:許多圖像的理解和某些知識(shí)有關(guān)聯(lián)。比如人類面部的固定結(jié)構(gòu)。
在進(jìn)行當(dāng)前信號(hào)編碼時(shí),編碼器首先會(huì)產(chǎn)生對(duì)當(dāng)前信號(hào)做預(yù)測的信號(hào),稱作預(yù)測信號(hào)。也是為了解決兩個(gè)方向的數(shù)據(jù)冗余。
時(shí)間上的預(yù)測(interprediction):由于視頻相鄰的兩幀之間內(nèi)容相似,使用先前幀的信號(hào)做預(yù)測。 空間上的預(yù)測 (intra prediction):視頻的某一幀內(nèi)部的相鄰像素存在相似性,即使用同一張幀之中相鄰像素的信號(hào)做預(yù)測
0x111 視頻壓縮編碼的基本技術(shù)
0x1111 預(yù)測編碼
視頻處理中的預(yù)測編碼主要分為兩大類:幀內(nèi)預(yù)測和幀間預(yù)測。
時(shí)間上的預(yù)測(幀間預(yù)測) 幀間預(yù)測的實(shí)際值位于當(dāng)前幀,預(yù)測值位于參考幀,用于消除圖像的時(shí)間冗余。例如我們只需要保存一幀的圖形數(shù)據(jù),而其他的幀都在這一幀上按規(guī)則預(yù)測出來。幀間預(yù)測的壓縮率高于幀內(nèi)預(yù)測,然而不能獨(dú)立解碼,必須在獲取參考幀數(shù)據(jù)之后才能重建當(dāng)前幀。 空間上的預(yù)測(幀內(nèi)預(yù)測) 預(yù)測值與實(shí)際值位于同一幀,用于消除圖像的空間冗余。例如人眼在對(duì)圖像識(shí)別時(shí),對(duì)低頻的亮度很敏感,而對(duì)高頻下的亮度不太敏感。幀內(nèi)預(yù)測技術(shù)就是對(duì)人眼中不敏感的數(shù)據(jù)去除。幀內(nèi)預(yù)測的特點(diǎn)是壓縮率相對(duì)較低,然而可以獨(dú)立解碼,不依賴其他幀的數(shù)據(jù),通常在視頻中的關(guān)鍵幀都采用幀內(nèi)預(yù)測。
通常在視頻碼流中,I幀全部使用幀內(nèi)編碼,P幀/B幀中的數(shù)據(jù)可能使用幀內(nèi)或者幀間編碼。
0x1112 變換編碼
為了提高編碼效率,主流的視頻編碼均屬于有損編碼,因此會(huì)對(duì)視頻內(nèi)的圖片造成信息損失。視頻編碼算法常用正交變換進(jìn)行變換編碼,常用的變換方法有:**離散余弦變換(DCT)、離散正弦變換(DST)**等。
DCT
將上述的殘差數(shù)據(jù)做整數(shù)離散余弦變換,去除數(shù)據(jù)的相關(guān)性,進(jìn)行二次壓縮數(shù)據(jù)。如一個(gè)圖片為 4x4 的圖片矩陣:

變換后:

可以看到能量(低頻信號(hào))集中到了左上方,而變換后的數(shù)據(jù)完全可以通過反變換還原。為了達(dá)到壓縮數(shù)據(jù),我們需要丟棄掉一些能量低的數(shù)據(jù)(高頻信號(hào)),而對(duì)圖像質(zhì)量影響很小。
最后再去除相關(guān)性,可以得到更大的壓縮量。具體的轉(zhuǎn)換可以看下大佬的博客:https://www.cnblogs.com/xkfz007/archive/2012/07/31/2616791.html
0x1113 熵編碼
主要用于消除視頻信息中的統(tǒng)計(jì)冗余。由于信號(hào)中每個(gè)符號(hào)出現(xiàn)的概率并不一致,導(dǎo)致使用統(tǒng)一長度的碼字表示所有符號(hào)會(huì)造成浪費(fèi)。通過熵編碼,可以針對(duì)不同語法元素分配不同長度的碼元,消除視頻信息中由于符號(hào)概率導(dǎo)致的冗余。常用的編碼方式有:指數(shù)哥倫布編碼(UVLC)、變長編碼(CAVLC)、二進(jìn)制算術(shù)編碼(CABAC)等。
CABAC
上面的幀內(nèi)壓縮是屬于有損壓縮技術(shù)。也就是說圖像被壓縮后,無法完全復(fù)原。而CABAC屬于無損壓縮技術(shù)。屬于熵編碼的一種,包括霍夫曼編碼、指數(shù)哥倫布編碼、CABAC等等。這類編碼主旨就是,找到一種編碼,使得碼字的平均碼長達(dá)到熵極限。如:取得概率較大的符號(hào),取較短的碼長,而對(duì)于概率較小的符號(hào),取較大的碼長。
熵:信息越是隨機(jī),它的熵值越高。而信息熵,就是為了解決信息的量化度量問題,它描述了整個(gè)信源的平均信息量。在使用熵編碼時(shí),碼字的平均碼長盡量達(dá)到熵極限,表明熵編碼的壓縮效率越高。
H264編碼使用的是0階哥倫布編碼方式壓縮,但是這種方式可能在某些時(shí)候不減數(shù)據(jù)量,反而增大。而使用CABAC則可以大大減小數(shù)據(jù)量
0x1114 運(yùn)動(dòng)估計(jì)和運(yùn)動(dòng)補(bǔ)償
運(yùn)動(dòng)估計(jì):將當(dāng)前的輸入圖像分割成若干不相重疊的小圖像子塊,然后在前一或者后一圖像某個(gè)搜索窗口的范圍內(nèi)為每一個(gè)圖像塊尋找一個(gè)與之最為相似的圖像塊。 運(yùn)動(dòng)補(bǔ)償:通過計(jì)算最相似的圖像塊與該圖像塊之間的位置信息,得到了一個(gè)運(yùn)動(dòng)矢量。在編碼過程中就可以將當(dāng)前圖像中的塊與參考圖像運(yùn)動(dòng)矢量所指向的最相思的圖像塊相減,得到一個(gè)殘差圖像塊。
0x112 H264編碼過程
簡介
每一幀的H圖像被分為一個(gè)或多個(gè)條帶(slice)進(jìn)行編碼。
每一個(gè)條帶包含多個(gè)宏塊(MB)。他是H264中基本的編碼單元,基本結(jié)構(gòu)包含一個(gè)16×16個(gè)亮度像素塊和兩個(gè)8×8色度像素塊,以及其他一些宏塊頭信息。
在對(duì)宏塊進(jìn)行編碼時(shí),每一個(gè)宏塊會(huì)分割成多種不同大小的子塊進(jìn)行預(yù)測。幀內(nèi)預(yù)測采用的塊大小可能為16×16或者4×4,幀間預(yù)測采用的塊可能有7種不同的形狀:16×16、16×8、8×16、8×8、8×4、4×8和4×4。
在變換編碼方面,針對(duì)預(yù)測殘差數(shù)據(jù)進(jìn)行的變換塊大小為4×4或8×8。
H.264標(biāo)準(zhǔn)中采用的熵編碼方法主要有上下文自適應(yīng)的變長編碼CAVLC和上下文自適應(yīng)的二進(jìn)制算數(shù)編碼CABAC,根據(jù)不同的語法元素類型指定不同的編碼方式。通過這兩種熵編碼方式達(dá)到一種編碼效率與運(yùn)算復(fù)雜度之間的平衡。
條帶也具有不同的類型,最常用的有I條帶、P條帶和B條帶。另外,為了支持碼流切換,在擴(kuò)展檔次中還定義了SI和SP片。
I條帶:幀內(nèi)編碼條帶,只包含I宏塊; P條帶:單向幀間編碼條帶,可能包含P宏塊和I宏塊; B條帶:雙向幀間編碼條帶,可能包含B宏塊和I宏塊; 視頻編碼中采用的如預(yù)測編碼、變化量化、熵編碼等編碼工具主要工作在slice層或以下,這一層通常被稱為**視頻編碼層(Video Coding Layer, VCL)**。
相對(duì)的,在slice以上所進(jìn)行的數(shù)據(jù)和算法通常稱之為**網(wǎng)絡(luò)抽象層(Network Abstraction Layer, NAL)**。主要意義在于提升H.264格式的視頻對(duì)網(wǎng)絡(luò)傳輸和數(shù)據(jù)存儲(chǔ)的親和性。
H264的三種檔次
基準(zhǔn)檔次(Baseline Profile):主要用于視頻會(huì)議、可視電話等低延時(shí)實(shí)時(shí)通信領(lǐng)域;支持I條帶和P條帶,熵編碼支持CAVLC算法。 主要檔次(Main Profile):主要用于數(shù)字電視廣播、數(shù)字視頻數(shù)據(jù)存儲(chǔ)等;支持視頻場編碼、B條帶雙向預(yù)測和加權(quán)預(yù)測,熵編碼支持CAVLC和CABAC算法。 擴(kuò)展檔次(Extended Profile):主要用于網(wǎng)絡(luò)視頻直播與點(diǎn)播等;支持基準(zhǔn)檔次的所有特性,并支持SI和SP條帶,支持?jǐn)?shù)據(jù)分割以改進(jìn)誤碼性能,支持B條帶和加權(quán)預(yù)測,但不支持CABAC和場編碼。
交錯(cuò)視頻編碼
針對(duì)隔行掃描的視頻,H.264專門定義了用于處理此類交錯(cuò)視頻的算法。
PicAFF:Picture Adaptive Frame Field——圖像層的幀場自適應(yīng) MBAFF:MacroBlock Adaptive Frame Field——宏塊層的幀場自適應(yīng)
0x12 專業(yè)術(shù)語
一些需要提前準(zhǔn)備的信息打基礎(chǔ),否則大量的術(shù)語會(huì)懵了。
0x121 幀
上述的過程就是H264的編碼的大部分過程(核心算法)。這幾個(gè)取幀的命名有以下幾個(gè):
I幀:完整編碼。該幀可壓縮程度最低,也不需要通過其他視頻幀解碼。自身可以通過視頻解壓算法解壓成一張單獨(dú)的完整的圖片。
IDR幀:一個(gè)序列的第一個(gè)圖像叫做IDR圖像(立即刷新圖像),IDR幀都是I幀。引入IDR是為了在解碼的時(shí)候可以立即同步,將已解碼的數(shù)據(jù)全部拋出。
P幀:參考之前的I幀生成的只包含差異部分編碼的幀。該幀可以引用前面的幀的數(shù)據(jù)來解壓縮并且相對(duì)于I幀來說,該幀可以壓縮程度更高。需要參考其前面的一個(gè)I frame 或者B frame來生成一張完整的圖片。
B幀:參考前后的幀編碼的幀叫B幀。該幀可以引用前面的幀和后面的幀的數(shù)據(jù),從而壓縮程度最高。參考其前一個(gè)I或者P幀及其后面的一個(gè)P幀來生成一張完整的圖片。相較于P幀,壓縮量更大,預(yù)測效果更好,但是在實(shí)時(shí)互動(dòng)的情況下,會(huì)引起延時(shí),特別是在網(wǎng)絡(luò)較差的情況。I幀為幀內(nèi)壓縮,P幀和B幀為幀間壓縮。B幀不能作為參考幀。

0x122 場
幀和場都是來產(chǎn)生一個(gè)編碼的圖像,一幀都是一個(gè)完整的圖像,但是場卻不一定。在使用隔行掃描的時(shí)候,就會(huì)有一幀為兩場的情況。這是因?yàn)楫?dāng)屏幕過大導(dǎo)致的逐行掃描可能導(dǎo)致圖像上下亮度不一致導(dǎo)致的緩和顯示方式。
0x123 SPS 和 PPS
I幀、P幀、B幀都是被封裝成一個(gè)或者多個(gè)NALU進(jìn)行傳輸或者存儲(chǔ)的。?每一個(gè)I幀開始之前也有非VCL的NALU單元,用于保存其他信息,它們是PPS、SPS。
PPS(Picture Parameter Sets):圖像參數(shù)集 SPS(Sequence Parameter Set):序列參數(shù)集
0x124 GOP
在H264中圖像以序列為單位進(jìn)行組織,一個(gè)序列是一段圖像編碼后的數(shù)據(jù)流。也稱GOP序列,每一個(gè)序列的第一個(gè)圖片是IDR圖像(立即刷新圖像),也就是I幀。其算法是:在相鄰幾幅圖像畫面中,一般有差別的像素只有10%以內(nèi)的點(diǎn),亮度差值變化不超過2%,而色度差值的變化只有1%以內(nèi),我們認(rèn)為這樣的圖可以分到一組。

0x125 NALU
NALU是將每一幀數(shù)據(jù)寫入到一個(gè)NALU單元中,進(jìn)行傳輸或存儲(chǔ)的NALU設(shè)計(jì)的目的,是根據(jù)不同的網(wǎng)絡(luò)把數(shù)據(jù)打包成相應(yīng)的格式,將VCL產(chǎn)生的比特字符串適配到各種各樣的網(wǎng)絡(luò)和多元環(huán)境中。
NALU是將每一幀數(shù)據(jù)寫入到一個(gè)NALU單元中,進(jìn)行傳輸或存儲(chǔ)的。
NALU的封裝方式:NALU分為NALU頭和NALU體

0x126 字節(jié)流格式和RTP格式
多個(gè)NALU數(shù)據(jù)組合在一起形成總的輸出碼流。對(duì)于不同的應(yīng)用場景,NAL規(guī)定了一種通用的格式適應(yīng)不同的傳輸封裝類型。
字節(jié)流格式 大多數(shù)編碼器實(shí)現(xiàn)的默認(rèn)輸出格式。字節(jié)流格式以連續(xù)的bit字節(jié)形式傳輸碼流。 RTP包格式 包格式將NALU按照RTP數(shù)據(jù)包的格式封裝。因此不需要像流傳輸那樣分割識(shí)別碼,并且很好解包,但是封裝格式并沒有在標(biāo)準(zhǔn)協(xié)議文檔中明確規(guī)定,僅此使用起來會(huì)有一定風(fēng)險(xiǎn)。
0x127 片(slice)
每幀圖片中都含有多個(gè)切片,他們承載這多個(gè)宏塊數(shù)據(jù)。片是H264中提出的新概念,在編碼圖片后切分并整合出來的一個(gè)概念。
片之所以被創(chuàng)造出來,主要目的是為限制誤碼的擴(kuò)散和傳輸。使編碼片相互間是獨(dú)立的。某片的預(yù)測不能以其他片中的宏塊為參考圖像,這樣某一片中的預(yù)測誤差才不會(huì)傳播到其他片中。片又分為:切片頭和切片數(shù)據(jù)
類型:
I片:只包含I宏塊 P片:包含P和I宏塊 B片:包含B和I宏塊 SP片:包含P 和/或 I宏塊,用于不同碼流之間的切換 SI片:一種特殊類型的編碼宏塊 
0x128 宏塊(macroblock)和子塊
H264默認(rèn)是使用16x16大小的區(qū)域作為一個(gè)宏塊。宏塊是視頻信息的主要承載者。也就是我們最想知道的圖片信息(YUV格式)。一個(gè)編碼圖像通常劃分為多個(gè)宏塊組成。
如果需要更高的壓縮率,還可以在宏塊的基礎(chǔ)上劃出更小的子塊。如:16x8、8x16、8x8、.. 4x4。

0x129 運(yùn)動(dòng)方向/矢量(MV)
編碼圖像中的當(dāng)前宏塊相對(duì)于參考圖像的宏塊所移動(dòng)的距離和方向就是運(yùn)動(dòng)向量。
0x12A 運(yùn)動(dòng)估計(jì)(ME)
尋找最佳匹配宏塊的過程。
0x12B 運(yùn)動(dòng)補(bǔ)償(MC)
描述相鄰(編碼關(guān)系傷的相鄰,在播放順序上兩幀未必相鄰)差別的方法,例:描述前一幀的每個(gè)小塊如何移動(dòng)到當(dāng)前幀中的某個(gè)位置。
0x12C Inter-Prediction
參考幀(編碼后且經(jīng)過濾波)中的宏塊經(jīng)運(yùn)動(dòng)補(bǔ)償?shù)玫健?/p>
0x12D Intra-Prediction
根據(jù)當(dāng)前幀中已編碼的宏塊(未經(jīng)過濾波)對(duì)當(dāng)前宏塊預(yù)測。
0x12E 殘差(剩余)宏塊
當(dāng)前宏塊減去預(yù)測宏塊就得殘差宏塊,表示預(yù)測的誤差。
0x12F pts/dts
PTS(Presentation Time Stamp):PTS 主要用于度量解碼后的視頻幀什么時(shí)候被顯示出來。 DTS(Decode Time Stamp):DTS 主要是標(biāo)識(shí)內(nèi)存中的 bit 流什么時(shí)候開始送入解碼器中進(jìn)行解碼。

DTS 與 PTS 的不同:DTS 主要用戶視頻的解碼,在解碼階段使用。PTS主要用于視頻的同步和輸出,在 display 的時(shí)候使用。再?zèng)]有 B frame 的時(shí)候輸出順序是一樣的。
0x12F1(先這樣寫) 碼流分層

0x13 分層設(shè)計(jì)
0x131 層級(jí)
H264在概念上分為兩層:
VCL:視頻編碼層,負(fù)責(zé)高效的內(nèi)容表示。 NAL:網(wǎng)絡(luò)提取層,負(fù)責(zé)以網(wǎng)絡(luò)所要求的恰當(dāng)?shù)姆绞綄?duì)數(shù)據(jù)進(jìn)行打包和傳送。
0x2 編碼(原始碼)
264的兩種碼流格式,它們分別為:字節(jié)流格式和RTP包格式。
字節(jié)流格式:這是在h264官方協(xié)議文檔中規(guī)定的格式,所以它也成為了大多數(shù)編碼器,默認(rèn)的輸出格式。它的基本數(shù)據(jù)單位為NAL單元,也即NALU。 RTP包格式:在這種格式中,NALU并不需要起始碼Start_Code來進(jìn)行識(shí)別,而是在NALU開始的若干字節(jié)(1,2,4字節(jié)),代表NALU的長度。
0x21 起始碼與NALU
前面說到264的字節(jié)流就是起始碼+NALU+...
起始碼:00 00 00 01 或者 00 00 01 NALU:每一幀的封裝,分為 NALU header和EBSP

0x22 NALU
分為NALU header和EBSP。
0x221 NALU header
由三個(gè)元素組成:forbidden_zero_bit(1 bit)、nal_ref_idc(2 bit)和nal_unit_type(5 bit)。它們總共占據(jù)一個(gè)字節(jié)。
0x2211 forbidden_zero_bit - 1bit
這個(gè)值應(yīng)該為0,當(dāng)它不為0時(shí),表示網(wǎng)絡(luò)傳輸過程中,當(dāng)前NALU中可能存在錯(cuò)誤,解碼器可以考慮不對(duì)這個(gè)NALU進(jìn)行解碼。
0x2212 nal_ref_idc(優(yōu)先級(jí)) - 2bit
取值0~3,代表當(dāng)前這個(gè)NALU的重要性,取值越大,代表當(dāng)前NALU越重要。尤其是當(dāng)前NALU為圖像參數(shù)集、序列參數(shù)集或IDR圖像時(shí),或者為參考圖像條帶(片/Slice),或者為參考圖像的條帶數(shù)據(jù)分割時(shí),nal_ref_idc值肯定不為0。而當(dāng)NALU 類型,nal_unit_type為6、9、10、11、或12時(shí),nal_ref_idc都為0。
0x2213 nal_unit_type(類型) - 5bit
表示NALU Header后面的RBSP的數(shù)據(jù)結(jié)構(gòu)的類型。

需要注意的幾個(gè)值,他們?cè)诰幋a解碼中需要特別注意:
1~5:表示RBSP里面包含的數(shù)據(jù)為條帶(片/Slice)數(shù)據(jù),所以值為1-5的NALU統(tǒng)稱為VCL(視像編碼層)單元,其他的NALU則稱為非VCL NAL單元。5為I幀,1為P幀。 7:代表當(dāng)前NALU為序列參數(shù)集,SPS。 8:為8時(shí)為圖像參數(shù)集,PPS。 9:為訪問單元分隔符 14~31:目前幾乎用不到。
0x222 EBSP - 擴(kuò)展字節(jié)序列載荷
首先這里有三個(gè)概念:EBSP、RBSP、SODB。(大小順序從左到右)
EBSP:擴(kuò)展字節(jié)序列載荷,完整的NALU體。 RBSP:原始字節(jié)序列載荷,EBSP + 0x03(防止競爭校驗(yàn)字節(jié))。 SODB:最原始的編碼數(shù)據(jù),SODB + RBSP_STOP_ONE_BIT(1) + RBSP_ALIGNMENT_ZERO_BIT(0…)

0x2221 EBSP(原始字節(jié)序列載荷)與RBSP的差距差別0x03
由于NALU依靠起始碼來分割NALU段,0x03就是為了防止這些可能在出現(xiàn)的干擾(如:在NALU中出現(xiàn)了0x000001或0x00000就會(huì)被認(rèn)為是新NALU點(diǎn),但這樣肯定是不對(duì)的)。為了解決這個(gè)問題,提供了0x03的一個(gè)安全機(jī)制。當(dāng)檢測到它們存在時(shí),編碼器就在最后一個(gè)字節(jié)前,插入一個(gè)新的字節(jié):0x03。
0x00 00 00 -> 0x00 00 03 00
0x00 00 01 -> 0x00 00 03 01
0x00 00 02 -> 0x00 00 03 02
0x00 00 03 -> 0x00 00 03 03
這里的 0x000000 和 0x000001上面已經(jīng)講了,0x000002作為保留項(xiàng),0x000003則是因?yàn)樵贜ALU內(nèi)部也存在使用情況。
0x2222 RBSP與SODB(數(shù)據(jù)字節(jié)流)的差別
差別在于SODB的尾部添加結(jié)束字符。有以下兩種方式
方式一大多數(shù)的NALU使用的RBSP_STOP_ONE_BIT、RBSP_ALIGNMENT_ZERO_BIT。
RBSP_STOP_ONE_BIT:1個(gè)比特,值為1。
RBSP_ALIGNMENT_ZERO_BIT:值為0,為使字節(jié)對(duì)齊。
也即:SODB + RBSP_STOP_ONE_BIT + RBSP_ALIGNMENT_ZERO_BIT(0…)
方式二另一種尾部,就是當(dāng)NALU類型為片時(shí),也即nal_unit_type等于1~5時(shí)。只是當(dāng)entropy_coding_mode_flag值為1,采用CABAC無損壓縮技術(shù),并且more_rbsp_trailing_data()返回為true,也即RBSP中有更多數(shù)據(jù)時(shí),添加一個(gè)或多個(gè)0x0000。
以上就是EBSP的這個(gè)數(shù)據(jù)結(jié)構(gòu)。
【音視頻】iOS AVAudioSession梳理
以上,便是今天的分享,希望大家喜歡,覺得內(nèi)容不錯(cuò)的,歡迎「分享」「贊」或者點(diǎn)擊「在看」支持,謝謝各位。

