文章目录
- TAppEncTop.cpp
-
- 引言
- Void TAppEncTop::encode()
- 执行流程
- 实现细节
-
- 1.Class TAppEncTop;
- 2.Void TAppEncTop::xInitLibCfg()
- 3.Void TAppEncTop::xCreateLib();
- 4.Void TAppEncTop::xInitLib(Bool isFieldCoding);
- 5.Void TComPicYuv::create (const Int picWidth, const Int picHeight, const ChromaFormat chromaFormatIDC, const UInt maxCUWidth, const UInt maxCUHeight, const UInt maxCUDepth, const Bool bUseMargin) )
TAppEncTop.cpp
引言
TAppEncTop是最外层的应用层编码类,通过使它拥有所有的默认参数和文件参数,配置低层的编码类参数和初始化参数,执行最高层的编码流程。
首先从Void TAppEncTop::encode()入手,了解编码的最高层编码流程。它是执行编码的第一层函数,是TAppEncTop的核心公用函数,使用了TAppEncTop的成员函数和属性,完成功能为:1.初始化TAppEncTop内部变量,引入YUV缓冲类和其他中间类;2. 引用TEncTop类的encode函数进行编码,直到YUV文件全部读取完;3.回收资源;
然后分析TAppEncTop类的结构和核心函数,以助了解实现细节。
VPS:视频参数集,描述时域各层之间的依赖关系,服务对象是一组视频序列,内容包括:子层共用的语法、会话需要的档次和级别等信息、其他非SPS信息;
SPS:序列参数集,描述解码相关的信息,服务对象是一个GOP编码后产生的CVS,内容包括:档次级别、分辨率、编码工具标识、时域分级信息等;
PPS:图像参数集,描述一幅图像共用的公共参数,服务一幅图像的所有SS,内容包括初始图像控制信息,如QP、分块信息等;
Void TAppEncTop::encode()
1.打开YUV文件并初始化编码器和缓冲区
//以二进制输出方式打开比特流文件
fstream bitstreamFile(m_bitstreamFileName.c_str(), fstream::binary | fstream::out);
//判断比特流文件是否存在,若bitstreamFile为空则输出错误提示并退出程序
if (!bitstreamFile)
{
fprintf(stderr, "\nfailed to open bitstream file `%s' for writing\n", m_bitstreamFileName.c_str());
exit(EXIT_FAILURE);
}
//定义YUV缓冲类指针对象
//此类定义了:原始图像和缓存图像的像素级宽高、色度格式和缓冲区中各CTU的偏移量等等,管理YUV缓存的信息和控制访问
TComPicYuv* pcPicYuvOrg = new TComPicYuv;
TComPicYuv* pcPicYuvRec = NULL;//记录缓存的临时对象
//设置低层编码类TEncTop的参数,包括VPS和其他配置文件的参数
xInitLibCfg();
//创建原始输入YUV文件和重构YUV文件,并将它们分别与TVideoIOYuv类成员m_cTVideoIOYuvInputFile、m_cTVideoIOYuvReconFile绑定;最后用一个空操作说明低层编码类的创建
xCreateLib();
//低层编码类的初始化工作m_cTEncTop.init(isFieldCoding);包括PPS和SPS初始化、各等级计算单元GOP、Slice、CU的初始化、转换量化单元初始化、运动搜索等。
xInitLib(m_isField);
//打印输入和输出的YUV色度格式
printChromaFormat();
// 初始化本编码类中的部分变量
Int iNumEncoded = 0;//记录已编码帧数
Bool bEos = false;//控制编码是否结束
//InputColourSpaceConversion枚举是编码输入前的颜色空间转换,有四种:不变、RGBtoGBR、YCbCrtoYCrCb、YCbCrtoYYY 后两者用于debug中
const InputColourSpaceConversion ipCSC = m_inputColourSpaceConvert;//输入文件应用的颜色空间装换
const InputColourSpaceConversion snrCSC = (!m_snrInternalColourSpace) ? m_inputColourSpaceConvert : IPCOLOURSPACE_UNCHANGED;//计算信噪比是否使用颜色空间转换?是则同上转换,否则不转换
list<AccessUnit> outputAccessUnits; ///编码过程中写入的访问单元列表
TComPicYuv cPicYuvTrueOrg;//临时YUV缓存对象
// 为原始YUV缓冲区分配内存空间
if( m_isField )//是否需要场编码,默认非场编码,场编码的YUV文件会在配置文件中说明
{
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->create ( m_iSourceWidth, m_iSourceHeight, m_chromaFormatIDC, m_uiMaxCUWidth, m_uiMaxCUHeight, m_uiMaxTotalCUDepth, true );//初始化YUV缓存的指针,计算了CU的数量,依次划分了CTU和CU的结构
cPicYuvTrueOrg.create(m_iSourceWidth, m_iSourceHeight, m_chromaFormatIDC, m_uiMaxCUWidth, m_uiMaxCUHeight, m_uiMaxTotalCUDepth, true );
}
//针对球形视频的拓展编码支持
#if EXTENSION_360_VIDEO
TExt360AppEncTop ext360(*this, m_cTEncTop.getGOPEncoder()->getExt360Data(), *(m_cTEncTop.getGOPEncoder()), *pcPicYuvOrg);
#endif
TEncTemporalFilter temporalFilter;//时间滤波器
if (m_gopBasedTemporalFilterEnabled)//是否启动基于GOP的时间滤波器,默认禁止
{
temporalFilter.init(m_FrameSkip, m_inputBitDepth, m_MSBExtendedBitDepth, m_internalBitDepth, m_iSourceWidth, m_iSourceHeight,
m_aiPad, m_framesToBeEncoded, m_bClipInputVideoToRec709Range, m_inputFileName, m_chromaFormatIDC,
m_inputColourSpaceConvert, m_iQP, m_iGOPSize, m_gopBasedTemporalFilterStrengths,
m_gopBasedTemporalFilterFutureReference);
}
2.循环编码
while ( !bEos )//由bEos控制,对视频帧进行编码
{
//更新缓冲。新建缓存到重建YUV文件列表m_cListPicYuvRec。
xGetBuffer(pcPicYuvRec);
// read input YUV file
#if EXTENSION_360_VIDEO
//省略
#else
m_cTVideoIOYuvInputFile.read( pcPicYuvOrg, &cPicYuvTrueOrg, ipCSC, m_aiPad, m_InputChromaFormatIDC, m_bClipInputVideoToRec709Range );
#endif
//默认不使用
if (m_gopBasedTemporalFilterEnabled)
{
temporalFilter.filter(pcPicYuvOrg, m_iFrameRcvd);
}
// 增加接收的帧
m_iFrameRcvd++;
//场模式下要偶数且编完可结束,非场模式下编码帧完成数量要求即可结束
bEos = (m_isField && (m_iFrameRcvd == (m_framesToBeEncoded >> 1) )) || ( !m_isField && (m_iFrameRcvd == m_framesToBeEncoded) );
Bool flush = 0;
// 文件读取完成后刷新编码器中的图像队列
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, ipCSC, snrCSC, m_cListPicYuvRec, outputAccessUnits, iNumEncoded, m_isTopFieldFirst );
}
else
{
m_cTEncTop.encode( bEos, flush ? 0 : pcPicYuvOrg, flush ? 0 : &cPicYuvTrueOrg, ipCSC, snrCSC, m_cListPicYuvRec, outputAccessUnits, iNumEncoded );
}
// 结合重构YUV文件列表和访问列表outputAccessUnits将编码数据写入到二进制流输出文件中
if ( iNumEncoded > 0 )
{
xWriteOutput(bitstreamFile, iNumEncoded, outputAccessUnits);
outputAccessUnits.clear();
}
// 暂时跳过帧,默认不用
if( m_temporalSubsampleRatio > 1 )
{
m_cTVideoIOYuvInputFile.skipFrames(m_temporalSubsampleRatio-1, m_inputFileWidth, m_inputFileHeight, m_InputChromaFormatIDC);
}
}
3.收尾工作:Debug和释放资源
//打印编码结果统计信息
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;
执行流程
实现细节
1.Class TAppEncTop;
继承自TAppEncCfg,使用了第二层编码类TEncTop 和其他基础类等类接口;
它的类结构包括:
- 定义类接口成员:TEncTop编码类、YUV文件类TVideoIOYuv、缓存列表类TComList<TComPicYuv*> 、
- 初始化函数和资源回收函数;
- 文件相关函数:写文件函数等
class TAppEncTop : public TAppEncCfg
{
private:
// class interface
TEncTop m_cTEncTop; ///< encoder class
TVideoIOYuv m_cTVideoIOYuvInputFile; ///< input YUV file
TVideoIOYuv m_cTVideoIOYuvReconFile; ///< output reconstruction file
TComList<TComPicYuv*> m_cListPicYuvRec; ///< list of reconstruction YUV files
Int m_iFrameRcvd; ///< number of received frames
UInt m_essentialBytes;
UInt m_totalBytes;
protected:
// initialization
Void xCreateLib (); ///< create files & encoder class
Void xInitLibCfg (); ///< initialize internal variables
Void xInitLib (Bool isFieldCoding); ///< initialize encoder class
Void xDestroyLib (); ///< destroy encoder class
/// 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); ///< write bitstream to file
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
};
2.Void TAppEncTop::xInitLibCfg()
借助TAppEncTop将参数传递给TEncTop,TEncTop的参数更深入编码细节: VPS参数集、GOP编码结构、Slice模式、去方块滤波器、MV搜索、量化参数、率失真控制、并行合并等。
//代码余额500行,简要分析
//1.从TAppEncTop(继承自TAppEncCfg)的成员属性中设置VPS的参数,包括时域层数及其标记
TComVPS vps;
vps.setxxx(..);
//2.设置成员m_cTEncTop(TEncTop型)的属性,编码器使用的参数
m_cTEncTop.setVPS(&vps);
m_cTEncTop.setxxx(...);
3.Void TAppEncTop::xCreateLib();
创建输入和重构的YUV文件,与TVideoIOYuv类绑定
Void TAppEncTop::xCreateLib()
{
// false指读模式,读取输入YUV文件的信息并设置到TVideoIOYuv类,包括各通道文件设置的比特深度、读写中变动的比特深度
m_cTVideoIOYuvInputFile.open( m_inputFileName, false, m_inputBitDepth, m_MSBExtendedBitDepth, m_internalBitDepth );
//跳过开头的m_FrameSkip个帧。这个函数可以正确处理输入文件不可查找的情况。
//其中高、宽和色度格式用于计算帧尺寸frameSize,与m_FrameSkip相乘即为该帧在文件流的位置,以此调整输入流的起始位置,否则清空输入流后存入相同大小的字节。
m_cTVideoIOYuvInputFile.skipFrames(m_FrameSkip, m_inputFileWidth, m_inputFileHeight, m_InputChromaFormatIDC);
if (!m_reconFileName.empty())//若没有输出的重建文件则新建。
{
m_cTVideoIOYuvReconFile.open(m_reconFileName, true, m_outputBitDepth, m_outputBitDepth, m_internalBitDepth); // true指写模式
}
m_cTEncTop.create(); // 创建TEncTop类的编码器,也是空操作
}
4.Void TAppEncTop::xInitLib(Bool isFieldCoding);
初始化低层编码器参数,包括PPS、SPS和各处理、转换好量化单元的初始化。
Void TAppEncTop::xInitLib(Bool isFieldCoding)
{
m_cTEncTop.init(isFieldCoding);//调用低层编码类的初始化函数
}
//简要分析
Void TEncTop::init(Bool isFieldCoding)
{
//完成SPS参数的设置
xInitSPS(sps0);
xInitVPS(m_cVPS, sps0);
//完成PPS参数的设置
xInitPPS(pps0, sps0);
xInitRPS(sps0, isFieldCoding);
xInitScalingLists(sps0, pps0);
// 初始化处理单元
m_cGOPEncoder. init( this );
m_cSliceEncoder.init( this );
m_cCuEncoder. init( this );
m_cCuEncoder.setSliceEncoder(&m_cSliceEncoder);
// 初始化变换和量化单元
m_pcCavlcCoder = getCavlcCoder();
m_cTrQuant.init( 1 << m_uiQuadtreeTULog2MaxSize,
m_useRDOQ,
m_useRDOQTS,
m_useSelectiveRDOQ,
true
,m_useTransformSkipFast
#if ADAPTIVE_QP_SELECTION
,m_bUseAdaptQpSelect
#endif
);
//初始化编码器搜索类
m_cSearch.init( this, &m_cTrQuant, m_iSearchRange, m_bipredSearchRange, m_motionEstimationSearchMethod, m_maxCUWidth, m_maxCUHeight, m_maxTotalCUDepth, &m_cEntropyCoder, &m_cRdCost, getRDSbacCoder(), getRDGoOnSbacCoder() );
//用来模拟解码器使用的滑动机制,我们需要一个通用的编码器和解码器都使用滑动机制
m_iMaxRefPicNum = 0;
}
5.Void TComPicYuv::create (const Int picWidth, const Int picHeight, const ChromaFormat chromaFormatIDC, const UInt maxCUWidth, const UInt maxCUHeight, const UInt maxCUDepth, const Bool bUseMargin) )
参数说明:(图像宽,图像高,色度格式,CU的最大宽、高、深度(用于计算各CU的偏移),true表示在图像周围创建边距);
功能说明:初始化YUV缓存的指针,计算了CU的数量,依次划分了CTU和CU的结构
//简要说明
//1.该函数完成的工作:清空指针和释放缓存区内容,申请新的缓存空间和相应指针,
//将缓存区的指针为有效通道上的图像数组的左上角,无效通道的指为空,CTU及其子块的偏移数组指针为空。
createWithoutCUInfo(picWidth, picHeight, chromaFormatIDC, bUseMargin, maxCUWidth, maxCUHeight);
//2.计算CU在宽高上的数目:图像宽/高除以CU的最大宽/高 + 两者是否有余
const Int numCuInWidth = m_picWidth / maxCUWidth + (m_picWidth % maxCUWidth != 0);
//根据色度格式和对应缩放比例分别设置亮度、色度CTU相应的宽、高和步幅,结合步幅计算每个CTU的偏移量m_ctuOffsetInBuffer[chan];
const Int ctuHeight = maxCUHeight>>getChannelTypeScaleY(ch);
const Int stride = getStride(ch);
m_ctuOffsetInBuffer[chan][cuRow * numCuInWidth + cuCol] = stride * cuRow * ctuHeight + cuCol * ctuWidth;
//同理计算子CU的高宽。
const Int minSubBlockWidth=(ctuWidth >> maxCUDepth);
//结合设置的CU深度,结合步幅计算子CTU的数量和偏移量m_subCuOffsetInBuffer[chan]
m_subCuOffsetInBuffer[chan][(buRow << maxCUDepth) + buCol] = stride * buRow * minSubBlockHeight + buCol * minSubBlockWidth;