在H264和H265编解码标准中,运动补偿的参考像素可越过参考图像的边界。图像边界之外的参考像素是不存在的,标准使用边界处的像素来填充处于边界之外的像素值。
标准对填充的算法做了如下的描述,如图1所示的两个图,分别描述了H264和H265亮度分量的标准填充算法(H265色度和亮度分量的填充算法是一致的,但是由于H264色度运动补偿和亮度补偿算法不一致,所以H264色度的填充方式和亮度填充方式略有差异。但是总得来说,他们的原理都是用边界值填充不存在的像素值)。虽然这两个图中的变量名不一样,但表示的是同一个意思。
(a).H264填充算法
(b).H265填充算法
图1. 标准填充算法
标准算法在描述填充算法时,是一个一个的像素去判断是否越界,然后再去填充相应的像素值。这种方法的性能较差,会影响编解码效率。
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. 运动补偿
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. 各区域示意图
