【H264/AVC 句法和語義詳解】:h264碼流格式與NALU詳解一

轉(zhuǎn)載自:http://www.itdecent.cn/p/a2dc69c8bf70

上一篇中,我們站在句法元素(或稱語法元素)的角度,介紹了H.264的句法和語義,和句法元素的分層結(jié)構(gòu)。在這篇中,我們更進(jìn)一步,從比特的角度出發(fā),來探索h264碼流的組成。通過這篇的學(xué)習(xí),我們會初步具備解析h264碼流的能力,從碼流中分離出NAL單元,并識別NAL類型。

1. H264碼流格式

不過大道始于腳下,我們還是先從頭介紹一下,h264的兩種碼流格式,它們分別為:字節(jié)流格式和RTP包格式。

(1)字節(jié)流格式:這是在h264官方協(xié)議文檔中規(guī)定的格式,處于文檔附錄B(Annex-B Byte stream format)中。所以它也成為了大多數(shù)編碼器,默認(rèn)的輸出格式。它的基本數(shù)據(jù)單位為NAL單元,也即NALU。為了從字節(jié)流中提取出NALU,協(xié)議規(guī)定,在每個NALU的前面加上起始碼:0x000001或0x00000001(0x代表十六進(jìn)制) 。

(2)RTP包格式:這種格式并沒有在h264中規(guī)定,那為什么還要介紹它呢?是因?yàn)樵趆264的官方參考軟件JM里,有這種封裝格式的實(shí)現(xiàn)。在這種格式中,NALU并不需要起始碼Start_Code來進(jìn)行識別,而是在NALU開始的若干字節(jié)(1,2,4字節(jié)),代表NALU的長度。

顯而易見,我們通常所指,以及接下來要研究的,是h264的字節(jié)流格式。由于它沒有經(jīng)過傳輸協(xié)議封裝,所以也可以稱之為裸流。比如我們打開一個,經(jīng)編碼器編碼存于本地后綴為.h264文件,里面的數(shù)據(jù)即為h264裸流。

而我們接下來的研究方向,就從已經(jīng)打開了一個本地的.h264文件,然后對里面的h264裸流,按照字節(jié)流格式進(jìn)行分析開始。所以拿到碼流的第一刻,我們需要知道,如何從中提取出NALU。

2. 起始碼與NALU

通過上面我們已經(jīng)知道:

H264比特流 = Start_Code_Prefix + NALU + Start_Code_Prefix + NALU + …

只要我們從碼流中,找到一個一個的起始碼,那么位于起始碼之間的數(shù)據(jù),即為NALU。所以拿到碼流,我們需要先從頭開始,找到起始碼0x000001或0x00000001,找到Start_Code_Prefix之后,從它之后的下一個字節(jié)開始,就是NALU的部分。

這部分的實(shí)現(xiàn)過程描述在H264官方文檔附錄B中,已經(jīng)下載的同學(xué)可以查看B.1.1節(jié):

image
3. NALU

看到這一小節(jié)時,我們已經(jīng)有能力根據(jù)附錄B的內(nèi)容,從h264碼流中找出NALU,所以是時候來看一下,h264碼流結(jié)構(gòu)的組成了:

image

這就是NALU在H264碼流中的構(gòu)成了,由上圖我們也知道:

NALU = NALU Header + RBSP

這就是接下來我們要干的,分析NALU Header 和 RBSP,為了對NALU有個宏觀的認(rèn)識,我們先來看一下,NALU有哪些句法元素構(gòu)成,這位于h264文檔的7.3.1節(jié):

image

可以看到,整個NALU語法元素分為三部分:(1)NALU Header、(3)RBSP、(2)1和3之間的部分。

其中第2部分,是近期的h264文檔才更新的,所以我特意查看了JM、x264、FFmpeg等主流編解碼器,這部分是還沒有實(shí)現(xiàn)的,所以我們可以不必理睬。而且細(xì)心的同學(xué)會發(fā)現(xiàn),只有當(dāng)nal_unit_type等于14、20、21時,才會進(jìn)入第二部分。

所以接下來呢,我們就重點(diǎn)介紹NALU Header和RBSP。

3.1 NALU Header

