x265中dct4函数的向量实现

摘要

对于DCT函数,不同尺寸函数的实现存在较大的差异。因此在这一系列中,会分别讲述DCT函数不同尺寸的实现方法及原理。本文讲述的是4x4尺寸的内容。4x4尺寸较小,不会达到寄存器数量的极限,数据构造简单,实现相对较为容易,重点讲述一下原理。

正文

原理

DCT函数的实现原理其实是基于FFT的蝶形算法,一定程度上减少加法、乘法运算的次数。具体来看。

在H.265/HEVC中4x4整数DCT公式为:

Y = ( [ 64 64 64 64 83 36 − 36 − 83 64 − 64 − 64 64 36 − 83 83 − 36 ] X [ 64 83 64 36 64 36 − 64 − 83 64 − 36 − 64 83 64 − 83 64 − 36 ] ) ∗ 1 128 ∗ 1 128 Y=(\begin{bmatrix} 64&64&64&64\\ 83&36&-36&-83\\ 64&-64&-64&64\\ 36&-83&83&-36\\ \end{bmatrix}X\begin{bmatrix} 64&83&64&36\\ 64&36&-64&-83\\ 64&-36&-64&83\\ 64&-83&64&-36\\ \end{bmatrix})*\frac{1}{128}*\frac{1}{128} Y=(64836436643664836436648364836436X64646464833636836464646436838336)12811281

其中还是分为列变换和行变换,两者使用的算法是完全相同的。单看右侧的行变换。以第一行作为例子。第一行的元素为 { c o l 0 , c o l 1 , c o l 2 , c o l 3 } \{col0, col1, col2, col3\} {col0,col1,col2,col3} ,行变换后得到四各系数。

v a l 0 = 64 ∗ c o l 0 + 64 ∗ c o l 1 + 64 ∗ c o l 2 + 64 ∗ c o l 3 val0 = 64*col0+64*col1+64*col2+64*col3 val0=64col0+64col1+64col2+64col3
v a l 1 = 83 ∗ c o l 0 + 36 ∗ c o l 1 − 36 ∗ c o l 2 − 83 ∗ c o l 3 val1 = 83*col0+36*col1-36*col2-83*col3 val1=83col0+36col136col283col3
v a l 2 = 64 ∗ c o l 0 − 64 ∗ c o l 1 − 64 ∗ c o l 2 + 64 ∗ c o l 3 val2 = 64*col0-64*col1-64*col2+64*col3 val2=64col064col164col2+64col3
v a l 3 = 36 ∗ c o l 0 − 83 ∗ c o l 1 + 83 ∗ c o l 2 − 36 ∗ c o l 3 val3 = 36*col0-83*col1+83*col2-36*col3 val3=36col083col1+83col236col3

其中有16次乘法操作和12次加法操作。但观察变换系数,其实其中是存在一定的规律的。可以将这一规律加以利用。将整个计算过程划分为两个步骤。

步骤一

c o l 0 + c o l 3 col0+col3 col0+col3
c o l 0 − c o l 3 col0-col3 col0col3
c o l 1 + c o l 2 col1+col2 col1+col2
c o l 1 − c o l 2 col1-col2 col1col2

步骤二

v a l 0 = 64 ∗ ( c o l 0 + c o l 3 ) + 64 ∗ ( c o l 1 + c o l 2 ) val0 = 64*(col0+col3)+64*(col1+col2) val0=64(col0+col3)+64(col1+col2)
v a l 1 = 83 ∗ ( c o l 0 − c o l 3 ) + 36 ∗ ( c o l 1 − c o l 2 ) val1 = 83*(col0-col3)+36*(col1-col2) val1=83(col0col3)+36(col1col2)
v a l 2 = 64 ∗ ( c o l 0 + c o l 3 ) − 64 ∗ ( c o l 1 + c o l 2 ) val2 = 64*(col0+col3)-64*(col1+col2) val2=64(col0+col3)64(col1+col2)
v a l 3 = 36 ∗ ( c o l 0 − c o l 3 ) − 83 ∗ ( c o l 1 − c o l 2 ) val3 = 36*(col0-col3)-83*(col1-col2) val3=36(col0col3)83(col1col2)

