TComPicYuv、TComPic、TComPicSym、TComDataCU以及TComYuv的关系

TComPicYuv、TComPic、TComPicSym、TComDataCU以及TComYuv的关系TComPicYuv TComPic TComPicSym TComDataCU 以及 TComYuv 的关系 tcompicyuv

转载TComPicYuv、TComPic、TComPicSym、TComDataCU以及TComYuv的关系

原帖写的已经很清楚了,纸上得来终觉浅,自己写一下熟悉一遍

 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两个填充变量,关于填充的理解参考

TComPicYuv、TComPic、TComPicSym、TComDataCU以及TComYuv的关系

有点类似于回字,内部框是原始图像,外部框和内部框之间的是填充

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

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

参考自:TComPicYuv、TComPic、TComPicSym、TComDataCU以及TComYuv的关系

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顺序和光栅扫描顺序间的转换。

  1. g_auiZscanToRaster[ z-scan index ] = raster scan index
  2. 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 }

今天的文章 TComPicYuv、TComPic、TComPicSym、TComDataCU以及TComYuv的关系分享到此就结束了,感谢您的阅读。
编程小号
上一篇 2025-01-03 21:30
下一篇 2025-01-03 21:27

相关推荐

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/bian-cheng-ji-chu/100323.html