HM编码器代码阅读(38)——帧内预测(五)帧内预测之正式的预测操作

正式的预测操作


    在前面的操作中,我们已经得到了模式候选列表,但是我们的目的是要得到一个最优的模式,因此我们还需要对这个列表中的模式进行遍历,对于每一个模式,进行预测操作,为了计算率失真代价还必须进行变换量化,根据率失真代价来选取最优的预测模式。得到最优的预测模式的同时,我们也得到了变换系数(因为预测之后我们还进行了变换量化操作)。

    执行正式的预测操作的相关代码在estIntraPredQT中,主要的功能是遍历模式候选列表中的所有模式,对每一个模式进行预测(顺带变换量化),选出最优的预测模式

		// 遍历候选集中的模式
		for( UInt uiMode = 0; uiMode < numModesForFullRD; uiMode++ )
		{
			// set luma prediction mode
			UInt uiOrgMode = uiRdModeList[uiMode];

			pcCU->setLumaIntraDirSubParts ( uiOrgMode, uiPartOffset, uiDepth + uiInitTrDepth );

			// set context models
			// 设置上下文模型
			m_pcRDGoOnSbacCoder->load( m_pppcRDSbacCoder[uiDepth][CI_CURR_BEST] );

			// determine residual for partition
			UInt   uiPUDistY = 0;
			UInt   uiPUDistC = 0;
			Double dPUCost   = 0.0;
#if HHI_RQT_INTRA_SPEEDUP

			// 通过多候选模式进行预测、变换、量化等操作来计算代价
			// 注意倒数第二个参数bCheckFirst是true,表示会继续按照四叉树的方式向下划分
			xRecurIntraCodingQT( pcCU, uiInitTrDepth, uiPartOffset, bLumaOnly, pcOrgYuv, pcPredYuv, pcResiYuv, uiPUDistY, uiPUDistC, true, dPUCost );
			// 重要函数end
#else
			xRecurIntraCodingQT( pcCU, uiInitTrDepth, uiPartOffset, bLumaOnly, pcOrgYuv, pcPredYuv, pcResiYuv, uiPUDistY, uiPUDistC, dPUCost );
#endif

			// check r-d cost
			// 从候选列表中选取最优的模式
			if( dPUCost < dBestPUCost )
			{
#if HHI_RQT_INTRA_SPEEDUP_MOD
				uiSecondBestMode  = uiBestPUMode;
				dSecondBestPUCost = dBestPUCost;
#endif
				uiBestPUMode  = uiOrgMode;
				uiBestPUDistY = uiPUDistY;
				uiBestPUDistC = uiPUDistC;
				dBestPUCost   = dPUCost;

				xSetIntraResultQT( pcCU, uiInitTrDepth, uiPartOffset, bLumaOnly, pcRecoYuv );

				UInt uiQPartNum = pcCU->getPic()->getNumPartInCU() >> ( ( pcCU->getDepth(0) + uiInitTrDepth ) << 1 );
				::memcpy( m_puhQTTempTrIdx,  pcCU->getTransformIdx()       + uiPartOffset, uiQPartNum * sizeof( UChar ) );
				::memcpy( m_puhQTTempCbf[0], pcCU->getCbf( TEXT_LUMA     ) + uiPartOffset, uiQPartNum * sizeof( UChar ) );
				::memcpy( m_puhQTTempCbf[1], pcCU->getCbf( TEXT_CHROMA_U ) + uiPartOffset, uiQPartNum * sizeof( UChar ) );
				::memcpy( m_puhQTTempCbf[2], pcCU->getCbf( TEXT_CHROMA_V ) + uiPartOffset, uiQPartNum * sizeof( UChar ) );
				::memcpy( m_puhQTTempTransformSkipFlag[0], pcCU->getTransformSkip(TEXT_LUMA)     + uiPartOffset, uiQPartNum * sizeof( UChar ) );
				::memcpy( m_puhQTTempTransformSkipFlag[1], pcCU->getTransformSkip(TEXT_CHROMA_U) + uiPartOffset, uiQPartNum * sizeof( UChar ) );
				::memcpy( m_puhQTTempTransformSkipFlag[2], pcCU->getTransformSkip(TEXT_CHROMA_V) + uiPartOffset, uiQPartNum * sizeof( UChar ) );
			}
#if HHI_RQT_INTRA_SPEEDUP_MOD
			else if( dPUCost < dSecondBestPUCost )
			{
				uiSecondBestMode  = uiOrgMode;
				dSecondBestPUCost = dPUCost;
			}
#endif
		} // Mode loop



