本文分析FFmpeg的H.264解码器的环路滤波(Loop Filter)部分。FFmpeg的H.264解码器调用decode_slice()函数完成了解码工作。这些解码工作可以大体上分为3个步骤:熵解码,宏块解码以及环路滤波。本文分析这3个步骤中的第3个步骤。
函数调用关系图
环路滤波(Loop Filter)部分的源代码在整个H.264解码器中的位置如下图所示。
环路滤波(Loop Filter)部分的源代码的调用关系如下图所示。
环路滤波主要用于滤除方块效应。decode_slice()在解码完一行宏块之后,会调用loop_filter()函数完成环路滤波功能。loop_filter()函数会遍历该行宏块中的每一个宏块,并且针对每一个宏块调用ff_h264_filter_mb_fast()。ff_h264_filter_mb_fast()又会调用h264_filter_mb_fast_internal()。
h264_filter_mb_fast_internal()完成了一个宏块的环路滤波工作。该函数调用filter_mb_edgev()和filter_mb_edgeh()对亮度垂直边界和水平边界进行滤波,或者调用filter_mb_edgecv()和filter_mb_edgech()对色度的的垂直边界和水平边界进行滤波。
下面首先回顾一下decode_slice()函数。
decode_slice()
decode_slice()用于解码H.264的Slice。该函数完成了“熵解码”、“宏块解码”、“环路滤波”的功能。它的定义位于libavcodec\h264_slice.c,如下所示。
//解码slice //三个主要步骤: //1.熵解码(CAVLC/CABAC) //2.宏块解码 //3.环路滤波 //此外还包含了错误隐藏代码 static int decode_slice(struct AVCodecContext *avctx, void *arg) { H264Context *h = *(void **)arg; int lf_x_start = h->mb_x; h->mb_skip_run = -1; av_assert0(h->block_offset[15] == (4 * ((scan8[15] - scan8[0]) & 7) << h->pixel_shift) + 4 * h->linesize * ((scan8[15] - scan8[0]) >> 3)); h->is_complex = FRAME_MBAFF(h) || h->picture_structure != PICT_FRAME || avctx->codec_id != AV_CODEC_ID_H264 || (CONFIG_GRAY && (h->flags & CODEC_FLAG_GRAY)); if (!(h->avctx->active_thread_type & FF_THREAD_SLICE) && h->picture_structure == PICT_FRAME && h->er.error_status_table) { const int start_i = av_clip(h->resync_mb_x + h->resync_mb_y * h->mb_width, 0, h->mb_num - 1); if (start_i) { int prev_status = h->er.error_status_table[h->er.mb_index2xy[start_i - 1]]; prev_status &= ~ VP_START; if (prev_status != (ER_MV_END | ER_DC_END | ER_AC_END)) h->er.error_occurred = 1; } } //CABAC情况 if (h->pps.cabac) { /* realign */ align_get_bits(&h->gb); /* init cabac */ //初始化CABAC解码器 ff_init_cabac_decoder(&h->cabac, h->gb.buffer + get_bits_count(&h->gb) / 8, (get_bits_left(&h->gb) + 7) / 8); ff_h264_init_cabac_states(h); //循环处理每个宏块 for (;;) { // START_TIMER //解码CABAC数据 int ret = ff_h264_decode_mb_cabac(h); int eos; // STOP_TIMER("decode_mb_cabac") //解码宏块 if (ret >= 0) ff_h264_hl_decode_mb(h); // FIXME optimal? or let mb_decode decode 16x32 ? //宏块级帧场自适应。很少接触 if (ret >= 0 && FRAME_MBAFF(h)) { h->mb_y++; ret = ff_h264_decode_mb_cabac(h); //解码宏块 if (ret >= 0) ff_h264_hl_decode_mb(h); h->mb_y--; } eos = get_cabac_terminate(&h->cabac); if ((h->workaround_bugs & FF_BUG_TRUNCATED) && h->cabac.bytestream > h->cabac.bytestream_end + 2) { //错误隐藏 er_add_slice(h, h->resync_mb_x, h->resync_mb_y, h->mb_x - 1, h->mb_y, ER_MB_END); if (h->mb_x >= lf_x_start) loop_filter(h, lf_x_start, h->mb_x + 1); return 0; } if (h->cabac.bytestream > h->cabac.bytestream_end + 2 ) av_log(h->avctx, AV_LOG_DEBUG, "bytestream overread %"PTRDIFF_SPECIFIER"\n", h->cabac.bytestream_end - h->cabac.bytestream); if (ret < 0 || h->cabac.bytestream > h->cabac.bytestream_end + 4) { av_log(h->avctx, AV_LOG_ERROR, "error while decoding MB %d %d, bytestream %"PTRDIFF_SPECIFIER"\n", h->mb_x, h->mb_y, h->cabac.bytestream_end - h->cabac.bytestream); er_add_slice(h, h->resync_mb_x, h->resync_mb_y, h->mb_x, h->mb_y, ER_MB_ERROR); return AVERROR_INVALIDDATA; } //mb_x自增 //如果自增后超过了一行的mb个数 if (++h->mb_x >= h->mb_width) { //环路滤波 loop_filter(h, lf_x_start, h->mb_x); h->mb_x = lf_x_start = 0; decode_finish_row(h); //mb_y自增(处理下一行) ++h->mb_y; //宏块级帧场自适应,暂不考虑 if (FIELD_OR_MBAFF_PICTURE(h)) { ++h->mb_y; if (FRAME_MBAFF(h) && h->mb_y < h->mb_height) predict_field_decoding_flag(h); } } //如果mb_y超过了mb的行数 if (eos || h->mb_y >= h->mb_height) { tprintf(h->avctx, "slice end %d %d\n", get_bits_count(&h->gb), h->gb.size_in_bits); er_add_slice(h, h->resync_mb_x, h->resync_mb_y, h->mb_x - 1, h->mb_y, ER_MB_END); if (h->mb_x > lf_x_start) loop_filter(h, lf_x_start, h->mb_x); return 0; } } } else { //CAVLC情况 //循环处理每个宏块 for (;;) { //解码宏块的CAVLC int ret = ff_h264_decode_mb_cavlc(h); //解码宏块 if (ret >= 0) ff_h264_hl_decode_mb(h); // FIXME optimal? or let mb_decode decode 16x32 ? if (ret >= 0 && FRAME_MBAFF(h)) { h->mb_y++; ret = ff_h264_decode_mb_cavlc(h); if (ret >= 0) ff_h264_hl_decode_mb(h); h->mb_y--; } if (ret < 0) { av_log(h->avctx, AV_LOG_ERROR, "error while decoding MB %d %d\n", h->mb_x, h->mb_y); er_add_slice(h, h->resync_mb_x, h->resync_mb_y, h->mb_x, h->mb_y, ER_MB_ERROR); return ret; } if (++h->mb_x >= h->mb_width) { //环路滤波 loop_filter(h, lf_x_start, h->mb_x); h->mb_x = lf_x_start = 0; decode_finish_row(h); ++h->mb_y; if (FIELD_OR_MBAFF_PICTURE(h)) { ++h->mb_y; if (FRAME_MBAFF(h) && h->mb_y < h->mb_height) predict_field_decoding_flag(h); } if (h->mb_y >= h->mb_height) { tprintf(h->avctx, "slice end %d %d\n", get_bits_count(&h->gb), h->gb.size_in_bits); if ( get_bits_left(&h->gb) == 0 || get_bits_left(&h->gb) > 0 && !(h->avctx->err_recognition & AV_EF_AGGRESSIVE)) { //错误隐藏 er_add_slice(h, h->resync_mb_x, h->resync_mb_y, h->mb_x - 1, h->mb_y, ER_MB_END); return 0; } else { er_add_slice(h, h->resync_mb_x, h->resync_mb_y, h->mb_x, h->mb_y, ER_MB_END); return AVERROR_INVALIDDATA; } } } if (get_bits_left(&h->gb) <= 0 && h->mb_skip_run <= 0) { tprintf(h->avctx, "slice end %d %d\n", get_bits_count(&h->gb), h->gb.size_in_bits); if (get_bits_left(&h->gb) == 0) { er_add_slice(h, h->resync_mb_x, h->resync_mb_y, h->mb_x - 1, h->mb_y, ER_MB_END); if (h->mb_x > lf_x_start) loop_filter(h, lf_x_start, h->mb_x); return 0; } else { er_add_slice(h, h->resync_mb_x, h->resync_mb_y, h->mb_x, h->mb_y, ER_MB_ERROR); return AVERROR_INVALIDDATA; } } } } }
重复记录一下decode_slice()的流程:
(1)判断H.264码流是CABAC编码还是CAVLC编码,进入不同的处理循环。
(2)如果是CABAC编码,首先调用ff_init_cabac_decoder()初始化CABAC解码器。然后进入一个循环,依次对每个宏块进行以下处理:a)调用ff_h264_decode_mb_cabac()进行CABAC熵解码
b)调用ff_h264_hl_decode_mb()进行宏块解码
c)解码一行宏块之后调用loop_filter()进行环路滤波
d)此外还有可能调用er_add_slice()进行错误隐藏处理
(3)如果是CABAC编码,直接进入一个循环,依次对每个宏块进行以下处理:
a)调用ff_h264_decode_mb_cavlc()进行CAVLC熵解码
b)调用ff_h264_hl_decode_mb()进行宏块解码
c)解码一行宏块之后调用loop_filter()进行环路滤波
d)此外还有可能调用er_add_slice()进行错误隐藏处理
可以看出,环路滤波函数是loop_filter()。下面看一下这个函数。
loop_filter()
loop_filter()完成了环路滤波工作。该函数的定义位于libavcodec\h264_slice.c,如下所示。
//环路滤波 static void loop_filter(H264Context *h, int start_x, int end_x) { uint8_t *dest_y, *dest_cb, *dest_cr; int linesize, uvlinesize, mb_x, mb_y; const int end_mb_y = h->mb_y + FRAME_MBAFF(h); const int old_slice_type = h->slice_type; const int pixel_shift = h->pixel_shift; const int block_h = 16 >> h->chroma_y_shift; if (h->deblocking_filter) { //循环处理宏块 //例如从一行开始的mb_x到一行结束的mb_x for (mb_x = start_x; mb_x < end_x; mb_x++) for (mb_y = end_mb_y - FRAME_MBAFF(h); mb_y <= end_mb_y; mb_y++) {//逐行扫描只有一行 int mb_xy, mb_type; mb_xy = h->mb_xy = mb_x + mb_y * h->mb_stride; h->slice_num = h->slice_table[mb_xy]; mb_type = h->cur_pic.mb_type[mb_xy]; h->list_count = h->list_counts[mb_xy]; if (FRAME_MBAFF(h)) h->mb_mbaff = h->mb_field_decoding_flag = !!IS_INTERLACED(mb_type); h->mb_x = mb_x; h->mb_y = mb_y; //像素数据 dest_y = h->cur_pic.f.data[0] + ((mb_x << pixel_shift) + mb_y * h->linesize) * 16; dest_cb = h->cur_pic.f.data[1] + (mb_x << pixel_shift) * (8 << CHROMA444(h)) + mb_y * h->uvlinesize * block_h; dest_cr = h->cur_pic.f.data[2] + (mb_x << pixel_shift) * (8 << CHROMA444(h)) + mb_y * h->uvlinesize * block_h; // FIXME simplify above if (MB_FIELD(h)) { linesize = h->mb_linesize = h->linesize * 2; uvlinesize = h->mb_uvlinesize = h->uvlinesize * 2; if (mb_y & 1) { // FIXME move out of this function? dest_y -= h->linesize * 15; dest_cb -= h->uvlinesize * (block_h - 1); dest_cr -= h->uvlinesize * (block_h - 1); } } else { linesize = h->mb_linesize = h->linesize; uvlinesize = h->mb_uvlinesize = h->uvlinesize; } backup_mb_border(h, dest_y, dest_cb, dest_cr, linesize, uvlinesize, 0); if (fill_filter_caches(h, mb_type)) continue; h->chroma_qp[0] = get_chroma_qp(h, 0, h->cur_pic.qscale_table[mb_xy]); h->chroma_qp[1] = get_chroma_qp(h, 1, h->cur_pic.qscale_table[mb_xy]); //宏块滤波器 if (FRAME_MBAFF(h)) { //宏块级帧场自适应才用,不研究 ff_h264_filter_mb(h, mb_x, mb_y, dest_y, dest_cb, dest_cr, linesize, uvlinesize); } else { //宏块滤波器(快速?) ff_h264_filter_mb_fast(h, mb_x, mb_y, dest_y, dest_cb, dest_cr, linesize, uvlinesize); } } } h->slice_type = old_slice_type; h->mb_x = end_x; h->mb_y = end_mb_y - FRAME_MBAFF(h); h->chroma_qp[0] = get_chroma_qp(h, 0, h->qscale); h->chroma_qp[1] = get_chroma_qp(h, 1, h->qscale); }
从源代码可以看出,loop_filter()循环遍历一行宏块,并且针对每一个宏块调用了ff_h264_filter_mb_fast()函数。
ff_h264_filter_mb_fast()
ff_h264_filter_mb_fast()用于对一个宏块进行环路滤波工作。该函数的定义位于libavcodec\h264_loopfilter.c,如下所示。
//宏块滤波器(快速?) void ff_h264_filter_mb_fast( H264Context *h, int mb_x, int mb_y, uint8_t *img_y, uint8_t *img_cb, uint8_t *img_cr, unsigned int linesize, unsigned int uvlinesize) { av_assert2(!FRAME_MBAFF(h)); if(!h->h264dsp.h264_loop_filter_strength || h->pps.chroma_qp_diff) { ff_h264_filter_mb(h, mb_x, mb_y, img_y, img_cb, img_cr, linesize, uvlinesize); return; } #if CONFIG_SMALL h264_filter_mb_fast_internal(h, mb_x, mb_y, img_y, img_cb, img_cr, linesize, uvlinesize, h->pixel_shift); #else //宏块滤波器-internal(快速?) if(h->pixel_shift){ h264_filter_mb_fast_internal(h, mb_x, mb_y, img_y, img_cb, img_cr, linesize, uvlinesize, 1); }else{ h264_filter_mb_fast_internal(h, mb_x, mb_y, img_y, img_cb, img_cr, linesize, uvlinesize, 0); } #endif }
可以看出ff_h264_filter_mb_fast()代码比较简单,其中调用了另一个函数h264_filter_mb_fast_internal()。
h264_filter_mb_fast_internal()
h264_filter_mb_fast_internal()用于对一个宏块进行环路滤波。该函数的定义位于libavcodec\h264_loopfilter.c,如下所示。
//宏块滤波器-internal(快速?) static av_always_inline void h264_filter_mb_fast_internal(H264Context *h, int mb_x, int mb_y, uint8_t *img_y, uint8_t *img_cb, uint8_t *img_cr, unsigned int linesize, unsigned int uvlinesize, int pixel_shift) { int chroma = CHROMA(h) && !(CONFIG_GRAY && (h->flags&CODEC_FLAG_GRAY)); int chroma444 = CHROMA444(h); int chroma422 = CHROMA422(h); //宏块序号 int mb_xy = h->mb_xy; int left_type= h->left_type[LTOP]; int top_type= h->top_type; int qp_bd_offset = 6 * (h->sps.bit_depth_luma - 8); int a = 52 + h->slice_alpha_c0_offset - qp_bd_offset; int b = 52 + h->slice_beta_offset - qp_bd_offset; //宏块类型 int mb_type = h->cur_pic.mb_type[mb_xy]; //量化参数 //qp用于推导alpha,beta(判断是否滤波的门限值) int qp = h->cur_pic.qscale_table[mb_xy]; int qp0 = h->cur_pic.qscale_table[mb_xy - 1]; int qp1 = h->cur_pic.qscale_table[h->top_mb_xy]; int qpc = get_chroma_qp( h, 0, qp ); int qpc0 = get_chroma_qp( h, 0, qp0 ); int qpc1 = get_chroma_qp( h, 0, qp1 ); qp0 = (qp + qp0 + 1) >> 1; qp1 = (qp + qp1 + 1) >> 1; qpc0 = (qpc + qpc0 + 1) >> 1; qpc1 = (qpc + qpc1 + 1) >> 1; //Intra类型 if( IS_INTRA(mb_type) ) { static const int16_t bS4[4] = {4,4,4,4}; static const int16_t bS3[4] = {3,3,3,3}; const int16_t *bSH = FIELD_PICTURE(h) ? bS3 : bS4; /* * 帧内宏块滤波 * 滤波顺序如下所示(大方框代表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 | * +----+----+----+----+ * */ if(left_type) //宏块的左边边界,强度bs为4的滤波(Vertical) filter_mb_edgev( &img_y[4*0<<pixel_shift], linesize, bS4, qp0, a, b, h, 1); //0 //不考虑8x8DCT if( IS_8x8DCT(mb_type) ) { filter_mb_edgev( &img_y[4*2<<pixel_shift], linesize, bS3, qp, a, b, h, 0); if(top_type){ filter_mb_edgeh( &img_y[4*0*linesize], linesize, bSH, qp1, a, b, h, 1); } filter_mb_edgeh( &img_y[4*2*linesize], linesize, bS3, qp, a, b, h, 0); } else { //宏块内部强度bs为3的滤波(Vertical) filter_mb_edgev( &img_y[4*1<<pixel_shift], linesize, bS3, qp, a, b, h, 0); //1 filter_mb_edgev( &img_y[4*2<<pixel_shift], linesize, bS3, qp, a, b, h, 0); //2 filter_mb_edgev( &img_y[4*3<<pixel_shift], linesize, bS3, qp, a, b, h, 0); //3 if(top_type){ //宏块的上边边界,强度bs为4的滤波(逐行扫描)(Horizontal) filter_mb_edgeh( &img_y[4*0*linesize], linesize, bSH, qp1, a, b, h, 1); //4 } //宏块内部强度bs为3的滤波(Horizontal) filter_mb_edgeh( &img_y[4*1*linesize], linesize, bS3, qp, a, b, h, 0); //5 filter_mb_edgeh( &img_y[4*2*linesize], linesize, bS3, qp, a, b, h, 0); //6 filter_mb_edgeh( &img_y[4*3*linesize], linesize, bS3, qp, a, b, h, 0); //7 } if(chroma){ if(chroma444){ if(left_type){ filter_mb_edgev( &img_cb[4*0<<pixel_shift], linesize, bS4, qpc0, a, b, h, 1); filter_mb_edgev( &img_cr[4*0<<pixel_shift], linesize, bS4, qpc0, a, b, h, 1); } if( IS_8x8DCT(mb_type) ) { filter_mb_edgev( &img_cb[4*2<<pixel_shift], linesize, bS3, qpc, a, b, h, 0); filter_mb_edgev( &img_cr[4*2<<pixel_shift], linesize, bS3, qpc, a, b, h, 0); if(top_type){ filter_mb_edgeh( &img_cb[4*0*linesize], linesize, bSH, qpc1, a, b, h, 1 ); filter_mb_edgeh( &img_cr[4*0*linesize], linesize, bSH, qpc1, a, b, h, 1 ); } filter_mb_edgeh( &img_cb[4*2*linesize], linesize, bS3, qpc, a, b, h, 0); filter_mb_edgeh( &img_cr[4*2*linesize], linesize, bS3, qpc, a, b, h, 0); } else { filter_mb_edgev( &img_cb[4*1<<pixel_shift], linesize, bS3, qpc, a, b, h, 0); filter_mb_edgev( &img_cr[4*1<<pixel_shift], linesize, bS3, qpc, a, b, h, 0); filter_mb_edgev( &img_cb[4*2<<pixel_shift], linesize, bS3, qpc, a, b, h, 0); filter_mb_edgev( &img_cr[4*2<<pixel_shift], linesize, bS3, qpc, a, b, h, 0); filter_mb_edgev( &img_cb[4*3<<pixel_shift], linesize, bS3, qpc, a, b, h, 0); filter_mb_edgev( &img_cr[4*3<<pixel_shift], linesize, bS3, qpc, a, b, h, 0); if(top_type){ filter_mb_edgeh( &img_cb[4*0*linesize], linesize, bSH, qpc1, a, b, h, 1); filter_mb_edgeh( &img_cr[4*0*linesize], linesize, bSH, qpc1, a, b, h, 1); } //水平horizontal filter_mb_edgeh( &img_cb[4*1*linesize], linesize, bS3, qpc, a, b, h, 0); filter_mb_edgeh( &img_cr[4*1*linesize], linesize, bS3, qpc, a, b, h, 0); filter_mb_edgeh( &img_cb[4*2*linesize], linesize, bS3, qpc, a, b, h, 0); filter_mb_edgeh( &img_cr[4*2*linesize], linesize, bS3, qpc, a, b, h, 0); filter_mb_edgeh( &img_cb[4*3*linesize], linesize, bS3, qpc, a, b, h, 0); filter_mb_edgeh( &img_cr[4*3*linesize], linesize, bS3, qpc, a, b, h, 0); } }else if(chroma422){ if(left_type){ filter_mb_edgecv(&img_cb[2*0<<pixel_shift], uvlinesize, bS4, qpc0, a, b, h, 1); filter_mb_edgecv(&img_cr[2*0<<pixel_shift], uvlinesize, bS4, qpc0, a, b, h, 1); } filter_mb_edgecv(&img_cb[2*2<<pixel_shift], uvlinesize, bS3, qpc, a, b, h, 0); filter_mb_edgecv(&img_cr[2*2<<pixel_shift], uvlinesize, bS3, qpc, a, b, h, 0); if(top_type){ filter_mb_edgech(&img_cb[4*0*uvlinesize], uvlinesize, bSH, qpc1, a, b, h, 1); filter_mb_edgech(&img_cr[4*0*uvlinesize], uvlinesize, bSH, qpc1, a, b, h, 1); } filter_mb_edgech(&img_cb[4*1*uvlinesize], uvlinesize, bS3, qpc, a, b, h, 0); filter_mb_edgech(&img_cr[4*1*uvlinesize], uvlinesize, bS3, qpc, a, b, h, 0); filter_mb_edgech(&img_cb[4*2*uvlinesize], uvlinesize, bS3, qpc, a, b, h, 0); filter_mb_edgech(&img_cr[4*2*uvlinesize], uvlinesize, bS3, qpc, a, b, h, 0); filter_mb_edgech(&img_cb[4*3*uvlinesize], uvlinesize, bS3, qpc, a, b, h, 0); filter_mb_edgech(&img_cr[4*3*uvlinesize], uvlinesize, bS3, qpc, a, b, h, 0); }else{ if(left_type){ filter_mb_edgecv( &img_cb[2*0<<pixel_shift], uvlinesize, bS4, qpc0, a, b, h, 1); filter_mb_edgecv( &img_cr[2*0<<pixel_shift], uvlinesize, bS4, qpc0, a, b, h, 1); } filter_mb_edgecv( &img_cb[2*2<<pixel_shift], uvlinesize, bS3, qpc, a, b, h, 0); filter_mb_edgecv( &img_cr[2*2<<pixel_shift], uvlinesize, bS3, qpc, a, b, h, 0); if(top_type){ filter_mb_edgech( &img_cb[2*0*uvlinesize], uvlinesize, bSH, qpc1, a, b, h, 1); filter_mb_edgech( &img_cr[2*0*uvlinesize], uvlinesize, bSH, qpc1, a, b, h, 1); } filter_mb_edgech( &img_cb[2*2*uvlinesize], uvlinesize, bS3, qpc, a, b, h, 0); filter_mb_edgech( &img_cr[2*2*uvlinesize], uvlinesize, bS3, qpc, a, b, h, 0); } } return; } else { //非Intra类型 LOCAL_ALIGNED_8(int16_t, bS, [2], [4][4]); int edges; if( IS_8x8DCT(mb_type) && (h->cbp&7) == 7 && !chroma444 ) { edges = 4; AV_WN64A(bS[0][0], 0x0002000200020002ULL); AV_WN64A(bS[0][2], 0x0002000200020002ULL); AV_WN64A(bS[1][0], 0x0002000200020002ULL); AV_WN64A(bS[1][2], 0x0002000200020002ULL); } else { int mask_edge1 = (3*(((5*mb_type)>>5)&1)) | (mb_type>>4); //(mb_type & (MB_TYPE_16x16 | MB_TYPE_8x16)) ? 3 : (mb_type & MB_TYPE_16x8) ? 1 : 0; int mask_edge0 = 3*((mask_edge1>>1) & ((5*left_type)>>5)&1); // (mb_type & (MB_TYPE_16x16 | MB_TYPE_8x16)) && (h->left_type[LTOP] & (MB_TYPE_16x16 | MB_TYPE_8x16)) ? 3 : 0; int step = 1+(mb_type>>24); //IS_8x8DCT(mb_type) ? 2 : 1; edges = 4 - 3*((mb_type>>3) & !(h->cbp & 15)); //(mb_type & MB_TYPE_16x16) && !(h->cbp & 15) ? 1 : 4; h->h264dsp.h264_loop_filter_strength( bS, h->non_zero_count_cache, h->ref_cache, h->mv_cache, h->list_count==2, edges, step, mask_edge0, mask_edge1, FIELD_PICTURE(h)); } if( IS_INTRA(left_type) ) AV_WN64A(bS[0][0], 0x0004000400040004ULL); if( IS_INTRA(top_type) ) AV_WN64A(bS[1][0], FIELD_PICTURE(h) ? 0x0003000300030003ULL : 0x0004000400040004ULL); //专门定义了一个宏? #define FILTER(hv,dir,edge,intra) if(AV_RN64A(bS[dir][edge])) { filter_mb_edge##hv( &img_y[4*edge*(dir?linesize:1<<pixel_shift)], linesize, bS[dir][edge], edge ? qp : qp##dir, a, b, h, intra ); if(chroma){ if(chroma444){ filter_mb_edge##hv( &img_cb[4*edge*(dir?linesize:1<<pixel_shift)], linesize, bS[dir][edge], edge ? qpc : qpc##dir, a, b, h, intra ); filter_mb_edge##hv( &img_cr[4*edge*(dir?linesize:1<<pixel_shift)], linesize, bS[dir][edge], edge ? qpc : qpc##dir, a, b, h, intra ); } else if(!(edge&1)) { filter_mb_edgec##hv( &img_cb[2*edge*(dir?uvlinesize:1<<pixel_shift)], uvlinesize, bS[dir][edge], edge ? qpc : qpc##dir, a, b, h, intra ); filter_mb_edgec##hv( &img_cr[2*edge*(dir?uvlinesize:1<<pixel_shift)], uvlinesize, bS[dir][edge], edge ? qpc : qpc##dir, a, b, h, intra ); } } } /* * 非Intra宏块滤波 * 滤波顺序如下所示(大方框代表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 | * +----+----+----+----+ * */ if(left_type) FILTER(v,0,0,1); //0 if( edges == 1 ) { if(top_type) FILTER(h,1,0,1); } else if( IS_8x8DCT(mb_type) ) { FILTER(v,0,2,0); if(top_type) FILTER(h,1,0,1); FILTER(h,1,2,0); } else { FILTER(v,0,1,0); //1 FILTER(v,0,2,0); //2 FILTER(v,0,3,0); //3 if(top_type) FILTER(h,1,0,1);//4 FILTER(h,1,1,0); //5 FILTER(h,1,2,0); //6 FILTER(h,1,3,0); //7 } #undef FILTER } }
通过源代码整理出来h264_filter_mb_fast_internal()的流程如下:
(1)读取QP等几个参数,用于推导滤波门限值alpha,beta。
(2)如果是帧内宏块(Intra),作如下处理:
a)对于水平的边界,调用filter_mb_edgeh()进行滤波。
b)对于垂直的边界,调用filter_mb_edgev()进行滤波。
帧内宏块滤波过程中,对于在宏块边界上的边界(最左边的垂直边界和最上边的水平边界),采用滤波强度Bs为4的滤波;对于其它边界则采用滤波强度Bs为3的滤波。
(3)如果是其他宏块,作如下处理:
a)对于水平的边界,调用filter_mb_edgeh()进行滤波。
b)对于垂直的边界,调用filter_mb_edgev()进行滤波。
此类宏块的滤波强度需要另作判断。
总体说来,一个宏块内部的滤波顺序如下图所示。图中的“0”、“1”、“2”、“3”为滤波的顺序。可以看出首先对垂直边界进行滤波,然后对水平边界进行滤波。垂直边界滤波按照从左到右的顺序进行,而水平边界的滤波按照从上到下的顺序进行。
下面分别看一下对水平边界滤波的函数filter_mb_edgeh()以及对垂直边界滤波的函数filter_mb_edgev()。
filter_mb_edgeh()
filter_mb_edgeh()用于对水平边界进行滤波。该函数定义位于libavcodec\h264_loopfilter.c,如下所示。
//滤波水平边界(Horizontal)-亮度 //垂直(Vertical)滤波器 // 边界 // x // x // 边界---------- // x // x static av_always_inline void filter_mb_edgeh(uint8_t *pix, int stride, const int16_t bS[4], unsigned int qp, int a, int b, H264Context *h, int intra) { //alpha,beta为判断是否滤波的门限值 //它们是通过将(qp+offset)作为索引查表得到的 //qp大(压缩大),门限高,更容易发生滤波 const unsigned int index_a = qp + a; const int alpha = alpha_table[index_a]; const int beta = beta_table[qp + b]; //门限为0,不用滤波了 if (alpha ==0 || beta == 0) return; if( bS[0] < 4 || !intra ) { int8_t tc[4]; tc[0] = tc0_table[index_a][bS[0]]; tc[1] = tc0_table[index_a][bS[1]]; tc[2] = tc0_table[index_a][bS[2]]; tc[3] = tc0_table[index_a][bS[3]]; //边界强度3以下(弱滤波) h->h264dsp.h264_v_loop_filter_luma(pix, stride, alpha, beta, tc); } else { //边界强度为4个滤波(强滤波) h->h264dsp.h264_v_loop_filter_luma_intra(pix, stride, alpha, beta); } }
从filter_mb_edgeh()的定义可以看出,该函数首先计算了alpha,beta两个滤波的门限值,然后根据输入信息判断是否需要强滤波。如果需要强滤波(Bs取值为4),就调用H264DSPContext中的滤波汇编函数h264_v_loop_filter_luma_intra();如果不需要强滤波(Bs取值为1、2、3),就调用H264DSPContext中的滤波汇编函数h264_v_loop_filter_luma()。
在这里有一点需要注意,对水平边界进行滤波的函数(函数名中包含“_edgeh”),调用的是垂直滤波函数(函数名中包含“_v”)。
filter_mb_edgev()
filter_mb_edgev()用于对垂直边界进行滤波。该函数定义位于libavcodec\h264_loopfilter.c,如下所示。
//滤波垂直边界(Vertical)-亮度 //水平(Horizontal)滤波器 // 边界 // | // x x x | x x x // | static av_always_inline void filter_mb_edgev(uint8_t *pix, int stride, const int16_t bS[4], unsigned int qp, int a, int b, H264Context *h, int intra) { const unsigned int index_a = qp + a; const int alpha = alpha_table[index_a]; const int beta = beta_table[qp + b]; if (alpha ==0 || beta == 0) return; if( bS[0] < 4 || !intra ) { int8_t tc[4]; tc[0] = tc0_table[index_a][bS[0]]; tc[1] = tc0_table[index_a][bS[1]]; tc[2] = tc0_table[index_a][bS[2]]; tc[3] = tc0_table[index_a][bS[3]]; //Bs取值为1,2,3的弱滤波 h->h264dsp.h264_h_loop_filter_luma(pix, stride, alpha, beta, tc); } else { //Bs取值为4的强滤波 h->h264dsp.h264_h_loop_filter_luma_intra(pix, stride, alpha, beta); } }
可以看出filter_mb_edgev()的定义与filter_mb_edgeh()是类似的。也是先计算了alpha,beta两个滤波的门限值,然后根据输入信息判断是否需要强滤波。如果需要强滤波(Bs取值为4),就调用H264DSPContext中的滤波汇编函数h264_h_loop_filter_luma_intra();如果不需要强滤波(Bs取值为1、2、3),就调用H264DSPContext中的滤波汇编函数h264_h_loop_filter_luma()。下文将会对H264DSPContext中的h264_h_loop_filter_luma()和h264_h_loop_filter_luma_intra()这两个汇编函数进行分析。
环路滤波小知识
H.264解码器在解码后的数据一般情况下会出现方块效应。产生这种效应的原因主要有两个:
(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越大表明压缩的程度越大,所以也可以得知高压缩比的情况下更需要进行环路滤波。
有关环路滤波的基本知识就记录到这里,下文开始分析和环路滤波相关的汇编函数的源代码。
环路滤波汇编函数
首先看一下环路滤波汇编函数的初始化函数ff_h264dsp_init()。
ff_h264dsp_init()
ff_h264dsp_init()用于初始化环路滤波函数(实际上该函数也用于初始化DCT反变换和Hadamard反变换函数)。该函数的定义位于libavcodec\h264dsp.c,如下所示。
//初始化DSP相关的函数。包含了IDCT、环路滤波函数等 av_cold void ff_h264dsp_init(H264DSPContext *c, const int bit_depth, const int chroma_format_idc) { #undef FUNC #define FUNC(a, depth) a ## _ ## depth ## _c #define ADDPX_DSP(depth) c->h264_add_pixels4_clear = FUNC(ff_h264_add_pixels4, depth); c->h264_add_pixels8_clear = FUNC(ff_h264_add_pixels8, depth) if (bit_depth > 8 && bit_depth <= 16) { ADDPX_DSP(16); } else { ADDPX_DSP(8); } #define H264_DSP(depth) c->h264_idct_add= FUNC(ff_h264_idct_add, depth); c->h264_idct8_add= FUNC(ff_h264_idct8_add, depth); c->h264_idct_dc_add= FUNC(ff_h264_idct_dc_add, depth); c->h264_idct8_dc_add= FUNC(ff_h264_idct8_dc_add, depth); c->h264_idct_add16 = FUNC(ff_h264_idct_add16, depth); c->h264_idct8_add4 = FUNC(ff_h264_idct8_add4, depth); if (chroma_format_idc <= 1) c->h264_idct_add8 = FUNC(ff_h264_idct_add8, depth); else c->h264_idct_add8 = FUNC(ff_h264_idct_add8_422, depth); c->h264_idct_add16intra= FUNC(ff_h264_idct_add16intra, depth); c->h264_luma_dc_dequant_idct= FUNC(ff_h264_luma_dc_dequant_idct, depth); if (chroma_format_idc <= 1) c->h264_chroma_dc_dequant_idct= FUNC(ff_h264_chroma_dc_dequant_idct, depth); else c->h264_chroma_dc_dequant_idct= FUNC(ff_h264_chroma422_dc_dequant_idct, depth); c->weight_h264_pixels_tab[0]= FUNC(weight_h264_pixels16, depth); c->weight_h264_pixels_tab[1]= FUNC(weight_h264_pixels8, depth); c->weight_h264_pixels_tab[2]= FUNC(weight_h264_pixels4, depth); c->weight_h264_pixels_tab[3]= FUNC(weight_h264_pixels2, depth); c->biweight_h264_pixels_tab[0]= FUNC(biweight_h264_pixels16, depth); c->biweight_h264_pixels_tab[1]= FUNC(biweight_h264_pixels8, depth); c->biweight_h264_pixels_tab[2]= FUNC(biweight_h264_pixels4, depth); c->biweight_h264_pixels_tab[3]= FUNC(biweight_h264_pixels2, depth); c->h264_v_loop_filter_luma= FUNC(h264_v_loop_filter_luma, depth); c->h264_h_loop_filter_luma= FUNC(h264_h_loop_filter_luma, depth); c->h264_h_loop_filter_luma_mbaff= FUNC(h264_h_loop_filter_luma_mbaff, depth); c->h264_v_loop_filter_luma_intra= FUNC(h264_v_loop_filter_luma_intra, depth); c->h264_h_loop_filter_luma_intra= FUNC(h264_h_loop_filter_luma_intra, depth); c->h264_h_loop_filter_luma_mbaff_intra= FUNC(h264_h_loop_filter_luma_mbaff_intra, depth); c->h264_v_loop_filter_chroma= FUNC(h264_v_loop_filter_chroma, depth); if (chroma_format_idc <= 1) c->h264_h_loop_filter_chroma= FUNC(h264_h_loop_filter_chroma, depth); else c->h264_h_loop_filter_chroma= FUNC(h264_h_loop_filter_chroma422, depth); if (chroma_format_idc <= 1) c->h264_h_loop_filter_chroma_mbaff= FUNC(h264_h_loop_filter_chroma_mbaff, depth); else c->h264_h_loop_filter_chroma_mbaff= FUNC(h264_h_loop_filter_chroma422_mbaff, depth); c->h264_v_loop_filter_chroma_intra= FUNC(h264_v_loop_filter_chroma_intra, depth); if (chroma_format_idc <= 1) c->h264_h_loop_filter_chroma_intra= FUNC(h264_h_loop_filter_chroma_intra, depth); else c->h264_h_loop_filter_chroma_intra= FUNC(h264_h_loop_filter_chroma422_intra, depth); if (chroma_format_idc <= 1) c->h264_h_loop_filter_chroma_mbaff_intra= FUNC(h264_h_loop_filter_chroma_mbaff_intra, depth); else c->h264_h_loop_filter_chroma_mbaff_intra= FUNC(h264_h_loop_filter_chroma422_mbaff_intra, depth); c->h264_loop_filter_strength= NULL; //根据颜色位深,初始化不同的函数 //一般为8bit,即执行H264_DSP(8) switch (bit_depth) { case 9: H264_DSP(9); break; case 10: H264_DSP(10); break; case 12: H264_DSP(12); break; case 14: H264_DSP(14); break; default: av_assert0(bit_depth<=8); H264_DSP(8); break; } //这个函数查找startcode的时候用到 //在这里竟然单独列出 c->startcode_find_candidate = ff_startcode_find_candidate_c; //如果系统支持,则初始化经过汇编优化的函数 if (ARCH_AARCH64) ff_h264dsp_init_aarch64(c, bit_depth, chroma_format_idc); if (ARCH_ARM) ff_h264dsp_init_arm(c, bit_depth, chroma_format_idc); if (ARCH_PPC) ff_h264dsp_init_ppc(c, bit_depth, chroma_format_idc); if (ARCH_X86) ff_h264dsp_init_x86(c, bit_depth, chroma_format_idc); }
从源代码可以看出,ff_h264dsp_init()初始化了环路滤波函数,DCT反变换函数和Hadamard反变换函数。下面展开“H264_DSP(8)”宏看一下C语言版本函数初始化的代码。
c->h264_idct_add= ff_h264_idct_add_8_c; c->h264_idct8_add= ff_h264_idct8_add_8_c; c->h264_idct_dc_add= ff_h264_idct_dc_add_8_c; c->h264_idct8_dc_add= ff_h264_idct8_dc_add_8_c; c->h264_idct_add16 = ff_h264_idct_add16_8_c; c->h264_idct8_add4 = ff_h264_idct8_add4_8_c; if (chroma_format_idc <= 1) c->h264_idct_add8 = ff_h264_idct_add8_8_c; else c->h264_idct_add8 = ff_h264_idct_add8_422_8_c; c->h264_idct_add16intra= ff_h264_idct_add16intra_8_c; c->h264_luma_dc_dequant_idct= ff_h264_luma_dc_dequant_idct_8_c; if (chroma_format_idc <= 1) c->h264_chroma_dc_dequant_idct= ff_h264_chroma_dc_dequant_idct_8_c; else c->h264_chroma_dc_dequant_idct= ff_h264_chroma422_dc_dequant_idct_8_c; c->weight_h264_pixels_tab[0]= weight_h264_pixels16_8_c; c->weight_h264_pixels_tab[1]= weight_h264_pixels8_8_c; c->weight_h264_pixels_tab[2]= weight_h264_pixels4_8_c; c->weight_h264_pixels_tab[3]= weight_h264_pixels2_8_c; c->biweight_h264_pixels_tab[0]= biweight_h264_pixels16_8_c; c->biweight_h264_pixels_tab[1]= biweight_h264_pixels8_8_c; c->biweight_h264_pixels_tab[2]= biweight_h264_pixels4_8_c; c->biweight_h264_pixels_tab[3]= biweight_h264_pixels2_8_c; c->h264_v_loop_filter_luma= h264_v_loop_filter_luma_8_c; c->h264_h_loop_filter_luma= h264_h_loop_filter_luma_8_c; c->h264_h_loop_filter_luma_mbaff= h264_h_loop_filter_luma_mbaff_8_c; c->h264_v_loop_filter_luma_intra= h264_v_loop_filter_luma_intra_8_c; c->h264_h_loop_filter_luma_intra= h264_h_loop_filter_luma_intra_8_c; c->h264_h_loop_filter_luma_mbaff_intra= h264_h_loop_filter_luma_mbaff_intra_8_c; c->h264_v_loop_filter_chroma= h264_v_loop_filter_chroma_8_c; if (chroma_format_idc <= 1) c->h264_h_loop_filter_chroma= h264_h_loop_filter_chroma_8_c; else c->h264_h_loop_filter_chroma= h264_h_loop_filter_chroma422_8_c; if (chroma_format_idc <= 1) c->h264_h_loop_filter_chroma_mbaff= h264_h_loop_filter_chroma_mbaff_8_c; else c->h264_h_loop_filter_chroma_mbaff= h264_h_loop_filter_chroma422_mbaff_8_c; c->h264_v_loop_filter_chroma_intra= h264_v_loop_filter_chroma_intra_8_c; if (chroma_format_idc <= 1) c->h264_h_loop_filter_chroma_intra= h264_h_loop_filter_chroma_intra_8_c; else c->h264_h_loop_filter_chroma_intra= h264_h_loop_filter_chroma422_intra_8_c; if (chroma_format_idc <= 1) c->h264_h_loop_filter_chroma_mbaff_intra= h264_h_loop_filter_chroma_mbaff_intra_8_c; else c->h264_h_loop_filter_chroma_mbaff_intra= h264_h_loop_filter_chroma422_mbaff_intra_8_c; c->h264_loop_filter_strength= ((void *)0);
从“H264_DSP(8)”宏展开的结果可以看出,和亮度环路滤波有关的C语言函数有如下4个:
h264_v_loop_filter_luma_8_c():亮度垂直的普通滤波。
h264_h_loop_filter_luma_8_c():亮度水平的普通滤波。
h264_v_loop_filter_luma_intra_8_c():亮度垂直的强滤波。h264_h_loop_filter_luma_intra_8_c():亮度水平的强滤波。
下面分别分析这4个函数的源代码。
h264_v_loop_filter_luma_8_c()
h264_v_loop_filter_luma_8_c()实现了亮度边界垂直普通滤波器(处理水平边界)。该函数的定义位于libavcodec\h264dsp_template.c,如下所示。
//垂直(Vertical)普通滤波器 // 边界 // x // x // 边界---------- // x // x static void h264_v_loop_filter_luma_8_c(uint8_t *pix, int stride, int alpha, int beta, int8_t *tc0) { //xstride=stride(用于选择滤波的像素) //ystride=1 //inner_iters=4 h264_loop_filter_luma_8_c(pix, stride, sizeof(pixel), 4, alpha, beta, tc0); }
从源代码中可以看出,h264_v_loop_filter_luma_8_c()调用了另一个函数h264_loop_filter_luma_8_c()。需要注意在调用h264_loop_filter_luma_8_c()的时候传递的3个主要的参数:
xstride=stride ystride=1 inner_iters=4
这几个参数中的xstride,ystride决定了滤波器的方向。下面看一下垂直和水平方向通用的普通滤波函数h264_loop_filter_luma_8_c()的定义。
h264_loop_filter_luma_8_c()
h264_loop_filter_luma_8_c()用于垂直或者水平滤波的普通滤波器(Bs取值为1、2、3)函数。该函数的定义位于libavcodec\h264dsp_template.c。原函数中包含了一些宏定义,宏定义展开后的结果如下所示。
//----------------------------------------------------------------------- //代码中函数名包含大量的“FUNCC”的宏,该宏的定义如下所示 //“FUNCC(xxx)”展开后的结果为“xxx_8_c”,即在“xxx”后面加上“_8_c” //下面的代码中为了阅读方便,手动展开了一些重要函数的“FUNCC”宏。 //但是手动展开宏比较麻烦,所以还是有一些“FUNCC”宏没有展开 /* * 环路滤波函数(Loop Filter)展开结果 * * 源代码注释和处理:雷霄骅 * [email protected] * http://blog.csdn.net/leixiaohua1020 */ //亮度的环路滤波器-普通滤波器 //边界强度Bs取1,2,3 //参数: //p_pix:像素数据 //xstride,ystride:决定了是横向边界滤波器还是纵向边界滤波器 //inner_iters:逐行扫描为4 //alpha,beta:决定滤波器是否滤波的门限值,由QP确定。QP大,门限会高一些,更有可能滤波。 //tc0:限幅值,由QP确定。QP大,限幅值会高一些,相对宽松。此外边界强度Bs大,限幅值也会大。 //普通滤波涉及到方块边界周围的6个点(边界两边各3个点):p2,p1,p0,q0,q1,q2。 static av_always_inline av_flatten void h264_loop_filter_luma_8_c(uint8_t *p_pix, int xstride, int ystride, int inner_iters, int alpha, int beta, int8_t *tc0) { //pixel代表了一个像素,在这里是uint8_t,定义如下所示 //# define pixel uint8_t pixel *pix = (pixel*)p_pix; int i, d; //不右移 xstride >>= sizeof(pixel)-1; ystride >>= sizeof(pixel)-1; //BIT_DEPTH在这里取值为8,定义如下所示 //#define BIT_DEPTH 8 alpha <<= BIT_DEPTH - 8; beta <<= BIT_DEPTH - 8; //循环一共4x4=16次,相当于处理了16个点,与宏块的宽度是相同的 /* * [滤波示例] 大方框代表一个宏块 * * xstride=1,ystride=stride * * +----+----+----+----+ * | X | | | * +----+----+----+----+ * | X | | | * +----+----+----+----+ * | X | | | * +----+----+----+----+ * | X | | | * +----+----+----+----+ * * xstride=stride,ystride=1 * * +----+----+----+----+ * | | | | | * +--X-+--X-+--X-+--X-+ * | | | | | * +----+----+----+----+ * | | | | | * +----+----+----+----+ * | | | | | * +----+----+----+----+ */ //外部循环4次 for( i = 0; i < 4; i++ ) { const int tc_orig = tc0[i] << (BIT_DEPTH - 8); if( tc_orig < 0 ) { pix += inner_iters*ystride; continue; } //一般inner_iters=4 for( d = 0; d < inner_iters; d++ ) { //p和q //如果xstride=stride,ystride=1 //就是处理纵向的6个像素 //对应的是方块的横向边界的滤波(后文以此举例子)。如下所示: // p2 // p1 // p0 //=====图像边界===== // q0 // q1 // q2 // //如果xstride=1,ystride=stride //就是处理纵向的6个像素 //对应的是方块的横向边界的滤波,即如下所示: // || // p2 p1 p0 || q0 q1 q2 // || // 边界 //注意:这里乘的是xstride const int p0 = pix[-1*xstride]; const int p1 = pix[-2*xstride]; const int p2 = pix[-3*xstride]; const int q0 = pix[0]; const int q1 = pix[1*xstride]; const int q2 = pix[2*xstride]; //计算方法参考相关的标准 //alpha和beta是用于检查图像内容的2个参数 //只有满足if()里面3个取值条件的时候(只涉及边界旁边的4个点),才会滤波 if( FFABS( p0 - q0 ) < alpha && FFABS( p1 - p0 ) < beta && FFABS( q1 - q0 ) < beta ) { int tc = tc_orig; int i_delta; //上面2个点(p0,p2)满足条件的时候,滤波p1 if( FFABS( p2 - p0 ) < beta ) { //av_clip(int a, int amin, int amax)用于限幅: Clip a signed integer value into the amin-amax range. if(tc_orig) pix[-2*xstride] = p1 + av_clip( (( p2 + ( ( p0 + q0 + 1 ) >> 1 ) ) >> 1) - p1, -tc_orig, tc_orig ); tc++; } //下面2个点(q0,q2)满足条件的时候,滤波q1 if( FFABS( q2 - q0 ) < beta ) { //q1 if(tc_orig) pix[ xstride] = q1 + av_clip( (( q2 + ( ( p0 + q0 + 1 ) >> 1 ) ) >> 1) - q1, -tc_orig, tc_orig ); tc++; } i_delta = av_clip( (((q0 - p0 ) << 2) + (p1 - q1) + 4) >> 3, -tc, tc ); //p0 pix[-xstride] = av_clip_pixel( p0 + i_delta ); /* p0‘ */ //q0 pix[0] = av_clip_pixel( q0 - i_delta ); /* q0‘ */ } //移动指针 //注意:这里加的是ystride pix += ystride; } } }
由于源代码中写了比较充分的注释,在这里就不再逐行解析代码了。可以看出函数中包含了两个嵌套的for()循环,每个for()循环循环4次,合计运行16次。for()循环执行一遍即完成了一次水平(或者垂直)的滤波,所以for()循环执行完毕的时候,就完成了对宏块中一个纵向边界(或者横向边界)的滤波。
函数的输入参数xstride和ystride决定了函数是水平滤波器还是垂直滤波器。如果xstride=stride、ystride=1,滤波器处理垂直的6个像素,为垂直滤波器;xstride=1、ystride=stride,滤波器处理水平的6个像素,为水平滤波器。
函数在确定了处理的6个点之后,就会根据滤波的门限值alpha和beta判定边界是否满足滤波条件。如果满足条件,就会根据下面的公式进行滤波(只列出p点的,q点类似):
p0’ = p0 + (((q0 - p0 ) << 2) + (p1 - q1) + 4) >> 3
p1’ = ( p2 + ( ( p0 + q0 + 1 ) >> 1) – 2p1 ) >> 1
h264_h_loop_filter_luma_8_c()
h264_h_loop_filter_luma_8_c()实现了亮度边界水平普通滤波器(处理垂直边界)。该函数的定义位于libavcodec\h264dsp_template.c,如下所示。
//水平(Horizontal)普通滤波器 // 边界 // | // x x x | x x x // | static void h264_h_loop_filter_luma_8_c(uint8_t *pix, int stride, int alpha, int beta, int8_t *tc0) { //xstride=1(用于选择滤波的像素) //ystride=stride //inner_iters=4 h264_loop_filter_luma_8_c(pix, sizeof(pixel), stride, 4, alpha, beta, tc0); }
从源代码中可以看出,h264_h_loop_filter_luma_8_c()和h264_v_loop_filter_luma_8_c()类似,也调用了h264_loop_filter_luma_8_c()。需要注意在调用h264_loop_filter_luma_8_c()的时候传递的3个主要的参数:
xstride=1 ystride=stride inner_iters=4
h264_v_loop_filter_luma_intra_8_c()
h264_v_loop_filter_luma_intra_8_c()实现了亮度边界垂直强滤波器(处理水平边界)。该函数的定义位于libavcodec\h264dsp_template.c,如下所示。
//垂直(Vertical)强滤波器 // 边界 // x // x // 边界---------- // x // x static void h264_v_loop_filter_luma_intra_8_c(uint8_t *pix, int stride, int alpha, int beta) { //xstride=stride //ystride=1 //inner_iters=4 h264_loop_filter_luma_intra_8_c(pix, stride, sizeof(pixel), 4, alpha, beta); }
可以看出h264_v_loop_filter_luma_intra_8_c()调用了水平垂直通用的强滤波器函数h264_loop_filter_luma_intra_8_c()。并传递了以下参数:
xstride=stride ystride=1 inner_iters=4
h264_loop_filter_luma_intra_8_c()
h264_loop_filter_luma_intra_8_c()是用于垂直或者水平滤波的强滤波器(Bs取值为4)函数。该函数的定义位于libavcodec\h264dsp_template.c,定义如下所示。
//亮度的环路滤波器-强滤波器 //边界强度Bs取4(最强) //强滤波涉及到方块边界周围的8个点(边界两边各4个点):p3,p2,p1,p0,q0,q1,q2,q3 static av_always_inline av_flatten void h264_loop_filter_luma_intra_8_c(uint8_t *p_pix, int xstride, int ystride, int inner_iters, int alpha, int beta) { pixel *pix = (pixel*)p_pix; int d; xstride >>= sizeof(pixel)-1; ystride >>= sizeof(pixel)-1; alpha <<= BIT_DEPTH - 8; beta <<= BIT_DEPTH - 8; //循环一共16次,相当于处理了16个点,与宏块的宽度是相同的 /* * [滤波示例] 大方框代表一个宏块 * * xstride=1,ystride=stride * * +----+----+----+----+ * X | | | | * +----+----+----+----+ * X | | | | * +----+----+----+----+ * X | | | | * +----+----+----+----+ * X | | | | * +----+----+----+----+ * * xstride=stride,ystride=1 * * +--X-+--X-+--X-+--X-+ * | | | | | * +----+----+----+----+ * | | | | | * +----+----+----+----+ * | | | | | * +----+----+----+----+ * | | | | | * +----+----+----+----+ */ //一般inner_iters=4 for( d = 0; d < 4 * inner_iters; d++ ) { //p和q //如果xstride=stride,ystride=1 //就是处理纵向的6个像素 //对应的是方块的横向边界的滤波(后文以此举例子)。如下所示: // p2 // p1 // p0 //=====图像边界===== // q0 // q1 // q2 // //如果xstride=1,ystride=stride //就是处理纵向的6个像素 //对应的是方块的横向边界的滤波,即如下所示: // || // p2 p1 p0 || q0 q1 q2 // || // 边界 //注意:这里乘的是xstride const int p2 = pix[-3*xstride]; const int p1 = pix[-2*xstride]; const int p0 = pix[-1*xstride]; const int q0 = pix[ 0*xstride]; const int q1 = pix[ 1*xstride]; const int q2 = pix[ 2*xstride]; if( FFABS( p0 - q0 ) < alpha && FFABS( p1 - p0 ) < beta && FFABS( q1 - q0 ) < beta ) { //满足条件的时候,使用强滤波器 if(FFABS( p0 - q0 ) < (( alpha >> 2 ) + 2 )){ //p if( FFABS( p2 - p0 ) < beta) { const int p3 = pix[-4*xstride]; /* p0‘, p1‘, p2‘ */ pix[-1*xstride] = ( p2 + 2*p1 + 2*p0 + 2*q0 + q1 + 4 ) >> 3; pix[-2*xstride] = ( p2 + p1 + p0 + q0 + 2 ) >> 2; pix[-3*xstride] = ( 2*p3 + 3*p2 + p1 + p0 + q0 + 4 ) >> 3; } else { //不满足条件的时候 /* p0‘ */ pix[-1*xstride] = ( 2*p1 + p0 + q1 + 2 ) >> 2; } //q if( FFABS( q2 - q0 ) < beta) { const int q3 = pix[3*xstride]; /* q0‘, q1‘, q2‘ */ pix[0*xstride] = ( p1 + 2*p0 + 2*q0 + 2*q1 + q2 + 4 ) >> 3; pix[1*xstride] = ( p0 + q0 + q1 + q2 + 2 ) >> 2; pix[2*xstride] = ( 2*q3 + 3*q2 + q1 + q0 + p0 + 4 ) >> 3; } else { /* q0‘ */ pix[0*xstride] = ( 2*q1 + q0 + p1 + 2 ) >> 2; } }else{ //不满足条件的时候,使用下式修正 /* p0‘, q0‘ */ pix[-1*xstride] = ( 2*p1 + p0 + q1 + 2 ) >> 2; pix[ 0*xstride] = ( 2*q1 + q0 + p1 + 2 ) >> 2; } } pix += ystride; } }
由于源代码中写了比较充分的注释,在这里就不再逐行解析代码了。可以看出函数中包含了一个会执行16次的for()循环。for()循环执行一遍即完成了一次水平(或者垂直)的滤波,所以for()循环执行完毕的时候,就完成了对宏块中一个纵向边界(或者横向边界)的滤波。
函数的输入参数xstride和ystride决定了函数是水平滤波器还是垂直滤波器。如果xstride=stride、ystride=1,滤波器处理垂直的8个像素,为垂直滤波器;xstride=1、ystride=stride,滤波器处理水平的8个像素,为水平滤波器。
函数在确定了处理的8个点之后,就会根据滤波的门限值alpha和beta判定边界是否满足滤波条件。如果满足条件,就会根据下面的公式进行滤波(只列出p点的,q点类似):
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
h264_h_loop_filter_luma_intra_8_c()
h264_v_loop_filter_luma_intra_8_c()实现了亮度边界水平强滤波器(处理垂直边界)。该函数的定义位于libavcodec\h264dsp_template.c,如下所示。
//水平(Horizontal)强滤波器 // 边界 // | // x x x | x x x // | static void h264_h_loop_filter_luma_intra_8_c(uint8_t *pix, int stride, int alpha, int beta) { //xstride=1 //ystride=stride //inner_iters=4 h264_loop_filter_luma_intra_8_c(pix, sizeof(pixel), stride, 4, alpha, beta); }
可以看出h264_h_loop_filter_luma_intra_8_c()和h264_v_loop_filter_luma_intra_8_c()类似,都调用了h264_loop_filter_luma_intra_8_c()。
至此FFmpeg H.264解码器熵解码的部分就分析完毕了。
雷霄骅
[email protected]
http://blog.csdn.net/leixiaohua1020