H264分辨率解码概述

1. 申明

本文章属于原创,其中参考的代码及文章在结尾处标明,侵删。

2. 目的

本文是为了解析H264中携带的视频分辨率而写的一个demo。

3. 背景知识

本文及文中所涉及代码,均以TS流作为媒介。

3.1 哥伦比亚指数编码无符号数的解码

(1)首先读取当前位置的bit位,记录连续0的个数N

(2)对于连续0的个数为0值为0,对于连续0的个数不为0 跳过第一个非0的bit位

(3)再读取N个数据,此数据以无符号形式解析num

(4)num=num-1+2的N次方

3.2 哥伦比亚指数编码有符号数的解码

(1)按照3.1的讲述先获取此段数据的哥伦比亚指数编码的无符号形式

(2)如果数据为奇数num=(num+1)/2;如果是偶数num=-(num/2)

3.3 解析参数序列集的过程

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
描述符一列表示此处数据的类型f和u表示的是无符号的数据类型,ue表示的是哥伦比亚指数编码的无符号数据,se表示的是哥伦比亚指数编码的有符号数据。

4. 代码demo


#define H264_SPS_HEAD_FLAG 0x67

/*
  当满足0x00 0x00 剔除0x03
  @param1 buf:          数据指针
  @param2 nstart_bit:   计算因子
*/
static inline void kick_out(unsigned char *buf, uint32_t *nstart_bit)
{
    int pos = *nstart_bit / 8;
    if (buf[pos] == 0x03 && pos >= 2)
    {
        if (buf[pos -1] == 0x00 && buf[pos - 2] == 0x00)
            *nstart_bit += 8;
    }
}

/*
  无符号指数哥伦布熵编码
  @param1 buf:          数据指针
  @param2 len:          数据长度
  @param3 nstart_bit:   计算因子
*/
static inline uint32_t ue(unsigned char *buf,
    uint32_t len, uint32_t *nstart_bit)
{
    //计算0bit的个数
    kick_out(buf, nstart_bit);
    uint32_t n_zero_num = 0;
    while (*nstart_bit < len * 8)
    {
        if (buf[*nstart_bit / 8] & (0x80 >> (*nstart_bit % 8)))
        {
            break;
        }
        n_zero_num++;
        ++(*nstart_bit);
    }
    ++(*nstart_bit);

    //计算结果
    uint32_t ret = 0, i = 0;
    for (; i < n_zero_num; ++i)
    {
        ret <<= 1;
        if (buf[*nstart_bit / 8] & (0x80 >> (*nstart_bit % 8)))
        {
            ret += 1;
        }
        ++(*nstart_bit);
    }
    return (1 << n_zero_num) - 1 + ret;
}

/*
  有符号指数哥伦布熵编码
  @param1 buf:          数据指针
  @param2 len:          数据长度
  @param3 nstart_bit:   计算因子
*/
static inline int se(unsigned char *buf, uint32_t len, uint32_t *nstart_bit)
{
    int ue_val = ue(buf, len, nstart_bit);
    //1.调用库函数取不小于本身的最小整数,
    // double k = ue_val;
    // int n_value = ceil(k / 2);//ceil函数:ceil函数的作用是求不小于给定实数的最小整数。ceil(2)=ceil(1.2)=cei(1.5)=2.00
    // if (ue_val % 2 == 0)
    //     n_value = -n_value;

    //2.取余法
    int n_value;
    if (ue_val % 2 == 0)
        n_value = -(ue_val / 2);
    else
        n_value = (ue_val / 2) + 1;
    
    return n_value;
}

/*
  根据bit_count进行解码
  @param1 bit_count:    比特位数
  @param2 buf:          数据指针
  @param3 nstart_bit:   计算因子
*/
static inline uint64_t u(uint32_t bit_count,
    unsigned char *buf, uint32_t *nstart_bit)
{
    uint32_t ret = 0, i = 0;
    kick_out(buf, nstart_bit);
    for (; i < bit_count; ++i)
    {
        ret <<= 1;
        if (buf[(*nstart_bit) / 8] & (0x80 >> ((*nstart_bit) % 8)))
        {
            ret += 1;
        }
        ++(*nstart_bit);
    }

    return ret;
}