正式预测操作的入口函数

入口函数是xRecurIntraCodingQT,它的流程如下:

一、检测bCheckFull标识是否为true,如果是true,那么进行下面操作:
    1、检查TransformSkip标识是否为true,如果是true(表示将会跳过变换步骤),进行下面操作
        (1)进行两次遍历,每一次遍历都调用xIntraCodingLumaBlk、xIntraCodingChromaBlk对三个分量进行预测、计算残差(没有变换步骤)、量化
        (2)选取两次遍历中更优的模式
    2、如果TransformSkip是false,那么进行下面操作:
        (1)调用xIntraCodingLumaBlk、xIntraCodingChromaBlk对三个分量进行预测、变换、量化
二、检查bCheckSplit表示是否为true,如果为true,那么对子PU进行递归处理

/*
** 正式的预测操作(顺便进行了变换量化)
** 为了清晰易懂,把不太相关的代码删除,只保留了清晰的架构
*/
Void TEncSearch::xRecurIntraCodingQT( TComDataCU*  pcCU, 
	UInt         uiTrDepth,
	UInt         uiAbsPartIdx, 
	Bool         bLumaOnly,
	TComYuv*     pcOrgYuv, 
	TComYuv*     pcPredYuv, 
	TComYuv*     pcResiYuv, 
	UInt&        ruiDistY,
	UInt&        ruiDistC,
#if HHI_RQT_INTRA_SPEEDUP
	Bool         bCheckFirst,
#endif
	Double&      dRDCost )
{
	
	// 删除不太重要的代码******
	
	Bool    bCheckFull    = ( uiLog2TrSize  <= pcCU->getSlice()->getSPS()->getQuadtreeTULog2MaxSize() );
	Bool    bCheckSplit   = ( uiLog2TrSize  >  pcCU->getQuadtreeTULog2MinSizeInCU(uiAbsPartIdx) );


	// 删除不太重要的代码******

	Bool noSplitIntraMaxTuSize = bCheckFull;
	if(m_pcEncCfg->getRDpenalty() && ! isIntraSlice)
	{
		// in addition don't check split if TU size is less or equal to 16x16 TU size for non-intra slice
		noSplitIntraMaxTuSize = ( uiLog2TrSize  <= min(maxTuSize,4) );

		// if maximum RD-penalty don't check TU size 32x32 
		if(m_pcEncCfg->getRDpenalty()==2)
		{
			bCheckFull    = ( uiLog2TrSize  <= min(maxTuSize,4));
		}
	}
	if( bCheckFirst && noSplitIntraMaxTuSize )
	{
		bCheckSplit = false;
	}

	// 删除不太重要的代码******
	
	Bool    checkTransformSkip  = pcCU->getSlice()->getPPS()->getUseTransformSkip();
	UInt    widthTransformSkip  = pcCU->getWidth ( 0 ) >> uiTrDepth;
	UInt    heightTransformSkip = pcCU->getHeight( 0 ) >> uiTrDepth;
	
	// 删除不太重要的代码******
	
	checkTransformSkip         &= (widthTransformSkip == 4 && heightTransformSkip == 4);
	checkTransformSkip         &= (!pcCU->getCUTransquantBypass(0));
	if ( m_pcEncCfg->getUseTransformSkipFast() )
	{
		checkTransformSkip       &= (pcCU->getPartitionSize(uiAbsPartIdx)==SIZE_NxN);
	}
	
	if( bCheckFull )
	{
		// skip模式为真
		if(checkTransformSkip == true)
		{
			// 删除不太重要的代码******

			// 遍历两次也是为了选取最优的模式,modeId能够决定xIntraCodingLumaBlk的最后一个参数,该参数控制了预测像素如何生成
			for(Int modeId = firstCheckId; modeId < 2; modeId ++)
			{
				// 删除不太重要的代码******
				
				// 亮度块的预测、变换和量化
				xIntraCodingLumaBlk( pcCU, uiTrDepth, uiAbsPartIdx, pcOrgYuv, pcPredYuv, pcResiYuv, singleDistYTmp,default0Save1Load2); 

				singleCbfYTmp = pcCU->getCbf( uiAbsPartIdx, TEXT_LUMA, uiTrDepth );
				//----- code chroma blocks with given intra prediction mode and store Cbf-----
				if( !bLumaOnly )
				{
					// 删除不太重要的代码******
					
					// 色度块的预测、变换和量化
					xIntraCodingChromaBlk ( pcCU, uiTrDepth, uiAbsPartIdx, pcOrgYuv, pcPredYuv, pcResiYuv, singleDistCTmp, 0, default0Save1Load2); 
					xIntraCodingChromaBlk ( pcCU, uiTrDepth, uiAbsPartIdx, pcOrgYuv, pcPredYuv, pcResiYuv, singleDistCTmp, 1, default0Save1Load2); 
					
					// 删除不太重要的代码******
				}
				
				// 删除不太重要的代码******

				// 代价更新
				if(singleCostTmp < dSingleCost)
				{
					dSingleCost   = singleCostTmp;
					uiSingleDistY = singleDistYTmp;
					uiSingleDistC = singleDistCTmp;
					uiSingleCbfY  = singleCbfYTmp;
					uiSingleCbfU  = singleCbfUTmp;
					uiSingleCbfV  = singleCbfVTmp;
					bestModeId    = modeId;
					if(bestModeId == firstCheckId)
					{
						xStoreIntraResultQT(pcCU, uiTrDepth, uiAbsPartIdx,bLumaOnly );
						m_pcRDGoOnSbacCoder->store( m_pppcRDSbacCoder[ uiFullDepth ][ CI_TEMP_BEST ] );
					}
				}
				
				// 删除不太重要的代码******
			}

			// 删除不太重要的代码******
		}
		else
		{
			// 删除不太重要的代码******
			
			xIntraCodingLumaBlk( pcCU, uiTrDepth, uiAbsPartIdx, pcOrgYuv, pcPredYuv, pcResiYuv, uiSingleDistY ); 
			
			// 删除不太重要的代码******
			
			if( !bLumaOnly )
			{
				// 删除不太重要的代码******
				
				xIntraCodingChromaBlk ( pcCU, uiTrDepth, uiAbsPartIdx, pcOrgYuv, pcPredYuv, pcResiYuv, uiSingleDistC, 0 ); 
				xIntraCodingChromaBlk ( pcCU, uiTrDepth, uiAbsPartIdx, pcOrgYuv, pcPredYuv, pcResiYuv, uiSingleDistC, 1 ); 
				
				// 删除不太重要的代码******
			}
			
			// 删除不太重要的代码******
		}
	}

	// 当前块是否向下继续划分为4个子块,如果是,那么就递归处理
	if( bCheckSplit )
	{
		// 删除不太重要的代码******

		for( UInt uiPart = 0; uiPart < 4; uiPart++, uiAbsPartIdxSub += uiQPartsDiv )
		{
#if HHI_RQT_INTRA_SPEEDUP
			// 递归调用
			xRecurIntraCodingQT( pcCU, uiTrDepth + 1, uiAbsPartIdxSub, bLumaOnly, pcOrgYuv, pcPredYuv, pcResiYuv, uiSplitDistY, uiSplitDistC, bCheckFirst, dSplitCost );
#else
			xRecurIntraCodingQT( pcCU, uiTrDepth + 1, uiAbsPartIdxSub, bLumaOnly, pcOrgYuv, pcPredYuv, pcResiYuv, uiSplitDistY, uiSplitDistC, dSplitCost );
#endif

			// 删除不太重要的代码******
		}

		// 删除不太重要的代码******
		
		//----- determine rate and r-d cost -----
		UInt uiSplitBits = xGetIntraBitsQT( pcCU, uiTrDepth, uiAbsPartIdx, true, !bLumaOnly, false );
		dSplitCost       = m_pcRdCost->calcRdCost( uiSplitBits, uiSplitDistY + uiSplitDistC );

		//===== compare and set best =====
		if( dSplitCost < dSingleCost )
		{
			//--- update cost ---
			ruiDistY += uiSplitDistY;
			ruiDistC += uiSplitDistC;
			dRDCost  += dSplitCost;
			return;
		}
		
		// 删除不太重要的代码******

		//--- set reconstruction for next intra prediction blocks ---
		
		// 到这里是像素块的重建操作,这里把相关的代码删了
	}
	ruiDistY += uiSingleDistY;
	ruiDistC += uiSingleDistC;
	dRDCost  += dSingleCost;
}