两个步骤共需要8次乘法和8次加法。这样看来就非常的明了。对于更大的尺寸也同样适用,只是需要更多的步骤。

实现

实现的关键还是在与数据的构造。当前使用的还是128位向量寄存器,MIPS指令集(msa1.0)。指令+数据,综合呈现处理过程。

函数参数
static void dct4_c(const int16_t* src, int16_t* dst, intptr_t srcStride)

首先使用vbld指令加载数据, 得到4个向量寄存器,低64位保存每行4个数据。然后使用insve指令,插入操作,得到2个向量寄存器。内容分别为。

这里统一一下寄存器的表述方法:
左侧为寄存器高位,右侧为低位。
r o w n [ c o l m ] row_n[col_m] rown[colm]表示第n行的第m列元素, c o l n [ r o w m ] col_n[row_m] coln[rowm]则表示第n列第m行元素。我认为这种表述方法比较明确,直接使用 ( n , m ) (n,m) (nm)表示n行m列,就相当混乱,难以理解。

{ r o w 1 [ c o l 3 , c o l 2 , c o l 1 , c o l 0 ] , r o w 0 [ c o l 3 , c o l 2 , c o l 1 , c o l 0 ] } \{row1[col3, col2,col1,col0], row0[col3,col2,col1,col0]\} {row1[col3,col2,col1,col0],row0[col3,col2,col1,col0]}
{ r o w 3 [ c o l 3 , c o l 2 , c o l 1 , c o l 0 ] , r o w 2 [ c o l 3 , c o l 2 , c o l 1 , c o l 0 ] } \{row3[col3, col2,col1,col0], row2[col3, col2,col1,col0]\} {row3[col3,col2,col1,col0],row2[col3,col2,col1,col0]}

得到初始数据后开始进行变换。首先进行右侧的行变换。之所以是这样的顺序是因为,load访存是以行的顺序加载数据。当前尺寸较小,可以一次性加载完。但处理后续尺寸时,由于寄存器数量的限制,并行度是受限的。可能无法同时保存所有的原始数据。因此,这种情况下只有完整的行数据,无完整列数据,也就只能进行行变换。而且即使可以保存所有原始数据,先进行行变换,也可以给整个变换过程更大的并行度调动范围,增大可控性。

首先构造步骤一的内容。要得到一行中各个元素的加减组合,依仗当前的数据结构是完全不可能的,需要进行结构调整。

使用shf混洗指令,以32位为混洗单位,简单来说就是交换一下中间的两个32位数据。得到

{ r o w 1 [ c o l 3 , c o l 2 ] , r o w 0 [ c o l 3 , c o l 2 ] , r o w 1 [ c o l 1 , c o l 0 ] , r o w 0 [ c o l 1 , c o l 0 ] } \{row1[col3, col2], row0[col3,col2], row1[col1, col0], row0[col1, col0]\} {row1[col3,col2],row0[col3,col2],row1[col1,col0],row0[col1,col0]}
{ r o w 3 [ c o l 3 , c o l 2 ] , r o w 2 [ c o l 3 , c o l 2 ] , r o w 3 [ c o l 1 , c o l 0 ] , r o w 2 [ c o l 1 , c o l 0 ] } \{row3[col3, col2], row2[col3,col2], row3[col1, col0], row2[col1, col0]\} {row3[col3,col2],row2[col3,col2],row3[col1,col0],row2[col1,col0]}

然后需要将第一个向量寄存器的高64位和第二个寄存器的低64位交换一下,将同一行的元素置于两个寄存器的对应位置。这里用到的指令是insve插入指令和pckod选择奇数位数据(64位),进行交错。得到

{ r o w 3 [ c o l 1 , c o l 0 ] , r o w 2 [ c o l 1 , c o l 0 ] , r o w 1 [ c o l 1 , c o l 0 ] , r o w 0 [ c o l 1 , c o l 0 ] } \{row3[col1, col0], row2[col1,col0], row1[col1, col0], row0[col1, col0]\} {row3[col1,col0],row2[col1,col0],row1[col1,col0],row0[col1,col0]}
{ r o w 3 [ c o l 3 , c o l 2 ] , r o w 2 [ c o l 3 , c o l 2 ] , r o w 1 [ c o l 3 , c o l 2 ] , r o w 0 [ c o l 3 , c o l 2 ] } \{row3[col3, col2], row2[col3, col2], row1[col3, col2], row0[col3, col2]\} {row3[col3,col2],row2[col3,col2],row1[col3,col2],row0[col3,col2]}

