源码阅读 x264 - 帧内预测

本文分析 x264 库中的帧内预测的 C 语言函数。

16x16 预测

x264 代码通过 x264_predict_16x16_init() 函数初始化 Intra16x16 帧内预测汇编函数,初始化的预测模式如下:

intra16x16Predmode Name of Intra16x16PredMode Note Function
0 Intra_16x16_Vertical 由上边像素推出相应像素值 x264_predict_16x16_v_c
1 Intra_16x16_Horicontal 由左边像素推出相应像素值 x264_predict_16x16_h_c
2 Intra_16x16_DC 由上边和左边像素平均值推出相应像素值 x264_predict_16x16_dc_c
3 Intra_16x16_Plane 利用线性 plan 函数及左、上像素推出相应像素值,适用于亮度变化平缓区域 x264_predict_16x16_p_c

Intra_16x16_Vertical

在 SPEC 中,关于该预测模式的定义如下:

Intra_16x16_Vertical

x264 中关于模式 Intra_4x4_Vertical 的代码如下:

/* common/predict.c */
void x264_predict_16x16_v_c(pixel *src) {
    // pixel4 实际上是 uint32_t(占用 32bit),存储 4 个像素的值(每个像素占用 8bit)
    pixel4 v0 = MPIXEL_X4(&src[ 0 - FDEC_STRIDE]);
    pixel4 v1 = MPIXEL_X4(&src[ 4 - FDEC_STRIDE]);
    pixel4 v2 = MPIXEL_X4(&src[ 8 - FDEC_STRIDE]);
    pixel4 v3 = MPIXEL_X4(&src[12 - FDEC_STRIDE]);

    /*
     * Vertical 预测方式
     *   |X1 X2 X3 X4
     * --+-----------
     *   |X1 X2 X3 X4
     *   |X1 X2 X3 X4
     *   |X1 X2 X3 X4
     *   |X1 X2 X3 X4
     *
     * 展开宏定义如下:
     * uint32_t v0 = ((x264_union32_t*)(&src[ 0-FDEC_STRIDE]))->i;
     * uint32_t v1 = ((x264_union32_t*)(&src[ 4-FDEC_STRIDE]))->i;
     * uint32_t v2 = ((x264_union32_t*)(&src[ 8-FDEC_STRIDE]))->i;
     * uint32_t v3 = ((x264_union32_t*)(&src[12-FDEC_STRIDE]))->i;
     * x264_union32_t 的定义如下:
     * typedef union {uint64_t i; uint32_t d[2]; uint16_t w[4]; uint8_t b[8]; } MAY_ALIAS x264_union64_t;
     * 即将一行 16 字节数据分成 4 次,每次取出 4 个像素(一共 16 个像素),分别赋值给 v0,v1,v2,v3
     * 取出的值源自于 16x16 块上面的一行像素
     *    0|          4          8          12         16
     *    ||    v0    |    v1    |    v2    |    v3    |
     * ---++==========+==========+==========+==========+
     *    ||
     *    ||
     */

    for (int i = 0; i < 16; i++) {
        MPIXEL_X4(src +  0) = v0;
        MPIXEL_X4(src +  4) = v1;
        MPIXEL_X4(src +  8) = v2;
        MPIXEL_X4(src + 12) = v3;
    /* 展开宏定义如下:
     * ((x264_union32_t*)(src +  0))->i = v0;
     * ((x264_union32_t*)(src +  4))->i = v1;
     * ((x264_union32_t*)(src +  8))->i = v2;
     * ((x264_union32_t*)(src + 12))->i = v3;
     * 即分成 4 次,每次赋值 4 个像素
     */
        src += FDEC_STRIDE;
    }
}

Intra_16x16_Horicontal

在 SPEC 中,关于该预测模式的定义如下:

Intra_16x16_Horicontal

x264 中关于模式 Intra_16x16_Horicontal 的代码如下:

#define PIXEL_SPLAT_X4(x) ((x)*0x01010101U)

