x264源代码简单分析:滤波(Filter)部分

来源:互联网 发布:网络数据传输 编辑:程序博客网 时间:2024/05/20 02:55

本文转自:http://blog.csdn.net/leixiaohua1020/article/details/45870269  欢迎访问原处!

     本文记录x264的x264_slice_write()函数中调用的x264_fdec_filter_row()的源代码。x264_fdec_filter_row()对应着x264中的滤波模块。滤波模块主要完成了下面3个方面的功能:

(1)环路滤波(去块效应滤波)
(2)半像素内插
(3)视频质量指标PSNR和SSIM的计算
本文分别记录上述3个方面的源代码。


函数调用关系图

滤波(Filter)部分的源代码在整个x264中的位置如下图所示。


单击查看更清晰的图片

滤波(Filter)部分的函数调用关系如下图所示。


单击查看更清晰的图片

从图中可以看出,滤波模块对应的x264_fdec_filter_row()调用了如下函数:
x264_frame_deblock_row():去块效应滤波器。
x264_frame_filter():半像素插值。
x264_pixel_ssd_wxh():PSNR计算。
x264_pixel_ssim_wxh():SSIM计算。

x264_slice_write()

x264_slice_write()是x264项目的核心,它完成了编码了一个Slice的工作。有关该函数的分析可以参考文章《x264源代码简单分析:x264_slice_write()》。本文分析其调用的x264_fdec_filter_row()函数。


x264_fdec_filter_row()

x264_fdec_filter_row()用于对一行宏块进行滤波。该函数的定义位于encoder\encoder.c,如下所示。
[cpp] view plain copy
  1. /**************************************************************************** 
  2.  * 滤波-去块效应滤波、半像素插值、SSIM/PSNR计算等 
  3.  * 一次处理一行宏块 
  4.  * 
  5.  * 注释和处理:雷霄骅 
  6.  * http://blog.csdn.net/leixiaohua1020 
  7.  * leixiaohua1020@126.com 
  8.  ****************************************************************************/  
  9. static void x264_fdec_filter_row( x264_t *h, int mb_y, int pass )  
  10. {  
  11.     /* mb_y is the mb to be encoded next, not the mb to be filtered here */  
  12.     int b_hpel = h->fdec->b_kept_as_ref;  
  13.     int b_deblock = h->sh.i_disable_deblocking_filter_idc != 1;  
  14.     int b_end = mb_y == h->i_threadslice_end;  
  15.     int b_measure_quality = 1;  
  16.     int min_y = mb_y - (1 << SLICE_MBAFF);  
  17.     int b_start = min_y == h->i_threadslice_start;  
  18.     /* Even in interlaced mode, deblocking never modifies more than 4 pixels 
  19.      * above each MB, as bS=4 doesn't happen for the top of interlaced mbpairs. */  
  20.     int minpix_y = min_y*16 - 4 * !b_start;  
  21.     int maxpix_y = mb_y*16 - 4 * !b_end;  
  22.     b_deblock &= b_hpel || h->param.b_full_recon || h->param.psz_dump_yuv;  
  23.     if( h->param.b_sliced_threads )  
  24.     {  
  25.         switch( pass )  
  26.         {  
  27.             /* During encode: only do deblock if asked for */  
  28.             default:  
  29.             case 0:  
  30.                 b_deblock &= h->param.b_full_recon;  
  31.                 b_hpel = 0;  
  32.                 break;  
  33.             /* During post-encode pass: do deblock if not done yet, do hpel for all 
  34.              * rows except those between slices. */  
  35.             case 1:  
  36.                 b_deblock &= !h->param.b_full_recon;  
  37.                 b_hpel &= !(b_start && min_y > 0);  
  38.                 b_measure_quality = 0;  
  39.                 break;  
  40.             /* Final pass: do the rows between slices in sequence. */  
  41.             case 2:  
  42.                 b_deblock = 0;  
  43.                 b_measure_quality = 0;  
  44.                 break;  
  45.         }  
  46.     }  
  47.     if( mb_y & SLICE_MBAFF )  
  48.         return;  
  49.     if( min_y < h->i_threadslice_start )  
  50.         return;  
  51.     //去块效应滤波  
  52.     if( b_deblock )  
  53.         forint y = min_y; y < mb_y; y += (1 << SLICE_MBAFF) )  
  54.             x264_frame_deblock_row( h, y );//处理一行  
  55.   
  56.     /* FIXME: Prediction requires different borders for interlaced/progressive mc, 
  57.      * but the actual image data is equivalent. For now, maintain this 
  58.      * consistency by copying deblocked pixels between planes. */  
  59.     if( PARAM_INTERLACED && (!h->param.b_sliced_threads || pass == 1) )  
  60.         forint p = 0; p < h->fdec->i_plane; p++ )  
  61.             forint i = minpix_y>>(CHROMA_V_SHIFT && p); i < maxpix_y>>(CHROMA_V_SHIFT && p); i++ )  
  62.                 memcpy( h->fdec->plane_fld[p] + i*h->fdec->i_stride[p],  
  63.                         h->fdec->plane[p]     + i*h->fdec->i_stride[p],  
  64.                         h->mb.i_mb_width*16*sizeof(pixel) );  
  65.   
  66.     if( h->fdec->b_kept_as_ref && (!h->param.b_sliced_threads || pass == 1) )  
  67.         x264_frame_expand_border( h, h->fdec, min_y );  
  68.     //半像素内插  
  69.     if( b_hpel )  
  70.     {  
  71.         int end = mb_y == h->mb.i_mb_height;  
  72.         /* Can't do hpel until the previous slice is done encoding. */  
  73.         if( h->param.analyse.i_subpel_refine )  
  74.         {  
  75.             //半像素内插  
  76.             x264_frame_filter( h, h->fdec, min_y, end );  
  77.             x264_frame_expand_border_filtered( h, h->fdec, min_y, end );  
  78.         }  
  79.     }  
  80.   
  81.     if( SLICE_MBAFF && pass == 0 )  
  82.         forint i = 0; i < 3; i++ )  
  83.         {  
  84.             XCHG( pixel *, h->intra_border_backup[0][i], h->intra_border_backup[3][i] );  
  85.             XCHG( pixel *, h->intra_border_backup[1][i], h->intra_border_backup[4][i] );  
  86.         }  
  87.   
  88.     if( h->i_thread_frames > 1 && h->fdec->b_kept_as_ref )  
  89.         x264_frame_cond_broadcast( h->fdec, mb_y*16 + (b_end ? 10000 : -(X264_THREAD_HEIGHT << SLICE_MBAFF)) );  
  90.   
  91.     //计算编码的质量  
  92.     if( b_measure_quality )  
  93.     {  
  94.         maxpix_y = X264_MIN( maxpix_y, h->param.i_height );  
  95.         //如果需要打印输出PSNR  
  96.         if( h->param.analyse.b_psnr )  
  97.         {  
  98.             //实际上是计算SSD  
  99.             //输出的时候调用x264_psnr()换算SSD为PSNR  
  100.             /** 
  101.              * 计算PSNR的过程 
  102.              * 
  103.              * MSE = SSD*1/(w*h) 
  104.              * PSNR= 10*log10(MAX^2/MSE) 
  105.              * 
  106.              * 其中MAX指的是图像的灰度级,对于8bit来说就是2^8-1=255 
  107.              */  
  108.             forint p = 0; p < (CHROMA444 ? 3 : 1); p++ )  
  109.                 h->stat.frame.i_ssd[p] += x264_pixel_ssd_wxh( &h->pixf,  
  110.                     h->fdec->plane[p] + minpix_y * h->fdec->i_stride[p], h->fdec->i_stride[p],//重建帧  
  111.                     h->fenc->plane[p] + minpix_y * h->fenc->i_stride[p], h->fenc->i_stride[p],//编码帧  
  112.                     h->param.i_width, maxpix_y-minpix_y );  
  113.             if( !CHROMA444 )  
  114.             {  
  115.                 uint64_t ssd_u, ssd_v;  
  116.                 int v_shift = CHROMA_V_SHIFT;  
  117.                 x264_pixel_ssd_nv12( &h->pixf,  
  118.                     h->fdec->plane[1] + (minpix_y>>v_shift) * h->fdec->i_stride[1], h->fdec->i_stride[1],  
  119.                     h->fenc->plane[1] + (minpix_y>>v_shift) * h->fenc->i_stride[1], h->fenc->i_stride[1],  
  120.                     h->param.i_width>>1, (maxpix_y-minpix_y)>>v_shift, &ssd_u, &ssd_v );  
  121.                 h->stat.frame.i_ssd[1] += ssd_u;  
  122.                 h->stat.frame.i_ssd[2] += ssd_v;  
  123.             }  
  124.         }  
  125.         //如果需要打印输出SSIM  
  126.         if( h->param.analyse.b_ssim )  
  127.         {  
  128.             int ssim_cnt;  
  129.             x264_emms();  
  130.             /* offset by 2 pixels to avoid alignment of ssim blocks with dct blocks, 
  131.              * and overlap by 4 */  
  132.             minpix_y += b_start ? 2 : -6;  
  133.             //计算SSIM  
  134.             h->stat.frame.f_ssim +=  
  135.                 x264_pixel_ssim_wxh( &h->pixf,  
  136.                     h->fdec->plane[0] + 2+minpix_y*h->fdec->i_stride[0], h->fdec->i_stride[0],//重建帧  
  137.                     h->fenc->plane[0] + 2+minpix_y*h->fenc->i_stride[0], h->fenc->i_stride[0],//编码帧  
  138.                     h->param.i_width-2, maxpix_y-minpix_y, h->scratch_buffer, &ssim_cnt );  
  139.             h->stat.frame.i_ssim_cnt += ssim_cnt;  
  140.         }  
  141.     }  
  142. }  

从源代码可以看出,x264_fdec_filter_row()完成了三步工作:
(1)环路滤波(去块效应滤波)。通过调用x264_frame_deblock_row()实现。
(2)半像素内插。通过调用x264_frame_filter()实现。
(3)视频质量SSIM和PSNR计算。PSNR在这里只计算了SSD,通过调用x264_pixel_ssd_wxh()实现;SSIM的计算则是通过x264_pixel_ssim_wxh()实现。

x264_frame_deblock_row()

