【HEVC学习与研究】27、CABAC解析语法元素SAO
2014-03-11 21:24
489 查看
解析完成条带头之后下一步的工作是解析条带数据slice_segment_data。slice_segment_data数据主要由一个个的Coding_Tree_Unit(CTU)组成。每一个CTU的结构如下所示:
可以看出,每一个CTU的coding_quadtree部分之前是语法结构sao。那么现在来研究一下如何从码流中解码出sao,这里着重讨论cabac而非sao的意义。
对sao的解析部分在TDecSlice::decompressSlice()中。在真正开始解码之前,TDecSlice::decompressSlice()调用了m_pcEntropyDecoderIf->resetEntropy(p)函数进行熵解码器的一些初始化操作,如为不同语法元素进行initBuffer。在最后,调用了m_pcTDecBinIf->start()函数。该函数读出了码流中的前两个字节组成一个UInt16的数字赋给m_uiValue。
以下是TDecSlice::decompressSlice()中sao的解析部分:
具体的解析过程在pcSbacDecoder->parseSaoOneLcuInterleaving()中实现。实现如下:
在调试过程中,对于第一个ctu来讲,rx和ry均为0,因此不会解析MergeUp和MergeLeft语法元素。实际上执行的部分是parseSaoOffset(&(pSaoParam->saoLcuParam[iCompIdx][iAddr]), iCompIdx);
在该函数中执行parseSaoTypeIdx(uiSymbol);
该函数调用了上篇文章中解释过的decodeBin函数实现解析。回顾上篇文章中对该函数的解释,可以看出该函数的输出值实际就是valMps或者1-valMps,组成一串二进制码流,也就是算数编码前的二值化过的语法元素。通过这个函数输出了一个二进制字符,但是却并没有从原始码流中读取数据,这种现象开始时相当让我感到费解,不过想了一下便明白了这是很正常的,因为这正是算数编码过程的逆过程。在编码的时候,编码器“吸收”了一个、两个甚至更多个字符,多次进行MPS/LPS判断和区间划分,直到满足一定条件后才会输出一个0或1。那么在解码的时候,也会可能会出现输出了多个字符才“消耗”了1bit码流的情况。从上述函数的实现中可以得知,parseSaoTypeIdx函数返回给uiSymbol的值为0,那么parseSaoOffset(SaoLcuParam*
psSaoLcuParam, UInt compIdx)函数便不会做后续操作,将psSaoLcuParam->length置0后直接返回。
从parseSaoOneLcuInterleaving函数中可以看出,parseSaoOffset将会循环调用3次。在第二次调用中,我们的demo程序显示,parseSaoTypeIdx中uiCode返回值为1,因此,下面继续调用了decodeBinEP函数:
该函数向实参uiCode输出值为0,根据条件,parseSaoTypeIdx的输出值为5。并且将执行4次parseSaoMaxUvlc,将解码的结果赋予psSaoLcuParam->offset[i]。
在执行第一次parseSaoMaxUvlc中,只执行了函数①,code等于0因此返回val=0;执行第二次parseSaoMaxUvlc,①返回code=1,因此将会执行下面的while循环,共执行三次,返回val=3;值得注意的是,在第三次循环中,decodeBinEP函数读取了码流中的第三个字符加到了m_uiValue上;执行第三次parseSaoMaxUvlc,①返回code=1,下面的while循环只会执行一次,返回val=1;第四次执行parseSaoMaxUvlc,①返回code=1,while循环执行一次,返回val=1。
接下来,对第2~4个offset值,将各调用一次decodeBinEP,为1则取其负值。
调用parseSaoUflc(5, uiSymbol ),该函数调用了decodeBinsEP,该函数可以认为一次处理多位。实现如下:
在该函数中读取了码流中的第四个字节(107),在for循环中比较m_uiValue和scaledRange的值并决定bins加1或倍增,最终返回值为13。该值赋予psSaoLcuParam->subTypeIdx。
自此,parseSaoOffset的任务便已完成。而类似的工作将循环三次以完成pcSbacDecoder->parseSaoOneLcuInterleaving的功能。
PS:跟踪了这么久的代码,但是现在的问题依然是知其然不知其所以然……看来接下来需要在标准文档和其他一些资料上面下些功夫了。
coding_tree_unit( ) { xCtb = ( CtbAddrInRs % PicWidthInCtbsY ) << CtbLog2SizeY yCtb = ( CtbAddrInRs / PicWidthInCtbsY ) << CtbLog2SizeY if( slice_sao_luma_flag | | slice_sao_chroma_flag ) sao( xCtb >> CtbLog2SizeY, yCtb >> CtbLog2SizeY ) coding_quadtree( xCtb, yCtb, CtbLog2SizeY, 0 ) }
可以看出,每一个CTU的coding_quadtree部分之前是语法结构sao。那么现在来研究一下如何从码流中解码出sao,这里着重讨论cabac而非sao的意义。
对sao的解析部分在TDecSlice::decompressSlice()中。在真正开始解码之前,TDecSlice::decompressSlice()调用了m_pcEntropyDecoderIf->resetEntropy(p)函数进行熵解码器的一些初始化操作,如为不同语法元素进行initBuffer。在最后,调用了m_pcTDecBinIf->start()函数。该函数读出了码流中的前两个字节组成一个UInt16的数字赋给m_uiValue。
TDecBinCABAC::start() { assert( m_pcTComBitstream->getNumBitsUntilByteAligned() == 0 ); m_uiRange = 510; m_bitsNeeded = -8; m_uiValue = (m_pcTComBitstream->readByte() << 8); m_uiValue |= m_pcTComBitstream->readByte(); }
以下是TDecSlice::decompressSlice()中sao的解析部分:
if ( pcSlice->getSPS()->getUseSAO() && (pcSlice->getSaoEnabledFlag()||pcSlice->getSaoEnabledFlagChroma()) ) { SAOParam *saoParam = rpcPic->getPicSym()->getSaoParam(); saoParam->bSaoFlag[0] = pcSlice->getSaoEnabledFlag(); if (iCUAddr == iStartCUAddr) { saoParam->bSaoFlag[1] = pcSlice->getSaoEnabledFlagChroma(); } Int numCuInWidth = saoParam->numCuInWidth; Int cuAddrInSlice = iCUAddr - rpcPic->getPicSym()->getCUOrderMap(pcSlice->getSliceCurStartCUAddr()/rpcPic->getNumPartInCU()); Int cuAddrUpInSlice = cuAddrInSlice - numCuInWidth; Int rx = iCUAddr % numCuInWidth; Int ry = iCUAddr / numCuInWidth; Int allowMergeLeft = 1; Int allowMergeUp = 1; if (rx!=0) { if (rpcPic->getPicSym()->getTileIdxMap(iCUAddr-1) != rpcPic->getPicSym()->getTileIdxMap(iCUAddr)) { allowMergeLeft = 0; } } if (ry!=0) { if (rpcPic->getPicSym()->getTileIdxMap(iCUAddr-numCuInWidth) != rpcPic->getPicSym()->getTileIdxMap(iCUAddr)) { allowMergeUp = 0; } } pcSbacDecoder->parseSaoOneLcuInterleaving(rx, ry, saoParam,pcCU, cuAddrInSlice, cuAddrUpInSlice, allowMergeLeft, allowMergeUp); } else if ( pcSlice->getSPS()->getUseSAO() ) { Int addr = pcCU->getAddr(); SAOParam *saoParam = rpcPic->getPicSym()->getSaoParam(); for (Int cIdx=0; cIdx<3; cIdx++) { SaoLcuParam *saoLcuParam = &(saoParam->saoLcuParam[cIdx][addr]); if ( ((cIdx == 0) && !pcSlice->getSaoEnabledFlag()) || ((cIdx == 1 || cIdx == 2) && !pcSlice->getSaoEnabledFlagChroma())) { saoLcuParam->mergeUpFlag = 0; saoLcuParam->mergeLeftFlag = 0; saoLcuParam->subTypeIdx = 0; saoLcuParam->typeIdx = -1; saoLcuParam->offset[0] = 0; saoLcuParam->offset[1] = 0; saoLcuParam->offset[2] = 0; saoLcuParam->offset[3] = 0; } } }
具体的解析过程在pcSbacDecoder->parseSaoOneLcuInterleaving()中实现。实现如下:
Void TDecSbac::parseSaoOneLcuInterleaving(Int rx, Int ry, SAOParam* pSaoParam, TComDataCU* pcCU, Int iCUAddrInSlice, Int iCUAddrUpInSlice, Int allowMergeLeft, Int allowMergeUp) { Int iAddr = pcCU->getAddr(); UInt uiSymbol; for (Int iCompIdx=0; iCompIdx<3; iCompIdx++) { pSaoParam->saoLcuParam[iCompIdx][iAddr].mergeUpFlag = 0; pSaoParam->saoLcuParam[iCompIdx][iAddr].mergeLeftFlag = 0; pSaoParam->saoLcuParam[iCompIdx][iAddr].subTypeIdx = 0; pSaoParam->saoLcuParam[iCompIdx][iAddr].typeIdx = -1; pSaoParam->saoLcuParam[iCompIdx][iAddr].offset[0] = 0; pSaoParam->saoLcuParam[iCompIdx][iAddr].offset[1] = 0; pSaoParam->saoLcuParam[iCompIdx][iAddr].offset[2] = 0; pSaoParam->saoLcuParam[iCompIdx][iAddr].offset[3] = 0; } if (pSaoParam->bSaoFlag[0] || pSaoParam->bSaoFlag[1] ) { if (rx>0 && iCUAddrInSlice!=0 && allowMergeLeft) { parseSaoMerge(uiSymbol); pSaoParam->saoLcuParam[0][iAddr].mergeLeftFlag = (Bool)uiSymbol; } if (pSaoParam->saoLcuParam[0][iAddr].mergeLeftFlag==0) { if ((ry > 0) && (iCUAddrUpInSlice>=0) && allowMergeUp) { parseSaoMerge(uiSymbol); pSaoParam->saoLcuParam[0][iAddr].mergeUpFlag = (Bool)uiSymbol; } } } for (Int iCompIdx=0; iCompIdx<3; iCompIdx++) { if ((iCompIdx == 0 && pSaoParam->bSaoFlag[0]) || (iCompIdx > 0 && pSaoParam->bSaoFlag[1]) ) { if (rx>0 && iCUAddrInSlice!=0 && allowMergeLeft) { pSaoParam->saoLcuParam[iCompIdx][iAddr].mergeLeftFlag = pSaoParam->saoLcuParam[0][iAddr].mergeLeftFlag; } else { pSaoParam->saoLcuParam[iCompIdx][iAddr].mergeLeftFlag = 0; } if (pSaoParam->saoLcuParam[iCompIdx][iAddr].mergeLeftFlag==0) { if ((ry > 0) && (iCUAddrUpInSlice>=0) && allowMergeUp) { pSaoParam->saoLcuParam[iCompIdx][iAddr].mergeUpFlag = pSaoParam->saoLcuParam[0][iAddr].mergeUpFlag; } else { pSaoParam->saoLcuParam[iCompIdx][iAddr].mergeUpFlag = 0; } if (!pSaoParam->saoLcuParam[iCompIdx][iAddr].mergeUpFlag) { pSaoParam->saoLcuParam[2][iAddr].typeIdx = pSaoParam->saoLcuParam[1][iAddr].typeIdx; parseSaoOffset(&(pSaoParam->saoLcuParam[iCompIdx][iAddr]), iCompIdx); } else { copySaoOneLcuParam(&pSaoParam->saoLcuParam[iCompIdx][iAddr], &pSaoParam->saoLcuParam[iCompIdx][iAddr-pSaoParam->numCuInWidth]); } } else { copySaoOneLcuParam(&pSaoParam->saoLcuParam[iCompIdx][iAddr], &pSaoParam->saoLcuParam[iCompIdx][iAddr-1]); } } else { pSaoParam->saoLcuParam[iCompIdx][iAddr].typeIdx = -1; pSaoParam->saoLcuParam[iCompIdx][iAddr].subTypeIdx = 0; } } }
在调试过程中,对于第一个ctu来讲,rx和ry均为0,因此不会解析MergeUp和MergeLeft语法元素。实际上执行的部分是parseSaoOffset(&(pSaoParam->saoLcuParam[iCompIdx][iAddr]), iCompIdx);
Void TDecSbac::parseSaoOffset(SaoLcuParam* psSaoLcuParam, UInt compIdx) { UInt uiSymbol; static Int iTypeLength[MAX_NUM_SAO_TYPE] = { SAO_EO_LEN, SAO_EO_LEN, SAO_EO_LEN, SAO_EO_LEN, SAO_BO_LEN }; if (compIdx==2) { uiSymbol = (UInt)( psSaoLcuParam->typeIdx + 1); } else { parseSaoTypeIdx(uiSymbol); } psSaoLcuParam->typeIdx = (Int)uiSymbol - 1; if (uiSymbol) { psSaoLcuParam->length = iTypeLength[psSaoLcuParam->typeIdx]; Int bitDepth = compIdx ? g_bitDepthC : g_bitDepthY; Int offsetTh = 1 << min(bitDepth - 5,5); if( psSaoLcuParam->typeIdx == SAO_BO ) { for(Int i=0; i< psSaoLcuParam->length; i++) { parseSaoMaxUvlc(uiSymbol, offsetTh -1 ); psSaoLcuParam->offset[i] = uiSymbol; } for(Int i=0; i< psSaoLcuParam->length; i++) { if (psSaoLcuParam->offset[i] != 0) { m_pcTDecBinIf->decodeBinEP ( uiSymbol); if (uiSymbol) { psSaoLcuParam->offset[i] = -psSaoLcuParam->offset[i] ; } } } parseSaoUflc(5, uiSymbol ); psSaoLcuParam->subTypeIdx = uiSymbol; } else if( psSaoLcuParam->typeIdx < 4 ) { parseSaoMaxUvlc(uiSymbol, offsetTh -1 ); psSaoLcuParam->offset[0] = uiSymbol; parseSaoMaxUvlc(uiSymbol, offsetTh -1 ); psSaoLcuParam->offset[1] = uiSymbol; parseSaoMaxUvlc(uiSymbol, offsetTh -1 ); psSaoLcuParam->offset[2] = -(Int)uiSymbol; parseSaoMaxUvlc(uiSymbol, offsetTh -1 ); psSaoLcuParam->offset[3] = -(Int)uiSymbol; if (compIdx != 2) { parseSaoUflc(2, uiSymbol ); psSaoLcuParam->subTypeIdx = uiSymbol; psSaoLcuParam->typeIdx += psSaoLcuParam->subTypeIdx; } } } else { psSaoLcuParam->length = 0; } }
在该函数中执行parseSaoTypeIdx(uiSymbol);
Void TDecSbac::parseSaoTypeIdx (UInt& ruiVal) { UInt uiCode; m_pcTDecBinIf->decodeBin( uiCode, m_cSaoTypeIdxSCModel.get( 0, 0, 0 ) ); if (uiCode == 0) { ruiVal = 0; } else { m_pcTDecBinIf->decodeBinEP( uiCode ); if (uiCode == 0) { ruiVal = 5; } else { ruiVal = 1; } } }
该函数调用了上篇文章中解释过的decodeBin函数实现解析。回顾上篇文章中对该函数的解释,可以看出该函数的输出值实际就是valMps或者1-valMps,组成一串二进制码流,也就是算数编码前的二值化过的语法元素。通过这个函数输出了一个二进制字符,但是却并没有从原始码流中读取数据,这种现象开始时相当让我感到费解,不过想了一下便明白了这是很正常的,因为这正是算数编码过程的逆过程。在编码的时候,编码器“吸收”了一个、两个甚至更多个字符,多次进行MPS/LPS判断和区间划分,直到满足一定条件后才会输出一个0或1。那么在解码的时候,也会可能会出现输出了多个字符才“消耗”了1bit码流的情况。从上述函数的实现中可以得知,parseSaoTypeIdx函数返回给uiSymbol的值为0,那么parseSaoOffset(SaoLcuParam*
psSaoLcuParam, UInt compIdx)函数便不会做后续操作,将psSaoLcuParam->length置0后直接返回。
从parseSaoOneLcuInterleaving函数中可以看出,parseSaoOffset将会循环调用3次。在第二次调用中,我们的demo程序显示,parseSaoTypeIdx中uiCode返回值为1,因此,下面继续调用了decodeBinEP函数:
Void TDecBinCABAC::decodeBinEP( UInt& ruiBin ) { m_uiValue += m_uiValue; if ( ++m_bitsNeeded >= 0 ) { m_bitsNeeded = -8; m_uiValue += m_pcTComBitstream->readByte(); } ruiBin = 0; UInt scaledRange = m_uiRange << 7; if ( m_uiValue >= scaledRange ) { ruiBin = 1; m_uiValue -= scaledRange; } }
该函数向实参uiCode输出值为0,根据条件,parseSaoTypeIdx的输出值为5。并且将执行4次parseSaoMaxUvlc,将解码的结果赋予psSaoLcuParam->offset[i]。
Void TDecSbac::parseSaoMaxUvlc ( UInt& val, UInt maxSymbol ) { if (maxSymbol == 0) { val = 0; return; } UInt code; Int i; m_pcTDecBinIf->decodeBinEP( code );//① if ( code == 0 ) { val = 0; return; } i=1; while (1) { m_pcTDecBinIf->decodeBinEP( code );//② if ( code == 0 ) { break; } i++; if (i == maxSymbol) { break; } } val = i; }
在执行第一次parseSaoMaxUvlc中,只执行了函数①,code等于0因此返回val=0;执行第二次parseSaoMaxUvlc,①返回code=1,因此将会执行下面的while循环,共执行三次,返回val=3;值得注意的是,在第三次循环中,decodeBinEP函数读取了码流中的第三个字符加到了m_uiValue上;执行第三次parseSaoMaxUvlc,①返回code=1,下面的while循环只会执行一次,返回val=1;第四次执行parseSaoMaxUvlc,①返回code=1,while循环执行一次,返回val=1。
接下来,对第2~4个offset值,将各调用一次decodeBinEP,为1则取其负值。
调用parseSaoUflc(5, uiSymbol ),该函数调用了decodeBinsEP,该函数可以认为一次处理多位。实现如下:
Void TDecBinCABAC::decodeBinsEP( UInt& ruiBin, Int numBins ) { UInt bins = 0; while ( numBins > 8 ) { m_uiValue = ( m_uiValue << 8 ) + ( m_pcTComBitstream->readByte() << ( 8 + m_bitsNeeded ) ); UInt scaledRange = m_uiRange << 15; for ( Int i = 0; i < 8; i++ ) { bins += bins; scaledRange >>= 1; if ( m_uiValue >= scaledRange ) { bins++; m_uiValue -= scaledRange; } } numBins -= 8; } m_bitsNeeded += numBins; m_uiValue <<= numBins; if ( m_bitsNeeded >= 0 ) { m_uiValue += m_pcTComBitstream->readByte() << m_bitsNeeded; m_bitsNeeded -= 8; } UInt scaledRange = m_uiRange << ( numBins + 7 ); for ( Int i = 0; i < numBins; i++ ) { bins += bins; scaledRange >>= 1; if ( m_uiValue >= scaledRange ) { bins++; m_uiValue -= scaledRange; } } ruiBin = bins; }
在该函数中读取了码流中的第四个字节(107),在for循环中比较m_uiValue和scaledRange的值并决定bins加1或倍增,最终返回值为13。该值赋予psSaoLcuParam->subTypeIdx。
自此,parseSaoOffset的任务便已完成。而类似的工作将循环三次以完成pcSbacDecoder->parseSaoOneLcuInterleaving的功能。
PS:跟踪了这么久的代码,但是现在的问题依然是知其然不知其所以然……看来接下来需要在标准文档和其他一些资料上面下些功夫了。
相关文章推荐
- 【HEVC学习与研究】27、CABAC解析语法元素SAO
- 【HEVC学习与研究】28、第一帧第一个宏块的SAO部分完整解析结果
- 【HEVC学习与研究】28、第一帧第一个宏块的SAO部分完整解析结果
- 【HEVC学习与研究】16、HEVC中的PPS解析
- 【HEVC学习与研究】18.HEVC的条带头解析
- 【HEVC学习与研究】46、HEVC参考代码中SAO的实现
- 【HEVC学习与研究】45、HEVC的自适应采样点补偿SAO
- HEVC学习(十四) —— SAO函数解析之二
- HEVC学习与研究】11.HEVC参考解码器的设置及参数解析过程
- 【HEVC学习与研究】44、HEVC量化系数的解析——反量化过程
- HEVC学习(十五) —— SAO函数解析之三
- 【HEVC学习与研究】12.HEVC的高层语法结构
- HEVC学习(十六) —— SAO函数解析之四
- 【HEVC学习与研究】11.HEVC参考解码器的设置及参数解析过程
- 【HEVC学习与研究】14.HEVC解码中VPS参数集解析
- 【HEVC学习与研究】15、HEVC解码中的SPS解析
- IOS学习笔记27—使用GDataXML解析XML文档
- 【HEVC学习与研究】43、HEVC变换编码的实现
- 新版火狐浏览器如何使用Xpath语法解析网页元素。
- Android(java)学习笔记258:JNI之hello.c(c代码功能实现)指针语法解析