VVCの量子化モジュールは、依存スカラー量子化という新しいテクノロジーを使用しています。スカラー依存の量子化とは、変換係数の許容可能な再構成値のセットが、再構成順序の現在の変換係数レベルの前の変換係数レベルの値に依存することを意味します。HEVCで使用される従来の独立スカラー量子化と比較すると、この方法の主な効果は、許可される再構成ベクトルがN次元ベクトル空間でより密になることです(Nは変換ブロック内の変換係数の数を表します)。これは、N次元の単位体積あたりの許容可能な再構成ベクトルの特定の平均数に対して、入力ベクトルと最も近い再構成ベクトルの間の平均歪みが減少することを意味します。
スカラー量子化に依存する実装プロセスは次のとおりです。
- 再構成レベルが異なる2つのスカラー量子化器を定義します。
- 2つのスカラー量子化器間の変換方法を定義します。
使用される2つのスカラー量子化器は、上の図に示すように、Q0とQ1で表されます。利用可能な再構成レベルの位置は、量子化ステップδ(デルタ)によって一意に指定されます。使用されるスカラー量子化器(Q0またはQ1)は、ビットストリームで明示的に通知されません。逆に、現在の変換係数に使用される量子化器は、エンコード/再構成の順序で現在の変換係数の前の変換係数レベルのパリティによって決定されます。
上の図に示すように、2つのスカラー量子化器(Q0とQ1)間の変換は、4つの状態を持つステートマシンによって実現されます。ステータスは、0、1、2、3の4つの異なる値を取ることができます。これは、コーディング/再構成順序での現在の変換係数の前の変換係数レベルのパリティによって一意に決定されます。変換ブロックの逆量子化の開始時に、状態は0に設定されます。変換係数はスキャン順序で再構築されます(つまり、同じ順序でエントロピーデコードされます)。現在の変換係数を再構築した後、図に示すように状態を更新します。ここで、kは変換係数レベルの値を表します。
VTMコードでは、DQの入力関数はDepQuant :: quant関数です。主な手順は次のとおりです。
(1)量子化関連パラメータを事前に初期化する
(2)TUの最初の非ゼロ係数を見つけます
(3)すべての状態パラメータを初期化します
(4)すべての係数を後ろから前にトラバースし、対応する量子化状態チェーン、量子化係数、および各係数の量子化状態に対応するRDコストを見つけます。(XDecideAndUpdate関数の実装)
(5)RDコストの最小の定量的状態チェーンを決定します
(6)最適な状態量に従って、Hualianはすべての係数を順方向にスキャンし、対応する量子化された変換係数レベルを格納します。
コードは次のように表示されます。
void DepQuant::quant( TransformUnit& tu, const CCoeffBuf& srcCoeff, const ComponentID compID, const QpParam& cQP, const double lambda, const Ctx& ctx, TCoeff& absSum, bool enableScalingLists, int* quantCoeff )
{
CHECKD( tu.cs->sps->getSpsRangeExtension().getExtendedPrecisionProcessingFlag(), "ext precision is not supported" );
//===== reset / pre-init =====
const TUParameters& tuPars = *g_Rom.getTUPars( tu.blocks[compID], compID );
m_quant.initQuantBlock ( tu, compID, cQP, lambda );//初始化相关量化参数
TCoeff* qCoeff = tu.getCoeffs( compID ).buf;
const TCoeff* tCoeff = srcCoeff.buf;//
const int numCoeff = tu.blocks[compID].area();
::memset( tu.getCoeffs( compID ).buf, 0x00, numCoeff*sizeof(TCoeff) );
absSum = 0;
const CompArea& area = tu.blocks[ compID ];
const uint32_t width = area.width;
const uint32_t height = area.height;
const uint32_t lfnstIdx = tu.cu->lfnstIdx;
//===== scaling matrix ====
//const int qpDQ = cQP.Qp + 1;
//const int qpPer = qpDQ / 6;
//const int qpRem = qpDQ - 6 * qpPer;
//TCoeff thresTmp = thres;
bool zeroOut = false;
bool zeroOutforThres = false;
int effWidth = tuPars.m_width, effHeight = tuPars.m_height;
// MTS或者SBT模式,是否进行高频调零
if( ( tu.mtsIdx[compID] > MTS_SKIP || (tu.cs->sps->getUseMTS() && tu.cu->sbtInfo != 0 && tuPars.m_height <= 32 && tuPars.m_width <= 32)) && compID == COMPONENT_Y)
{
effHeight = (tuPars.m_height == 32) ? 16 : tuPars.m_height;
effWidth = (tuPars.m_width == 32) ? 16 : tuPars.m_width;
zeroOut = (effHeight < tuPars.m_height || effWidth < tuPars.m_width);//是否高频调零
}
zeroOutforThres = zeroOut || (32 < tuPars.m_height || 32 < tuPars.m_width);
//===== find first test position =====
//===== 找到第一个测试位置 =====
int firstTestPos = numCoeff - 1;
if (lfnstIdx > 0 && tu.mtsIdx[compID] != MTS_SKIP && width >= 4 && height >= 4)
{
firstTestPos = ( ( width == 4 && height == 4 ) || ( width == 8 && height == 8 ) ) ? 7 : 15 ;
}
const TCoeff defaultQuantisationCoefficient = (TCoeff)m_quant.getQScale();
const TCoeff thres = m_quant.getLastThreshold();
for( ; firstTestPos >= 0; firstTestPos-- )//反向扫描找到第一个非零系数
{
if (zeroOutforThres && (tuPars.m_scanId2BlkPos[firstTestPos].x >= ((tuPars.m_width == 32 && zeroOut) ? 16 : 32)
|| tuPars.m_scanId2BlkPos[firstTestPos].y >= ((tuPars.m_height == 32 && zeroOut) ? 16 : 32)))
continue;
TCoeff thresTmp = (enableScalingLists) ? TCoeff(thres / (4 * quantCoeff[tuPars.m_scanId2BlkPos[firstTestPos].idx]))
: TCoeff(thres / (4 * defaultQuantisationCoefficient));
if (abs(tCoeff[tuPars.m_scanId2BlkPos[firstTestPos].idx]) > thresTmp)
{
break;
}
}
if( firstTestPos < 0 )
{
return;
}
//===== real init =====
//===== 初始化所有状态 =====
RateEstimator::initCtx( tuPars, tu, compID, ctx.getFracBitsAcess() );
m_commonCtx.reset( tuPars, *this );
for( int k = 0; k < 12; k++ )
{
m_allStates[k].init();
}
m_startState.init();
//高频调零后实际存在系数的边界
int effectWidth = std::min(32, effWidth);
int effectHeight = std::min(32, effHeight);
for (int k = 0; k < 12; k++)
{
m_allStates[k].effWidth = effectWidth;
m_allStates[k].effHeight = effectHeight;
}
m_startState.effWidth = effectWidth;
m_startState.effHeight = effectHeight;
//===== populate trellis =====
//===== 尝试不同的状态 =====
//从后向前遍历所有的系数,针对每一个系数找出其最优的量化状态链
// 这里的扫描顺序使用的是4x4子块扫描顺序的倒序
for( int scanIdx = firstTestPos; scanIdx >= 0; scanIdx-- )
{
const ScanInfo& scanInfo = tuPars.m_scanInfo[ scanIdx ];
if (enableScalingLists)
{
m_quant.initQuantBlock(tu, compID, cQP, lambda, quantCoeff[scanInfo.rasterPos]);
xDecideAndUpdate( abs( tCoeff[scanInfo.rasterPos]), scanInfo, (zeroOut && (scanInfo.posX >= effWidth || scanInfo.posY >= effHeight)), quantCoeff[scanInfo.rasterPos] );
}
else
xDecideAndUpdate( abs( tCoeff[scanInfo.rasterPos]), scanInfo, (zeroOut && (scanInfo.posX >= effWidth || scanInfo.posY >= effHeight)), defaultQuantisationCoefficient );
}
//===== find best path =====
//=====确定RD cost最小的量化状态链 =====
Decision decision = { std::numeric_limits<int64_t>::max(), -1, -2 };
int64_t minPathCost = 0;
for( int8_t stateId = 0; stateId < 4; stateId++ )
{
int64_t pathCost = m_trellis[0][stateId].rdCost;
if( pathCost < minPathCost )
{
decision.prevId = stateId;
minPathCost = pathCost;
}
}
//===== backward scanning =====
//=====根据上面确定的最优量化状态链正向扫描全部系数====
int scanIdx = 0;
for( ; decision.prevId >= 0; scanIdx++ )
{
decision = m_trellis[ scanIdx ][ decision.prevId ];
int32_t blkpos = tuPars.m_scanId2BlkPos[scanIdx].idx;
qCoeff[ blkpos ] = ( tCoeff[ blkpos ] < 0 ? -decision.absLevel : decision.absLevel );//量化后的系数
absSum += decision.absLevel;
}
}
}; // namespace DQIntern
xDecideAndUpdate関数は、xDecide関数を呼び出すことにより、各変換係数の量子化状態、量子化係数、およびRDコストを実装し、各量子化状態チェーンのRDコストを更新します。
void DepQuant::xDecideAndUpdate( const TCoeff absCoeff, const ScanInfo& scanInfo, bool zeroOut, int quantCoeff )
{
Decision* decisions = m_trellis[ scanInfo.scanIdx ];
std::swap( m_prevStates, m_currStates );
xDecide( scanInfo.spt, absCoeff, lastOffset(scanInfo.scanIdx), decisions, zeroOut, quantCoeff );
if( scanInfo.scanIdx )
{
if( scanInfo.eosbb )
{
m_commonCtx.swap();
m_currStates[0].updateStateEOS( scanInfo, m_prevStates, m_skipStates, decisions[0] );
m_currStates[1].updateStateEOS( scanInfo, m_prevStates, m_skipStates, decisions[1] );
m_currStates[2].updateStateEOS( scanInfo, m_prevStates, m_skipStates, decisions[2] );
m_currStates[3].updateStateEOS( scanInfo, m_prevStates, m_skipStates, decisions[3] );
::memcpy( decisions+4, decisions, 4*sizeof(Decision) );
}
else if( !zeroOut )
{
switch( scanInfo.nextNbInfoSbb.num )
{
case 0:
//更新当前状态为0的rdcost的值为decisions[0].rdcost;
m_currStates[0].updateState<0>( scanInfo, m_prevStates, decisions[0] );
//更新当前状态为1的rdcost的值为decisions[1].rdcost;
m_currStates[1].updateState<0>( scanInfo, m_prevStates, decisions[1] );
//更新当前状态为2的rdcost的值为decisions[2].rdcost;
m_currStates[2].updateState<0>( scanInfo, m_prevStates, decisions[2] );
//更新当前状态为3的rdcost的值为decisions[3].rdcost;
m_currStates[3].updateState<0>( scanInfo, m_prevStates, decisions[3] );
break;
case 1:
m_currStates[0].updateState<1>( scanInfo, m_prevStates, decisions[0] );
m_currStates[1].updateState<1>( scanInfo, m_prevStates, decisions[1] );
m_currStates[2].updateState<1>( scanInfo, m_prevStates, decisions[2] );
m_currStates[3].updateState<1>( scanInfo, m_prevStates, decisions[3] );
break;
case 2:
m_currStates[0].updateState<2>( scanInfo, m_prevStates, decisions[0] );
m_currStates[1].updateState<2>( scanInfo, m_prevStates, decisions[1] );
m_currStates[2].updateState<2>( scanInfo, m_prevStates, decisions[2] );
m_currStates[3].updateState<2>( scanInfo, m_prevStates, decisions[3] );
break;
case 3:
m_currStates[0].updateState<3>( scanInfo, m_prevStates, decisions[0] );
m_currStates[1].updateState<3>( scanInfo, m_prevStates, decisions[1] );
m_currStates[2].updateState<3>( scanInfo, m_prevStates, decisions[2] );
m_currStates[3].updateState<3>( scanInfo, m_prevStates, decisions[3] );
break;
case 4:
m_currStates[0].updateState<4>( scanInfo, m_prevStates, decisions[0] );
m_currStates[1].updateState<4>( scanInfo, m_prevStates, decisions[1] );
m_currStates[2].updateState<4>( scanInfo, m_prevStates, decisions[2] );
m_currStates[3].updateState<4>( scanInfo, m_prevStates, decisions[3] );
break;
default:
m_currStates[0].updateState<5>( scanInfo, m_prevStates, decisions[0] );
m_currStates[1].updateState<5>( scanInfo, m_prevStates, decisions[1] );
m_currStates[2].updateState<5>( scanInfo, m_prevStates, decisions[2] );
m_currStates[3].updateState<5>( scanInfo, m_prevStates, decisions[3] );
}
}
if( scanInfo.spt == SCAN_SOCSBB )
{
std::swap( m_prevStates, m_skipStates );
}
}
}
void DepQuant::xDecide( const ScanPosType spt, const TCoeff absCoeff, const int lastOffset, Decision* decisions, bool zeroOut, int quanCoeff)
{
::memcpy( decisions, startDec, 8*sizeof(Decision) );
if( zeroOut )
{
if( spt==SCAN_EOCSBB )
{
m_skipStates[0].checkRdCostSkipSbbZeroOut( decisions[0] );
m_skipStates[1].checkRdCostSkipSbbZeroOut( decisions[1] );
m_skipStates[2].checkRdCostSkipSbbZeroOut( decisions[2] );
m_skipStates[3].checkRdCostSkipSbbZeroOut( decisions[3] );
}
return;
}
//存储4个预量化值的相关参数
PQData pqData[4];
//对absCoeff进行4次预量化,得到量化后的变换系数level和量化成该值的rdcost,第0个和第3个量化值是偶数,第1个和第2个量化值是奇数;
m_quant.preQuantCoeff( absCoeff, pqData, quanCoeff );
//前一个量化状态是0,则当前状态可以是0或者2,根据rdcost更新decision[0/2].rdcost的值
m_prevStates[0].checkRdCosts( spt, pqData[0], pqData[2], decisions[0], decisions[2]);
//前一个量化状态是1,则当前状态可以是2或者0,根据rdcost更新decision[2/0].rdcost的值
m_prevStates[1].checkRdCosts( spt, pqData[0], pqData[2], decisions[2], decisions[0]);
//前一个量化状态是2,则当前状态可以是1或3,根据rdcost更新decision[1/3].rdcost的值
m_prevStates[2].checkRdCosts( spt, pqData[3], pqData[1], decisions[1], decisions[3]);
//前一个量化状态是3,则当前状态可以是3或者1,根据rdcost更新decision[3/1].rdcost的值
m_prevStates[3].checkRdCosts( spt, pqData[3], pqData[1], decisions[3], decisions[1]);
if( spt==SCAN_EOCSBB )
{
m_skipStates[0].checkRdCostSkipSbb( decisions[0] );
m_skipStates[1].checkRdCostSkipSbb( decisions[1] );
m_skipStates[2].checkRdCostSkipSbb( decisions[2] );
m_skipStates[3].checkRdCostSkipSbb( decisions[3] );
}
//初始化状态0和2的RD Cost
m_startState.checkRdCostStart( lastOffset, pqData[0], decisions[0] );
m_startState.checkRdCostStart( lastOffset, pqData[2], decisions[2] );
}
私が理解していない詳細はまだたくさんあります。。。後で、理解する時間があれば、追加してください