AVS3中Intra预测过程 - HPM代码分析

  视频编码中,帧内预测需要获取相邻块的参考像素才能获得重建,在AVS3参考软件平台HPM中,这个过程是如何实现的呢?

HPM中的map_scu

1、什么是SCU?

  SCU是Smallest CU的缩写。HPM中,最大CU是128x128,最小是4x4。

  CU编码的信息,无论CU大小都以SCU为基本单位进行存储。这样只需知道CU的(x, y)坐标,就可以计算相邻左边、上边、左上角等块的位置,获取他们的编码信息为当前编码块所用。

#define MAX_CU_LOG2                        7
#define MIN_CU_LOG2                        2
#define MAX_CU_SIZE                       (1 << MAX_CU_LOG2)
#define MIN_CU_SIZE                       (1 << MIN_CU_LOG2)

2、什么是map_scu?

  HPM为每个scu都定义了一个map_scu用于存储CU编码的重要信息,map_scu是一个unsigned int 32数组,将CU的编码模式,QP,Skip mode Flag,luma cbf等信息紧凑的存储在一个变量中,这样可以更高效利用存储,减少内存访问及Cache Miss,具体定义如下。其中第31位存储了CU是否编完的信息。图1为map_scu一个32bit数据所存放的编码信息示意图。

 

图1、map_scu编码信息存放示意图
/*****************************************************************************
 * macros for CU map

 - [ 0: 6] : slice number (0 ~ 128)
 - [ 7:14] : reserved
 - [15:15] : 1 -> intra CU, 0 -> inter CU
 - [16:22] : QP
 - [23:23] : skip mode flag
 - [24:24] : luma cbf
 - [25:25] : ibc flag
 - [26:30] : reserved
 - [31:31] : 0 -> no encoded/decoded CU, 1 -> encoded/decoded CU
 *****************************************************************************/

Intra预测过程

  (1) 调用com_get_avail_intra,判断左边、上边和左上相邻块是否存在。

u16 com_get_avail_intra(int x_scu, int y_scu, int pic_width_in_scu, int scup, u32 * map_scu)
{
    u16 avail = 0;
    if (x_scu > 0 && MCU_GET_CODED_FLAG(map_scu[scup - 1]))
    {
        SET_AVAIL(avail, AVAIL_LE);
    }
    if (y_scu > 0)
    {
        if (MCU_GET_CODED_FLAG(map_scu[scup - pic_width_in_scu]))
        {
            SET_AVAIL(avail, AVAIL_UP);
        }
        if (x_scu > 0 && MCU_GET_CODED_FLAG(map_scu[scup - pic_width_in_scu - 1]))
        {
            SET_AVAIL(avail, AVAIL_UP_LE);
        }
    }
    return avail;
}

  (2) 调用com_get_nbr获取Intra预测参考像素:从重建图像中获取上边行、左边列,左上角的重建像素值,作为当前帧内预测参考像素。如下图所示:

图2、Intra预测参考像素获取过程示意图
void com_get_nbr(int x, int y, int width, int height, pel *src, int s_src, u16 avail_cu, pel nb[N_C][N_REF][MAX_CU_SIZE * 3], int scup, u32 * map_scu, int pic_width_in_scu, int pic_height_in_scu, int bit_depth, int ch_type)
{
    int  i;
    int  width_in_scu  = (ch_type == Y_C) ? (width >> MIN_CU_LOG2)  : (width >> (MIN_CU_LOG2 - 1));
    int  height_in_scu = (ch_type == Y_C) ? (height >> MIN_CU_LOG2) : (height >> (MIN_CU_LOG2 - 1));
    int  unit_size = (ch_type == Y_C) ? MIN_CU_SIZE : (MIN_CU_SIZE >> 1);
    int  x_scu = PEL2SCU(ch_type == Y_C ? x : x << 1);
    int  y_scu = PEL2SCU(ch_type == Y_C ? y : y << 1);
    pel *const src_bak = src;
    pel *left = nb[ch_type][0] + STNUM;
    pel *up   = nb[ch_type][1] + STNUM;
    int pad_le = height;  //number of padding pixel in the left column
    int pad_up = width;   //number of padding pixel in the upper row
    int pad_le_in_scu = height_in_scu;
    int pad_up_in_scu = width_in_scu;
    
    *首先,设置左边参考列和上边参考行的值 = 1 << (bit_depth - 1)
    com_mset_16b(left - STNUM, 1 << (bit_depth - 1), height + pad_le + STNUM);
    com_mset_16b(up   - STNUM, 1 << (bit_depth - 1), width  + pad_up + STNUM);
    if(IS_AVAIL(avail_cu, AVAIL_UP))
    {
        com_mcpy(up, src - s_src, width * sizeof(pel));  *从重建图像中拷贝上边行的参考像素
        for(i = 0; i < pad_up_in_scu; i++)  *以SCU为单位继续往右看获取右上行参考像素
        {
            if(x_scu + width_in_scu + i < pic_width_in_scu && MCU_GET_CODED_FLAG(map_scu[scup - pic_width_in_scu + width_in_scu + i]))
            {  *右上行的像素在图像内部,且已经重建,则拷贝右上的参考像素
                com_mcpy(up + width + i * unit_size, src - s_src + width + i * unit_size, unit_size * sizeof(pel));
            }
            else
            {  *当前右上SCU没有重建像素,则将当前位置左边像素值复制到右边
                com_mset_16b(up + width + i * unit_size, up[width + i * unit_size - 1], unit_size);
            }
        }
    }

    if(IS_AVAIL(avail_cu, AVAIL_LE))
    {
        src--;
        for(i = 0; i < height; ++i)  *拷贝左边列重建像素值到left[]
        {
            left[i] = *src;
            src += s_src;
        }
        for(i = 0; i < pad_le_in_scu; i++) *以SCU为单位继续往下看获取左下列参考像素
        {
            if(y_scu + height_in_scu + i < pic_height_in_scu && MCU_GET_CODED_FLAG(map_scu[scup - 1 + (height_in_scu + i) *pic_width_in_scu]))
            {
                int j;
                for(j = 0; j < unit_size; ++j)
                {
                    left[height + i * unit_size + j] = *src;
                    src += s_src;
                }
            }
            else
            {
                com_mset_16b(left + height + i * unit_size, left[height + i * unit_size - 1], unit_size);
                src += (s_src * unit_size);
            }
        }
    }

    if (IS_AVAIL(avail_cu, AVAIL_UP_LE))  *拷贝左上角重建像素值
    {
        up[-1] = left[-1] = src_bak[-s_src - 1];
    }
    else if (IS_AVAIL(avail_cu, AVAIL_UP))
    {
        up[-1] = left[-1] = up[0];
    }
    else if (IS_AVAIL(avail_cu, AVAIL_LE))
    {
        up[-1] = left[-1] = left[0];
    }

    up[-2] = left[0];
    left[-2] = up[0];
    up[-3] = left[1];
    left[-3] = up[1];
#if IIP
    if (STNUM > 3)
    {
        up[-4] = left[2];
        left[-4] = up[2];
        up[-5] = left[3];
        left[-5] = up[3];
    }
#endif
}

  (3) 调用com_get_mpm获取 mpm。

