H.264码流中的NALU进行了一个简单的划分,标出了NALU的类型和长度等信息。因为我们在解析SPS和PPS
中要使用到指数哥伦布编码的解析,所以有必要了解一下指数哥伦布编码。
一、指数哥伦布编码(理论篇)
指数哥伦布码(Exponential-Golomb code, 即Exp-Golomb code)
是熵编码的一种编码方式,正常来说,可以拓展为k阶,但是在H264中使用的是0阶指数哥伦布编码
,在H.264中使用ue(v)表示0阶无符号指数哥伦布编码的解码
过程,用se(v)表示0阶有符号指数哥伦布编码过程
1.1、无符号指数哥伦布编码
用来表示无符号整数k阶指数哥伦布编码的生成步骤如下:
1、将数字以二进制形式写出,去掉最低位的k个比特位,之后加1
2、计算留下的比特数位数,将此数减1,即是需要增加的前导0的个数
3、将第一步中去掉的最低个比特位补回到比特串尾部
1.1.1、0阶无符号指数哥伦布编码过程
0阶无符号指数哥伦布编码最后生成的比特串格式为"前缀1后缀
",前缀和后缀的长度是相同的。
假如我们待编码的数字codeNum = 4,0阶无符号指数哥伦布编码的步骤如下:
1、将数字以二进制写出,4的二进制为100,因为0阶指数哥伦布编码所有不用去掉低位
2、将上面的二进制+1,100加1为101,留下的比特数为3,3-1=2,所以需要增加前导0的个数为2
3、因为第一步没有去掉,所有这一步不进行任何操作,最终生成的比特串为00101
0阶指数哥伦布编码可以简化为如下步骤
1、将codeNum+1,4+1=5
2、将加1后的数字用二进制表示,5的二进制位101,1后缀=101,后缀为01,长度2
3、前缀与后缀长度相同,在前面加上2个0
下面对不同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;
比如比特串的二进制为:00111,首先找到第一个不为0的比特,前面0的个数为2,然后再读2个后缀11,11十进制为3,这个时候codeNum = (1 << 2) + 3 - 1 = 4 + 3 - 1 = 6
1.1.2、k阶无符号指数哥伦布编码过程
1、将codeNum加上2^k
2、将加上2^k的数字用二进制表示
3、计算二进制的长度len,然后再前面加上len-1-k个0
比特串的格式位"前缀1后缀"。前缀 = 后缀 - k
codeNum | k=1 | len-1-k | 编码后比特串 | k=2 | len-1-k | 编码后比特串 |
---|---|---|---|---|---|---|
1 | 1+2^1=3(11) | 2-1-1=0 | 11 | 1+2^2=5(101) | 3-1-2=0 | 101 |
2 | 2+2^1=4(100) | 3-1-1=1 | 0100 | 2+2^2=6(110) | 3-1-2=0 | 110 |
3 | 3+2^1=5(101) | 3-1-1=1 | 0101 | 3+2^2=7(111) | 3-1-2=0 | 111 |
4 | 4+2^1=6(110) | 3-1-1=1 | 0110 | 4+2^2=8(1000) | 4-1-2=1 | 01000 |
1.2、有符号指数哥伦布编码
有符号指数哥伦布编码一般使用se(v)表示
,输出可能是负数,有符号指数哥伦布编码解析的过程是在无符号指数哥伦布编码解析过程之上进行的,遇到se(v)需要先调用ue(v)得到codeNum,然后再调用se(v)的过程.
value = (-1)^(codeNum+1) * (codeNum+1)/2;
(-1)^(codeNum+1):表示如果codeNum为奇数那么是1,偶数为-1
二、SPS字节位简介
2.1、h264 profile
h264 分为三种profile
基准档次:baseline profile;
主要档次:main profile;
扩展档次:extended profile(常见无high profile);
在H.264的SPS中,第一个字节表示profile_idc,根据profile_idc的值可以确定码流符合哪一种档次。判断规律为:
profile_idc = 66 → baseline profile;
profile_idc = 77 → main profile;
profile_idc = 88 → extended profile;
- constrained_set0_flag
- constraint_set1_flag
- constraint_set2_flag
- constraint_set3_flag
- reserved_zero_4bits
2.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) |
2.3、level_idc
最大支持码流范围
标识当前码流的Level
。编码的Level定义了某种条件下的最大视频分辨率、最大视频帧率等参数,码流所遵从的level由level_idc指定。
2.4、seq_parameter_set_id
表示当前的序列参数集的id。通过该id值,图像参数集pps可以引用其代表的sps中的参数。
seq_parameter_set_id
指定了由图像参数集指明的序列参数集。seq_parameter_set_id
值应该是从0到31,包括0和31
注意: 当可用的情况下,编码器应该在sps值不同的情况下使用不同的seq_parameter_set_id
值,而不是变化某一特定值的
2.5、log2_max_frame_num_minus4
这个句法元素主要是为读取另一个句法元素 frame_num 服务的,frame_num 是最重要的句法元素之一,它标识所属图像的解码顺序。
2.6、chroma_format_idc
与亮度取样对应的色度取样
chroma_format_idc 的值应该在 0到 3的范围内(包括 0和 3)。当 chroma_format_idc不存在时,应推断其值为 1(4:2:0的色度格式)。
色度采样结构
2.7、bit_depth_luma_minus8
表示视频位深
如图: YUV420 8bit
2.8、pic_order_cnt_type
指明了 poc (picture order count) 的编码方法, poc 标识图像的播放顺序。由于H.264 使用了 B 帧预测,使得图像的解码顺序并不一定等于播放顺序,但它们之间存在一定的映射关系。 poc 可以由 frame-num 通过映射关系计算得来,也可以索性由编码器显式地传送。 H.264 中一共定义了三种 poc 的编码方法,这个句法元素就是用来通知解码器该用哪种方法来计算 poc。
2.9、log2_max_pic_order_cnt_lsb_minus4
指明了变量 MaxPicOrderCntLsb 的值:
MaxPicOrderCntLsb = 2( log2_max_pic_order_cnt_lsb_minus4 + 4 )
该变量在 pic_order_cnt_type = 0 时使用。
2.10、max_num_ref_frames
指定参考帧队列可能达到的最大长度,解码器依照这个句法元素的值开辟存储区,这个存储区用于存放已解码的参考帧, H.264 规定最多可用 16 个参考帧。
2.11、pic_width_in_mbs_minus1
本句法元素加 1 后指明图像宽度,以宏块为单位:
frame_width = 16 × (pic_width_in_mbs_minus1 + 1);
2.12、pic_height_in_map_units_minus1
本句法元素加 1 后指明图像高度,以宏块为单位:
frame_height = 16 × (pic_height_in_map_units_minus1 + 1);
2.13、frame_mbs_only_flag
标识位,说明宏块的编码方式。当该标识位为0时,宏块可能为帧编码或场编码;该标识位为1时,所有宏块都采用帧编码。根据该标识位取值不同,PicHeightInMapUnits的含义也不同,为0时表示一场数据按宏块计算的高度,为1时表示一帧数据按宏块计算的高度。
2.14、mb_adaptive_frame_field_flag
指明本序列是否属于帧场自适应模式。 mb_adaptive_frame_field_flag
等于1时表明在本序列中的图像如果不是场模式就是帧场自适应模式,等于0时表示本序列中的图如果不是场模式就是帧模式。。表 列举了一个序列中可能出现的编码模式:
a. 全部是帧,对应于 frame_mbs_only_flag =1 的情况。
b. 帧和场共存。 frame_mbs_only_flag =0, mb_adaptive_frame_field_flag =0
c. 帧场自适应和场共存。 frame_mbs_only_flag =0, mb_adaptive_frame_field_flag =1
值得注意的是,帧和帧场自适应不能共存在一个序列中。
2.15、direct_8x8_inference_flag
标识位,用于B_Skip、B_Direct模式运动矢量的推导计算。
2.16、vui_parameters_present_flag
指明 vui 子结构是否出现在码流中,用以表征视频格式等额外信息。
三、解码sps中宽高信息
下面是手动解析sps中的宽高信息,通过这些可以知道sps中有哪些信息,为后续深入学习音视频做准备。
public class Main {
public void Ue() {
int nStartBit = 3;
byte data = 5 & 0xFF;//字节上的5 0000 0101
//统计0 的个数
int nZeroNum = 0;
while (nStartBit < 8) {
if ((data & (0x80 >> (nStartBit))) != 0){
break;
}
nZeroNum++;
nStartBit++;
}
nStartBit++;
int dwRet = 0;//1 0
for (int i = 0; i < nZeroNum; i++) {
dwRet <<= 1;//0 <<1 1*2=2 11 0 3*2=6
if ((data & (0x80 >> (nStartBit % 8))) != 0){
dwRet += 1;//6+0 dwRet=6
}
nStartBit++;
}
int value = (1 << nZeroNum) -1+ dwRet;
System.out.println(value);
}
static int nStartBit = 0;
public static int Ue(byte[] pBuff) {
//统计0 的个数
int nZeroNum = 0;
while (nStartBit < pBuff.length * 8) {
if ((pBuff[nStartBit / 8] & (0x80 >> (nStartBit%8))) != 0){
break;
}
nZeroNum++;
nStartBit++;
}
nStartBit++;
int dwRet = 0;//1 0
for (int i = 0; i < nZeroNum; i++) {
dwRet <<= 1;//0 <<1 1*2=2 11 0 3*2=6
if ((pBuff[nStartBit / 8] & (0x80 >> (nStartBit % 8))) != 0){
dwRet += 1;//6+0 dwRet=6
}
nStartBit++;
}
int value = (1 << nZeroNum) -1+ dwRet;
System.out.println(value);
return value;
}
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;
}
private static int u(int bitIndex, byte[] h264){
int dwRet = 0;
for (int i = 0; i < bitIndex; i++) {
dwRet <<= 1;
if ((h264[nStartBit / 8] & (0x80 >> (nStartBit % 8))) != 0)
{
dwRet += 1;
}
nStartBit++;
}
return dwRet;
}
public static void main1(String[] args) {
//这里面数据是sps信息
byte[] h264=hexStringToByteArray("00 00 00 01 67 42 00 0A 8D 8D 40 28 02 AD 35 05 02 02 07 84 42 29 C0".replace(" ", ""));
// 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(" ", ""));
nStartBit = 83;
int widthTemp =Ue(h264);
nStartBit = 92;
int heigthTmp =Ue(h264);
int width = (widthTemp + 1) * 16;
int height = (heigthTmp + 1) * 16;
System.out.println("width: " + width);
System.out.println("height: " + height);
}
public static void main(String[] args) {
// 0 A-67
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(" ", ""));
// byte[] h264=hexStringToByteArray("00 00 00 01 67 42 00 0A 8D 8D 40 28 02 AD 35 05 02 02 07 84 42 29 C0".replace(" ", ""));
nStartBit = 4*8;
int forbidden_zero_bit=u(1, h264);
int nal_ref_idc =u(2, h264);//
int nal_unit_type =u(5, h264);//
if(nal_unit_type==7) {
System.out.println("----------");
int profile_idc = u(8, h264);
// 当constrained_set0_flag值为1的时候,就说明码流应该遵循基线profile(Baseline profile)的所有约束.constrained_set0_flag值为0时,说明码流不一定要遵循基线profile的所有约束。
int constraint_set0_flag = u(1, h264);//(h264[1] & 0x80)>>7;
// 当constrained_set1_flag值为1的时候,就说明码流应该遵循主profile(Main profile)的所有约束.constrained_set1_flag值为0时,说明码流不一定要遵
int constraint_set1_flag = u(1, h264);//(h264[1] & 0x40)>>6;
//当constrained_set2_flag值为1的时候,就说明码流应该遵循扩展profile(Extended profile)的所有约束.constrained_set2_flag值为0时,说明码流不一定要遵循扩展profile的所有约束。
int constraint_set2_flag = u(1, h264);//(h264[1] & 0x20)>>5;
// 注意:当constraint_set0_flag,constraint_set1_flag或constraint_set2_flag中不只一个值为1的话,那么码流必须满足所有相应指明的profile约束。
int constraint_set3_flag = u(1, h264);//(h264[1] & 0x10)>>4;
// 4个零位
int reserved_zero_4bits = u(4, h264);
// 它指的是码流对应的level级
int level_idc = u(8, h264);
// 0
int seq_parameter_set_id = Ue(h264);
//
System.out.println("----------");
if (profile_idc == 100) {
// chroma_format_idc 的值应该在 0到 3的范围内(包括 0和 3) yuv420 yuv422 yuv 444
int chroma_format_idc=Ue(h264);
// bit_depth_luma_minus8 视频位深 0 八位 1 代表10位
int bit_depth_luma_minus8 =Ue(h264);
int bit_depth_chroma_minus8 =Ue(h264);
int qpprime_y_zero_transform_bypass_flag=u(1, h264);
// 缩放换标志位
int seq_scaling_matrix_present_flag =u(1, h264);
}
// 最大帧率
int log2_max_frame_num_minus4=Ue(h264);
//确定播放顺序和解码顺序的映射
int pic_order_cnt_type =Ue(h264);
int log2_max_pic_order_cnt_lsb_minus4=Ue(h264);
//编码索引 码流顺序
int num_ref_frames =Ue(h264);
//
int gaps_in_frame_num_value_allowed_flag=u(1, h264);
int pic_width_in_mbs_minus1 =Ue(h264);
int pic_height_in_map_units_minus1 =Ue(h264);
//sps宽高信息
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);
}
}
}