x264_frame_deblock_row()用于进行环路滤波(去块效应滤波)。该函数的定义位于common\deblock.c,如下所示。
[cpp] view plain copy
  1. //去块效应滤波  
  2. void x264_frame_deblock_row( x264_t *h, int mb_y )  
  3. {  
  4.     int b_interlaced = SLICE_MBAFF;  
  5.     int a = h->sh.i_alpha_c0_offset - QP_BD_OFFSET;  
  6.     int b = h->sh.i_beta_offset - QP_BD_OFFSET;  
  7.     int qp_thresh = 15 - X264_MIN( a, b ) - X264_MAX( 0, h->pps->i_chroma_qp_index_offset );  
  8.     int stridey   = h->fdec->i_stride[0];  
  9.     int strideuv  = h->fdec->i_stride[1];  
  10.     int chroma444 = CHROMA444;  
  11.     int chroma_height = 16 >> CHROMA_V_SHIFT;  
  12.     intptr_t uvdiff = chroma444 ? h->fdec->plane[2] - h->fdec->plane[1] : 1;  
  13.   
  14.     forint mb_x = 0; mb_x < h->mb.i_mb_width; mb_x += (~b_interlaced | mb_y)&1, mb_y ^= b_interlaced )  
  15.     {  
  16.         x264_prefetch_fenc( h, h->fdec, mb_x, mb_y );  
  17.         x264_macroblock_cache_load_neighbours_deblock( h, mb_x, mb_y );  
  18.   
  19.         int mb_xy = h->mb.i_mb_xy;  
  20.         int transform_8x8 = h->mb.mb_transform_size[mb_xy];  
  21.         int intra_cur = IS_INTRA( h->mb.type[mb_xy] );  
  22.         uint8_t (*bs)[8][4] = h->deblock_strength[mb_y&1][h->param.b_sliced_threads?mb_xy:mb_x];  
  23.         //找到像素数据(宏块的大小是16x16)  
  24.         pixel *pixy = h->fdec->plane[0] + 16*mb_y*stridey  + 16*mb_x;  
  25.         pixel *pixuv = h->fdec->plane[1] + chroma_height*mb_y*strideuv + 16*mb_x;  
  26.   
  27.         if( mb_y & MB_INTERLACED )  
  28.         {  
  29.             pixy -= 15*stridey;  
  30.             pixuv -= (chroma_height-1)*strideuv;  
  31.         }  
  32.   
  33.         int stride2y  = stridey << MB_INTERLACED;  
  34.         int stride2uv = strideuv << MB_INTERLACED;  
  35.         //QP,用于计算环路滤波的门限值alpha和beta  
  36.         int qp = h->mb.qp[mb_xy];  
  37.         int qpc = h->chroma_qp_table[qp];  
  38.         int first_edge_only = (h->mb.partition[mb_xy] == D_16x16 && !h->mb.cbp[mb_xy] && !intra_cur) || qp <= qp_thresh;  
  39.   
  40.         /* 
  41.          * 滤波顺序如下所示(大方框代表16x16块) 
  42.          * 
  43.          * +--4-+--4-+--4-+--4-+ 
  44.          * 0    1    2    3    | 
  45.          * +--5-+--5-+--5-+--5-+ 
  46.          * 0    1    2    3    | 
  47.          * +--6-+--6-+--6-+--6-+ 
  48.          * 0    1    2    3    | 
  49.          * +--7-+--7-+--7-+--7-+ 
  50.          * 0    1    2    3    | 
  51.          * +----+----+----+----+ 
  52.          * 
  53.          */  
  54.   
  55.   
  56.         //一个比较长的宏,用于进行环路滤波  
  57.         //根据不同的情况传递不同的参数  
  58.         //几个参数的含义:  
  59.         //intra:  
  60.         //为“_intra”的时候:  
  61.         //其中的“deblock_edge##intra()”展开为函数deblock_edge_intra()  
  62.         //其中的“h->loopf.deblock_luma##intra[dir]”展开为强滤波汇编函数h->loopf.deblock_luma_intra[dir]()  
  63.         //为“”(空),其中的“deblock_edge##intra()”展开为函数deblock_edge()  
  64.         //其中的“h->loopf.deblock_luma##intra[dir]”展开为普通滤波汇编函数h->loopf.deblock_luma[dir]()  
  65.         //dir:  
  66.         //决定了滤波的方向:0为水平滤波器(垂直边界),1为垂直滤波器(水平边界)  
  67.         #define FILTER( intra, dir, edge, qp, chroma_qp )\  
  68.         do\  
  69.         {\  
  70.             if( !(edge & 1) || !transform_8x8 )\  
  71.             {\  
  72.                 deblock_edge##intra( h, pixy + 4*edge*(dir?stride2y:1),\  
  73.                                      stride2y, bs[dir][edge], qp, a, b, 0,\  
  74.                                      h->loopf.deblock_luma##intra[dir] );\  
  75.                 if( CHROMA_FORMAT == CHROMA_444 )\  
  76.                 {\  
  77.                     deblock_edge##intra( h, pixuv          + 4*edge*(dir?stride2uv:1),\  
  78.                                          stride2uv, bs[dir][edge], chroma_qp, a, b, 0,\  
  79.                                          h->loopf.deblock_luma##intra[dir] );\  
  80.                     deblock_edge##intra( h, pixuv + uvdiff + 4*edge*(dir?stride2uv:1),\  
  81.                                          stride2uv, bs[dir][edge], chroma_qp, a, b, 0,\  
  82.                                          h->loopf.deblock_luma##intra[dir] );\  
  83.                 }\  
  84.                 else if( CHROMA_FORMAT == CHROMA_420 && !(edge & 1) )\  
  85.                 {\  
  86.                     deblock_edge##intra( h, pixuv + edge*(dir?2*stride2uv:4),\  
  87.                                          stride2uv, bs[dir][edge], chroma_qp, a, b, 1,\  
  88.                                          h->loopf.deblock_chroma##intra[dir] );\  
  89.                 }\  
  90.             }\  
  91.             if( CHROMA_FORMAT == CHROMA_422 && (dir || !(edge & 1)) )\  
  92.             {\  
  93.                 deblock_edge##intra( h, pixuv + edge*(dir?4*stride2uv:4),\  
  94.                                      stride2uv, bs[dir][edge], chroma_qp, a, b, 1,\  
  95.                                      h->loopf.deblock_chroma##intra[dir] );\  
  96.             }\  
  97.         } while(0)  
  98.   
  99.         if( h->mb.i_neighbour & MB_LEFT )  
  100.         {  
  101.             if( b_interlaced && h->mb.field[h->mb.i_mb_left_xy[0]] != MB_INTERLACED )  
  102.             {  
  103.                 //隔行的  
  104.                 int luma_qp[2];  
  105.                 int chroma_qp[2];  
  106.                 int left_qp[2];  
  107.                 x264_deblock_inter_t luma_deblock = h->loopf.deblock_luma_mbaff;  
  108.                 x264_deblock_inter_t chroma_deblock = h->loopf.deblock_chroma_mbaff;  
  109.                 x264_deblock_intra_t luma_intra_deblock = h->loopf.deblock_luma_intra_mbaff;  
  110.                 x264_deblock_intra_t chroma_intra_deblock = h->loopf.deblock_chroma_intra_mbaff;  
  111.                 int c = chroma444 ? 0 : 1;  
  112.   
  113.                 left_qp[0] = h->mb.qp[h->mb.i_mb_left_xy[0]];  
  114.                 luma_qp[0] = (qp + left_qp[0] + 1) >> 1;  
  115.                 chroma_qp[0] = (qpc + h->chroma_qp_table[left_qp[0]] + 1) >> 1;  
  116.                 if( intra_cur || IS_INTRA( h->mb.type[h->mb.i_mb_left_xy[0]] ) )  
  117.                 {  
  118.                     deblock_edge_intra( h, pixy,           2*stridey,  bs[0][0], luma_qp[0],   a, b, 0, luma_intra_deblock );  
  119.                     deblock_edge_intra( h, pixuv,          2*strideuv, bs[0][0], chroma_qp[0], a, b, c, chroma_intra_deblock );  
  120.                     if( chroma444 )  
  121.                         deblock_edge_intra( h, pixuv + uvdiff, 2*strideuv, bs[0][0], chroma_qp[0], a, b, c, chroma_intra_deblock );  
  122.                 }  
  123.                 else  
  124.                 {  
  125.                     deblock_edge( h, pixy,           2*stridey,  bs[0][0], luma_qp[0],   a, b, 0, luma_deblock );  
  126.                     deblock_edge( h, pixuv,          2*strideuv, bs[0][0], chroma_qp[0], a, b, c, chroma_deblock );  
  127.                     if( chroma444 )  
  128.                         deblock_edge( h, pixuv + uvdiff, 2*strideuv, bs[0][0], chroma_qp[0], a, b, c, chroma_deblock );  
  129.                 }  
  130.   
  131.                 int offy = MB_INTERLACED ? 4 : 0;  
  132.                 int offuv = MB_INTERLACED ? 4-CHROMA_V_SHIFT : 0;  
  133.                 left_qp[1] = h->mb.qp[h->mb.i_mb_left_xy[1]];  
  134.                 luma_qp[1] = (qp + left_qp[1] + 1) >> 1;  
  135.                 chroma_qp[1] = (qpc + h->chroma_qp_table[left_qp[1]] + 1) >> 1;  
  136.                 if( intra_cur || IS_INTRA( h->mb.type[h->mb.i_mb_left_xy[1]] ) )  
  137.                 {  
  138.                     deblock_edge_intra( h, pixy           + (stridey<<offy),   2*stridey,  bs[0][4], luma_qp[1],   a, b, 0, luma_intra_deblock );  
  139.                     deblock_edge_intra( h, pixuv          + (strideuv<<offuv), 2*strideuv, bs[0][4], chroma_qp[1], a, b, c, chroma_intra_deblock );  
  140.                     if( chroma444 )  
  141.                         deblock_edge_intra( h, pixuv + uvdiff + (strideuv<<offuv), 2*strideuv, bs[0][4], chroma_qp[1], a, b, c, chroma_intra_deblock );  
  142.                 }  
  143.                 else  
  144.                 {  
  145.                     deblock_edge( h, pixy           + (stridey<<offy),   2*stridey,  bs[0][4], luma_qp[1],   a, b, 0, luma_deblock );  
  146.                     deblock_edge( h, pixuv          + (strideuv<<offuv), 2*strideuv, bs[0][4], chroma_qp[1], a, b, c, chroma_deblock );  
  147.                     if( chroma444 )  
  148.                         deblock_edge( h, pixuv + uvdiff + (strideuv<<offuv), 2*strideuv, bs[0][4], chroma_qp[1], a, b, c, chroma_deblock );  
  149.                 }  
  150.             }  
  151.             else  
  152.             {  
  153.                 //逐行的  
  154.   
  155.                 //左边宏块的qp  
  156.                 int qpl = h->mb.qp[h->mb.i_mb_xy-1];  
  157.                 int qp_left = (qp + qpl + 1) >> 1;  
  158.                 int qpc_left = (qpc + h->chroma_qp_table[qpl] + 1) >> 1;  
  159.                 //Intra宏块左边宏块的qp  
  160.                 int intra_left = IS_INTRA( h->mb.type[h->mb.i_mb_xy-1] );  
  161.                 int intra_deblock = intra_cur || intra_left;  
  162.   
  163.                 /* Any MB that was coded, or that analysis decided to skip, has quality commensurate with its QP. 
  164.                  * But if deblocking affects neighboring MBs that were force-skipped, blur might accumulate there. 
  165.                  * So reset their effective QP to max, to indicate that lack of guarantee. */  
  166.                 if( h->fdec->mb_info && M32( bs[0][0] ) )  
  167.                 {  
  168. #define RESET_EFFECTIVE_QP(xy) h->fdec->effective_qp[xy] |= 0xff * !!(h->fdec->mb_info[xy] & X264_MBINFO_CONSTANT);  
  169.                     RESET_EFFECTIVE_QP(mb_xy);  
  170.                     RESET_EFFECTIVE_QP(h->mb.i_mb_left_xy[0]);  
  171.                 }  
  172.   
  173.                 if( intra_deblock )  
  174.                     FILTER( _intra, 0, 0, qp_left, qpc_left );//【0】强滤波,水平滤波器(垂直边界)  
  175.                 else  
  176.                     FILTER(       , 0, 0, qp_left, qpc_left );//【0】普通滤波,水平滤波器(垂直边界)  
  177.             }  
  178.         }  
  179.         if( !first_edge_only )  
  180.         {  
  181.             //普通滤波,水平滤波器(垂直边界)  
  182.             FILTER( , 0, 1, qp, qpc );//【1】  
  183.             FILTER( , 0, 2, qp, qpc );//【2】  
  184.             FILTER( , 0, 3, qp, qpc );//【3】  
  185.         }  
  186.   
  187.         if( h->mb.i_neighbour & MB_TOP )  
  188.         {  
  189.             if( b_interlaced && !(mb_y&1) && !MB_INTERLACED && h->mb.field[h->mb.i_mb_top_xy] )  
  190.             {  
  191.                 int mbn_xy = mb_xy - 2 * h->mb.i_mb_stride;  
  192.   
  193.                 forint j = 0; j < 2; j++, mbn_xy += h->mb.i_mb_stride )  
  194.                 {  
  195.                     int qpt = h->mb.qp[mbn_xy];  
  196.                     int qp_top = (qp + qpt + 1) >> 1;  
  197.                     int qpc_top = (qpc + h->chroma_qp_table[qpt] + 1) >> 1;  
  198.                     int intra_top = IS_INTRA( h->mb.type[mbn_xy] );  
  199.                     if( intra_cur || intra_top )  
  200.                         M32( bs[1][4*j] ) = 0x03030303;  
  201.   
  202.                     // deblock the first horizontal edge of the even rows, then the first horizontal edge of the odd rows  
  203.                     deblock_edge( h, pixy      + j*stridey,  2* stridey, bs[1][4*j], qp_top, a, b, 0, h->loopf.deblock_luma[1] );  
  204.                     if( chroma444 )  
  205.                     {  
  206.                         deblock_edge( h, pixuv          + j*strideuv, 2*strideuv, bs[1][4*j], qpc_top, a, b, 0, h->loopf.deblock_luma[1] );  
  207.                         deblock_edge( h, pixuv + uvdiff + j*strideuv, 2*strideuv, bs[1][4*j], qpc_top, a, b, 0, h->loopf.deblock_luma[1] );  
  208.                     }  
  209.                     else  
  210.                         deblock_edge( h, pixuv          + j*strideuv, 2*strideuv, bs[1][4*j], qpc_top, a, b, 1, h->loopf.deblock_chroma[1] );  
  211.                 }  
  212.             }  
  213.             else  
  214.             {  
  215.                 int qpt = h->mb.qp[h->mb.i_mb_top_xy];  
  216.                 int qp_top = (qp + qpt + 1) >> 1;  
  217.                 int qpc_top = (qpc + h->chroma_qp_table[qpt] + 1) >> 1;  
  218.                 int intra_top = IS_INTRA( h->mb.type[h->mb.i_mb_top_xy] );  
  219.                 int intra_deblock = intra_cur || intra_top;  
  220.   
  221.                 /* This edge has been modified, reset effective qp to max. */  
  222.                 if( h->fdec->mb_info && M32( bs[1][0] ) )  
  223.                 {  
  224.                     RESET_EFFECTIVE_QP(mb_xy);  
  225.                     RESET_EFFECTIVE_QP(h->mb.i_mb_top_xy);  
  226.                 }  
  227.   
  228.                 if( (!b_interlaced || (!MB_INTERLACED && !h->mb.field[h->mb.i_mb_top_xy])) && intra_deblock )  
  229.                 {  
  230.                     FILTER( _intra, 1, 0, qp_top, qpc_top );//【4】普通滤波,垂直滤波器(水平边界)  
  231.                 }  
  232.                 else  
  233.                 {  
  234.                     if( intra_deblock )  
  235.                         M32( bs[1][0] ) = 0x03030303;  
  236.                     FILTER(       , 1, 0, qp_top, qpc_top );//【4】普通滤波,垂直滤波器(水平边界)  
  237.                 }  
  238.             }  
  239.         }  
  240.   
  241.         if( !first_edge_only )  
  242.         {  
  243.             //普通滤波,垂直滤波器(水平边界)  
  244.             FILTER( , 1, 1, qp, qpc );//【5】  
  245.             FILTER( , 1, 2, qp, qpc );//【6】  
  246.             FILTER( , 1, 3, qp, qpc );//【7】  
  247.         }  
  248.   
  249.         #undef FILTER  
  250.     }  
  251. }  