对某个分量进行预测(顺带变换量化)

流程很简单:

1、先进行预测,得到预测像素

2、计算残差

3、对残差进行变换、量化

4、进行反变换、反量化

5、重建像素块

/*
** 帧内预测的一整套流程:预测+变换量化+反变换反量化+重建像素
** 被xRecurIntraCodingQT调用
*/
Void TEncSearch::xIntraCodingLumaBlk( TComDataCU* pcCU,
	UInt        uiTrDepth,
	UInt        uiAbsPartIdx,
	TComYuv*    pcOrgYuv, 
	TComYuv*    pcPredYuv, 
	TComYuv*    pcResiYuv, 
	UInt&       ruiDist,
	Int        default0Save1Load2 )
{
	// 获取亮度块的预测模式
	UInt    uiLumaPredMode    = pcCU     ->getLumaIntraDir     ( uiAbsPartIdx );
	// 获取深度
	UInt    uiFullDepth       = pcCU     ->getDepth   ( 0 )  + uiTrDepth;
	// 获取宽高
	UInt    uiWidth           = pcCU     ->getWidth   ( 0 ) >> uiTrDepth;
	UInt    uiHeight          = pcCU     ->getHeight  ( 0 ) >> uiTrDepth;
	// 获取偏移
	UInt    uiStride          = pcOrgYuv ->getStride  ();
	// 原始的像素地址
	Pel*    piOrg             = pcOrgYuv ->getLumaAddr( uiAbsPartIdx );
	// 预测的像素地址
	Pel*    piPred            = pcPredYuv->getLumaAddr( uiAbsPartIdx );
	// 残差的像素地址
	Pel*    piResi            = pcResiYuv->getLumaAddr( uiAbsPartIdx );
	// 重建的像素地址
	Pel*    piReco            = pcPredYuv->getLumaAddr( uiAbsPartIdx );

	UInt    uiLog2TrSize      = g_aucConvertToBit[ pcCU->getSlice()->getSPS()->getMaxCUWidth() >> uiFullDepth ] + 2;
	UInt    uiQTLayer         = pcCU->getSlice()->getSPS()->getQuadtreeTULog2MaxSize() - uiLog2TrSize;
	UInt    uiNumCoeffPerInc  = pcCU->getSlice()->getSPS()->getMaxCUWidth() * pcCU->getSlice()->getSPS()->getMaxCUHeight() >> ( pcCU->getSlice()->getSPS()->getMaxCUDepth() << 1 );
	// 系数(实际是一个int数组)
	TCoeff* pcCoeff           = m_ppcQTTempCoeffY[ uiQTLayer ] + uiNumCoeffPerInc * uiAbsPartIdx;

#if ADAPTIVE_QP_SELECTION
	Int*    pcArlCoeff        = m_ppcQTTempArlCoeffY[ uiQTLayer ] + uiNumCoeffPerInc * uiAbsPartIdx;
#endif

	Pel*    piRecQt           = m_pcQTTempTComYuv[ uiQTLayer ].getLumaAddr( uiAbsPartIdx );
	UInt    uiRecQtStride     = m_pcQTTempTComYuv[ uiQTLayer ].getStride  ();

	// Z扫描的顺序
	UInt    uiZOrder          = pcCU->getZorderIdxInCU() + uiAbsPartIdx;
	Pel*    piRecIPred        = pcCU->getPic()->getPicYuvRec()->getLumaAddr( pcCU->getAddr(), uiZOrder );
	UInt    uiRecIPredStride  = pcCU->getPic()->getPicYuvRec()->getStride  ();
	Bool    useTransformSkip  = pcCU->getTransformSkip(uiAbsPartIdx, TEXT_LUMA);
	//===== init availability pattern =====

	// 上方是否有效
	Bool  bAboveAvail = false;
	// 左侧是否有效
	Bool  bLeftAvail  = false;

	// default0Save1Load2参数控制了预测像素的生成方式
	if(default0Save1Load2 != 2)
	{
		pcCU->getPattern()->initPattern   ( pcCU, uiTrDepth, uiAbsPartIdx );

		pcCU->getPattern()->initAdiPattern( pcCU, uiAbsPartIdx, uiTrDepth, m_piYuvExt, m_iYuvExtStride, m_iYuvExtHeight, bAboveAvail, bLeftAvail );

		// 预测操作
		predIntraLumaAng( pcCU->getPattern(), uiLumaPredMode, piPred, uiStride, uiWidth, uiHeight, bAboveAvail, bLeftAvail );
		// save prediction 
		// 保存预测信息
		if(default0Save1Load2 == 1)
		{
			Pel*  pPred   = piPred;
			Pel*  pPredBuf = m_pSharedPredTransformSkip[0];
			Int k = 0;
			for( UInt uiY = 0; uiY < uiHeight; uiY++ )
			{
				for( UInt uiX = 0; uiX < uiWidth; uiX++ )
				{
					pPredBuf[ k ++ ] = pPred[ uiX ];
				}
				pPred += uiStride;
			}
		}
	}
	else 
	{
		// load prediction
		
		// 直接计算预测值!
		Pel*  pPred   = piPred;
		Pel*  pPredBuf = m_pSharedPredTransformSkip[0];
		Int k = 0;
		for( UInt uiY = 0; uiY < uiHeight; uiY++ )
		{
			for( UInt uiX = 0; uiX < uiWidth; uiX++ )
			{
				pPred[ uiX ] = pPredBuf[ k ++ ];
			}
			pPred += uiStride;
		}
	}


	//===== get residual signal =====
	{
		// get residual
		
		// 计算残差
		Pel*  pOrg    = piOrg;
		Pel*  pPred   = piPred;
		Pel*  pResi   = piResi;
		for( UInt uiY = 0; uiY < uiHeight; uiY++ )
		{
			for( UInt uiX = 0; uiX < uiWidth; uiX++ )
			{
				// 此处计算残差数据
				pResi[ uiX ] = pOrg[ uiX ] - pPred[ uiX ];
			}
			pOrg  += uiStride;
			pResi += uiStride;
			pPred += uiStride;
		}
	}

	// 变换和量化
	//===== transform and quantization =====
	//--- init rate estimation arrays for RDOQ ---
	// 是否跳过变换操作
	if( useTransformSkip? m_pcEncCfg->getUseRDOQTS():m_pcEncCfg->getUseRDOQ())
	{
		// 比特数估计
		m_pcEntropyCoder->estimateBit( m_pcTrQuant->m_pcEstBitsSbac, uiWidth, uiWidth, TEXT_LUMA );
	}

	//--- transform and quantization ---
	UInt uiAbsSum = 0;
	pcCU       ->setTrIdxSubParts ( uiTrDepth, uiAbsPartIdx, uiFullDepth );

	m_pcTrQuant->setQPforQuant    ( pcCU->getQP( 0 ), TEXT_LUMA, pcCU->getSlice()->getSPS()->getQpBDOffsetY(), 0 );

#if RDOQ_CHROMA_LAMBDA 
	m_pcTrQuant->selectLambda     (TEXT_LUMA);  
#endif

	// 变换(连同量化一起)
	m_pcTrQuant->transformNxN     ( pcCU, piResi, uiStride, pcCoeff, 
#if ADAPTIVE_QP_SELECTION
		pcArlCoeff, 
#endif
		uiWidth, uiHeight, uiAbsSum, TEXT_LUMA, uiAbsPartIdx,useTransformSkip );

	//--- set coded block flag ---
	pcCU->setCbfSubParts          ( ( uiAbsSum ? 1 : 0 ) << uiTrDepth, TEXT_LUMA, uiAbsPartIdx, uiFullDepth );
	//--- inverse transform ---
	
	// uiAbsSum表示变换系数的绝对值只和
	if( uiAbsSum )
	{
		Int scalingListType = 0 + g_eTTable[(Int)TEXT_LUMA];
		assert(scalingListType < SCALING_LIST_NUM);

		// 反变换
		m_pcTrQuant->invtransformNxN( pcCU->getCUTransquantBypass(uiAbsPartIdx), TEXT_LUMA,pcCU->getLumaIntraDir( uiAbsPartIdx ), piResi, uiStride, pcCoeff, uiWidth, uiHeight, scalingListType, useTransformSkip );
	}
	else
	{
		Pel* pResi = piResi;
		memset( pcCoeff, 0, sizeof( TCoeff ) * uiWidth * uiHeight );
		for( UInt uiY = 0; uiY < uiHeight; uiY++ )
		{
			memset( pResi, 0, sizeof( Pel ) * uiWidth );
			pResi += uiStride;
		}
	}

	// 图像重建
	//===== reconstruction =====
	{
		Pel* pPred      = piPred;
		Pel* pResi      = piResi;
		Pel* pReco      = piReco;
		Pel* pRecQt     = piRecQt;
		Pel* pRecIPred  = piRecIPred;
		for( UInt uiY = 0; uiY < uiHeight; uiY++ )
		{
			for( UInt uiX = 0; uiX < uiWidth; uiX++ )
			{
				pReco    [ uiX ] = ClipY( pPred[ uiX ] + pResi[ uiX ] );
				pRecQt   [ uiX ] = pReco[ uiX ];
				pRecIPred[ uiX ] = pReco[ uiX ];
			}
			pPred     += uiStride;
			pResi     += uiStride;
			pReco     += uiStride;
			pRecQt    += uiRecQtStride;
			pRecIPred += uiRecIPredStride;
		}
	}

	//===== update distortion =====
	// 失真代价更新!
	ruiDist += m_pcRdCost->getDistPart(g_bitDepthY, piReco, uiStride, piOrg, uiStride, uiWidth, uiHeight );
}


