
前言:
為什么需要編碼呢?比如當(dāng)前屏幕是1280*720.一秒24張圖片.那么我們一秒的視頻數(shù)據(jù)是
1280*720(位像素)*24(張) / 8(1字節(jié)8位)(結(jié)果:B) / 1024(結(jié)果:KB) / 1024 (結(jié)果:MB) = 2.64MB
一秒的數(shù)據(jù)有2.64MB數(shù)據(jù)量。1分鐘就會(huì)有100多MB。這對(duì)用戶來說真心是災(zāi)難。所以現(xiàn)在我們需要一種壓縮方式減小數(shù)據(jù)的大小.在更低 比特率(bps)的情況下依然提供清晰的視頻。
H264: H264/AVC是廣泛采用的一種編碼方式。我們這邊會(huì)帶大家了解。從大到小排序依次是 序列,圖像,NALU,片,宏塊,亞宏塊,塊,像素。
問題背景:
前面在講封裝格式過程中,都有一個(gè)章節(jié)講解如何將H.264的NALU單元如何打包到TS、FLV、RTP中,解裝剛好相反,怎么從這些封裝格式里面解析出一個(gè)個(gè)NALU單元。NALU即是編碼器的輸出數(shù)據(jù)又是解碼器的輸入數(shù)據(jù),所以在封裝和傳輸時(shí),我們一般處理對(duì)象就是NALU,至于NALU內(nèi)部到底是什么則很少關(guān)心。甚至我們?cè)诰幗獯a時(shí),我們只需要初始化好x264編碼庫(kù),然后輸入YUV數(shù)據(jù),它就會(huì)給你經(jīng)過一系列壓縮算法后輸出NALU,或者將NALU輸入到x264解碼庫(kù)就會(huì)輸出YUV數(shù)據(jù)。
這篇文章就初步帶你看下NALU能傳輸那些數(shù)據(jù),NALU的類型和結(jié)構(gòu)以及H264碼流的層次,最后通過分析工具分析下裸碼流記性驗(yàn)證,你可以選擇感興趣章節(jié)閱讀。
NALU結(jié)構(gòu):
H.264的基本流(elementary stream),也叫裸流(沒有加格式封裝),就是一系列NALU的集合,如下圖所示:
用Notepad十六進(jìn)制形式打開,以annexb格式存儲(chǔ)的h264裸流文件內(nèi)容:

字節(jié)流格式(Annex B)和RTP格式流淺析:
AnnexB(附錄B)格式:NALU數(shù)據(jù)+開始前綴(00 00 00 01或00 00 01),針對(duì)H.320電話會(huì)議。
RTP格式:NALU數(shù)據(jù)+20個(gè)字節(jié)的類似的并不符合RTP協(xié)議的RTP頭。針對(duì)IP網(wǎng)絡(luò)的RTP打包方式。為原始的NAL打包格式,就是開始的若干字節(jié)(1,2,4字節(jié))是NAL的長(zhǎng)度,而不是start_code,此時(shí)必須借助某個(gè)全局的數(shù)據(jù)來獲得編碼器的profile,level,PPS,SPS等信息才可以解碼。
tips:H.264協(xié)議只規(guī)定了字節(jié)流格式,沒有規(guī)定 RTP 格式。可能也是因?yàn)檫@個(gè)原因,RTP 格式?jīng)]有被用到任何場(chǎng)合場(chǎng)合中,成為了擺設(shè)。
NALU結(jié)構(gòu)分為兩層,包含了視頻編碼層(VCL)和網(wǎng)絡(luò)適配層(NAL):
視頻編碼層(VCL即Video Coding Layer):負(fù)責(zé)高效的視頻內(nèi)容表示,這是核心算法引擎,其中對(duì)宏塊、片的處理都包含在這個(gè)層級(jí)上,它輸出的數(shù)據(jù)是SODB;
網(wǎng)絡(luò)適配層(NAL即Network Abstraction Layer):以網(wǎng)絡(luò)所要求的恰當(dāng)方式對(duì)數(shù)據(jù)進(jìn)行打包和發(fā)送,比較簡(jiǎn)單,先報(bào)VCL吐出來的數(shù)據(jù)SODB進(jìn)行字節(jié)對(duì)齊,形成RBSP,最后再RBSP數(shù)據(jù)前面加上NAL頭則組成一個(gè)NALU單元。
分層目的:
這樣做的目的:VCL只負(fù)責(zé)視頻的信號(hào)處理,包含壓縮,量化等處理,NAL解決編碼后數(shù)據(jù)的網(wǎng)絡(luò)傳輸,這樣可以將VCL和NAL的處理放到不同平臺(tái)來處理,可以減少因?yàn)榫W(wǎng)絡(luò)環(huán)境不同對(duì)VCL的比特流進(jìn)行重構(gòu)和重編碼;
NLAU結(jié)構(gòu):
其實(shí)NALU的承載數(shù)據(jù)真實(shí)并不是RBSP(Raw Byte Sequence Playload)而是EBSP即(Extent Byte Sequence Payload),EBSP和RBSP的區(qū)別就是在 RBSP里面加入防偽起始碼字節(jié)(0x03),因?yàn)镠.264規(guī)范規(guī)定,編碼器吐出來的數(shù)據(jù)需要在每個(gè)NALU添加起始碼:0x00 00 01或者0x00 00 00 01,用來指示一個(gè)NALU的起始和終止位置,那么RBSP數(shù)據(jù)內(nèi)部是有可能含有這種字節(jié)序列的,為了防止解析錯(cuò)誤,所以在RBSP數(shù)據(jù)流里面碰到0x 00 00 00 01的0x01前面就會(huì)加上0x03,解碼時(shí)將NALU的EBSP中的0x03去掉成為RBSP,稱為脫殼操作。
原始字節(jié)序列負(fù)載RBSP即Raw Byte Sequence Playload,因?yàn)閂CL輸出的原始數(shù)據(jù)比特流SODB即String Of Data Bits,其長(zhǎng)度不一定是8bit的整數(shù)倍,為了湊成整數(shù)個(gè)字節(jié),往往需要對(duì)SODB最后一個(gè)字節(jié)進(jìn)行填充形成RBSP,所以從SODB到RBSP的示意圖如下:
填充方式就是對(duì)VCL的輸出數(shù)據(jù)進(jìn)行8bit進(jìn)行切分,最后一個(gè)不滿8bit的字節(jié)第一bit位置1,然后后面缺省的bit置0即可
具體填充語法見下文:
參考ISO IEC 14496-10文檔
7.3.2.11 RBSP trailing bits syntax
rbsp_trailing_bits( ) {
rbsp_stop_one_bit /* equal to 1 /
while( !byte_aligned( ) )
rbsp_alignment_zero_bit / equal to 0 */
}
原來文檔中的解釋:
An RBSP is specified as an ordered sequence of bytes as follows.The RBSP contains an SODB as follows:– If the SODB is empty (i.e., zero bits in length), the RBSP is also empty.– Otherwise, the RBSP contains the SODB as follows:1) The first byte of the RBSP contains the (most significant, left-most) eight bits of the SODB; the next byte ofthe RBSP contains the next eight bits of the SODB, etc., until fewer than eight bits of the SODB remain.2) rbsp_trailing_bits( ) are present after the SODB as follows:i) The first (most significant, left-most) bits of the final RBSP byte contains the remaining bits of the SODB(if any).ii) The next bit consists of a single rbsp_stop_one_bit equal to 1.
iii)
When the rbsp_stop_one_bit is not the last bit of a byte-aligned byte, one or morerbsp_alignment_zero_bit is present to result in byte alignment.3) One or more cabac_zero_word 16-bit syntax elements equal to 0x0000 may be present in some RBSPs after therbsp_trailing_bits( ) at the end of the RBSP.
主要的意思我的理解如下:
- 如果SODB恰好占到8的倍數(shù)個(gè)bits,這時(shí)候還是需要RBSP
trailing bits,即二進(jìn)制的‘10000000’ - 如果SODB按8位字節(jié)占據(jù),多余7個(gè)bits,這時(shí)候的RBSP
trailing bits,即二進(jìn)制的‘1’,后面不用再添0了 - 普通的情況(多余1-6bits)是后面補(bǔ)齊二進(jìn)制1和(1-6個(gè))0
還有一種情況是,有的RBSP最后還會(huì)出現(xiàn)1個(gè)或多個(gè)cabac_zero_word,即16位的0
其中H.264規(guī)范規(guī)定,編碼器吐出來的數(shù)據(jù)需要在每個(gè)NALU添加起始碼:0x00 00 01或者0x00 00 00 01,用來指示一個(gè)NALU的起始和終止位置。
所以H.264編碼器輸出的碼流中每個(gè)幀開頭3-4字節(jié)的start code起始碼為0x00 00 01或者0x00 00 00 01。
上面我們分析了NALU的結(jié)構(gòu)以及每層輸出數(shù)據(jù)的處理方法,但是對(duì)于NALU的RBSP數(shù)據(jù)二進(jìn)制表示的什么含義并不清楚,下面分析下NALU的類型。
1. NALU Header