从源代码可以看出,x264_frame_deblock_row()中有一个很长的宏定义“FILTER()”定义了函数调用的方式。FILTER( intra, dir, edge, qp, chroma_qp )中:
“intra”指定了是普通滤波(Bs=1,2,3)还是强滤波(Bs=4);
“dir”指定了滤波器的方向。0为水平滤波器(垂直边界),1为垂直滤波器(水平边界);

“edge”指定了边界的位置。“0”,“1”,“2”,“3”分别代表了水平(或者垂直)的4条边界;

滤波的主干代码如下所示。
[cpp] view plain copy
  1. FILTER( _intra, 0, 0, qp_left, qpc_left );//【0】强滤波,水平滤波器(垂直边界)  
  2. //普通滤波,水平滤波器(垂直边界)  
  3. FILTER( , 0, 1, qp, qpc );//【1】  
  4. FILTER( , 0, 2, qp, qpc );//【2】  
  5. FILTER( , 0, 3, qp, qpc );//【3】  
  6. FILTER( _intra, 1, 0, qp_top, qpc_top );//【4】普通滤波,垂直滤波器(水平边界)  
  7. //普通滤波,垂直滤波器(水平边界)  
  8. FILTER( , 1, 1, qp, qpc );//【5】  
  9. FILTER( , 1, 2, qp, qpc );//【6】  
  10. FILTER( , 1, 3, qp, qpc );//【7】  
上述代码滤波的顺序如下图所示。图中蓝色边缘的边界是强滤波,其他边界是普通滤波。
 
下面分别看一下两个宏“FILTER( _intra, 0, 0, qp_left, qpc_left )”和“FILTER( , 0, 1, qp, qpc )”展开后的代码。

FILTER( _intra, 0, 0, qp_left, qpc_left )