要得到 c o l 0 col0 col0 c o l 3 col3 col3对应, c o l 1 col1 col1 c o l 2 col2 col2对应还需要对第二个寄存器处理一下。还是使用shf混洗指令,16位数据为单位,得到第二个寄存器。

{ r o w 3 [ c o l 2 , c o l 3 ] , r o w 2 [ c o l 2 , c o l 3 ] , r o w 1 [ c o l 2 , c o l 3 ] , r o w 0 [ c o l 2 , c o l 3 ] } \{row3[col2, col3], row2[col2, col3], row1[col2, col3], row0[col2, col3]\} {row3[col2,col3],row2[col2,col3],row1[col2,col3],row0[col2,col3]}

数据构造完毕,进行加减操作。使用addvsubv指令,进行16位数据加减。得到

{ r o w 3 [ c o l 1 + c o l 2 , c o l 0 + c o l 3 ] , r o w 2 [ . . . ] , r o w 1 [ . . . ] , r o w 0 [ . . . ] } \{row3[col1+col2, col0+col3], row2[...], row1[...],row0[...]\} {row3[col1+col2,col0+col3],row2[...],row1[...],row0[...]}
{ r o w 3 [ c o l 1 − c o l 2 , c o l 0 − c o l 3 ] , r o w 2 [ . . . ] , r o w 1 [ . . . ] , r o w 0 [ . . . ] } \{row3[col1-col2, col0-col3], row2[...], row1[...],row0[...]\} {row3[col1col2,col0col3],row2[...],row1[...],row0[...]}

完成步骤一,开始步骤二的内容。观察当前数据结构,需要乘法、加减操作的数据是在同一寄存器的相邻位置。可以直接使用横向运算指令实现。当前设置了四个常量数据(16位),分别完成步骤二中的四个操作。在寄存器中表示为

c o n 0 = { 64 , 64 , 64 , 64 , 64 , 64 , 64 , 64 } con0=\{64,64,64,64,64,64,64,64\} con0={64,64,64,64,64,64,64,64}
c o n 1 = { 36 , 83 , 36 , 83 , 36 , 83 , 36 , 83 } con1=\{36,83,36,83,36,83,36,83\} con1={36,83,36,83,36,83,36,83}
c o n 2 = { − 64 , 64 , − 64 , 64 , − 64 , 64 , − 64 , 64 } con2=\{-64,64,-64,64,-64,64,-64,64\} con2={64,64,64,64,64,64,64,64}
c o n 3 = { − 83 , 36 , − 83 , 36 , − 83 , 36 , − 83 , 36 } con3=\{-83,36,-83,36,-83,36,-83,36\} con3={83,36,83,36,83,36,83,36}

使用dotp点乘指令,对应位置相乘,相邻位置相加。第一个寄存器和 c o n 0 con0 con0 c o n 2 con2 con2点乘,第二个寄存器和 c o n 1 con1 con1 c o n 3 con3 con3点乘。表示为

{ r o w 3 [ 64 ∗ ( c o l 1 + c o l 2 ) + 64 ∗ ( c o l 0 + c o l 3 ) ] , r o w 2 [ . . . ] , r o w 1 [ . . . ] , r o w 0 [ . . . ] } \{row3[64*(col1+col2)+64*(col0+col3)], row2[...],row1[...],row0[...]\} {row3[64(col1+col2)+64(col0+col3)],row2[...],row1[...],row0[...]}
{ r o w 3 [ 36 ∗ ( c o l 1 − c o l 2 ) + 83 ∗ ( c o l 0 − c o l 3 ) ] , . . . } \{row3[36*(col1-col2)+83*(col0-col3)], ...\} {row3[36(col1col2)+83(col0col3)],...}
{ r o w 3 [ ( − 64 ) ∗ ( c o l 1 + c o l 2 ) + 64 ∗ ( c o l 0 + c o l 3 ) ] , . . . } \{row3[(-64)*(col1+col2)+64*(col0+col3)], ...\} {row3[(64)(col1+col2)+64(col0+col3)],...}
{ r o w 3 [ ( − 83 ) ∗ ( c o l 1 − c o l 2 ) + 36 ∗ ( c o l 0 − c o l 3 ) ] , . . . } \{row3[(-83)*(col1-col2)+36*(col0-col3)], ...\} {row3[(83)(col1col2)+36(col0col3)],...}