通過上面我們也可以看到,NALU Header由三個句法元素組成,分別為:forbidden_zero_bit、nal_ref_idc和nal_unit_type,它們總共占據(jù)一個字節(jié),也就是說,NALU Header,在整個NALU中,占據(jù)一個字節(jié)。

而且forbidden_zero_bit的值對應(yīng)1個bit,nal_ref_idc的值對應(yīng)2個bit,nal_unit_type的值對應(yīng)5個bit,加起來剛好一個字節(jié)。

正如在上一篇(鏈接)中所介紹的,知道了句法元素,我們就來分別看看它的語義:

3.1.1 forbidden_zero_bit

h264文檔規(guī)定,這個值應(yīng)該為0,當(dāng)它不為0時,表示網(wǎng)絡(luò)傳輸過程中,當(dāng)前NALU中可能存在錯誤,解碼器可以考慮不對這個NALU進(jìn)行解碼。

3.1.2 nal_ref_idc

取值0~3,代表當(dāng)前這個NALU的重要性,取值越大,代表當(dāng)前NALU越重要,就需要優(yōu)先被保護(hù)。尤其是當(dāng)前NALU為圖像參數(shù)集、序列參數(shù)集或IDR圖像時,或者為參考圖像條帶(片/Slice),或者為參考圖像的條帶數(shù)據(jù)分割時,nal_ref_idc值肯定不為0。

而當(dāng)NALU 類型,nal_unit_type為6、9、10、11、或12時,nal_ref_idc都為0。

【注】IDR幀,即:即時解碼刷新圖像,它是一個序列的第一個圖像,H.264引入IDR圖像是為了解碼的重新同步。當(dāng)解碼器解碼到IDR圖像時,立即將參考幀隊列清空,將已解碼的數(shù)據(jù)全部輸出或拋棄,重新查找參數(shù)集,開始一個新的序列。這樣一來,如果前一個序列發(fā)生重大錯誤,在這里就可以獲得重新同步。

所以IDR圖像之后的圖像,永遠(yuǎn)不會引用IDR圖像之前的圖像來解碼。并且IDR圖像一定是I圖像,而I圖像不一定是IDR圖像(H264里沒有圖像層,圖像可以理解為幀、片或宏塊)。

3.1.3 nal_unit_type

顧名思義,這個應(yīng)該是最好理解的了,它表示NALU Header后面的RBSP的數(shù)據(jù)結(jié)構(gòu)的類型。下圖為nal_unit_type所有可能的取值,和對應(yīng)的語義,它處于h264文檔7.4.1節(jié):

image

可以看到,nal_unit_type的值為1-5時,表示RBSP里面包含的數(shù)據(jù)為條帶(片/Slice)數(shù)據(jù),所以值為1-5的NALU統(tǒng)稱為VCL(視像編碼層)單元,其他的NALU則稱為非VCL NAL單元。

當(dāng)nal_unit_type為7時,代表當(dāng)前NALU為序列參數(shù)集,為8時為圖像參數(shù)集。這也是我們打開.h264文件后,遇到的前兩個NALU,它們位于碼流的最前面。

而且當(dāng)nal_unit_type為14-31時,我們可以不用理睬,目前幾乎用不到。

解析完NALU Header之后,下面就開始解析RBSP了,它包含了NALU數(shù)據(jù)的主體部分,我們放在下一篇詳細(xì)介紹。

4. 關(guān)于H.264的協(xié)議文檔

有的同學(xué)可能還沒下載H.264的官方文檔,這里我再貼一下下載地址:

全部版本,下載2017最新版:

http://www.itu.int/rec/T-REC-H.264

最新版為英文版,05年3月份有中文版:

http://www.itu.int/rec/T-REC-H.264-200503-S/en

NALU的主體涉及到三個重要的名詞,分別為EBSP、RBSP和SODB。其中EBSP完全等價于NALU主體,而且它們?nèi)齻€的結(jié)構(gòu)關(guān)系為:

EBSP包含RBSP,RBSP包含SODB。

其中SODB就是最原始的編碼數(shù)據(jù)。

1. EBSP和RBSP

上篇我們說,NALU的組成部分為:

NALU = NALU Header + RBSP

其實(shí)嚴(yán)格來說,這個等式是不成立的,因?yàn)镽BSP并不等于NALU刨去NALU Header。嚴(yán)格來說,NALU的組成部分應(yīng)為:

