这篇是学习过程中一些新体会,不得不说编码框架太大了
class TAppEncTop
这个类可以说是整个代码的入口了,在emain函数就是该类对象调用encode开始编码
cTAppEncTop.encode();
class TAppEncTop : public TAppEncCfg
{
private:
// class interface
TEncTop m_cTEncTop; ///< 编码类,TAppEncTop是应用层编码类
TVideoIOYuv m_cTVideoIOYuvInputFile; ///< 输入的YUV文件
TVideoIOYuv m_cTVideoIOYuvReconFile; ///< 输出的YUV文件
TComList<TComPicYuv*> m_cListPicYuvRec; ///< 列表,放的是输出的每一帧的YUV
Int m_iFrameRcvd; ///<收到的帧数
UInt m_essentialBytes;
UInt m_totalBytes;
protected:
// initialization
Void xCreateLib (); ///< 创建编码类
Void xInitLibCfg (); ///< 初始化内部变量
Void xInitLib (Bool isFieldCoding); ///< 初始化编码类
Void xDestroyLib (); ///< 销毁编码类
/// obtain required buffers
Void xGetBuffer(TComPicYuv*& rpcPicYuvRec); //为重建图像申请内存
/// delete allocated buffers
Void xDeleteBuffer ();//删除分配的内存
// file I/O
Void xWriteOutput(std::ostream& bitstreamFile, Int iNumEncoded, const std::list<AccessUnit>& accessUnits);//给文件中输出码流
Void rateStatsAccum(const AccessUnit& au, const std::vector<UInt>& stats);
Void printRateSummary(); //输出总码率
Void printChromaFormat();输出视频格式
public:
TAppEncTop();
virtual ~TAppEncTop();
Void encode (); ///< main encoding function
TEncTop& getTEncTop () { return m_cTEncTop; } ///< return encoder class pointer reference
};// END CLASS DEFINITION TAppEncTop
TAppEncTop::encode()
//输入的YUV文件读取
fstream bitstreamFile(m_bitstreamFileName.c_str(), fstream::binary | fstream::out);
//读取失败
if (!bitstreamFile)
{
fprintf(stderr, "\nfailed to open bitstream file `%s' for writing\n", m_bitstreamFileName.c_str());
exit(EXIT_FAILURE);
}
//原始图像和重建图像YUV
TComPicYuv* pcPicYuvOrg = new TComPicYuv;
TComPicYuv* pcPicYuvRec = NULL;
// 初始化编码类和一些参数
xInitLibCfg();
xCreateLib();
xInitLib(m_isField);
//打印原文件格式
printChromaFormat();
//刚开始编码的入口,收到的帧数为0
Int iNumEncoded = 0;
Bool bEos = false;
list<AccessUnit> outputAccessUnits;//输出的码流,应该就是bin文件
TComPicYuv cPicYuvTrueOrg;//TrueOrg和pcPicYuvOrg的区别见之前的文章
//非场,默认执行下面else
if( m_isField )
{
pcPicYuvOrg->create ( m_iSourceWidth, m_iSourceHeightOrg, m_chromaFormatIDC, m_uiMaxCUWidth, m_uiMaxCUHeight, m_uiMaxTotalCUDepth, true );
cPicYuvTrueOrg.create(m_iSourceWidth, m_iSourceHeightOrg, m_chromaFormatIDC, m_uiMaxCUWidth, m_uiMaxCUHeight, m_uiMaxTotalCUDepth, true);
}
else
{
//为pcPicYuvOrg和cPicYuvTrueOrg初始化空间
pcPicYuvOrg->create ( m_iSourceWidth, m_iSourceHeight, m_chromaFormatIDC, m_uiMaxCUWidth, m_uiMaxCUHeight, m_uiMaxTotalCUDepth, true );
cPicYuvTrueOrg.create(m_iSourceWidth, m_iSourceHeight, m_chromaFormatIDC, m_uiMaxCUWidth, m_uiMaxCUHeight, m_uiMaxTotalCUDepth, true );
}
//开始循环压缩每一帧
while ( !bEos )
{
// 为重建图像申请内存
xGetBuffer(pcPicYuvRec);
// 从输入的YUV文件中读取帧,放到pcPicYuvOrg和cPicYuvTrueOrg中
m_cTVideoIOYuvInputFile.read( pcPicYuvOrg, &cPicYuvTrueOrg, ipCSC, m_aiPad, m_InputChromaFormatIDC, m_bClipInputVideoToRec709Range );
// 收到的帧数+1
m_iFrameRcvd++;
//当m_iFrameRcvd==m_framesToBeEncoded(总共要编码的帧数,cfg文件中配置)时,bEos=true,退出循环,编码结束
bEos = (m_isField && (m_iFrameRcvd == (m_framesToBeEncoded >> 1) )) || ( !m_isField && (m_iFrameRcvd == m_framesToBeEncoded) );
Bool flush = 0;
// if end of file (which is only detected on a read failure) flush the encoder of any queued pictures
if (m_cTVideoIOYuvInputFile.isEof())
{
flush = true;
bEos = true;
m_iFrameRcvd--;
m_cTEncTop.setFramesToBeEncoded(m_iFrameRcvd);
}
// 开始编码每一帧
if ( m_isField )
{
m_cTEncTop.encode( bEos, flush ? 0 : pcPicYuvOrg, flush ? 0 : &cPicYuvTrueOrg, snrCSC, m_cListPicYuvRec, outputAccessUnits, iNumEncoded, m_isTopFieldFirst );
}
else
{
m_cTEncTop.encode( bEos, flush ? 0 : pcPicYuvOrg, flush ? 0 : &cPicYuvTrueOrg, snrCSC, m_cListPicYuvRec, outputAccessUnits, iNumEncoded );
}
//如果有帧被编码,就把码流输出到文件中
if ( iNumEncoded > 0 )
{
//输出码流和YUV文件
xWriteOutput(bitstreamFile, iNumEncoded, outputAccessUnits);
outputAccessUnits.clear();
}
// temporally skip frames
if( m_temporalSubsampleRatio > 1 )
{
m_cTVideoIOYuvInputFile.skipFrames(m_temporalSubsampleRatio-1, m_iSourceWidth - m_aiPad[0], m_iSourceHeight - m_aiPad[1], m_InputChromaFormatIDC);
}
}
m_cTEncTop.printSummary(m_isField);
// delete original YUV buffer
pcPicYuvOrg->destroy();
delete pcPicYuvOrg;
pcPicYuvOrg = NULL;
// delete used buffers in encoder class
m_cTEncTop.deletePicBuffer();
cPicYuvTrueOrg.destroy();
// delete buffers & classes
xDeleteBuffer();
xDestroyLib();
printRateSummary();
return;
}
TAppEncTop::xWriteOutput
该函数输出码流和YUV文件
Void TAppEncTop::xWriteOutput(std::ostream& bitstreamFile, Int iNumEncoded, const std::list<AccessUnit>& accessUnits)
{
const InputColourSpaceConversion ipCSC = (!m_outputInternalColourSpace) ? m_inputColourSpaceConvert : IPCOLOURSPACE_UNCHANGED;
if (m_isField)
{
//Reinterlace fields
Int i;
TComList<TComPicYuv*>::iterator iterPicYuvRec = m_cListPicYuvRec.end();
list<AccessUnit>::const_iterator iterBitstream = accessUnits.begin();
for ( i = 0; i < iNumEncoded; i++ )
{
--iterPicYuvRec;
}
for ( i = 0; i < iNumEncoded/2; i++ )
{
TComPicYuv* pcPicYuvRecTop = *(iterPicYuvRec++);
TComPicYuv* pcPicYuvRecBottom = *(iterPicYuvRec++);
if (!m_reconFileName.empty())
{
m_cTVideoIOYuvReconFile.write( pcPicYuvRecTop, pcPicYuvRecBottom, ipCSC, m_confWinLeft, m_confWinRight, m_confWinTop, m_confWinBottom, NUM_CHROMA_FORMAT, m_isTopFieldFirst );
}
const AccessUnit& auTop = *(iterBitstream++);
const vector<UInt>& statsTop = writeAnnexB(bitstreamFile, auTop);
rateStatsAccum(auTop, statsTop);
const AccessUnit& auBottom = *(iterBitstream++);
const vector<UInt>& statsBottom = writeAnnexB(bitstreamFile, auBottom);
rateStatsAccum(auBottom, statsBottom);
}
}
else
{
Int i;
TComList<TComPicYuv*>::iterator iterPicYuvRec = m_cListPicYuvRec.end();
list<AccessUnit>::const_iterator iterBitstream = accessUnits.begin();
for ( i = 0; i < iNumEncoded; i++ )
{
--iterPicYuvRec;
}
for ( i = 0; i < iNumEncoded; i++ )
{
TComPicYuv* pcPicYuvRec = *(iterPicYuvRec++);
if (!m_reconFileName.empty())
{
m_cTVideoIOYuvReconFile.write( pcPicYuvRec, ipCSC, m_confWinLeft, m_confWinRight, m_confWinTop, m_confWinBottom,
NUM_CHROMA_FORMAT, m_bClipOutputVideoToRec709Range );
}
const AccessUnit& au = *(iterBitstream++);
const vector<UInt>& stats = writeAnnexB(bitstreamFile, au);
rateStatsAccum(au, stats);
}
}
}
TAppEncTop::xGetBuffer
该函数的作用就是给重建图像申请空间,没啥好讲的,对重建图像的列表m_cListPicYuvRec内帧数进行了限制
Void TAppEncTop::xGetBuffer( TComPicYuv*& rpcPicYuvRec)
{
assert( m_iGOPSize > 0 );
// org. buffer
if ( m_cListPicYuvRec.size() >= (UInt)m_iGOPSize ) // buffer will be 1 element longer when using field coding, to maintain first field whilst processing second.
{
rpcPicYuvRec = m_cListPicYuvRec.popFront();
}
else
{
rpcPicYuvRec = new TComPicYuv;
rpcPicYuvRec->create( m_iSourceWidth, m_iSourceHeight, m_chromaFormatIDC, m_uiMaxCUWidth, m_uiMaxCUHeight, m_uiMaxTotalCUDepth, true );
}
m_cListPicYuvRec.pushBack( rpcPicYuvRec );
}
TAppEncTop::xWriteOutput
函数重点就是调用m_cTVideoIOYuvReconFile.write()写入到文件中
Int i;
TComList<TComPicYuv*>::iterator iterPicYuvRec = m_cListPicYuvRec.end();
list<AccessUnit>::const_iterator iterBitstream = accessUnits.begin();
for ( i = 0; i < iNumEncoded; i++ )
{
--iterPicYuvRec;
}
for ( i = 0; i < iNumEncoded; i++ )
{
TComPicYuv* pcPicYuvRec = *(iterPicYuvRec++);
if (!m_reconFileName.empty())
{
m_cTVideoIOYuvReconFile.write( pcPicYuvRec, ipCSC, m_confWinLeft, m_confWinRight, m_confWinTop, m_confWinBottom,
NUM_CHROMA_FORMAT, m_bClipOutputVideoToRec709Range );
}
const AccessUnit& au = *(iterBitstream++);
const vector<UInt>& stats = writeAnnexB(bitstreamFile, au);
rateStatsAccum(au, stats);
}
总结:
这个类是应用层的一些准备工作,例如读取YUV文件,重建的图像写入到文件中,为图像申请空间,调用encode函数正式开始编码等等
m_cTEncTop.encode( bEos, flush ? 0 : pcPicYuvOrg, flush ? 0 : &cPicYuvTrueOrg, snrCSC, m_cListPicYuvRec, outputAccessUnits, iNumEncoded );
encode函数的输入值得注意,m_cListPicYuvRec是重建图像的列表,outputAccessUnits是码流,iNumEncoded是调用该函数后总共编码了几帧
class TEncTop
正式的编码类,也真正进入了编码
TEncTop::encode
Void TEncTop::encode( Bool flush, TComPicYuv* pcPicYuvOrg, TComPicYuv* pcPicYuvTrueOrg, const InputColourSpaceConversion snrCSC, TComList<TComPicYuv*>& rcListPicYuvRecOut, std::list<AccessUnit>& accessUnitsOut, Int& iNumEncoded )
{
if (pcPicYuvOrg != NULL)
{
// get original YUV
TComPic* pcPicCurr = NULL;
//给传入的原始图像帧申请一个TComPic的空间,把他加入m_cListPic列表中
xGetNewPicBuffer( pcPicCurr );
//然后把pcPicYuvOrg和pcPicYuvTrueOrg都复制pcPicCurr对象中
pcPicYuvOrg->copyToPic( pcPicCurr->getPicYuvOrg() );
pcPicYuvTrueOrg->copyToPic( pcPicCurr->getPicYuvTrueOrg() );
// compute image characteristics
if ( getUseAdaptiveQP() )
{
m_cPreanalyzer.xPreanalyze( dynamic_cast<TEncPic*>( pcPicCurr ) );
}
}
//如果传入的帧数不满足一个GOP大小,退出,例如GOP大小为4,那我必须传入够4帧才开始编码
if ((m_iNumPicRcvd == 0) || (!flush && (m_iPOCLast != 0) && (m_iNumPicRcvd != m_iGOPSize) && (m_iGOPSize != 0)))
{
iNumEncoded = 0;
return;
}
//开启码率控制
if ( m_RCEnableRateControl )
{
m_cRateCtrl.initRCGOP( m_iNumPicRcvd );
}
// compress GOP 这个函数是整个编码中的最重要的地方!
m_cGOPEncoder.compressGOP(m_iPOCLast, m_iNumPicRcvd, m_cListPic, rcListPicYuvRecOut, accessUnitsOut, false, false, snrCSC, m_printFrameMSE);
if ( m_RCEnableRateControl )
{
m_cRateCtrl.destroyRCGOP();
}
//参数更新
调用encode函数总共编码了帧数。收到的帧数清0
iNumEncoded = m_iNumPicRcvd;
m_iNumPicRcvd = 0;
m_uiNumAllPicCoded += iNumEncoded;
}
TEncTop::xGetNewPicBuffer
该函数的作用就是将创建的TComPic* pcPicCurr加入到m_cListPic列表中, m_cListPic.pushBack( rpcPic );
//一些列表大小限制
if (m_cListPic.size() >= (UInt)(m_iGOPSize + getMaxDecPicBuffering(MAX_TLAYER-1) + 2) )
{
TComList<TComPic*>::iterator iterPic = m_cListPic.begin();
Int iSize = Int( m_cListPic.size() );
for ( Int i = 0; i < iSize; i++ )
{
rpcPic = *(iterPic++);
if(rpcPic->getSlice(0)->isReferenced() == false)
{
break;
}
}
}
else
{
rpcPic = new TComPic;
rpcPic->create( m_cSPS, m_cPPS, false );
}
m_cListPic.pushBack( rpcPic );
}
rpcPic->setReconMark (false);
//POC更新,收到的帧数++
m_iPOCLast++;
m_iNumPicRcvd++;
//rpcPic可以理解为一个集合体,放了原始图像和重建图像
//设置该帧的POC
rpcPic->getSlice(0)->setPOC( m_iPOCLast );
// mark it should be extended
rpcPic->getPicYuvRec()->setBorderExtension(false);
TComPic类中有个属性是
TComPicYuv* m_apcPicYuv[NUM_PIC_YUV];
NUM_PIC_YUV =3;
typedef enum { PIC_YUV_ORG=0, PIC_YUV_REC=1, PIC_YUV_TRUE_ORG=2, NUM_PIC_YUV=3 } PIC_YUV_T;
原始图像是0,重建图像是1,TRUE_ORG是2
pcPicYuvOrg->copyToPic( pcPicCurr->getPicYuvOrg() );
pcPicYuvTrueOrg->copyToPic( pcPicCurr->getPicYuvTrueOrg() );
上面两行就是把原始图像和TrueOrg图像复制到TComPic::pcPicCurr中
整个编码算法的中心
传入的是m_iPOCLast是当前压缩GOP中最后一帧的POC顺序,m_iNumPicRcvd收到的图像,一般为GOP大小,也有特殊情况,例如第一帧I帧。
m_cListPic就是TComPic列表,rcListPicYuvRecOut是重建图像YUV列表
m_cGOPEncoder.compressGOP(m_iPOCLast, m_iNumPicRcvd, m_cListPic, rcListPicYuvRecOut, accessUnitsOut, false, false, snrCSC, m_printFrameMSE);
TEncGOP::compressGOP
新建三个对象,分别是图像集合体pcPic,重建图像YUVpcPicYuvRecOut,条pcSlice
TComPic* pcPic = NULL;
TComPicYuv* pcPicYuvRecOut;
TComSlice* pcSlice;
//置m_iGopSize,表示当前GOP的大小,除了第一帧是1的情况外,是恒定的即使当前GOP没有读取到足够的帧数
xInitGOP( iPOCLast, iNumPicRcvd, isField );
// 将m_GOPList中的m_isEncoded设置为false,表示当前帧还未编码。m_GOPList里面存着一个GOP内每个帧的一些信息(例如POC、I/B/P帧等等),这些都是直接从cfg文件中得到的的信息。
for ( Int iGOPid=0; iGOPid < m_iGopSize; iGOPid++ )
{
m_pcCfg->setEncodedFlag(iGOPid, false);
}
//当前时间
clock_t iBeforeTime = clock();
Int iTimeOffset;//表示当前编码帧是当前GOP播放顺序的第几帧
Int pocCurr;//表示当前编码帧的POC
for ( Int iGOPid=0; iGOPid < m_iGopSize; iGOPid++ )
{
// 启动新的访问设备:在输出访问设备列表中创建一个条目
accessUnitsInGOP.push_back(AccessUnit());
AccessUnit& accessUnit = accessUnitsInGOP.back();
xGetBuffer( rcListPic, rcListPicYuvRecOut, iNumPicRcvd, iTimeOffset, pcPic, pcPicYuvRecOut, pocCurr, isField );
// 片级数据初始化
pcPic->clearSliceBuffer();
pcPic->allocateNewSlice();
m_pcSliceEncoder->setSliceIdx(0);
pcPic->setCurrSliceIdx(0);
m_pcSliceEncoder->initEncSlice ( pcPic, iPOCLast, pocCurr, iGOPid, pcSlice, isField );
.........
省略了一部分slice级和参考帧的工作
正式进入编码,这是帧级编码,CTU级编码在compressSlice函数中
Double lambda = 0.0;
Int actualHeadBits = 0;
Int actualTotalBits = 0;
Int estimatedBits = 0;
Int tmpBitsBeforeWriting = 0;
//帧级码率控制,这部分内容之前码率控制的文章讲很清楚了,进行省略
if ( m_pcCfg->getUseRateCtrl() ) // TODO: does this work with multiple slices and slice-segments?
{
Int frameLevel = m_pcRateCtrl->getRCSeq()->getGOPID2Level( iGOPid );
if ( pcPic->getSlice(0)->getSliceType() == I_SLICE )
{
frameLevel = 0;
}
m_pcRateCtrl->initRCPic( frameLevel );
estimatedBits = m_pcRateCtrl->getRCPic()->getTargetBits();
......
}
CTU级编码
{
const UInt numberOfCtusInFrame=pcPic->getPicSym()->getNumberOfCtusInFrame();
pcSlice->setSliceCurStartCtuTsAddr( 0 );
pcSlice->setSliceSegmentCurStartCtuTsAddr( 0 );
for(UInt nextCtuTsAddr = 0; nextCtuTsAddr < numberOfCtusInFrame; )
{
m_pcSliceEncoder->precompressSlice( pcPic );
m_pcSliceEncoder->compressSlice ( pcPic, false, false );
const UInt curSliceSegmentEnd = pcSlice->getSliceSegmentCurEndCtuTsAddr();
if (curSliceSegmentEnd < numberOfCtusInFrame)
{
const Bool bNextSegmentIsDependentSlice=curSliceSegmentEnd<pcSlice->getSliceCurEndCtuTsAddr();
const UInt sliceBits=pcSlice->getSliceBits();
pcPic->allocateNewSlice();
// prepare for next slice
pcPic->setCurrSliceIdx ( uiNumSliceSegments );
m_pcSliceEncoder->setSliceIdx ( uiNumSliceSegments );
pcSlice = pcPic->getSlice ( uiNumSliceSegments );
assert(pcSlice->getPPS()!=0);
pcSlice->copySliceInfo ( pcPic->getSlice(uiNumSliceSegments-1) );
pcSlice->setSliceIdx ( uiNumSliceSegments );
if (bNextSegmentIsDependentSlice)
{
pcSlice->setSliceBits(sliceBits);
}
else
{
pcSlice->setSliceCurStartCtuTsAddr ( curSliceSegmentEnd );
pcSlice->setSliceBits(0);
}
pcSlice->setDependentSliceSegmentFlag(bNextSegmentIsDependentSlice);
pcSlice->setSliceSegmentCurStartCtuTsAddr ( curSliceSegmentEnd );
// TODO: optimise cabac_init during compress slice to improve multi-slice operation
// pcSlice->setEncCABACTableIdx(m_pcSliceEncoder->getEncCABACTableIdx());
uiNumSliceSegments ++;
}
nextCtuTsAddr = curSliceSegmentEnd;
}
}
该帧编码结束,如果开启了码率控制则进行参数更新
if ( m_pcCfg->getUseRateCtrl() )
{
Double avgQP = m_pcRateCtrl->getRCPic()->calAverageQP();
Double avgLambda = m_pcRateCtrl->getRCPic()->calAverageLambda();
if ( avgLambda < 0.0 )
{
avgLambda = lambda;
}
m_pcRateCtrl->getRCPic()->updateAfterPicture( actualHeadBits, actualTotalBits, avgQP, avgLambda, pcSlice->getSliceType());
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 );
}
.......
将pcPic中的重建图像复制到pcPicYuvRecOut
//到这里编码基本就结束了,我在这里的pcPicYuvRecOut中引入了一些错误来证明本文算优劣
pcPic->getPicYuvRec()->copyToPic(pcPicYuvRecOut);
}
TEncGOP::GetBuffer
该函数的作用是从rcListPic、rcListPicYuvRecOut获取当前正在编码帧的应该存放的位置,然后突然函数的传入参数传出,即pcPic和pcPicYuvRecOut
Void TEncGOP::xGetBuffer( TComList<TComPic*>& rcListPic,
TComList<TComPicYuv*>& rcListPicYuvRecOut,
Int iNumPicRcvd,
Int iTimeOffset,
TComPic*& rpcPic,
TComPicYuv*& rpcPicYuvRecOut,
Int pocCurr,
Bool isField)
{
Int i;
// Rec. output
TComList<TComPicYuv*>::iterator iterPicYuvRec = rcListPicYuvRecOut.end();
for ( i = 0; i < (iNumPicRcvd - iTimeOffset + 1); i++ )
{
iterPicYuvRec--;
}
rpcPicYuvRecOut = *(iterPicYuvRec);
// Current pic.
TComList<TComPic*>::iterator iterPic = rcListPic.begin();
while (iterPic != rcListPic.end())
{
rpcPic = *(iterPic);
rpcPic->setCurrSliceIdx(0);
if (rpcPic->getPOC() == pocCurr)
{
break;
}
iterPic++;
}
assert (rpcPic != NULL);
assert (rpcPic->getPOC() == pocCurr);
return;
}