预测函数

流程也很简单:

1、判断模式属于哪一类:planar模式、水平类型的模式、垂直类型的模式、DC模式

2、根据模式来调用不同的函数,进行预测

// 亮度块的帧内预测
Void TComPrediction::predIntraLumaAng(TComPattern* pcTComPattern, UInt uiDirMode, Pel* piPred, UInt uiStride, Int iWidth, Int iHeight, Bool bAbove, Bool bLeft )
{
	Pel *pDst = piPred;
	Int *ptrSrc;

	assert( g_aucConvertToBit[ iWidth ] >= 0 ); //   4x  4
	assert( g_aucConvertToBit[ iWidth ] <= 5 ); // 128x128
	assert( iWidth == iHeight  );

	// 获取数据的指针
	ptrSrc = pcTComPattern->getPredictorPtr( uiDirMode, g_aucConvertToBit[ iWidth ] + 2, m_piYuvExt );

	// get starting pixel in block
	// 获取块中的开始像素
	Int sw = 2 * iWidth + 1; // 9

	// Create the prediction
	// 如果指定了planar模式
	if ( uiDirMode == PLANAR_IDX )
	{
		// planar预测模式
		xPredIntraPlanar( ptrSrc+sw+1, sw, pDst, uiStride, iWidth, iHeight );
	}
	else
	{
		// 没有指定planar模式
		// 区分水平或者垂直模式进行预测
		if ( (iWidth > 16) || (iHeight > 16) )
		{
			xPredIntraAng(g_bitDepthY, ptrSrc+sw+1, sw, pDst, uiStride, iWidth, iHeight, uiDirMode, bAbove, bLeft, false );
		}
		else
		{
			xPredIntraAng(g_bitDepthY, ptrSrc+sw+1, sw, pDst, uiStride, iWidth, iHeight, uiDirMode, bAbove, bLeft, true );

			// 观察是否为DC模式
			if( (uiDirMode == DC_IDX ) && bAbove && bLeft )
			{
				// 对DC模式进行滤波
				xDCPredFiltering( ptrSrc+sw+1, sw, pDst, uiStride, iWidth, iHeight);
			}
		}
	}
}


预测的实现细节

下面这些函数基本都是某个公式的实现,详细信息请参考标准

planar模式:xPredIntraPlanar

DC模式和其他模式:xPredIntraAng


变换和量化

处理变换和量化的函数是transformNxN,这个函数等讲解变换量化的时候再细讲

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值