1、獲取SPS碼流

2、編寫代碼
import java.util.HashMap;
import java.util.Map;
/**
* 基於java解碼H264 SPS碼流研究筆記(哥倫布編碼)
* @author fu (參考資料:碼牛學院)
* @date 2021年10月22日 10:01 上午
*/
public class ColumbusService {
public int nStartBit = 0; // 解析起始位置
/**
* 0階無符號指數哥倫布解碼運算,每調用一次,返回一次結果
* @param pBuff 需要解析的16進制
* @return 返回解析出來的十進制
*
* 0階無符號指數哥倫布編碼過程(例如待編碼5):
* 1、將數字以二進制寫出,5的二進制為101,因為0階指數哥倫布編碼所有不用去掉低位
* 2、將上面的二進制+1,101加1為110,留下的比特數為3,3-1=2,所有需要增加前導0的個數為2
* 3、因為第一步沒有去掉,所有這一步不進行任何操作,最終生成的比特串為00110
*
*/
public int ue(byte[] pBuff){
int nZeroNum = 0;
/*
根據哥倫布編碼原理,先統計一個段1前面0的個數
nZeroNum 目的只要得出哥倫布編碼中,一個段的內容所占位數
例如:00110,所占位數為3,根據0階哥倫布編碼原理前面補齊2個0,即 nZeroNum=2
代碼邏輯原理:
0x80 ==> 1000 0000
由左向到右 (->) 的方向進行相與(&) --> 000 00110 & 000 10000
如結果返回0(即 1&0 = 0),則記錄0的個數 ==> nZeroNum++
如結果返回1(即 1&1 = 1),匹配if判斷(1 != 0), 則跳出循環,得到計算結果nZeroNum
同時,不管結果如何,起始位(nStartBit)都多加一位,為后面持續方法調用定位
*/
while (nStartBit < pBuff.length * 8) {
if ((pBuff[nStartBit / 8] & (0x80 >> (nStartBit%8))) != 0){
break;
}
nZeroNum++;
nStartBit++;
}
nStartBit++;
/*
根據統計到的nZeroNum計算出實際內容十進制數
例如:00110 ,nZeroNum=2 --> 十進制:5
代碼邏輯原理:
先計算一個段分割1后2位(nZeroNum)十進制數,例如:00110(二進制) --> 10(二進制) --> 2(十進制)
初始化后nZeroNum位,通過循環nZeroNum去計算,每向右進一位(dwRet <<= 1),相當於dwRet*2
判斷准則:
由於起始位(nStartBit)已經在分割1后,則繼續由左向到右 (->) 的方向相與(&) --> 000001 10 & 000000 10
如結果返回1(即 1&1 = 1),則對結果dwRet累加1
如結果返回0(即 1&0 = 0),則跳過,繼續循環判斷
*/
return (1 << nZeroNum) -1+ u(nZeroNum,pBuff);
}
// 將二進制轉十進制
public int u(int bitIndex, byte[] pBuff){
int dwRet = 0;
for (int i = 0; i < bitIndex; i++) {
dwRet <<= 1;
if ((pBuff[nStartBit / 8] & (0x80 >> (nStartBit % 8))) != 0) {
dwRet += 1;
}
nStartBit++;
}
return dwRet;
}
// 十六進制轉byte數組
public byte[] hexStringToByteArray(String s) {
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;
}
public Map<String,Integer> SPSInfo(String data){
byte[] spsData = hexStringToByteArray(data.replace(" ", ""));
this.nStartBit = 4*8;
Map<String,Integer> sps = new HashMap<>();
/*
H.264碼流在網絡中傳輸時實際是以NALU的形式進行傳輸的
每個NALU由一個字節的Header和RBSP組成.
NAL Header 的組成為:
forbidden_zero_bit(1bit) + nal_ref_idc(2bit) + nal_unit_type(5bit)
*/
// 禁止位,初始為0,當網絡發現NAL單元有比特錯誤時可設置該比特為1,以便接收方糾錯或丟掉該單元。
sps.put("forbidden_zero_bit",u(1, spsData));
// nal重要性指示,標志該NAL單元的重要性,值越大,越重要,解碼器在解碼處理不過來的時候,可以丟掉重要性為0的NALU。
sps.put("nal_ref_idc",u(2, spsData));
/*
幀類型:
7-序列參數集(sps)
8-圖像參數集(pps)
5-IDR圖像(I幀)
6-補充增強信息單元(SEI)
*/
sps.put("nal_unit_type",u(5, spsData));
if(sps.get("nal_unit_type") ==7) {
/*
編碼等級:
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)
*/
sps.put("profile_idc",u(8, spsData));
/*
當constrained_set0_flag值為1的時候,就說明碼流應該遵循基線profile(Baseline profile)的所有約束
constrained_set0_flag值為0時,說明碼流不一定要遵循基線profile的所有約束。
當constrained_set1_flag值為1的時候,就說明碼流應該遵循主profile(Main profile)的所有約束
當constrained_set2_flag值為1的時候,就說明碼流應該遵循主profile(Extended profile)的所有約束
注意:當constraint_set0_flag,constraint_set1_flag或constraint_set2_flag中不只一個值為1的話,那么碼流必須滿足所有相應指明的profile約束。
*/
sps.put("constraint_set0_flag",u(1, spsData));
sps.put("constraint_set1_flag",u(1, spsData));
sps.put("constraint_set2_flag",u(1, spsData));
sps.put("constraint_set3_flag",u(1, spsData));
sps.put("reserved_zero_4bits",u(4, spsData));
/*
標識當前碼流的Level。
編碼的Level定義了某種條件下的最大視頻分辨率、最大視頻幀率等參數,碼流所遵從的level由level_idc指定。
10 - 1 (supports only QCIF format and below with 380160 samples/sec)
11 - 1.1 (CIF and below. 768000 samples/sec)
21 - 2.1 (Supports HHR formats. Enables Interlace support. 5 068 800 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)
51 - 5.1 (Supports 4096x2304 format. Frame coding only. 251658240 samples/sec)
......
*/
sps.put("level_idc",u(8, spsData));
sps.put("seq_parameter_set_id",ue(spsData));
if (sps.get("profile_idc") == 100) {
/*
與亮度取樣對應的色度取樣
chroma_format_idc 的值應該在 0到 3的范圍內(包括 0和 3)
當 chroma_format_idc不存在時,應推斷其值為 1(4:2:0的色度格式)
0 - 單色
1 - 4:2:0
2 - 4:2:2
3 - 4:4:4
*/
sps.put("chroma_format_idc",ue(spsData));
/*
視頻位深
0 - High 只支持8bit
1 High10 才支持10bit
*/
sps.put("bit_depth_luma_minus8",ue(spsData));
sps.put("bit_depth_chroma_minus8",ue(spsData));
sps.put("qpprime_y_zero_transform_bypass_flag",u(1,spsData));
sps.put("seq_scaling_matrix_present_flag",u(1,spsData));
}
// 最大幀率
sps.put("log2_max_frame_num_minus4",ue(spsData));
// 確定播放順序和解碼順序的映射
sps.put("pic_order_cnt_type",ue(spsData));
if (sps.get("pic_order_cnt_type") == 0){
sps.put("log2_max_pic_order_cnt_lsb_minus4",ue(spsData));
}
// 參考幀隊列可達到的最大長度
sps.put("num_ref_frames",ue(spsData));
sps.put("gaps_in_frame_num_value_allowed_flag",u(1,spsData));
// 本元素+1 指明以宏塊為單位的圖像寬度
sps.put("pic_width_in_mbs_minus1",ue(spsData));
// 本元素+1 指明以宏塊為單位的圖像高度
sps.put("pic_height_in_map_units_minus1",ue(spsData));
sps.put("frame_mbs_only_flag",ue(spsData));
// 指明B幀的直接和skip模式下的運動矢量的計算方式
sps.put("direct_8x8_inference_flag",u(1,spsData));
// 解碼器是否要將圖片裁剪后輸出,如果是,則后面為裁剪的左右上下的寬度
sps.put("frame_cropping_flag",u(1,spsData));
sps.put("vui_parameters_present_flag",u(1,spsData));
}
return sps;
}
public void FormatPrint(Map<String,Integer> sps){
System.out.println("[0] seq_parameter_set()");
System.out.println(" nal_unit()");
System.out.println(" forbidden_zero_bit = " + sps.get("forbidden_zero_bit"));
System.out.println(" nal_ref_idc = " + sps.get("nal_ref_idc"));
System.out.println(" nal_unit_type = " + sps.get("nal_unit_type"));
System.out.println(" profile_idc = " + sps.get("profile_idc"));
System.out.println(" constraint_set0_flag = " + sps.get("constraint_set0_flag"));
System.out.println(" constraint_set1_flag = " + sps.get("constraint_set1_flag"));
System.out.println(" constraint_set2_flag = " + sps.get("constraint_set2_flag"));
System.out.println(" constraint_set3_flag = " + sps.get("constraint_set3_flag"));
System.out.println(" reserved_zero_4bits = " + sps.get("reserved_zero_4bits"));
System.out.println(" level_idc = " + sps.get("level_idc"));
System.out.println(" seq_parameter_set_id = " + sps.get("seq_parameter_set_id"));
System.out.println(" if (profile_idc == 100)");
System.out.println(" chroma_format_idc = " + sps.get("chroma_format_idc"));
System.out.println(" bit_depth_luma_minus8 = " + sps.get("bit_depth_luma_minus8"));
System.out.println(" bit_depth_chroma_minus8 = " + sps.get("bit_depth_chroma_minus8"));
System.out.println(" qpprime_y_zero_transform_bypass_flag = " + sps.get("qpprime_y_zero_transform_bypass_flag"));
System.out.println(" seq_scaling_matrix_present_flag = " + sps.get("seq_scaling_matrix_present_flag"));
System.out.println(" log2_max_frame_num_minus4 = " + sps.get("log2_max_frame_num_minus4"));
System.out.println(" pic_order_cnt_type = " + sps.get("pic_order_cnt_type"));
System.out.println(" if (pic_order_cnt_type == 0)");
System.out.println(" log2_max_pic_order_cnt_lsb_minus4 = " + sps.get("log2_max_pic_order_cnt_lsb_minus4"));
System.out.println(" num_ref_frames = " + sps.get("num_ref_frames"));
System.out.println(" gaps_in_frame_num_value_allowed_flag = " + sps.get("gaps_in_frame_num_value_allowed_flag"));
System.out.println(" pic_width_in_mbs_minus1 = " + sps.get("pic_width_in_mbs_minus1"));
System.out.println(" pic_height_in_map_units_minus1 = " + sps.get("pic_height_in_map_units_minus1"));
System.out.println(" frame_mbs_only_flag = " + sps.get("frame_mbs_only_flag"));
System.out.println(" direct_8x8_inference_flag = " + sps.get("direct_8x8_inference_flag"));
System.out.println(" frame_cropping_flag = " + sps.get("frame_cropping_flag"));
System.out.println(" vui_parameters_present_flag = " + sps.get("vui_parameters_present_flag"));
}
public static void main(String[] args) {
ColumbusService service = new ColumbusService();
String data = "00 00 00 01 67 64 00 1f ac d9 40 50 05 bb 01 10 00 00 03 00 10 00 00 03 03 20 f1 83 19 60";
service.FormatPrint(service.SPSInfo(data));
}
}
3、輸出結果:
[0] seq_parameter_set()
nal_unit()
forbidden_zero_bit = 0
nal_ref_idc = 3
nal_unit_type = 7
profile_idc = 100
constraint_set0_flag = 0
constraint_set1_flag = 0
constraint_set2_flag = 0
constraint_set3_flag = 0
reserved_zero_4bits = 0
level_idc = 31
seq_parameter_set_id = 0
if (profile_idc == 100)
chroma_format_idc = 1
bit_depth_luma_minus8 = 0
bit_depth_chroma_minus8 = 0
qpprime_y_zero_transform_bypass_flag = 0
seq_scaling_matrix_present_flag = 0
log2_max_frame_num_minus4 = 0
pic_order_cnt_type = 0
if (pic_order_cnt_type == 0)
log2_max_pic_order_cnt_lsb_minus4 = 2
num_ref_frames = 4
gaps_in_frame_num_value_allowed_flag = 0
pic_width_in_mbs_minus1 = 79
pic_height_in_map_units_minus1 = 44
frame_mbs_only_flag = 0
direct_8x8_inference_flag = 1
frame_cropping_flag = 0
vui_parameters_present_flag = 1