FILTER( _intra, 0, 0, qp_left, qpc_left )用于对上文图中“0”号垂直边界进行强滤波(Bs=4)。该宏的展开结果如下所示。
[cpp] view plain copy
  1. do  
  2. {  
  3.     if( !(0 & 1) || !transform_8x8 )  
  4.     {  
  5.         deblock_edge_intra( h, pixy + 4*0*(0?stride2y:1),  
  6.                              stride2y, bs[0][0], qp_left, a, b, 0,  
  7.                              h->loopf.deblock_luma_intra[0] );  
  8.         if( h->sps->i_chroma_format_idc == CHROMA_444 )  
  9.         {  
  10.             deblock_edge_intra( h, pixuv          + 4*0*(0?stride2uv:1),  
  11.                                  stride2uv, bs[0][0], qpc_left, a, b, 0,  
  12.                                  h->loopf.deblock_luma_intra[0] );  
  13.             deblock_edge_intra( h, pixuv + uvdiff + 4*0*(0?stride2uv:1),  
  14.                                  stride2uv, bs[0][0], qpc_left, a, b, 0,  
  15.                                  h->loopf.deblock_luma_intra[0] );  
  16.         }  
  17.         else if( h->sps->i_chroma_format_idc == CHROMA_420 && !(0 & 1) )  
  18.         {  
  19.             deblock_edge_intra( h, pixuv + 0*(0?2*stride2uv:4),  
  20.                                  stride2uv, bs[0][0], qpc_left, a, b, 1,  
  21.                                  h->loopf.deblock_chroma_intra[0] );  
  22.         }  
  23.     }  
  24.     if( h->sps->i_chroma_format_idc == CHROMA_422 && (0 || !(0 & 1)) )  
  25.     {  
  26.         deblock_edge_intra( h, pixuv + 0*(0?4*stride2uv:4),  
  27.                              stride2uv, bs[0][0], qpc_left, a, b, 1,  
  28.                              h->loopf.deblock_chroma_intra[0] );  
  29.     }  
  30. while(0)  

从代码中可以看出,FILTER( _intra, 0, 0, qp_left, qpc_left )调用了deblock_edge_intra()完成了强滤波。该函数的最后一个参数指定了环路滤波的汇编函数,在这里是h->loopf.deblock_luma_intra[0]()。有关h->loopf.deblock_luma_intra[0]()的代码在后面进行分析。

deblock_edge_intra()

deblock_edge_intra()通过调用相应的滤波函数完成强滤波(Bs=4)。该函数的定义位于common\deblock.c,如下所示。
[cpp] view plain copy
  1. //强滤波(Bs取值为4)  
  2. static ALWAYS_INLINE void deblock_edge_intra( x264_t *h, pixel *pix, intptr_t i_stride, uint8_t bS[4], int i_qp,  
  3.                                               int a, int b, int b_chroma, x264_deblock_intra_t pf_intra )  
  4. {  
  5.     int index_a = i_qp + a;  
  6.     int index_b = i_qp + b;  
  7.     //根据QP,通过查表的方法获得是否滤波的门限值alpha和beta  
  8.     //alpha为边界两边2点的门限值  
  9.     //beta为边界一边最靠近边界的2点的门限值  
  10.     //总体说来,QP越大,alpha和beta越大,越有可能滤波  
  11.     int alpha = alpha_table(index_a) << (BIT_DEPTH-8);  
  12.     int beta  = beta_table(index_b) << (BIT_DEPTH-8);  
  13.     //alpha或者beta有一个门限为0的时候,根本不用滤波  
  14.     if( !alpha || !beta )  
  15.         return;  
  16.     //滤波函数,通过传参而来  
  17.     pf_intra( pix, i_stride, alpha, beta );  
  18. }  

从源代码可以看出,deblock_edge_intra()首先计算滤波的门限值alpha和beta,然后调用通过参数传过来的pf_intra()汇编函数完成滤波。

FILTER( , 0, 1, qp, qpc )

FILTER( , 0, 1, qp, qpc )用于对上文图中“1”号垂直边界进行普通滤波(Bs=1,2,3)。该宏的展开结果如下所示。
[cpp] view plain copy
  1. do  
  2. {  
  3.     if( !(1 & 1) || !transform_8x8 )  
  4.     {  
  5.         deblock_edge( h, pixy + 4*1*(0?stride2y:1),  
  6.                              stride2y, bs[0][1], qp, a, b, 0,  
  7.                              h->loopf.deblock_luma[0] );  
  8.         if( h->sps->i_chroma_format_idc == CHROMA_444 )  
  9.         {  
  10.             deblock_edge( h, pixuv          + 4*1*(0?stride2uv:1),  
  11.                                  stride2uv, bs[0][1], qpc, a, b, 0,  
  12.                                  h->loopf.deblock_luma[0] );  
  13.             deblock_edge( h, pixuv + uvdiff + 4*1*(0?stride2uv:1),  
  14.                                  stride2uv, bs[0][1], qpc, a, b, 0,  
  15.                                  h->loopf.deblock_luma[0] );  
  16.         }  
  17.         else if( h->sps->i_chroma_format_idc == CHROMA_420 && !(1 & 1) )  
  18.         {  
  19.             deblock_edge( h, pixuv + 1*(0?2*stride2uv:4),  
  20.                                  stride2uv, bs[0][1], qpc, a, b, 1,  
  21.                                  h->loopf.deblock_chroma[0] );  
  22.         }  
  23.     }  
  24.     if( h->sps->i_chroma_format_idc == CHROMA_422 && (0 || !(1 & 1)) )  
  25.     {  
  26.         deblock_edge( h, pixuv + 1*(0?4*stride2uv:4),  
  27.                              stride2uv, bs[0][1], qpc, a, b, 1,  
  28.                              h->loopf.deblock_chroma[0] );  
  29.     }  
  30. while(0)  

从代码中可以看出,FILTER( , 0, 1, qp, qpc )调用了deblock_edge()完成了普通滤波(Bs=1,2,3)。该函数的最后一个参数指定了环路滤波的汇编函数,在这里是h->loopf.deblock_luma[0]()。有关h->loopf.deblock_luma[0]()的代码在后面进行分析。


deblock_edge()

deblock_edge()通过调用相应的滤波函数完成强滤波(Bs=4)。该函数的定义位于common\deblock.c,如下所示。
[cpp] view plain copy
  1. //普通滤波(Bs取值1-3)  
  2. static ALWAYS_INLINE void deblock_edge( x264_t *h, pixel *pix, intptr_t i_stride, uint8_t bS[4], int i_qp,  
  3.                                         int a, int b, int b_chroma, x264_deblock_inter_t pf_inter )  
  4. {  
  5.     int index_a = i_qp + a;  
  6.     int index_b = i_qp + b;  
  7.     //根据QP,通过查表的方法获得是否滤波的门限值alpha和beta  
  8.     //alpha为边界两边2点的门限值  
  9.     //beta为边界一边最靠近边界的2点的门限值  
  10.     //总体说来,QP越大,alpha和beta越大,越有可能滤波  
  11.     int alpha = alpha_table(index_a) << (BIT_DEPTH-8);  
  12.     int beta  = beta_table(index_b) << (BIT_DEPTH-8);  
  13.     int8_t tc[4];  
  14.     //alpha或者beta有一个门限为0的时候,根本不用滤波  
  15.     if( !M32(bS) || !alpha || !beta )  
  16.         return;  
  17.   
  18.     tc[0] = (tc0_table(index_a)[bS[0]] << (BIT_DEPTH-8)) + b_chroma;  
  19.     tc[1] = (tc0_table(index_a)[bS[1]] << (BIT_DEPTH-8)) + b_chroma;  
  20.     tc[2] = (tc0_table(index_a)[bS[2]] << (BIT_DEPTH-8)) + b_chroma;  
  21.     tc[3] = (tc0_table(index_a)[bS[3]] << (BIT_DEPTH-8)) + b_chroma;  
  22.     //滤波函数,通过传参而来  
  23.     pf_inter( pix, i_stride, alpha, beta, tc );  
  24. }  

从源代码可以看出,deblock_edge()首先计算滤波的门限值alpha和beta,然后计算tc[]的取值,最后调用通过参数传过来的pf_inter()汇编函数完成滤波。
下文开始分析环路滤波模块调用的汇编函数。

环路滤波小知识

简单记录一下环路滤波的知识。X264的重建帧(通过解码得到)一般情况下会出现方块效应。产生这种效应的原因主要有两个:
(1)DCT变换后的量化造成误差(主要原因)
(2)运动补偿
正是由于这种块效应的存在,才需要添加环路滤波器调整相邻的“块”边缘上的像素值以减轻这种视觉上的不连续感。下面一张图显示了环路滤波的效果。图中左边的图没有使用环路滤波,而右边的图使用了环路滤波。

环路滤波分类
环路滤波器根据滤波的强度可以分为两种:
(1)普通滤波器。针对边界的Bs(边界强度)为1、2、3的滤波器。此时环路滤波涉及到方块边界周围的6个点(边界两边各3个点):p2,p1,p0,q0,q1,q2。需要处理4个点(边界两边各2个点,只以p点为例):
p0’ = p0 + (((q0 - p0 ) << 2) + (p1 - q1) + 4) >> 3
p1’ = ( p2 + ( ( p0 + q0 + 1 ) >> 1) – 2p1 ) >> 1
(2)强滤波器。针对边界的Bs(边界强度)为4的滤波器。此时环路滤波涉及到方块边界周围的8个点(边界两边各4个点):p3,p2,p1,p0,q0,q1,q2,q3。需要处理6个点(边界两边各3个点,只以p点为例):
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
其中上文中提到的边界强度Bs的判定方式如下。

条件(针对两边的图像块)

Bs

有一个块为帧内预测 + 边界为宏块边界

4

有一个块为帧内预测

3

有一个块对残差编码

2

运动矢量差不小于1像素

1

运动补偿参考帧不同

1

其它

0


总体说来,与帧内预测相关的图像块(帧内预测块)的边界强度比较大,取值为3或者4;与运动补偿相关的图像块(帧间预测块)的边界强度比较小,取值为1。

环路滤波的门限
并不是所有的块的边界处都需要环路滤波。例如画面中物体的边界正好和块的边界重合的话,就不能进行滤波,否则会使画面中物体的边界变模糊。因此需要区别开物体边界和块效应边界。一般情况下,物体边界两边的像素值差别很大,而块效应边界两边像素值差别比较小。《H.264标准》以这个特点定义了2个变量alpha和beta来判决边界是否需要进行环路滤波。只有满足下面三个条件的时候才能进行环路滤波:
| p0 - q0 | < alpha
| p1 – p0 | < beta
| q1 - q0 | < beta
简而言之,就是边界两边的两个点的像素值不能太大,即不能超过alpha;边界一边的前两个点之间的像素值也不能太大,即不能超过beta。其中alpha和beta是根据量化参数QP推算出来(具体方法不再记录)。总体说来QP越大,alpha和beta的值也越大,也就越容易触发环路滤波。由于QP越大表明压缩的程度越大,所以也可以得知高压缩比的情况下更需要进行环路滤波。

x264_deblock_init()

x264_deblock_init()用于初始化去块效应滤波器相关的汇编函数。该函数的定义位于common\deblock.c,如下所示。
[cpp] view plain copy
  1. //去块效应滤波  
  2. void x264_deblock_init( int cpu, x264_deblock_function_t *pf, int b_mbaff )  
  3. {  
  4.     //注意:标记“v”的垂直滤波器是处理水平边界用的  
  5.     //亮度-普通滤波器-边界强度Bs=1,2,3  
  6.     pf->deblock_luma[1] = deblock_v_luma_c;  
  7.     pf->deblock_luma[0] = deblock_h_luma_c;  
  8.     //色度的  
  9.     pf->deblock_chroma[1] = deblock_v_chroma_c;  
  10.     pf->deblock_h_chroma_420 = deblock_h_chroma_c;  
  11.     pf->deblock_h_chroma_422 = deblock_h_chroma_422_c;  
  12.     //亮度-强滤波器-边界强度Bs=4  
  13.     pf->deblock_luma_intra[1] = deblock_v_luma_intra_c;  
  14.     pf->deblock_luma_intra[0] = deblock_h_luma_intra_c;  
  15.     pf->deblock_chroma_intra[1] = deblock_v_chroma_intra_c;  
  16.     pf->deblock_h_chroma_420_intra = deblock_h_chroma_intra_c;  
  17.     pf->deblock_h_chroma_422_intra = deblock_h_chroma_422_intra_c;  
  18.     pf->deblock_luma_mbaff = deblock_h_luma_mbaff_c;  
  19.     pf->deblock_chroma_420_mbaff = deblock_h_chroma_mbaff_c;  
  20.     pf->deblock_luma_intra_mbaff = deblock_h_luma_intra_mbaff_c;  
  21.     pf->deblock_chroma_420_intra_mbaff = deblock_h_chroma_intra_mbaff_c;  
  22.     pf->deblock_strength = deblock_strength_c;  
  23.   
  24. #if HAVE_MMX  
  25.     if( cpu&X264_CPU_MMX2 )  
  26.     {  
  27. #if ARCH_X86  
  28.         pf->deblock_luma[1] = x264_deblock_v_luma_mmx2;  
  29.         pf->deblock_luma[0] = x264_deblock_h_luma_mmx2;  
  30.         pf->deblock_chroma[1] = x264_deblock_v_chroma_mmx2;  
  31.         pf->deblock_h_chroma_420 = x264_deblock_h_chroma_mmx2;  
  32.         pf->deblock_chroma_420_mbaff = x264_deblock_h_chroma_mbaff_mmx2;  
  33.         pf->deblock_h_chroma_422 = x264_deblock_h_chroma_422_mmx2;  
  34.         pf->deblock_h_chroma_422_intra = x264_deblock_h_chroma_422_intra_mmx2;  
  35.         pf->deblock_luma_intra[1] = x264_deblock_v_luma_intra_mmx2;  
  36.         pf->deblock_luma_intra[0] = x264_deblock_h_luma_intra_mmx2;  
  37.         pf->deblock_chroma_intra[1] = x264_deblock_v_chroma_intra_mmx2;  
  38.         pf->deblock_h_chroma_420_intra = x264_deblock_h_chroma_intra_mmx2;  
  39.         pf->deblock_chroma_420_intra_mbaff = x264_deblock_h_chroma_intra_mbaff_mmx2;  
  40. #endif  
  41.     //此处省略大量的X86、ARM等平台的汇编函数初始化代码  
  42. }  

从源代码可以看出,x264_deblock_init()中初始化了一系列环路滤波函数。这些函数名称的规则如下:
(1)包含“v”的是垂直滤波器,用于处理水平边界;包含“h”的是水平滤波器,用于处理垂直边界。
(2)包含“luma”的是亮度滤波器,包含“chroma”的是色度滤波器。
(3)包含“intra”的是处理边界强度Bs为4的强滤波器,不包含“intra”的是普通滤波器。
x264_deblock_init()的输入参数x264_deblock_function_t是一个结构体,其中包含了环路滤波器相关的函数指针。x264_deblock_function_t的定义如下所示。
[cpp] view plain copy
  1. typedef struct  
  2. {  
  3.     x264_deblock_inter_t deblock_luma[2];  
  4.     x264_deblock_inter_t deblock_chroma[2];  
  5.     x264_deblock_inter_t deblock_h_chroma_420;  
  6.     x264_deblock_inter_t deblock_h_chroma_422;  
  7.     x264_deblock_intra_t deblock_luma_intra[2];  
  8.     x264_deblock_intra_t deblock_chroma_intra[2];  
  9.     x264_deblock_intra_t deblock_h_chroma_420_intra;  
  10.     x264_deblock_intra_t deblock_h_chroma_422_intra;  
  11.     x264_deblock_inter_t deblock_luma_mbaff;  
  12.     x264_deblock_inter_t deblock_chroma_mbaff;  
  13.     x264_deblock_inter_t deblock_chroma_420_mbaff;  
  14.     x264_deblock_inter_t deblock_chroma_422_mbaff;  
  15.     x264_deblock_intra_t deblock_luma_intra_mbaff;  
  16.     x264_deblock_intra_t deblock_chroma_intra_mbaff;  
  17.     x264_deblock_intra_t deblock_chroma_420_intra_mbaff;  
  18.     x264_deblock_intra_t deblock_chroma_422_intra_mbaff;  
  19.     void (*deblock_strength) ( uint8_t nnz[X264_SCAN8_SIZE], int8_t ref[2][X264_SCAN8_LUMA_SIZE],  
  20.                                int16_t mv[2][X264_SCAN8_LUMA_SIZE][2], uint8_t bs[2][8][4], int mvy_limit,  
  21.                                int bframe );  
  22. } x264_deblock_function_t;  

x264_deblock_init()的工作就是对x264_deblock_function_t中的函数指针进行赋值。可以看出x264_deblock_function_t中很多的元素是一个包含2个元素的数组,例如deblock_luma[2],deblock_luma_intra[2]等。这些数组中的元素[0]一般是水平滤波器,而元素[1]是垂直滤波器。下面记录几个最有代表性的滤波函数:普通滤波函数deblock_v_luma_c()和deblock_h_luma_c(),以及强滤波函数deblock_v_luma_intra_c()和deblock_h_luma_intra_c()。

普通滤波函数(Bs=1,2,3)

deblock_v_luma_c()

deblock_v_luma_c()是一个普通强度的垂直滤波器,用于处理边界强度Bs为1,2,3的水平边界。该函数的定义位于common\deblock.c,如下所示。
[cpp] view plain copy
  1. //去块效应滤波-普通滤波,Bs为1,2,3  
  2. //垂直(Vertical)滤波器  
  3. //      边界  
  4. //         x  
  5. //         x  
  6. // 边界----------  
  7. //         x  
  8. //         x  
  9. //  
  10. //  
  11. static void deblock_v_luma_c( pixel *pix, intptr_t stride, int alpha, int beta, int8_t *tc0 )  
  12. {  
  13.     //xstride=stride(用于选择滤波的像素)  
  14.     //ystride=1  
  15.     deblock_luma_c( pix, stride, 1, alpha, beta, tc0 );  
  16. }  
可以看出deblock_v_luma_c()调用了另一个函数deblock_luma_c()。需要注意deblock_luma_c()是一个水平滤波器和垂直滤波器都会调用的“通用”滤波器函数。在这里传递给deblock_luma_c()第二个参数xstride的值为stride,第三个参数ystride的值为1。

deblock_luma_c()
deblock_luma_c()是一个通用的滤波器函数,定义如下所示。
[cpp] view plain copy
  1. //去块效应滤波-普通滤波,Bs为1,2,3  
  2. static inline void deblock_luma_c( pixel *pix, intptr_t xstride, intptr_t ystride, int alpha, int beta, int8_t *tc0 )  
  3. {  
  4.     forint i = 0; i < 4; i++ )  
  5.     {  
  6.         if( tc0[i] < 0 )  
  7.         {  
  8.             pix += 4*ystride;  
  9.             continue;  
  10.         }  
  11.         //滤4个像素  
  12.         forint d = 0; d < 4; d++, pix += ystride )  
  13.             deblock_edge_luma_c( pix, xstride, alpha, beta, tc0[i] );  
  14.     }  
  15. }  
从源代码中可以看出,具体的滤波在deblock_edge_luma_c()中完成。处理完一个像素后,会继续处理与当前像素距离为ystride的像素。

deblock_edge_luma_c()
deblock_edge_luma_c()用于完成一个点的滤波工作。该函数的定义如下所示。
[cpp] view plain copy
  1. /* From ffmpeg */  
  2. //去块效应滤波-普通滤波,Bs为1,2,3  
  3. //从FFmpeg复制过来的?  
  4. static ALWAYS_INLINE void deblock_edge_luma_c( pixel *pix, intptr_t xstride, int alpha, int beta, int8_t tc0 )  
  5. {  
  6.     //p和q  
  7.     //如果xstride=stride,ystride=1  
  8.     //就是处理纵向的6个像素  
  9.     //对应的是方块的横向边界的滤波,即如下所示:  
  10.     //        p2  
  11.     //        p1  
  12.     //        p0  
  13.     //=====图像边界=====  
  14.     //        q0  
  15.     //        q1  
  16.     //        q2  
  17.     //  
  18.     //如果xstride=1,ystride=stride  
  19.     //就是处理纵向的6个像素  
  20.     //对应的是方块的横向边界的滤波,即如下所示:  
  21.     //          ||  
  22.     // p2 p1 p0 || q0 q1 q2  
  23.     //          ||  
  24.     //          边界  
  25.   
  26.     //注意:这里乘的是xstride  
  27.   
  28.     int p2 = pix[-3*xstride];  
  29.     int p1 = pix[-2*xstride];  
  30.     int p0 = pix[-1*xstride];  
  31.     int q0 = pix[ 0*xstride];  
  32.     int q1 = pix[ 1*xstride];  
  33.     int q2 = pix[ 2*xstride];  
  34.     //计算方法参考相关的标准  
  35.     //alpha和beta是用于检查图像内容的2个参数  
  36.     //只有满足if()里面3个取值条件的时候(只涉及边界旁边的4个点),才会滤波  
  37.     if( abs( p0 - q0 ) < alpha && abs( p1 - p0 ) < beta && abs( q1 - q0 ) < beta )  
  38.     {  
  39.         int tc = tc0;  
  40.         int delta;  
  41.         //上面2个点(p0,p2)满足条件的时候,滤波p1  
  42.         //int x264_clip3( int v, int i_min, int i_max )用于限幅  
  43.         if( abs( p2 - p0 ) < beta )  
  44.         {  
  45.             if( tc0 )  
  46.                 pix[-2*xstride] = p1 + x264_clip3( (( p2 + ((p0 + q0 + 1) >> 1)) >> 1) - p1, -tc0, tc0 );  
  47.             tc++;  
  48.         }  
  49.         //下面2个点(q0,q2)满足条件的时候,滤波q1  
  50.         if( abs( q2 - q0 ) < beta )  
  51.         {  
  52.             if( tc0 )  
  53.                 pix[ 1*xstride] = q1 + x264_clip3( (( q2 + ((p0 + q0 + 1) >> 1)) >> 1) - q1, -tc0, tc0 );  
  54.             tc++;  
  55.         }  
  56.   
  57.         delta = x264_clip3( (((q0 - p0 ) << 2) + (p1 - q1) + 4) >> 3, -tc, tc );  
  58.         //p0  
  59.         pix[-1*xstride] = x264_clip_pixel( p0 + delta );    /* p0' */  
  60.         //q0  
  61.         pix[ 0*xstride] = x264_clip_pixel( q0 - delta );    /* q0' */  
  62.     }  
  63. }  
从源代码可以看出,deblock_edge_luma_c()实现了前文记录的普通强度的滤波公式。

deblock_h_luma_c()
deblock_h_luma_c()是一个普通强度的水平滤波器,用于处理边界强度Bs为1,2,3的垂直边界。该函数的定义如下所示。
[cpp] view plain copy
  1. //去块效应滤波-普通滤波,Bs为1,2,3  
  2. //水平(Horizontal)滤波器  
  3. //      边界  
  4. //       |  
  5. // x x x | x x x  
  6. //       |  
  7. static void deblock_h_luma_c( pixel *pix, intptr_t stride, int alpha, int beta, int8_t *tc0 )  
  8. {  
  9.     //xstride=1(用于选择滤波的像素)  
  10.     //ystride=stride  
  11.     deblock_luma_c( pix, 1, stride, alpha, beta, tc0 );  
  12. }  
从源代码可以看出,和deblock_v_luma_c()类似,deblock_h_luma_c()同样调用了deblock_luma_c()函数。唯一的不同在于它传递给deblock_luma_c()的第2个参数xstride为1,第3个参数ystride为stride。

强滤波函数(Bs=4)

deblock_v_luma_intra_c()
deblock_v_luma_intra_c()是一个强滤波的垂直滤波器,用于处理边界强度Bs为4的水平边界。该函数的定义位于common\deblock.c,如下所示。
[cpp] view plain copy
  1. //垂直(Vertical)强滤波器-Bs为4  
  2. //      边界  
  3. //         x  
  4. //         x  
  5. // 边界----------  
  6. //         x  
  7. //         x  
  8. static void deblock_v_luma_intra_c( pixel *pix, intptr_t stride, int alpha, int beta )  
  9. {  
  10.     //注意  
  11.     //xstride=stride  
  12.     //ystride=1  
  13.     //处理完1个像素点之后,pix增加ystride  
  14.   
  15.     //水平滤波和垂直滤波通用的强滤波函数  
  16.     deblock_luma_intra_c( pix, stride, 1, alpha, beta );  
  17. }  
可以看出deblock_v_luma_intra_c()调用了另一个函数deblock_luma_intra_c()。需要注意deblock_luma_intra_c()是一个水平滤波器和垂直滤波器都会调用的“通用”滤波器函数。在这里传递给deblock_luma_intra_c()第二个参数xstride的值为stride,第三个参数ystride的值为1。

deblock_luma_intra_c()
deblock_luma_intra_c()是一个通用的滤波器函数,定义如下所示。
[cpp] view plain copy
  1. //水平滤波和垂直滤波通用的强滤波函数-Bs为4  
  2. static inline void deblock_luma_intra_c( pixel *pix, intptr_t xstride, intptr_t ystride, int alpha, int beta )  
  3. {  
  4.     //循环处理16个点  
  5.     //处理完1个像素点之后,pix增加ystride  
  6.     forint d = 0; d < 16; d++, pix += ystride )  
  7.         deblock_edge_luma_intra_c( pix, xstride, alpha, beta );    //每次处理1个点  
  8. }  