void com_get_mpm(int x_scu, int y_scu, u32 *map_scu, s8 *map_ipm, int scup, int pic_width_in_scu, u8 mpm[2])
{
    u8 ipm_l = IPD_DC, ipm_u = IPD_DC;
    int valid_l = 0, valid_u = 0;

    if(x_scu > 0 && MCU_GET_INTRA_FLAG(map_scu[scup - 1]) && MCU_GET_CODED_FLAG(map_scu[scup - 1]))
    {
        ipm_l = map_ipm[scup - 1];
        valid_l = 1;
    }

    if(y_scu > 0 && MCU_GET_INTRA_FLAG(map_scu[scup - pic_width_in_scu]) && MCU_GET_CODED_FLAG(map_scu[scup - pic_width_in_scu]))
    {
        ipm_u = map_ipm[scup - pic_width_in_scu];
        valid_u = 1;
    }
    mpm[0] = COM_MIN(ipm_l, ipm_u);
    mpm[1] = COM_MAX(ipm_l, ipm_u);
    if(mpm[0] == mpm[1])
    {
        mpm[0] = IPD_DC;
        mpm[1] = (mpm[1] == IPD_DC) ? IPD_BI : mpm[1];
    }
}

  (4) 按照Intra预测公式,对各个预测方向生成预测平面。

Patch边界对Intra预测的影响

  在HPM中,当下一个LCU在新的Patch当中时,会将map_cu数组中的信息清除。这样,在patch边界的LCU,他访问的相邻SCU在其他Patch中时,CODED_FLAG为uncoded,拿不到预测值。

  com_mset_x64a(ctx->map.map_scu, 0, sizeof(u32)* ctx->info.f_scu);  // 将整幅图像的map_scu清零。

  代码如下所示:

        ......
        编完一个LCU后,进行下一步处理:
        core->x_lcu++;
#if PATCH
        patch_cur_index = patch->idx;
        if (core->x_lcu >= *(patch->width_in_lcu + patch->x_pat) + patch_cur_lcu_x)
        {
            core->x_lcu = patch_cur_lcu_x;
            core->y_lcu++;
            if (core->y_lcu >= *(patch->height_in_lcu + patch->y_pat) + patch_cur_lcu_y)
            {
                新的Patch开始了(a new patch starts)
                core->cnt_hmvp_cands = 0;
                ......
                enc_eco_slice_end_flag(bs, 1);
                enc_sbac_finish(bs, 0);
                /*update and store map_scu*/
                en_copy_lcu_scu(map_scu_temp, ctx->map.map_scu, map_refi_temp, ctx->map.map_refi, map_mv_temp, ctx->map.map_mv, map_cu_mode_temp, ctx->map.map_cu_mode, ctx->patch, ctx->info.pic_width, ctx->info.pic_height);
                com_mset_x64a(ctx->map.map_scu, 0, sizeof(u32)* ctx->info.f_scu);   重置map_scu中的所有flag
                com_mset_x64a(ctx->map.map_refi, -1, sizeof(s8)* ctx->info.f_scu * REFP_NUM);
                com_mset_x64a(ctx->map.map_mv, 0, sizeof(s16)* ctx->info.f_scu * REFP_NUM * MV_D);
                com_mset_x64a(ctx->map.map_cu_mode, 0, sizeof(u32) * ctx->info.f_scu);
                /*set patch head*/
                if( patch_cur_index + 1 < num_of_patch )
                {
                    set_sh( ctx, shext, patch_sao_enable + (patch_cur_index + 1)*N_C );
                }
                /* initialize entropy coder */
                enc_sbac_init(bs);
                com_sbac_ctx_init(&(GET_SBAC_ENC(bs)->ctx));
                com_sbac_ctx_init(&(core->s_curr_best[ctx->info.log2_max_cuwh - 2][ctx->info.log2_max_cuwh - 2].ctx));
                ......

版权声明:本文为博主原创文章,未经博主允许请勿转载。

  • 4
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ITRonnie

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值