1. 程式人生 > 其它 >H.264格式分析

H.264格式分析

一.H.264基本流結構

H.264 的基本流(elementary stream,ES)的結構分為兩層,包括視訊編碼層(VCL)和網路適配層(NAL)。視訊編碼層負責高效的視訊內容表示,而網路適配層負責以網路所要求的恰當的方式對資料進行打包和傳送。引入NAL並使之與VCL分離帶來的好處包括兩方面:1、使訊號處理和網路傳輸分離,VCL 和NAL 可以在不同的處理平臺上實現;2、VCL 和NAL 分離設計,使得在不同的網路環境內,閘道器不需要因為網路環境不同而對VCL位元流進行重構和重編碼。

☆VCL(Video Coding Layer):VCL是對核心演算法引擎,塊,巨集塊及片的語法級別的定義,他最終輸出編碼完的資料 SODB

☆NAL(Net Abstraction Layer):NAL將SODB打包成RBSP然後加上NAL頭,組成一個NALU(NAL單元)

一個典型的NALU如下圖所示:

☆SODB(String Of Data Bits):原始資料位元流, 長度不一定是8的倍數,故需要補齊

☆RBSP(Raw Byte Sequence Payload):原始資料位元組流,SODB+RBSP trailing bits=RBSP,新增加trailing bits是為了使一個RBSP為整位元組數

H.264 的基本流由一系列NALU (Network Abstraction Layer Unit )組成,不同的NALU資料量各不相同。H.264 草案指出,當資料流是儲存在介質上時,在每個NALU 前新增起始碼:0x000001或0x00000001,用來指示一個NALU 的起始和終止位置。在這樣的機制下,在碼流中檢測起始碼,作為一個NALU得起始標識,當檢測到下一個起始碼時,當前NALU結束。

H.264 碼流中每個幀的開頭的3~4個位元組是H.264 的start_code(起始碼),0x00000001或0x000001。3位元組的0x000001只有一種場合下使用,就是一個完整的幀被編為多個slice(片)的時候,從第二個slice開始,包含這些slice的NALU 使用3位元組起始碼。也就是說,如果NALU對應的slice為一幀的開始就用0x00000001,否則就用0x000001。

關於這一點從《ITU-T H.264建議書》和x264原始碼中可以看出,下面是部分x264原始碼。

//一個一個NALU處理   
for( int i = start; i < h->out.i_nal; i++ )    
{    
 int old_payload_len = h->out.nal[i].i_payload;    
     h->out.nal[i].b_long_startcode = !i || h->out.nal[i].i_type == NAL_SPS || h->out.nal[i].i_type == NAL_PPS || h->param.i_avcintra_class;    
 //新增起始碼   
     x264_nal_encode( h, nal_buffer, &h->out.nal[i] );    
     nal_buffer += h->out.nal[i].i_payload;    
 if( h->param.i_avcintra_class )    
     {    
          h->out.nal[i].i_padding -= h->out.nal[i].i_payload - (old_payload_len + NALU_OVERHEAD);    
 if( h->out.nal[i].i_padding > 0 )    
          {    
              memset( nal_buffer, 0, h->out.nal[i].i_padding );    
              nal_buffer += h->out.nal[i].i_padding;    
              h->out.nal[i].i_payload += h->out.nal[i].i_padding;    
          }    
          h->out.nal[i].i_padding = X264_MAX( h->out.nal[i].i_padding, 0 );    
      }    
}    

程式碼中的b_long_startcode,就是在編碼前判斷是否用長起始碼,即四位元組起始碼0x00000001。然後呼叫x264_nal_encode函式新增起始碼。

//新增起始碼   
void x264_nal_encode( x264_t *h, uint8_t *dst, x264_nal_t *nal )    
{    
    uint8_t *src = nal->p_payload;    
    uint8_t *end = nal->p_payload + nal->i_payload;    
    uint8_t *orig_dst = dst;    
 //起始碼  
 //annexb格式,起始碼為0x000001或0x00000001 
 if( h->param.b_annexb )    
    {    
 if( nal->b_long_startcode )    
            *dst++ = 0x00;    
        *dst++ = 0x00;    
        *dst++ = 0x00;    
        *dst++ = 0x01;    
    }    
 else /* save room for size later */ 
        dst += 4;//mp4格式   
......  
......  
......  
}  

二.NAL頭結構分析

NAL頭結構如下圖所示:

長度:1Byte,orbidden_bit(1bit) + nal_reference_bit(2bit) + nal_unit_type(5bit) ☆F(forbidden_zero_bit):1 位,初始為0。當網路識別此單元存在位元錯誤時,可將其設為 1,以便接收方丟掉該單元 ☆NRI(nal_ref_idc):2 位,用來指示該NALU 的重要性等級。值越大,表示當前NALU越重要。具體大於0 時取何值,沒有明確規定 ☆Type(nal_unit_type):5 位,指出NALU 的型別

nal_unit_type

NALU型別

nal_reference_bit

0

未指定

0

1

非IDR的片

此片屬於參考幀,則不等於0, 不屬於參考幀,則等與0

2

片資料A分割槽

同上

3

片資料B分割槽

同上

4

片資料C分割槽

同上

5

IDR影象的片

5

6

補充增強資訊單元(SEI)

0

7

序列引數集(SPS)

非0

8

影象引數集(PPS)

非0

9

分界符

0

10

序列結束

0

11

碼流結束

0

12

填充

0

13..23

保留

0

24..31

未指定

0

nal_unit_type=5:表示當前NAL是IDR影象的一個片,在這種情況下,IDR影象中的每個片的nal_unit_type都應該等於5。注意,IDR影象不能使用分割槽。

nal_unit_type=7或8:每個SPS 或者PPS 僅對應一個NALU。 相應的RBSP資料型別如下表所示:

RBSP型別

縮寫

描述

引數集

PS

包括SPS和PPS,序列的全域性資訊,如影象尺寸,視訊格式等

增強資訊

SEI

視訊序列解碼的增強資訊

影象界定符

PD

視訊影象的邊界

編碼片

SLICE

編碼片的頭資訊和資料

資料分割

DP片層的資料,用於錯誤恢復解碼

序列結束符

表明一個序列的結束,下一個影象為IDR影象

流結束符

表明該流中已沒有影象

填充資料

亞元資料,用於填充位元組

序列和影象引數集:減少了重複引數的傳送,每個VCL NAL單元包含一個標識,指向有關的影象引數集,每個影象引數集包含一個標識,指向有關的序列引數集的內容因此,只用少數的指標資訊,引用大量的引數,大大減少每個VCL NAL單元重複傳送的資訊。 資料分割:組成片的編碼資料存放在 3 個獨立的 DP(資料分割,A、B、C)中,各自包含一個編碼片的子集。分割A包含片頭和片中每個巨集塊頭資料。分割B包含幀內和 SI 片巨集塊的編碼殘差資料。分割 C包含幀間巨集塊的編碼殘差資料。每個分割可放在獨立的 NAL 單元並獨立傳輸。

三.frame、field、slice和macro block

☆幀(frame):當取樣視訊訊號時,如果是通過逐行掃描,那麼得到的訊號就是一幀影象,通常幀頻為25幀每秒(PAL制)、30幀每秒(NTSC制)

從巨集觀上來說,SPS、PPS、IDR 幀(包含一個或多個I-Slice)、P 幀(包含一個或多個P-Slice )、B 幀(包含一個或多個B-Slice )共同構成典型的H.264 碼流結構。

☆場(field):當取樣視訊訊號時,如果是通過隔行掃描(奇、偶數行),那麼一幀影象就被分成了兩場(每次掃描—奇掃描或偶掃描,各稱為一場),通常場頻為50Hz(PAL制)、 60Hz(NTSC制)

☆片(slice):一幀影象可編碼成一個或者多個片,每片包含整數個巨集塊(macro block),即每片至少一個巨集塊,最多時包含整個影象的巨集塊。分片的目的是為了限制誤碼的擴散和傳輸,使編碼片相互間保持獨立。片共有5種類型:I片(只包含I巨集塊)、P片(P和I巨集塊)、B片(B和I巨集塊)、SP片(用 於不同編碼流之間的切換)和SI片(特殊型別的編碼巨集塊)。

片的語法結構如下圖所示:

☆巨集塊(Macro Block):一個編碼影象首先要劃分成多個塊(4x4 畫素)才能進行處理,顯然巨集塊應該是整數個塊組成,通常巨集塊大小為16x16個畫素。巨集塊分為I、P、B巨集塊,I巨集塊只能利用當前片中已解碼的畫素作為參考進行幀內預測;P巨集塊可以利用前面已解碼的影象作為參考影象進行幀內預測;B巨集塊則是利用前後向的參考圖形進行幀內預測

