今天来学习xT
和xTrMxN
和xTrMxN_EMT
函数。
在之前的 H.266代码学习:transformNxN函数 中提到JEM变换分为两次:主变换+二次NSST。transformNxN
会调用xT
进行主变换。
在JEM中,主变换有三种:
1.原HEVC中的DCT-2/4x4 DST。
2.多核变换。
3.KLT变换。
其中1和3的入口函数为xTrMxN
,2的入口函数为xTrMxN_EMT
。
当然,有变换就有反变换,三者对应的反变换函数分别是:xIT
、xITrMxN
、xITrMxN_EMT
。
下面先来看xT
,它是主变换的入口函数。 函数体很短,比较简单。
HEVC代码学习36:xTrMxN函数中对HM中的xT
进行了学习。
JEM中与HM主要区别:
在JEM中,在原HEVC中的DCT-2/4x4 DST基础上增加了多核变换和KLT变换,xT
增加了多核变换的入口函数xTrMxN_EMT
,而KLT变换的入口函数放在了xTrMxN
中,之后会具体学习。
流程如下:
一、读取残差
二、如果变换模式ucTrIdx
不是DCT2_HEVC
且KLT为false时,执行1,否则执行2
1.调用xTrMxN_EMT
进行多核变换。
2.调用xTrMxN
进行DCT-2/4x4 DST和KLT变换。
代码分析:
/** Wrapper function between HM interface and core NxN forward transform (2D)
* \param channelBitDepth bit depth of channel
* \param useDST
* \param piBlkResi input data (residual)
* \param uiStride stride of input residual data
* \param psCoeff output data (transform coefficients)
* \param iWidth transform width
* \param iHeight transform height
* \param maxLog2TrDynamicRange
*/
//变换函数
Void TComTrQuant::xT( const Int channelBitDepth, Bool useDST, Pel* piBlkResi, UInt uiStride, TCoeff* psCoeff, Int iWidth, Int iHeight, const Int maxLog2TrDynamicRange
#if COM16_C806_EMT
, UChar ucMode
, UChar ucTrIdx
#endif
#if VCEG_AZ08_KLT_COMMON
, Bool useKLT //KLT flag
#endif
)
{
#if MATRIX_MULT
if( iWidth == iHeight)
{
#if COM16_C806_EMT
if( ucTrIdx!=DCT2_HEVC )
{
xTr_EMT(channelBitDepth, piBlkResi, psCoeff, uiStride, (UInt)iWidth, useDST, maxLog2TrDynamicRange, ucMode, ucTrIdx );
}
else
#endif
xTr(channelBitDepth, piBlkResi, psCoeff, uiStride, (UInt)iWidth, useDST, maxLog2TrDynamicRange);
return;
}
#endif
TCoeff block[ MAX_TU_SIZE * MAX_TU_SIZE ];
TCoeff coeff[ MAX_TU_SIZE * MAX_TU_SIZE ];
for (Int y = 0; y < iHeight; y++)
{
for (Int x = 0; x < iWidth; x++)
{
block[(y * iWidth) + x] = piBlkResi[(y * uiStride) + x]; //读取残差
}
}
#if COM16_C806_EMT
#if VCEG_AZ08_KLT_COMMON
if( ucTrIdx!=DCT2_HEVC && useKLT == false) //不是DCT-2且KLT为false时,使用EMT
#else
if( ucTrIdx!=DCT2_HEVC )
#endif
{
xTrMxN_EMT(channelBitDepth, block, coeff, iWidth, iHeight, useDST, maxLog2TrDynamicRange, ucMode, ucTrIdx );
}
else //否则使用DCT-2或KLT
#endif
#if VCEG_AZ08_KLT_COMMON
xTrMxN( channelBitDepth, block, coeff, iWidth, iHeight, useDST, maxLog2TrDynamicRange, useKLT);
#else
xTrMxN( channelBitDepth, block, coeff, iWidth, iHeight, useDST, maxLog2TrDynamicRange );
#endif
memcpy(psCoeff, coeff, (iWidth * iHeight * sizeof(TCoeff)));
}
HM中的xTrMxN
函数学习见HEVC代码学习36:xTrMxN函数。
xTrMxN
是DCT-2/4x4 DST和KLT变换的入口函数。
JEM中与HM主要区别:
1.在JEM中新增了KLT变换,入口函数xKLTr
在xTrMxN
中。
2.增加了调零操作,对变换后的残差系数宽、高大于32的部分直接取0。(调零是JEM中新增的内容,详见 H.266变换编码:高频调零的大尺寸块变换)
3.增加了更多尺寸的DCT-2变换:尺寸为2fastForwardDCT2_B2
,尺寸为64fastForwardDCT2_B64
,尺寸为128fastForwardDCT2_B128
。
流程如下:
一、当useKLT
为true,调用xKLTr进行KLT变换,直接返回。
二、当useKLT
为false,进行原HEVC中的DCT-2/DST变换。
1.计算水平和垂直偏移。
2.设置调零宽度和高度。
3.根据宽度进行对应尺寸的水平变换,然后根据高度进行对应尺寸的垂直变换。
代码分析:
Void xTrMxN(Int bitDepth, TCoeff *block, TCoeff *coeff, Int iWidth, Int iHeight, Bool useDST, const Int maxLog2TrDynamicRange
#if VCEG_AZ08_KLT_COMMON
, Bool useKLT //是否使用KLT
#endif
)
{
#if VCEG_AZ08_KLT_COMMON
if (useKLT == true) //使用KLT
{
xKLTr(bitDepth, block, coeff, iWidth); //KLT
return;
}
#endif
const Int TRANSFORM_MATRIX_SHIFT = g_transformMatrixShift[TRANSFORM_FORWARD];
#if !COM16_C806_T64
const
#endif
#if JVET_C0024_QTBT
//第一维水平变换偏移
Int shift_1st = ((g_aucConvertToBit[iWidth] + MIN_CU_LOG2) + bitDepth + TRANSFORM_MATRIX_SHIFT) - maxLog2TrDynamicRange;
#else
Int shift_1st = ((g_aucConvertToBit[iWidth] + 2) + bitDepth + TRANSFORM_MATRIX_SHIFT) - maxLog2TrDynamicRange;
#endif
#if !COM16_C806_T64
const
#endif
#if JVET_C0024_QTBT
//第二维垂直变换偏移
Int shift_2nd = (g_aucConvertToBit[iHeight] + MIN_CU_LOG2) + TRANSFORM_MATRIX_SHIFT;
#else
Int shift_2nd = (g_aucConvertToBit[iHeight] + 2) + TRANSFORM_MATRIX_SHIFT;
#endif
#if COM16_C806_T64
#if JVET_C0024_QTBT
if( iWidth>=64 || iWidth==2) //宽度大于64或宽度等于2时
{
shift_1st += COM16_C806_TRANS_PREC; //COM16_C806_TRANS_PREC=2
}
if( iHeight>=64 || iHeight==2) //宽度大于64或宽度等于2时
{
shift_2nd += COM16_C806_TRANS_PREC; //COM16_C806_TRANS_PREC=2
}
}
#else
// 删除默认不启用的代码
#endif
#endif
assert(shift_1st >= 0);
assert(shift_2nd >= 0);
TCoeff tmp[ MAX_TU_SIZE * MAX_TU_SIZE ];
//调零,宽度和高度大于32的部分调零。
Int iSkipWidth = (iWidth > ZERO_OUT_TH ? iWidth-ZERO_OUT_TH : 0); //ZERO_OUT_TH=32
Int iSkipHeight = (iHeight > ZERO_OUT_TH ? iHeight-ZERO_OUT_TH : 0);
switch (iWidth) //根据宽度选择不同尺寸的水平变换
{
#if JVET_C0024_QTBT
#if JVET_D0077_TRANSFORM_OPT
case 2: fastForwardDCT2_B2( block, tmp, shift_1st, iHeight, 0, iSkipWidth, 0 ); break;
#else
case 2: fastForwardDCT2_B2( block, tmp, shift_1st, iHeight, 0, 0 ); break;
#endif
#endif
case 4:
{
if ((iHeight == 4) && useDST) // Check for DCT or DST 4x4的块使用DST变换
{
fastForwardDst( block, tmp, shift_1st );
}
else //否则使用蝶形快速变换
{
#if JVET_D0077_TRANSFORM_OPT
partialButterfly4 ( block, tmp, shift_1st, iHeight, iSkipHeight );
#else
partialButterfly4 ( block, tmp, shift_1st, iHeight );
#endif
}
}
break;
#if JVET_D0077_TRANSFORM_OPT
//其余尺寸块使用蝶形快速变换
case 8: partialButterfly8 ( block, tmp, shift_1st, iHeight, iSkipHeight ); break;
case 16: partialButterfly16( block, tmp, shift_1st, iHeight, iSkipHeight ); break;
case 32: partialButterfly32( block, tmp, shift_1st, iHeight, iSkipHeight ); break;
#else
// 删除默认不启用的代码
#endif
#if COM16_C806_T64
#if JVET_C0024_QTBT
#if JVET_D0077_TRANSFORM_OPT
case 64: fastForwardDCT2_B64( block, tmp, shift_1st, iHeight, 0, iSkipWidth, 0 ); break;
case 128: fastForwardDCT2_B128( block, tmp, shift_1st, iHeight, 0, iSkipWidth, 0 ); break;
#else
// 删除默认不启用的代码
#endif
#else
case 64: fastForwardDCT2_B64( block, tmp, shift_1st, iHeight, 1, 0 ); break;
#endif
#endif
default:
assert(0); exit (1); break;
}
switch (iHeight) //根据高度选择不同尺寸的水平变换
{
#if JVET_C0024_QTBT
#if JVET_D0077_TRANSFORM_OPT
case 2: fastForwardDCT2_B2( tmp, coeff, shift_2nd, iWidth, iSkipWidth, iSkipHeight, 0 ); break;
#else
case 2: fastForwardDCT2_B2( tmp, coeff, shift_2nd, iWidth, 0, 0 ); break;
#endif
#endif
case 4:
{
if ((iWidth == 4) && useDST) // Check for DCT or DST 4x4的块使用DST变换
{
fastForwardDst( tmp, coeff, shift_2nd );
}
else //否则使用蝶形快速变换
{
#if JVET_D0077_TRANSFORM_OPT
partialButterfly4 ( tmp, coeff, shift_2nd, iWidth, iSkipWidth );
#else
partialButterfly4 ( tmp, coeff, shift_2nd, iWidth );
#endif
}
}
break;
#if JVET_D0077_TRANSFORM_OPT
//其余尺寸块使用蝶形快速变换
case 8: partialButterfly8 ( tmp, coeff, shift_2nd, iWidth, iSkipWidth ); break;
case 16: partialButterfly16( tmp, coeff, shift_2nd, iWidth, iSkipWidth ); break;
case 32: partialButterfly32( tmp, coeff, shift_2nd, iWidth, iSkipWidth ); break;
#else
// 删除默认不启用的代码
#endif
#if COM16_C806_T64
#if JVET_C0024_QTBT
#if JVET_D0077_TRANSFORM_OPT
case 64: fastForwardDCT2_B64( tmp, coeff, shift_2nd, iWidth, iSkipWidth, iSkipHeight, 0 ); break;
case 128: fastForwardDCT2_B128( tmp, coeff, shift_2nd, iWidth, iSkipWidth, iSkipHeight, 0 ); break;
#else
// 删除默认不启用的代码
#endif
#else
case 64: fastForwardDCT2_B64( tmp, coeff, shift_2nd, iWidth, 2, 0 ); break;
#endif
#endif
default:
assert(0); exit (1); break;
}
}
xTrMxN_EMT
是多核变换的入口函数。
在之前的 H.266代码学习:AMT相关代码 中对多核变换的整体代码已经进行了学习,这里再来详细看一下xTrMxN_EMT
。
JEM中与HM主要区别:
多核变换是JEM新增内容。
流程如下:
一、计算水平、垂直偏移,设置调零的宽度和高度。
二、选择变换模式:
1.帧内且变换模式非DCT2_EMT时,根据帧内模式选择水平、垂直方向的变换模式。
2.帧间且变换模式非DCT2_EMT时,设置水平和垂直方向的变换模式。
三、进行水平、垂直变换。
代码分析:
/** MxN forward transform (2D)
* \param bitDepth [in] bit depth
* \param block [in] residual block
* \param coeff [out] transform coefficients
* \param iWidth [in] width of transform
* \param iHeight [in] height of transform
* \param useDST [in]
* \param maxLog2TrDynamicRange [in]
*/
#if COM16_C806_EMT
void xTrMxN_EMT(Int bitDepth, TCoeff *block, TCoeff *coeff, Int iWidth, Int iHeight, Bool useDST, const Int maxLog2TrDynamicRange, UChar ucMode, UChar ucTrIdx )
{
const Int TRANSFORM_MATRIX_SHIFT = g_transformMatrixShift[TRANSFORM_FORWARD];
#if JVET_C0024_QTBT
//为保证精度,偏移在HEVC的基础上增加了2
const Int shift_1st = ((g_aucConvertToBit[iWidth] + MIN_CU_LOG2) + bitDepth + TRANSFORM_MATRIX_SHIFT) - maxLog2TrDynamicRange + COM16_C806_TRANS_PREC;
const Int shift_2nd = (g_aucConvertToBit[iHeight] + MIN_CU_LOG2) + TRANSFORM_MATRIX_SHIFT + COM16_C806_TRANS_PREC;
const UInt nLog2WidthMinus1 = g_aucConvertToBit[iWidth] + MIN_CU_LOG2 - 1; //nLog2WidthMinus1, since transform start from 2-point
const UInt nLog2HeightMinus1 = g_aucConvertToBit[iHeight] + MIN_CU_LOG2 - 1; //nLog2HeightMinus1, since transform start from 2-point
//调零
Int iSkipWidth = (iWidth > ZERO_OUT_TH ? iWidth-ZERO_OUT_TH : 0);
Int iSkipHeight = (iHeight > ZERO_OUT_TH ? iHeight-ZERO_OUT_TH : 0);
#else
// 删除默认不启用的代码
);
#endif
TCoeff tmp[ MAX_TU_SIZE * MAX_TU_SIZE ];
UInt nTrIdxHor = DCT2, nTrIdxVer = DCT2; //水平和垂直方向默认变换模式为DCT2
if( ucMode != INTER_MODE_IDX && ucTrIdx != DCT2_EMT ) //帧内且非DCT2_EMT
{
//根据帧内模式设置水平垂直方向的变换模式
UInt nTrSubsetHor = g_aucTrSetHorz[ucMode];
UInt nTrSubsetVer = g_aucTrSetVert[ucMode];
nTrIdxHor = g_aiTrSubsetIntra[nTrSubsetHor][ucTrIdx &1];
nTrIdxVer = g_aiTrSubsetIntra[nTrSubsetVer][ucTrIdx>>1];
}
if ( ucMode == INTER_MODE_IDX && ucTrIdx != DCT2_EMT ) //帧间且非DCT2_EMT
{
//设置帧间水平垂直方向的变换模式
nTrIdxHor = g_aiTrSubsetInter[ucTrIdx &1];
nTrIdxVer = g_aiTrSubsetInter[ucTrIdx>>1];
}
#if JVET_C0024_QTBT
#if JVET_D0077_TRANSFORM_OPT
//进行水平、垂直变换
fastFwdTrans[nTrIdxHor][nLog2WidthMinus1]( block, tmp, shift_1st, iHeight, 0, iSkipWidth, 1 );
fastFwdTrans[nTrIdxVer][nLog2HeightMinus1]( tmp, coeff, shift_2nd, iWidth, iSkipWidth, iSkipHeight, 1 );
#else
// 删除默认不启用的代码
#endif
#else
// 删除默认不启用的代码
#endif
}
#endif