FFmpeg H264/H265边界填充一

来源:互联网 发布:深入浅出node.js 编辑:程序博客网 时间:2024/06/04 19:32

在H264和H265编解码标准中,运动补偿的参考像素可越过参考图像的边界。图像边界之外的参考像素是不存在的,标准使用边界处的像素来填充处于边界之外的像素值。

1.  标准填充

标准对填充的算法做了如下的描述,如图1所示的两个图,分别描述了H264和H265亮度分量的标准填充算法(H265色度和亮度分量的填充算法是一致的,但是由于H264色度运动补偿和亮度补偿算法不一致,所以H264色度的填充方式和亮度填充方式略有差异。但是总得来说,他们的原理都是用边界值填充不存在的像素值)。虽然这两个图中的变量名不一样,但表示的是同一个意思。

(a).H264填充算法

(b).H265填充算法

图1. 标准填充算法

       标准算法在描述填充算法时,是一个一个的像素去判断是否越界,然后再去填充相应的像素值。这种方法的性能较差,会影响编解码效率。

2.  FFmpeg填充算法

FFmpeg使用了memcpy的方式,一次填充好几个像素值,该方法可以相对提高填充的效率。图2(a)简要说明了FFmpeg中垂直填充的例子。其中[0,start_y)是超出上边界的部分,这部分像素需要使用start_y处的像素行进行填充;[start_y,end_y]处的像素值是实际存在的,可以直接拷贝;(start_y,bh]处的像素是超出边界下部分的像素值,需要使用边界end_y处的像素值填充。实际中,一般不会出现图2(a)所示的情况,实际中一般出现图2(b)所示的情况,要么是上边界越界,要么是下边界越界,几乎不会出现上下边界都有越界的情况。因为在H264中宏块大小为16×16,而亮度运动补偿为6抽头滤波,如图3所示,所以H264最大只需要21×21的空间来存储当前块所需的参考像素。H265原理与此类似,只是CTU的大小为64,同时使用8抽头滤波,所以最大只需要71×71的空间来存储当前块所需的参考像素。

 

(a)                                  (b)

图2. 垂直填充

图3. 运动补偿

 

3.  FFmpeg源代码分析

void FUNC(ff_emulated_edge_mc)(uint8_t *buf, const uint8_t *src,                               ptrdiff_t buf_linesize,                               ptrdiff_t src_linesize,                               int block_w, int block_h,                               int src_x, int src_y, int w, int h){    int x, y;    int start_y, start_x, end_y, end_x;    if (!w || !h)        return;    av_assert2(block_w * sizeof(pixel) <= FFABS(buf_linesize));    if (src_y >= h) {        src -= src_y * src_linesize;        src += (h - 1) * src_linesize;        src_y = h - 1;    } else if (src_y <= -block_h) {        src -= src_y * src_linesize;        src += (1 - block_h) * src_linesize;        src_y = 1 - block_h;    }    if (src_x >= w) {        src  += (w - 1 - src_x) * sizeof(pixel);        src_x = w - 1;    } else if (src_x <= -block_w) {        src  += (1 - block_w - src_x) * sizeof(pixel);        src_x = 1 - block_w;    }    start_y = FFMAX(0, -src_y);    start_x = FFMAX(0, -src_x);    end_y = FFMIN(block_h, h-src_y);    end_x = FFMIN(block_w, w-src_x);    av_assert2(start_y < end_y && block_h);    av_assert2(start_x < end_x && block_w);    w    = end_x - start_x;    src += start_y * src_linesize + start_x * sizeof(pixel);    buf += start_x * sizeof(pixel);    // top    for (y = 0; y < start_y; y++) {        memcpy(buf, src, w * sizeof(pixel));        buf += buf_linesize;    }    // copy existing part    for (; y < end_y; y++) {        memcpy(buf, src, w * sizeof(pixel));        src += src_linesize;        buf += buf_linesize;    }    // bottom    src -= src_linesize;    for (; y < block_h; y++) {        memcpy(buf, src, w * sizeof(pixel));        buf += buf_linesize;    }    buf -= block_h * buf_linesize + start_x * sizeof(pixel);    while (block_h--) {        pixel *bufp = (pixel *) buf;        // left        for(x = 0; x < start_x; x++) {            bufp[x] = bufp[start_x];        }        // right        for (x = end_x; x < block_w; x++) {            bufp[x] = bufp[end_x - 1];        }        buf += buf_linesize;    }}


ff_emulated_edge_mc函数第15~18行是判断参考的像素块是否超出参考图像的下边界,如图4(a)所示,当前src对应的坐标为(src_x,src_y),第16行代码的含义是将像素的坐标设置为(src_x,0),第17行代码的含义是将像素的坐标设置为(src_x,h-1),也即将当前图像的y坐标指向最后一行像素所在的位置,第18行将y坐标值src_y同步设置为h-1。

第19~22行是判断参考的像素是否超出参考图像的上边界,如图4(b)所示。24~26行是判断参考像素是否超出参考图像的右边界,如图4(c)所示,27~29行判断参考图像是否超出参考图像的左边界,如图4(d)所示。

32~35行根据15~30行代码计算像素垂直拷贝和水平拷贝边界范围。40和41行确定像素拷贝的源地址和目的地址。

 

    

(a). 超出下边界                    (b). 超出上边界

   

(c).超出右边界                       (d).超出左边界

图4. 超出边界的四种特殊情况

 

第44~46行对应4(b)将超出上边界部分用边界处的值填充。第50~53行将未超出边界区域的像素拷贝填充到临时空间中。第57~60行将超出下边界的部分用下边界行的像素值填充。68~70行用左边界填充超出左边界的部分,73~75行用右边界填充超出右边界的部分。如图5所示各区域的示意图,其中5号区域是在边界以内的区域,2区域是超出上边界的部分,4区域是超出左边界的部分,6是超出右边界的部分,8是超出下边界的部分,1是超出上边界和超出左边界的部分,3是超出上边界和右边界的部分,7是超出下边界和左边界的部分,9是超出下边界和右边界的部分,当然,针对某一个特定的宏块或者CTU,不会同时存在这9个区域。其中2号区域将使用2号和5号区域重叠的像素行填充,4号区域将使用4号和5号区域重叠的像素列填充,6号区域将使用6号区域和5号区域重叠的像素列填充,8号区域将使用8号区域和5号区域重叠的像素行填充,1号区域将使用1号区域和5号区域重叠的那一个像素点填充(也就是1号区域右下角和5号区域左上角重叠的那一个像素点填充),同理,3号区域将使用3号区域和5号区域重叠的那一个像素点填充,7号区域将使用7号区域和5号区域重叠的那一个像素点填充,9号区域将使用9号区域和5号区域重叠的那一个像素点填充。

图5. 各区域示意图

1 0
原创粉丝点击