VVC支持色度联合编码(joint coding of chroma residual ,JCCR)模式。
色度联合编码模式的使用(激活)由TU级标志tu_joint_cbcr_residual_flag表示,所选模式由色度CBFs隐式表示。如果一个TU的色度CBFs中的一个或两个都等于1(tu_cbf_cb或者tu_cbf_cr),则存在标志tu_joint_cbcr_residual_flag。
在PPS和slice head中,联合色度残差编码模式下的色度QP偏移值与常规色度残差编码模式下的色度QP偏移值不同,这些色度QP偏移值用于推导使用联合色度残差编码模式编码的块的色度QP值。
JCCR模式有3个子模式。当相应的联合色度编码模式(表中的模式2)在TU中激活时,在对该TU进行量化和解码时,将该色度QP偏移添加到应用的亮度派生色度QP中(the applied luma-derived chroma QP)。对于其他模式(表中的模式1和模式3),色度QPs的推导方法与常规Cb或Cr块相同。
传输变换块的色度残差(resCb和resCr)的重建过程如表所示。当JCCR模式被激活时,一个联合色度残差块(resJointC [x] [y]被传递过来,然后通过考虑各种信息(比如tu_cbf_cb tu_cbf_cr, CSign)推导出残差块Cb (resCb)和残差块Cr (resCr)。
在编码器端,联合色度分量的推导如下。根据模式(如上表所示),编码器产生resJointC{1,2}如下:
若mode = 2(重构后的单个残差Cb = C, Cr = CSign * C),则根据
-resJointC[ x ][ y ] = ( resCb[ x ][ y ] + CSign * resCr[ x ][ y ] ) / 2.
否则,若mode = 1(重构后的单个残差Cb = C, Cr = (CSign * C) / 2),则根据
-resJointC[ x ][ y ] = ( 4 * resCb[ x ][ y ] + 2 * CSign * resCr[ x ][ y ] ) / 5.
否则(mode = 3,重构后的单个残差Cr = C, Cb = (CSign * C) / 2),根据
-resJointC[ x ][ y ] = ( 4 * resCr[ x ][ y ] + 2 * CSign * resCb[ x ][ y ] ) / 5.
tu_cbf_cb |
tu_cbf_cr |
reconstruction of Cb and Cr residuals |
mode |
1 |
0 |
resCb[ x ][ y ] = resJointC[ x ][ y ] |
1 |
1 |
1 |
resCb[ x ][ y ] = resJointC[ x ][ y ] |
2 |
0 |
1 |
resCb[ x ][ y ] = ( CSign * resJointC[ x ][ y ] ) >> 1 |
3 |
上述三种联合色度编码模式仅在I片上受支持。在P和B片中,只支持模式2。因此,在P和B片中,语法元素tu_joint_cbcr_residual_flag仅在色度cbfs都为1时才存在(即tu_cbf_cb=1且tu_cbf_cr=1)。
JCCR模式可以与色度变换跳过(TransformSkip,TS)模式相结合。 为了加快编码端选择速度,JCCR变换的选择取决于Cb和Cr分量的独立编码是否选择DCT-2或TS作为最佳变换,以及独立色度编码中是否存在非零系数。具体分为以下两种情况:
(1)当Cb和Cr分量都使用DCT-2或者当只有一个色度分量(Cb或Cr)具有非零系数并且色度分量选择DCT-2时,则JCCR模式仅检查DCT-2。类似地,当Cb和Cr分量都使用TS时,或者当只有一个色度分量具有非零系数并且分量选择TS时,JCCR模式仅检查TS。
(2)当Cb和Cr分量的选择变换不同时,即当一个选择DCT-2而另一个选择TS或反之时,JCCR对DCT-2和TS都进行测试。在这些情况下,在TS模式之前执行DCT-2模式的R-D检查,如果DCT-2的系数都为零,则跳过TS R-D检查。
TrQuant::TrQuant() : m_quant( nullptr )
{
// allocate temporary buffers
{
m_invICT = m_invICTMem + maxAbsIctMode;
m_invICT[ 0] = invTransformCbCr< 0>;
m_invICT[ 1] = invTransformCbCr< 1>;
m_invICT[-1] = invTransformCbCr<-1>;
m_invICT[ 2] = invTransformCbCr< 2>;
m_invICT[-2] = invTransformCbCr<-2>;
m_invICT[ 3] = invTransformCbCr< 3>;
m_invICT[-3] = invTransformCbCr<-3>;
m_fwdICT = m_fwdICTMem + maxAbsIctMode;
m_fwdICT[ 0] = fwdTransformCbCr< 0>;
m_fwdICT[ 1] = fwdTransformCbCr< 1>;
m_fwdICT[-1] = fwdTransformCbCr<-1>;
m_fwdICT[ 2] = fwdTransformCbCr< 2>;
m_fwdICT[-2] = fwdTransformCbCr<-2>;
m_fwdICT[ 3] = fwdTransformCbCr< 3>;
m_fwdICT[-3] = fwdTransformCbCr<-3>;
}
}
invTransformCbCr函数如下,该函数主要是通过Cb(Cr)残差求出Cr(Cb)残差
template<int signedMode> void invTransformCbCr( PelBuf &resCb, PelBuf &resCr )
{
Pel* cb = resCb.buf;
Pel* cr = resCr.buf;
for( SizeType y = 0; y < resCb.height; y++, cb += resCb.stride, cr += resCr.stride )
{
for( SizeType x = 0; x < resCb.width; x++ )
{
if ( signedMode == 1 ) { cr[x] = cb[x] >> 1; }
else if ( signedMode == -1 ) { cr[x] = -cb[x] >> 1; }
else if ( signedMode == 2 ) { cr[x] = cb[x]; }
else if ( signedMode == -2 ) { cr[x] = (cb[x] == -32768 && sizeof(Pel) == 2) ? 32767 : -cb[x]; } // non-normative clipping to prevent 16-bit overflow
else if ( signedMode == 3 ) { cb[x] = cr[x] >> 1; }
else if ( signedMode == -3 ) { cb[x] = -cr[x] >> 1; }
}
}
}
fwdTransformCbCr函数是通过ResCb和ResCr求出ResJointC
template<int signedMode> std::pair<int64_t,int64_t> fwdTransformCbCr( const PelBuf &resCb, const PelBuf &resCr, PelBuf& resC1, PelBuf& resC2 )
{
const Pel* cb = resCb.buf;
const Pel* cr = resCr.buf;
Pel* c1 = resC1.buf;
Pel* c2 = resC2.buf;
int64_t d1 = 0;
int64_t d2 = 0;
for( SizeType y = 0; y < resCb.height; y++, cb += resCb.stride, cr += resCr.stride, c1 += resC1.stride, c2 += resC2.stride )
{
for( SizeType x = 0; x < resCb.width; x++ )
{
int cbx = cb[x], crx = cr[x];
if ( signedMode == 1 )
{
c1[x] = Pel( ( 4*cbx + 2*crx ) / 5 );
d1 += square( cbx - c1[x] ) + square( crx - (c1[x]>>1) );
}
else if ( signedMode == -1 )
{
c1[x] = Pel( ( 4*cbx - 2*crx ) / 5 );
d1 += square( cbx - c1[x] ) + square( crx - (-c1[x]>>1) );
}
else if ( signedMode == 2 )
{
c1[x] = Pel( ( cbx + crx ) / 2 );
d1 += square( cbx - c1[x] ) + square( crx - c1[x] );
}
else if ( signedMode == -2 )
{
c1[x] = Pel( ( cbx - crx ) / 2 );
d1 += square( cbx - c1[x] ) + square( crx + c1[x] );
}
else if ( signedMode == 3 )
{
c2[x] = Pel( ( 4*crx + 2*cbx ) / 5 );
d1 += square( cbx - (c2[x]>>1) ) + square( crx - c2[x] );
}
else if ( signedMode == -3 )
{
c2[x] = Pel( ( 4*crx - 2*cbx ) / 5 );
d1 += square( cbx - (-c2[x]>>1) ) + square( crx - c2[x] );
}
else
{
d1 += square( cbx );
d2 += square( crx );
}
}
}
return std::make_pair(d1,d2);
}