前言
- 源码:
https://github.com/Peakmain/Video_Audio/blob/master/javaLib/src/main/java/com/peakmain/javalib/H264Parse.java - 我的简书:https://www.jianshu.com/u/3ff32f5aea98
- 我的Github:https://github.com/peakmain
H.264码流中的NALU进行了一个简单的划分,标出了NALU的类型和长度等信息。因为我们在解析SPS和PPS中要使用到指数哥伦布编码的解析,所以有必要了解一下指数哥伦布编码。
哥伦布编码
指数哥伦布码正常来说,可以拓展位k阶,但是在H264中使用的是0阶指数哥伦布编码,在H.264中使用ue(v)表示0阶无符号指数哥伦布编码的解码过程,用se(v)表示0阶有符号指数哥伦布编码过程
无符号指数哥伦布编码
用来表示无符号整数k阶指数哥伦布编码的生成步骤如下:
(1)将数字以二进制形式写出,去掉最低位的k个比特位,之后加1
(2)计算留下的比特数位数,将此数减1,即是需要增加的前导0的个数
(3)将第一步中去掉的最低个比特位补回到比特串尾部
0阶无符号指数哥伦布编码过程
0阶无符号指数哥伦布编码最后生成的比特串格式为"前缀1后缀",前缀和后缀的长度是相同的。
假如我们待编码的数字codeNum = 4,0阶无符号指数哥伦布编码的步骤如下:
(1)将数字以二进制写出,4的二进制为100,因为0阶指数哥伦布编码所有,所以不用去掉低位
(2)将上面的二进制+1,100加1为101,留下的比特数为3,3-1=2,所以需要增加前导0的个数为2
(3)因为第一步没有去掉,所有这一步不进行任何操作,最终生成的比特串为00101
下面对不同codeNum进行编码结果
codeNum | codeNum+1 | codeNum+1的二进制 | 需补前缀0的个数 | 编码后的比特串(红色表示补的前缀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阶无符号指数哥伦布编码的解析过程如下
(1)找到第一个不为0的bit,并记录总共找到了0的个数(num),这个时候读到的这个bit肯定是1
(2)然后读num个后缀
(3)1后缀转变成十进制就是原来的codeNum,codeNum = (1 <<i) + 后缀(十进制) - 1;
比如比特串的二进制为:00101,首先找到第一个不为0的比特,前面0的个数为2,然后再读2个后缀10,10十进制为2,这个时候codeNum = (1 << 2) + 2 - 1 = 4 + 3 - 1 = 5
代码实现
- 1.将编码好的数据5还原成原来的数据4
(1)5的二进制是101,首先我们需要将5转成字节码8位0000 0101
//5的字节码是101,转成8位字节码
byte data = 6 & 0xFF;
(2)我们需要统计0的个数,也就是首先要获取第一个不是0的个数
000 00101
& 000 10000
= 000 00000
当前元素&0x80右移动N位就可以获取当前的元素,当获取到第一个元素是非0跳出
//0000 0101
int i = 3;
//统计0的个数
int zeroNum = 0;
while (i < 8) {
//找到第一个不为0
if ((data & (0x80 >> i)) != 0) {
break;
}
zeroNum++;
i++;
}
(3)获取到0的个数后,跳过获取到第一个非0的元素,也就是i++.找到第一个不为0之后,往后找zeroNum个数的字节
比如6的字节码是111,找到第一个非0之后往后找两个字节,也就是11,字节码的值是2*1+1,也就是说,无论多少字节,最后一个非0的数据一定是+1,而最后的前面的数据分边左移1位
i++;
//找到第一个不为0之后,往后找zeroNum个数
int value = 0;
for (int j = 0; j < zeroNum; j++) {
value <<= 1;
//找到元素不为0 的个数
if ((data & (0x80 >> i)) != 0) {
value += 1;
}
i++;
}
int result=(1<<zeroNum)+value-1;
System.out.println(result);
SPS解析分析
我们来看一个h264的sps信息
image.png
NALU
-
NALU:H264编码数据存储或传输的基本单元,一般H264码流最开始的两个NALU是SPS和PPS,第三个NALU是IDR。SPS、PPS、SEI这三种NALU不属于帧的范畴。
NALU语法结构.png - 原始的NALU单元组成
[start code] + [NALU header] + [NALU payload];
- H.264码流在网络中传输时实际是以NALU的形式进行传输的.
- 每个
NALU
由一个字节的Header
和RBSP
组成. - NAL Header 的组成为:
forbidden_zero_bit(1bit) + nal_ref_idc(2bit) + nal_unit_type(5bit)
- 每个
SPS解析
1.1 NAL Header
网上的.png帧类型.png
我们可以再x264.h源码中看到这行代码
image.png image.png
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是十六进制,转成二进制是100,也就是说我们现在的编码等级是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的范围内(包括 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:哥伦布编码
image.png- 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
image.png
1.7 log2_max_frame_num_minus4
这个句法元素主要是为读取另一个句法元素 frame_num 服务的,frame_num 是最重要的句法元素之一,它标识所属图像的解码顺序。可以在句法表看到, fram-num的解码函数是 ue(v),函数中的 v 在这里指定:
v = log2_max_frame_num_minus4 + 4
从另一个角度看,这个句法元素同时也指明了 frame_num 的所能达到的最大值:
MaxFrameNum = 2( log2_max_frame_num_minus4 + 4 )变量 MaxFrameNum 表示 frame_num 的最大值,在后文中可以看到,在解码过程中它也是一个非常重要的变量。
值得注意的是 frame_num 是循环计数的,即当它到达 MaxFrameNum 后又从 0 重新开始新一轮的计数。 解码器必须要有机制检测这种循环, 不然会引起类似千年虫的问题,在图像的顺序上造成混乱。
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) {
//统计0的个数
int zeroNum = 0;
while (i < pBuff.length * 8) {
//找到第一个不为0
if ((pBuff[i / 8] & (0x80 >> (i % 8))) != 0) {
break;
}
zeroNum++;
i++;
}
i++;
//找到第一个不为0之后,往后找zeroNum个数
int value = 0;
for (int j = 0; j < zeroNum; j++) {
value <<= 1;
//找到元素不为0 的个数
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) {
//十六进制转byte数组
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 字节数位数
*/
private static int paraseH264(int bitIndex, byte[] h264) {
int value = 0;
for (int j = 0; j < bitIndex; j++) {
value <<= 1;
//获取到每个字节
if ((h264[i / 8] & (0x80 >> (i % 8))) != 0) {
value += 1;
}
i++;
}
return value;
}
public static void main(String[] args) {
//十六进制转byte数组
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);
}
}
网友评论