从源代码中可以看出,具体的滤波在deblock_edge_luma_intra_c()中完成。处理完一个像素后,会继续处理与当前像素距离为ystride的像素。

deblock_edge_luma_intra_c()
deblock_edge_luma_intra_c()用于完成一个点的滤波工作。该函数的定义如下所示。
[cpp] view plain copy
  1. //水平滤波和垂直滤波通用的强滤波函数-处理1个点-Bs为4  
  2. //注意涉及到8个像素  
  3. static ALWAYS_INLINE void deblock_edge_luma_intra_c( pixel *pix, intptr_t xstride, int alpha, int beta )  
  4. {  
  5.     //如果xstride=stride,ystride=1  
  6.     //就是处理纵向的6个像素  
  7.     //对应的是方块的横向边界的滤波。如下所示:  
  8.     //        p2  
  9.     //        p1  
  10.     //        p0  
  11.     //=====图像边界=====  
  12.     //        q0  
  13.     //        q1  
  14.     //        q2  
  15.     //  
  16.     //如果xstride=1,ystride=stride  
  17.     //就是处理纵向的6个像素  
  18.     //对应的是方块的横向边界的滤波,即如下所示:  
  19.     //          ||  
  20.     // p2 p1 p0 || q0 q1 q2  
  21.     //          ||  
  22.     //         边界  
  23.   
  24.     //注意:这里乘的是xstride  
  25.     int p2 = pix[-3*xstride];  
  26.     int p1 = pix[-2*xstride];  
  27.     int p0 = pix[-1*xstride];  
  28.     int q0 = pix[ 0*xstride];  
  29.     int q1 = pix[ 1*xstride];  
  30.     int q2 = pix[ 2*xstride];  
  31.     //满足条件的时候,才滤波  
  32.     if( abs( p0 - q0 ) < alpha && abs( p1 - p0 ) < beta && abs( q1 - q0 ) < beta )  
  33.     {  
  34.         if( abs( p0 - q0 ) < ((alpha >> 2) + 2) )  
  35.         {  
  36.             if( abs( p2 - p0 ) < beta ) /* p0', p1', p2' */  
  37.             {  
  38.                 const int p3 = pix[-4*xstride];  
  39.                 pix[-1*xstride] = ( p2 + 2*p1 + 2*p0 + 2*q0 + q1 + 4 ) >> 3;  
  40.                 pix[-2*xstride] = ( p2 + p1 + p0 + q0 + 2 ) >> 2;  
  41.                 pix[-3*xstride] = ( 2*p3 + 3*p2 + p1 + p0 + q0 + 4 ) >> 3;  
  42.             }  
  43.             else /* p0' */  
  44.                 pix[-1*xstride] = ( 2*p1 + p0 + q1 + 2 ) >> 2;  
  45.             if( abs( q2 - q0 ) < beta ) /* q0', q1', q2' */  
  46.             {  
  47.                 const int q3 = pix[3*xstride];  
  48.                 pix[0*xstride] = ( p1 + 2*p0 + 2*q0 + 2*q1 + q2 + 4 ) >> 3;  
  49.                 pix[1*xstride] = ( p0 + q0 + q1 + q2 + 2 ) >> 2;  
  50.                 pix[2*xstride] = ( 2*q3 + 3*q2 + q1 + q0 + p0 + 4 ) >> 3;  
  51.             }  
  52.             else /* q0' */  
  53.                 pix[0*xstride] = ( 2*q1 + q0 + p1 + 2 ) >> 2;  
  54.         }  
  55.         else /* p0', q0' */  
  56.         {  
  57.             pix[-1*xstride] = ( 2*p1 + p0 + q1 + 2 ) >> 2;  
  58.             pix[ 0*xstride] = ( 2*q1 + q0 + p1 + 2 ) >> 2;  
  59.         }  
  60.     }  
  61. }  
从源代码可以看出,deblock_edge_luma_intra_c()实现了前文记录的强滤波公式。

至此有关环路滤波的源代码就分析完毕了。


x264_frame_filter()

x264_frame_filter()用于完成半像素内插的工作。该函数的定义位于common\mc.c,如下所示。
[cpp] view plain copy
  1. //半像素内插  
  2. void x264_frame_filter( x264_t *h, x264_frame_t *frame, int mb_y, int b_end )  
  3. {  
  4.     const int b_interlaced = PARAM_INTERLACED;  
  5.     int start = mb_y*16 - 8; // buffer = 4 for deblock + 3 for 6tap, rounded to 8  
  6.     int height = (b_end ? frame->i_lines[0] + 16*PARAM_INTERLACED : (mb_y+b_interlaced)*16) + 8;  
  7.   
  8.     if( mb_y & b_interlaced )  
  9.         return;  
  10.   
  11.     forint p = 0; p < (CHROMA444 ? 3 : 1); p++ )  
  12.     {  
  13.         int stride = frame->i_stride[p];  
  14.         const int width = frame->i_width[p];  
  15.         int offs = start*stride - 8; // buffer = 3 for 6tap, aligned to 8 for simd  
  16.         //半像素内插  
  17.         if( !b_interlaced || h->mb.b_adaptive_mbaff )  
  18.             h->mc.hpel_filter(  
  19.                 frame->filtered[p][1] + offs,//水平半像素内插  
  20.                 frame->filtered[p][2] + offs,//垂直半像素内插  
  21.                 frame->filtered[p][3] + offs,//中间半像素内插  
  22.                 frame->plane[p] + offs,  
  23.                 stride, width + 16, height - start,  
  24.                 h->scratch_buffer );  
  25.   
  26.         if( b_interlaced )  
  27.         {  
  28.             /* MC must happen between pixels in the same field. */  
  29.             stride = frame->i_stride[p] << 1;  
  30.             start = (mb_y*16 >> 1) - 8;  
  31.             int height_fld = ((b_end ? frame->i_lines[p] : mb_y*16) >> 1) + 8;  
  32.             offs = start*stride - 8;  
  33.             forint i = 0; i < 2; i++, offs += frame->i_stride[p] )  
  34.             {  
  35.                 h->mc.hpel_filter(  
  36.                     frame->filtered_fld[p][1] + offs,  
  37.                     frame->filtered_fld[p][2] + offs,  
  38.                     frame->filtered_fld[p][3] + offs,  
  39.                     frame->plane_fld[p] + offs,  
  40.                     stride, width + 16, height_fld - start,  
  41.                     h->scratch_buffer );  
  42.             }  
  43.         }  
  44.     }  
  45.   
  46.     /* generate integral image: 
  47.      * frame->integral contains 2 planes. in the upper plane, each element is 
  48.      * the sum of an 8x8 pixel region with top-left corner on that point. 
  49.      * in the lower plane, 4x4 sums (needed only with --partitions p4x4). */  
  50.   
  51.     if( frame->integral )  
  52.     {  
  53.         int stride = frame->i_stride[0];  
  54.         if( start < 0 )  
  55.         {  
  56.             memset( frame->integral - PADV * stride - PADH, 0, stride * sizeof(uint16_t) );  
  57.             start = -PADV;  
  58.         }  
  59.         if( b_end )  
  60.             height += PADV-9;  
  61.         forint y = start; y < height; y++ )  
  62.         {  
  63.             pixel    *pix  = frame->plane[0] + y * stride - PADH;  
  64.             uint16_t *sum8 = frame->integral + (y+1) * stride - PADH;  
  65.             uint16_t *sum4;  
  66.             if( h->frames.b_have_sub8x8_esa )  
  67.             {  
  68.                 h->mc.integral_init4h( sum8, pix, stride );  
  69.                 sum8 -= 8*stride;  
  70.                 sum4 = sum8 + stride * (frame->i_lines[0] + PADV*2);  
  71.                 if( y >= 8-PADV )  
  72.                     h->mc.integral_init4v( sum8, sum4, stride );  
  73.             }  
  74.             else  
  75.             {  
  76.                 h->mc.integral_init8h( sum8, pix, stride );  
  77.                 if( y >= 8-PADV )  
  78.                     h->mc.integral_init8v( sum8-8*stride, stride );  
  79.             }  
  80.         }  
  81.     }  
  82. }  

从源代码中可以看出,x264_frame_filter()调用了汇编函数h->mc.hpel_filter()完成了半像素内插的工作。经过汇编半像素内插函数处理之后,得到的水平半像素内差点存储在x264_frame_t的filtered[][1]中,垂直半像素内差点存储在x264_frame_t的filtered[][2]中,对角线半像素内差点存储在x264_frame_t的filtered[][3]中(整像素点存储在x264_frame_t的filtered[][0]中)。

下文开始分析半像素内插模块调用的汇编函数。

1/4像素内插小知识

(1)半像素内插

简单记录一下半像素插值的知识。《H.264标准》中规定,运动估计为1/4像素精度。因此在H.264编码和解码的过程中,需要将画面中的像素进行插值——简单地说就是把原先的1个像素点拓展成4x4一共16个点。下图显示了H.264编码和解码过程中像素插值情况。可以看出原先的G点的右下方通过插值的方式产生了a、b、c、d等一共16个点。