名詞解釋
IDR(Instantaneous Decoding Refresh): 即時(shí)解碼刷新。I和IDR幀都是使用幀內(nèi)預(yù)測(cè)的。它們都是同一個(gè)東西, 在編碼和解碼中為了方便,要把首個(gè)I幀和其他I幀區(qū)別開,所以首個(gè)I幀叫IDR,以方便控制編碼和解碼流程。IDR幀的作用是立刻刷新, 使錯(cuò)誤不致傳播。從IDR幀開始, 重新算一個(gè)新的序列開始編碼。而I幀不具有隨機(jī)訪問的能力,這個(gè)功能是由IDR承擔(dān)。
頭信息協(xié)議如上圖。
舉例說明:
1. 00 00 00 01 06: SEI信息
2. 00 00 00 01 67: 0x67&0x1f = 0x07 :SPS,Nalu_Type占Nalu header后5bit,所以&0x1f
3. 00 00 00 01 68: 0x68&0x1f = 0x08 :PPS
4. 00 00 00 01 65: 0x65&0x1f = 0x05 :IDR Slice
5. 00 00 00 01 41: 0x41&0x1f = 0x01 :非IDR Slice(P Slice or B Slice)
這其中NALU的RBSP除了能承載真實(shí)的視頻壓縮數(shù)據(jù),還能傳輸編碼器的配置信息,其中能傳輸視頻壓縮數(shù)據(jù)的為slice。
那么如果NLAU傳輸視頻壓縮數(shù)據(jù)時(shí),編碼器沒有開啟DP(數(shù)據(jù)分割)機(jī)制,則一個(gè)片就是一個(gè)NALU,一個(gè) NALU 也就是一個(gè)片。否則,一個(gè)片由三個(gè) NALU 組成,即DPA、DPB和DPC,對(duì)應(yīng)的nal_unit_type 類型為 2、3和4。
通常情況我們看到的NLAU類型就是SPS、PPS、SEI、IDR的slice、非IDR這幾種。
上面站在NALU的角度看了NALU的類型、結(jié)構(gòu)、數(shù)據(jù)來源、分層處理的原因等,其中NLAU最主要的目的就是傳輸視頻數(shù)據(jù)壓縮結(jié)果。那么站在對(duì)數(shù)據(jù)本身的理解上,我們看下H.264碼流的層次結(jié)構(gòu)。
H.264層次結(jié)構(gòu):
其實(shí)為了理解H.264是如何看待視頻數(shù)據(jù),先要了解下視頻的形成過程。其實(shí)你把多副連續(xù)的有關(guān)聯(lián)圖像連續(xù)播就可以形成視頻,這主要利用了人視覺系統(tǒng)的暫留效應(yīng),當(dāng)把連續(xù)的圖片以每秒25張的速度播放,人眼基本就感覺是連續(xù)的視頻了。動(dòng)畫片就是這個(gè)原理:一張圖像里面相鄰的區(qū)域或者一段時(shí)間內(nèi)連續(xù)圖像的相同位置,像素、亮度、色溫差別比較小,所以視頻壓縮本質(zhì)就是利于這種空間冗余和時(shí)間上冗余進(jìn)行編碼,我們可以選取一段時(shí)間第一幅圖像的YUV值,后面的只需要記錄和這個(gè)的完整圖像的差別即可,同時(shí)即使記錄一副圖像的YUV值,當(dāng)有鏡頭完全切換時(shí),我們又選取切換后的第一張作為基本圖像,后面有一篇文章回講述下目前視頻壓縮的基本原理。
所以從這里面就可以引申以下幾個(gè)概念:
GOP:Group Of Pictures(圖像組),指的就是兩個(gè)I幀之間的間隔. 比較說GOP為120,如果是720 p60 的話,那就是2s一次I幀.
幀:一副圖像編碼后的視頻數(shù)據(jù)也叫做一幀,其中有I幀、B幀、P幀,前文多次提到,不再贅述;
片:一幀圖像又可以劃分為很多片,由一個(gè)片或者多個(gè)片組成;
宏塊:視頻編碼的最小處理單元,承載了視頻的具體YUV信息,一片由一個(gè)或者多個(gè)宏塊組成;
所以視頻流分析的對(duì)象可以用下面的圖片描述:
如果站在數(shù)據(jù)的角度分析NALU的層次關(guān)系,如下圖:
這里視頻幀被劃分為一個(gè)片或者多個(gè)片,其中slice數(shù)據(jù)主要就是通過NLAU進(jìn)行傳輸,其中slice數(shù)據(jù)又是由:
一個(gè)Slice = Silce + Slice Data
一幀圖片跟 NALU 的關(guān)聯(lián) :
究竟 NALU 是怎么由一幀圖片變化而來的呀,H.264究竟為什么這么神奇?
一幀圖片經(jīng)過 H.264 編碼器之后,就被編碼為一個(gè)或多個(gè)片(slice),而裝載著這些片(slice)的載體,就是 NALU 了,我們可以來看看 NALU 跟片的關(guān)系(slice)。


