原帖写的已经很清楚了,纸上得来终觉浅,自己写一下熟悉一遍
1、HM首先使用TComPicYuv保存从文件中读取出来的YUV数据
2、利用TComPicYuv构造TComPic,并把YUV数据复制给他(TComPic包含了TComPicYuv成员)
3、TComPic表示一帧图像,包含了数据(TComPicYuv),以及图像信息(TComPicSym,在TComPicSym中还包含了TComDataCU)
4、TComPic被放入图像队列中
5、处理图像队列中的每一个TComPic,实际是处理TComPic中的每一个CTU/CU(存放在TComPicSym中)
6、从TComPic中取出每一个CTU(用TComDataCU表示),调用xCompressCU进行处理
7、根据原始的CTU初始化TEncCu中的TComDataCU(最优的和临时的),这两个在编码的时候会使用
8、把TComPic的数据复制给TComYuv对象,表示编码过程中的数据(原始,预测、残差、重建等)
9、进行编码,tempCU用于编码过程中,bestCU用于保存最优信息
10、产生的最优信息会被复制回TComPicSym中
最重要的TComPicYuv,TComPic,TComPicSym三个类,一个个来看
TComPicYuv
class TComPicYuv
{
private:
Pel* m_apiPicBuf[MAX_NUM_COMPONENT]; //MAX_NUM_COMPONENT=3,三个颜色分量的缓冲区,包括了填充的内容
// 三个颜色分量的起始地址,由m_apiPicBufY计算得到
Pel* m_piPicOrg[MAX_NUM_COMPONENT]; ///< m_apiPicBufY + m_iMarginLumaY*getStride() + m_iMarginLumaX
Int m_picWidth; ///< Width of picture in pixels
Int m_picHeight; ///< Height of picture in pixels
ChromaFormat m_chromaFormatIDC; ///< Chroma Format,色度采样率
Int* m_ctuOffsetInBuffer[MAX_NUM_CHANNEL_TYPE]; ///< Gives an offset in the buffer for a given CTU (and channel)
Int* m_subCuOffsetInBuffer[MAX_NUM_CHANNEL_TYPE];///< Gives an offset in the buffer for a given sub-CU (and channel), relative to start of CTU
Int m_marginX; //X分量的填充宽度和高度 < margin of Luma channel (chroma's may be smaller, depending on ratio)
Int m_marginY; //Y分量的填充宽度和高度 ///< margin of Luma channel (chroma's may be smaller, depending on ratio)
Bool m_bIsBorderExtended;// 是否需要填充图像
}
这就是一个图像类,保存了一些图像高度和宽度,还有YUV数据,可以看到有MarginX MarginY两个填充变量,关于填充的理解参考
HEVC MarginX MarginY的理解https://blog.csdn.net/rankling315/article/details/54707841
有点类似于回字,内部框是原始图像,外部框和内部框之间的是填充
m_apiPicBu就是回字
m_piPicOrg就是内部真实为填充的图像YUV的起始位置
Int* m_ctuOffsetInBuffer[MAX_NUM_CHANNEL_TYPE]; ///< Gives an offset in the buffer for a given CTU (and channel)
Int* m_subCuOffsetInBuffer[MAX_NUM_CHANNEL_TYPE];///< Gives an offset in the buffer for a given sub-CU (and channel), relative to start of CTU
这两个变量
m_ctuOffsetInBuffer就是该帧中每个LCU的起始位置(偏移量)
m_subCuOffsetInBuffer子就是子CU的起始位置(偏移量)
但是是指相对于m_apiPicBuf指针的偏移量,也就是填充后的图像偏移量,LCU是从填充后开始算的,这点我是没想到
其中m_chromaFormatIDC是色度采样率,是一个枚举类型
enum ChromaFormat
{
CHROMA_400 = 0,
CHROMA_420 = 1,
CHROMA_422 = 2,
CHROMA_444 = 3,
NUM_CHROMA_FORMAT = 4
};
同理ChannelType是通道,亮度和色度
enum ChannelType
{
CHANNEL_TYPE_LUMA = 0,
CHANNEL_TYPE_CHROMA = 1,
MAX_NUM_CHANNEL_TYPE = 2
};
ComponentID是YCbCr
enum ComponentID
{
COMPONENT_Y = 0,
COMPONENT_Cb = 1,
COMPONENT_Cr = 2,
MAX_NUM_COMPONENT = 3
};
再看看cerate函数,有两个,一个是带CUInfo的,一个不带
create函数第一行就调用了createWithoutCUInfo,所以先来看看createWithoutCUInfo函数
Void TComPicYuv::createWithoutCUInfo ( const Int picWidth, ///< picture width
const Int picHeight, ///< picture height
const ChromaFormat chromaFormatIDC, ///< chroma format
const Bool bUseMargin, ///< if true, then a margin of uiMaxCUWidth+16 and uiMaxCUHeight+16 is created around the image.
const UInt maxCUWidth, ///< used for margin only
const UInt maxCUHeight) ///< used for margin only
{
destroy();
m_picWidth = picWidth;
m_picHeight = picHeight;
m_chromaFormatIDC = chromaFormatIDC;
m_marginX = (bUseMargin?maxCUWidth:0) + 16; // for 16-byte alignment
m_marginY = (bUseMargin?maxCUHeight:0) + 16; // margin for 8-tap filter and infinite padding
m_bIsBorderExtended = false;
// assign the picture arrays and set up the ptr to the top left of the original picture
//分配图片矩阵并将 PTR 设置为原始图片的左上角
//getNumberValidComponents函数,400采样返回1,不是400采样返回3
for(UInt comp=0; comp<getNumberValidComponents(); comp++)
{
const ComponentID ch=ComponentID(comp);
m_apiPicBuf[comp] = (Pel*)xMalloc( Pel, getStride(ch) * getTotalHeight(ch));
m_piPicOrg[comp] = m_apiPicBuf[comp] + (m_marginY >> getComponentScaleY(ch)) * getStride(ch) + (m_marginX >> getComponentScaleX(ch));
}
// initialize pointers for unused components to NULL
for(UInt comp=getNumberValidComponents();comp<MAX_NUM_COMPONENT; comp++)
{
m_apiPicBuf[comp] = NULL;
m_piPicOrg[comp] = NULL;
}
for(Int chan=0; chan<MAX_NUM_CHANNEL_TYPE; chan++)
{
m_ctuOffsetInBuffer[chan] = NULL;
m_subCuOffsetInBuffer[chan] = NULL;
}
}
其中getNumberValidComponents函数,YUV400采样返回1,不是400采样返回3
UInt getNumberValidComponents() const { return ::getNumberValidComponents(m_chromaFormatIDC); }
static inline UInt getNumberValidComponents (const ChromaFormat fmt) { return (fmt==CHROMA_400) ? 1 : MAX_NUM_COMPONENT; }
以YUV420为例,comp循环从0到2,对应的ch为Y,Cb,Cr,即一个亮度两个色度,之所以这么搞是因为三个缓存区的大小因为采样率的关系并不一致,对于亮度就是填充后的宽x高,但对于色度4:2:0的两个色度的宽高都是亮度的一半。
m_apiPicBuf[comp] = (Pel*)xMalloc( Pel, getStride(ch) * getTotalHeight(ch));
m_piPicOrg[comp] = m_apiPicBuf[comp] + (m_marginY >> getComponentScaleY(ch)) * getStride(ch) + (m_marginX >> getComponentScaleX(ch));
// 对比一下这个公式m_apiPicBufY + m_iMarginLumaY*getStride() + m_iMarginLumaX
//可以理解为 (m_marginY >> getComponentScaleY(ch)) 就是m_iMarginLumaY, (m_marginX >> getComponentScaleX(ch))就是m_iMarginLumaX
看看 getStride(ch)和getTotalHeight(ch),getStride和getTotalHeight就是填充后的长度和宽度
Int getStride (const ComponentID id) const
{ return ((m_picWidth ) + (m_marginX <<1)) >> getComponentScaleX(id); }
Int getTotalHeight (const ComponentID id) const { return ((m_picHeight ) + (m_marginY <<1)) >> getComponentScaleY(id); }
static inline UInt getChannelTypeScaleX (const ChannelType id, const ChromaFormat fmt) { return (isLuma(id) || (fmt==CHROMA_444)) ? 0 : 1; }
static inline UInt getChannelTypeScaleY (const ChannelType id, const ChromaFormat fmt) { return (isLuma(id) || (fmt!=CHROMA_420)) ? 0 : 1; }
getComponentScaleX(ch),ch是通道的类型,是luna亮度或者该图像的格式为444则返回0,即不对变长进行缩放。否则要进行右移1位,也就是缩小1倍.
getComponentScaleY(ch)同理,如果是luna亮度或者该图像的格式不是420则返回0,即不对变长进行缩放。否则进行右移1位也就是缩小1倍,因为420格式下的cb和cr是Y的宽高的1/2.可以看到只要是亮度,则不缩小。
在4:2:0的采样中,色度的宽高都是亮度的一半,所以缩放。
在4:4:4采样中,色度的宽高和亮度一致
在4:2:2采样中,色度的宽度缩小一半,高度保持不变
可以看到分配的空间是大于实际像素数量的
再来看看cerate函数,这个create主要是计算所有CTU左上角第一个像素在填充图像中三个分量的偏移位置(相对于m_apiPicBuf指针的偏移量,也就是填充后的图像偏移量),以及sub每一个4*4的子块相对于CTU的偏移量的偏移,代码逻辑挺好懂的就不赘述了
Void TComPicYuv::create ( const Int picWidth, ///< picture width
const Int picHeight, ///< picture height
const ChromaFormat chromaFormatIDC, ///< chroma format
const UInt maxCUWidth, ///< used for generating offsets to CUs.
const UInt maxCUHeight, ///< used for generating offsets to CUs.
const UInt maxCUDepth, ///< used for generating offsets to CUs.
const Bool bUseMargin) ///< if true, then a margin of uiMaxCUWidth+16 and uiMaxCUHeight+16 is created around the image.
{
createWithoutCUInfo(picWidth, picHeight, chromaFormatIDC, bUseMargin, maxCUWidth, maxCUHeight);
//计算LCU的个数
const Int numCuInWidth = m_picWidth / maxCUWidth + (m_picWidth % maxCUWidth != 0);
const Int numCuInHeight = m_picHeight / maxCUHeight + (m_picHeight % maxCUHeight != 0);
for(Int chan=0; chan<MAX_NUM_CHANNEL_TYPE; chan++)
{
const ChannelType ch= ChannelType(chan);//Luma=0 ,Choma=1 Max=2
const Int ctuHeight = maxCUHeight>>getChannelTypeScaleY(ch);
const Int ctuWidth = maxCUWidth>>getChannelTypeScaleX(ch);
const Int stride = getStride(ch);
m_ctuOffsetInBuffer[chan] = new Int[numCuInWidth * numCuInHeight];
for (Int cuRow = 0; cuRow < numCuInHeight; cuRow++)
{
for (Int cuCol = 0; cuCol < numCuInWidth; cuCol++)
{
m_ctuOffsetInBuffer[chan][cuRow * numCuInWidth + cuCol] = stride * cuRow * ctuHeight + cuCol * ctuWidth;
}
}
m_subCuOffsetInBuffer[chan] = new Int[(size_t)1 << (2 * maxCUDepth)];
const Int numSubBlockPartitions=(1<<maxCUDepth);
const Int minSubBlockHeight =(ctuHeight >> maxCUDepth);
const Int minSubBlockWidth =(ctuWidth >> maxCUDepth);
for (Int buRow = 0; buRow < numSubBlockPartitions; buRow++)
{
for (Int buCol = 0; buCol < numSubBlockPartitions; buCol++)
{
m_subCuOffsetInBuffer[chan][(buRow << maxCUDepth) + buCol] = stride * buRow * minSubBlockHeight + buCol * minSubBlockWidth;
}
}
}
}
还有一些成员函数蛮重要的
Int getWidth (const ComponentID id) const { return m_picWidth >> getComponentScaleX(id); }
Int getHeight (const ComponentID id) const { return m_picHeight >> getComponentScaleY(id); }
Int getStride (const ComponentID id) const { return ((m_picWidth ) + (m_marginX <<1)) >> getComponentScaleX(id); }
Int getTotalHeight (const ComponentID id) const { return ((m_picHeight ) + (m_marginY <<1)) >> getComponentScaleY(id); }
Pel* getBuf (const ComponentID ch) { return m_apiPicBuf[ch]; }
Pel* getAddr (const ComponentID ch) { return m_piPicOrg[ch]; }
Pel* getAddr (const ComponentID ch, const Int ctuRSAddr ) { return m_piPicOrg[ch] + m_ctuOffsetInBuffer[ch==0?0:1][ ctuRSAddr ]; }
Pel* getAddr (const ComponentID ch, const Int ctuRSAddr, const Int uiAbsZorderIdx )
{ return m_piPicOrg[ch] + m_ctuOffsetInBuffer[ch==0?0:1][ctuRSAddr] + m_subCuOffsetInBuffer[ch==0?0:1][g_auiZscanToRaster[uiAbsZorderIdx]];
getWidth和getHeight获取帧的宽高
getStride和getTotalHeight获取图像填充后的宽高
getBuf是获取通道ch的缓存区首地址
getAddr是获取通道ch的实际图像首地址
getAddr (const ComponentID ch, const Int ctuRSAddr ) 是访问特定编码单元 (CU) 或分区单元 (PU) 的原始图片起始位置
TComPic
/// picture class (symbol + YUV buffers)
//TComPic表示一张图像,它包含YUV数据(TComPicYuv)和信息(TComPicSym)
class TComPic
{
public:
typedef enum { PIC_YUV_ORG=0, PIC_YUV_REC=1, PIC_YUV_TRUE_ORG=2, NUM_PIC_YUV=3 } PIC_YUV_T;
// TRUE_ORG is the input file without any pre-encoder colour space conversion (but with possible bit depth increment)
TComPicYuv* getPicYuvTrueOrg() { return m_apcPicYuv[PIC_YUV_TRUE_ORG]; }
private:
UInt m_uiTLayer; // Temporal layer 时间层
Bool m_bUsedByCurr; // Used by current picture 是否被作为参考帧
Bool m_bIsLongTerm; // IS long term picture 是否为长参考图像
TComPicSym m_picSym; // Symbol 图像的信息
TComPicYuv* m_apcPicYuv[NUM_PIC_YUV]; // 图像的数据(索引0是原始图像,索引1是重建图像)
TComPicYuv* m_pcPicYuvPred; // Prediction // 图像的预测数据
TComPicYuv* m_pcPicYuvResi; // Residual 图像的残差数据
Bool m_bReconstructed; // 是否被重建
Bool m_bNeededForOutput; // 是否需要输出
UInt m_uiCurrSliceIdx; // Index of current slice 在此图像中,当前条带的索引
Bool m_bCheckLTMSB;
Bool m_isTop; // 顶场还是底场
Bool m_isField; // 帧还是场
// 条带的CU链表,即:
// 每一个slice中有若干LCU,每一个LCU又被细分为各个CU
// std::vector<TComDataCU*>就是存放LCU的CU
// 没有用到,因为CTU的相关信息存放在TComPicSym中
std::vector<std::vector<TComDataCU*> > m_vSliceCUDataLink;
}
还有一个函数我经常使用:getPOC()
Int getPOC() const { return m_picSym.getSlice(m_uiCurrSliceIdx)->getPOC(); }
获取当前图像在视频序列中POC
可以看到成员属性有TComPicYuv和TComPicSym两个类成员
TComPicSym
//picture symbol class
//TComPicSym表示图像的信息,它内部有一个TComDataCU数组,描述了图像组每一个LCU(CTU)的信息
class TComPicSym
{
private:
UInt m_frameWidthInCtus; // 图像在横向上有多少个LCU
UInt m_frameHeightInCtus;// 图像在纵向上有多少个LCU
UInt m_uiMinCUWidth; // 最小的CU的尺寸:4x4
UInt m_uiMinCUHeight;
UChar m_uhTotalDepth; ///< max. depth LCU可以划分的最大深度:4
UInt m_numPartitionsInCtu; // = 1<<(m_uhTotalDepth<<1);256,我的理解是一个LCU有多少个最小的CU
UInt m_numPartInCtuWidth; //一个LCU横向上有多少个最小的CU
UInt m_numPartInCtuHeight;//一个LCU纵向上有多少个最小的CU
UInt m_numCtusInFrame; //=m_frameWidthInCtus* m_frameHeightInCtus
std::deque<TComSlice*> m_apSlices;
TComDataCU** m_pictureCtuArray; //其实是一维数组,数组大小为LCU数量,数组放的TComDataCU*
std::vector<TComTile> m_tileParameters;
//对于给定的TS地址转为Rs地址,该参数加上TS地址解地址就是得到的RS地址,这个参数也是一个数组,数组长度为该帧的LCU个数+1,在create函数中该数组初始化为LCU的序号索引,从0开始
UInt* m_ctuTsToRsAddrMap;
//TS地址转为Rs地址的映射,通过改参数计算当前Tile序号currentTileIdx
UInt* m_puiTileIdxMap;
//对于给定的RS地址转为TS地址,该参数加上RS地址就是得到的TS地址,这个参数也是一个数组,数组长度为该帧的LCU个数+1
UInt* m_ctuRsToTsAddrMap;
TComSPS m_sps;
TComPPS m_pps;
}
来看该类的create函数
Void TComPicSym::create ( const TComSPS &sps, const TComPPS &pps, UInt uiMaxDepth )
{
destroy();
m_sps = sps;
m_pps = pps;
const ChromaFormat chromaFormatIDC = sps.getChromaFormatIdc();
const Int iPicWidth = sps.getPicWidthInLumaSamples();
const Int iPicHeight = sps.getPicHeightInLumaSamples();
const UInt uiMaxCuWidth = sps.getMaxCUWidth();
const UInt uiMaxCuHeight = sps.getMaxCUHeight();
m_uhTotalDepth = uiMaxDepth;
m_numPartitionsInCtu = 1<<(m_uhTotalDepth<<1); 1<<(4<<1) =256
m_uiMinCUWidth = uiMaxCuWidth >> m_uhTotalDepth; 64>>4
m_uiMinCUHeight = uiMaxCuHeight >> m_uhTotalDepth;
m_numPartInCtuWidth = uiMaxCuWidth / m_uiMinCUWidth; // equivalent to 1<<m_uhTotalDepth
m_numPartInCtuHeight = uiMaxCuHeight / m_uiMinCUHeight; // equivalent to 1<<m_uhTotalDepth
m_frameWidthInCtus = ( iPicWidth %uiMaxCuWidth ) ? iPicWidth /uiMaxCuWidth + 1 : iPicWidth /uiMaxCuWidth;
m_frameHeightInCtus = ( iPicHeight%uiMaxCuHeight ) ? iPicHeight/uiMaxCuHeight + 1 : iPicHeight/uiMaxCuHeight;
m_numCtusInFrame = m_frameWidthInCtus * m_frameHeightInCtus;
m_pictureCtuArray = new TComDataCU*[m_numCtusInFrame];
clearSliceBuffer();
allocateNewSlice();
//初始化m_pictureCtuArray数组,再调用TComDataCU::create函数,创建该该帧所有LCU的Data对象
for (UInt i=0; i<m_numCtusInFrame ; i++ )
{
m_pictureCtuArray[i] = new TComDataCU;
m_pictureCtuArray[i]->create( chromaFormatIDC, m_numPartitionsInCtu, uiMaxCuWidth, uiMaxCuHeight, false, uiMaxCuWidth >> m_uhTotalDepth
#if ADAPTIVE_QP_SELECTION
, m_pParentARLBuffer
#endif
);
}
}
Void setCtuTsToRsAddrMap( Int ctuTsAddr, Int ctuRsAddr ) { *(m_ctuTsToRsAddrMap + ctuTsAddr) = ctuRsAddr; }
Void setCtuRsToTsAddrMap( Int ctuRsAddr, Int ctuTsOrder ) { *(m_ctuRsToTsAddrMap + ctuRsAddr) = ctuTsOrder; }
UInt getTileIdxMap( Int ctuRsAddr ) const { return *(m_puiTileIdxMap + ctuRsAddr); }
TComDataCU* getCtu( UInt ctuRsAddr ) { return m_pictureCtuArray[ctuRsAddr]; }
四个后面会经常用到的函数,Ts地址和Rs地址的相互转换
获取Tileindex
还有通过ctuRsAddr,获取 TComDataCU,ctuRsAddr就是LCU位置索引,按Rs顺序
TComDataCU
CTU可以按四叉树方式递归划分为子CU,子CU按zig-zag顺序递归处理,如下图所示:
图中数字是CU的处理顺序。
实际上整个CTU的信息都在TComDataCU类中,且数据仅在CTU中存储一份,子CU只需指定其在CTU中的位置即可。类中成员变量m_absZIdxInCtu表示子CU在CTU中的位置(zig-zag顺序)。
CTU并不是按照二维数组形式表示的,因为CTU的划分方式不同,每个部分的深度也不同,所以CTU的“形状”不同无法用统一的形式表示出来。
为了使用统一的数据结构简洁的表示CTU,TComDataCU类将CTU划分成4x4的小块,存储每个小块的深度。64x64的CTU可以划分为256个4x4块,32x32的CTU可以划分为64个4x4块。
上图是一个32x32的CTU划分成64个4x4块的结果,按zig-zag顺序扫描。
TComDataCU类中成员m_puhWidth、m_puhHeight、m_puhDepth三个数组存储了每个4x4小块的宽、高和深度。
下面是一个真实的64x64CTU划分示例:
上面64x64的CTU包含256个4x4的块,每个块对应的宽、高、深如下:
1、深度等于0时,CU的尺寸是64x64,该CU下面4x4小块的数量是256
2、深度等于1时,CU的尺寸是32x32,该CU下面4x4小块的数量是64
3、深度等于2时,CU的尺寸是16x16,该CU下面4x4小块的数量是16
4、深度等于3时,CU的尺寸是8x8,该CU下面4x4小块的数量是4
由上图可以看到虽然不是每个CU都以最小4X4划分,但是 m_puhWidth、m_puhHeight、m_puhDepth记录的是最小4X4所处CU位置的深度,宽度和高度来确定的。并且是以Z字扫描的方式,10是因为这里是16进制,16进制10=十进制16
因为四叉树划分都是正方形块,所以宽高一样。对比m_puhDepth数组和上面的划分结构可以看出两者结果一致。
HM中提供了两个数组g_auiZscanToRaster和g_auiRasterToZscan可以进行zig-zag顺序和光栅扫描顺序间的转换。
-
g_auiZscanToRaster[ z-scan index ] = raster scan index
-
g_auiRasterToZscan[ raster index ] = z-scan index
class TComDataCU
{
private:
TComPic* m_pcPic; ///< picture class pointer // CU所在的图像
TComSlice* m_pcSlice; ///< slice header pointer CU所在的条带
UInt m_ctuRsAddr; ///< CTU (also known as LCU) address in a slice (Raster-scan address, as opposed to tile-scan/encoding order). CTU(LCU)地址,Raster-scan 扫描
UInt m_absZIdxInCtu; ///< absolute address in a CTU. It's Z scan order 在LCU中Z扫描顺序的地址
UInt m_uiCUPelX; ///< CU position in a pixel (X) 以像素为单位x方向的CU地址
UInt m_uiCUPelY; ///< CU position in a pixel (Y) 以像素为单位y方向的CU地址
UInt m_uiNumPartition; ///< total number of minimum partitions in a CU 当前CU中有多少个4x4的块
UChar* m_puhWidth; ///< array of widths 存放CU高度的数组
UChar* m_puhHeight; ///< array of heights 存放CU高度的数组
UChar* m_puhDepth; ///< array of depths 存放CU深度的数组
Int m_unitSize; ///< size of a "minimum partition" partition的最小尺寸,我理解为4X4
TComDataCU* m_pCtuAboveLeft; ///< pointer of above-left CTU. 当前CU左上角的CU
TComDataCU* m_pCtuAboveRight; ///< pointer of above-right CTU. 当前CU右上角的CU
TComDataCU* m_pCtuAbove; ///< pointer of above CTU. 当前CU上面的CU
TComDataCU* m_pCtuLeft; ///< pointer of left CTU 当前CU左边的CU
TComDataCU* m_apcCUColocated[NUM_REF_PIC_LIST_01]; ///< pointer of temporally colocated CU's for both directions
TComMvField m_cMvFieldA; ///< motion vector of position A
TComMvField m_cMvFieldB; ///< motion vector of position B
TComMvField m_cMvFieldC; ///< motion vector of position C
TComMv m_cMvPred; ///< motion vector predictor
Bool m_bDecSubCu; ///< indicates decoder-mode
Double m_dTotalCost; ///< sum of partition RD costs 总的代价
Distortion m_uiTotalDistortion; ///< sum of partition distortion 总的失真
UInt m_uiTotalBits; ///< sum of partition bits 总的比特数
UInt m_uiTotalBins; ///< sum of partition bins 总的二进制数
SChar m_codedQP; //量化的编码
}
Bool* m_skipFlag; ///< skip
SChar* m_pePartSize; ///< 当前CU的分割尺寸
SChar* m_pePredMode; ///< 预测模式
函数重点就是四个, // create / destroy / initialize / copy
Void create ( ChromaFormat chromaFormatIDC, UInt uiNumPartition, UInt uiWidth, UInt uiHeight, Bool bDecSubCu, Int unitSize);
Void destroy ( );
Void initCtu ( TComPic* pcPic, UInt ctuRsAddr );
Void copySubCU ( TComDataCU* pcCU, UInt uiPartUnitIdx );
Void copyInterPredInfoFrom ( TComDataCU* pcCU, UInt uiAbsPartIdx, RefPicList eRefPicList );
Void copyPartFrom ( TComDataCU* pcCU, UInt uiPartUnitIdx, UInt uiDepth );
Void copyToPic ( UChar uiDepth );
create函数
这个函数是在TComPicSym::create函数中进行调用的
for (UInt i=0; i<m_numCtusInFrame ; i++ )
{
m_pictureCtuArray[i] = new TComDataCU;
m_pictureCtuArray[i]->create( chromaFormatIDC, m_numPartitionsInCtu, uiMaxCuWidth, uiMaxCuHeight, false, uiMaxCuWidth >> m_uhTotalDepth
#if ADAPTIVE_QP_SELECTION
, m_pParentARLBuffer
#endif
);
}
传参m_numPartitionsInCtu 是该CU的4X4块数量,m_numPartitionsInCtu不一定就是256,LCU时是256,但如果是四叉树划分后深度不为0,就不是256了。
Void TComDataCU::create( ChromaFormat chromaFormatIDC, UInt uiNumPartition, UInt uiWidth, UInt uiHeight, Bool bDecSubCu, Int unitSize
#if ADAPTIVE_QP_SELECTION
, TCoeff *pParentARLBuffer
#endif
)
{
m_bDecSubCu = bDecSubCu;
m_pcPic = NULL;
m_pcSlice = NULL;
m_uiNumPartition = uiNumPartition;
m_unitSize = unitSize;
if ( !bDecSubCu )
{
m_phQP = (SChar* )xMalloc(SChar, uiNumPartition);
m_puhDepth = (UChar* )xMalloc(UChar, uiNumPartition);
m_puhWidth = (UChar* )xMalloc(UChar, uiNumPartition);
m_puhHeight = (UChar* )xMalloc(UChar, uiNumPartition);
}
m_pCtuAboveLeft = NULL;
m_pCtuAboveRight = NULL;
m_pCtuAbove = NULL;
m_pCtuLeft = NULL;
for(UInt i=0; i<NUM_REF_PIC_LIST_01; i++)
{
m_apcCUColocated[i] = NULL;
}
}
对深度,宽度,高度分配了m_numPartitionsInCtu个空间,然后对参考帧进行了初始化。
m_numPartitionsInCtu得具体情况具体分析,如果是上述TComPicSym::create函数中进行调用的的create,是以LCU调用的,如果是四叉树划分后的CU,就不是了。
m_ppcBestCU[i] = new TComDataCU; m_ppcBestCU[i]->create( chromaFormat, uiNumPartitions, uiWidth, uiHeight, false, uiMaxWidth >> (m_uhTotalDepth - 1) );
m_ppcTempCU[i] = new TComDataCU; m_ppcTempCU[i]->create( chromaFormat, uiNumPartitions, uiWidth, uiHeight, false, uiMaxWidth >> (m_uhTotalDepth - 1) );
init函数
/**
Initialize top-level CU: create internal buffers and set initial values before encoding the CTU.
\param pcPic picture (TComPic) class pointer
\param ctuRsAddr CTU address in raster scan order
*/
翻译一下就是初始化最顶层CU(LCU),在对 CTU 进行编码之前创建内部缓冲区并设置初始值。
换句话说就是这个函数只会被LCU层调用,普通CU的初始化使用initSubCU ,其中sub是指子。
//主要流程:
//(1)计算当前LCU在图像中的像素地址
//(2)计算LCU可以被分成多少个4x4的CU
//(3)把片的起始CU的地址存储起来
//(4)设置每一个4x4子CU的参数信息
//(5)设置当前LCU的左边、上方、右上角、左上角的相邻的LCU
//(6)设置参考帧的数组
Void TComDataCU::initCtu( TComPic* pcPic, UInt ctuRsAddr ){
const UInt maxCUWidth = pcPic->getPicSym()->getSPS().getMaxCUWidth();
const UInt maxCUHeight= pcPic->getPicSym()->getSPS().getMaxCUHeight();
m_pcPic = pcPic; // 当前CU所属的图像
m_pcSlice = pcPic->getSlice(pcPic->getCurrSliceIdx()); // 当前CU所属的片
m_ctuRsAddr = ctuRsAddr; // 当前CU地址
LCU在图像中的实际像素地址
m_uiCUPelX = ( ctuRsAddr % pcPic->getFrameWidthInCtus() ) * maxCUWidth;
m_uiCUPelY = ( ctuRsAddr / pcPic->getFrameWidthInCtus() ) * maxCUHeight;
m_absZIdxInCtu = 0;
m_dTotalCost = MAX_DOUBLE; // 总的RD消耗
m_uiTotalDistortion = 0;
m_uiTotalBits = 0;
m_uiTotalBins = 0;
m_uiNumPartition = pcPic->getNumPartitionsInCtu(); //256,当前LCU中可以有多少个最小4X4的CU
后面是一些初始化操作
memset( m_puhWidth , maxCUWidth, m_uiNumPartition * sizeof( *m_puhWidth ) );
memset( m_puhHeight , maxCUHeight, m_uiNumPartition * sizeof( *m_puhHeight ) );
// Setting neighbor CU 设置相邻CU
m_pCtuLeft = NULL;
m_pCtuAbove = NULL;
m_pCtuAboveLeft = NULL;
m_pCtuAboveRight = NULL;
UInt frameWidthInCtus = pcPic->getFrameWidthInCtus(); // 图像在横向上有多少个CTU
// 分别获取当前CU各个方向上相邻的CU
if ( m_ctuRsAddr % frameWidthInCtus )//说明不是每行第一个LCU,则左边一定有
{
m_pCtuLeft = pcPic->getCtu( m_ctuRsAddr - 1 );
}
if ( m_ctuRsAddr / frameWidthInCtus )//m_ctuRsAddr>frameWidthInCtus ,结果!=0,说明不是第一行,则上边一定有
{
m_pCtuAbove = pcPic->getCtu( m_ctuRsAddr - frameWidthInCtus );
}
if ( m_pCtuLeft && m_pCtuAbove ) //左边和上边都有,则左上角一定有
{
m_pCtuAboveLeft = pcPic->getCtu( m_ctuRsAddr - frameWidthInCtus - 1 );
}
if ( m_pCtuAbove && ( (m_ctuRsAddr%frameWidthInCtus) < (frameWidthInCtus-1) ) ) //右上角
{
m_pCtuAboveRight = pcPic->getCtu( m_ctuRsAddr - frameWidthInCtus + 1 );
}
//设置参考帧的数组
for(UInt i=0; i<NUM_REF_PIC_LIST_01; i++)
{
const RefPicList rpl=RefPicList(i);
if ( getSlice()->getNumRefIdx( rpl ) > 0 )
{
m_apcCUColocated[rpl] = getSlice()->getRefPic( rpl, 0)->getCtu( m_ctuRsAddr );
}
}
}
Init时还未编码,memset宽度和高度为最大尺寸及64
TComYuv
general YUV buffer class
TComYuv保存了每一个CU对应的YUV数据,原始数据从TComPic中得到(实际从TComPic的TComPicYuv成员中得到)。它主要用于表示编码过程中的原始数据、预测数据、残差数据以及重建数据
class TComYuv
{
private:
// YUV buffer
Pel* m_apiBuf[MAX_NUM_COMPONENT]; // 颜色分量的起始地址
// Parameter for general YUV buffer usage
UInt m_iWidth; // CU(可以是LCU,也可以是普通CU)的宽和高
UInt m_iHeight;
ChromaFormat m_chromaFormatIDC; < Chroma Format
}