O VVC oferece suporte à codificação conjunta do modo de croma residual (JCCR).
O uso (ativação) do modo de codificação de junção de croma é indicado pelo sinalizador de nível TU tu_joint_cbcr_residual_flag e o modo selecionado é implicitamente indicado pelos CBFs de croma. Se um ou ambos os CBFs de crominância de uma TU forem iguais a 1 (tu_cbf_cb ou tu_cbf_cr), há um sinalizador tu_joint_cbcr_residual_flag .
No PPS e no cabeçote de fatia, o valor de compensação QP de crominância no modo de codificação residual de crominância conjunta é diferente do valor de compensação QP de crominância no modo de codificação residual de crominância convencional. Esses valores de compensação QP de crominância são usados para derivar o valor QP de crominância de o bloco codificado usando o modo de codificação residual de crominância conjunta.
O modo JCCR possui 3 submodos. Quando o modo de codificação de crominância conjunta correspondente (modo 2 na tabela) é ativado na TU, quando a TU é quantizada e decodificada, o deslocamento de QP de crominância é adicionado à crominância derivada de luminância aplicada QP (a croma derivada de luma aplicada QP ) Para outros modos (modo 1 e modo 3 na tabela), o método de derivação dos QPs de crominância é o mesmo dos blocos convencionais Cb ou Cr.
O processo de reconstrução do resíduo de crominância (resCb e resCr) do bloco de transformação de transmissão é mostrado na tabela. Quando o modo JCCR é ativado, um bloco residual de crominância conjunta (resJointC [x] [y] é passado e, em seguida, considerando várias informações (como tu_cbf_cb tu_cbf_cr, CSign), o bloco residual Cb (resCb) e o bloco de diferença residual Cr (resCr).
No lado do codificador, o componente de crominância da junta é derivado como segue. De acordo com o modo (conforme mostrado na tabela acima), o codificador gera resJointC {1,2} da seguinte maneira:
Se modo = 2 (único residual Cb = C, Cr = CSign * C após a reconstrução), então de acordo com
-resJointC [x] [y] = (resCb [x] [y] + CSign * resCr [x] [y]) / 2.
Caso contrário, se modo = 1 (o único residual reconstruído Cb = C, Cr = (CSign * C) / 2), então de acordo com
-resJointC [x] [y] = (4 * resCb [x] [y] + 2 * CSign * resCr [x] [y]) / 5.
Caso contrário (modo = 3, o único residual reconstruído Cr = C, Cb = (CSign * C) / 2), de acordo com
-resJointC [x] [y] = (4 * resCr [x] [y] + 2 * CSign * resCb [x] [y]) / 5.
tu_cbf_cb |
tu_cbf_cr |
reconstrução de resíduos de Cb e Cr |
modo |
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 |
Os três modos de codificação de croma conjunta acima são suportados apenas no I-chip. Em filmes P e B, apenas o modo 2 é compatível. Portanto, em fatias P e B, o elemento de sintaxe tu_joint_cbcr_residual_flag só existe quando os cbfs de croma são 1 (ou seja, tu_cbf_cb = 1 e tu_cbf_cr = 1).
O modo JCCR pode ser combinado com o modo Transform Skip (TS). A fim de acelerar a velocidade de seleção da extremidade de codificação, a escolha da transformação JCCR depende se a codificação independente dos componentes Cb e Cr seleciona DCT-2 ou TS como a melhor transformação, e se há coeficientes diferentes de zero na codificação de crominância independente. Especificamente dividido nas duas situações a seguir:
(1) Quando os componentes Cb e Cr usam DCT-2 ou quando apenas um componente de crominância (Cb ou Cr) tem um coeficiente diferente de zero e o componente de crominância seleciona DCT-2, o modo JCCR verifica apenas DCT-2. Da mesma forma, quando os componentes Cb e Cr usam TS, ou quando apenas um componente de croma tem um coeficiente diferente de zero e o componente seleciona TS, o modo JCCR verifica apenas TS.
(2) Quando as transformações seletivas dos componentes Cb e Cr são diferentes, ou seja, quando um escolhe DCT-2 e o outro escolhe TS ou vice-versa, o JCCR testa tanto o DCT-2 quanto o TS. Nestes casos, a verificação RD do modo DCT-2 é executada antes do modo TS e, se os coeficientes do DCT-2 forem todos zero, a verificação TS RD é ignorada.
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>;
}
}
A função invTransformCbCr é a seguinte, a função é principalmente para obter o residual Cr (Cb) através do residual Cb (Cr)
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; }
}
}
}
A função fwdTransformCbCr é encontrar ResJointC por meio de ResCb e ResCr
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);
}