/*
  H264的NAL起始码防竞争机制
  @param1 buf:      临时buf
  @param2 buf_size: 临时buf大小
*/
void de_emulation_prevention(unsigned char *buf, uint32_t *buf_size)
{
    uint32_t i = 0, j = 0;
    unsigned char *tmp_ptr = NULL;
    uint32_t tmp_buf_size = 0;
    uint32_t val = 0;
    tmp_ptr = buf;
    tmp_buf_size = *buf_size;
    for (i = 0; i < (tmp_buf_size - 2); ++i)
    {
        //check for 0x000003
        val = (tmp_ptr[i] ^ 0x00) + (tmp_ptr[i+1] ^ 0x00) + (tmp_ptr[i+2] ^ 0x03);
        if (val == 0)
        {
            //kick out 0x03
            for (j = i + 2; j < tmp_buf_size - 1; j++)
                tmp_ptr[j] = tmp_ptr[j + 1];

            //and so we should devrease bufsize
            --(*buf_size);
        }
    }
}

/*
  根据误差调整分辨率
  @param1 width:        原始宽数据的指针
  @param2 hight:        原始高数据的指针
  @param3 offset_width: 横向误差和
  @param4 offset_hight: 纵向误差和
*/
static inline BOOL adjust_resolution_by_offset(uint32_t *width, uint32_t *hight,
    uint8_t offset_width, uint8_t offset_hight)
{
    //左右存在对齐,分辨率宽度减去二倍的像素
    if (offset_width) {
        if ((offset_width * 2) <= *width)
            *width -= offset_width * 2;
        else
            return FALSE;
    }
    //上下存在对齐,分辨率高度减去二倍的像素
    if (offset_hight) {
        if ((offset_hight * 2) <= *hight)
            *hight -= offset_hight * 2;
        else
            return FALSE;
    }

    return TRUE;
}

/*
  查找H265 SPS数据起始部分
  @param1 buf:  原始数据buf的二级指针
  @param2 len:  原始数据大小的指针
  @param3 type: 要查找的视频类型起始字节:
                H264: 0x67
                H265: 0x42
*/
static inline BOOL find_sps_first_byte(unsigned char **buf, uint32_t *len, uint8_t type)
{
    unsigned char *pos = (unsigned char *)memchr(*buf, type, *len);
    if (NULL == pos)
        return FALSE;

    *len -= pos - *buf;
    *buf = pos;
    return TRUE;
}

