【x264编码器】章节6——x264的变换量化

系列文章目录

  HEVC视频编解码标准简介

【x264编码器】章节1——x264编码流程及基于x264的编码器demo

【x264编码器】章节2——x264的lookahead流程分析

【x264编码器】章节3——x264的码率控制

【x264编码器】章节4——x264的帧内预测流程

【x264编码器】章节5——x264的帧间预测流程

【x264编码器】章节6——x264的变换量化

【x265编码器】章节1——lookahead模块分析

【x265编码器】章节2——编码流程及基于x265的编码器demo

【x265编码器】章节3——帧内预测流程

【x265编码器】章节4——帧间预测流程

【x265编码器】章节5——x265帧间运动估计流程

【x265编码器】章节6——x265的码率控制

【x265编码器】章节7——滤波模块

【x265编码器】章节8——变换量化模块



一、x264的变换量化流程

x264的不同尺寸的变换过程,最终都会划分成4x4尺寸进行变换,变换量化总体的流程如下:

二、使用步骤

1.16x16宏块编码mb_encode_i16x16

对于16×16的亮度块,变换量化的过程包括两个部分:直流部分(DC)和交流部分(AC)。为了实现16×16亮度块的变换和量化,需要将其分为16个4×4的子块。与4×4模式不同的是,16×16模式首先从这16个4×4系数矩阵中提取出直流分量,并组成一个新的4×4矩阵。随后,对这个直流矩阵进行Hadamard变换,并进行量化处理。

代码如下:

static void mb_encode_i16x16( x264_t *h, int p, int i_qp )
{
    pixel *p_src = h->mb.pic.p_fenc[p];//定义了指向输入帧和输出帧的指针p_src和p_dst
    pixel *p_dst = h->mb.pic.p_fdec[p];

    ALIGNED_ARRAY_64( dctcoef, dct4x4,[16],[16] );//用于存储DCT系数的数组dct4x4和dct_dc4x4
    ALIGNED_ARRAY_64( dctcoef, dct_dc4x4,[16] );

    int nz, block_cbp = 0;//非零系数的数量nz、块的CBP(coded block pattern)值
    int decimate_score = h->mb.b_dct_decimate ? 0 : 9;//降采样得分decimate_score
    int i_quant_cat = p ? CQM_4IC : CQM_4IY;//量化矩阵的类别i_quant_cat
    int i_mode = h->mb.i_intra16x16_pred_mode;//16x16内预测模式i_mode

    if( h->mb.b_lossless )//当前帧是无损模式
        x264_predict_lossless_16x16( h, p, i_mode );//调用x264_predict_lossless_16x16函数进行16x16预测
    else
        h->predict_16x16[i_mode]( h->mb.pic.p_fdec[p] );

    if( h->mb.b_lossless )
    {   //如果当前帧是无损模式,执行无损编码的逻辑,遍历每个4x4块,计算非零系数的数量,更新非零系数计数数组和块的CBP值
        for( int i = 0; i < 16; i++ )
        {
            int oe = block_idx_xy_fenc[i];
            int od = block_idx_xy_fdec[i];
            nz = h->zigzagf.sub_4x4ac( h->dct.luma4x4[16*p+i], p_src+oe, p_dst+od, &dct_dc4x4[block_idx_yx_1d[i]] );
            h->mb.cache.non_zero_count[x264_scan8[16*p+i]] = nz;
            block_cbp |= nz;
        }
        h->mb.i_cbp_luma |= block_cbp * 0xf;
        h->mb.cache.non_zero_count[x264_scan8[LUMA_DC+p]] = array_non_zero( dct_dc4x4, 16 );
        h->zigzagf.scan_4x4( h->dct.luma16x16_dc[p], dct_dc4x4 );
        return;
    }

    CLEAR_16x16_NNZ( p );
    //如果当前帧不是无损模式,执行有损编码的逻辑,先对16x16块进行DCT变换
    h->dctf.sub16x16_dct( dct4x4, p_src, p_dst );
    //如果启用了噪声减弱(noise reduction),则对DCT系数进行噪声减弱处理
    if( h->mb.b_noise_reduction )
        for( int idx = 0; idx < 16; idx++ )
            h->quantf.denoise_dct( dct4x4[idx], h->nr_residual_sum[0], h->nr_offset[0], 16 );
    //遍历每个4x4块,将DCT系数的直流分量存入dct_dc4x4数组,并将DCT系数的直流分量置零
    for( int idx = 0; idx < 16; idx++ )
    {
        dct_dc4x4[block_idx_xy_1d[idx]] = dct4x4[idx][0];
        dct4x4[idx][0] = 0;
    }

    if( h->mb.b_trellis )
    {   //启用了Trellis量化,则使用循环遍历每个4x4块,调用x264_quant_4x4_trellis函数进行Trellis量化
        for( int idx = 0; idx < 16; idx++ )
            if( x264_quant_4x4_trellis( h, dct4x4[idx], i_quant_cat, i_qp, ctx_cat_plane[DCT_LUMA_AC][p], 1, !!p, idx ) )
            {   //果返回值为真,表示该块经过Trellis量化后非零系数的数量发生了变化
                block_cbp = 0xf;//将块的CBP值设置为0xf(即全部块都有非零系数
                h->zigzagf.scan_4x4( h->dct.luma4x4[16*p+idx], dct4x4[idx] );//然后执行扫描、反量化和判定非零系数的操作
                h->quantf.dequant_4x4( dct4x4[idx], h->dequant4_mf[i_quant_cat], i_qp );
                if( decimate_score < 6 ) decimate_score += h->quantf.decimate_score15( h->dct.luma4x4[16*p+idx] );
                h->mb.cache.non_zero_count[x264_scan8[16*p+idx]] = 1;
            }
    }
    else
    {
        for( int i8x8 = 0; i8x8 < 4; i8x8++ )
        {   //如果未启用Trellis量化,则使用循环遍历每个8x8块,调用h->quantf.quant_4x4x4函数进行普通的量化。该函数返回非零系数的数量
            nz = h->quantf.quant_4x4x4( &dct4x4[i8x8*4], h->quant4_mf[i_quant_cat][i_qp], h->quant4_bias[i_quant_cat][i_qp] );
            if( nz )
            {
                block_cbp = 0xf;//如果非零系数的数量大于0,将块的CBP值设置为0xf,然后执行扫描、反量化和判定非零系数的操作
                FOREACH_BIT( idx, i8x8*4, nz )
                {
                    h->zigzagf.scan_4x4( h->dct.luma4x4[16*p+idx], dct4x4[idx] );
                    h->quantf.dequant_4x4( dct4x4[idx], h->dequant4_mf[i_quant_cat], i_qp );
                    if( decimate_score < 6 ) decimate_score += h->quantf.decimate_score15( h->dct.luma4x4[16*p+idx] );
                    h->mb.cache.non_zero_count[x264_scan8[16*p+idx]] = 1;
                }
            }
        }
    }

    /* Writing the 16 CBFs in an i16x16 block is quite costly, so decimation can save many bits. */
    /* More useful with CAVLC, but still useful with CABAC. */
    if( decimate_score < 6 )
    {   //如果decimate_score小于6,表示减采样得分低于阈值,执行减采样的逻辑。首先将16x16块的非零系数数量清零,将块的CBP值设置为0,表示该块没有非零系数
        CLEAR_16x16_NNZ( p );
        block_cbp = 0;
    }
    else//将块的CBP值加入到h->mb.i_cbp_luma中,表示该块存在非零系数
        h->mb.i_cbp_luma |= block_cbp;
    //调用h->dctf.dct4x4dc函数对16x16块的直流分量进行DCT变换
    h->dctf.dct4x4dc( dct_dc4x4 );
    if( h->mb.b_trellis )
        nz = x264_quant_luma_dc_trellis( h, dct_dc4x4, i_quant_cat, i_qp, ctx_cat_plane[DCT_LUMA_DC][p], 1, LUMA_DC+p );
    else//调用h->quantf.quant_4x4_dc函数进行普通的直流分量量化。返回值nz表示非零系数的数量
        nz = h->quantf.quant_4x4_dc( dct_dc4x4, h->quant4_mf[i_quant_cat][i_qp][0]>>1, h->quant4_bias[i_quant_cat][i_qp][0]<<1 );
    //更新块的非零系数计数数组
    h->mb.cache.non_zero_count[x264_scan8[LUMA_DC+p]] = nz;
    if( nz )
    {   //如果非零系数的数量大于0,执行扫描、反量化和判定非零系数的操作。将DCT系数按照扫描顺序重新排列,并将直流分量存入dct_dc4x4数组中
        h->zigzagf.scan_4x4( h->dct.luma16x16_dc[p], dct_dc4x4 );

        /* output samples to fdec */
        h->dctf.idct4x4dc( dct_dc4x4 );
        h->quantf.dequant_4x4_dc( dct_dc4x4, h->dequant4_mf[i_quant_cat], i_qp );  /* XXX not inversed */
        if( block_cbp )
            for( int i = 0; i < 16; i++ )
                dct4x4[i][0] = dct_dc4x4[block_idx_xy_1d[i]];
    }

    /* put pixels to fdec */
    if( block_cbp )//如果block_cbp(块的CBP值)为真,则调用h->dctf.add16x16_idct函数将反量化后的DCT系数加到输出帧p_dst中
        h->dctf.add16x16_idct( p_dst, dct4x4 );
    else if( nz )//如果block_cbp为假且非零系数的数量大于0,则调用h->dctf.add16x16_idct_dc函数将反量化后的直流分量加到输出帧p_dst中
        h->dctf.add16x16_idct_dc( p_dst, dct_dc4x4 );
}

2.4x4变换sub4x4_dct

这段代码实现了将两个4x4块的像素差值进行DCT变换,并将结果存储在dct数组中,代码如下:

static void sub4x4_dct( dctcoef dct[16], pixel *pix1, pixel *pix2 )
{
    dctcoef d[16];
    dctcoef tmp[16];//首先定义了一个临时数组d和一个中间结果数组tmp,用于存储计算过程中的中间结果
    //通过调用pixel_sub_wxh函数,计算两个4x4块的像素差值,将结果存储在数组d中,pix1和pix2分别表示两个4x4块的像素数组,FENC_STRIDE和FDEC_STRIDE表示对应的步长
    pixel_sub_wxh( d, 4, pix1, FENC_STRIDE, pix2, FDEC_STRIDE );
    //使用两次循环遍历,分别对tmp数组和最终结果数组dct进行计算
    for( int i = 0; i < 4; i++ )
    {   //第一个循环遍历对tmp数组进行计算。对于每个4x4块中的每一行
        int s03 = d[i*4+0] + d[i*4+3];//s03:第0列和第3列的和
        int s12 = d[i*4+1] + d[i*4+2];//s12:第1列和第2列的和
        int d03 = d[i*4+0] - d[i*4+3];//d03:第0列和第3列的差值
        int d12 = d[i*4+1] - d[i*4+2];//d12:第1列和第2列的差值
        //然后将这些值按照特定的公式计算得到中间结果,并存储在tmp数组中
        tmp[0*4+i] =   s03 +   s12;
        tmp[1*4+i] = 2*d03 +   d12;
        tmp[2*4+i] =   s03 -   s12;
        tmp[3*4+i] =   d03 - 2*d12;
    }

    for( int i = 0; i < 4; i++ )
    {   //第二个循环遍历对最终结果数组dct进行计算。对于每个4x4块中的每一列
        int s03 = tmp[i*4+0] + tmp[i*4+3];//s03:第0行和第3行的和
        int s12 = tmp[i*4+1] + tmp[i*4+2];//s12:第1行和第2行的和
        int d03 = tmp[i*4+0] - tmp[i*4+3];//d03:第0行和第3行的差值
        int d12 = tmp[i*4+1] - tmp[i*4+2];//d12:第1行和第2行的差值
        //然后将这些值按照特定的公式计算得到最终的DCT系数,并存储在dct数组中
        dct[i*4+0] =   s03 +   s12;
        dct[i*4+1] = 2*d03 +   d12;
        dct[i*4+2] =   s03 -   s12;
        dct[i*4+3] =   d03 - 2*d12;
    }
}

3.量化quant_4x4x4

实现了对一个4x4x4的DCT系数数组进行量化操作,并返回记录非零系数情况的标志变量,代码如下:

static int quant_4x4x4( dctcoef dct[4][16], udctcoef mf[16], udctcoef bias[16] )
{
    int nza = 0;//首先定义了一个变量nza用于记录非零系数的情况。初始值为0
    for( int j = 0; j < 4; j++ )
    {   //外层循环for遍历4个4x4块的DCT系数
        int nz = 0;
        for( int i = 0; i < 16; i++ )//内层循环for遍历每个4x4块中的16个DCT系数
            QUANT_ONE( dct[j][i], mf[i], bias[i] );//在内层循环中,调用QUANT_ONE函数对每个DCT系数进行量化
        nza |= (!!nz)<<j;//如果量化后的系数nz不为零,则将对应的位(!!nz)设置为1
    }
    return nza;//返回记录非零系数情况的变量nza
}

4.针对DC量化dct4x4dc

这段代码实现了对一个4x4块的DCT系数进行直流成分变换,并将结果存储在数组d中,代码如下:

static void dct4x4dc( dctcoef d[16] )
{
    dctcoef tmp[16];

    for( int i = 0; i < 4; i++ )
    {
        int s01 = d[i*4+0] + d[i*4+1];
        int d01 = d[i*4+0] - d[i*4+1];
        int s23 = d[i*4+2] + d[i*4+3];
        int d23 = d[i*4+2] - d[i*4+3];

        tmp[0*4+i] = s01 + s23;
        tmp[1*4+i] = s01 - s23;
        tmp[2*4+i] = d01 - d23;
        tmp[3*4+i] = d01 + d23;
    }

    for( int i = 0; i < 4; i++ )
    {
        int s01 = tmp[i*4+0] + tmp[i*4+1];
        int d01 = tmp[i*4+0] - tmp[i*4+1];
        int s23 = tmp[i*4+2] + tmp[i*4+3];
        int d23 = tmp[i*4+2] - tmp[i*4+3];

        d[i*4+0] = ( s01 + s23 + 1 ) >> 1;
        d[i*4+1] = ( s01 - s23 + 1 ) >> 1;
        d[i*4+2] = ( d01 - d23 + 1 ) >> 1;
        d[i*4+3] = ( d01 + d23 + 1 ) >> 1;
    }
}

点赞、收藏,会是我继续写作的动力!赠人玫瑰,手有余香

  • 6
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值