x264源码学习-x264_pixel_init

前言

学习x264代码,本章主要学习x264_pixel_init绑定的相关函,像素比较函数主要使用在h264编码过程中进行像素比较,通过调用像素比较函数获取各个预测下的代价cost,再进行比较,选择代价最小的预测方式。

x264_pixel_init

x264_pixel_init是在x264_encoder_open中调用的,该函数的主要功能是在创建编码器handler的时候根据cpu相关信息绑定对应的像素处理函数,由于整个函数特别长,进行局部分析。首先是几个定义宏。

#define INIT2_NAME( name1, name2, cpu ) \
    pixf->name1[PIXEL_16x16] = x264_pixel_##name2##_16x16##cpu;\
    pixf->name1[PIXEL_16x8]  = x264_pixel_##name2##_16x8##cpu;
#define INIT4_NAME( name1, name2, cpu ) \
    INIT2_NAME( name1, name2, cpu ) \
    pixf->name1[PIXEL_8x16]  = x264_pixel_##name2##_8x16##cpu;\
    pixf->name1[PIXEL_8x8]   = x264_pixel_##name2##_8x8##cpu;
#define INIT5_NAME( name1, name2, cpu ) \
    INIT4_NAME( name1, name2, cpu ) \
    pixf->name1[PIXEL_8x4]   = x264_pixel_##name2##_8x4##cpu;
#define INIT6_NAME( name1, name2, cpu ) \
    INIT5_NAME( name1, name2, cpu ) \
    pixf->name1[PIXEL_4x8]   = x264_pixel_##name2##_4x8##cpu;
#define INIT7_NAME( name1, name2, cpu ) \
    INIT6_NAME( name1, name2, cpu ) \
    pixf->name1[PIXEL_4x4]   = x264_pixel_##name2##_4x4##cpu;
#define INIT8_NAME( name1, name2, cpu ) \
    INIT7_NAME( name1, name2, cpu ) \
    pixf->name1[PIXEL_4x16]  = x264_pixel_##name2##_4x16##cpu;
   
#define INIT2( name, cpu ) INIT2_NAME( name, name, cpu )
#define INIT4( name, cpu ) INIT4_NAME( name, name, cpu )
#define INIT5( name, cpu ) INIT5_NAME( name, name, cpu )
#define INIT6( name, cpu ) INIT6_NAME( name, name, cpu )
#define INIT7( name, cpu ) INIT7_NAME( name, name, cpu )
#define INIT8( name, cpu ) INIT8_NAME( name, name, cpu )

#define INIT_ADS( cpu ) \
    pixf->ads[PIXEL_16x16] = x264_pixel_ads4##cpu;\
    pixf->ads[PIXEL_16x8] = x264_pixel_ads2##cpu;\
    pixf->ads[PIXEL_8x8] = x264_pixel_ads1##cpu;

这几个宏是为了后面方便赋值,根据宏的名字直接对相对应数量的像素处理函数指针进行赋值。后续调用这些宏进行函数绑定。x3和x4是对多个像素块进行比较,应该是用在多参考帧的场景里。

    INIT8( sad, );
    INIT8_NAME( sad_aligned, sad, );
    INIT7( sad_x3, ); // 对比三个像素块的sad函数
    INIT7( sad_x4, );
    INIT8( ssd, );
    INIT8( satd, );
    INIT7( satd_x3, );
    INIT7( satd_x4, );
    INIT4( hadamard_ac, );

x264像素处理函数 - SAD

SAD(Sum of Absolute Difference)即绝对误差和,也被称作SAE(Sum of Absolute Error)。它是一种用于衡量两个块之间相似度的指标,具体计算方法是将两个块对应像素值的差的绝对值求和。SAD仅反映残差时域差异,影响PSNR(峰值信噪比)值,但不能有效反映码流的大小。
SAD计算的是像素差的绝对值之和,因此看函数绑定的宏定义展开

pixf->sad[PIXEL_16x16] = x264_pixel_sad_16x16; 
pixf->sad[PIXEL_16x8] = x264_pixel_sad_16x8; 
pixf->sad[PIXEL_8x16] = x264_pixel_sad_8x16; 
pixf->sad[PIXEL_8x8] = x264_pixel_sad_8x8; 
pixf->sad[PIXEL_8x4] = x264_pixel_sad_8x4; 
pixf->sad[PIXEL_4x8] = x264_pixel_sad_4x8; 
pixf->sad[PIXEL_4x4] = x264_pixel_sad_4x4; 
pixf->sad[PIXEL_4x16] = x264_pixel_sad_4x16;

sad的c语言函数由这个宏展开可以看到处理方式是差的绝对值之和,同理sad_x3和sad_x4是将3个和4个像素的sad计算后复制到入参score中

PIXEL_SAD_C( x264_pixel_sad_16x16, 16, 16 )
PIXEL_SAD_C( x264_pixel_sad_16x8,  16,  8 )
PIXEL_SAD_C( x264_pixel_sad_8x16,   8, 16 )
...
static int x264_pixel_sad_16x16(pixel *pix1, intptr_t i_stride_pix1, pixel *pix2, intptr_t i_stride_pix2)
{
    int i_sum = 0;
    for (int y = 0; y < 16; y++)
    {
        for (int x = 0; x < 16; x++)
        {
            i_sum += abs(pix1[x] - pix2[x]);
        }
        pix1 += i_stride_pix1;
        pix2 += i_stride_pix2;
    }
    return i_sum;
}

#define SAD_X( size ) \
static void x264_pixel_sad_x3_##size( pixel *fenc, pixel *pix0, pixel *pix1, pixel *pix2,\
                                      intptr_t i_stride, int scores[3] )\
{\
    scores[0] = x264_pixel_sad_##size( fenc, FENC_STRIDE, pix0, i_stride );\
    scores[1] = x264_pixel_sad_##size( fenc, FENC_STRIDE, pix1, i_stride );\
    scores[2] = x264_pixel_sad_##size( fenc, FENC_STRIDE, pix2, i_stride );\
}\
static void x264_pixel_sad_x4_##size( pixel *fenc, pixel *pix0, pixel *pix1,pixel *pix2, pixel *pix3,\
                                      intptr_t i_stride, int scores[4] )\
{\
    scores[0] = x264_pixel_sad_##size( fenc, FENC_STRIDE, pix0, i_stride );\
    scores[1] = x264_pixel_sad_##size( fenc, FENC_STRIDE, pix1, i_stride );\
    scores[2] = x264_pixel_sad_##size( fenc, FENC_STRIDE, pix2, i_stride );\
    scores[3] = x264_pixel_sad_##size( fenc, FENC_STRIDE, pix3, i_stride );\
}

x264像素处理函数 - SSD

SSD(Sum of Squared Difference)即差值的平方和。它是衡量重构图像(或称为重建块)与原始图像之间差异的一种方法。具体来说,SSD通过计算重构图像中的每个像素值与原始图像中对应像素值之差的平方,并将这些平方值相加得到。简言之SSD计算的是像素差的平方和。结合代码可以看到相关SSD的宏。

/****************************************************************************
 * pixel_ssd_WxH
 ****************************************************************************/
#define PIXEL_SSD_C( name, lx, ly ) \
static int name( pixel *pix1, intptr_t i_stride_pix1,  \
                 pixel *pix2, intptr_t i_stride_pix2 ) \
{                                                   \
    int i_sum = 0;                                  \
    for( int y = 0; y < ly; y++ )                   \
    {                                               \
        for( int x = 0; x < lx; x++ )               \
        {                                           \
            int d = pix1[x] - pix2[x];              \
            i_sum += d*d;                           \
        }                                           \
        pix1 += i_stride_pix1;                      \
        pix2 += i_stride_pix2;                      \
    }                                               \
    return i_sum;                                   \
}

x264像素处理函数 - SATD

SATD(Sum of Absolute Transformed Difference)即hadamard变换后再绝对值求和,是一种视频编码失真测度,可以理解为简单的视频变换,不涉及乘法,用于评估预测残差的大小。
SATD处理函数是个迭代的过程,首先调用的是4x4的SATD处理函数,整个函数的处理流程是对4x4的两个块的差进行hadamard变换
4x4像素块的hadamard变换过程:

  1. 确定变换矩阵H
int hadamard_4x4_matrix[4][4] = {
    {1, 1, 1, 1},
    {1, -1, 1, -1},
    {1, 1, -1, -1},
    {1, -1, -1, 1}
};
  1. 将目标4x4像素块进行矩阵计算,计算公式是H x 像素矩阵 x H * 1/2,其中H是hadamard变换矩阵,像素矩阵就是目标像素矩阵
  2. 将计算后得到的4x4矩阵的每个元素进行绝对值求和

整体hadamard变换的python代码如下

import numpy as np

# 定义 4x4 哈达玛变换矩阵 H_4
H_4 = np.array([
    [1, 1, 1, 1],
    [1, -1, 1, -1],
    [1, 1, -1, -1],
    [1, -1, -1, 1]
])

def hadamard_transform(matrix):
    """
    对 4x4 矩阵进行哈达玛变换
    :param matrix: 4x4 矩阵
    :return: 经过哈达玛变换后的 4x4 矩阵
    """
    print(matrix)
    print(np.dot(H_4, matrix))
    print(np.dot(np.dot(H_4, matrix), H_4))
    return np.dot(np.dot(H_4, matrix), H_4) / 2

def calculate_satd(matrix1, matrix2):
    """
    计算两个 4x4 像素矩阵的 SATD 值
    :param matrix1: 第一个 4x4 像素矩阵
    :param matrix2: 第二个 4x4 像素矩阵
    :return: SATD 值
    """
    # 检查输入矩阵是否为 4x4
    if matrix1.shape != (4, 4) or matrix2.shape != (4, 4):
        raise ValueError("输入矩阵必须是 4x4 矩阵")
    
    # 计算差值矩阵
    diff_matrix = matrix1 - matrix2
    
    # 对差值矩阵进行哈达玛变换
    transformed_matrix = hadamard_transform(diff_matrix)
    
    # 计算变换后矩阵元素的绝对值之和
    satd = np.sum(np.abs(transformed_matrix))
    
    return satd


# 示例 4x4 像素矩阵
# matrix1 = np.array([
#     [   0,    1,    2,    3 ],
#     [   4,    5,    6,    7 ],
#     [   8,    9,   10,   11 ],
#     [  12,   13,   14,   15 ],
# ])

matrix1 = np.array([
    [   1,    1,    1,      1 ],
    [   1,    1,    1,      1 ],
    [   1,    1,    1,      1 ],
    [   1,    1,    1,      1 ],
])

matrix2 = np.array([
    [   0,    2,    4,    6 ],
    [   8,   10,   12,   14 ],
    [  16,   18,   20,   22 ],
    [  24,   26,   28,   30 ],
])



# 计算 SATD 值
satd_value = calculate_satd(matrix1, matrix2)
print(f"SATD 值: {satd_value}")

x264中对satd的计算采用了快速蝶形算法,简化了操作,并且移除了系数相乘的步骤,satd是计算量更大的像素比较函数,但更贴近于DCT变换后的结果。

static NOINLINE int x264_pixel_satd_4x4( pixel *pix1, intptr_t i_pix1, pixel *pix2, intptr_t i_pix2 )
{
    sum2_t tmp[4][2];
    sum2_t a0, a1, a2, a3, b0, b1;
    sum2_t sum = 0;
	
    for( int i = 0; i < 4; i++, pix1 += i_pix1, pix2 += i_pix2 )
    {
        a0 = (sum2_t)(pix1[0] - pix2[0]);
        a1 = (sum2_t)(pix1[1] - pix2[1]);
        b0 = (a0+a1) + ((a0-a1)<<BITS_PER_SUM);
        a2 = (sum2_t)(pix1[2] - pix2[2]);
        a3 = (sum2_t)(pix1[3] - pix2[3]);
        b1 = (a2+a3) + ((a2-a3)<<BITS_PER_SUM);
        tmp[i][0] = b0 + b1;
        tmp[i][1] = b0 - b1;
    }
    for( int i = 0; i < 2; i++ )
    {
        HADAMARD4( a0, a1, a2, a3, tmp[0][i], tmp[1][i], tmp[2][i], tmp[3][i] );
        a0 = abs2(a0) + abs2(a1) + abs2(a2) + abs2(a3);
        sum += ((sum_t)a0) + (a0>>BITS_PER_SUM);
    }
    return sum >> 1;
}

对宏HADAMARD4展开后结果如下

{
    sum2_t t0 = tmp[0][i] + tmp[1][i];
    sum2_t t1 = tmp[0][i] - tmp[1][i];
    sum2_t t2 = tmp[2][i] + tmp[3][i];
    sum2_t t3 = tmp[2][i] - tmp[3][i];
    a0 = t0 + t2;
    a2 = t0 - t2;
    a1 = t1 + t3;
    a3 = t1 - t3;
}
获取到矩阵计算结果后,对结果进行平方求和,最终结果因为hadamard变换会放大2倍系数,再除2。
其他宏块大小的SATD计算方式都是基于4x4的基础上进行计算的。