void x264_predict_16x16_h_c( pixel *src ) {
    for ( int i = 0; i < 16; i++ ) {
        const pixel4 v = PIXEL_SPLAT_X4( src[-1] );
        MPIXEL_X4( src+ 0 ) = v;
        MPIXEL_X4( src+ 4 ) = v;
        MPIXEL_X4( src+ 8 ) = v;
        MPIXEL_X4( src+12 ) = v;
        /* 展开宏定义如下:
         * uint32_t v = src[-1] * 0x01010101U;
         * ((x264_union32_t*)(src +  0))->i = v;
         * ((x264_union32_t*)(src +  4))->i = v;
         * ((x264_union32_t*)(src +  8))->i = v;
         * ((x264_union32_t*)(src + 12))->i = v;
         */
        src += FDEC_STRIDE;
    }
}

Intra_16x16_DC

在 SPEC 中,关于该预测模式的定义如下:

Intra_16x16_DC

x264 中关于模式 Intra_16x16_DC 的代码如下:

#define PREDICT_16x16_DC(v)\
    for( int i = 0; i < 16; i++ )\
    {\
        MPIXEL_X4( src+ 0 ) = v;\
        MPIXEL_X4( src+ 4 ) = v;\
        MPIXEL_X4( src+ 8 ) = v;\
        MPIXEL_X4( src+12 ) = v;\
        src += FDEC_STRIDE;\
    }

void x264_predict_16x16_dc_c( pixel *src ) {
    int dc = 0;

    for( int i = 0; i < 16; i++ ) {
        dc += src[-1 + i * FDEC_STRIDE];
        dc += src[i - FDEC_STRIDE];
    }
    pixel4 dcsplat = PIXEL_SPLAT_X4( ( dc + 16 ) >> 5 );
    PREDICT_16x16_DC( dcsplat );
    /* 展开宏定义如下:
     * uint32_t dcsplat = ((dc + 16) >> 5) * 0x01010101U;
     * for ( int i = 0; i < 16; i++ ) {
     *     ((x264_union32_t*)(src +  0))->i = v;
     *     ((x264_union32_t*)(src +  4))->i = v;
     *     ((x264_union32_t*)(src +  8))->i = v;
     *     ((x264_union32_t*)(src + 12))->i = v;
     *     src += FDEC_STRIDE;
     * }
     */
}

Intra_16x16_Plane

在 SPEC 中,关于该预测模式的定义如下:

Intra_16x16_Plane

x264 中关于模式 Intra_16x16_Plane 的代码如下:

#define PIXEL_MAX 255
static ALWAYS_INLINE pixel x264_clip_pixel(int x) {
    return ((x & ~PIXEL_MAX) ? (-x)>>31 & PIXEL_MAX : x );
}

void x264_predict_16x16_p_c(pixel *src) {
    int H = 0, V = 0;

    /* calculate H and V */
    for (int i = 0; i <= 7; i++) {
        H += (i + 1) * ( src[ 8 + i - FDEC_STRIDE ] - src[6 - i - FDEC_STRIDE] );
        V += (i + 1) * ( src[-1 + (8+i) * FDEC_STRIDE] - src[-1 + (6-i) * FDEC_STRIDE] );
    }

    int a = 16 * (src[-1 + 15*FDEC_STRIDE] + src[15 - FDEC_STRIDE] );
    int b = (5 * H + 32) >> 6;
    int c = (5 * V + 32) >> 6;

    int i00 = a - b * 7 - c * 7 + 16;

    for (int y = 0; y < 16; y++) {
        int pix = i00;
        for (int x = 0; x < 16; x++) {
            src[x] = x264_clip_pixel( pix >> 5 );
            pix += b;
        }
        src += FDEC_STRIDE;
        i00 += c;
    }
}

4x4 预测

x264 中对 4x4 的预测模式如下:

Intra4x4PredMode[luma4x4BlkIdx] Name of Intra4x4PredMode[luma4x4BlkIdx] x264 Function
0 Intra_4x4_Vertical x264_predict_4x4_v_c()
1 Intra_4x4_Horizontal x264_predict_4x4_h_c()
2 Intra_4x4_DC x264_predict_4x4_dc_c()
3 Intra_4x4_Diagonal_Down_Left predict_4x4_ddl_c()
4 Intra_4x4_Diagonal_Down_Right predict_4x4_ddr_c()
5 Intra_4x4_Vertical_Right predict_4x4_vr_c()
6 Intra_4x4_Horizontal_Down predict_4x4_hd_c()
7 Intra_4x4_Vertical_Left predict_4x4_vl_c()
8 Intra_4x4_Horizontal_Up predict_4x4_hu_c()

Intra_4x4_Vertical

在 SPEC 中,关于该预测模式的定义如下:

Intra_4x4_Vertical

x264 中关于模式 Intra_4x4_Vertical 的代码如下:

#define SRC(x,y) src[(x)+(y)*FDEC_STRIDE]
#define SRC_X4(x,y) MPIXEL_X4( &SRC(x,y) )
#define PREDICT_4x4_DC(v)\
    SRC_X4(0,0) = SRC_X4(0,1) = SRC_X4(0,2) = SRC_X4(0,3) = v;

void x264_predict_4x4_v_c(pixel *src) {
    PREDICT_4x4_DC(SRC_X4(0,-1));
    /* 展开宏定义如下:
     * ((x264_union32_t*)(&src[0+0*FDEC_STRIDE])->i = \
     * ((x264_union32_t*)(&src[0+1*FDEC_STRIDE])->i = \
     * ((x264_union32_t*)(&src[0+2*FDEC_STRIDE])->i = \
     * ((x264_union32_t*)(&src[0+3*FDEC_STRIDE])->i = ((x264_union32_t*)(&src[0+(-1)*FDEC_STRIDE])->i;
     */
}

Intra_4x4_Horizontal

在 SPEC 中,关于该预测模式的定义如下:

Intra_4x4_Horizontal

x264 中关于模式 Intra_4x4_Horizontal 的代码如下:

#define M32(src) (((x264_union32_t*)(src))->i)
#define MPIXEL_X4(src) M32(src)

#define SRC(x,y) src[(x)+(y)*FDEC_STRIDE]
#define SRC_X4(x,y) MPIXEL_X4( &SRC(x,y) )

#define PIXEL_SPLAT_X4(x) ((x)*0x01010101U)

void x264_predict_4x4_h_c(pixel *src) {
    SRC_X4(0,0) = PIXEL_SPLAT_X4( SRC(-1,0) );
    SRC_X4(0,1) = PIXEL_SPLAT_X4( SRC(-1,1) );
    SRC_X4(0,2) = PIXEL_SPLAT_X4( SRC(-1,2) );
    SRC_X4(0,3) = PIXEL_SPLAT_X4( SRC(-1,3) );
    /* 展开宏定义如下:
     * ((x264_union32_t*)(&src[0+0*FDEC_STRIDE])->i = src[-1+0*FDEC_STRIDE]*0x01010101U;
     * ((x264_union32_t*)(&src[0+1*FDEC_STRIDE])->i = src[-1+1*FDEC_STRIDE]*0x01010101U;
     * ((x264_union32_t*)(&src[0+2*FDEC_STRIDE])->i = src[-1+2*FDEC_STRIDE]*0x01010101U;
     * ((x264_union32_t*)(&src[0+3*FDEC_STRIDE])->i = src[-1+3*FDEC_STRIDE]*0x01010101U;
     */
}

Intra_4x4_DC

在 SPEC 中,关于该预测模式的定义如下:

Intra_4x4_DC

x264 中关于模式 Intra_4x4_DC 的代码如下:

typedef uint32_t pixel4;
#define PREDICT_4x4_DC(v) SRC_X4(0,0) = SRC_X4(0,1) = SRC_X4(0,2) = SRC_X4(0,3) = v;

void x264_predict_4x4_dc_c(pixel *src) {
    pixel4 dc = PIXEL_SPLAT_X4((SRC(-1,0) + SRC(-1,1) + SRC(-1,2) + SRC(-1,3) +
                                 SRC(0,-1) + SRC(1,-1) + SRC(2,-1) + SRC(3,-1) + 4) >> 3 );
    PREDICT_4x4_DC(dc);
    /* 展开宏定义如下:
     * uint32_t dc = (( src[-1+0*FDEC_STRIDE] + src[-1+1*FDEC_STRIDE] +
                        src[-1+2*FDEC_STRIDE] + src[-1+3*FDEC_STRIDE] +
                        src[0+(-1)*FDEC_STRIDE] + src[1+(-1)*FDEC_STRIDE] +
                        src[2+(-1)*FDEC_STRIDE] + src[3+(-1)*FDEC_STRIDE] ) >> 3 ) * 0x01010101U;
     * ((x264_union32_t*)(&src[(0)+(0)*FDEC_STRIDE])->i = \
     * ((x264_union32_t*)(&src[(0)+(1)*FDEC_STRIDE])->i = \
     * ((x264_union32_t*)(&src[(0)+(2)*FDEC_STRIDE])->i = \
     * ((x264_union32_t*)(&src[(0)+(3)*FDEC_STRIDE])->i = dc;
     */
}

Intra_4x4_Diagonal_Down_Left

在 SPEC 中,关于该预测模式的定义如下:

Intra_4x4_Diagonal_Down_Left

x264 中关于模式 Intra_4x4_Diagonal_Down_Left 的代码如下:

#define PREDICT_4x4_LOAD_TOP\
    int t0 = SRC(0,-1);\
    int t1 = SRC(1,-1);\
    int t2 = SRC(2,-1);\
    UNUSED int t3 = SRC(3,-1);
#define PREDICT_4x4_LOAD_TOP_RIGHT\
    int t4 = SRC(4,-1);\
    int t5 = SRC(5,-1);\
    int t6 = SRC(6,-1);\
    UNUSED int t7 = SRC(7,-1);
#define F2(a,b,c) (((a)+2*(b)+(c)+2)>>2)

static void predict_4x4_ddl_c(pixel *src) {
    PREDICT_4x4_LOAD_TOP
    PREDICT_4x4_LOAD_TOP_RIGHT
    SRC(0,0)= F2(t0,t1,t2);
    SRC(1,0)=SRC(0,1)= F2(t1,t2,t3);
    SRC(2,0)=SRC(1,1)=SRC(0,2)= F2(t2,t3,t4);
    SRC(3,0)=SRC(2,1)=SRC(1,2)=SRC(0,3)= F2(t3,t4,t5);
    SRC(3,1)=SRC(2,2)=SRC(1,3)= F2(t4,t5,t6);
    SRC(3,2)=SRC(2,3)= F2(t5,t6,t7);
    SRC(3,3)= F2(t6,t7,t7);
    /* 展开宏定义如下:
     * int t0 = src[0+(-1)*FDEC_STRIDE];
     * int t1 = src[1+(-1)*FDEC_STRIDE];
     * int t2 = src[2+(-1)*FDEC_STRIDE];
     * int t3 = src[3+(-1)*FDEC_STRIDE];
     * int t4 = src[4+(-1)*FDEC_STRIDE];
     * int t5 = src[5+(-1)*FDEC_STRIDE];
     * int t6 = src[6+(-1)*FDEC_STRIDE];
     * int t7 = src[7+(-1)*FDEC_STRIDE];
     * src[0+0*FDEC_STRIDE] = (t0+2*t1+t2+2) >> 2;
     * src[1+0*FDEC_STRIDE] = src[0+1*FDEC_STRIDE] = (t1+2*t2+t3+2) >> 2;
     * src[2+0*FDEC_STRIDE] = src[1+1*FDEC_STRIDE] = src[0+2*FDEC_STRIDE] = (t2+2*t3+t4+2) >> 2;
     * src[3+0*FDEC_STRIDE] = src[2+1*FDEC_STRIDE] = src[1+2*FDEC_STRIDE] = src[0+3*FDEC_STRIDE] = (t3+2*t4+t5+2) >> 2;
     * src[3+1*FDEC_STRIDE] = src[2+2*FDEC_STRIDE] = src[1+3*FDEC_STRIDE] = (t4+2*t5+t6+2) >> 2;
     * src[3+2*FDEC_STRIDE] = src[2+3*FDEC_STRIDE] = (t5+2*t6+t7+2) >> 2;
     * src[3+3*FDEC_STRIDE] = (t6+2*t7+t7+2) >> 2;
     */
}

Intra_4x4_Diagonal_Down_Right

在 SPEC 中,关于该预测模式的定义如下:

Intra_4x4_Diagonal_Down_Right

x264 中关于模式 Intra_4x4_Diagonal_Down_Right 的代码如下:

#define PREDICT_4x4_LOAD_LEFT\
    int l0 = SRC(-1,0);\
    int l1 = SRC(-1,1);\
    int l2 = SRC(-1,2);\
    UNUSED int l3 = SRC(-1,3);

static void predict_4x4_ddr_c( pixel *src ) {
    int lt = SRC(-1,-1);
    PREDICT_4x4_LOAD_LEFT
    PREDICT_4x4_LOAD_TOP
    SRC(3,0)= F2(t3,t2,t1);
    SRC(2,0)=SRC(3,1)= F2(t2,t1,t0);
    SRC(1,0)=SRC(2,1)=SRC(3,2)= F2(t1,t0,lt);
    SRC(0,0)=SRC(1,1)=SRC(2,2)=SRC(3,3)= F2(t0,lt,l0);
    SRC(0,1)=SRC(1,2)=SRC(2,3)= F2(lt,l0,l1);
    SRC(0,2)=SRC(1,3)= F2(l0,l1,l2);
    SRC(0,3)= F2(l1,l2,l3);
    /* 展开宏定义如下,后续类似代码不再进行宏定义展开:
     * int lt = src[-1+(-1)*FDEC_STRIDE];
     * int l0 = src[-1+0*FDEC_STRIDE];
     * int l1 = src[-1+1*FDEC_STRIDE];
     * int l2 = src[-1+2*FDEC_STRIDE];
     * int t0 = src[0+(-1)*FDEC_STRIDE];
     * int t1 = src[1+(-1)*FDEC_STRIDE];
     * int t2 = src[2+(-1)*FDEC_STRIDE];
     * int t3 = src[3+(-1)*FDEC_STRIDE];
     * src[3+0*FDEC_STRIDE] = (t3+2*t2+t1+2) >> 2;
     * src[2+0*FDEC_STRIDE] = src[3+1*FDEC_STRIDE] = (t2+2*t1+t0+2) >> 2;
     * src[1+0*FDEC_STRIDE] = src[2+1*FDEC_STRIDE] = src[3+2*FDEC_STRIDE] = (t1+2*t0+lt+2) >> 2;
     * src[0+0*FDEC_STRIDE] = src[1+1*FDEC_STRIDE] = src[2+2*FDEC_STRIDE] = src[3+3*FDEC_STRIDE] = (t0+2*lt+l0+2) >> 2;
     * src[0+1*FDEC_STRIDE] = src[1+2*FDEC_STRIDE] = src[2+3*FDEC_STRIDE] = (lt+2*l0+l1+2) >> 2;
     * src[0+2*FDEC_STRIDE] = src[1+3*FDEC_STRIDE] = (l0+2*l1+l2+2) >> 2;
     * src[0+3*FDEC_STRIDE] = (l1+2*l2+l3+2) >> 2;
     */
}

Intra_4x4_Vertical_Right

在 SPEC 中,关于该预测模式的定义如下:

Intra_4x4_Vertical_Right

x264 中关于模式 Intra_4x4_Vertical_Right 的代码如下:

static void predict_4x4_vr_c( pixel *src ) {
    int lt = SRC(-1,-1);
    PREDICT_4x4_LOAD_LEFT
    PREDICT_4x4_LOAD_TOP
    SRC(0,3)= F2(l2,l1,l0);
    SRC(0,2)= F2(l1,l0,lt);
    SRC(0,1)=SRC(1,3)= F2(l0,lt,t0);
    SRC(0,0)=SRC(1,2)= F1(lt,t0);
    SRC(1,1)=SRC(2,3)= F2(lt,t0,t1);
    SRC(1,0)=SRC(2,2)= F1(t0,t1);
    SRC(2,1)=SRC(3,3)= F2(t0,t1,t2);
    SRC(2,0)=SRC(3,2)= F1(t1,t2);
    SRC(3,1)= F2(t1,t2,t3);
    SRC(3,0)= F1(t2,t3);
}

Intra_4x4_Horizontal_Down

在 SPEC 中,关于该预测模式的定义如下:

Intra_4x4_Horizontal_Down

x264 中关于模式 Intra_4x4_Horizontal_Down 的代码如下:

static void predict_4x4_hd_c( pixel *src ) {
    int lt= SRC(-1,-1);
    PREDICT_4x4_LOAD_LEFT
    PREDICT_4x4_LOAD_TOP
    SRC(0,3)= F1(l2,l3);
    SRC(1,3)= F2(l1,l2,l3);
    SRC(0,2)=SRC(2,3)= F1(l1,l2);
    SRC(1,2)=SRC(3,3)= F2(l0,l1,l2);
    SRC(0,1)=SRC(2,2)= F1(l0,l1);
    SRC(1,1)=SRC(3,2)= F2(lt,l0,l1);
    SRC(0,0)=SRC(2,1)= F1(lt,l0);
    SRC(1,0)=SRC(3,1)= F2(t0,lt,l0);
    SRC(2,0)= F2(t1,t0,lt);
    SRC(3,0)= F2(t2,t1,t0);
}

Intra_4x4_Vertical_Left

在 SPEC 中,关于该预测模式的定义如下:

Intra_4x4_Vertical_Left

x264 中关于模式 Intra_4x4_Vertical_Left 的代码如下:

static void predict_4x4_vl_c( pixel *src ) {
    PREDICT_4x4_LOAD_TOP
    PREDICT_4x4_LOAD_TOP_RIGHT
    SRC(0,0)= F1(t0,t1);
    SRC(0,1)= F2(t0,t1,t2);
    SRC(1,0)=SRC(0,2)= F1(t1,t2);
    SRC(1,1)=SRC(0,3)= F2(t1,t2,t3);
    SRC(2,0)=SRC(1,2)= F1(t2,t3);
    SRC(2,1)=SRC(1,3)= F2(t2,t3,t4);
    SRC(3,0)=SRC(2,2)= F1(t3,t4);
    SRC(3,1)=SRC(2,3)= F2(t3,t4,t5);
    SRC(3,2)= F1(t4,t5);
    SRC(3,3)= F2(t4,t5,t6);
}

Intra_4x4_Horizontal_Up

在 SPEC 中,关于该预测模式的定义如下:

Intra_4x4_Horizontal_Up

x264 中关于模式 Intra_4x4_Horizontal_Up 的代码如下:

static void predict_4x4_hu_c( pixel *src ) {
    PREDICT_4x4_LOAD_LEFT
    SRC(0,0)= F1(l0,l1);
    SRC(1,0)= F2(l0,l1,l2);
    SRC(2,0)=SRC(0,1)= F1(l1,l2);
    SRC(3,0)=SRC(1,1)= F2(l1,l2,l3);
    SRC(2,1)=SRC(0,2)= F1(l2,l3);
    SRC(3,1)=SRC(1,2)= F2(l2,l3,l3);
    SRC(3,2)=SRC(1,3)=SRC(0,3)=
    SRC(2,2)=SRC(2,3)=SRC(3,3)= l3;
}

8x8 预测

x264 中对 8x8 的预测模式如下:

intra8x8Predmodei[luma8x8BlkIdx] Name of Intra8x8PredMode[luma8x8BlkIdx] Function
0 Intra_8x8_Vertical
1 Intra_8x8_Horizontal
2 Intra_8x8_DC
3 Intra_8x8_Diagonal_Down_Left
4 Intra_8x8_Diagonal_Down_Right
5 Intra_8x8_Vertical_Right
6 Intra_8x8_Horizontal_Down
7 Intra_8x8_Vertical_Left
8 Intra_8x8_Horizontal_Up

参考资料