NALU = NALU Header + EBSP

其中的EBSP為擴(kuò)展字節(jié)序列載荷(Encapsulated Byte Sequence Payload),而RBSP為原始字節(jié)序列載荷(Raw Byte Sequence Payload)。那為什么我們上篇中,沒有使用2式而使用了1式呢?那是因?yàn)?,在h264的文檔中,并沒有EBSP這一名詞出現(xiàn),但是在h264的官方參考軟件JM里,卻使用了EBSP。

而且在我們下面的分析中,我們會看到,使用EBSP是很易于理解的。

1.1 防止競爭

EBSP相較于RBSP,多了防止競爭的一個字節(jié):0x03。

我們知道,NALU的起始碼為0x000001或0x00000001,同時H264規(guī)定,當(dāng)檢測到0x000000時,也可以表示當(dāng)前NALU的結(jié)束。那這樣就會產(chǎn)生一個問題,就是如果在NALU的內(nèi)部,出現(xiàn)了0x000001或0x000000時該怎么辦?

所以H264就提出了“防止競爭”這樣一種機(jī)制,當(dāng)編碼器編碼完一個NAL時,應(yīng)該檢測NALU內(nèi)部,是否出現(xiàn)如下左側(cè)的四個序列。當(dāng)檢測到它們存在時,編碼器就在最后一個字節(jié)前,插入一個新的字節(jié):0x03。

image

圖中0x000000和0x000001前面介紹了,0x000002是作為保留使用,而0x000003,則是為了防止NALU內(nèi)部,原本就有序列為0x000003這樣的數(shù)據(jù)。

這樣一來,當(dāng)我們拿到EBSP時,就需要檢測EBSP內(nèi)是否有序列:0x000003,如果有,則去掉其中的0x03。這樣一來,我們就能得到原始字節(jié)序列載荷:RBSP。

2. RBSP和SODB

得到RBSP之后,我們迫切想做的,就是從RBSP中,提取出原始編碼數(shù)據(jù)SODB(String Of Data Bits)。這就涉及到RBSP與SODB的關(guān)系:

RBSP = SODB + RBSP尾部

而且RBSP的尾部,在規(guī)定中有兩種,我們分別介紹。

2.1 RBSP尾部

其中大多數(shù)類型的NALU,使用這種尾部。

image

其中:

rbsp_stop_one_bit 占1個比特位,值為1

rbsp_alignment_zero_bit 值為0,目的是為了進(jìn)行字節(jié)對齊,占據(jù)若干比特位

所以RBSP就等于,SODB在它的最后一個字節(jié)的最后一個比特后,緊跟值為1的1個比特,然后增加若干比特的0,以補(bǔ)齊這個字節(jié)。

2.2 條帶RBSP尾部

另一種尾部,就是當(dāng)NALU類型為條帶時,也即nal_unit_type等于1~5時,這時RBSP使用下面這種尾部:

image

可以看到,rbsp_slice_trailing_bits()默認(rèn)情況下,就是2.1介紹的第一種尾部。只是當(dāng)entropy_coding_mode_flag值為1,也即當(dāng)前采用的熵編碼為CABAC,而且more_rbsp_trailing_data()返回為true,也即RBSP中有更多數(shù)據(jù)時,添加一個或多個0x0000。

所以我們拿到RBSP,只需要按照上述語法,去掉RBSP的尾部,就可以得到SODB。然后就可以對照對應(yīng)類型的NALU的句法,解析出語法元素的值。

總結(jié)上篇和這篇,H264的碼流結(jié)構(gòu)如下:

image
3. H264句法元素解析流程

而當(dāng)我們拿到RBSP或SODB之后,就可以對照各類型的NALU,去解析它們的語法元素,進(jìn)而再根據(jù)語法元素,重建圖像。其中解析語法元素的框圖如下:

image

由圖可見,解析NALU的各個句法元素并不難,只要根據(jù)h264文檔對應(yīng)章節(jié)的句法,并配合相應(yīng)的編解碼算法解析即可。而相應(yīng)的編解碼算法如指數(shù)哥倫布編碼、CAVLC、CABAC、算術(shù)編碼,我們會一步步涉獵。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容