/*
  解码H264
  @param1 pdata:    原始数据buf的指针
  @param2 len:      原始数据的大小
*/
static inline BOOL decode_h264(unsigned char *pdata, uint32_t len)
{
    uint32_t start_bit = 0;
    //查找SPS起始头
    if (!find_sps_first_byte(&pdata, &len, H264_SPS_HEAD_FLAG))
        return FALSE;

    //H264的NAL起始码防竞争机制,该函数会改变原始码流,替换使用kick_out函数
    //de_emulation_prevention(pdata, &len);

    //解码 forbidden_zero_bit
    u(1, pdata, &start_bit);
    //解码 nal_ref_idc
    u(2, pdata, &start_bit);
    //解码 nal_unit_type
    int nal_unit_type = u(5, pdata, &start_bit);
    if (nal_unit_type == 7)
    {
        //解码 profile_idc
        int profile_idc = u(8, pdata, &start_bit);
        //解码 constraint_set0_flag
        u(1, pdata, &start_bit);//(pdata[1] & 0x80)>>7;
        //解码 constraint_set1_flag
        u(1, pdata, &start_bit);//(pdata[1] & 0x40)>>6;
        //解码 constraint_set2_flag
        u(1, pdata, &start_bit);//(pdata[1] & 0x20)>>5;
        //解码 constraint_set3_flag
        u(1, pdata, &start_bit);//(pdata[1] & 0x10)>>4;
        //解码 reserved_zero_4bits
        u(4, pdata, &start_bit);
        //解码 level_idc
        u(8, pdata, &start_bit);
        //解码 seq_parameter_set_id
        ue(pdata, len, &start_bit);

        if (profile_idc == 100 || profile_idc == 110 ||
            profile_idc == 122 || profile_idc == 144 )
        {
            //解码 chroma_format_idc
            int chroma_format_idc = ue(pdata, len, &start_bit);
            if ( chroma_format_idc == 3)
                u(1, pdata, &start_bit);//解码 residual_colour_transform_flag
            //解码 bit_depth_luma_minus8
            ue(pdata, len, &start_bit);
            //解码 bit_depth_chroma_minus8
            ue(pdata, len, &start_bit);
            //解码 qpprime_y_zero_transform_bypass_flag
            u(1, pdata, &start_bit);
            //解码判断 seq_scaling_matrix_present_flag
            if (u(1, pdata, &start_bit))
            {
                int i = 0;
                for (; i < 8; ++i)
                    u(1, pdata, &start_bit);//解码 seq_scaling_list_present_flag
            }
        }
        //解码 log2_max_frame_num_minus4
        ue(pdata, len, &start_bit);
        //解码 pic_order_cnt_type
        int pic_order_cnt_type = ue(pdata, len, &start_bit);
        if (pic_order_cnt_type == 0) {
            //解码 log2_max_pic_order_cnt_lsb_minus4
            ue(pdata, len, &start_bit);
        }
        else if (pic_order_cnt_type == 1)
        {
            //解码 delta_pic_order_always_zero_flag
            u(1, pdata, &start_bit);
            //解码 offset_for_non_ref_pic
            se(pdata, len, &start_bit);
            //解码 offset_for_top_to_bottom_field
            se(pdata, len, &start_bit);
            int i = 0;
            //解码作为循环控制条件 num_ref_frames_in_pic_order_cnt_cycle
            for (; i < ue(pdata, len, &start_bit); ++i)
                se(pdata, len, &start_bit);// 解码 offset_for_ref_frame
        }
        //解码 num_ref_frames
        ue(pdata, len, &start_bit);
        //解码 gaps_in_frame_num_value_allowed_flag
        u(1, pdata, &start_bit);
        //解码分辨率宽 pic_width_in_mbs_minus1
        uint32_t width = ue(pdata, len, &start_bit);
        //解码分辨率高 pic_height_in_map_units_minus1
        uint32_t height = ue(pdata, len, &start_bit);

        width =(width + 1) * 16;
        height =(height + 1) * 16;
        //需要判断场分片还是帧分片
        uint64_t frame_mbs_only_flag = u(1, pdata, &start_bit);
        height *= (2 - frame_mbs_only_flag);
        //解码作判断 frame_mbs_only_flag
        if(!frame_mbs_only_flag)  
            u(1, pdata, &start_bit);//解码 mb_adaptive_frame_field_flag
  
        //解码 direct_8x8_inference_flag
        u(1,pdata,&start_bit);  
        //解码做判断 frame_cropping_flag
        if(u(1, pdata, &start_bit))
        {//为真则出现了对齐现象,需要在宽或高上减掉对齐的像素
            //解码 frame_crop_left_offset
            uint32_t frame_crop_left_offset = ue(pdata, len, &start_bit);
            //解码 frame_crop_right_offset
            uint32_t frame_crop_right_offset = ue(pdata, len, &start_bit);
            //解码 frame_crop_top_offset
            uint32_t frame_crop_top_offset = ue(pdata, len, &start_bit);
            //解码 frame_crop_bottom_offset
            uint32_t frame_crop_bottom_offset = ue(pdata, len, &start_bit);
            //调整分辨率
            if (!adjust_resolution_by_offset(&width, &height,
                frame_crop_left_offset + frame_crop_right_offset,
                frame_crop_top_offset + frame_crop_bottom_offset)) {
                ;//不够减
            }
        }
    }
    return TRUE;
}

划重点: 为了解析分辨率,需要一层层进行解码,所以上面代码中,只调用u(),ue()而未获取返回值的,可以暂时不用关注,有兴趣可以自己了解。需要强调的是,最后对于补齐的计算,如果不进行补齐的核对,会出现640*368这种分辨率,而实际应用中我们的宽高比为16:9, 所以分辨率应该为640:360, 那么这多出来的8,其实就是在传输过程中,必须以宏块(16的倍数)来传输,为了满足这个要求,高度向上进行了补齐,所以在计算真实值的时候,要根据frame_crop_left_offset,frame_crop_right_offset,frame_crop_top_offset,frame_crop_bottom_offset进行相应的调整。

5. 专栏知识链接

1. 协议知识概述
2. H265分辨率解码概述
3. 以太网Ethernet解码概述

6. 写在最后

本文引用了以下文章作者的代码或思路,
并结合了实际项目中的代码整理出的demo,如有问题欢迎指正。

https://www.jianshu.com/p/b26963f2f926
https://blog.csdn.net/wishfly/article/details/75127050
https://blog.csdn.net/jifukui/article/details/90698872

官方文档
https://www.doc88.com/p-3601708718418.html

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值