那天摄像时背景是雪白的墙,结果解码时问题出现了。

问题现象:
        解码的图像在墙壁部分有很明显的方块,其它图像变化大的地方(如人脸部分)情况还好。

问题分析:
        以前没有这种情况的呀。改变了的只有编码速率。原来为了兼顾网络状况,我们将码率降得比较低。码率小,量化的精度就低,宏块与宏块之间的量化误差变大。当颜色变化很平缓时,这种很小的误差就变得明显了,图像上的体现就是宏块边界出现明显的颜色跳变,也就是方块。

问题解决:
        还好mpeg4标准考虑到了这个现象,他们提供了一种叫deblock的解码后处理方法来弱化这种现象。实际将deblock添加后图像确实平滑许多,方块也没了。


deblock原理:
     大概做法就是将每个点与它上下10个点和左右10个点的颜色平均。具体做法是将图像按8*8划分成块,按块的边界做平均。 如下图所示。

                         |                             |
                         |                             |
         块1          |   块2                    |            v0
  v0 v1 v2 v3 v4|v5 v6 v7 v8 v9      |             v1
                         |                             |            v2
                         |                             |            v3
                         |                             |            v4
 ----------------------------------------------------------------
                        |                              |            v5
                        |                              |            v6
                        |                              |            v7
           块3       |        块4                |            v8
                        |                              |            v9
                        |                              |
                        |                              |
 -----------------------------------------------------------------

      每次计算取10个点,首先判断10个点的差别,如果差别很小就用默认模式将v4 v5做个细小的修正,如果差别很大则用DC offset模式修正v1-v8共8个点的值。判别的算法是:
     eq_cnt =  y(v0-v1) + y(v1-v2) + y(v2-v3) + y(v4-v5) + y(v5-v6)
  + y(v6-v7) + y(v7-v8)
 其中if(x<= THR1) y(x) =0  else  y(x) = 1;    //THR1 是一个经验值 1

    if(eq_cnt < THR2)           //THR1 是一个经验值 6
        default mode
    else
        DC offset mode

DC offset模式的规则:
    
    max = MAX(v[1],v[2],v[3],v[4],v[5],v[6],v[7],v[8]);  
    min = MIN(v[1],v[2],v[3],v[4],v[5],v[6],v[7],v[8]);
    if((max - min) < 2* quant)
     颜色平均
    else
        什么也不做

按 1,1,2,2,4,2,2,1,1 的权重平均颜色。比如

s[1] = (uint8_t)((6*p0 +(v[1]<<2)+(v[2]<<1)+(v[3]<<1)+v[4]+v[5]+8)>>4);   
s[2] = (uint8_t)(((p0<<2)+(v[1]<<1)+(v[2]<<2)+(v[3]<<1)+(v[4]<<1)+v[5]+v[6]+8)>>4);
s[3] = (uint8_t)(((p0<<1)+(v[1]<<1)+(v[2]<<1)+(v[3]<<2)+(v[4]<<1)+(v[5]<<1)+v[6]+v[7]+8)>>4);

    类似的计算出s[1]-s[8],然后回写到v[1]-v[8]的位置。

 

计算量分析:
    统计可以知道绝大部分使用了DC offset模式。如果图像尺寸是352*288,只分析亮度分量的计算量。
8*8块竖向分界线有352/8-1 = 43 条 ,每条分界线上有288个点,也就是竖向deblock要做43*288=12384个平均操作。
横向向分界线有288/8-1 = 35 条 ,每条分界线上有352个点,也就是竖向deblock要做35*352=12320个平均操作。
每个平均操作需要执行:
    (1)从内存中读取10个byte, 竖向时10个byte是连续的,横向时每个byte相隔一行的宽度
    (2)计算eq_cnt
    (3)从v[1]-v[8]中找出最大的最小的值
    (4)计算s[1]-s[8]
    (5)将s[1]-s[8]存到v[1]-v[8]

    另外,由于这些操作会修改图像的值,而如果图像是I或P帧,它还要作为后继帧的参考帧,图像数据不能变,所以需要将它拷贝到临时区中处理,这个拷贝也极耗时间。


    天啦,每帧deblock将会大约读两遍帧数据和写一遍帧数据,将比yuv->rgb更恐怖的耗时间。实际的情况真是
令人沮丧,原本优化到32fbs了,加上deblock后直接降到16fbs。


优化分析:
    (1)读取和写入内存会占大部分时间,不过这部分没什么好优化的。
    (2)计算eq_cnt,这部分计算量很小,也不必在意
    (3)从v[1]-v[8]中找出最大的最小的值

max = MAX(s[1],MAX(s[2],MAX(s[3],MAX(s[4],MAX(s[5],MAX(s[6],MAX(s[7],s[8])))))));
min = MIN(s[1],MIN(s[2],MIN(s[3],MIN(s[4],MIN(s[5],MIN(s[6],MIN(s[7],s[8])))))));

xvid给了两种方法计算MAX和MIN

#define MIN(X, Y) ((X)<(Y)?(X):(Y))
#define MAX(X, Y) ((X)>(Y)?(X):(Y))
#define FAST_MAX(x,y) ((x) - ((((x) - (y))>>(32 - 1)) & ((x) - (y))))
#define FAST_MIN(x,y) ((x) + ((((y) - (x))>>(32 - 1)) & ((y) - (x))))
第二种方法耗4条指令。
如果用汇编则只需3条指令 

   mov r1,r2    //    r1 = max(r2,r3)
   cmp r3,r1
   movgt r1,r3

     (4)计算s[1]-s[8],公式中有一些重复的计算,可以先计算重复部分后整体代换。


      这些优化的都是耗时不太大的部分,真正耗时的是内存的存取部分。可以根据图像质量的状况适当减小计算的点数。标准中每次平均计算都取10点写8点,实际可以读8点写6点。