在HEVC中,支持33种角度模式、DC模式和Planar模式,为了减少编码比特,使用长度为3的最可能模式列表。在VVC中,引入了ISP模式、MRL模式、MIP模式等,帧内模式编码时需要先对这些模式的flag进行编码。VVC将角度模式扩展到了65种角度模式,因此,将MPM列表相应地扩展到了长度6。这里,Planar模式总是在MPM列表中,且有单独的flag表示。
帧内亮度模式编码流程如上图所示,其中包括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; } }
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); } }