H265编码 SPS分析

学习目标:

H265编码分析

学习内容:

H265出现的原因:

  1. 我们视频的分辨率 从 720p到1080p,而且电视的屏幕也越来越大

  2. 视频帧率从30帧 到60帧,再到120帧
    这就会导致我们cpu在编解码的时候,会出现宏块个数爆发式增长,运动矢量复杂度增加的后果,直接导致我们编码后的视频文件依旧很大,所以H264编码方式已经不满足现在的需求了,H265也就应运而生。
    下面是通过Elecard HEVCAnalyzer 软件分析宏块的效果,可以看到我们的宏块大小在颜色差异大的地方越小,这样我们的画面在界面明显差异的地方就会越清晰。
    在这里插入图片描述
    H265和H264编码数据比较:

  3. 编码后H265 I帧数据大小 比H264 I帧数据大

  4. 编码H265 P帧、B帧数据大小 比 H264P帧、B帧数据小

所以H265的编码数据小的主要原因在于 P帧、B帧数据很小。

下图是类型的占用字节数。(前面依旧是四个字节的分隔符)
在这里插入图片描述

H265-NALU-Type
接下来看一下H265的NALU类型的种类。如下图。

在这里插入图片描述
在这里插入图片描述
接下来直接来分析H265编码数据中的 NALU_TYPE;
在这里插入图片描述
来举个例子,解释怎么计算的吧。

40 01
//根据上面的NAL_unit_header_type
16进制:01000000 000 00001
//第二位包括第二位往后6位都是nal_Type,所以计算40 里的 6位 就行了
01000000 & 0x7E << 1
计算结果:32
//查表得知
这个帧类型是 vps

上面是热身,接下来就进入实战,我们来解析sps里的高度和宽度。
首先我们必须知道那两个字段是高度和宽度,这就要查找官方文档了。
首先我们需要看几个字段的含义,不然看文档的时候会一脸懵

名称含义
ae(v)基于上下文自适应的二元算术编码
b(8)读进连续的 8 bit
f(n)读进连续的nbit
se(v)有符号指数 Golomb 熵编码
u(n)读进连续的 n bit,且它们解码后的值为无符号整数
e(n)无符号指数 Golomb熵编码

好的我们了解了这几个字符之后,就可以看懂点文档了,贴sps编码格式
在这里插入图片描述
从图中我们可以很容易的发现,宽度和高度在sps编码格式的所处位置和占用字节数,我们只需要按照这张表格的规范,逐个解析即可,但是,我们需要注意,当我们解析的时候遇到 00 00 03的数值时,我们需要跳过03,接下来,看一个表格(sps模拟数据读取宽高表格分解图)
在这里插入图片描述
上图就是我截取的sps数据(一段真实的视频编码),图中灰色的区域,我们解析的时候需要跳过这个字段,不去解析,这个是转义符的意思。红色字体就是我们的宽度(pic_width_in_luma_samples),而橘黄色字体就是我们的高度(pic_height_in_luma_samples),图中其他不同颜色的是解析的时候比较混淆的地方,我标出来,可以更好的区分。

解析代码如下:

#include <iostream>
#include <memory.h>
#include <bh.h>
typedef signed char int8;

typedef unsigned char   uint8;

typedef unsigned short uint16;

typedef unsigned long uint32;
typedef signed char int8;
typedef signed short int16;
typedef signed long int32;
struct vc_params_t
{
    long width,height;
    int8 profile, level;
    int8 nal_length_size;
    void clear()
    {
        memset(this, 0, sizeof(*this));
    }
};

class NALBitstream
{
public:
    int m_idx;
    NALBitstream() : m_data(NULL), m_len(0), m_idx(0), m_bits(0), m_byte(0), m_zeros(0)
    {

    };
    NALBitstream(void * data, int len)
    {
        Init(data, len);
    };
    void Init(void * data, int len)
    {
        m_data = (LPBYTE)data;
        m_len = len;
        m_idx = 0;
        m_bits = 0;
        m_byte = 0;
        m_zeros = 0;
    };
    BYTE GetBYTE()
    {
        if ( m_idx >= m_len )
            return 0;
        BYTE b = m_data[m_idx++];
        if ( b == 0 )
        {
            m_zeros++;
            if ( (m_idx < m_len) && (m_zeros == 2) && (m_data[m_idx] == 0x03) )
            {
                m_idx++;
                m_zeros=0;
            }
        }
        else  m_zeros = 0;

        return b;
    };

    UINT32 GetBit()
    {
        if (m_bits == 0)
        {
            m_byte = GetBYTE();
            m_bits = 8;
        }
        m_bits--;
        return (m_byte >> m_bits) & 0x1;
    };

