FFmpeg的HEVC解码器源代码学习笔记-2

摘要

这篇主要厘清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 );
}

后面将继续厘清整体的解码流程,再整理解码流程的过程中发现了很多在以前只关注编码流程中没有意识到的问题。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/704340.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

利用HubSpot出海营销CRM扩大企业在东南亚市场的影响力

东南亚市场作为全球最具活力和潜力的市场之一&#xff0c;吸引着越来越多的企业前来拓展业务。在这个竞争激烈的市场中&#xff0c;如何高效地管理营销和客户关系成为了企业成功的关键。今天运营坛将介绍如何利用HubSpot这一出海营销CRM工具&#xff0c;实现在东南亚市场的影响…

本地部署ChatGPT

发布一下我之前做的一个本地大模型部署,不需要API key,但要有自己的账号 利用Docker 的Pandora做本地ChatGPT模型部署 先下载安装Docker,设置好运行如下 会要求升级核心,cmd运行如下命令就OK 安装Pandora 再管理员cmd中输入如下命令拉取Pandora镜像 docker pull pengzhi…

js之事件代理/事件委托

事件代理也叫事件委托&#xff0c;原理&#xff1a;利用DOM元素的事件冒泡&#xff0c;指定一个事件的处理程序就可以管理某一类型的所有事件。 事件冒泡和事件捕获 如上图所示&#xff0c;事件传播分成三个阶段&#xff1a; 捕获阶段&#xff1a;从window对象传导到目标节点&…

Nginx的重定向,nginx.conf中location的匹配,rewrite介绍,Nginx发内置变量

目录 一、Nginx的内置变量 二、Nginx中的配置选项和指令 三、Nginx常见的正则表达式 四、location匹配 4.1、location分类 4.2、location常用的匹配规则 4.3、location优先级 4.4、实际网站中使用的匹配规则 五、rewrite介绍 5.1、rewrite作用 5.2、rewrite跳转实现 5.…

【MySQL面试复习】详细说下事务的特性

系列文章目录 在MySQL中&#xff0c;如何定位慢查询&#xff1f; 发现了某个SQL语句执行很慢&#xff0c;如何进行分析&#xff1f; 了解过索引吗&#xff1f;(索引的底层原理)/B 树和B树的区别是什么&#xff1f; 什么是聚簇索引&#xff08;聚集索引&#xff09;和非聚簇索引…

猫毛过敏却想养猫时?如何缓解猫毛过敏?宠物空气净化器推荐

作为一个新养猫的主人&#xff0c;一开始并没有发现对猫咪过敏。直到养了半年才意识到这个问题&#xff0c;而此时我已经和猫咪有了深厚的感情。我不想放弃我的猫咪&#xff0c;但是留着它的话&#xff0c;我经常会因为流眼泪、打喷嚏、眼睛发红等过敏症状而影响日常生活&#…

Unity编辑器扩展之Text组件中字体替换工具

想要批量化替换项目预制体资源中Text组件引用的Font字体文件&#xff0c;可以采用以下步骤。 1、在项目的Editor文件中&#xff0c;新建一个名为FontToolEditor的C#脚本文件&#xff0c;然后把以下代码复制粘贴到新建的FontToolEditor的C#脚本文件中。 using System.Collect…

【深度学习笔记】3_14 正向传播、反向传播和计算图

3.14 正向传播、反向传播和计算图 前面几节里我们使用了小批量随机梯度下降的优化算法来训练模型。在实现中&#xff0c;我们只提供了模型的正向传播&#xff08;forward propagation&#xff09;的计算&#xff0c;即对输入计算模型输出&#xff0c;然后通过autograd模块来调…

哪只基金更值得买入?学会这套BI基金分析逻辑,稳赚不赔

投资基金是一种出色的理财方式&#xff0c;对于初次涉足基金领域的投资者而言&#xff0c;首先需要解决两个关键问题&#xff1a;一是基金是否值得投资&#xff1f;二是如何选择适合自己的基金&#xff1f; 以往盲目跟随成功的基金经理&#xff0c;或者仅仅依赖历史涨跌经验的…

