0
点赞
收藏
分享

微信扫一扫

Android音视频开发——SPS分析与提取

闲云困兽 2021-09-28 阅读 75

前言

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信息


NALU

  • NALU:H264编码数据存储或传输的基本单元,一般H264码流最开始的两个NALU是SPS和PPS,第三个NALU是IDR。SPS、PPS、SEI这三种NALU不属于帧的范畴。

  • 原始的NALU单元组成
    [start code] + [NALU header] + [NALU payload];
  • H.264码流在网络中传输时实际是以NALU的形式进行传输的.
    • 每个NALU由一个字节的HeaderRBSP组成.
    • 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是十六进制,转成二进制是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:哥伦布编码

  • 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 服务的,frame_num 是最重要的句法元素之一,它标识所属图像的解码顺序。可以在句法表看到, fram-num的解码函数是 ue(v),函数中的 v 在这里指定:

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);
        }
    }
举报

相关推荐

0 条评论