一.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 );
- }
- }
- //添加起始碼
- 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=7或8:每個SPS 或者PPS 僅對應一個NALU。
相應的RBSP數據類型如下表所示:
RBSP類型 | 縮寫 | 描述 |
參數集 | PS | 包括SPS和PPS,序列的全局信息,如圖像尺寸,視頻格式等 |
增強信息 | SEI | 視頻序列解碼的增強信息 |
圖像界定符 | PD | 視頻圖像的邊界 |
編碼片 | SLICE | 編碼片的頭信息和數據 |
數據分割 |
|
DP片層的數據,用於錯誤恢復解碼 |
序列結束符 |
|
表明一個序列的結束,下一個圖像爲IDR圖像 |
流結束符 |
|
表明該流中已沒有圖像 |
填充數據 |
|
亞元數據,用於填充字節 |
數據分割:組成片的編碼數據存放在 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 爲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 |
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圖像中的片 |
☆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圖像的片 |
關於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