对于Dither_convert_8_to_16().Dither_convey_yuv4xxp16_on_yvxx()输出的16bit,LSB全是0,这是一个特殊的情况,也就是说不管是直接右移6位还是使用error diffusion dither方法转换到10bit均不会产生精度损失,也不会产生banding,两者结果应该是一致的(或者说两者应该是等价的?)
现在看一下x264里面的dither算法
注意我标红的那行,对于我们输入的LSB全是0的16bit数据,此时计算出来的量化误差却不等于0?!65 #define DITHER_PLANE( pitch ) \
66 static void dither_plane_##pitch( pixel *dst, int dst_stride, uint16_t *src, int src_stride, \
67 int width, int height, int16_t *errors ) \
68 { \
69 const int lshift = 16-BIT_DEPTH; \
70 const int rshift = 2*BIT_DEPTH-16; \
71 const int pixel_max = (1 << BIT_DEPTH)-1; \
72 const int half = 1 << (16-BIT_DEPTH); \
73 memset( errors, 0, (width+1) * sizeof(int16_t) ); \
74 for( int y = 0; y < height; y++, src += src_stride, dst += dst_stride ) \
75 { \
76 int err = 0; \
77 for( int x = 0; x < width; x++ ) \
78 { \
79 err = err*2 + errors[x] + errors[x+1]; \
80 dst[x*pitch] = x264_clip3( (((src[x*pitch]+half)<<2)+err)*pixel_max >> 18, 0, pixel_max ); \
81 errors[x] = err = src[x*pitch] - (dst[x*pitch] << lshift) - (dst[x*pitch] >> rshift); \
82 } \
83 } \
84 }
我们分析下err = src[x*pitch] - (dst[x*pitch] << lshift) - (dst[x*pitch] >> rshift)这个运算
对于输入的某个像素点,假定其值为235*2^8,右移6位转换到10bit应该是235*2^2,由于没有精度损失,也不会产生banding,所以应该没有量化误差,也就是说基于error diffusion dither方法,计算该点的量化误差应该是0
我们把235*2^8和235*2^2代入上面那个式子
err = 235*2^8 - ( 235*2^2*2^6 ) - ( 235*2^2/2^4) = -58
看出问题了吗?原本应该是0的量化误差,按照这个error diffusion dither算法算出来却是-58
这个式子的问题在于它将16bit数值量化后的10bit数值,通过(dst[x*pitch] << lshift) + (dst[x*pitch] >> rshift)再次转换到16bit,再计算和最初16bit数值差值。但是我们输入的16bit数据是通过8bit数据左移8位得到的,如果我们肯定了“8bit数据左移2位得到10bit数据”的做法是正确的,那么10bit数据到16bit数据的转换应该是左移6位。
所以我认为err = src[x*pitch] - (dst[x*pitch] << lshift) - (dst[x*pitch] >> rshift);应该改成err = src[x*pitch] - (dst[x*pitch] << lshift);
这样err = 235*2^8 - ( 235*2^2*2^6 ) = 0,符合量化误差等于0
“这样修改过后的error diffusion dither算法将LSB全是0的16bit数据转换到10bit”和”将LSB全是0的16bit数据直接右移6位转换到10bit”是等价的
当然我上面也说过了,即使不修改这个dither算法,最终结果的误差也是很小的,肉眼可能无法察觉,对于某些数据可能最终结果根本没有误差
至于16bit输入x264时到底有没有调用这个dither函数,各位可以用printf大法验证