系列文章目录
【x264编码器】章节1——x264编码流程及基于x264的编码器demo
【x264编码器】章节2——x264的lookahead流程分析
【x265编码器】章节2——编码流程及基于x265的编码器demo
前言
x265完整的流程框架如下:
一、变换量化模块
变换量化的类型总的来说,有以下几种情况:
1.useTransformSkip模式,此时会直接复制到量化内存中;
2.4x4帧内模式,使用dst4x4进行4x4的dst变换;
3.常规的dct变换;
4.如果开启了m_rdoqLevel,此时采用RDOQ的变换量化操作;
5.使用m_quantCoef进行常规量化操作;
对应代码分析如下:
uint32_t Quant::transformNxN(const CUData& cu, const pixel* fenc, uint32_t fencStride, const int16_t* residual, uint32_t resiStride,
coeff_t* coeff, uint32_t log2TrSize, TextType ttype, uint32_t absPartIdx, bool useTransformSkip)
{ //首先,根据log2TrSize计算出变换的尺寸索引sizeIdx
const uint32_t sizeIdx = log2TrSize - 2;
//如果cu.m_tqBypass[0]为真,表示使用旁路量化(bypass),直接进行系数复制操作,并返回复制的系数个数。否则,继续执行后续的变换和量化操作
if (cu.m_tqBypass[0])
{
X265_CHECK(log2TrSize >= 2 && log2TrSize <= 5, "Block size mistake!\n");
return primitives.cu[sizeIdx].copy_cnt(coeff, residual, resiStride);
}
//判断是否为亮度分量(isLuma)和是否使用PsyRDOQ(usePsy)。PsyRDOQ是一种基于心理视觉模型的变换系数量化优化方法
bool isLuma = ttype == TEXT_LUMA;
bool usePsy = m_psyRdoqScale && isLuma && !useTransformSkip;
int transformShift = MAX_TR_DYNAMIC_RANGE - X265_DEPTH - log2TrSize; // Represents scaling through forward transform 计算变换的位移量transformShift,用于表示通过正向变换进行的缩放
//检查变换尺寸是否超出了最大允许的尺寸
X265_CHECK((cu.m_slice->m_sps->quadtreeTULog2MaxSize >= log2TrSize), "transform size too large\n");
if (useTransformSkip)
{//如果使用变换跳过(Transform Skip),则根据transformShift的正负值调用不同的函数对残差进行复制和移位操作
#if X265_DEPTH <= 10
X265_CHECK(transformShift >= 0, "invalid transformShift\n");
primitives.cu[sizeIdx].cpy2Dto1D_shl(m_resiDctCoeff, residual, resiStride, transformShift);
#else
if (transformShift >= 0)
primitives.cu[sizeIdx].cpy2Dto1D_shl(m_resiDctCoeff, residual, resiStride, transformShift);
else
primitives.cu[sizeIdx].cpy2Dto1D_shr(m_resiDctCoeff, residual, resiStride, -transformShift);
#endif
}
else
{ //否则,根据不同的情况调用相应的变换函数对残差进行变换,如4x4变换、8x8变换或其他尺寸的变换
bool isIntra = cu.isIntra(absPartIdx);
if (!sizeIdx && isLuma && isIntra)
primitives.dst4x4(residual, m_resiDctCoeff, resiStride);
else
primitives.cu[sizeIdx].dct(residual, m_resiDctCoeff, resiStride);
/* NOTE: if RDOQ is disabled globally, psy-rdoq is also disabled, so
* there is no risk of performing this DCT unnecessarily */
if (usePsy)
{ //如果启用了PsyRDOQ,则对源像素进行DCT变换,并将结果存储在m_fencDctCoeff中
int trSize = 1 << log2TrSize;
/* perform DCT on source pixels for psy-rdoq */
primitives.cu[sizeIdx].copy_ps(m_fencShortBuf, trSize, fenc, fencStride);
primitives.cu[sizeIdx].dct(m_fencShortBuf, m_fencDctCoeff, trSize);
}
if (m_nr && m_nr->offset)
{//如果启用了噪声减弱(Denoising)功能,并且存在噪声减弱参数,则根据不同的情况调用相应的噪声减弱函数对变换系数进行处理
/* denoise is not applied to intra residual, so DST can be ignored */
int cat = sizeIdx + 4 * !isLuma + 8 * !isIntra;
int numCoeff = 1 << (log2TrSize * 2);
primitives.denoiseDct(m_resiDctCoeff, m_nr->residualSum[cat], m_nr->offset[cat], numCoeff);
m_nr->count[cat]++;
}
}
//如果启用了RDOQ(Rate-Distortion Optimization Quantization),则根据不同的变换尺寸调用相应的RDOQ函数对系数进行量化和优化
if (m_rdoqLevel)
return (this->*rdoQuant_func[log2TrSize - 2])(cu, coeff, ttype, absPartIdx, usePsy);
else
{ //否则,根据量化参数和变换系数的值进行量化操作,并返回非零系数的个数
int deltaU[32 * 32];
int scalingListType = (cu.isIntra(absPartIdx) ? 0 : 3) + ttype;
int rem = m_qpParam[ttype].rem;
int per = m_qpParam[ttype].per;
const int32_t* quantCoeff = m_scalingList->m_quantCoef[log2TrSize - 2][scalingListType][rem];
int qbits = QUANT_SHIFT + per + transformShift;
int add = (cu.m_slice->m_sliceType == I_SLICE ? 171 : 85) << (qbits - 9);
int numCoeff = 1 << (log2TrSize * 2);
uint32_t numSig = primitives.quant(m_resiDctCoeff, quantCoeff, deltaU, coeff, qbits, add, numCoeff);
//如果非零系数的个数大于等于2并且启用了符号隐藏(Sign Hide)功能,则对系数进行符号隐藏处理
if (numSig >= 2 && cu.m_slice->m_pps->bSignHideEnabled)
{
TUEntropyCodingParameters codeParams;
cu.getTUEntropyCodingParameters(codeParams, absPartIdx, log2TrSize, isLuma);
return signBitHidingHDQ(coeff, deltaU, numSig, codeParams, log2TrSize);
}
else//返回非零系数的个数
return numSig;
}
}