如图所示,1/4像素内插一般分成两步:
(1)半像素内插。这一步通过6抽头滤波器获得5个半像素点。
(2)线性内插。这一步通过简单的线性内插获得剩余的1/4像素点。

图中半像素内插点为b、m、h、s、j五个点。半像素内插方法是对整像素点进行6 抽头滤波得出,滤波器的权重为(1/32, -5/32, 5/8, 5/8, -5/32, 1/32)。例如b的计算公式为:

b=round( (E - 5F + 20G + 20H - 5I + J ) / 32)

剩下几个半像素点的计算关系如下:
m:由B、D、H、N、S、U计算
h:由A、C、G、M、R、T计算
s:由K、L、M、N、P、Q计算
j:由cc、dd、h、m、ee、ff计算。需要注意j点的运算量比较大,因为cc、dd、ee、ff都需要通过半像素内插方法进行计算。
在获得半像素点之后,就可以通过简单的线性内插获得1/4像素内插点了。1/4像素内插的方式如下图所示。例如图中a点的计算公式如下:
A=round( (G+b)/2 )

在这里有一点需要注意:位于4个角的e、g、p、r四个点并不是通过j点计算计算的,而是通过b、h、s、m四个半像素点计算的。


(2)半像素点实例
下图显示了一个4x4图像块经过半像素内插处理后,得到的半像素与整像素点之间的位置关系。

(3)1/4像素内插
1/4像素内插点是通过是通过半像素点之间(或者和整像素点)线性内插获得。下图显示了一个4x4图像块进行1/4像素内插的过程。上面一张图中水平半像素点(存储于filter[][1])和垂直半像素点(存储于filter[][2])线性内插后得到了绿色的1/4像素内插点X。下面一张图中整像素点(存储于filter[][0])和垂直半像素点(存储于filter[][2])线性内插后得到了绿色的1/4像素内插点X。
 

x264_mc_init()

x264_mc_init()用于初始化运动补偿相关的汇编函数。该函数的定义位于common\mc.c,如下所示。
[cpp] view plain copy
  1. //运动补偿  
  2. void x264_mc_init( int cpu, x264_mc_functions_t *pf, int cpu_independent )  
  3. {  
  4.     //亮度运动补偿  
  5.     pf->mc_luma   = mc_luma;  
  6.     //获得匹配块  
  7.     pf->get_ref   = get_ref;  
  8.   
  9.     pf->mc_chroma = mc_chroma;  
  10.     //求平均  
  11.     pf->avg[PIXEL_16x16]= pixel_avg_16x16;  
  12.     pf->avg[PIXEL_16x8] = pixel_avg_16x8;  
  13.     pf->avg[PIXEL_8x16] = pixel_avg_8x16;  
  14.     pf->avg[PIXEL_8x8]  = pixel_avg_8x8;  
  15.     pf->avg[PIXEL_8x4]  = pixel_avg_8x4;  
  16.     pf->avg[PIXEL_4x16] = pixel_avg_4x16;  
  17.     pf->avg[PIXEL_4x8]  = pixel_avg_4x8;  
  18.     pf->avg[PIXEL_4x4]  = pixel_avg_4x4;  
  19.     pf->avg[PIXEL_4x2]  = pixel_avg_4x2;  
  20.     pf->avg[PIXEL_2x8]  = pixel_avg_2x8;  
  21.     pf->avg[PIXEL_2x4]  = pixel_avg_2x4;  
  22.     pf->avg[PIXEL_2x2]  = pixel_avg_2x2;  
  23.     //加权相关  
  24.     pf->weight    = x264_mc_weight_wtab;  
  25.     pf->offsetadd = x264_mc_weight_wtab;  
  26.     pf->offsetsub = x264_mc_weight_wtab;  
  27.     pf->weight_cache = x264_weight_cache;  
  28.     //赋值-只包含了方形的  
  29.     pf->copy_16x16_unaligned = mc_copy_w16;  
  30.     pf->copy[PIXEL_16x16] = mc_copy_w16;  
  31.     pf->copy[PIXEL_8x8]   = mc_copy_w8;  
  32.     pf->copy[PIXEL_4x4]   = mc_copy_w4;  
  33.   
  34.     pf->store_interleave_chroma       = store_interleave_chroma;  
  35.     pf->load_deinterleave_chroma_fenc = load_deinterleave_chroma_fenc;  
  36.     pf->load_deinterleave_chroma_fdec = load_deinterleave_chroma_fdec;  
  37.     //拷贝像素-不论像素块大小  
  38.     pf->plane_copy = x264_plane_copy_c;  
  39.     pf->plane_copy_interleave = x264_plane_copy_interleave_c;  
  40.     pf->plane_copy_deinterleave = x264_plane_copy_deinterleave_c;  
  41.     pf->plane_copy_deinterleave_rgb = x264_plane_copy_deinterleave_rgb_c;  
  42.     pf->plane_copy_deinterleave_v210 = x264_plane_copy_deinterleave_v210_c;  
  43.     //关键:半像素内插  
  44.     pf->hpel_filter = hpel_filter;  
  45.     //几个空函数  
  46.     pf->prefetch_fenc_420 = prefetch_fenc_null;  
  47.     pf->prefetch_fenc_422 = prefetch_fenc_null;  
  48.     pf->prefetch_ref  = prefetch_ref_null;  
  49.     pf->memcpy_aligned = memcpy;  
  50.     pf->memzero_aligned = memzero_aligned;  
  51.     //降低分辨率-线性内插(不是半像素内插)  
  52.     pf->frame_init_lowres_core = frame_init_lowres_core;  
  53.   
  54.     pf->integral_init4h = integral_init4h;  
  55.     pf->integral_init8h = integral_init8h;  
  56.     pf->integral_init4v = integral_init4v;  
  57.     pf->integral_init8v = integral_init8v;  
  58.   
  59.     pf->mbtree_propagate_cost = mbtree_propagate_cost;  
  60.     pf->mbtree_propagate_list = mbtree_propagate_list;  
  61.     //各种汇编版本  
  62. #if HAVE_MMX  
  63.     x264_mc_init_mmx( cpu, pf );  
  64. #endif  
  65. #if HAVE_ALTIVEC  
  66.     if( cpu&X264_CPU_ALTIVEC )  
  67.         x264_mc_altivec_init( pf );  
  68. #endif  
  69. #if HAVE_ARMV6  
  70.     x264_mc_init_arm( cpu, pf );  
  71. #endif  
  72. #if ARCH_AARCH64  
  73.     x264_mc_init_aarch64( cpu, pf );  
  74. #endif  
  75.   
  76.     if( cpu_independent )  
  77.     {  
  78.         pf->mbtree_propagate_cost = mbtree_propagate_cost;  
  79.         pf->mbtree_propagate_list = mbtree_propagate_list;  
  80.     }  
  81. }  
从源代码可以看出,x264_mc_init()中包含了大量的像素内插、拷贝、求平均的函数。这些函数都是用于在H.264编码过程中进行运动估计和运动补偿的。其中半像素内插函数是hpel_filter()。

hpel_filter()

hpel_filter()用于进行半像素插值。该函数的定义位于common\mc.c,如下所示。
[cpp] view plain copy
  1. //半像素插值公式  
  2. //b= (E - 5F + 20G + 20H - 5I + J)/32  
  3. //              x  
  4. //d取1,水平滤波器;d取stride,垂直滤波器(这里没有除以32)  
  5. #define TAPFILTER(pix, d) ((pix)[x-2*d] + (pix)[x+3*d] - 5*((pix)[x-d] + (pix)[x+2*d]) + 20*((pix)[x] + (pix)[x+d]))  
  6.   
  7. /* 
  8.  * 半像素插值 
  9.  * dsth:水平滤波得到的半像素点(aa,bb,b,s,gg,hh) 
  10.  * dstv:垂直滤波的到的半像素点(cc,dd,h,m,ee,ff) 
  11.  * dstc:“水平+垂直”滤波得到的位于4个像素中间的半像素点(j) 
  12.  * 
  13.  * 半像素插值示意图如下: 
  14.  * 
  15.  *         A aa B 
  16.  * 
  17.  *         C bb D 
  18.  * 
  19.  * E   F   G  b H   I   J 
  20.  * 
  21.  * cc  dd  h  j m  ee  ff 
  22.  * 
  23.  * K   L   M  s N   P   Q 
  24.  * 
  25.  *         R gg S 
  26.  * 
  27.  *         T hh U 
  28.  * 
  29.  * 计算公式如下: 
  30.  * b=round( (E - 5F + 20G + 20H - 5I + J ) / 32) 
  31.  * 
  32.  * 剩下几个半像素点的计算关系如下: 
  33.  * m:由B、D、H、N、S、U计算 
  34.  * h:由A、C、G、M、R、T计算 
  35.  * s:由K、L、M、N、P、Q计算 
  36.  * j:由cc、dd、h、m、ee、ff计算。需要注意j点的运算量比较大,因为cc、dd、ee、ff都需要通过半像素内插方法进行计算。 
  37.  * 
  38.  */  
  39. static void hpel_filter( pixel *dsth, pixel *dstv, pixel *dstc, pixel *src,  
  40.                          intptr_t stride, int width, int height, int16_t *buf )  
  41. {  
  42.     const int pad = (BIT_DEPTH > 9) ? (-10 * PIXEL_MAX) : 0;  
  43.     /* 
  44.      * 几种半像素点之间的位置关系 
  45.      * 
  46.      * X: 像素点 
  47.      * H:水平滤波半像素点 
  48.      * V:垂直滤波半像素点 
  49.      * C: 中间位置半像素点 
  50.      * 
  51.      * X   H   X       X       X 
  52.      * 
  53.      * V   C 
  54.      * 
  55.      * X       X       X       X 
  56.      * 
  57.      * 
  58.      * 
  59.      * X       X       X       X 
  60.      * 
  61.      */  
  62.     //一行一行处理  
  63.     forint y = 0; y < height; y++ )  
  64.     {  
  65.         //一个一个点处理  
  66.         //每个整像素点都对应h,v,c三个半像素点  
  67.         //v  
  68.         forint x = -2; x < width+3; x++ )//(aa,bb,b,s,gg,hh),结果存入buf  
  69.         {  
  70.             //垂直滤波半像素点  
  71.             int v = TAPFILTER(src,stride);  
  72.             dstv[x] = x264_clip_pixel( (v + 16) >> 5 );  
  73.             /* transform v for storage in a 16-bit integer */  
  74.             //这应该是给dstc计算使用的?  
  75.             buf[x+2] = v + pad;  
  76.         }  
  77.         //c  
  78.         forint x = 0; x < width; x++ )  
  79.             dstc[x] = x264_clip_pixel( (TAPFILTER(buf+2,1) - 32*pad + 512) >> 10 );//四个相邻像素中间的半像素点  
  80.         //h  
  81.         forint x = 0; x < width; x++ )  
  82.             dsth[x] = x264_clip_pixel( (TAPFILTER(src,1) + 16) >> 5 );//水平滤波半像素点  
  83.         dsth += stride;  
  84.         dstv += stride;  
  85.         dstc += stride;  
  86.         src += stride;  
  87.     }  
  88. }  

从源代码可以看出,hpel_filter()中包含了一个宏TAPFILTER()用来完成半像素点像素值的计算。在完成半像素插值工作后,dsth中存储的是经过水平插值后的半像素点,dstv中存储的是经过垂直插值后的半像素点,dstc中存储的是位于4个相邻像素点中间位置的半像素点。这三块内存中的点的位置关系如下图所示(灰色的点是整像素点)。



视频质量计算-PSNR和SSIM

X264中支持两种视频质量计算方法:PSNR和SSIM。这两种的方法都是在x264_fdec_filter_row()中计算完成的。其中PSNR在此只计算了SSD,在编码一帧结束之后的x264_encoder_frame_end()中,调用x264_psnr()完成计算。

视频质量评价的知识


PSNR知识

PSNR(Peak Signal to Noise Ratio,峰值信噪比)是最基础的视频质量评价方法。它的取值一般在20-50之间,值越大代表受损图片越接近原图片。PSNR通过对原始图像和失真图像进行像素的逐点对比,计算两幅图像像素点之间的误差,并由这些误差最终确定失真图像的质量评分。该方法由于计算简便、数学意义明确,在图像处理领域中应用最为广泛。

一幅MxN尺寸的图像的PSNR的计算公式如下所示:


其中xij 和yij 分别表示失真图像和原始图像对应像素点的灰度值;i,j 分别代表图像的行和列;L 是图像灰度值可到达的动态范围,8位的灰度图像的L=2^8-1=255。如果已知SSD,MxN尺寸图像的PSNR公式如下所示。

