【x264】滤波模块的简单分析

【x264】滤波模块的简单分析

  • 1. 滤波模块概述
    • 1.1 自适应边界
    • 1.2 自适应样点级滤波器
    • 1.3 滤波过程
  • 2. 函数入口(fdec_filter_row)
    • 2.1 去块滤波(x264_frame_deblock_row)
      • 2.1.1 强滤波函数(deblock_edge_intra)
      • 2.1.2 普通滤波函数(deblock_edge)
  • 3.小结

参数分析:
【x264】x264编码器参数配置

流程分析:
【x264】x264编码主流程简单分析
【x264】编码核心函数(x264_encoder_encode)的简单分析
【x264】分析模块(analyse)的简单分析—帧内预测
【x264】分析模块(analyse)的简单分析—帧间预测
【x264】码率控制模块的简单分析—宏块级码控工具Mbtree和AQ
【x264】码率控制模块的简单分析—帧级码控策略
【x264】码率控制模块的简单分析—编码主流程
【x264】lookahead模块的简单分析
【x264】变换量化模块的简单分析
【x264】熵编码模块的简单分析

1. 滤波模块概述

滤波模块存在的意义是,由于编解码过程中的变换量化过程带来的误差,重建帧和原始帧往往有一定的差异程度,这种差异程度在mb的边界部分较为突出,具体表现为两个mb之间的边界模糊、不连续,此时需要进行滤波来去除这种边界模糊的不利影响。由于滤波模块存在位置在编解码的环路之中,同时去除的是相邻块之间的模糊,所以滤波模块又称之为环路去块滤波(In-loop deblock filter)或环路滤波。下图中,左侧图片没有使用滤波,右侧图片使用了滤波。明显看出没有使用滤波之前,块之间有明显的不连续情况,而使用了之后明显平滑许多
在这里插入图片描述

1.1 自适应边界

边界强度(Border Strength,BS)决定去方块滤波器选择的滤波参数,控制去方块效应的程度。对所有4x4亮度块间的边界,边界强度参数值在0~4之间,它与边界的性质有关。滤波器强度参数与编码模式的关系为

图像块模式与条件Bs
边界两边一个图像块为帧内预测并且边界为宏块边界4
边界两边一个图像块为帧内预测3
边界两边一个图像块对残差编码2
边界两边图像块运动矢量差不小于1个亮度图像点距离1
其他0

在实际的滤波算法中,Bs决定对边界的滤波强度,包括对两个主要滤波模式的选择。当其值为4时表示要用特定最强的滤波模式,而其值为0表示不需要对边界进行滤波。当其值为1~3时,为标准的滤波模式,Bs值影响滤波器对样点的最大修正程度。Bs值下降趋势说明最强的方块效应主要来自于帧内预测模式及对预测残差编码,而在较小程度上与图像的运动补偿有关。此外,色度块边界滤波的Bs值不会单独计算,而是从相应亮度边界的Bs值复制而来。

1.2 自适应样点级滤波器

