当前位置: 首页 > 工具软件 > x265 > 使用案例 >

X265编码核心函数分析

汪博艺
2023-12-01

X265编码核心函数分析

compressCTU流程

  1. m_bChromaSa8d = m_pParam->rdLevel >= 3;
  2. 计算i32CtuQp
  3. m_aRqtData[0].m_cEntropyCur.load(cInitEntrConxt); 初始化熵编码环境.推测先做下一层时,要加载下一层的初始环境
  4. 对I_SLICE,调compressIntraCU
    根据rd设置,调用qprdRefine
  5. 对NON_I_SLICE:
    如果是intra_refresh,调compressIntraCU;
    否则,根据rdlevel分别执行compressInterCU_rd0_4或compressInterCU_rd5_6

compressIntraCU流程

  1. 可以不划分时,先调checkIntra检查最佳预测方向,再调用checkBestMode检查是否最佳模式,如果是8x8cu,再调用checkIntra检查NxN的模式(4个PU),调用checkBestMode检查是否最佳模式
  2. 如果可以划分,检查SPLIT模式的rdcost
    1. 每个SUBCU开始检查前要m_aRqtData[i32NextDepth].m_cEntropyCur,第0个SUBCU的初始值为m_aRqtData[u32CuDepth].m_cEntropyCur,后面的SUBCU的初始值为m_modeDepth[i32NextDepth].bestMode->contexts
    2. 调用compressIntraCU检查SUBCU返回rdcost
    3. 保存SUBCU的编码数据,
  3. 调用检查SPLIT模式是否最佳
  4. 保存最佳模式的CU数据到帧级缓存,并保存recon图像

checkIntra流程

当前CU大小做intra最佳预测的计算返回rdcost

  1. getIntraTUQtDepthRange:根据参数设置的TU的最大最小size,以及设置的intraTU大小以及当前CU大小设置TU范围
  2. estIntraPredQT:计算当前大小的CU的最佳模式的distortion(SSE)
    1. 对1或者4个PU(8x8的intraCU可以有4个PU)
    2. 算出35种模式的sadcost,记录最小的sadcost
    3. 以最小sadcost的1.25倍为限,选出最多u32MaxRdCandCount = 2 + m_pParam->rdLevel + ((u32Depth + u32InitTuDepth) >> 1)种cand模式
    4. 如果只有一个候选模式,则此模式为最佳模式
    5. 如果有多个候选模式,对每个候选模式调用codeIntraLumaQT编码(会记录rdcost),记录最小的rdcost以及对应的bestMode
    6. 当前CU设置成bestMode,再次调用codeIntraLumaQT编码,
    7. 调用extractIntraResultQT将每个TU的coeff和recYUV复制出来
    8. 返回distortion(SSE)
  3. 调用estimateCuBitNum估计出最优模式下的bits
  4. 调用calcEnergyAndRdCost算出rdcost

estimateCuBitNum

对intra/inter/skip都会调用,除编码模式信息外,还调用m_cEntropyCoder.codeCoeff编码系数,在Mode中记录产生的mvBits/coeffBits/totalBits