MSE=SSD*1/(M*N)

PSNR=10*lg(255^2/MSE)

但是PSNR仅仅计算了图像像素点间的绝对误差,没有考虑像素点间的视觉相关性,更没顾及人类视觉系统的感知特性,所以其评价结果与主观感受往往相差较大。例如下图两张图片的PSNR取值都在23.6左右,但是给人的感觉却是(a)图比(b)图清晰得多。
 
正是由于PSNR方法存在上述的问题,人们才开始研究与人类视觉系统特性相关的质量评价方法。SSIM就是一种典型的与人类视觉系统特性结合的质量评价方法。

SSIM知识
SSIM(Structural SIMilarity,结构相似度)是一种结合了亮度信息,对比度信息以及结构信息的视频质量评价方法。它的取值在0-1之间,值越大代表受损图片越接近原图片。该方法的模型图如下所示。
 
从模型图可以看出,SSIM 评价方法中的结构相似度由三个层次的结构信息共同决定。首先假设 x、y 分别是原始图像信号和失真图像信号,然后分别计算这两个信号的亮度比较函数l(x,y)、对比度比较函数c(x,y)以及结构比较函数s(x,y),最后经过加权合并计算得出图像结构相似度评价结果。这3个比较函数具体的公式如下所示。
(1)亮度比较函数l(x,y)
亮度均值μx如下所示。

亮度比较函数的公式如下所示。其中C1为常量。


(2)对比度比较函数c(x,y)
亮度标准差σx如下所示。
 
对比度比较函数的公式如下所示。其中C2为常量。
 
(3)结构比较函数s(x,y)
两个图像信号的相关系数σxy如下所示。
 
结构比较函数定义如下所示。其中C3为常量。
 
SSIM就是将上述三个公式相乘,公式如下所示。
 
为了便于计算,将α、β、γ的值都设为 1,并且令C3=C2/2,则上式的简化为下式。
 
实际经验中,对整幅图像直接使用 SSIM 模型,不如局部分块使用最后综合的效果好。因此SSIM的计算都是按照一个一个的小方块(例如8x8这种的方块)进行计算的。
PS:有关PSNR和SSIM和人眼主观感受之间的关系可以参考文章《全参考视频质量评价方法(PSNR,SSIM)以及相关数据库》

视频质量评价的源代码

X264中计算PSNR使用了两个函数:x264_pixel_ssd_wxh()和x264_psnr();而计算SSIM使用了一个函数x264_pixel_ssim_wxh()。

x264_pixel_ssd_wxh()

x264_pixel_ssd_wxh()用于计算SSD(用于以后计算PSNR)。该函数的定义位于common\pixel.c,如下所示。
[cpp] view plain copy
  1. /* 
  2.  * 计算SSD(可用于计算PSNR) 
  3.  * pix1: 受损数据 
  4.  * pix2: 原始数据 
  5.  * i_width: 图像宽 
  6.  * i_height: 图像高 
  7.  */  
  8. uint64_t x264_pixel_ssd_wxh( x264_pixel_function_t *pf, pixel *pix1, intptr_t i_pix1,  
  9.                              pixel *pix2, intptr_t i_pix2, int i_width, int i_height )  
  10. {  
  11.     //计算结果都累加到i_ssd变量上  
  12.     uint64_t i_ssd = 0;  
  13.     int y;  
  14.     int align = !(((intptr_t)pix1 | (intptr_t)pix2 | i_pix1 | i_pix2) & 15);  
  15.   
  16. #define SSD(size) i_ssd += pf->ssd[size]( pix1 + y*i_pix1 + x, i_pix1, \  
  17.                                           pix2 + y*i_pix2 + x, i_pix2 );  
  18.   
  19.   
  20.     /* 
  21.      * SSD计算过程: 
  22.      * 从左上角开始,绝大部分块使用16x16的SSD计算 
  23.      * 右边边界部分可能用16x8的SSD计算 
  24.      * 下边边界可能用8x8的SSD计算 
  25.      * 注意:这么做主要是出于汇编优化的考虑 
  26.      * 
  27.      * +----+----+----+----+----+----+----+----+----+----+-+ 
  28.      * |                   |                   |         | 
  29.      * +                   +                   +         + 
  30.      * |                   |                   |         | 
  31.      * +      16x16        +       16x16       +  8x16   + 
  32.      * |                   |                   |         | 
  33.      * +                   +                   +         + 
  34.      * |                   |                   |         | 
  35.      * +----+----+----+----+----+----+----+----+----+----+-+ 
  36.      * |         | 
  37.      * +   8x8   + 
  38.      * |         | 
  39.      * +----+----+ 
  40.      * +         + 
  41.      */  
  42.     for( y = 0; y < i_height-15; y += 16 )  
  43.     {  
  44.         int x = 0;  
  45.         //大部分使用16x16的SSD  
  46.         if( align )  
  47.             for( ; x < i_width-15; x += 16 )  
  48.                 SSD(PIXEL_16x16);        //i_ssd += pf->ssd[PIXEL_16x16]();  
  49.         //右边边缘部分可能用8x16的SSD  
  50.         for( ; x < i_width-7; x += 8 )  
  51.             SSD(PIXEL_8x16);             //i_ssd += pf->ssd[PIXEL_8x16]();  
  52.     }  
  53.     //下边边缘部分可能用到8x8的SSD  
  54.     if( y < i_height-7 )  
  55.         forint x = 0; x < i_width-7; x += 8 )  
  56.             SSD(PIXEL_8x8);              //i_ssd += pf->ssd[PIXEL_8x8]();  
  57. #undef SSD  
  58.   
  59. #define SSD1 { int d = pix1[y*i_pix1+x] - pix2[y*i_pix2+x]; i_ssd += d*d; }  
  60.   
  61.     //如果像素不是16/8的整数倍,边界上的点需要单独算  
  62.     if( i_width & 7 )  
  63.     {  
  64.         for( y = 0; y < (i_height & ~7); y++ )  
  65.             forint x = i_width & ~7; x < i_width; x++ )  
  66.                 SSD1;  
  67.     }  
  68.     if( i_height & 7 )  
  69.     {  
  70.         for( y = i_height & ~7; y < i_height; y++ )  
  71.             forint x = 0; x < i_width; x++ )  
  72.                 SSD1;  
  73.     }  
  74. #undef SSD1  
  75.   
  76.     return i_ssd;  
  77. }  

从源代码可以看出,x264_pixel_ssd_wxh()在计算大部分块的SSD的时候是以16x16的块为单位;当宽度不是16的整数倍的时候,在左侧边缘处不足16像素的地方使用了8x16的块进行计算;当高度不是16的整数倍的时候,在下方不足16像素的地方使用了8x8的块进行计算;当宽高不是8的整数倍的时候,则再单独计算。计算方法示意图如下所示。


源代码中计算16x16块的SSD的宏“SSD(PIXEL_16x16)”展开的结果如下所示。
[cpp] view plain copy
  1. i_ssd += pf->ssd[PIXEL_16x16]( pix1 + y*i_pix1 + x, i_pix1, pix2 + y*i_pix2 + x, i_pix2 );  
而pf->ssd[PIXEL_16x16]()指向的C语言版本的SSD计算函数为x264_pixel_ssd_16x16()。

x264_pixel_ssd_16x16()

x264_pixel_ssd_16x16()用于计算16x16的两个像素块的SSD。它的源代码如下所示。
[cpp] view plain copy
  1. static int x264_pixel_ssd_16x16( pixel *pix1, intptr_t i_stride_pix1,   
  2.                  pixel *pix2, intptr_t i_stride_pix2 )  
  3. {                                                    
  4.     int i_sum = 0;                                   
  5.     forint y = 0; y < 16; y++ )                    
  6.     {                                                
  7.         forint x = 0; x < 16; x++ )                
  8.         {                                            
  9.             int d = pix1[x] - pix2[x];               
  10.             i_sum += d*d;                            
  11.         }                                            
  12.         pix1 += i_stride_pix1;                       
  13.         pix2 += i_stride_pix2;                       
  14.     }                                                
  15.     return i_sum;                                    
  16. }  

从源代码可以看出,x264_pixel_ssd_16x16()将两个16x16块的对应点相减之后求平方,然后累加。其他尺寸的块的计算也是类似的,再看一个4x4块的例子。

x264_pixel_ssd_4x4()

x264_pixel_ssd_4x4()用于计算4x4的两个像素块的SSD。它的源代码如下所示。
[cpp] view plain copy
  1. static int x264_pixel_ssd_4x4( pixel *pix1, intptr_t i_stride_pix1,  
  2.                  pixel *pix2, intptr_t i_stride_pix2 )  
  3. {  
  4.     int i_sum = 0;  
  5.     forint y = 0; y < 4; y++ ) //4个像素  
  6.     {  
  7.         forint x = 0; x < 4; x++ ) //4个像素  
  8.         {  
  9.             int d = pix1[x] - pix2[x]; //相减  
  10.             i_sum += d*d;              //平方之后,累加  
  11.         }  
  12.         pix1 += i_stride_pix1;  
  13.         pix2 += i_stride_pix2;  
  14.     }  
  15.     return i_sum;  
  16. }  
可以看出4x4的块和16x16的块的计算方法是类似的,不再重复叙述。在计算完一幅图片的SSD之后,就可以将该值换算成为PSNR了。将SSD换算成PSNR的函数并不在滤波函数x264_fdec_filter_row()中,而是在x264_slice_write()执行完成之后的x264_encoder_frame_end()函数中。

x264_encoder_frame_end()中的x264_psnr()

x264_encoder_frame_end()中的x264_psnr()用于将SSD换算成为PSNR,该函数的定义如下所示。
[cpp] view plain copy
  1. //通过SSD换算PSNR  
  2. static double x264_psnr( double sqe, double size )  
  3. {  
  4.     /** 
  5.      * 计算PSNR的过程 
  6.      * 
  7.      * MSE = SSD*1/(w*h) 
  8.      * PSNR= 10*log10(MAX^2/MSE) 
  9.      * 
  10.      * 其中MAX指的是图像的灰度级,对于8bit来说就是2^8-1=255 
  11.      */  
  12.     //PIXEL_MAX=255  
  13.     double mse = sqe / (PIXEL_MAX*PIXEL_MAX * size);  
  14.     if( mse <= 0.0000000001 ) /* Max 100dB */  
  15.         return 100;  
  16.     //MSE转换为PSNR  
  17.     return -10.0 * log10( mse );  
  18. }  

从源代码中可以看出,x264_psnr()实现了上文中提到的MxN尺寸图像的PSNR计算公式:

MSE=SSD*1/(M*N)

PSNR=10*lg(255^2/MSE)

PS:实现过程看上去有点不同,实际上是一样的。

x264_pixel_ssim_wxh()

x264_pixel_ssim_wxh()用于计算SSIM。该函数的定义位于common\pixel.c,如下所示。
[cpp] view plain copy
  1. /* 
  2.  * 计算SSIM 
  3.  * pix1: 受损数据 
  4.  * pix2: 原始数据 
  5.  * i_width: 图像宽 
  6.  * i_height: 图像高 
  7.  */  
  8. float x264_pixel_ssim_wxh( x264_pixel_function_t *pf,  
  9.                            pixel *pix1, intptr_t stride1,  
  10.                            pixel *pix2, intptr_t stride2,  
  11.                            int width, int height, void *buf, int *cnt )  
  12. {  
  13.     /* 
  14.      * SSIM公式 
  15.      * SSIM = ((2*ux*uy+C1)(2*σxy+C2))/((ux^2+uy^2+C1)(σx^2+σy^2+C2)) 
  16.      * 
  17.      * 其中 
  18.      * ux=E(x) 
  19.      * uy=E(y) 
  20.      * σxy=cov(x,y)=E(XY)-ux*uy 
  21.      * σx^2=E(x^2)-E(x)^2 
  22.      * 
  23.      */  
  24.     int z = 0;  
  25.     float ssim = 0.0;  
  26.     //这是数组指针,注意和指针数组的区别  
  27.     //数组指针就是指向数组的指针  
  28.     int (*sum0)[4] = buf;  
  29.     /* 
  30.      * sum0是一个数组指针,其中存储了一个4元素数组的地址 
  31.      * 换句话说,sum0[]中每一个元素对应一个4x4块的信息(该信息包含4个元素)。 
  32.      * 
  33.      * 4个元素中: 
  34.      * [0]原始像素之和 
  35.      * [1]受损像素之和 
  36.      * [2]原始像素平方之和+受损像素平方之和 
  37.      * [3]原始像素*受损像素的值的和 
  38.      * 
  39.      */  
  40.     int (*sum1)[4] = sum0 + (width >> 2) + 3;  
  41.     //除以4,编程以“4x4块”为单位  
  42.     width >>= 2;  
  43.     height >>= 2;  
  44.     //以8*8的块为单位计算SSIM值。然后以4个像素为step滑动窗口  
  45.     forint y = 1; y < height; y++ )  
  46.     {  
  47.         //下面这个循环,只有在第一次执行的时候执行2次,处理第1行和第2行的块  
  48.         //后面的都只会执行一次  
  49.         for( ; z <= y; z++ )  
  50.         {  
  51.             //执行完XCHG()之后,sum1[]存储上1行块的值(在上面),而sum0[]等待ssim_4x4x2_core()计算当前行的值(在下面)  
  52.             XCHG( void*, sum0, sum1 );  
  53.             //获取4x4块的信息(这里并没有代入公式计算SSIM结果)  
  54.             //结果存储在sum0[]中。从左到右每个4x4的块依次存储在sum0[0],sum0[1],sum0[2]...  
  55.             //每次x前进2个块  
  56.             /* 
  57.              * ssim_4x4x2_core():计算2个4x4块 
  58.              * +----+----+ 
  59.              * |    |    | 
  60.              * +----+----+ 
  61.              */  
  62.             forint x = 0; x < width; x+=2 )  
  63.                 pf->ssim_4x4x2_core( &pix1[4*(x+z*stride1)], stride1, &pix2[4*(x+z*stride2)], stride2, &sum0[x] );  
  64.         }  
  65.         //x每次增加4,前进4个块  
  66.         //以8*8的块为单位计算  
  67.         /* 
  68.          * sum1[]为上一行4x4块信息,sum0[]为当前行4x4块信息 
  69.          * 示例(line以4x4块为单位) 
  70.          * 第1次运行 
  71.          *       +----+----+----+----+ 
  72.          * 1line |   sum1[] 
  73.          *       +----+----+----+----+ 
  74.          * 2line |   sum0[] 
  75.          *       +----+----+----+----+ 
  76.          * 
  77.          * 第2次运行 
  78.          *       + 
  79.          * 1line | 
  80.          *       +----+----+----+----+ 
  81.          * 2line |   sum1[] 
  82.          *       +----+----+----+----+ 
  83.          * 3line |   sum0[] 
  84.          *       +----+----+----+----+ 
  85.          */  
  86.         forint x = 0; x < width-1; x += 4 )  
  87.             ssim += pf->ssim_end4( sum0+x, sum1+x, X264_MIN(4,width-x-1) );//累加  
  88.     }  
  89.     *cnt = (height-1) * (width-1);  
  90.     return ssim;  
  91. }  

计算SSIM这段代码虽然看上去比较短,但是却不太容易理解。总体说来这段代码实现的SSIM的计算是以8x8的块为单元,而以4为滑动窗口的滑动步长。计算的示意图如下所示,图中每一个小方块代表一个4x4的像素块,绿色方块是正在计算区域。

x264_pixel_ssim_wxh()中是按照4x4的块对像素进行处理的。使用sum1[]保存上一行块的“信息”,sum0[]保存当前一行块的“信息”。“信息”包含4个元素:
s1: 原始像素之和
s2: 受损像素之和
ss: 原始像素平方之和+受损像素平方之和
s12: 原始像素*受损像素的值的和

ssim_4x4x2_core()用于获取上述信息;而ssim_end4()用于根据这些信息计算SSIM。


ssim_4x4x2_core()

ssim_4x4x2_core()用于获取2个4x4块计算SSIM时候需要用到的信息。该函数的定义如下所示。
[cpp] view plain copy
  1. /**************************************************************************** 
  2.  * structural similarity metric 
  3.  * 获取2个4x4的块的信息 
  4.  ****************************************************************************/  
  5. static void ssim_4x4x2_core( const pixel *pix1, intptr_t stride1,  
  6.                              const pixel *pix2, intptr_t stride2,  
  7.                              int sums[2][4] )  
  8. {  
  9.     //计算2个块,分别存在sums[0]和sums[1]  
  10.     forint z = 0; z < 2; z++ )  
  11.     {  
  12.         uint32_t s1 = 0, s2 = 0, ss = 0, s12 = 0;  
  13.         /* 
  14.          * 计算4x4块 
  15.          * +----+ 
  16.          * |    | 
  17.          * +----+ 
  18.          */  
  19.         forint y = 0; y < 4; y++ )  
  20.             forint x = 0; x < 4; x++ )  
  21.             {  
  22.                 //两个图像上分别取一个点  
  23.                 int a = pix1[x+y*stride1];  
  24.                 int b = pix2[x+y*stride2];  
  25.                 //累加  
  26.                 s1  += a;  
  27.                 s2  += b;  
  28.                 //平方累加  
  29.                 ss  += a*a;  
  30.                 ss  += b*b;  
  31.                 //相乘累加  
  32.                 s12 += a*b;  
  33.             }  
  34.         /* 
  35.          * [0]原始像素之和 
  36.          * [1]受损像素之和 
  37.          * [2]原始像素平方之和+受损像素平方之和 
  38.          * [3]原始像素*受损像素的值的和 
  39.          * 
  40.          * [0]为a00+a01+a02.... 
  41.          * [1]为b00+b01+b02.... 
  42.          * [2]为a00^2 +a01^2+...+b00^2+b01^2+... 
  43.          * [3]为a00*b00+a01*b01+... 
  44.          */  
  45.         sums[z][0] = s1;  
  46.         sums[z][1] = s2;  
  47.         sums[z][2] = ss;  
  48.         sums[z][3] = s12;  
  49.         //右移4个像素  
  50.         pix1 += 4;  
  51.         pix2 += 4;  
  52.     }  
  53. }  

从源代码可以看出,ssim_4x4x2_core()计算了2个4x4的下列信息:
s1: 原始像素之和
s2: 受损像素之和
ss: 原始像素平方之和+受损像素平方之和
s12: 原始像素*受损像素的值的和

ssim_end4()

ssim_end4()用于计算SSIM,它的定义如下所示。
[cpp] view plain copy
  1. //width一般取4  
  2. static float ssim_end4( int sum0[5][4], int sum1[5][4], int width )  
  3. {  
  4.     float ssim = 0.0;  
  5.     //循环计算8x8块的SSIM(通过4个4x4块),并且累加  
  6.     /* 
  7.      *      +----+----+----+----+----+ 
  8.      * sum1 | 0  | 1  | 2  | 3  | 4  | 
  9.      *      +----+----+----+----+----+ 
  10.      * sum0 | 0  | 1  | 2  | 3  | 4  | 
  11.      *      +----+----+----+----+----+ 
  12.      * 
  13.      *      +----+----+ 
  14.      * sum1 | 0  | 1  | 
  15.      *      +----+----+ 
  16.      * sum0 | 0  | 1  | 
  17.      *      +----+----+ 
  18.      * 
  19.      *           +----+----+ 
  20.      * sum1      | 1  | 2  | 
  21.      *           +----+----+ 
  22.      * sum0      | 1  | 2  | 
  23.      *           +----+----+ 
  24.      * 
  25.      *                +----+----+ 
  26.      * sum1           | 2  | 3  | 
  27.      *                +----+----+ 
  28.      * sum0           | 2  | 3  | 
  29.      *                +----+----+ 
  30.      * 
  31.      *                     +----+----+ 
  32.      * sum1                | 3  | 4  | 
  33.      *                     +----+----+ 
  34.      * sum0                | 3  | 4  | 
  35.      *                     +----+----+ 
  36.      * 
  37.      */  
  38.     forint i = 0; i < width; i++ )  
  39.         ssim += ssim_end1( sum0[i][0] + sum0[i+1][0] + sum1[i][0] + sum1[i+1][0],  
  40.                            sum0[i][1] + sum0[i+1][1] + sum1[i][1] + sum1[i+1][1],  
  41.                            sum0[i][2] + sum0[i+1][2] + sum1[i][2] + sum1[i+1][2],  
  42.                            sum0[i][3] + sum0[i+1][3] + sum1[i][3] + sum1[i+1][3] );  
  43.     return ssim;  
  44. }  

该函数中,sum0[]存储了当前一行4x4块的信息,sum1[]存储了上一行4x4块的信息,将sum0[i],sum0[i+1],sum1[i],sum1[i]四个4x4块结合之后就形成了1个8x8的块,传递给ssim_end1()进行计算。

ssim_end1()

ssim_end1()根据SSIM的公式计算1个块的SSIM。该函数的定义如下所示。
[cpp] view plain copy
  1. //计算1个块的SSIM  
  2. static float ssim_end1( int s1, int s2, int ss, int s12 )  
  3. {  
  4. /* Maximum value for 10-bit is: ss*64 = (2^10-1)^2*16*4*64 = 4286582784, which will overflow in some cases. 
  5.  * s1*s1, s2*s2, and s1*s2 also obtain this value for edge cases: ((2^10-1)*16*4)^2 = 4286582784. 
  6.  * Maximum value for 9-bit is: ss*64 = (2^9-1)^2*16*4*64 = 1069551616, which will not overflow. */  
  7. #if BIT_DEPTH > 9  
  8. #define type float  
  9.     static const float ssim_c1 = .01*.01*PIXEL_MAX*PIXEL_MAX*64;  
  10.     static const float ssim_c2 = .03*.03*PIXEL_MAX*PIXEL_MAX*64*63;  
  11. #else  
  12. #define type int  
  13.     //常量C1,C2  
  14.     static const int ssim_c1 = (int)(.01*.01*PIXEL_MAX*PIXEL_MAX*64 + .5);  
  15.     static const int ssim_c2 = (int)(.03*.03*PIXEL_MAX*PIXEL_MAX*64*63 + .5);  
  16. #endif  
  17.   
  18.     /* 
  19.      * SSIM公式 
  20.      * SSIM = ((2*ux*uy+C1)(2*σxy+C2))/((ux^2+uy^2+C1)(σx^2+σy^2+C2)) 
  21.      * 其中 
  22.      * ux=E(x) 
  23.      * uy=E(y) 
  24.      * σxy=cov(x,y)=E(XY)-ux*uy 
  25.      * σx^2=E(x^2)-E(x)^2 
  26.      * 
  27.      * 4个元素中: 
  28.      * [0]原始像素之和 
  29.      * [1]受损像素之和 
  30.      * [2]原始像素平方之和+受损像素平方之和 
  31.      * [3]原始像素*受损像素的值的和 
  32.      * 
  33.      */  
  34.     //注意:这里都没有求平均值  
  35.     //E(x)  
  36.     type fs1 = s1;  
  37.     //E(y)  
  38.     type fs2 = s2;  
  39.     type fss = ss;  
  40.     type fs12 = s12;  
  41.     //E(x^2)-E(x)^2+E(y^2)-E(y)^2  
  42.     type vars = fss*64 - fs1*fs1 - fs2*fs2;  
  43.     //cov(x,y)  
  44.     type covar = fs12*64 - fs1*fs2;  
  45.   
  46.     //计算公式在这里  
  47.     return (float)(2*fs1*fs2 + ssim_c1) * (float)(2*covar + ssim_c2)  
  48.          / ((float)(fs1*fs1 + fs2*fs2 + ssim_c1) * (float)(vars + ssim_c2));  
  49. #undef type  
  50. }  

从源代码可以看出,ssim_end1()实现了上文所述的SSIM计算公式。


至此有关x264中的滤波模块的源代码就分析完毕了。
阅读全文
0 0
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 企安宝登录不上怎么办 苹果维修没发票怎么办 园林绿化资质取消后怎么办 出租车从业资格证到期怎么办 养老金认证身份证不符怎么办 硬盘指示灯不亮怎么办 做业务产品质量很差怎么办 没有户籍证明了怎么办 回执编号忘了怎么办 泳镜里面花了怎么办 网页打印预览空白怎么办 中专毕业证掉了怎么办 会计证年检忘了怎么办 会计准考证丢了怎么办 从业资格证没带怎么办 安全证过期了怎么办 安全员证掉了怎么办 局部抗浮不满足怎么办 职称代评被骗怎么办 租到了公租房怎么办 公租房不住了怎么办 公寓不退押金怎么办 找物业租房被骗怎么办 租房子被骗了怎么办 公租房怎么办入住手续 重庆公租房摇到号怎么办 重庆公租房摇到号后怎么办 我被网上起诉怎么办 摇号摇到了不买怎么办 保障房离婚了怎么办 深圳有房子户口怎么办 北京户口没有房怎么办 选房顺序号靠后怎么办 房子拖着不开盘怎么办 楼开盘人多怎么办 低保取消廉租房怎么办 航空公司病退审核慢怎么办 江里水流大跑线怎么办 js逻辑好难怎么办 怀孕了好无聊怎么办 做表率我们怎么办心得