摘要
这篇主要厘清FFmpeg如何调用多种视频编解码代码进行解码的主要函数调用逻辑
背景
FFmpeg作为一个视频编解码开源框架,被企业和个人广泛使用,但是一直不清楚他是怎么调用多种编解码器的,由于现在想做一个HEVC的码流分析器,需要了解FFmpeg对265码流解析的具体过程,今天按照官方提供的解码样例代码,整理一下FFmpeg是如何从外部包装代码到指定编解码代码实现解码流程的,主要以HEVC的解码过程为例。
// 从上至下进行调用
avcodec_receive_frame
decode_receive_frame_internal
decode_simple_receive_frame
decode_simple_internal
// 从这里开始使用decode函数指针指向hevc_decode_frame
avctx->codec->decode(avctx, frame, &got_frame, pkt);//函数指针指向hevc_decode_frame(),也即调用的hevc_decode_frame;
decode_nal_units
decode_nal_unit
ctb_addr_ts = hls_slice_data(s);
// 从这里开始进入解码,前面的函数主要是解析功能
s->avctx->execute(s->avctx, hls_decode_entry, arg, ret , 1, sizeof(int));
hls_decode_entry
hls_coding_quadtree
hls_coding_unit
hls_prediction_unit
hls_transform_tree
hls_transform_unit
ff_hevc_hls_residual_coding
整理流程中,才发现PU不是获得残差信息,PU只是获取到预测角度,运动矢量等信息,而实际计算残差信号也是在TU中计算的,TU里面才真正根据PU的信息获取到残差信号。
之前一直以为先PU计算得到了残差数据,然后传递给TU,TU直接对残差数据进行DCT变换后进行cabac编码了,我就说为什么hevc解码的时候,是先解码PU,再解码TU。
按照我之前的理解先PU获取残差数据,再TU变换编码,应该先idct TU里面的数据,再解码出残差信号。
特意回去看了一下HM的编码代码,再TU的编码函数中,找到了如下代码:
Void TEncSearch::xIntraCodingTUBlock( TComYuv* pcOrgYuv,TComYuv* pcPredYuv,TComYuv* pcResiYuv,Pel resiLuma[NUMBER_OF_STORED_RESIDUAL_TYPES][MAX_CU_SIZE * MAX_CU_SIZE],const Bool checkCrossCPrediction,Distortion& ruiDist,const ComponentID compID,TComTU& rTuDEBUG_STRING_FN_DECLARE(sDebug),Int default0Save1Load2)
{if (!rTu.ProcessComponentSection(compID)){return;}const Bool bIsLuma = isLuma(compID);const TComRectangle &rect = rTu.getRect(compID);TComDataCU *pcCU = rTu.getCU();const UInt uiAbsPartIdx = rTu.GetAbsPartIdxTU();const TComSPS &sps = *(pcCU->getSlice()->getSPS());const UInt uiTrDepth = rTu.GetTransformDepthRelAdj(compID);const UInt uiFullDepth = rTu.GetTransformDepthTotal();const UInt uiLog2TrSize = rTu.GetLog2LumaTrSize();const ChromaFormat chFmt = pcOrgYuv->getChromaFormat();const ChannelType chType = toChannelType(compID);const Int bitDepth = sps.getBitDepth(chType);const UInt uiWidth = rect.width;const UInt uiHeight = rect.height;const UInt uiStride = pcOrgYuv ->getStride (compID);Pel *piOrg = pcOrgYuv ->getAddr( compID, uiAbsPartIdx );Pel *piPred = pcPredYuv->getAddr( compID, uiAbsPartIdx );Pel *piResi = pcResiYuv->getAddr( compID, uiAbsPartIdx );Pel *piReco = pcPredYuv->getAddr( compID, uiAbsPartIdx );const UInt uiQTLayer = sps.getQuadtreeTULog2MaxSize() - uiLog2TrSize;Pel *piRecQt = m_pcQTTempTComYuv[ uiQTLayer ].getAddr( compID, uiAbsPartIdx );const UInt uiRecQtStride = m_pcQTTempTComYuv[ uiQTLayer ].getStride(compID);const UInt uiZOrder = pcCU->getZorderIdxInCtu() + uiAbsPartIdx;Pel *piRecIPred = pcCU->getPic()->getPicYuvRec()->getAddr( compID, pcCU->getCtuRsAddr(), uiZOrder );UInt uiRecIPredStride = pcCU->getPic()->getPicYuvRec()->getStride ( compID );TCoeff *pcCoeff = m_ppcQTTempCoeff[compID][uiQTLayer] + rTu.getCoefficientOffset(compID);Bool useTransformSkip = pcCU->getTransformSkip(uiAbsPartIdx, compID);#if ADAPTIVE_QP_SELECTIONTCoeff *pcArlCoeff = m_ppcQTTempArlCoeff[compID][ uiQTLayer ] + rTu.getCoefficientOffset(compID);
#endifconst UInt uiChPredMode = pcCU->getIntraDir( chType, uiAbsPartIdx );const UInt partsPerMinCU = 1<<(2*(sps.getMaxTotalCUDepth() - sps.getLog2DiffMaxMinCodingBlockSize()));const UInt uiChCodedMode = (uiChPredMode==DM_CHROMA_IDX && !bIsLuma) ? pcCU->getIntraDir(CHANNEL_TYPE_LUMA, getChromasCorrespondingPULumaIdx(uiAbsPartIdx, chFmt, partsPerMinCU)) : uiChPredMode;const UInt uiChFinalMode = ((chFmt == CHROMA_422) && !bIsLuma) ? g_chroma422IntraAngleMappingTable[uiChCodedMode] : uiChCodedMode;const Int blkX = g_auiRasterToPelX[ g_auiZscanToRaster[ uiAbsPartIdx ] ];const Int blkY = g_auiRasterToPelY[ g_auiZscanToRaster[ uiAbsPartIdx ] ];const Int bufferOffset = blkX + (blkY * MAX_CU_SIZE);Pel *const encoderLumaResidual = resiLuma[RESIDUAL_ENCODER_SIDE ] + bufferOffset;Pel *const reconstructedLumaResidual = resiLuma[RESIDUAL_RECONSTRUCTED] + bufferOffset;const Bool bUseCrossCPrediction = isChroma(compID) && (uiChPredMode == DM_CHROMA_IDX) && checkCrossCPrediction;const Bool bUseReconstructedResidualForEstimate = m_pcEncCfg->getUseReconBasedCrossCPredictionEstimate();Pel *const lumaResidualForEstimate = bUseReconstructedResidualForEstimate ? reconstructedLumaResidual : encoderLumaResidual;#if DEBUG_STRINGconst Int debugPredModeMask=DebugStringGetPredModeMask(MODE_INTRA);
#endif//===== init availability pattern =====DEBUG_STRING_NEW(sTemp)#if !DEBUG_STRINGif( default0Save1Load2 != 2 )
#endif{const Bool bUseFilteredPredictions=TComPrediction::filteringIntraReferenceSamples(compID, uiChFinalMode, uiWidth, uiHeight, chFmt, sps.getSpsRangeExtension().getIntraSmoothingDisabledFlag());initIntraPatternChType( rTu, compID, bUseFilteredPredictions DEBUG_STRING_PASS_INTO(sDebug) );// 这里写明了是在此处获取预测信号//===== get prediction signal =====predIntraAng( compID, uiChFinalMode, piOrg, uiStride, piPred, uiStride, rTu, bUseFilteredPredictions );// save predictionif( default0Save1Load2 == 1 ){Pel* pPred = piPred;Pel* pPredBuf = m_pSharedPredTransformSkip[compID];Int k = 0;for( UInt uiY = 0; uiY < uiHeight; uiY++ ){for( UInt uiX = 0; uiX < uiWidth; uiX++ ){pPredBuf[ k ++ ] = pPred[ uiX ];}pPred += uiStride;}}}
#if !DEBUG_STRINGelse{// load predictionPel* pPred = piPred;Pel* pPredBuf = m_pSharedPredTransformSkip[compID];Int k = 0;for( UInt uiY = 0; uiY < uiHeight; uiY++ ){for( UInt uiX = 0; uiX < uiWidth; uiX++ ){pPred[ uiX ] = pPredBuf[ k ++ ];}pPred += uiStride;}}
#endif// 这里写明了是在此处计算预测残差信号,如果需要修改JND的阈值,也应该在这,之前经常修改这个位置,当时居然后没有意识到这个问题。//===== get residual signal ====={// get residualPel* 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;}}if (pcCU->getSlice()->getPPS()->getPpsRangeExtension().getCrossComponentPredictionEnabledFlag()){if (bUseCrossCPrediction){if (xCalcCrossComponentPredictionAlpha( rTu, compID, lumaResidualForEstimate, piResi, uiWidth, uiHeight, MAX_CU_SIZE, uiStride ) == 0){return;}TComTrQuant::crossComponentPrediction ( rTu, compID, reconstructedLumaResidual, piResi, piResi, uiWidth, uiHeight, MAX_CU_SIZE, uiStride, uiStride, false );}else if (isLuma(compID) && !bUseReconstructedResidualForEstimate){xStoreCrossComponentPredictionResult( encoderLumaResidual, piResi, rTu, 0, 0, MAX_CU_SIZE, uiStride );}}//===== transform and quantization =====//--- init rate estimation arrays for RDOQ ---if( useTransformSkip ? m_pcEncCfg->getUseRDOQTS() : m_pcEncCfg->getUseRDOQ() ){COEFF_SCAN_TYPE scanType = COEFF_SCAN_TYPE(pcCU->getCoefScanIdx(uiAbsPartIdx, uiWidth, uiHeight, compID));m_pcEntropyCoder->estimateBit( m_pcTrQuant->m_pcEstBitsSbac, uiWidth, uiHeight, chType, scanType );}//--- transform and quantization ---TCoeff uiAbsSum = 0;if (bIsLuma){pcCU ->setTrIdxSubParts ( uiTrDepth, uiAbsPartIdx, uiFullDepth );}const QpParam cQP(*pcCU, compID);#if RDOQ_CHROMA_LAMBDAm_pcTrQuant->selectLambda (compID);
#endifm_pcTrQuant->transformNxN ( rTu, compID, piResi, uiStride, pcCoeff,
#if ADAPTIVE_QP_SELECTIONpcArlCoeff,
#endifuiAbsSum, cQP);//--- inverse transform ---#if DEBUG_STRINGif ( (uiAbsSum > 0) || (DebugOptionList::DebugString_InvTran.getInt()&debugPredModeMask) )
#elseif ( uiAbsSum > 0 )
#endif{m_pcTrQuant->invTransformNxN ( rTu, compID, piResi, uiStride, pcCoeff, cQP DEBUG_STRING_PASS_INTO_OPTIONAL(&sDebug, (DebugOptionList::DebugString_InvTran.getInt()&debugPredModeMask)) );}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;if (pcCU->getSlice()->getPPS()->getPpsRangeExtension().getCrossComponentPredictionEnabledFlag()){if (bUseCrossCPrediction){TComTrQuant::crossComponentPrediction( rTu, compID, reconstructedLumaResidual, piResi, piResi, uiWidth, uiHeight, MAX_CU_SIZE, uiStride, uiStride, true );}else if (isLuma(compID)){xStoreCrossComponentPredictionResult( reconstructedLumaResidual, piResi, rTu, 0, 0, MAX_CU_SIZE, uiStride );}}#if DEBUG_STRINGstd::stringstream ss(stringstream::out);const Bool bDebugPred=((DebugOptionList::DebugString_Pred.getInt()&debugPredModeMask) && DEBUG_STRING_CHANNEL_CONDITION(compID));const Bool bDebugResi=((DebugOptionList::DebugString_Resi.getInt()&debugPredModeMask) && DEBUG_STRING_CHANNEL_CONDITION(compID));const Bool bDebugReco=((DebugOptionList::DebugString_Reco.getInt()&debugPredModeMask) && DEBUG_STRING_CHANNEL_CONDITION(compID));if (bDebugPred || bDebugResi || bDebugReco){ss << "###: " << "CompID: " << compID << " pred mode (ch/fin): " << uiChPredMode << "/" << uiChFinalMode << " absPartIdx: " << rTu.GetAbsPartIdxTU() << "\n";for( UInt uiY = 0; uiY < uiHeight; uiY++ ){ss << "###: ";if (bDebugPred){ss << " - pred: ";for( UInt uiX = 0; uiX < uiWidth; uiX++ ){ss << pPred[ uiX ] << ", ";}}if (bDebugResi){ss << " - resi: ";}for( UInt uiX = 0; uiX < uiWidth; uiX++ ){if (bDebugResi){ss << pResi[ uiX ] << ", ";}pReco [ uiX ] = Pel(ClipBD<Int>( Int(pPred[uiX]) + Int(pResi[uiX]), bitDepth ));pRecQt [ uiX ] = pReco[ uiX ];pRecIPred[ uiX ] = pReco[ uiX ];}if (bDebugReco){ss << " - reco: ";for( UInt uiX = 0; uiX < uiWidth; uiX++ ){ss << pReco[ uiX ] << ", ";}}pPred += uiStride;pResi += uiStride;pReco += uiStride;pRecQt += uiRecQtStride;pRecIPred += uiRecIPredStride;ss << "\n";}DEBUG_STRING_APPEND(sDebug, ss.str())}else
#endif{for( UInt uiY = 0; uiY < uiHeight; uiY++ ){for( UInt uiX = 0; uiX < uiWidth; uiX++ ){pReco [ uiX ] = Pel(ClipBD<Int>( Int(pPred[uiX]) + Int(pResi[uiX]), bitDepth ));pRecQt [ uiX ] = pReco[ uiX ];pRecIPred[ uiX ] = pReco[ uiX ];}pPred += uiStride;pResi += uiStride;pReco += uiStride;pRecQt += uiRecQtStride;pRecIPred += uiRecIPredStride;}}}//===== update distortion =====ruiDist += m_pcRdCost->getDistPart( bitDepth, piReco, uiStride, piOrg, uiStride, uiWidth, uiHeight, compID );
}
后面将继续厘清整体的解码流程,再整理解码流程的过程中发现了很多在以前只关注编码流程中没有意识到的问题。