## pixel_hadamard_ac
该函数是对8x8宏块的进行hadamard变换,和求差的hadamard变换进行对比发现该ac变换是对宏块自身进行计算
```c
static NOINLINE uint64_t pixel_hadamard_ac( pixel *pix, intptr_t stride )
{
    sum2_t tmp[32];
    sum2_t a0, a1, a2, a3, dc;
    sum2_t sum4 = 0, sum8 = 0;

	// 矩阵初变换,对8x8的每4x4的矩阵进行初变换
    for( int i = 0; i < 8; i++, pix+=stride )
    {
        sum2_t *t = tmp + (i&3) + (i&4)*4;
        a0 = (pix[0]+pix[1]) + ((sum2_t)(pix[0]-pix[1])<<BITS_PER_SUM);
        a1 = (pix[2]+pix[3]) + ((sum2_t)(pix[2]-pix[3])<<BITS_PER_SUM);
        t[0] = a0 + a1;
        t[4] = a0 - a1;
        a2 = (pix[4]+pix[5]) + ((sum2_t)(pix[4]-pix[5])<<BITS_PER_SUM);
        a3 = (pix[6]+pix[7]) + ((sum2_t)(pix[6]-pix[7])<<BITS_PER_SUM);
        t[8] = a2 + a3;
        t[12] = a2 - a3;
    }

	// 对初变换后的矩阵进行一次变换
    for( int i = 0; i < 8; i++ )
    {
        HADAMARD4( a0, a1, a2, a3, tmp[i*4+0], tmp[i*4+1], tmp[i*4+2], tmp[i*4+3] );
        tmp[i*4+0] = a0;
        tmp[i*4+1] = a1;
        tmp[i*4+2] = a2;
        tmp[i*4+3] = a3;
        sum4 += abs2(a0) + abs2(a1) + abs2(a2) + abs2(a3);
    }

	// 进行二次变换
    for( int i = 0; i < 8; i++ )
    {
        HADAMARD4( a0,a1,a2,a3, tmp[i], tmp[8+i], tmp[16+i], tmp[24+i] );
        sum8 += abs2(a0) + abs2(a1) + abs2(a2) + abs2(a3);
    }
    dc = (sum_t)(tmp[0] + tmp[8] + tmp[16] + tmp[24]);
    sum4 = (sum_t)sum4 + (sum4>>BITS_PER_SUM) - dc;
    sum8 = (sum_t)sum8 + (sum8>>BITS_PER_SUM) - dc;
    return ((uint64_t)sum8<<32) + sum4;
}

x264像素处理函数 - ADS

ADS 绝对差值和(ADS,Absolute Difference Sum)来决定哪些运动向量低于某个阈值。根据代码判断应该是用在计算运动矢量的绝对差值合。通过将enc_dc和sums进行绝对差值合计算,如果在阈值内将其记录到mvs数组中,具体该函数的使用需要结合后续代码分析。

static int x264_pixel_ads1( int enc_dc[1], uint16_t *sums, int delta,
                            uint16_t *cost_mvx, int16_t *mvs, int width, int thresh )
{
    int nmv = 0;
    for( int i = 0; i<width; i++, sums++ )
    {
        int ads = abs( enc_dc[0] - sums[0] )
                + cost_mvx[i];
        if( ads < thresh )
            mvs[nmv++] = i;
    }
    return nmv;
}

x264像素处理函数pixel_var_16x16

函数绑定的地方

    pixf->var[PIXEL_16x16] = pixel_var_16x16;
    pixf->var[PIXEL_8x16]  = pixel_var_8x16;
    pixf->var[PIXEL_8x8]   = pixel_var_8x8;
    pixf->var2[PIXEL_8x16]  = pixel_var2_8x16;
    pixf->var2[PIXEL_8x8]   = pixel_var2_8x8;

将定义的宏展开以后,可以看到该函数是将像素块中每个像素计算和以及平方和,并将平方和作为高32位和作为低32位返回

static uint64_t pixel_var_16x16(pixel *pix, intptr_t i_stride)
{
    uint32_t sum = 0, sqr = 0;
    for (int y = 0; y < 16; y++)
    {
        for (int x = 0; x < 16; x++)
        {
            sum += pix[x];
            sqr += pix[x] * pix[x];
        }
        pix += i_stride;
    }
    return sum + ((uint64_t)sqr << 32);
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值