视频编码中,帧内预测需要获取相邻块的参考像素才能获得重建,在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数据所存放的编码信息示意图。

/*****************************************************************************
* 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预测参考像素:从重建图像中获取上边行、左边列,左上角的重建像素值,作为当前帧内预测参考像素。如下图所示:

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));
......
版权声明:本文为博主原创文章,未经博主允许请勿转载。