    UINT32 GetWord(int bits)
    {
        UINT32 u = 0;
        while ( bits > 0 )
        {
            u <<= 1;
            u |= GetBit();
            bits--;
        }
        printf("end index is :%d\n",m_idx);
        return u;
    };

    //返回哥伦布编码对应的实际值
    UINT32 GetUE()
    {
        int zeros = 0;
        while (m_idx < m_len && GetBit() == 0 ) zeros++;
        UINT32 value=GetWord(zeros) + ((1 << zeros) - 1);
        return value;
    };

    INT32 GetSE()
    {
        UINT32 UE = GetUE();
        bool positive = UE & 1;
        INT32 SE = (UE + 1) >> 1;
        if ( !positive )
        {
            SE = -SE;
        }
        return SE;
    };
private:
    LPBYTE m_data;
    int m_len;
    int m_bits;
    BYTE m_byte;
    int m_zeros;
};

bool  ParseSequenceParameterSet(BYTE* data,int size, vc_params_t& params)
{
    if (size < 20)
    {
        return false;
    }
    NALBitstream bs(data, size);
    // seq_parameter_set_rbsp()
    int  test =bs.GetWord(4);// sps_video_parameter_set_id
    int sps_max_sub_layers_minus1 = bs.GetWord(3);
    if (sps_max_sub_layers_minus1 > 6)
    {
        return false;
    }
    test =bs.GetWord(1);
    {
        test =bs.GetWord(2);
        test =bs.GetWord(1);
        test = params.profile = bs.GetWord(5);
        test = bs.GetWord(32);//6
        test =bs.GetWord(1);//
        test =bs.GetWord(1);//
        test = bs.GetWord(1);//
        test =bs.GetWord(1);//
        test = bs.GetWord(44);//
        test= params.level   = bs.GetWord(8);// general_level_idc
        uint8 sub_layer_profile_present_flag[6] = {0};
        uint8 sub_layer_level_present_flag[6]   = {0};
        for (int i = 0; i < sps_max_sub_layers_minus1; i++) {
            sub_layer_profile_present_flag[i]= bs.GetWord(1);
            sub_layer_level_present_flag[i]= bs.GetWord(1);
        }
        if (sps_max_sub_layers_minus1 > 0)
        {
            for (int i = sps_max_sub_layers_minus1; i < 8; i++) {
                uint8 reserved_zero_2bits = bs.GetWord(2);
                printf("------");
            }
        }
        for (int i = 0; i < sps_max_sub_layers_minus1; i++)
        {
            if (sub_layer_profile_present_flag[i]) {
                test =bs.GetWord(2);
                test = bs.GetWord(1);
                test = bs.GetWord(5);
                test =bs.GetWord(32);
                test = bs.GetWord(1);
                test = bs.GetWord(1);
                test = bs.GetWord(1);
                test = bs.GetWord(1);
                test = bs.GetWord(44);
            }
            if (sub_layer_level_present_flag[i]) {
                test = bs.GetWord(8);// sub_layer_level_idc[i]
            }
        }
    }
    uint32 sps_seq_parameter_set_id= bs.GetUE();
    if (sps_seq_parameter_set_id > 15) {
        return false;
    }
    uint32 chroma_format_idc = bs.GetUE();
    if (sps_seq_parameter_set_id > 3) {
        return false;
    }
    if (chroma_format_idc == 3) {
        test =  bs.GetWord(1);//
    }

    params.width  = bs.GetUE(); // pic_width_in_luma_samples
    params.height  = bs.GetUE(); // pic_height_in_luma_samples
    if (bs.GetWord(1)) {
        bs.GetUE();
        bs.GetUE();
        bs.GetUE();
        bs.GetUE();
    }
    uint32 bit_depth_luma_minus8= bs.GetUE();
    uint32 bit_depth_chroma_minus8= bs.GetUE();
    if (bit_depth_luma_minus8 != bit_depth_chroma_minus8) {
        return false;
    }
    return true;
}
int main() {
    vc_params_t params = {0};
    BYTE Sps[41] = {0X42 ,0X01 ,0X01 ,0X01 ,0X60 ,0X00 ,0X00 ,0X03 ,0X00 ,0X90 ,0X00 ,
                    0X00 ,0X03 ,0X00 ,0X00 ,0X03 ,0X00 ,0X78,0XA0 ,0X04 ,0XB2 ,0X00 ,
                    0XC8 ,0X59 ,0X65 ,0X66 ,0X92 ,0X4C ,0XAF ,0X01 ,0X6C ,0X08 ,0X00 ,
                    0X00 ,0X03 ,0X00 ,0X08 ,0X00};
    ParseSequenceParameterSet(Sps,41,params);
    printf("%d-%d-%d\n",params.width,params.height,params.level);
    system("pause");
    return 0;
}

好了,大概就这些,欢迎指正交流。

  • 0
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值