h264解码之环路滤波

代码以ffmpeg为例,h264解码代码在h264.c里。

环路滤波(Loop Filter)部分

        FFmpeg的H.264解码器调用decode_slice()函数完成了解码工作。这些解码工作可以大体上分为3个步骤:熵解码,宏块解码以及环路滤波。

        环路滤波主要用于滤除方块效应。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()用于解码H.264的Slice。该函数完成了“熵解码”、“宏块解码”、“环路滤波”的功能。它的定义位于libavcodec\h264_slice.c。

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()循环遍历一行宏块,并且针对每一个宏块调用了ff_h264_filter_mb_fast()函数。ff_h264_filter_mb_fast()用于对一个宏块进行环路滤波工作。该函数的定义位于libavcodec\h264_loopfilter.c。ff_h264_filter_mb_fast()代码比较简单,其中调用了另一个函数h264_filter_mb_fast_internal()。

h264_filter_mb_fast_internal

h264_filter_mb_fast_internal()用于对一个宏块进行环路滤波。该函数的定义位于libavcodec\h264_loopfilter.c。通过源代码整理出来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()用于对水平边界进行滤波。该函数定义位于libavcodec\h264_loopfilter.c。该函数首先计算了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()用于对垂直边界进行滤波。该函数定义位于libavcodec\h264_loopfilter.c,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)运动补偿
正是由于这种块效应的存在,才需要添加环路滤波器调整相邻的“块”边缘上的像素值以减轻这种视觉上的不连续感。下面一张图显示了环路滤波的效果。图中左边的图没有使用环路滤波,而右边的图使用了环路滤波。

环路滤波的门限

        并不是所有的块的边界处都需要环路滤波。例如画面中物体的边界正好和块的边界重合的话,就不能进行滤波,否则会使画面中物体的边界变模糊。因此需要区别开物体边界和块效应边界。一般情况 下,物体边界两边的像素值差别很大,而块效应边界两边像素值差别比较小。《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()。f_h264dsp_init()用于初始化环路滤波函数(实际上该函数也用于初始化DCT反变换和Hadamard反变换函数)。该函数的定义位于libavcodec\h264dsp.c。ff_h264dsp_init()初始化了环路滤波函数,DCT反变换函数和Hadamard反变换函数。

h264_v_loop_filter_luma_8_c()实现了亮度边界垂直普通滤波器(处理水平边界)。该函数的定义位于libavcodec\h264dsp_template.c。

  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值