前言
- 源碼:
https://github.com/Peakmain/Video_Audio/blob/master/javaLib/src/main/java/com/peakmain/javalib/H264Parse.java - 我的簡書:http://www.reibang.com/u/3ff32f5aea98
- 我的Github:https://github.com/peakmain
H.264碼流中的NALU進行了一個簡單的劃分础倍,標出了NALU的類型和長度等信息童社。因為我們在解析SPS和PPS中要使用到指數(shù)哥倫布編碼的解析,所以有必要了解一下指數(shù)哥倫布編碼著隆。
哥倫布編碼
指數(shù)哥倫布碼正常來說,可以拓展位k階呀癣,但是在H264中使用的是0階指數(shù)哥倫布編碼美浦,在H.264中使用ue(v)表示0階無符號指數(shù)哥倫布編碼的解碼過程,用se(v)表示0階有符號指數(shù)哥倫布編碼過程
無符號指數(shù)哥倫布編碼
用來表示無符號整數(shù)k階指數(shù)哥倫布編碼的生成步驟如下:
(1)將數(shù)字以二進制形式寫出项栏,去掉最低位的k個比特位浦辨,之后加1
(2)計算留下的比特數(shù)位數(shù),將此數(shù)減1沼沈,即是需要增加的前導0的個數(shù)
(3)將第一步中去掉的最低個比特位補回到比特串尾部
0階無符號指數(shù)哥倫布編碼過程
0階無符號指數(shù)哥倫布編碼最后生成的比特串格式為"前綴1后綴"流酬,前綴和后綴的長度是相同的。
假如我們待編碼的數(shù)字codeNum = 4列另,0階無符號指數(shù)哥倫布編碼的步驟如下:
(1)將數(shù)字以二進制寫出芽腾,4的二進制為100,因為0階指數(shù)哥倫布編碼所有页衙,所以不用去掉低位
(2)將上面的二進制+1摊滔,100加1為101,留下的比特數(shù)為3店乐,3-1=2艰躺,所以需要增加前導0的個數(shù)為2
(3)因為第一步?jīng)]有去掉,所有這一步不進行任何操作眨八,最終生成的比特串為00101
下面對不同codeNum進行編碼結(jié)果
codeNum | codeNum+1 | codeNum+1的二進制 | 需補前綴0的個數(shù) | 編碼后的比特串(紅色表示補的前綴0) |
---|---|---|---|---|
0 | 1 | 1 | 0 | 1 |
1 | 2 | 10 | 1(0) | 010 |
2 | 3 | 11 | 1(0) | 011 |
3 | 4 | 100 | 2(00) | 00100 |
4 | 5 | 101 | 2(00) | 00101 |
5 | 6 | 110 | 2(00) | 00110 |
6 | 7 | 111 | 2(00) | 00111 |
0階無符號指數(shù)哥倫布編碼的解析過程如下
(1)找到第一個不為0的bit腺兴,并記錄總共找到了0的個數(shù)(num),這個時候讀到的這個bit肯定是1
(2)然后讀num個后綴
(3)1后綴轉(zhuǎn)變成十進制就是原來的codeNum廉侧,codeNum = (1 <<i) + 后綴(十進制) - 1;
比如比特串的二進制為:00101页响,首先找到第一個不為0的比特篓足,前面0的個數(shù)為2,然后再讀2個后綴10拘泞,10十進制為2纷纫,這個時候codeNum = (1 << 2) + 2 - 1 = 4 + 3 - 1 = 5
代碼實現(xiàn)
- 1.將編碼好的數(shù)據(jù)5還原成原來的數(shù)據(jù)4
(1)5的二進制是101,首先我們需要將5轉(zhuǎn)成字節(jié)碼8位0000 0101
//5的字節(jié)碼是101,轉(zhuǎn)成8位字節(jié)碼
byte data = 6 & 0xFF;
(2)我們需要統(tǒng)計0的個數(shù)陪腌,也就是首先要獲取第一個不是0的個數(shù)
000 00101
& 000 10000
= 000 00000
當前元素&0x80右移動N位就可以獲取當前的元素辱魁,當獲取到第一個元素是非0跳出
//0000 0101
int i = 3;
//統(tǒng)計0的個數(shù)
int zeroNum = 0;
while (i < 8) {
//找到第一個不為0
if ((data & (0x80 >> i)) != 0) {
break;
}
zeroNum++;
i++;
}
(3)獲取到0的個數(shù)后,跳過獲取到第一個非0的元素诗鸭,也就是i++.找到第一個不為0之后染簇,往后找zeroNum個數(shù)的字節(jié)
比如6的字節(jié)碼是111,找到第一個非0之后往后找兩個字節(jié)强岸,也就是11,字節(jié)碼的值是2*1+1锻弓,也就是說,無論多少字節(jié)蝌箍,最后一個非0的數(shù)據(jù)一定是+1青灼,而最后的前面的數(shù)據(jù)分邊左移1位
i++;
//找到第一個不為0之后,往后找zeroNum個數(shù)
int value = 0;
for (int j = 0; j < zeroNum; j++) {
value <<= 1;
//找到元素不為0 的個數(shù)
if ((data & (0x80 >> i)) != 0) {
value += 1;
}
i++;
}
int result=(1<<zeroNum)+value-1;
System.out.println(result);
SPS解析分析
我們來看一個h264的sps信息
NALU
-
NALU:H264編碼數(shù)據(jù)存儲或傳輸?shù)幕締卧嗣ぃ话鉎264碼流最開始的兩個NALU是SPS和PPS杂拨,第三個NALU是IDR。SPS悯衬、PPS弹沽、SEI這三種NALU不屬于幀的范疇。
- 原始的NALU單元組成
[start code] + [NALU header] + [NALU payload]筋粗;
- H.264碼流在網(wǎng)絡(luò)中傳輸時實際是以NALU的形式進行傳輸?shù)?
- 每個
NALU
由一個字節(jié)的Header
和RBSP
組成. - NAL Header 的組成為:
forbidden_zero_bit(1bit) + nal_ref_idc(2bit) + nal_unit_type(5bit)
- 每個
SPS解析
1.1 NAL Header
我們可以再x264.h源碼中看到這行代碼
1.2 profile_idc: 編碼等級,有以下取值
Options: | |
---|---|
66 | Baseline(直播) |
77 | Main(一般場景) |
88 | Extended |
100 | High (FRExt) |
110 | High 10 (FRExt) |
122 | High 4:2:2 (FRExt) |
144 | High 4:4:4 (FRExt) |
我們上面的64是十六進制策橘,轉(zhuǎn)成二進制是100,也就是說我們現(xiàn)在的編碼等級是High娜亿。等級越高丽已,代表視頻越清晰
1.3 level_idc : 標識當前碼流的等級
Options: | |
---|---|
10 | 1 (supports only QCIF format and below with 380160 samples/sec) |
11 | 1.1 (CIF and below. 768000 samples/sec) |
12 | 1.2 (CIF and below. 1536000 samples/sec) |
13 | 1.3 (CIF and below. 3041280 samples/sec) |
20 | 2 (CIF and below. 3041280 samples/sec) |
21 | 2.1 (Supports HHR formats. Enables Interlace support. 5 068 800 samples/sec) |
22 | 2.2 (Supports SD/4CIF formats. Enables Interlace support. 5184000 samples/sec) |
30 | 3 (Supports SD/4CIF formats. Enables Interlace support. 10368000 samples/sec) |
31 | 3.1 (Supports 720p HD format. Enables Interlace support. 27648000 samples/sec) |
32 | 3.2 (Supports SXGA format. Enables Interlace support. 55296000 samples/sec) |
40 | 4 (Supports 2Kx1K format. Enables Interlace support. 62914560 samples/sec) |
41 | 4.1 (Supports 2Kx1K format. Enables Interlace support. 62914560 samples/sec) |
42 | 4.2 (Supports 2Kx1K format. Frame coding only. 125829120 samples/sec) |
50 | 5 (Supports 3672x1536 format. Frame coding only. 150994944 samples/sec) |
51 | 5.1 (Supports 4096x2304 format. Frame coding only. 251658240 samples/sec) |
1.4 chroma_format_idc 與亮度取樣對應的色度取樣
chroma_format_idc 的值應該在 0到 3的范圍內(nèi)(包括 0和 3)。當 chroma_format_idc不存在時买决,應推斷其值為 1(4:2:0的色度格式)促脉。
chroma_format_idc | 色彩格式 |
---|---|
0 | 單色 |
1 | 4:2:0 |
2 | 4:2:2 |
3 | 4:4:4 |
1.5 seq_parameter_set_id:哥倫布編碼
- seq_parameter_set_id值應該是從0到31,包括0和31
- 當可用的情況下策州,編碼器應該在sps值不同的情況下使用不同的seq_parameter_set_id值瘸味,而不是變化某一特定值的
1.6 bit_depth_luma_minus8 表示視頻位深
- 0 High 只支持8bit
-
1 High10 才支持10bit
1.7 log2_max_frame_num_minus4
這個句法元素主要是為讀取另一個句法元素 frame_num 服務(wù)的,frame_num 是最重要的句法元素之一够挂,它標識所屬圖像的解碼順序旁仿。可以在句法表看到, fram-num的解碼函數(shù)是 ue(v)枯冈,函數(shù)中的 v 在這里指定:
v = log2_max_frame_num_minus4 + 4
從另一個角度看毅贮,這個句法元素同時也指明了 frame_num 的所能達到的最大值:
MaxFrameNum = 2( log2_max_frame_num_minus4 + 4 )變量 MaxFrameNum 表示 frame_num 的最大值,在后文中可以看到尘奏,在解碼過程中它也是一個非常重要的變量滩褥。
值得注意的是 frame_num 是循環(huán)計數(shù)的,即當它到達 MaxFrameNum 后又從 0 重新開始新一輪的計數(shù)炫加。 解碼器必須要有機制檢測這種循環(huán)瑰煎, 不然會引起類似千年蟲的問題,在圖像的順序上造成混亂俗孝。
1.8 pic_order_cnt_type
- 指明了picture order count的編碼方法酒甸,picture order count標識圖像的播放順序。
1.9 log2_max_pic_order_cnt_lsb_minus4
- 指明了變量 MaxPicOrderCntLsb 的值:
- MaxPicOrderCntLsb = 2( log2_max_pic_order_cnt_lsb_minus4 + 4 )
- 該變量在 pic_order_cnt_type = 0 時使用赋铝。
SPS分析與提取代碼
//哥倫布編碼
public static int columbusCode(byte[] pBuff) {
//統(tǒng)計0的個數(shù)
int zeroNum = 0;
while (i < pBuff.length * 8) {
//找到第一個不為0
if ((pBuff[i / 8] & (0x80 >> (i % 8))) != 0) {
break;
}
zeroNum++;
i++;
}
i++;
//找到第一個不為0之后插勤,往后找zeroNum個數(shù)
int value = 0;
for (int j = 0; j < zeroNum; j++) {
value <<= 1;
//找到元素不為0 的個數(shù)
if ((pBuff[i / 8] & (0x80 >> (i % 8))) != 0) {
value += 1;
}
i++;
}
int result = (1 << zeroNum) + value - 1;
return result;
}
public static byte[] hexStringToByteArray(String s) {
//十六進制轉(zhuǎn)byte數(shù)組
int len = s.length();
byte[] bs = new byte[len / 2];
for (int i = 0; i < len; i += 2) {
bs[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4) + Character.digit(s.charAt(i + 1), 16));
}
return bs;
}
static int i = 0;
/**
* @bitIndex 字節(jié)數(shù)位數(shù)
*/
private static int paraseH264(int bitIndex, byte[] h264) {
int value = 0;
for (int j = 0; j < bitIndex; j++) {
value <<= 1;
//獲取到每個字節(jié)
if ((h264[i / 8] & (0x80 >> (i % 8))) != 0) {
value += 1;
}
i++;
}
return value;
}
public static void main(String[] args) {
//十六進制轉(zhuǎn)byte數(shù)組
byte[] h264 = hexStringToByteArray("00 00 00 01 67 64 00 15 AC D9 41 70 C6 84 00 00 03 00 04 00 00 03 00 F0 3C 58 B6 58".replace(" ", ""));
i = 4 * 8;
//禁止位
int forbidden_zero_bit = paraseH264(1, h264);
System.out.println("forbidden_zero_bit:" + forbidden_zero_bit);
//重要性
int nal_ref_idc = paraseH264(2, h264);
System.out.println("nal_ref_idc:" + nal_ref_idc);
//幀類型
int nal_unit_type = paraseH264(5, h264);
System.out.println("nal_unit_type:" + nal_unit_type);
if (nal_unit_type == 7) {
//編碼等級
int profile_idc = paraseH264(8, h264);
System.out.println("profile_idc:" + profile_idc);
//64后面的(00),基本用不到
//當constrained_set0_flag值為1的時候革骨,就說明碼流應該遵循基線profile(Baseline profile)的所有約束.constrained_set0_flag值為0時农尖,說明碼流不一定要遵循基線profile的所有約束。
int constraint_set0_flag = paraseH264(1, h264);//(h264[1] & 0x80)>>7;
// 當constrained_set1_flag值為1的時候良哲,就說明碼流應該遵循主profile(Main profile)的所有約束.constrained_set1_flag值為0時盛卡,說明碼流不一定要遵
int constraint_set1_flag = paraseH264(1, h264);//(h264[1] & 0x40)>>6;
//當constrained_set2_flag值為1的時候,就說明碼流應該遵循擴展profile(Extended profile)的所有約束.constrained_set2_flag值為0時臂外,說明碼流不一定要遵循擴展profile的所有約束。
int constraint_set2_flag = paraseH264(1, h264);//(h264[1] & 0x20)>>5;
//注意:當constraint_set0_flag,constraint_set1_flag或constraint_set2_flag中不只一個值為1的話喇颁,那么碼流必須滿足所有相應指明的profile約束漏健。
int constraint_set3_flag = paraseH264(1, h264);//(h264[1] & 0x10)>>4;
// 4個零位
int reserved_zero_4bits = paraseH264(4, h264);
//碼流等級
int level_idc = paraseH264(8, h264);
System.out.println("level_idc:" + level_idc);
//(AC)就是哥倫布編碼
int seq_parameter_set_id = columbusCode(h264);
System.out.println("seq_parameter_set_id:" + seq_parameter_set_id);
if (profile_idc == 100) {
int chroma_format_idc = columbusCode(h264);
System.out.println("chroma_format_idc:" + chroma_format_idc);
int bit_depth_luma_minus8 = columbusCode(h264);
System.out.println("bit_depth_luma_minus8:" + bit_depth_luma_minus8);
int bit_depth_chroma_minus8 = columbusCode(h264);
System.out.println("bit_depth_chroma_minus8:" + bit_depth_chroma_minus8);
int qpprime_y_zero_transform_bypass_flag = paraseH264(1, h264);
System.out.println("qpprime_y_zero_transform_bypass_flag:" + qpprime_y_zero_transform_bypass_flag);
//縮放標志位
int seq_scaling_matrix_present_flag = paraseH264(1, h264);
System.out.println("seq_scaling_matrix_present_flag:" + seq_scaling_matrix_present_flag);
}
//最大幀率
int log2_max_frame_num_minus4 = columbusCode(h264);
System.out.println("log2_max_frame_num_minus4:" + log2_max_frame_num_minus4);
//確定播放順序和解碼順序的映射
int pic_order_cnt_type = columbusCode(h264);
System.out.println("pic_order_cnt_type:" + pic_order_cnt_type);
int log2_max_pic_order_cnt_lsb_minus4 = columbusCode(h264);
System.out.println("log2_max_pic_order_cnt_lsb_minus4:" + log2_max_pic_order_cnt_lsb_minus4);
int num_ref_frames = columbusCode(h264);
System.out.println("num_ref_frames:" + num_ref_frames);
int gaps_in_frame_num_value_allowed_flag = paraseH264(1, h264);
System.out.println("gaps_in_frame_num_value_allowed_flag:" + gaps_in_frame_num_value_allowed_flag);
System.out.println("------startBit " + i);//83
int pic_width_in_mbs_minus1 = columbusCode(h264);
System.out.println("------startBit " + i);//92
int pic_height_in_map_units_minus1 = columbusCode(h264);
int width = (pic_width_in_mbs_minus1 + 1) * 16;
int height = (pic_height_in_map_units_minus1 + 1) * 16;
System.out.println("width : " + width + " height: " + height);
}
}