消息中间件之RocketMQ源码分析(十七)

Broker CommitLog索引机制的数据结构 ConsumerQueue消费队列 主要用于消费拉取消息、更新消费位点等所用的索引。源代码参考org.apache.rocketmq.store.ConsumerQueue.该文件内保存了消息的物理位点、消息体大小、消息Tag的Hash值 物理位点:消息在CommitLog中的位点值消息体…

Android 水波纹扩散效果实现

人生只是一种体验&#xff0c;不必用来演绎完美。 效果图 View源码 package com.android.circlescalebar.view;import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.ObjectAnimator; import android.animation.…

算法训练营day36(补),动态规划4

背包最大重量为4。 物品为&#xff1a; 重量价值物品0115物品1320物品2430 问背包能背的物品最大价值是多少&#xff1f; func max(a, b int) int { if a > b { return a } return b } //二维数组解法 func package01(weight, value []int, bagweight int) int { // w…

代码随想录三刷day10

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、力扣1. 两数之和二、力扣454. 四数相加 II三、力扣383. 赎金信 前言 使用map的空间消耗要比数组大一些的&#xff0c;因为map要维护红黑树或者哈希表&…

el-tab-pane标签页如何加图标

效果如下 主要修改 <el-tab-pane name"tab6" v-if"subOrderType 10 && urlname ! wgSalesTerminationOrder"><span slot"label"> 售后判责<span class"el-icon-warning" style"color:#F66B6C;"&…

TensorFlow训练大模型做AI绘图,需要多少的GPU算力支撑

TensorFlow训练大模型做AI绘图&#xff0c;需要多少的GPU算力支撑&#xff01;这个问题就涉及到了资金投资的额度了。众所周知&#xff0c;现在京东里面一个英伟达的显卡&#xff0c;按照RTX3090(24G显存-涡轮风扇&#xff09;版本报价是7000-7500之间。如果你买一张这样的单卡…

unity-unity2d基础操作笔记(一)0.5.0

unity2d基础操作笔记 一、如何查看当前系统的输入设置二、如何获取水平或者垂直的输入的代码三、如何获取当前人物的x和y的值三、如何简单写出控制人物水平移动的代码四、如何设定游戏的帧率五、如何控制渲染顺序六、如何调整摄像机摄像范围大小七、如何对Hierachy中的图进行分…

【MySQL面试复习】谈一谈你对SQL的优化经验

系列文章目录 在MySQL中&#xff0c;如何定位慢查询&#xff1f; 发现了某个SQL语句执行很慢&#xff0c;如何进行分析&#xff1f; 了解过索引吗&#xff1f;(索引的底层原理)/B 树和B树的区别是什么&#xff1f; 什么是聚簇索引&#xff08;聚集索引&#xff09;和非聚簇索引…

原型设计工具Axure RP

Axure RP是一款专业的快速原型设计工具。Axure&#xff08;发音&#xff1a;Ack-sure&#xff09;&#xff0c;代表美国Axure公司&#xff1b;RP则是Rapid Prototyping&#xff08;快速原型&#xff09;的缩写。 下载链接&#xff1a;https://www.axure.com/ 下载 可以免费试用…

代码随想录算法训练营第四十一天|1049.最后一块石头的重量II、494.目标和、474.一和零

1049.最后一块石头的重量II 思路&#xff1a;将石头分为两堆&#xff0c;他们两个的重量最接近&#xff0c;碰撞的话&#xff0c;此时剩下的重量最小&#xff0c;故此时想到动态规划&#xff0c;但是我个人感觉很容易被陷入本道题的一些条件&#xff0c;比如说x<y&#xff0…

一个Post请求入门NestJS的路由与控制器

​ NestJS的控制器 控制器负责处理传入请求并向客户端返回响应。 控制器的目的是接收应用的特定请求。路由机制控制哪个控制器接收哪些请求。 通常&#xff0c;每个控制器都有不止一条路由&#xff0c;不同的路由可以执行不同的操作。 在使用了脚手架的项目中&#xff0c;我…