HEVC码率控制浅析——HM代码阅读之一
HM的码率控制提案主要参考如下三篇:K0103,M0036,M0257。本文及后续文章将基于HM12.0进行讨论,且首先仅讨论K0103对应的代码,之后再陆续补充M0036,M0257对应的代码分析,这么做可能会使得剧情不会显得那么地跳跃,分析起来能够更好地被接受。
按照我的个人习惯,还是先分析HM中码率控制部分(以后简称RC)的总体框架吧。
跟RC有关的头文件和源文件为工程TLibEncoder中的TEncRateCtrl.h和TEncRateCtrl.cpp,其余的地方都是调用这两个文件中定义的函数或者变量。
在最顶层,TEncTop这个类定义了成员变量TEncRateCtrl m_cRateCtrl,类TEncGOP,TEncSlice以及TEncCu也分别定义了成员变量TEncRateCtrl *m_pcRateCtrl,但是请注意,这三个类的m_pcRateCtrl实际上都是指向TEncTop这个类的m_cRateCtrl,即它们本质上是同一个。这个从这三个类的成员函数init中对各成员变量的初始化可以看出来。
(1)初始化:
首先,在TEncTop::create()中,对整个序列都要用到的相关参数进行初始化
#if RATE_CONTROL_LAMBDA_DOMAIN
if ( m_RCEnableRateControl )
{
m_cRateCtrl.init( m_framesToBeEncoded, m_RCTargetBitrate, m_iFrameRate, m_iGOPSize, m_iSourceWidth, m_iSourceHeight,
g_uiMaxCUWidth, g_uiMaxCUHeight, m_RCKeepHierarchicalBit, m_RCUseLCUSeparateModel, m_GOPList );
}
#else
m_cRateCtrl.create(getIntraPeriod(), getGOPSize(), getFrameRate(), getTargetBitrate(), getQP(), getNumLCUInUnit(), getSourceWidth(), getSourceHeight(), g_uiMaxCUWidth, g_uiMaxCUHeight);
#endif
其次,在TEncTop::encode()中,对整个GOP需要用到的相关参数进行初始化
#if RATE_CONTROL_LAMBDA_DOMAIN
if ( m_RCEnableRateControl )
{
m_cRateCtrl.initRCGOP( m_iNumPicRcvd );
}
#endif
接着,在TEncGOP::compressGOP()中,对每一幅picture需要用到的相关参数进行初始化
#if RATE_CONTROL_LAMBDA_DOMAIN
Double lambda = 0.0;
Int actualHeadBits = 0;
Int actualTotalBits = 0;
Int estimatedBits = 0;
Int tmpBitsBeforeWriting = 0;
if ( m_pcCfg->getUseRateCtrl() )
{
Int frameLevel = m_pcRateCtrl->getRCSeq()->getGOPID2Level( iGOPid );
if ( pcPic->getSlice(0)->getSliceType() == I_SLICE )
{
frameLevel = 0;
}
m_pcRateCtrl->initRCPic( frameLevel ); //!< picture level 初始化
estimatedBits = m_pcRateCtrl->getRCPic()->getTargetBits(); Int sliceQP = m_pcCfg->getInitialQP();
if ( ( pcSlice->getPOC() == 0 && m_pcCfg->getInitialQP() > 0 ) || ( frameLevel == 0 && m_pcCfg->getForceIntraQP() ) ) // QP is specified
{
Int NumberBFrames = ( m_pcCfg->getGOPSize() - 1 );
Double dLambda_scale = 1.0 - Clip3( 0.0, 0.5, 0.05*(Double)NumberBFrames );
Double dQPFactor = 0.57*dLambda_scale;
Int SHIFT_QP = 12;
Int bitdepth_luma_qp_scale = 0;
Double qp_temp = (Double) sliceQP + bitdepth_luma_qp_scale - SHIFT_QP;
lambda = dQPFactor*pow( 2.0, qp_temp/3.0 );
}
else if ( frameLevel == 0 ) // intra case, but use the model
{
#if RATE_CONTROL_INTRA
m_pcSliceEncoder->calCostSliceI(pcPic);
#endif
if ( m_pcCfg->getIntraPeriod() != 1 ) // do not refine allocated bits for all intra case
{
Int bits = m_pcRateCtrl->getRCSeq()->getLeftAverageBits();
#if RATE_CONTROL_INTRA
bits = m_pcRateCtrl->getRCPic()->getRefineBitsForIntra( bits );
#else
bits = m_pcRateCtrl->getRCSeq()->getRefineBitsForIntra( bits );
#endif
if ( bits < 200 )
{
bits = 200;
}
m_pcRateCtrl->getRCPic()->setTargetBits( bits );
} list<TEncRCPic*> listPreviousPicture = m_pcRateCtrl->getPicList();
#if RATE_CONTROL_INTRA
m_pcRateCtrl->getRCPic()->getLCUInitTargetBits();
lambda = m_pcRateCtrl->getRCPic()->estimatePicLambda( listPreviousPicture, pcSlice->getSliceType());
#else
lambda = m_pcRateCtrl->getRCPic()->estimatePicLambda( listPreviousPicture );
#endif
sliceQP = m_pcRateCtrl->getRCPic()->estimatePicQP( lambda, listPreviousPicture );
}
else // normal case
{
list<TEncRCPic*> listPreviousPicture = m_pcRateCtrl->getPicList();
#if RATE_CONTROL_INTRA
lambda = m_pcRateCtrl->getRCPic()->estimatePicLambda( listPreviousPicture, pcSlice->getSliceType());
#else
lambda = m_pcRateCtrl->getRCPic()->estimatePicLambda( listPreviousPicture );
#endif
sliceQP = m_pcRateCtrl->getRCPic()->estimatePicQP( lambda, listPreviousPicture );
} sliceQP = Clip3( -pcSlice->getSPS()->getQpBDOffsetY(), MAX_QP, sliceQP );
m_pcRateCtrl->getRCPic()->setPicEstQP( sliceQP ); m_pcSliceEncoder->resetQP( pcPic, sliceQP, lambda );
}
#endif
最后,在TEncSlice::compressSlice()中,对每一个LCU需要用到的相关参数进行初始化:
#if RATE_CONTROL_LAMBDA_DOMAIN
Double oldLambda = m_pcRdCost->getLambda();
if ( m_pcCfg->getUseRateCtrl() )
{
Int estQP = pcSlice->getSliceQp();
Double estLambda = -1.0;
Double bpp = -1.0; #if M0036_RC_IMPROVEMENT
if ( ( rpcPic->getSlice( 0 )->getSliceType() == I_SLICE && m_pcCfg->getForceIntraQP() ) || !m_pcCfg->getLCULevelRC() )
#else
if ( rpcPic->getSlice( 0 )->getSliceType() == I_SLICE || !m_pcCfg->getLCULevelRC() )
#endif
{
estQP = pcSlice->getSliceQp();
}
else
{
#if RATE_CONTROL_INTRA
bpp = m_pcRateCtrl->getRCPic()->getLCUTargetBpp(pcSlice->getSliceType());
if ( rpcPic->getSlice( 0 )->getSliceType() == I_SLICE)
{
estLambda = m_pcRateCtrl->getRCPic()->getLCUEstLambdaAndQP(bpp, pcSlice->getSliceQp(), &estQP);
}
else
{
estLambda = m_pcRateCtrl->getRCPic()->getLCUEstLambda( bpp );
estQP = m_pcRateCtrl->getRCPic()->getLCUEstQP ( estLambda, pcSlice->getSliceQp() );
}
#else
bpp = m_pcRateCtrl->getRCPic()->getLCUTargetBpp();
estLambda = m_pcRateCtrl->getRCPic()->getLCUEstLambda( bpp );
estQP = m_pcRateCtrl->getRCPic()->getLCUEstQP ( estLambda, pcSlice->getSliceQp() );
#endif estQP = Clip3( -pcSlice->getSPS()->getQpBDOffsetY(), MAX_QP, estQP ); m_pcRdCost->setLambda(estLambda);
#if M0036_RC_IMPROVEMENT
#if RDOQ_CHROMA_LAMBDA
// set lambda for RDOQ
Double weight=m_pcRdCost->getChromaWeight();
m_pcTrQuant->setLambda( estLambda, estLambda / weight );
#else
m_pcTrQuant->setLambda( estLambda );
#endif
#endif
} m_pcRateCtrl->setRCQP( estQP );
pcCU->getSlice()->setSliceQpBase( estQP );
}
#endif
(2)参数值更新:
首先,在TEncSlice::compressSlice中,每编码完一个LCU,进行一次更新:
#if TICKET_1090_FIX
#if RATE_CONTROL_LAMBDA_DOMAIN
if ( m_pcCfg->getUseRateCtrl() )
{
#if !M0036_RC_IMPROVEMENT
UInt SAD = m_pcCuEncoder->getLCUPredictionSAD();
Int height = min( pcSlice->getSPS()->getMaxCUHeight(),pcSlice->getSPS()->getPicHeightInLumaSamples() - uiCUAddr / rpcPic->getFrameWidthInCU() * pcSlice->getSPS()->getMaxCUHeight() );
Int width = min( pcSlice->getSPS()->getMaxCUWidth(),pcSlice->getSPS()->getPicWidthInLumaSamples() - uiCUAddr % rpcPic->getFrameWidthInCU() * pcSlice->getSPS()->getMaxCUWidth() );
Double MAD = (Double)SAD / (Double)(height * width);
MAD = MAD * MAD;
( m_pcRateCtrl->getRCPic()->getLCU(uiCUAddr) ).m_MAD = MAD; //!< 注意:此处的MAD已经进行过K0103中公式的两个处理了。
#endif Int actualQP = g_RCInvalidQPValue;
Double actualLambda = m_pcRdCost->getLambda();
Int actualBits = pcCU->getTotalBits();
Int numberOfEffectivePixels = 0;
for ( Int idx = 0; idx < rpcPic->getNumPartInCU(); idx++ )
{
if ( pcCU->getPredictionMode( idx ) != MODE_NONE && ( !pcCU->isSkipped( idx ) ) ) //!< 不考虑skip模式
{
numberOfEffectivePixels = numberOfEffectivePixels + 16;
break;
}
} if ( numberOfEffectivePixels == 0 )
{
actualQP = g_RCInvalidQPValue;
}
else
{
actualQP = pcCU->getQP( 0 );
}
m_pcRdCost->setLambda(oldLambda); #if RATE_CONTROL_INTRA
m_pcRateCtrl->getRCPic()->updateAfterLCU( m_pcRateCtrl->getRCPic()->getLCUCoded(), actualBits, actualQP, actualLambda,
pcCU->getSlice()->getSliceType() == I_SLICE ? 0 : m_pcCfg->getLCULevelRC() );
#else
m_pcRateCtrl->getRCPic()->updateAfterLCU( m_pcRateCtrl->getRCPic()->getLCUCoded(), actualBits, actualQP, actualLambda, m_pcCfg->getLCULevelRC() );
#endif
}
#endif
#endif
接着,在TEncGOP::compressGOP中,一个slice编码完后,进行一次更新:
#if RATE_CONTROL_LAMBDA_DOMAIN
if ( m_pcCfg->getUseRateCtrl() )
{
#if !M0036_RC_IMPROVEMENT
Double effectivePercentage = m_pcRateCtrl->getRCPic()->getEffectivePercentage();
#endif
Double avgQP = m_pcRateCtrl->getRCPic()->calAverageQP(); //!< arithmetic mean value for QP
Double avgLambda = m_pcRateCtrl->getRCPic()->calAverageLambda(); //!< geometric mean value for lamda
if ( avgLambda < 0.0 )
{
avgLambda = lambda;
}
#if M0036_RC_IMPROVEMENT
#if RATE_CONTROL_INTRA
m_pcRateCtrl->getRCPic()->updateAfterPicture( actualHeadBits, actualTotalBits, avgQP, avgLambda, pcSlice->getSliceType());
#else
m_pcRateCtrl->getRCPic()->updateAfterPicture( actualHeadBits, actualTotalBits, avgQP, avgLambda );
#endif
#else
m_pcRateCtrl->getRCPic()->updateAfterPicture( actualHeadBits, actualTotalBits, avgQP, avgLambda, effectivePercentage );
#endif
m_pcRateCtrl->getRCPic()->addToPictureLsit( m_pcRateCtrl->getPicList() ); m_pcRateCtrl->getRCSeq()->updateAfterPic( actualTotalBits );
if ( pcSlice->getSliceType() != I_SLICE )
{
m_pcRateCtrl->getRCGOP()->updateAfterPicture( actualTotalBits );
}
else // for intra picture, the estimated bits are used to update the current status in the GOP
{
m_pcRateCtrl->getRCGOP()->updateAfterPicture( estimatedBits );
}
}
#else
if(m_pcCfg->getUseRateCtrl())
{
UInt frameBits = m_vRVM_RP[m_vRVM_RP.size()-1];
m_pcRateCtrl->updataRCFrameStatus((Int)frameBits, pcSlice->getSliceType());
}
#endif
最后,在TEncTop::encode()中,对当前GOP的码控变量进行了销毁。
至此,总体框架大体分析完毕,最后,简单地提一下TEncRateCtrl.h中定义的类的关系:TEncRateCtrl属于RC顶层的控制,负责整个RC的流程管理;TEncRCSeq负责序列级的RC管理;TEncRCGOP负责GOP级的RC管理;TEncRCPic负责picture级的RC管理;TRCLCU负责LCU级的RC管理。
HEVC码率控制浅析——HM代码阅读之一的更多相关文章
- HEVC码率控制浅析——HM代码阅读之四
继续分析第一篇提到的compressSlice中对LCU的RC参数初始化: #if RATE_CONTROL_LAMBDA_DOMAIN Double oldLambda = m_pcRdCost-& ...
- HEVC码率控制浅析——HM代码阅读之二
上一篇文章主要讨论了RC的总体框架,本文开始分析具体的代码实现细节.分析的顺序按照总体框架来,即初始化-->更新. (1)m_cRateCtrl.init() #if M0036_RC_IMPR ...
- 最近调试HEVC中码率控制, 发现HM里面一个重大bug
最近调试HEVC中码率控制, 发现里面一个重大bug! 码率控制中有这么一个函数: Int TEncRCGOP::xEstGOPTargetBits( TEncRCSeq* encRCSeq, Int ...
- MediaInfo代码阅读
MediaInfo是一个用来分析媒体文件的开源工具. 支持的文件非常全面,基本上支持所有的媒体文件. 最近是在做HEVC开发,所以比较关注MediaInfo中关于HEVC的分析与处理. 从Meid ...
- FFMpeg的码率控制
mediaxyz是一位研究ffmpeg有三年的高人了,这几天一直在折腾ffmpeg中的x264,就是不知道该如何控制码率,主要是参数太多,也不知道该如何设置,在google上search了一下,这方面 ...
- [转载] FFMpeg的码率控制
mediaxyz是一位研究ffmpeg有三年的高人了,这几天一直在折腾ffmpeg中的x264,就是不知道该如何控制码率,主要是参数太多,也不知道该如何设置,在google上search了一下,这方面 ...
- [置顶] Linux协议栈代码阅读笔记(一)
Linux协议栈代码阅读笔记(一) (基于linux-2.6.21.7) (一)用户态通过诸如下面的C库函数访问协议栈服务 int socket(int domain, int type, int p ...
- Bleve代码阅读(二)——Index Mapping
引言 Bleve是Golang实现的一个全文检索库,类似Lucene之于Java.在这里通过阅读其代码,来学习如何使用及定制检索功能.也是为了通过阅读代码,学习在具体环境下Golang的一些使用方式. ...
- py-faster-rcnn代码阅读1-train_net.py & train.py
# train_net.py#!/usr/bin/env python # -------------------------------------------------------- # Fas ...
随机推荐
- IntelliJ IDEA 14 注册码生成器
IntelliJ IDEA 14 注册码生成器 文件为Java代码 自己编译运行里面的程序输入名称然后就生成注册码了工具:http://yun.baidu.com/s/1cZKsA部分工具生成的注册码 ...
- Jquery调用webService的四种方法 转载-记录
我总结几个关键点 1. 服务必须声明为ScriptService(否则会出现下面的问题) 2.服务的方法不需要任何更改,保持原状 3.客户端用jquery的.ajax方法来调用 3.1 type必须是 ...
- [Linux命令]查看Linux系统相关命令
#查看系统内核/操作系统/CPU信息 uname -a #返回:内核名 主机名 Linux内核版本 内核编译日期 操作系统版本 CPU型号 硬件平台 GNU/Linux#查看系统是32位还是64位un ...
- 数据表转换成json(DatatableToJson)
#region 转换Table为JSON数据 /// <summary> /// 转换Table为JSON数据 /// </summary> /// <param nam ...
- JS 获取星期几的四种写法
今天是星期几的4种JS代码写法,有需要的朋友可以参考一下 第一种写法 复制代码代码如下: var str = ""; var week = new Date().getDay() ...
- CSS3-旋转齿轮
CSS3-旋转齿轮 查看DEMO 通过 CSS3,我们能够创建动画,这可以在许多网页中取代动画图片.Flash 动画以及 JavaScript. 先来认识一下css3的animation animat ...
- poj 3608 Bridge Across Islands 两凸包间最近距离
/** 旋转卡壳,, **/ #include <iostream> #include <algorithm> #include <cmath> #include ...
- 阿尔宾我饿iejr89e 如何
http://www.huihui.cn/share/8112372 http://www.huihui.cn/share/8112363 http://www.huihui.cn/share/811 ...
- java学习之生产者和消费者案例
package com.gh.thread; /** * 生产者和消费者案例 * wait和sleep的区别 * wait不让出监视器锁,sleep让出监视器的锁 * @author ganhang ...
- Java for循环用法
Java中for循环多了一种写法--foreach写法(一般仅仅用于遍历整个数组,不用操心越界等问题). 1.1)常规写法: package foreach.main.sn; public class ...