影象以序列為單位進行組織,而影象通常稱為幀,幀、片和巨集塊的關係如下圖所示:

當一幀影象包含多個片時,如下圖所示:

幀、片與引數集的關係如下圖所示:

如果不採用DP(資料分割)機制,則一個片就是一個NALU,一個 NALU 也就是一個片。否則,一個片由三個 NALU 組成,即DPA、DPB和DPC,對應的nal_unit_type 值為 2、3和4。 

由於一幀可能編碼成多個片,解碼時需要保證幀的完整性。例如IDR幀就可能分成多個IDR片,可以從碼流中搜索並提取連續存放的若干個nalu_type 等於05 的nalu,即可獲得一個完整的IDR 幀。這裡實際上涉及到了幀邊界識別問題,H.264 將構成一幀影象所有NALU的集合稱為一個AU(Access Unit),幀邊界識別實際上就是識別AU。因為H.264 取消幀級語法,所以無法簡單地從碼流中獲取AU 。解碼器只有在解碼的過程中,通過某些語法元素的組合才能判斷一幀影象是否結束。

四.NALU解碼流程

五.UltraEdit分析H.264檔案

test.264檔案用UItraEdit開啟,效果如下圖:

test.264用MPlayer播放效果如下圖:

由於資料量較大,我挑選了其中2段資料來分析。 1.分析第一段資料:

☆00 00 00 01 67 00 00 00 01 為NALU的起始標誌。 00 00 00 01 後面的 67 為前面說的佔1個位元組的NALU頭。將十六進位制的67轉換為二進位制,得 0110 0111。

欄位

所佔bit位數

二進位制

十進位制

型別

forbidden_bit

1

0

0

nal_reference_bit

2

11

3

NALU 的重要性等級係數

nal_unit_type

5

00111

7

序列引數集,sps

☆00 00 00 01 68 00 00 00 01 為NALU的起始標誌。 00 00 00 01 後面的 68 為前面說的佔1個位元組的NALU頭。將十六進位制的68轉換為二進位制,得 0110 1000。

欄位

所佔bit位數

二進位制

十進位制

型別

forbidden_bit

1

0

0

nal_reference_bit

2

11

3

NALU 的重要性等級係數

nal_unit_type

5

01000

8

影象引數集,pps

☆00 00 03 00 H.264規定,當檢測到0x000000時,也可以表徵當前NAL的結束。那麼NAL中資料出現0x000001或0x000000時怎麼辦?H.264引入了防止競爭機制,如果編碼器檢測到NAL資料存在0x000001或0x000000時,編碼器會在最後個位元組前插入一個新的位元組0x03,這樣: 0x000000->0x00000300 0x000001->0x00000301 0x000002->0x00000302 0x000003->0x00000303

解碼器檢測到0x000003時,把03拋棄,恢復原始資料(脫殼操作)。解碼器在解碼時,首先逐個位元組讀取NAL的資料,統計NAL的長度,然後再開始解碼。 ☆00 00 00 01 65 00 00 00 01為NALU的起始標誌。 00 00 00 01 後面的 65 為前面說的佔1個位元組的NALU頭。將十六進位制的65轉換為二進位制,得 0110 0101。

欄位

所佔bit位數

二進位制

十進位制

型別

forbidden_bit

1

0

0

nal_reference_bit

2

11

3

NALU 的重要性等級係數

nal_unit_type

5

00101

5

IDR影象中的片

2.分析第二段資料:

☆00 00 00 01 41 00 00 00 01 為NALU的起始標誌。 00 00 00 01 後面的 41 為前面說的佔1個位元組的NALU頭。將十六進位制的41轉換為二進位制,得 0100 0001。

欄位

所佔bit位數

二進位制

十進位制

型別

forbidden_bit

1

0

0

nal_reference_bit

2

10

2

NALU 的重要性等級係數

nal_unit_type

5

00001

1

不分割槽,非IDR影象的片

在baseline(基準)類別中nal_unit_type=1表示的就是P幀,因為baseline沒有B幀。

關於H.264的類別和等級詳見:H.264視訊壓縮標準

參考書籍:《新一代視訊壓縮編碼標準H.264-AVC》

參考連結:http://depthlove.github.io/2015/09/23/use-tool-to-analyze-h264-file/

參考連結:http://www.cnblogs.com/TaigaCon/p/5215448.html

參考連結:http://blog.csdn.net/chinadragon76/article/details/22408727