最后使用vsrarin指令,集移位、舍入、截半与一身的指令,右移1位,完成行变换。具体为什么只右移1位,而之后的列变换右移8位。两者合起来并没有达到 1 128 ∗ 1 128 \frac{1}{128}*\frac{1}{128} 12811281的效果。这里可能涉及到整个系统的设计,因为变换过程中的部分操作是交给量化过程完成的。原因的话我目前也不是非常清楚。

完成行变换后,当前的数据表达形式已经并不适用了,应该更新为行变换后,数据的形式。更新一下当前的数据结构。

第一个寄存器都是使用第一列的系数,因此得到的都是行变换后第一列的数据。同样第二个寄存器就是第二列,第三个寄存器第三列,第四个寄存器第四列。可以表述为

{ 0 , 0 , 0 , 0 , c o l 0 [ r o w 3 , r o w 2 , r o w 1 , r o w 0 ] } \{0,0,0,0,col0[row3,row2, row1, row0]\} {0,0,0,0,col0[row3,row2,row1,row0]}
{ 0 , 0 , 0 , 0 , c o l 1 [ r o w 3 , r o w 2 , r o w 1 , r o w 0 ] } \{0,0,0,0,col1[row3,row2, row1, row0]\} {0,0,0,0,col1[row3,row2,row1,row0]}
{ 0 , 0 , 0 , 0 , c o l 2 [ r o w 3 , r o w 2 , r o w 1 , r o w 0 ] } \{0,0,0,0,col2[row3,row2, row1, row0]\} {0,0,0,0,col2[row3,row2,row1,row0]}
{ 0 , 0 , 0 , 0 , c o l 3 [ r o w 3 , r o w 2 , r o w 1 , r o w 0 ] } \{0,0,0,0,col3[row3,row2, row1, row0]\} {0,0,0,0,col3[row3,row2,row1,row0]}

后续需要完成的是列变换,因此可以发现,当前的数据结构和进行行变换时的起始数据结构是一样的。因此整个操作过程和上边的完全相同,唯一的不同就是最后的vsrarin指令右移的是8位。现在直接给出结果。

{ c o l 3 [ 64 ∗ ( r o w 1 + r o w 2 ) + 64 ∗ ( r o w 0 + r o w 3 ) ] , c o l 2 [ . . . ] , c o l 1 [ . . . ] , c o l 0 [ . . . ] } \{col3[64*(row1+row2)+64*(row0+row3)], col2[...],col1[...],col0[...]\} {col3[64(row1+row2)+64(row0+row3)],col2[...],col1[...],col0[...]}
{ c o l 3 [ 36 ∗ ( r o w 1 − r o w 2 ) + 83 ∗ ( r o w 0 − r o w 3 ) ] , . . . } \{col3[36*(row1-row2)+83*(row0-row3)], ...\} {col3[36(row1row2)+83(row0row3)],...}
{ c o l 3 [ ( − 64 ) ∗ ( r o w 1 + r o w 2 ) + 64 ∗ ( r o w 0 + r o w 3 ) ] , . . . } \{col3[(-64)*(row1+row2)+64*(row0+row3)], ...\} {col3[(64)(row1+row2)+64(row0+row3)],...}
{ c o l 3 [ ( − 83 ) ∗ ( r o w 1 − r o w 2 ) + 36 ∗ ( r o w 0 − r o w 3 ) ] , . . . } \{col3[(-83)*(row1-row2)+36*(row0-row3)], ...\} {col3[(83)(row1row2)+36(row0row3)],...}

第一个寄存器使用第一行的数据,在最终的结果矩阵中就位于第一行的位置。第二个寄存器第二行,第三个第三行,第四个第四行。因此可以直接使用st指令存储,不需要再进行结构变换。至此完成整个过程。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值