H.265/HEVC(High Efficiency Video Coding)のリファレンスソフトウェア(HM)のTComPicYuvクラスについて学んでいきます。前提として入力画像はQCIF(176×144)、YCbCr 4:2:0、configファイルはcfg/misc以下のencoder_randomaccess_main_GOP8.cfgを用いるものとして話を進めます。
TComPicYuv::create()
TComPicYuv::create()では、createWithoutCUInfo()をコールして入力画像のバッファ領域を確保し、バッファ領域をCTU単位で分割した場合の各CTUへのアドレスオフセットm_ctuOffsetInBufferと、さらにCTUをサブブロック単位で分割した場合のアドレスオフセットm_subCuOffsetInBufferの値を生成します。
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);
// numCuInWidth=3(入力画像の幅176をmaxCUWidth(64)で割って切り上げ)
const Int numCuInWidth = m_picWidth / maxCUWidth + (m_picWidth % maxCUWidth != 0);
// numCuInHeight=3(入力画像の高さ144をmaxCUHeight(64)で割って切り上げ)
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);
// Yの場合ctuHeight=64, CbCrの場合ctuHeight=32
const Int ctuHeight = maxCUHeight>>getChannelTypeScaleY(ch);
// Yの場合ctuWidth=64, CbCrの場合ctuWidth=32
const Int ctuWidth = maxCUWidth>>getChannelTypeScaleX(ch);
// Yの場合stride=336, CbCrの場合stride=168(createWithoutCUInfo()の処理詳細を参照)
const Int stride = getStride(ch);
// 各CTUへのアドレスオフセットを生成(3x3)
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;
}
}
// 各サブブロックへのアドレスオフセットを生成(maxCUDepth=4の場合, 16x16)
m_subCuOffsetInBuffer[chan] = new Int[(size_t)1 << (2 * maxCUDepth)];
// maxCUDepth=4の場合, numSubBlockPartitions=16
const Int numSubBlockPartitions=(1<<maxCUDepth);
// minSubBlockHeight=4
const Int minSubBlockHeight =(ctuHeight >> maxCUDepth);
// minSubBlockWidth=4
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;
}
}
}
}
有効画像領域とCTU、生成されるm_ctuOffsetInBufferの値の関係を図にすると以下のようになります。CbCrの場合は有効画像領域、CTUの幅と高さがそれぞれ1/2になり、横方向と縦方向のアドレスオフセットの動き方もそれぞれ1/2になります。
TComPicYuv::createWithoutCUInfo()
TComPicYuv::createWithoutCUInfo()は入力画像に対してマージン込みのバッファ領域を各色成分ごとに確保します。マージン領域は有効画像領域の周囲に配置され、マージンの幅はmaxCUWidth+16、高さはmaxCUHeight+16です。
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
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;
}
}
m_apiPicBufは確保したバッファ領域先頭へのポインタ、m_piPicOrgは有効画像領域先頭へのポインタになります。これらのポインタとマージン領域、有効画像領域の関係を図にすると以下のようになります。CbCrの場合はマージンや有効領域の幅と高さがそれぞれ1/2になります。
TComPicYuv::copyToPic()
TComPicYuv::copyToPic()では、pcPicYuvDst(コピー先)の示すバッファ領域に現バッファ領域を各色成分ごとにコピーします。
Void TComPicYuv::copyToPic (TComPicYuv* pcPicYuvDst) const
{
assert( m_chromaFormatIDC == pcPicYuvDst->getChromaFormat() );
for(Int comp=0; comp<getNumberValidComponents(); comp++)
{
const ComponentID compId=ComponentID(comp);
const Int width = getWidth(compId);
const Int height = getHeight(compId);
const Int strideSrc = getStride(compId);
assert(pcPicYuvDst->getWidth(compId) == width);
assert(pcPicYuvDst->getHeight(compId) == height);
// コピー元とコピー先のバッファ領域の幅(stride)が同じ場合
if (strideSrc==pcPicYuvDst->getStride(compId))
{
// バッファの全領域をコピー
// getBuf()によりm_apiPicBuf(バッファ領域先頭へのポインタ)が得られる
::memcpy ( pcPicYuvDst->getBuf(compId), getBuf(compId), sizeof(Pel)*strideSrc*getTotalHeight(compId));
}
else
{
// getAddr()によりm_piPicOrg(有効画像領域先頭へのポインタ)が得られる
const Pel *pSrc = getAddr(compId);
Pel *pDest = pcPicYuvDst->getAddr(compId);
const UInt strideDest = pcPicYuvDst->getStride(compId);
// コピー元とコピー先のバッファ領域の幅(stride)が異なる場合
// コピー元の有効画像領域をコピー先の有効画像領域にコピー
for(Int y=0; y<height; y++, pSrc+=strideSrc, pDest+=strideDest)
{
::memcpy(pDest, pSrc, width*sizeof(Pel));
}
}
}
}