compressInterCU_rd0_4 流程

  1. 调用topSkipMinDepth确定检查的起始CU划分层次(根据前后参考帧的对应CU的包含的子CU的最小深度以及深度和)
  2. STEP 1: SKIP/MERGE
    1. 如果当前CU大小可以不划分且当前深度大于检查的最小深度,并且sDepthMode.bestMode为NULL(),调用checkMerge2Nx2N_rd0_4检查SKIP/MERGE两者中的最佳模式
    2. 根据参数配置计算是否要跳过以后的INTER/INTRA模式检查(bSkipMode,如果checkMerge2Nx2N_rd0_4检查出的最佳模式为SKIP由跳过)
    3. 如果checkMerge2Nx2N_rd0_4选出了最佳模式,则根据配置检查是否要跳过更深的CU划分检查。条件是当前划分的最佳模式是否为SKIP(HM中是在检查完SKIP/MERGE/INTER/INTRA后再来检查最佳是否为SKIP),如果不是SKIP的话,再调用recursionDepthCheckcomplexityCheckCU检查是否要继续划分
  3. STEP 2: SPLIT
    1. 配置熵编码上下文件,初始为m_aRqtData[u32CurDepth].m_cEntropyCur
    2. 对每个SUBCU,先复制fencYuv,再加载熵编码上下文,后面的SUBCU的上下文为sNextDepthMode.bestMode->contexts,上一个SUBCU结束的最佳模式的
    3. 调用compressInterCU_rd0_4检查SUBCU,返回aSplitData,其中信息是使用的参考帧,mvcost/sa8dcost,用于限制大的CU做inter预测时的参数
    4. 记录分割后的SUBCU中是否有使用intra的
    5. 累加SPLIT模式的cost,并复制每个SUBCU的信息到SPLIT对应的CU结构体中
    6. 复制每个SUBCU的reconYUV到rec帧中,用于下一个SUBCU预测
    7. 这里的SPLIT模式的Sa8d并没有和SKIP/MERGE进行比较
  4. STEP 3: INTER
    1. 如果没跳过后面的INTER/RECT/AMP模式,则先调用checkInter_rd0_4检查2NX2N,此时参考帧限制为4个SUBCU使用过的参考帧
    2. 如果是B帧,调用checkBidir2Nx2N检查PRED_BIDIR
    3. 先假设最佳inter模式为PRED_2Nx2N,设置AMP的参考帧为2NX2N使用过的参考帧,调用checkInter_rd0_4检查PRED_2NxN/PRED_Nx2N以及B帧时的RECT,再检查PRED_nRx2N/PRED_nLx2N/PRED_2NxnD/PRED_2NxnU等AMP
    4. 根据sa8dCost找出最佳inter模式
  5. STEP 4: INTRA
    1. 快速模式:
      1. 比较inter与skip/merge/PRED_BIDIR的sa8dcost,选出最优模式
      2. 调用checkIntraInInter检查最佳intra模式(会计算rdcost),再根据sa8d与上面的最优比较
      3. 如果最优模式是inter,调用encodeResAndCalcRdInterCU,如果是intra,调用encodeIntraInInter
    2. 普通模式:
      1. 调encodeResAndCalcRdInterCU算最佳inter(2NX2N/RECT/AMP)的rdcost,与skip/merge的rdcost比较选出最佳
      2. 如果是B帧,在PRED_BIDIR的sa8dcost满足条件时调用encodeResAndCalcRdInterCU算PRED_BIDIR的rdcost最选出最优模式
      3. 如果最优模式的cbf不为0,调用checkIntraInInter和encodeIntraInInter算出rdcost并检查最优模式
  6. STEP 5.
    比较最优模式和SPLIT模式根据rdcost或sa8dcost选出最优模式
  7. 将当前inter模式的信息准备到sSplitCuData中返回给上一层CU使用

checkIntraInInter

  1. 此函数原生只检查了35种模式的sa8dcost,选出Sa8d最小的模式

encodeIntraInInter

  1. codeIntraLumaQT
  2. 调用extractIntraResultQT将变换系数从m_aRqtData[u32QtLayer].m_pCoeffRQT复制到cu.m_trCoeff,同时将recYUV从 m_aRqtData[u32QtLayer].m_cRecYuv复制到sIntraMode.reconYuv
  3. 调用estimateCuBitNum估计bits
  4. 算rdcost

codeIntraLumaQT

在inter/intra中都调用此函数对已确定预测模式的CU进行变换编码并计算rdcost,TU的大小根据参数设置检查多层。在estIntraPredQT/encodeIntraInInter中被调用,以及递归调用自己,对系数进行变换并编码,初始调用时tuDepth为0或1
注意:m_aRqtData[u32QtLayer]这里的u32QtLayer与ModeDepth里的方向是反的,u32QtLayer为0时为最小的TU(4x4)

  1. 如果可以不分割,直接算TU
    1. 先保存当前熵环境
    2. 用Fenc和Pred算出残差
    3. 调用transQuant进行变换和量化,返回非0系数个数
    4. 如果有非0系数,先调InvTransQuant进行反量化和反变换,再与pred相加,重建系数保存到m_aRqtData[u32QtLayer].m_cRecYuv;如果无非0系数,预测值就是重建
    5. 算SSE失真
    6. 调用estIntraPredLumaBits计算bits
    7. 算出rdcost
  2. 如果可以分割
    1. 先保存不分割时的熵状态,并加载不分割时编码前的熵状态
    2. 分割成4个TU,分别递归调codeIntraLumaQT
    3. 统计CBF
    4. 算分割后的rdcost
    5. 与不分割情况比较rdcost,先最小rdcost的模式设置CU对应信息
  3. 返回rdcost

transQuant

  1. 调用transform进行变换
  2. 根据是否rdoq调用rdoQuant或quant进行量化
  3. 返回非0系数个数

checkMerge2Nx2N_rd0_4

  1. 调用getInterMergeCandidates得到MergeCand的数量
  2. 对每个MergeCand,调用motionCompensation进行预测,再调用sa8d计算出残差的SAD,再计算这个mergeIdx的bits。如果在SAD中需要包含UV,再加入UV的sad。选出sad最小的MergeCand
  3. 调用encodeResAndCalcRdSkipCU计算出SKIP模式的full rdcost,并设置当前模式为MODE_SKIP。
  4. 调用encodeResAndCalcRdInterCU计算inter模式的rdcost,与SKIP模式比较rdcost后,设置sDepthMode.bestMode并返回

