美文网首页音视频积累
Android音视频开发——SPS分析与提取

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

作者: Peakmain | 来源:发表于2021-08-26 16:38 被阅读0次

    前言

    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由一个字节的HeaderRBSP组成.
      • 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 时使用。
    image.png

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

    相关文章

      网友评论

        本文标题:Android音视频开发——SPS分析与提取

        本文链接:https://www.haomeiwen.com/subject/ernyiltx.html