引用自:http://m.itdecent.cn/p/9522c4a7818d
Slice片類型:
| 片類型 | 含義 |
|---|---|
| I片 | 只包含I宏塊 |
| P片 | 包含P和I宏塊 |
| B片 | 包含B和I宏塊 |
| SP片 | 包含P 和/或 I宏塊,用于不同碼流之間的切換 |
| SI片 | 一種特殊類型的編碼宏塊 |
設(shè)置片的目的是限制誤碼的擴(kuò)散和傳輸,也就是一幀圖像中它們的編碼片是互相獨(dú)立的,這樣假設(shè)其中一張圖像的某一個(gè)片有問題導(dǎo)致解碼花屏,但是這個(gè)影響范圍就控制在這個(gè)片中,這就是我們平時(shí)看視頻發(fā)現(xiàn)只有局部花屏和綠屏的原因。
Slice Data里面?zhèn)鬏數(shù)氖且粋€(gè)個(gè)宏塊,宏塊中的數(shù)據(jù)承載各個(gè)像素點(diǎn)YUV的壓縮數(shù)據(jù)。一個(gè)圖像通常被我們劃分成宏塊來研究,通常有1616、168等格式。我們解碼的過程也就是恢復(fù)這些像素陣列的過程,如果知道了每個(gè)像素點(diǎn)的亮度和色度,就能渲染出一張完整的圖像,圖像的快速播放即是視頻。
剛才提到了宏塊.那么什么是宏塊呢?
宏塊是視頻信息的主要承載者。一個(gè)編碼圖像通常劃分為多個(gè)宏塊組成.包含著每一個(gè)像素的亮度和色度信息。視頻解碼最主要的工作則是提供高效的方式從碼流中獲得宏塊中像素陣列。
一個(gè)宏塊 = 一個(gè)16*16的亮度像素 + 一個(gè)8×8Cb + 一個(gè)8×8Cr彩色像素塊組成。(YCbCr 是屬于 YUV 家族的一員,在YCbCr 中 Y 是指亮度分量,Cb 指藍(lán)色色度分量,而 Cr 指紅色色度分量)
其中宏塊MB的類型:
| 宏塊分類 | 意義 |
|---|---|
| I宏塊 | 利用從當(dāng)前片中已解碼的像素作為參考進(jìn)行幀內(nèi)預(yù)測(cè) |
| P宏塊 | 利用前面已編碼圖像作為參考進(jìn)行幀內(nèi)預(yù)測(cè),一個(gè)幀內(nèi)編碼的宏塊可進(jìn)一步作宏塊的分割:即16×16.16×8.8×16.8×8亮度像素塊。如果選了8×8的子宏塊,則可再分成各種子宏塊的分割,其尺寸為8×8,8×4,4×8,4×4 |
| B宏塊 | 利用雙向的參考圖像(當(dāng)前和未來的已編碼圖像幀)進(jìn)行幀內(nèi)預(yù)測(cè) |
宏塊的結(jié)構(gòu):
H.264碼流示例分析:
這里我們分析一下H.264的NLAU數(shù)據(jù),其中包括了非VCL的NALU數(shù)據(jù)和VCL的NALU。
H.264碼流的NLAU單元:


我們發(fā)現(xiàn)視頻流數(shù)據(jù)就是由一系列SPS、PPS、I、P、B幀序列組成,這些數(shù)據(jù)都是通過NALU進(jìn)行承載的;
我們看到了NALU不僅僅可以承載SPS、PPS、SEI等非VCL數(shù)據(jù),也可以傳輸I、B、P幀的切片VCL數(shù)據(jù)。
我們同時(shí)看到了NALU的Data部分,如果是VCL數(shù)據(jù),則就是slice header+silce data這種結(jié)構(gòu),其中對(duì)VCL的SODB做了bit填充的字節(jié)對(duì)齊處理;
4. 這里由于沒有數(shù)據(jù)分割機(jī)制,所以一個(gè)NALU承載一個(gè)片,同時(shí)一個(gè)片就是一個(gè)視頻幀;
4.至于NALU的非VCL數(shù)據(jù)SPS、PPS、SEI各個(gè)字段的含義具體解析放到下篇文章,這個(gè)信息對(duì)于解碼器進(jìn)行播放視頻很重要,很多播放問題都是這個(gè)數(shù)據(jù)有問題導(dǎo)致的;
上面看了視頻的GOP序列,視頻幀信息和片的組成,下面分析片中的宏塊信息;
H.264的層次結(jié)構(gòu):
我們能看到整個(gè)視頻的視頻序列,顯示看了該視頻有I、B、P幀信息,這和上面的分析結(jié)果是一致的;
中間圖片部分用不同顏色的點(diǎn)顯示了宏塊和子宏塊信息,右上角是對(duì)宏塊內(nèi)容的具體說明;
其中不同的幀類型上面的宏塊類型也是不一樣的;
總結(jié):
本文主要講述了平時(shí)研究和分析視頻流對(duì)象的層次,然后這些視頻數(shù)據(jù)通過NALU傳輸時(shí),NALU的類型和層次關(guān)系,以及NALU數(shù)據(jù)在不同層次的輸出。最后用視頻分析工具分析了H.264裸碼流驗(yàn)證了上述層次關(guān)系。
所以對(duì)H.264數(shù)據(jù)分析時(shí),一定要了解你現(xiàn)在分析的層次和框架,因?yàn)槊總€(gè)層次我們關(guān)心的數(shù)據(jù)處理對(duì)象是不一樣的,這個(gè)非常重要。
一般H.264的分析工具都是收費(fèi)的,也有一些免費(fèi)和裁剪版本供大家學(xué)習(xí)和使用。推薦幾個(gè):Elecard StreamEye、CodecVisa、VideoEye、H264Analyzer、H264Visa等,有時(shí)需要交叉使用才能完成對(duì)你關(guān)心信息的分析,這些都放到我的Git上了,大家獲取使用即可。
rtsp 保存為.h264分析
25fps i幀間隔50 用分析軟件查看可以看出每50幀一個(gè)i幀,并發(fā)送sps、pps、sei

sps、pps、sei、I幀綁在一起發(fā)送