checkInter_rd0_4

  1. predInterSearch进行ME
  2. 算fenc与pred的sa8d
  3. 算sa8dCost

predInterSearch

对CU中的每个PU(1个或2个):

  1. setupSourceBlock
  2. 对两个方向分别搜索,每个方向搜索所有参考帧
    1. 调用getPMV获取最多2个MVP,以及最多10个MVC作为搜索开始点
    2. 调用selectMVP从2个MVP中选择一个sad最小的作为语法中的mvpIdx
    3. 调用motionSearch进行ME,返回satdCost,motionSearch只计算单向的ME
    4. 计算Bits以及cost
    5. 调用checkBestMVP再次检查前面选的mvpIdx是否是最佳的,不是则更新
    6. 记录最佳参考帧及最小cost
  3. (slow档次下,开启RECT/AMP时)对非2Nx2N大小的PU,检查Bpred模式以及merge模式
  4. 最后对最佳模式调用motionCompensation,将预测值记录到Mode.predYuv中

motionSearch

checkBidir2Nx2N

非2Nx2N的Bidir在predInterSearch中计算

  1. 使用predInterSearch中得到的两个方向的MV/REF,调用motionCompensation进行预测
  2. 计算pred的sa8d以及bits和对应的sa8dCost
  3. 检查zero MV是否更好
  4. 记录最佳的sa8dCost和sa8dBits

encodeResAndCalcRdSkipCU

不需要变换和量化,只直接计算SSE和Bits就算出rdcost

encodeResAndCalcRdInterCU

  1. 先计算残差存储在m_aRqtData[u32Depth].m_cTempResiYuv
  2. 调用estimateResidualQT计算出rdcost

estimateResidualQT

  1. 检查不做TU划分
    1. 调用transQuant进行LUMA的变换和量化,返回非0系数个数
    2. 记录cbf
    3. 调用codeCoeffNxN编码系数,记录bits(视上下文可能需要编码codeTransformSubdivFlag)
    4. 如果有非0系数,
      1. 调用InvTransQuant进行反量化和反变换,再重建记录到m_aRqtData[u32QtLayer].m_cRecYuv
      2. 算SSE,再算rdcost
      3. 再算如果量化成全0时的SSE和bits,算rdcost,决定是否要量化为全0
    5. 如果是全0,算rdcost,此时bits只有编码cbf为0的开销
    6. 类似方式处理UV
    7. 算出YUV整体的bits和distortion,算出rdcost
    8. 如果m_pParam->limitTU,检查是否要限制不再划分TU
  2. 如果可以划分TU
    1. 先做熵编码环境的保存和恢复
    2. 算编码splitflag的开销
    3. 调用splitTU测试划分TU,返回bTotalCbf

splitTU

将TU划分4个小的TU,分别调estimateResidualQT编码,累积cbf和rdcost

recursionDepthCheck

  1. 计算当前CTU中已经编码的CU(不分大小)中使用SKIP的个数,以及SKIP的CU的总的rdcost
  2. 计算上面3个CTU和左边1个CTU共4个CTU中的SKIP的CU个数及总的rdcost
  3. 以周边权重60%,自身权重40%,算出SKIP的CU的平均rdcost,如果当前CU的rdcost小于SKIP CU的平均rdcost,则返回true,不再划分;否则返回falst,继续划分

complexityCheckCU

算当前CU的象素平均值和象素值与平均值的差值的平均值,如果i32PixMeanDiff >= 0.1 * i32MeanPix,则要继续划分,否则不再划分

processRowEncoder中的熵编码上下文

  1. 设置要使用的Entropy& rCurrEntropy = m_pParamMan->bEnableWavefront ? pCurRow->cCABACStatusCur : m_pCTURows[0].cCABACStatusCur;
    2.对第0行的第0个CTU,调用rCurrEntropy.load(m_cSliceInitEntropy)同时加载上下文context和状态stats,后面的行都是加载上一行的context以及m_cSliceInitEntropy的状态
  2. WPP中的熵编码上下文切换:
    1. 每行在编码完第1个CTU后(前面还有第0个,即下一行要等上面完成2个CTU后才开始),调用pCurRow->cCABACStatusOnCTU2.loadContexts(rCurrEntropy)保存context
    2. 非第0行的第0个CTU开始时,加载上一行保存的context,此外,还需要rCurrEntropy.copyState(m_cSliceInitEntropy),这里主要是CABAC状态机中的Range/Low,其中,m_cSliceInitEntropy在encodeFrame中调用m_cSliceInitEntropy.resetEntropy(pSlice)后就不再改变,在多处被load
  3. rCurrEntropy被传用调用compressCTU后,m_aRqtData[0].m_cEntropyCur.load(cInitEntrConxt),以后划分到更小的CU后,小的CU都会加载上一层的context
 类似资料: