在HEVC中,支持33种角度模式、DC模式和Planar模式,为了减少编码比特,使用长度为3的最可能模式列表。在VVC中,引入了ISP模式、MRL模式、MIP模式等,帧内模式编码时需要先对这些模式的flag进行编码。VVC将角度模式扩展到了65种角度模式,因此,将MPM列表相应地扩展到了长度6。这里,Planar模式总是在MPM列表中,且有单独的flag表示。
1. 帧内亮度模式编码
帧内亮度模式编码流程如上图所示,其中包括MIP模式、MRL模式、ISP模式以及MPM相关的语法元素。
首先编码mip_flag,如果mip_flag为1则进一步编码mip_transpose_flag和mip_mode,mip_transpose_flag表示是否需要将MIP矩阵转置,mip_mode表示所选择的MIP模式,编码完mip_transpose_flag和mip_mode之后就会退出,不再对其余模式进行编码。
如果mip_flag为0,则下一步编码mrl_idx,则多参考行的索引,如果mrl_idx大于0,则下一步需要编码MPM_index(使用多参考行时,帧内预测模式只能是MPM列表中的除Planar以外的模式)。如果mrl_idx为0,表示不使用多参考行模式,下一步继续编码isp_flag,isp_flag表示是否使用ISP模式,如果isp_flag为1,则需要进一步编码isp_split_flag,表示ISP是垂直划分还是水平划分方式。之后,需要编码mpm_flag,当mpm_flag为1时,表示预测模式使用的是MPM列表中的模式,需要进一步编码planar_flag,如果planar_flag为1表示应用Planar模式,否则需要编码mpm_idx以确定应用MPM中的五个非Planar中的哪一个模式;如果mpm_flag为0,则需要对非MPM模式进行编码。
MPM模式列表是通过相邻PU构建的,具体构建过程参考:H.266/VVC代码学习:MPM列表建立(getIntraMPMs函数)
各语法元素的编码方式如下表所示:(其中FL表示定长码,TB表示截断二进制码,TR表示截断莱斯码,各编码方式的细节参考:H.266/VVC熵编码之二进制化)
intra_mip_flag |
FL |
cMax = 1 |
intra_mip_transposed_flag[ ][ ] |
FL |
cMax = 1 |
intra_mip_mode[ ][ ] |
TB |
cMax = ( cbWidth = = 4 && cbHeight = = 4 ) ? 15 : ( ( ( cbWidth = = 4 | | cbHeight = = 4 ) | | ( cbWidth = = 8 && cbHeight = = 8 ) ) ? 7 : 5 ) |
intra_luma_ref_idx |
TR |
cMax = 2, cRiceParam = 0 |
intra_subpartitions_mode_flag |
FL |
cMax = 1 |
intra_subpartitions_split_flag |
FL |
cMax = 1 |
intra_luma_mpm_flag[ ][ ] |
FL |
cMax = 1 |
intra_luma_not_planar_flag[ ][ ] |
FL |
cMax = 1 |
intra_luma_mpm_idx[ ][ ] |
TR |
cMax = 4, cRiceParam = 0 |
intra_luma_mpm_remainder[ ][ ] |
TB |
cMax = 60 |
void CABACWriter::intra_luma_pred_modes( const CodingUnit& cu )
{
if( !cu.Y().valid() )
{
return;
}
if( cu.bdpcmMode )
{
cu.firstPU->intraDir[0] = cu.bdpcmMode == 2? VER_IDX : HOR_IDX;
return;
}
mip_flag(cu); //编码MIP标志
if (cu.mipFlag)
{
// 如果当前模式是MIP,则编码MIP转置标志和MIP模式号
mip_pred_modes(cu);
return;
}
extend_ref_line( cu ); // 编码MRL索引
isp_mode( cu ); // 编码ISP模式
const int numMPMs = NUM_MOST_PROBABLE_MODES; // MPM模式数
const int numBlocks = CU::getNumPUs( cu );
unsigned mpm_preds [4][numMPMs]; // 4表示CU中最多包含四个PU,表示每个PU中有numMPMs个MPM模式
unsigned mpm_idxs [4];
unsigned ipred_modes [4];
const PredictionUnit* pu = cu.firstPU;
// prev_intra_luma_pred_flag 遍历CU中全部PU
for( int k = 0; k < numBlocks; k++ )
{
unsigned* mpm_pred = mpm_preds[k];
unsigned& mpm_idx = mpm_idxs[k];
unsigned& ipred_mode = ipred_modes[k];
PU::getIntraMPMs( *pu, mpm_pred ); // 获取MPM列表
ipred_mode = pu->intraDir[0]; // 当前PU的模式
mpm_idx = numMPMs; // 将mpm_idx初始化为6
// 遍历MPM列表,如果当前模式在MPM列表中,则将对应的索引赋给mpm_idx
for( unsigned idx = 0; idx < numMPMs; idx++ )
{
if( ipred_mode == mpm_pred[idx] )
{
mpm_idx = idx;
break;
}
}
if ( pu->multiRefIdx )
{
CHECK(mpm_idx >= numMPMs, "use of non-MPM");
}
else
{
m_BinEncoder.encodeBin(mpm_idx < numMPMs, Ctx::IntraLumaMpmFlag()); // 编码MPM标志,如果mpm_idx小于6即表示当前模式在MPM列表中,编码其在MPM中的索引即可
}
pu = pu->next;
}
pu = cu.firstPU;
// mpm_idx / rem_intra_luma_pred_mode
for( int k = 0; k < numBlocks; k++ ) // 遍历全部PU
{
const unsigned& mpm_idx = mpm_idxs[k];// 每个PU对应的mpm_idx
if( mpm_idx < numMPMs ) // 如果mpm_idx小于6,说明该PU的预测模式在MP列表中,仅编码MPM索引即可
{
{
unsigned ctx = (pu->cu->ispMode == NOT_INTRA_SUBPARTITIONS ? 1 : 0);
if (pu->multiRefIdx == 0) // 当参考行索引大于0时,不可能是Planar模式
m_BinEncoder.encodeBin(mpm_idx > 0, Ctx::IntraLumaPlanarFlag(ctx)); // Planar flag
if( mpm_idx )
{
m_BinEncoder.encodeBinEP( mpm_idx > 1 );
}
if (mpm_idx > 1)
{
m_BinEncoder.encodeBinEP(mpm_idx > 2);
}
if (mpm_idx > 2)
{
m_BinEncoder.encodeBinEP(mpm_idx > 3);
}
if (mpm_idx > 3)
{
m_BinEncoder.encodeBinEP(mpm_idx > 4);
}
}
}
else
{
unsigned* mpm_pred = mpm_preds[k]; //PU的MPM模式
unsigned ipred_mode = ipred_modes[k]; //PU的预测模式
// sorting of MPMs
std::sort( mpm_pred, mpm_pred + numMPMs ); // 排序
{
for (int idx = numMPMs - 1; idx >= 0; idx--)
{
if (ipred_mode > mpm_pred[idx])
{
ipred_mode--;
}
}
CHECK(ipred_mode >= 64, "Incorrect mode");
// 截断二进制编码剩余的模式
xWriteTruncBinCode(ipred_mode, NUM_LUMA_MODE - NUM_MOST_PROBABLE_MODES); // Remaining mode is truncated binary coded
}
}
DTRACE( g_trace_ctx, D_SYNTAX, "intra_luma_pred_modes() idx=%d pos=(%d,%d) mode=%d\n", k, pu->lumaPos().x, pu->lumaPos().y, pu->intraDir[0] );
pu = pu->next;
}
}
2. 帧内色度模式编码
VVC中总共包括8种帧内色度模式:Planar、垂直、水平、DC、DM以及CCLM的三种模式。
在帧内色度模式编码时,首先编码一位bit(cclm_mode_flag)来表示当前模式是三种CCLM模式还是非CCLM模式。如果当前模式是CCLM模式,则需要进一步编码当前模式CCLM模式之中的哪一个模式。如果当前模式是非CCLM模式,则编码索引为0-4的intra_chroma_pred_mode,当当前模式是DM模式时,则仅需要编码一位bit来指示DM模式;否则,使用三位定长码指示其是四种非DM模式中的哪一种。
各语法元素的编码方式如下所示:
cclm_mode_flag |
FL |
cMax = 1 |
cclm_mode_idx |
TR |
cMax = 2, cRiceParam = 0 |
intra_chroma_pred_mode |
9.3.3.8 |
- |
Value of intra_chroma_pred_mode |
Bin string |
0 |
100 |
1 |
101 |
2 |
110 |
3 |
111 |
4 |
0 |
可以得到帧内色度各预测模式的编码比特如下表所示:
色度模式编号 |
码流 |
4(DM模式) |
00 |
0(Planar模式) |
0100 |
1(垂直模式) |
0101 |
2(水平模式) |
0110 |
3(DC模式) |
0111 |
5(LM模式) |
10 |
6(LM_L模式) |
110 |
7(LM_T模式) |
111 |
帧内色度预测模式编码相关代码:
void CABACWriter::intra_chroma_pred_mode(const PredictionUnit& pu)
{
const unsigned intraDir = pu.intraDir[1];
if (pu.cu->colorTransform) // ACT模式下使用DM模式,不对色度模式编码
{
CHECK(pu.intraDir[CHANNEL_TYPE_CHROMA] != DM_CHROMA_IDX, "chroma should use DM for adaptive color transform");
return;
}
if (pu.cs->sps->getUseLMChroma() && pu.cu->checkCCLMAllowed())
{
m_BinEncoder.encodeBin(PU::isLMCMode(intraDir) ? 1 : 0, Ctx::CclmModeFlag(0)); // 如果是CCLM模式,编码1,否则编码0
if (PU::isLMCMode(intraDir))
{
intra_chroma_lmc_mode(pu); // CCLM模式编码
return;
}
}
const bool isDerivedMode = intraDir == DM_CHROMA_IDX;
m_BinEncoder.encodeBin(isDerivedMode ? 0 : 1, Ctx::IntraChromaPredMode(0)); //DM模式
if (isDerivedMode)
{
return;
}
// chroma candidate index
unsigned chromaCandModes[NUM_CHROMA_MODE];
PU::getIntraChromaCandModes(pu, chromaCandModes); // 获取色度候选模式列表
int candId = 0;
for (; candId < NUM_CHROMA_MODE; candId++) // 遍历模式列表,找到当前的模式
{
if (intraDir == chromaCandModes[candId])
{
break;
}
}
CHECK(candId >= NUM_CHROMA_MODE, "Chroma prediction mode index out of bounds");
CHECK(chromaCandModes[candId] == DM_CHROMA_IDX, "The intra dir cannot be DM_CHROMA for this path");
{
m_BinEncoder.encodeBinsEP(candId, 2); // 使用两位bit编码
}
}
CCLM模式相关语法元素的编码 :
void CABACWriter::intra_chroma_lmc_mode(const PredictionUnit& pu)
{
const unsigned intraDir = pu.intraDir[1];
int lmModeList[10];
PU::getLMSymbolList(pu, lmModeList); // LM模式列表LM LM_L LM_T
int symbol = -1;
for (int k = 0; k < LM_SYMBOL_NUM; k++)
{
if (lmModeList[k] == intraDir)
{
symbol = k;
break;
}
}
CHECK(symbol < 0, "invalid symbol found");
m_BinEncoder.encodeBin(symbol == 0 ? 0 : 1, Ctx::CclmModeIdx(0)); // 编码一位表示是否是LM模式
if (symbol > 0)
{
CHECK(symbol > 2, "invalid symbol for MMLM");
unsigned int symbol_minus_1 = symbol - 1;
m_BinEncoder.encodeBinEP(symbol_minus_1);
}
}