好了,废话不多说,咱们不搞理论的,没有太多废话!基于HM 9.0
要想实际的打出CU划分的最终结果,我想了一个办法,就是修改HEVC的decoder,在CU最终划分的结果的地方把像素换成一个特殊值,比如luma改成0,就变成黑色的了。
怎么改呢,我代码才看了没多久,只好先研究一下decoder:
作为屌丝程序员,上来俺就找decoder的main函数,俺知道main函数里面这句话是关键:
1
2
|
//
call decoding function cTAppDecTop.decode(); |
不多说,进去,在TAppDecTop::decode()函数里面俺知道这一句也是关键:
1
|
bNewPicture
= m_cTDecTop.decode(nalu, m_iSkipFrame, m_iPOCLastDisplay); |
不多说,进去,然后就豁然了一下,下面是一个对nalu.m_nalUnitType switch的语句,是对不同的nal分别解码,我要管的不是pps也不是sps就直接看解码一个slice:
1
|
return
xDecodeSlice(nalu, iSkipFrame, iPOCLastDisplay); |
xDecodeSlice这个函数比较长,不过我就看上了一句话,这个函数的结尾部分:
1
2
|
//
Decode a picture m_cGopDecoder.decompressSlice(nalu.m_Bitstream,
pcPic); |
TDecGop::decompressSlice(TComInputBitstream*pcBitstream, TComPic*& rpcPic)这个函数俺也就看上一句话:
1
|
m_pcSliceDecoder->decompressSlice(
pcBitstream, ppcSubstreams, rpcPic, m_pcSbacDecoder, m_pcSbacDecoders); |
这个函数很关键,里面有这样的一个大循环:
1
2
|
for (
Int iCUAddr = iStartCUAddr; !uiIsLast && iCUAddr < rpcPic->getNumCUsInFrame(); iCUAddr = rpcPic->getPicSym()->xCalculateNxtCUAddr(iCUAddr) ) { /*循环里面的代码*/ } |
看到这个地方你想到了啥呢,没错,这是对LCU一个个循环的地方,在这个循环里面调用了m_pcCuDecoder->decodeCU( pcCU, uiIsLast ); decodeCU就是对一个LCU进行解码,到decodeCU里面看一看,发现他调用了xDecodeCU,而xDecodeCU是一个递归函数,是分别解码各个最终划分的CU,xDecodeCU里面有一个是否要一分为四的判断:
1
|
if (
( ( uiDepth < pcCU->getDepth( uiAbsPartIdx ) ) && ( uiDepth < g_uiMaxCUDepth - g_uiAddCUDepth ) ) || bBoundary ) |
如果条件不满足,不就说明到达最终的CU的划分的地方了吗,好,在if出来的地方填上这样的一句话:
1
|
std::cout<< "uiLPelX
uiTPelY uiRPelX uiBPelY " <<uiLPelX<< "
" <<uiTPelY<< "
" <<uiRPelX<< "
" <<uiBPelY<<std::endl; |
运行解码器,解码一个I帧,输出的部分结果如下:
1
2
3
4
5
6
7
8
9
10
|
uiLPelX
uiTPelY uiRPelX uiBPelY 0 0 15 15 uiLPelX
uiTPelY uiRPelX uiBPelY 16 0 31 15 uiLPelX
uiTPelY uiRPelX uiBPelY 0 16 15 31 uiLPelX
uiTPelY uiRPelX uiBPelY 16 16 31 31 uiLPelX
uiTPelY uiRPelX uiBPelY 32 0 63 31 uiLPelX
uiTPelY uiRPelX uiBPelY 0 32 15 47 uiLPelX
uiTPelY uiRPelX uiBPelY 16 32 31 47 uiLPelX
uiTPelY uiRPelX uiBPelY 0 48 15 63 uiLPelX
uiTPelY uiRPelX uiBPelY 16 48 31 63 uiLPelX
uiTPelY uiRPelX uiBPelY 32 32 63 63 |
可以明显看出这一个CU的划分是如下结果:
于是我就想个办法把每个小块的的两个坐标点存下来不就好了吗,于是在typedef.h我添加了如下代码:
1
2
3
4
5
6
7
8
|
//cheng
struct
PtPair { UInt
_pt1x; UInt
_pt1y; UInt
_pt2x; UInt
_pt2y; }; |
定义一个struct,保存一组坐标。在class TDecSlice里面添加一个私有成员:
1
|
std::vector<PtPair>
m_listLastCU |
用来保存一个slice里面所有的最终CU的划分结果
修改TDecCu::decodeCU的函数参数,把在class TDecSlice里面的m_listLastCU传递过去:
1
|
Void
TDecCu::decodeCU( TComDataCU* pcCU, UInt& ruiIsLast, std::vector<PtPair>& list ) |
调用改为:
1
|
m_pcCuDecoder->decodeCU
( pcCU, uiIsLast, m_listLastCU ); |
classTDecCu也添加一个私有成员用来保存class TDecSlice传递过来的vector的指针:
1
|
std::vector<PtPair>*
m_plistPt; |
Void TDecCu::decodeCU( TComDataCU* pcCU,UInt& ruiIsLast, std::vector<PtPair>& list )函数里面开头部分添加:
m_plistPt = &list;
decodeCU函数变成如下结果:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
Void
TDecCu::decodeCU( TComDataCU* pcCU, UInt& ruiIsLast, std::vector<PtPair>& list ) { //cheng m_plistPt
= &list; // if
( pcCU->getSlice()->getPPS()->getUseDQP() ) { setdQPFlag( true ); } #if
!REMOVE_BURST_IPCM pcCU->setNumSucIPCM(0); #endif //
start from the top level CU xDecodeCU(
pcCU, 0, 0, ruiIsLast); } |
在TDecCu::xDecodeCU( TComDataCU* pcCU, UInt uiAbsPartIdx, UIntuiDepth, UInt& ruiIsLast)这个函数里面的打印坐标信息的那一句话后面添加上:
1
2
|
pt._pt1x
= uiLPelX; pt._pt1y = uiTPelY; pt._pt2x = uiRPelX; pt._pt2y = uiBPelY; m_plistPt->push_back(pt); |
这样的话,解码完一帧,m_listLastCU里面就保存了CU最终划分的结果。
TDecSlice::decompressSlice开头需要把vector清空以便于在解码下一帧的时候vector中的信息要重新记录:
1
|
m_listLastCU.clear(); |
最后在TDecSlice::decompressSlice的LCU的for循环的后面添加绘制CU最终边框的代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
Pel*
pY = rpcPic->getPicYuvRec()->getLumaAddr(); UInt
stride = rpcPic->getPicYuvRec()->getStride(); for (UInt
index = 0; index < m_listLastCU.size(); index++) { for (UInt
y = m_listLastCU[index]._pt1y; y <= m_listLastCU[index]._pt2y; y++) { for (UInt
x = m_listLastCU[index]._pt1x; x <= m_listLastCU[index]._pt2x; x++) { if (y
== m_listLastCU[index]._pt1y /*||
y == m_listSCU[index]._pt2y*/ ) pY[y*stride
+ x] = 0; if (x
== m_listLastCU[index]._pt1x /*||
x == m_listSCU[index]._pt2x*/ ) pY[y*stride
+ x] = 0; } } } |
解码一帧图像使用YUV播放器查看效果:
所有的CU划分的结果很是明显,但是LCU的结果却看不出来!
怎么办呢,在TDecSlice::decompressSlice函数里面添加一个类似的vector记录一下LCU,在class TDecSlice里面再添加一个私有成员std::vector<PtPair>m_listLCU;,TDecSlice::decompressSlice函数开始的地方清空vector:m_listLastCU.clear();
在LCU的大大的for循环里面添加代码记录LCU划分的信息:
1
2
3
4
5
6
7
8
|
UInt
xpel = pcCU->getCUPelX(); UInt
ypel = pcCU->getCUPelY(); UInt
width = pcCU->getWidth(0); UInt
height = pcCU->getHeight(0); //std::cout<<"xpel
:"<<xpel<<"ypel :"<<ypel<<"width : "<<width<<"height :"<<height<<std::endl; PtPair
pt; pt._pt1x
= xpel; pt._pt1y = ypel; pt._pt2x = xpel + width; pt._pt2y = ypel + height; m_listLCU.push_back(pt); |
然后在刚才的画黑框的代码后面添加LCU的画白框的代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
for (UInt
index = 0; index < m_listLCU.size(); index++) { for (UInt
y = m_listLCU[index]._pt1y; y <= m_listLCU[index]._pt2y; y++) { for (UInt
x = m_listLCU[index]._pt1x; x <= m_listLCU[index]._pt2x; x++) { if (y
== m_listLCU[index]._pt1y /*||
y == m_listLCU[index]._pt2y*/ ) pY[y*stride
+ x] = 255; if (x
== m_listLCU[index]._pt1x /*||
x == m_listLCU[index]._pt2x*/ ) pY[y*stride
+ x] = 255; } } } |
运行解码器再试一下:
你可以很明显的看到LCU的大白色块!哈哈!
后面其实你可以发现:
和这个一模一样
注:以上画框的结果只能适用于I帧,对于P/B帧,由于解码我修改重建值也就修改了了P/B参考帧的像素值,导致P/B帧画框会出现一定混乱,不过对于P/B帧,vector里面存放的数据应该是对的!