在去块滤波中,需要区分图像中的真实边界和由DCT变换系数量化而造成的假边界。为了保持图像的逼真度,应该尽量在滤波假边界以不致被看出的同时保持图像真实边界不被滤波。为了区分,需要分析边界两边的样点值。现在假设相邻4x4块中一条直线上的样点为p3, p2, p1, p0, q0, q1, q2, q3,实际的图像边界在p0和q0之间,只有满足如下条件时才会进行滤波:
∣ p 0 − q 0 ∣ < α ( I n d e x A ) ∣ p 1 − p 0 ∣ < β ( I n d e x B ) ∣ q 1 − q 0 ∣ < β ( I n d e x B ) |p_0 - q_0| < \alpha (Index_A) \\ |p_1 - p_0| < \beta (Index_B) \\ |q_1 - q_0| < \beta (Index_B) p0q0<α(IndexA)p1p0<β(IndexB)q1q0<β(IndexB)
其中, α \alpha α β \beta β根据查表获取,查找索引值计算方式为
I n d e x i = { 0 , Q P + O f f s e t i ≤ 0 Q P + O f f s e t i , 0 < Q P + O f f s e t i < 51 51 , Q P + O f f s e t i ≥ 51 Index_i=\left\{ \begin{matrix} 0, QP+Offset_i \leq 0\\ QP+Offset_i, 0 < QP+Offset_i < 51 \\ 51, QP+Offset_i \geq 51 \end{matrix} \right. Indexi= 0,QP+Offseti0QP+Offseti,0<QP+Offseti<5151,QP+Offseti51
其中, i = A 或 B i=A或B i=AB

1.3 滤波过程

滤波是基于宏块的,先对垂直边界进行水平滤波,再对水平边界进行垂直滤波。滤波顺序如下所示,首先滤波宏块最左侧的边界(0号边界),后续依次滤波1,2,3号边界,随后滤波宏块最上方的边界(4号边界),随后依次滤波5,6,7号边界

/*
 * 滤波顺序如下所示(大方框代表16x16块)
 *
 * +--4-+--4-+--4-+--4-+
 * 0    1    2    3    |
 * +--5-+--5-+--5-+--5-+
 * 0    1    2    3    |
 * +--6-+--6-+--6-+--6-+
 * 0    1    2    3    |
 * +--7-+--7-+--7-+--7-+
 * 0    1    2    3    |
 * +----+----+----+----+
 *
*/

(1)如果Bs从1到3,滤波的过程可以描述为
p 0 ’ = p 0 + ( ( ( q 0 − p 0 ) < < 2 ) + ( p 1 − q 1 ) + 4 ) > > 3 p 1 ’ = ( p 2 + ( ( p 0 + q 0 + 1 ) > > 1 ) – 2 p 1 ) > > 1 p0’ = p0 + (((q0 - p0 ) << 2) + (p1 - q1) + 4) >> 3 \\ p1’ = ( p2 + ( ( p0 + q0 + 1 ) >> 1) – 2p1 ) >> 1 p0’=p0+(((q0p0)<<2)+(p1q1)+4)>>3p1’=(p2+((p0+q0+1)>>1)–2p1)>>1
(2)如果Bs为4,滤波的过程可以描述为
p 0 ’ = ( p 2 + 2 ∗ p 1 + 2 ∗ p 0 + 2 ∗ q 0 + q 1 + 4 ) > > 3 p 1 ’ = ( p 2 + p 1 + p 0 + q 0 + 2 ) > > 2 p 2 ’ = ( 2 ∗ p 3 + 3 ∗ p 2 + p 1 + p 0 + q 0 + 4 ) > > 3 p0’ = ( p2 + 2*p1 + 2*p0 + 2*q0 + q1 + 4 ) >> 3 \\ p1’ = ( p2 + p1 + p0 + q0 + 2 ) >> 2 \\ p2’ = ( 2*p3 + 3*p2 + p1 + p0 + q0 + 4 ) >> 3 p0’=(p2+2p1+2p0+2q0+q1+4)>>3p1’=(p2+p1+p0+q0+2)>>2p2’=(2p3+3p2+p1+p0+q0+4)>>3

2. 函数入口(fdec_filter_row)

滤波模块的入口为fdec_filter_row,定义在encoder\encoder.c中,主要的工作流程为:

  1. 进行去块滤波(x264_frame_deblock_row)
  2. 半像素内插(x264_frame_filter)
  3. 计算质量
    (1)计算psnr(x264_pixel_ssd_wxh)
    (2)计算ssim(x264_pixel_ssim_wxh)
static void fdec_filter_row( x264_t *h, int mb_y, int pass )
{
    /* mb_y is the mb to be encoded next, not the mb to be filtered here */
    int b_hpel = h->fdec->b_kept_as_ref;
    int b_deblock = h->sh.i_disable_deblocking_filter_idc != 1;
    int b_end = mb_y == h->i_threadslice_end;
    int b_measure_quality = 1;
    int min_y = mb_y - (1 << SLICE_MBAFF);
    int b_start = min_y == h->i_threadslice_start;
    /* Even in interlaced mode, deblocking never modifies more than 4 pixels
     * above each MB, as bS=4 doesn't happen for the top of interlaced mbpairs. */
    int minpix_y = min_y*16 - 4 * !b_start;
    int maxpix_y = mb_y*16 - 4 * !b_end;
    b_deblock &= b_hpel || h->param.b_full_recon || h->param.psz_dump_yuv;
	// slice级的线程使用
    if( h->param.b_sliced_threads )
    {
        switch( pass )
        {
            /* During encode: only do deblock if asked for */
            default:
            case 0:
                b_deblock &= h->param.b_full_recon;
                b_hpel = 0;
                break;
            /* During post-encode pass: do deblock if not done yet, do hpel for all
             * rows except those between slices. */
            case 1:
                b_deblock &= !h->param.b_full_recon;
                b_hpel &= !(b_start && min_y > 0);
                b_measure_quality = 0;
                break;
            /* Final pass: do the rows between slices in sequence. */
            case 2:
                b_deblock = 0;
                b_measure_quality = 0;
                break;
        }
    }
    if( mb_y & SLICE_MBAFF )
        return;
    if( min_y < h->i_threadslice_start )
        return;
	// ----- 1.进行deblock处理 ----- //
    if( b_deblock )
        for( int y = min_y; y < mb_y; y += (1 << SLICE_MBAFF) )
            x264_frame_deblock_row( h, y ); // 按照行处理

    /* FIXME: Prediction requires different borders for interlaced/progressive mc,
     * but the actual image data is equivalent. For now, maintain this
     * consistency by copying deblocked pixels between planes. */
	// 交错/渐进式mc的预测需要不同的边界,但实际图像数据是等效的
	// 现在,通过在平面之间复制去块像素来保持这种一致性
    if( PARAM_INTERLACED && (!h->param.b_sliced_threads || pass == 1) )
        for( int p = 0; p < h->fdec->i_plane; p++ )
            for( int i = minpix_y>>(CHROMA_V_SHIFT && p); i < maxpix_y>>(CHROMA_V_SHIFT && p); i++ )
                memcpy( h->fdec->plane_fld[p] + i*h->fdec->i_stride[p],
                        h->fdec->plane[p]     + i*h->fdec->i_stride[p],
                        h->mb.i_mb_width*16*SIZEOF_PIXEL );

    if( h->fdec->b_kept_as_ref && (!h->param.b_sliced_threads || pass == 1) )
        x264_frame_expand_border( h, h->fdec, min_y );
	// ----- 2.半像素内插 ----- //
    if( b_hpel )
    {
        int end = mb_y == h->mb.i_mb_height;
        /* Can't do hpel until the previous slice is done encoding. */
        if( h->param.analyse.i_subpel_refine )
        {
            x264_frame_filter( h, h->fdec, min_y, end ); // 半像素内插入口
            x264_frame_expand_border_filtered( h, h->fdec, min_y, end );
        }
    }

    if( SLICE_MBAFF && pass == 0 )
        for( int i = 0; i < 3; i++ )
        {
            XCHG( pixel *, h->intra_border_backup[0][i], h->intra_border_backup[3][i] );
            XCHG( pixel *, h->intra_border_backup[1][i], h->intra_border_backup[4][i] );
        }

    if( h->i_thread_frames > 1 && h->fdec->b_kept_as_ref )
        x264_frame_cond_broadcast( h->fdec, mb_y*16 + (b_end ? 10000 : -(X264_THREAD_HEIGHT << SLICE_MBAFF)) );
	// ----- 3.计算质量 ----- //
    if( b_measure_quality )
    {
        maxpix_y = X264_MIN( maxpix_y, h->param.i_height );
        if( h->param.analyse.b_psnr ) // 计算psnr
        {
            for( int p = 0; p < (CHROMA444 ? 3 : 1); p++ )
                h->stat.frame.i_ssd[p] += x264_pixel_ssd_wxh( &h->pixf,
                    h->fdec->plane[p] + minpix_y * h->fdec->i_stride[p], h->fdec->i_stride[p],
                    h->fenc->plane[p] + minpix_y * h->fenc->i_stride[p], h->fenc->i_stride[p],
                    h->param.i_width, maxpix_y-minpix_y );
            if( !CHROMA444 )
            {
                uint64_t ssd_u, ssd_v;
                int v_shift = CHROMA_V_SHIFT;
                x264_pixel_ssd_nv12( &h->pixf,
                    h->fdec->plane[1] + (minpix_y>>v_shift) * h->fdec->i_stride[1], h->fdec->i_stride[1],
                    h->fenc->plane[1] + (minpix_y>>v_shift) * h->fenc->i_stride[1], h->fenc->i_stride[1],
                    h->param.i_width>>1, (maxpix_y-minpix_y)>>v_shift, &ssd_u, &ssd_v );
                h->stat.frame.i_ssd[1] += ssd_u;
                h->stat.frame.i_ssd[2] += ssd_v;
            }
        }

        if( h->param.analyse.b_ssim ) // 计算ssim
        {
            int ssim_cnt;
            x264_emms();
            /* offset by 2 pixels to avoid alignment of ssim blocks with dct blocks,
             * and overlap by 4 */
            minpix_y += b_start ? 2 : -6;
            h->stat.frame.f_ssim +=
                x264_pixel_ssim_wxh( &h->pixf,
                    h->fdec->plane[0] + 2+minpix_y*h->fdec->i_stride[0], h->fdec->i_stride[0],
                    h->fenc->plane[0] + 2+minpix_y*h->fenc->i_stride[0], h->fenc->i_stride[0],
                    h->param.i_width-2, maxpix_y-minpix_y, h->scratch_buffer, &ssim_cnt );
            h->stat.frame.i_ssim_cnt += ssim_cnt;
        }
    }
}

2.1 去块滤波(x264_frame_deblock_row)

函数的主要功能是执行具体的边界滤波,主要的工作流程为:

  1. 定义FILTER宏,会决定滤波的强弱、滤波的方向和滤波的边界
  2. 逐次滤波每条边界
    (1)FILTER( _intra, 0, 0, qp_left, qpc_left ); // 第0条边界, 强滤波,水平滤波器(垂直边界)
    或 FILTER( , 0, 0, qp_left, qpc_left ); // 0, 普通滤波,水平滤波器(垂直边界)
    (2)FILTER( , 0, 1, qp, qpc ); // 第1条边界//普通滤波,水平滤波器(垂直边界)
    (3)FILTER( , 0, 2, qp, qpc ); // 第2条边界
    (4)FILTER( , 0, 3, qp, qpc ); // 第3条边界
    (5)FILTER( _intra, 1, 0, qp_top, qpc_top ); // 第4条边界,普通滤波,垂直滤波器(水平边界)
    (6)FILTER( , 1, 1, qp, qpc );// 第5条边界,普通滤波,垂直滤波器(水平边界)
    (7)FILTER( , 1, 2, qp, qpc );// 第6条边界
    (8)FILTER( , 1, 3, qp, qpc );// 第7条边界

边界和编号对应的关系是
在这里插入图片描述
此外,边界滤波会根据具体情况使用deblock_edge_intra或者是deblock_edge函数之一

void x264_frame_deblock_row( x264_t *h, int mb_y )
{
    int b_interlaced = SLICE_MBAFF;
    int a = h->sh.i_alpha_c0_offset - QP_BD_OFFSET;
    int b = h->sh.i_beta_offset - QP_BD_OFFSET;
    int qp_thresh = 15 - X264_MIN( a, b ) - X264_MAX( 0, h->pps->i_chroma_qp_index_offset );
    int stridey   = h->fdec->i_stride[0];
    int strideuv  = h->fdec->i_stride[1];
    int chroma_format = CHROMA_FORMAT;
    int chroma444 = CHROMA444;
    int chroma_height = 16 >> CHROMA_V_SHIFT;
    intptr_t uvdiff = chroma444 ? h->fdec->plane[2] - h->fdec->plane[1] : 1;

    for( int mb_x = 0; mb_x < h->mb.i_mb_width; mb_x += (~b_interlaced | mb_y)&1, mb_y ^= b_interlaced )
    {
		// 获取fdec信息
        x264_prefetch_fenc( h, h->fdec, mb_x, mb_y );
		// 从cache中获取相邻已进行deblock的块
        macroblock_cache_load_neighbours_deblock( h, mb_x, mb_y );

        int mb_xy = h->mb.i_mb_xy;
        int transform_8x8 = h->mb.mb_transform_size[mb_xy];
        int intra_cur = IS_INTRA( h->mb.type[mb_xy] );
        uint8_t (*bs)[8][4] = h->deblock_strength[mb_y&1][h->param.b_sliced_threads?mb_xy:mb_x];

        pixel *pixy = h->fdec->plane[0] + 16*mb_y*stridey  + 16*mb_x;
        pixel *pixuv = CHROMA_FORMAT ? h->fdec->plane[1] + chroma_height*mb_y*strideuv + 16*mb_x : NULL;

        if( mb_y & MB_INTERLACED )
        {
            pixy -= 15*stridey;
            if( CHROMA_FORMAT )
                pixuv -= (chroma_height-1)*strideuv;
        }

        int stride2y  = stridey << MB_INTERLACED;
        int stride2uv = strideuv << MB_INTERLACED;
        int qp = h->mb.qp[mb_xy];
        int qpc = h->chroma_qp_table[qp];
        int first_edge_only = (h->mb.partition[mb_xy] == D_16x16 && !h->mb.cbp[mb_xy] && !intra_cur) || qp <= qp_thresh;


		/*
		 * 滤波顺序如下所示(大方框代表16x16块)
		 *
		 * +--4-+--4-+--4-+--4-+
		 * 0    1    2    3    |
		 * +--5-+--5-+--5-+--5-+
		 * 0    1    2    3    |
		 * +--6-+--6-+--6-+--6-+
		 * 0    1    2    3    |
		 * +--7-+--7-+--7-+--7-+
		 * 0    1    2    3    |
		 * +----+----+----+----+
		 *
		*/

		// ## 表示连接符
		// 当intra有效时,deblock_edge##intra -> deblock_edge_intra()
		// 当intra 为空时,deblock_edge##intra -> deblock_edge()
		// dir为滤波的方向,dir=0表示水平滤波器(处理的是垂直边界),dir=1表示垂直滤波器(处理的是水平边界)
        #define FILTER( intra, dir, edge, qp, chroma_qp )\
        do\
        {\
            if( !(edge & 1) || !transform_8x8 )\
            {\
                deblock_edge##intra( h, pixy + 4*edge*(dir?stride2y:1),\
                                     stride2y, bs[dir][edge], qp, a, b, 0,\
                                     h->loopf.deblock_luma##intra[dir] );\
                if( chroma_format == CHROMA_444 )\
                {\
                    deblock_edge##intra( h, pixuv          + 4*edge*(dir?stride2uv:1),\
                                         stride2uv, bs[dir][edge], chroma_qp, a, b, 0,\
                                         h->loopf.deblock_luma##intra[dir] );\
                    deblock_edge##intra( h, pixuv + uvdiff + 4*edge*(dir?stride2uv:1),\
                                         stride2uv, bs[dir][edge], chroma_qp, a, b, 0,\
                                         h->loopf.deblock_luma##intra[dir] );\
                }\
                else if( chroma_format == CHROMA_420 && !(edge & 1) )\
                {\
                    deblock_edge##intra( h, pixuv + edge*(dir?2*stride2uv:4),\
                                         stride2uv, bs[dir][edge], chroma_qp, a, b, 1,\
                                         h->loopf.deblock_chroma##intra[dir] );\
                }\
            }\
            if( chroma_format == CHROMA_422 && (dir || !(edge & 1)) )\
            {\
                deblock_edge##intra( h, pixuv + edge*(dir?4*stride2uv:4),\
                                     stride2uv, bs[dir][edge], chroma_qp, a, b, 1,\
                                     h->loopf.deblock_chroma##intra[dir] );\
            }\
        } while( 0 )
		// 如果左侧的mb存在
        if( h->mb.i_neighbour & MB_LEFT )
        {
			// 如果是隔行处理
            if( b_interlaced && h->mb.field[h->mb.i_mb_left_xy[0]] != MB_INTERLACED )
            {
                int luma_qp[2];
                int chroma_qp[2];
                int left_qp[2];
                x264_deblock_inter_t luma_deblock = h->loopf.deblock_luma_mbaff;
                x264_deblock_inter_t chroma_deblock = h->loopf.deblock_chroma_mbaff;
                x264_deblock_intra_t luma_intra_deblock = h->loopf.deblock_luma_intra_mbaff;
                x264_deblock_intra_t chroma_intra_deblock = h->loopf.deblock_chroma_intra_mbaff;
                int c = chroma444 ? 0 : 1;

                left_qp[0] = h->mb.qp[h->mb.i_mb_left_xy[0]];
                luma_qp[0] = (qp + left_qp[0] + 1) >> 1;
                chroma_qp[0] = (qpc + h->chroma_qp_table[left_qp[0]] + 1) >> 1;
                if( intra_cur || IS_INTRA( h->mb.type[h->mb.i_mb_left_xy[0]] ) )
                {
                    deblock_edge_intra( h, pixy,           2*stridey,  bs[0][0], luma_qp[0],   a, b, 0, luma_intra_deblock );
                    if( chroma_format )
                    {
                        deblock_edge_intra( h, pixuv,          2*strideuv, bs[0][0], chroma_qp[0], a, b, c, chroma_intra_deblock );
                        if( chroma444 )
                            deblock_edge_intra( h, pixuv + uvdiff, 2*strideuv, bs[0][0], chroma_qp[0], a, b, c, chroma_intra_deblock );
                    }
                }
                else
                {
                    deblock_edge( h, pixy,           2*stridey,  bs[0][0], luma_qp[0],   a, b, 0, luma_deblock );
                    if( chroma_format )
                    {
                        deblock_edge( h, pixuv,          2*strideuv, bs[0][0], chroma_qp[0], a, b, c, chroma_deblock );
                        if( chroma444 )
                            deblock_edge( h, pixuv + uvdiff, 2*strideuv, bs[0][0], chroma_qp[0], a, b, c, chroma_deblock );
                    }
                }

                int offy = MB_INTERLACED ? 4 : 0;
                int offuv = MB_INTERLACED ? 4-CHROMA_V_SHIFT : 0;
                left_qp[1] = h->mb.qp[h->mb.i_mb_left_xy[1]];
                luma_qp[1] = (qp + left_qp[1] + 1) >> 1;
                chroma_qp[1] = (qpc + h->chroma_qp_table[left_qp[1]] + 1) >> 1;
                if( intra_cur || IS_INTRA( h->mb.type[h->mb.i_mb_left_xy[1]] ) )
                {
                    deblock_edge_intra( h, pixy           + (stridey<<offy),   2*stridey,  bs[0][4], luma_qp[1],   a, b, 0, luma_intra_deblock );
                    if( chroma_format )
                    {
                        deblock_edge_intra( h, pixuv          + (strideuv<<offuv), 2*strideuv, bs[0][4], chroma_qp[1], a, b, c, chroma_intra_deblock );
                        if( chroma444 )
                            deblock_edge_intra( h, pixuv + uvdiff + (strideuv<<offuv), 2*strideuv, bs[0][4], chroma_qp[1], a, b, c, chroma_intra_deblock );
                    }
                }
                else
                {
                    deblock_edge( h, pixy           + (stridey<<offy),   2*stridey,  bs[0][4], luma_qp[1],   a, b, 0, luma_deblock );
                    if( chroma_format )
                    {
                        deblock_edge( h, pixuv          + (strideuv<<offuv), 2*strideuv, bs[0][4], chroma_qp[1], a, b, c, chroma_deblock );
                        if( chroma444 )
                            deblock_edge( h, pixuv + uvdiff + (strideuv<<offuv), 2*strideuv, bs[0][4], chroma_qp[1], a, b, c, chroma_deblock );
                    }
                }
            }
            else
            {	// 非隔行处理
                int qpl = h->mb.qp[h->mb.i_mb_xy-1];
                int qp_left = (qp + qpl + 1) >> 1;
                int qpc_left = (qpc + h->chroma_qp_table[qpl] + 1) >> 1;
                int intra_left = IS_INTRA( h->mb.type[h->mb.i_mb_xy-1] );
                int intra_deblock = intra_cur || intra_left;

                /* Any MB that was coded, or that analysis decided to skip, has quality commensurate with its QP.
                 * But if deblocking affects neighboring MBs that were force-skipped, blur might accumulate there.
                 * So reset their effective QP to max, to indicate that lack of guarantee. */
				// 任何被编码的MB,或者分析决定跳过的MB,都具有与其QP相称的质量
				// 但是,如果块化影响了被强制跳过的相邻mb,则模糊可能会在那里积聚
				// 所以将他们的有效QP重置为最大值,以表明缺乏保证。
                if( h->fdec->mb_info && M32( bs[0][0] ) )
                {
#define RESET_EFFECTIVE_QP(xy) h->fdec->effective_qp[xy] |= 0xff * !!(h->fdec->mb_info[xy] & X264_MBINFO_CONSTANT);
                    RESET_EFFECTIVE_QP(mb_xy);
                    RESET_EFFECTIVE_QP(h->mb.i_mb_left_xy[0]);
                }

                if( intra_deblock )
                    FILTER( _intra, 0, 0, qp_left, qpc_left ); // 0, 强滤波,水平滤波器(垂直边界)
                else
                    FILTER(       , 0, 0, qp_left, qpc_left ); // 0, 普通滤波,水平滤波器(垂直边界)
            }
        }
        if( !first_edge_only )
        {
			// 水平滤波器(垂直边界)
            FILTER( , 0, 1, qp, qpc ); // 1
            FILTER( , 0, 2, qp, qpc ); // 2
            FILTER( , 0, 3, qp, qpc ); // 3
        }
		// 如果上方的mb存在
        if( h->mb.i_neighbour & MB_TOP )
        {
            if( b_interlaced && !(mb_y&1) && !MB_INTERLACED && h->mb.field[h->mb.i_mb_top_xy] )
            {
                int mbn_xy = mb_xy - 2 * h->mb.i_mb_stride;

                for( int j = 0; j < 2; j++, mbn_xy += h->mb.i_mb_stride )
                {
                    int qpt = h->mb.qp[mbn_xy];
                    int qp_top = (qp + qpt + 1) >> 1;
                    int qpc_top = (qpc + h->chroma_qp_table[qpt] + 1) >> 1;
                    int intra_top = IS_INTRA( h->mb.type[mbn_xy] );
                    if( intra_cur || intra_top )
                        M32( bs[1][4*j] ) = 0x03030303;

                    // deblock the first horizontal edge of the even rows, then the first horizontal edge of the odd rows
                    deblock_edge( h, pixy      + j*stridey,  2* stridey, bs[1][4*j], qp_top, a, b, 0, h->loopf.deblock_luma[1] );
                    if( chroma444 )
                    {
                        deblock_edge( h, pixuv          + j*strideuv, 2*strideuv, bs[1][4*j], qpc_top, a, b, 0, h->loopf.deblock_luma[1] );
                        deblock_edge( h, pixuv + uvdiff + j*strideuv, 2*strideuv, bs[1][4*j], qpc_top, a, b, 0, h->loopf.deblock_luma[1] );
                    }
                    else if( chroma_format )
                        deblock_edge( h, pixuv          + j*strideuv, 2*strideuv, bs[1][4*j], qpc_top, a, b, 1, h->loopf.deblock_chroma[1] );
                }
            }
            else
            {
                int qpt = h->mb.qp[h->mb.i_mb_top_xy];
                int qp_top = (qp + qpt + 1) >> 1;
                int qpc_top = (qpc + h->chroma_qp_table[qpt] + 1) >> 1;
                int intra_top = IS_INTRA( h->mb.type[h->mb.i_mb_top_xy] );
                int intra_deblock = intra_cur || intra_top;

                /* This edge has been modified, reset effective qp to max. */
				// 如果edge已经被更新,重置有效的qp为最大值
                if( h->fdec->mb_info && M32( bs[1][0] ) )
                {
                    RESET_EFFECTIVE_QP(mb_xy);
                    RESET_EFFECTIVE_QP(h->mb.i_mb_top_xy);
                }

                if( (!b_interlaced || (!MB_INTERLACED && !h->mb.field[h->mb.i_mb_top_xy])) && intra_deblock )
                {
                    FILTER( _intra, 1, 0, qp_top, qpc_top ); // 4 普通滤波,垂直滤波器(水平边界)
                }
                else
                {
                    if( intra_deblock )
                        M32( bs[1][0] ) = 0x03030303;
                    FILTER(       , 1, 0, qp_top, qpc_top ); // 4 普通滤波,垂直滤波器(水平边界)
                }
            }
        }

        if( !first_edge_only )
        {	// 垂直滤波器(水平边界)
            FILTER( , 1, 1, qp, qpc ); // 5
            FILTER( , 1, 2, qp, qpc ); // 6
            FILTER( , 1, 3, qp, qpc ); // 7
        }

        #undef FILTER
    }
}

2.1.1 强滤波函数(deblock_edge_intra)

强滤波函数会根据alpha和beta来进行滤波调整,alphae和beta与qp有对应关系。在标准当中,强滤波使用的情况是Bs=4

static ALWAYS_INLINE void deblock_edge_intra( x264_t *h, pixel *pix, intptr_t i_stride, uint8_t bS[4], int i_qp,
                                              int a, int b, int b_chroma, x264_deblock_intra_t pf_intra )
{
    int index_a = i_qp + a;
    int index_b = i_qp + b;
    int alpha = alpha_table(index_a) << (BIT_DEPTH-8);
    int beta  = beta_table(index_b) << (BIT_DEPTH-8);
	//根据QP,通过查表的方法获得是否滤波的门限值alpha和beta
    //alpha为边界两边2点的门限值
    //beta为边界一边最靠近边界的2点的门限值
    //总体说来,QP越大,alpha和beta越大,越有可能滤波
    // alpha或beta有一个为0时,不进行滤波
    if( !alpha || !beta )
        return;
	// 具体进行滤波的入口,pf_intra通常使用汇编函数来加速处理,极少会使用c语言函数来处理
    pf_intra( pix, i_stride, alpha, beta );
}

2.1.2 普通滤波函数(deblock_edge)

普通滤波函数除了会计算alpha和beta之外,还会根据查找表来计算tc,再进行具体的滤波。在标准中,普通滤波分为3种情况,Bs=1,2或3

static ALWAYS_INLINE void deblock_edge( x264_t *h, pixel *pix, intptr_t i_stride, uint8_t bS[4], int i_qp,
                                        int a, int b, int b_chroma, x264_deblock_inter_t pf_inter )
{
    int index_a = i_qp + a;
    int index_b = i_qp + b;
    int alpha = alpha_table(index_a) << (BIT_DEPTH-8);
    int beta  = beta_table(index_b) << (BIT_DEPTH-8);
    int8_t tc[4];

    if( !M32(bS) || !alpha || !beta )
        return;

    tc[0] = (tc0_table(index_a)[bS[0]] * (1 << (BIT_DEPTH-8))) + b_chroma;
    tc[1] = (tc0_table(index_a)[bS[1]] * (1 << (BIT_DEPTH-8))) + b_chroma;
    tc[2] = (tc0_table(index_a)[bS[2]] * (1 << (BIT_DEPTH-8))) + b_chroma;
    tc[3] = (tc0_table(index_a)[bS[3]] * (1 << (BIT_DEPTH-8))) + b_chroma;

    pf_inter( pix, i_stride, alpha, beta, tc );
}

3.小结

滤波模块旨在解决重建后相邻mb之间块效应的问题,由于处于编解码环路之中,所以又称为环路滤波。另外,由于解决的问题是块效应问题,所以又成为去块滤波。滤波模块需要考虑的问题包括:(1)是否要进行滤波,这是根据相邻像素的具体数值进行判断的;(2)使用何种强度进行滤波,这是根据相邻块编码模式决定的。在具体的实现上,x264编码器通常会使用汇编函数进行优化处理,但也有c函数的支持

CSDN : https://blog.csdn.net/weixin_42877471
Github : https://github.com/DoFulangChen

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/719155.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

FPGA - 滤波器 - IIR滤波器设计

一&#xff0c;IIR滤波器 在FPGA - 滤波器 - FIR滤波器设计中可知&#xff0c;数字滤波器是一个时域离散系统。任何一个时域离散系统都可以用一个N阶差分方程来表示&#xff0c;即&#xff1a; 式中&#xff0c;x(n)和y(n)分别是系统的输入序列和输出序列&#xff1b;aj和bi均为…

vue简介实例

先看样式 再看代码 <div v-else class"relative mt-4 h-44 cursor-pointer overflow-hidden rounded-xl"><divclass"absolute flex h-44 w-full blur-lg":style"{ backgroundImage: url(${currentSongList.list[0]?.coverImgUrl}) }"…

jpg压缩在线方法,我只用这2种(无损)

在数字化的时代&#xff0c;我们经常需要分享、存储或上传各种图像文件&#xff0c;而JPG是其中最常见的图像格式之一。然而&#xff0c;大文件大小有时可能成为一个问题&#xff0c;尤其是在网络传输或存储空间有限的情况下。为了解决这一问题&#xff0c;我们可以利用在线工具…

滚球游戏笔记

1、准备工作 (1) 创建地面&#xff1a;3D Object-Plane&#xff0c;命名为Ground (2) 创建小球&#xff1a;3D Object-sphere&#xff0c;命名为Player&#xff0c;PositionY 0.5。添加Rigidbody组件 (3) 创建文件夹&#xff1a;Create-Foder&#xff0c;分别命名为Material…

Ps:条件模式更改

Ps菜单&#xff1a;文件/自动/条件模式更改 Automate/Conditional Mode Change 条件模式更改 Conditional Mode Change脚本命令可用于将图像文档的颜色模式更改为指定的模式。 既可以将指定的源颜色模式转换为目标颜色模式&#xff0c;还可以将此命令记录在动作中以便实施快速转…

单通道触摸感应开关RH6016

1.简介 SOT23-6 RH6016 封装和丝印 RH6016 是一款内置稳压模块的单通道电容式触摸感应控制开关IC&#xff0c;可以替代传统的机械式开关。 RH6016可在有介质(如玻璃、亚克力、塑料、陶瓷等)隔离保护的情况下实现触摸功能&#xff0c;安全性高。 RH6016内置高精度稳压、上电复…

Apache Doris 全新分区策略 Auto Partition 应用场景与功能详解 | Deep Dive系列

编辑&#xff1a;SelectDB 技术团队 在当今数据驱动的时代&#xff0c;如何高效、有序地管理数据库中的海量数据成为挑战。为了处理庞大的数据集&#xff0c;分布式数据库引入了类似分区和分桶策略&#xff0c;通过将数据按特定规则划分成较小的单位并分布到不同节点上&#x…

远程桌面控制

收费程序&#xff0c;白嫖党勿扰 收费金额1000元 1 概述 之前做了一个云渲染程序&#xff0c;和虚幻的像素流原理是一样的&#xff0c;不过没有虚幻做的那么完善。云渲染是使用视频流技术&#xff0c;将三维可视化画面以视频流的形式推送到服务器&#xff0c;服务器将视频转发…

28. 空间滤波器

导论&#xff1a; 几何光线追迹是光线传播的一种不完善描述。严格地说&#xff0c;光束传播是一个相干过程。当波前通过自由空间或光学介质时&#xff0c;波前会发生相干干涉。这种相干传播的建模包括物理光学领域。 物理光学传播&#xff08;POP&#xff09;是zemax的一项强…

2024年【高压电工】模拟考试题及高压电工考试试题

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 2024年高压电工模拟考试题为正在备考高压电工操作证的学员准备的理论考试专题&#xff0c;每个月更新的高压电工考试试题祝您顺利通过高压电工考试。 1、【单选题】下列()安全用具是在电气操作中使用的基本安全用具。…

服务器数据恢复—KVM虚拟机被误删除如何恢复虚拟磁盘文件?

服务器数据恢复环境&故障&#xff1a; 1台服务器&#xff0c;Linux操作系统EXT4文件系统&#xff0c;部署了数台KVM虚拟机&#xff0c;每台虚拟机包含一个qcow2格式的磁盘文件&#xff0c;和一个raw格式的磁盘文件。 工作人员操作失误删除了3台服务器上的KVM虚拟机&#xf…

c# 二维图形绘制实践

1.等边三角形 1.1 概述 1.2 代码 using System; using System.Drawing; using System.Windows.Forms;public partial class TriangleForm : Form {public TriangleForm(){//InitializeComponent();// 确保窗体大小足够大&#xff0c;以容纳三角形 this.ClientSize new Siz…

springboot相关的一些知识

SpringBoot可以同时处理多少请求 SpringBoot默认的内嵌容器是Tomcat&#xff0c;所以SpringBoot可以同时处理多少请求取决于Tomcat。 SpringBoot中处理请求数量相关的参数有四个&#xff1a; server.tomcat.thread.min-spare&#xff1a;最少的工作线程数&#xff0c;默认大小…

表单中的常用元素

10.图像形式上传文件 <input type“image”>定义图像形式的提交。 src 属性和alt属性必须与<input type“image””>结合使用。 input type"image"src"img/l.jpg"alt"submit"/> 11.下拉列表框 <select>标签定义下拉列表框…

ubuntu 20.04禁止自动更新内核驱动、显卡驱动(使用命令行)

本文目录 一、禁止更新内核1.1 查看当前内核1.2 查看安装的内核1.3 根据需求&#xff0c;使用hold参数禁止固定内核1.4 查询被锁定不更新软件包的状态 二、禁止更新显卡驱动2.1 查看安装的显卡驱动2.2 查看详细的详细的显卡信息2.3 禁止显卡驱动更新2.4 查询显卡是否设置成功 前…

EE trade:现货黄金杠杆比例的计算方法

现货黄金杠杆交易是一种利用保证金进行交易的方式&#xff0c;允许投资者用较少的资金控制较大的交易量&#xff0c;从而放大收益或损失。杠杆比例的计算方法相对简单&#xff0c;主要取决于交易所需的保证金和实际交易金额之间的关系。 杠杆比例的计算公式 杠杆比例 实际交…

SoC设计更重要的是IP管理

对于大多数片上系统&#xff08;SoC&#xff09;设计来说&#xff0c;最关键的任务不是RTL编码&#xff0c;甚至不是创建芯片架构。今天&#xff0c;SoC的设计主要使用来自多个供应商的各种IP块。这使得管理硅IP成为SoC设计过程中的主要任务。 一般来说&#xff0c;新编写的RTL…

【挑战100天首通《谷粒商城》】-【第一天】【10 番外篇】 解决docker 仓库无法访问 + MobaXterm连接VirtualBox虚拟机

文章目录 课程介绍 1、解决docker 仓库无法访问 2、 MobaXterm连接VirtualBox虚拟机 Stage 1&#xff1a;下载MobaXterm选择适合你的版本 Stage 2&#xff1a;vagrant ssh 连接&#xff0c;开启ssh访问 Stage 2-1&#xff1a;su获取root账号权限,输入密码&#xff08;默认vagra…

微软修复Wi-Fi驱动高危漏洞 影响Windows所有版本 可通过局域网发起攻击

微软修复 WiFi 驱动程序中的高危安全漏洞&#xff0c;该漏洞影响 Windows 所有版本&#xff0c;可以通过公共 WiFi 等发起攻击。如果攻击者能够接入到目标设备所处的网络中&#xff0c;则可以悄无声息完成攻击&#xff0c;不需要用户进行任何交互。 在本月例行安全更新中微软修…

深入理解@EnableAspectJAutoProxy

Hi~&#xff01;这里是奋斗的小羊&#xff0c;很荣幸您能阅读我的文章&#xff0c;诚请评论指点&#xff0c;欢迎欢迎 ~~ &#x1f4a5;&#x1f4a5;个人主页&#xff1a;奋斗的小羊 &#x1f4a5;&#x1f4a5;所属专栏&#xff1a;C语言 &#x1f680;本系列文章为个人学习…