本文章主要参考
https://blog.csdn.net/HEVC_CJL/article/details/8184276
https://blog.csdn.net/sujunzy666/article/details/16928337
https://blog.csdn.net/g0ose/article/details/52116453
http://www.cnblogs.com/gamedes/p/4541765.html
大佬们的代码进行学习,作为学习记录
一、首先是涉及到的全局变量
#define MAX_CU_DEPTH 6 // log2(LCUSize) 最大CU深度为6
#define MAX_CU_SIZE (1<<(MAX_CU_DEPTH)) // maximum allowable size of CU, surely 64? (not 1<<7 = 128)最大CU_size
#define MIN_PU_SIZE 4
#define MIN_TU_SIZE 4
#define MAX_TU_SIZE 32
#define MAX_NUM_SPU_W (MAX_CU_SIZE/MIN_PU_SIZE) // maximum number of SPU in horizontal line水平方向上SPU的最大数目
#define SCALING_LIST_REM_NUM 6
注:(ps:1<<7表示二进制的1向左左移7位即10000000=128)(原谅我是个小白,大佬尽情鄙视)
二、对于图像步长值(stride value)的解释
先贴一段HM代码中的源代码
// Access starting position of YUV transform unit buffer by pix offset for square & non-square blocks;对于正方形和非正方形块,通过pix偏移访问YUV转换单元缓冲区的起始位置
Pel* getAddrPix (const ComponentID id, const UInt iPixX, const UInt iPixY ) { return m_apiBuf[id] + iPixY * getStride(id) + iPixX; }
const Pel* getAddrPix (const ComponentID id, const UInt iPixX, const UInt iPixY ) const { return m_apiBuf[id] + iPixY * getStride(id) + iPixX; }
// Get stride value of YUV buffer;得到YUV缓冲区域的步长值
UInt getStride (const ComponentID id) const { return m_iWidth >> getComponentScaleX(id); }
UInt getHeight (const ComponentID id) const { return m_iHeight >> getComponentScaleY(id); }
UInt getWidth (const ComponentID id) const { return m_iWidth >> getComponentScaleX(id); }
ChromaFormat getChromaFormat () const { return m_chromaFormatIDC; }
UInt getNumberValidComponents () const { return ::getNumberValidComponents(m_chromaFormatIDC); }
UInt getComponentScaleX (const ComponentID id) const { return ::getComponentScaleX(id, m_chromaFormatIDC); }
UInt getComponentScaleY (const ComponentID id) const { return ::getComponentScaleY(id, m_chromaFormatIDC); }
};
下面对图像步长值(stride value)进行解释
当视频图像存储在内存时,图像的每一行末尾也许包含一些扩展的内容,这些扩展的内容只影响图像如何存储在内存中,但是不影响图像如何显示出来。Stride 就是这些扩展内容的名称,Stride 也被称作 Pitch,如果图像的每一行像素末尾拥有扩展内容,Stride 的值一定大于图像的宽度值,就像下图所示:
两个缓冲区包含同样大小(宽度和高度)的视频帧,却不一定拥有同样的 Stride 值,如果你处理一个视频帧,你必须在计算的时候把 Stride 考虑进去。
另外,一张图像在内存中有两种不同的存储序列(arranged),对于一个从上而下存储(Top-Down) 的图像,最顶行的像素保存在内存中最开头的部分,对于一张从下而上存储(Bottom-Up)的图像,最后一行的像素保存在内存中最开头的部分,下面图示展示了这两种情况:
一张从下而上的图像拥有一个负的 Stride 值,因为 Stride 被定义为[从一行像素移动到下一行像素时需要跨过多少个像素],仅相对于被显示出来的图像而言;而 YUV 图像永远都是从上而下表示的,以及任何包含在 Direct3D Surface 中的图像必须是从上而下,RGB 图像保存在系统内存时通常是从下而上。尤其是视频变换,特别需要处理不同 Stride 值的图像,因为输入缓冲也许与输出缓冲不匹配。
三、具体代码注释实现(如果有大佬能够指出错误或者解答疑惑,小弟万分感谢)
Void TComPattern::initAdiPattern(TComDataCU* pcCU, //当前待处理的CU
UInt uiZorderIdxInPart, //当前待处理的PU相对于pcCU的位置,以4*4块为单位
UInt uiPartDepth, //当前PU的深度(相对于当前的CU),非0即1
Int* piAdiBuf, //其指向的空间是为了存储预测数据,这里只是使用上一行,左一列来存储参考点的数据
Int iOrgBufStride, //m_iYuvExtStride = ((MAX_CU_SIZE + 8) << 4);??
Int iOrgBufHeight, //m_iYuvExtHeight = ((MAX_CU_SIZE + 2) << 4);??
Bool& bAbove, //指示上面块可否使用,根据代码调试的过程看,这个是为了计算均值时使用的一个标记
Bool& bLeft, //指示左面块可否使用,同上
Bool bLMmode) //
{
Pel* piRoiOrigin;//指向原始数据
Int* piAdiTemp;
//当前PU的尺寸:先计算当前CU的尺寸,在根据uiPartDepth计算
UInt uiCuWidth = pcCU->getWidth(0) >> uiPartDepth;
UInt uiCuHeight = pcCU->getHeight(0) >> uiPartDepth;
//参考点水平和竖直方向上的个数(比当前PU的尺寸大一倍)
UInt uiCuWidth2 = uiCuWidth << 1;//uiCuWidth2、uiCuHeight2分别等于二倍的当前CU的宽度和高度
UInt uiCuHeight2 = uiCuHeight << 1;
UInt uiWidth;
UInt uiHeight;
//当前图像的跨度(比图像的宽稍微大点)
Int iPicStride = pcCU->getPic()->getStride();//图像跨度(步长)值要大于图像宽度
Int iUnitSize = 0;
Int iNumUnitsInCu = 0;
Int iTotalUnits = 0;
Bool bNeighborFlags[4 * MAX_NUM_SPU_W + 1];//MAX_NUM_SPU_W是水平方向上的SPU的数量,MAX_NUM_SPU_W=(MAX_CU_SIZE/MIN_PU_SIZE);存储五个方向上的邻域标志位,即是否可用
Int iNumIntraNeighbor = 0;//用来统计可用邻域的数目
UInt uiPartIdxLT, uiPartIdxRT, uiPartIdxLB;
//获取当前PU左上角,右上角以及左下角的位置, 以4x4块为单位的ZScanOrder
pcCU->deriveLeftRightTopIdxAdi(uiPartIdxLT, uiPartIdxRT, uiZorderIdxInPart, uiPartDepth);//指针指向左上角和右上角的4x4ZScanOrder
pcCU->deriveLeftBottomIdxAdi(uiPartIdxLB, uiZorderIdxInPart, uiPartDepth);//指针指向左下角的4x4ZScanOrder
iUnitSize = g_uiMaxCUWidth >> g_uiMaxCUDepth;
iNumUnitsInCu = uiCuWidth / iUnitSize;
iTotalUnits = (iNumUnitsInCu << 2) + 1;
//bNeighborFlags存放的是参考点是否可用,依次存放左下,左,左上,上,右上5个区域的参考点可用性,即扫描顺序为从左下到左上,再从左上到右上
bNeighborFlags[iNumUnitsInCu * 2] = isAboveLeftAvailable(pcCU, uiPartIdxLT);
iNumIntraNeighbor += (Int)(bNeighborFlags[iNumUnitsInCu * 2]);
iNumIntraNeighbor += isAboveAvailable(pcCU, uiPartIdxLT, uiPartIdxRT, bNeighborFlags + (iNumUnitsInCu * 2) + 1);
iNumIntraNeighbor += isAboveRightAvailable(pcCU, uiPartIdxLT, uiPartIdxRT, bNeighborFlags + (iNumUnitsInCu * 3) + 1);
iNumIntraNeighbor += isLeftAvailable(pcCU, uiPartIdxLT, uiPartIdxLB, bNeighborFlags + (iNumUnitsInCu * 2) - 1);
iNumIntraNeighbor += isBelowLeftAvailable(pcCU, uiPartIdxLT, uiPartIdxLB, bNeighborFlags + iNumUnitsInCu - 1);//计算5个区域内的可用邻域的个数
bAbove = true;
bLeft = true;
//参考点在水平和竖直方向上的个数,+1是因为要记录左上角的一个位置
uiWidth = uiCuWidth2 + 1;
uiHeight = uiCuHeight2 + 1;
//这个if语句我也不知道干嘛的,可能是为了让大家看不懂而设置????
if (((uiWidth << 2)>iOrgBufStride) || ((uiHeight << 2)>iOrgBufHeight))
{
return;
}
//获得当前PU对应的原始数据的地址
piRoiOrigin = pcCU->getPic()->getPicYuvRec()->getLumaAddr(pcCU->getAddr(), pcCU->getZorderIdxInCU() + uiZorderIdxInPart);
piAdiTemp = piAdiBuf;
//获得当前PU的参考点(内部分为3种情况:1 所有块都可用2所有块都不可用3有部分块可用)
fillReferenceSamples(g_bitDepthY, piRoiOrigin, piAdiTemp, bNeighborFlags, iNumIntraNeighbor, iUnitSize, iNumUnitsInCu, iTotalUnits, uiCuWidth, uiCuHeight, uiWidth, uiHeight, iPicStride, bLMmode);
//! 下面所进行的工作主要是对参考样点进行3抽头的滤波。piAdiBuf指向滤波前的参考样点的首地址,在滤波前,先将所有参考样点
//! 拷贝到piFilterBuf指向的区域,经滤波后的样点值保存在piFilterBufN指向的区域,最终将滤波后的样点值拷贝到piFilterBuf1
//! 值得一提的是,最终的结果是,piAdiBuf指向的区域是未经滤波的样点值,而piFilterBuf1指向的区域是经过滤波的样点值,
//! 两者的地址相差uiWH = uiWidth * uiHeight = (uiCuWidth2 + 1) * (uiCuHeight2 + 1),这就解释了在进行真正的帧内预测时,
//! 在需要滤波时,指向piAdiBuf的指针需要加上uiWH的偏移量
Int* piFilteredBuf1 = piAdiBuf + uiWH; // 1. filter buffer
Int* piFilteredBuf2 = piFilteredBuf1 + uiWH; // 2. filter buffer
Int* piFilterBuf = piFilteredBuf2 + uiWH; // buffer for 2. filtering (sequential)
Int* piFilterBufN = piFilterBuf + iBufSize; // buffer for 1. filtering (sequential) //!<存放的是参考样点经3抽头滤波后的值
// draft 8.4.4.2.3 Filtering process of neighbouring samples
Int l = 0;
// left border from bottom to top
for (i = 0; i < uiCuHeight2; i++)
{
piFilterBuf[l++] = piAdiTemp[uiWidth * (uiCuHeight2 - i)]; !< 左边界,存储顺序为从下往上
}
// top left corner
piFilterBuf[l++] = piAdiTemp[0]; //!< 左上边界
// above border from left to right
for (i = 0; i < uiCuWidth2; i++)
{
piFilterBuf[l++] = piAdiTemp[1 + i]; //!<上边界,存储顺序为从左往右
}
// 1. filtering with [1 2 1]
piFilterBufN[0] = piFilterBuf[0]; //!< 第1个点直接保存,不滤波
piFilterBufN[iBufSize - 1] = piFilterBuf[iBufSize - 1]; //!< 最后一个点也直接保存,不滤波
for (i = 1; i < iBufSize - 1; i++) //!< 对中间样点值进行3抽头[1 2 1] / 4 的平滑滤波
{
piFilterBufN[i] = (piFilterBuf[i - 1] + 2 * piFilterBuf[i] + piFilterBuf[i + 1] + 2) >> 2;
}
// fill 1. filter buffer with filtered values
l = 0;
for (i = 0; i < uiCuHeight2; i++)
{
piFilteredBuf1[uiWidth * (uiCuHeight2 - i)] = piFilterBufN[l++]; // left border from bottom to top //!< 左边界
}
piFilteredBuf1[0] = piFilterBufN[l++]; //!< 左上边界
for (i = 0; i < uiCuWidth2; i++)
{
piFilteredBuf1[1 + i] = piFilterBufN[l++]; // above border from left to right //!< 上边界
}
}
(注,上图中,uiWidth和uiHeight实际上对应的是代码中的uiCUWidth和uiCUHeight,因画图的时候发生了遗漏,特此说明)
本博文仅作小白学习记录使用,所有文章出处均在开头链接处可见,所有疑惑点均作了标注。欢迎各位大佬交流批评指正,侵删。