VVCは、彩度残差(JCCR)モードの共同コーディングをサポートします。
クロマジョイントコーディングモードの使用(アクティブ化)は、TUレベルフラグtu_joint_cbcr_residual_flagによって示され、選択されたモードは、クロマCBFによって暗黙的に示されます。1つのTUのクロミナンスCBFの一方または両方が1(tu_cbf_cbまたはtu_cbf_cr)に等しい場合、フラグtu_joint_cbcr_residual_flagがあります。
PPSとスライスヘッドでは、ジョイントクロミナンス残差コーディングモードのクロミナンスQPオフセット値は、従来のクロミナンス残差コーディングモードのクロミナンスQPオフセット値とは異なります。これらのクロミナンスQPオフセット値は、のクロミナンスQP値を導出するために使用されます。ジョイントクロミナンス残差コーディングモードを使用してコーディングされたブロック。
JCCRモードには3つのサブモードがあります。対応するジョイントクロミナンスコーディングモード(表のモード2)がTUでアクティブ化されている場合、TUが量子化およびデコードされると、クロミナンスQPオフセットが適用された輝度由来のクロミナンスQP(適用された輝度由来のクロマQP)に追加されます。 )。他のモード(表のモード1およびモード3)の場合、クロミナンスQPの導出方法は、従来のCbまたはCrブロックの導出方法と同じです。
透過変換ブロックのクロミナンス残差(resCbおよびresCr)の再構成プロセスを表に示します。JCCRモードがアクティブになると、ジョイントクロミナンス残差ブロック(resJointC [x] [y]が渡され、さまざまな情報(tu_cbf_cb tu_cbf_cr、CSignなど)を考慮して、残差ブロックCb(resCb)および残差差分ブロックCrが渡されます。 (resCr)。
エンコーダ側では、ジョイントクロミナンス成分は次のように導出されます。モード(上記の表に示されている)に従って、エンコーダーは次のようにresJointC {1,2}を生成します。
モード= 2(単一の残差Cb = C、再構成後のCr = CSign * C)の場合、
-resJointC [x] [y] =(resCb [x] [y] + CSign * resCr [x] [y])/ 2。
それ以外の場合、モード= 1(再構成された単一の残差Cb = C、Cr =(CSign * C)/ 2)の場合、
-resJointC [x] [y] =(4 * resCb [x] [y] + 2 * CSign * resCr [x] [y])/ 5。
それ以外の場合(モード= 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 |
CbおよびCr残差の再構築 |
モード |
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 |
上記の3つのジョイントクロマコーディングモードは、Iチップでのみサポートされています。PおよびBフィルムでは、モード2のみがサポートされています。したがって、PスライスとBスライスでは、構文要素tu_joint_cbcr_residual_flagは、彩度cbfsが両方とも1(つまり、tu_cbf_cb = 1とtu_cbf_cr = 1)の場合にのみ存在します。
JCCRモードは、変換スキップ(TS)モードと組み合わせることができます。エンコード側の選択速度を高速化するために、JCCR変換の選択は、CbおよびCrコンポーネントの独立したエンコードがDCT-2またはTSを最良の変換として選択するかどうか、およびゼロ以外の係数があるかどうかによって異なります。独立したクロミナンスエンコーディングで。具体的には、次の2つの状況に分けられます。
(1)Cb成分とCr成分の両方がDCT-2を使用する場合、または1つのクロミナンス成分(CbまたはCr)のみがゼロ以外の係数を持ち、クロミナンス成分がDCT-2を選択する場合、JCCRモードはDCT-2のみをチェックします。同様に、CbコンポーネントとCrコンポーネントの両方がTSを使用する場合、または1つの彩度コンポーネントのみがゼロ以外の係数を持ち、コンポーネントがTSを選択する場合、JCCRモードはTSのみをチェックします。
(2)Cb成分とCr成分の選択的変換が異なる場合、つまり、一方がDCT-2を選択し、もう一方がTSを選択する場合、またはその逆の場合、JCCRはDCT-2とTSの両方をテストします。このような場合、DCT-2モードのRDチェックはTSモードの前に実行され、DCT-2の係数がすべてゼロの場合、TSRDチェックはスキップされます。
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);
}