【HEVC学习与研究】27、CABAC解析语法元素SAO

解析完成条带头之后下一步的工作是解析条带数据slice_segment_data。slice_segment_data数据主要由一个个的Coding_Tree_Unit(CTU)组成。每一个CTU的结构如下所示:

[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. coding_tree_unit( )   
  2. {  
  3. xCtb = ( CtbAddrInRs % PicWidthInCtbsY ) << CtbLog2SizeY  
  4. yCtb = ( CtbAddrInRs / PicWidthInCtbsY ) << CtbLog2SizeY  
  5. if( slice_sao_luma_flag | | slice_sao_chroma_flag )  
  6. sao( xCtb >> CtbLog2SizeY, yCtb >> CtbLog2SizeY )  
  7. coding_quadtree( xCtb, yCtb, CtbLog2SizeY, 0 )  
  8. }  

可以看出,每一个CTU的coding_quadtree部分之前是语法结构sao。那么现在来研究一下如何从码流中解码出sao,这里着重讨论cabac而非sao的意义。

对sao的解析部分在TDecSlice::decompressSlice()中。在真正开始解码之前,TDecSlice::decompressSlice()调用了m_pcEntropyDecoderIf->resetEntropy(p)函数进行熵解码器的一些初始化操作,如为不同语法元素进行initBuffer。在最后,调用了m_pcTDecBinIf->start()函数。该函数读出了码流中的前两个字节组成一个UInt16的数字赋给m_uiValue。

[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. TDecBinCABAC::start()  
  2. {  
  3.   assert( m_pcTComBitstream->getNumBitsUntilByteAligned() == 0 );  
  4.   m_uiRange    = 510;  
  5.   m_bitsNeeded = -8;  
  6.   m_uiValue    = (m_pcTComBitstream->readByte() << 8);  
  7.   m_uiValue   |= m_pcTComBitstream->readByte();  
  8. }  

以下是TDecSlice::decompressSlice()中sao的解析部分:

[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. if ( pcSlice->getSPS()->getUseSAO() && (pcSlice->getSaoEnabledFlag()||pcSlice->getSaoEnabledFlagChroma()) )  
  2. {  
  3.     SAOParam *saoParam = rpcPic->getPicSym()->getSaoParam();  
  4.     saoParam->bSaoFlag[0] = pcSlice->getSaoEnabledFlag();  
  5.     if (iCUAddr == iStartCUAddr)  
  6.     {  
  7. <span style="white-space:pre">  </span> saoParam->bSaoFlag[1] = pcSlice->getSaoEnabledFlagChroma();  
  8.     }  
  9.     Int numCuInWidth     = saoParam->numCuInWidth;  
  10.     Int cuAddrInSlice = iCUAddr - rpcPic->getPicSym()->getCUOrderMap(pcSlice->getSliceCurStartCUAddr()/rpcPic->getNumPartInCU());  
  11.     Int cuAddrUpInSlice  = cuAddrInSlice - numCuInWidth;  
  12.     Int rx = iCUAddr % numCuInWidth;  
  13.     Int ry = iCUAddr / numCuInWidth;  
  14.     Int allowMergeLeft = 1;  
  15.     Int allowMergeUp   = 1;  
  16.     if (rx!=0)  
  17.     {  
  18.         if (rpcPic->getPicSym()->getTileIdxMap(iCUAddr-1) != rpcPic->getPicSym()->getTileIdxMap(iCUAddr))  
  19.         {  
  20.             allowMergeLeft = 0;  
  21.         }  
  22.     }  
  23.     if (ry!=0)  
  24.     {  
  25.         if (rpcPic->getPicSym()->getTileIdxMap(iCUAddr-numCuInWidth) != rpcPic->getPicSym()->getTileIdxMap(iCUAddr))  
  26.         {  
  27.             allowMergeUp = 0;  
  28.         }  
  29.     }  
  30.     pcSbacDecoder->parseSaoOneLcuInterleaving(rx, ry, saoParam,pcCU, cuAddrInSlice, cuAddrUpInSlice, allowMergeLeft, allowMergeUp);  
  31. }  
  32. else if ( pcSlice->getSPS()->getUseSAO() )  
  33. {  
  34.     Int addr = pcCU->getAddr();  
  35.     SAOParam *saoParam = rpcPic->getPicSym()->getSaoParam();  
  36.     for (Int cIdx=0; cIdx<3; cIdx++)  
  37.     {  
  38.         SaoLcuParam *saoLcuParam = &(saoParam->saoLcuParam[cIdx][addr]);  
  39.         if ( ((cIdx == 0) && !pcSlice->getSaoEnabledFlag()) || ((cIdx == 1 || cIdx == 2) && !pcSlice->getSaoEnabledFlagChroma()))  
  40.         {  
  41.             saoLcuParam->mergeUpFlag   = 0;  
  42.             saoLcuParam->mergeLeftFlag = 0;  
  43.             saoLcuParam->subTypeIdx    = 0;  
  44.             saoLcuParam->typeIdx       = -1;  
  45.             saoLcuParam->offset[0]     = 0;  
  46.             saoLcuParam->offset[1]     = 0;  
  47.             saoLcuParam->offset[2]     = 0;  
  48.             saoLcuParam->offset[3]     = 0;  
  49.         }  
  50.     }  
  51. }  

具体的解析过程在pcSbacDecoder->parseSaoOneLcuInterleaving()中实现。实现如下:

[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. Void TDecSbac::parseSaoOneLcuInterleaving(Int rx, Int ry, SAOParam* pSaoParam, TComDataCU* pcCU, Int iCUAddrInSlice, Int iCUAddrUpInSlice, Int allowMergeLeft, Int allowMergeUp)  
  2. {  
  3.     Int iAddr = pcCU->getAddr();  
  4.     UInt uiSymbol;  
  5.     for (Int iCompIdx=0; iCompIdx<3; iCompIdx++)  
  6.     {  
  7.         pSaoParam->saoLcuParam[iCompIdx][iAddr].mergeUpFlag    = 0;  
  8.         pSaoParam->saoLcuParam[iCompIdx][iAddr].mergeLeftFlag  = 0;  
  9.         pSaoParam->saoLcuParam[iCompIdx][iAddr].subTypeIdx     = 0;  
  10.         pSaoParam->saoLcuParam[iCompIdx][iAddr].typeIdx        = -1;  
  11.         pSaoParam->saoLcuParam[iCompIdx][iAddr].offset[0]     = 0;  
  12.         pSaoParam->saoLcuParam[iCompIdx][iAddr].offset[1]     = 0;  
  13.         pSaoParam->saoLcuParam[iCompIdx][iAddr].offset[2]     = 0;  
  14.         pSaoParam->saoLcuParam[iCompIdx][iAddr].offset[3]     = 0;  
  15.   
  16.     }  
  17.     if (pSaoParam->bSaoFlag[0] || pSaoParam->bSaoFlag[1] )  
  18.     {  
  19.         if (rx>0 && iCUAddrInSlice!=0 && allowMergeLeft)  
  20.         {  
  21.             parseSaoMerge(uiSymbol);   
  22.             pSaoParam->saoLcuParam[0][iAddr].mergeLeftFlag = (Bool)uiSymbol;    
  23.         }  
  24.         if (pSaoParam->saoLcuParam[0][iAddr].mergeLeftFlag==0)  
  25.         {  
  26.             if ((ry > 0) && (iCUAddrUpInSlice>=0) && allowMergeUp)  
  27.             {  
  28.                 parseSaoMerge(uiSymbol);  
  29.                 pSaoParam->saoLcuParam[0][iAddr].mergeUpFlag = (Bool)uiSymbol;  
  30.             }  
  31.         }  
  32.     }  
  33.   
  34.     for (Int iCompIdx=0; iCompIdx<3; iCompIdx++)  
  35.     {  
  36.         if ((iCompIdx == 0  && pSaoParam->bSaoFlag[0]) || (iCompIdx > 0  && pSaoParam->bSaoFlag[1]) )  
  37.         {  
  38.             if (rx>0 && iCUAddrInSlice!=0 && allowMergeLeft)  
  39.             {  
  40.                 pSaoParam->saoLcuParam[iCompIdx][iAddr].mergeLeftFlag = pSaoParam->saoLcuParam[0][iAddr].mergeLeftFlag;  
  41.             }  
  42.             else  
  43.             {  
  44.                 pSaoParam->saoLcuParam[iCompIdx][iAddr].mergeLeftFlag = 0;  
  45.             }  
  46.   
  47.             if (pSaoParam->saoLcuParam[iCompIdx][iAddr].mergeLeftFlag==0)  
  48.             {  
  49.                 if ((ry > 0) && (iCUAddrUpInSlice>=0) && allowMergeUp)  
  50.                 {  
  51.                     pSaoParam->saoLcuParam[iCompIdx][iAddr].mergeUpFlag = pSaoParam->saoLcuParam[0][iAddr].mergeUpFlag;  
  52.                 }  
  53.                 else  
  54.                 {  
  55.                     pSaoParam->saoLcuParam[iCompIdx][iAddr].mergeUpFlag = 0;  
  56.                 }  
  57.                 if (!pSaoParam->saoLcuParam[iCompIdx][iAddr].mergeUpFlag)  
  58.                 {  
  59.                     pSaoParam->saoLcuParam[2][iAddr].typeIdx = pSaoParam->saoLcuParam[1][iAddr].typeIdx;  
  60.                     parseSaoOffset(&(pSaoParam->saoLcuParam[iCompIdx][iAddr]), iCompIdx);  
  61.                 }  
  62.                 else  
  63.                 {  
  64.                     copySaoOneLcuParam(&pSaoParam->saoLcuParam[iCompIdx][iAddr], &pSaoParam->saoLcuParam[iCompIdx][iAddr-pSaoParam->numCuInWidth]);  
  65.                 }  
  66.             }  
  67.             else  
  68.             {  
  69.                 copySaoOneLcuParam(&pSaoParam->saoLcuParam[iCompIdx][iAddr],  &pSaoParam->saoLcuParam[iCompIdx][iAddr-1]);  
  70.             }  
  71.         }  
  72.         else  
  73.         {  
  74.             pSaoParam->saoLcuParam[iCompIdx][iAddr].typeIdx = -1;  
  75.             pSaoParam->saoLcuParam[iCompIdx][iAddr].subTypeIdx = 0;  
  76.         }  
  77.     }  
  78. }  

在调试过程中,对于第一个ctu来讲,rx和ry均为0,因此不会解析MergeUp和MergeLeft语法元素。实际上执行的部分是parseSaoOffset(&(pSaoParam->saoLcuParam[iCompIdx][iAddr]), iCompIdx);

[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. Void TDecSbac::parseSaoOffset(SaoLcuParam* psSaoLcuParam, UInt compIdx)  
  2. {  
  3.     UInt uiSymbol;  
  4.     static Int iTypeLength[MAX_NUM_SAO_TYPE] =  
  5.     {  
  6.         SAO_EO_LEN,  
  7.         SAO_EO_LEN,  
  8.         SAO_EO_LEN,  
  9.         SAO_EO_LEN,  
  10.         SAO_BO_LEN  
  11.     };   
  12.   
  13.     if (compIdx==2)  
  14.     {  
  15.         uiSymbol = (UInt)( psSaoLcuParam->typeIdx + 1);  
  16.     }  
  17.     else  
  18.     {  
  19.         parseSaoTypeIdx(uiSymbol);  
  20.     }  
  21.     psSaoLcuParam->typeIdx = (Int)uiSymbol - 1;  
  22.     if (uiSymbol)  
  23.     {  
  24.         psSaoLcuParam->length = iTypeLength[psSaoLcuParam->typeIdx];  
  25.   
  26.         Int bitDepth = compIdx ? g_bitDepthC : g_bitDepthY;  
  27.         Int offsetTh = 1 << min(bitDepth - 5,5);  
  28.   
  29.         if( psSaoLcuParam->typeIdx == SAO_BO )  
  30.         {  
  31.             for(Int i=0; i< psSaoLcuParam->length; i++)  
  32.             {  
  33.                 parseSaoMaxUvlc(uiSymbol, offsetTh -1 );  
  34.                 psSaoLcuParam->offset[i] = uiSymbol;  
  35.             }     
  36.             for(Int i=0; i< psSaoLcuParam->length; i++)  
  37.             {  
  38.                 if (psSaoLcuParam->offset[i] != 0)   
  39.                 {  
  40.                     m_pcTDecBinIf->decodeBinEP ( uiSymbol);  
  41.                     if (uiSymbol)  
  42.                     {  
  43.                         psSaoLcuParam->offset[i] = -psSaoLcuParam->offset[i] ;  
  44.                     }  
  45.                 }  
  46.             }  
  47.             parseSaoUflc(5, uiSymbol );  
  48.             psSaoLcuParam->subTypeIdx = uiSymbol;  
  49.         }  
  50.         else if( psSaoLcuParam->typeIdx < 4 )  
  51.         {  
  52.             parseSaoMaxUvlc(uiSymbol, offsetTh -1 ); psSaoLcuParam->offset[0] = uiSymbol;  
  53.             parseSaoMaxUvlc(uiSymbol, offsetTh -1 ); psSaoLcuParam->offset[1] = uiSymbol;  
  54.             parseSaoMaxUvlc(uiSymbol, offsetTh -1 ); psSaoLcuParam->offset[2] = -(Int)uiSymbol;  
  55.             parseSaoMaxUvlc(uiSymbol, offsetTh -1 ); psSaoLcuParam->offset[3] = -(Int)uiSymbol;  
  56.             if (compIdx != 2)  
  57.             {  
  58.                 parseSaoUflc(2, uiSymbol );  
  59.                 psSaoLcuParam->subTypeIdx = uiSymbol;  
  60.                 psSaoLcuParam->typeIdx += psSaoLcuParam->subTypeIdx;  
  61.             }  
  62.         }  
  63.     }  
  64.     else  
  65.     {  
  66.         psSaoLcuParam->length = 0;  
  67.     }  
  68. }  

在该函数中执行parseSaoTypeIdx(uiSymbol);

[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. Void TDecSbac::parseSaoTypeIdx (UInt&  ruiVal)  
  2. {  
  3.     UInt uiCode;  
  4.     m_pcTDecBinIf->decodeBin( uiCode, m_cSaoTypeIdxSCModel.get( 0, 0, 0 ) );  
  5.     if (uiCode == 0)   
  6.     {  
  7.         ruiVal = 0;  
  8.     }  
  9.     else  
  10.     {  
  11.         m_pcTDecBinIf->decodeBinEP( uiCode );   
  12.         if (uiCode == 0)  
  13.         {  
  14.             ruiVal = 5;  
  15.         }  
  16.         else  
  17.         {  
  18.             ruiVal = 1;  
  19.         }  
  20.     }  
  21. }  

该函数调用了上篇文章中解释过的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函数:

[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. Void  
  2. TDecBinCABAC::decodeBinEP( UInt& ruiBin )  
  3. {  
  4.   m_uiValue += m_uiValue;  
  5.     
  6.   if ( ++m_bitsNeeded >= 0 )  
  7.   {  
  8.     m_bitsNeeded = -8;  
  9.     m_uiValue += m_pcTComBitstream->readByte();  
  10.   }  
  11.     
  12.   ruiBin = 0;  
  13.   UInt scaledRange = m_uiRange << 7;  
  14.   if ( m_uiValue >= scaledRange )  
  15.   {  
  16.     ruiBin = 1;  
  17.     m_uiValue -= scaledRange;  
  18.   }  
  19. }  

该函数向实参uiCode输出值为0,根据条件,parseSaoTypeIdx的输出值为5。并且将执行4次parseSaoMaxUvlc,将解码的结果赋予psSaoLcuParam->offset[i]。

[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. Void TDecSbac::parseSaoMaxUvlc ( UInt& val, UInt maxSymbol )  
  2. {  
  3.   if (maxSymbol == 0)  
  4.   {  
  5.     val = 0;  
  6.     return;  
  7.   }  
  8.   
  9.   UInt code;  
  10.   Int  i;  
  11.   m_pcTDecBinIf->decodeBinEP( code );//①  
  12.   if ( code == 0 )  
  13.   {  
  14.     val = 0;  
  15.     return;  
  16.   }  
  17.   
  18.   i=1;  
  19.   while (1)  
  20.   {  
  21.     m_pcTDecBinIf->decodeBinEP( code );//②  
  22.     if ( code == 0 )  
  23.     {  
  24.       break;  
  25.     }  
  26.     i++;  
  27.     if (i == maxSymbol)   
  28.     {  
  29.       break;  
  30.     }  
  31.   }  
  32.   
  33.   val = i;  
  34. }  

在执行第一次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,该函数可以认为一次处理多位。实现如下:

[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. Void TDecBinCABAC::decodeBinsEP( UInt& ruiBin, Int numBins )  
  2. {  
  3.   UInt bins = 0;  
  4.     
  5.   while ( numBins > 8 )  
  6.   {  
  7.     m_uiValue = ( m_uiValue << 8 ) + ( m_pcTComBitstream->readByte() << ( 8 + m_bitsNeeded ) );  
  8.       
  9.     UInt scaledRange = m_uiRange << 15;  
  10.     for ( Int i = 0; i < 8; i++ )  
  11.     {  
  12.       bins += bins;  
  13.       scaledRange >>= 1;  
  14.       if ( m_uiValue >= scaledRange )  
  15.       {  
  16.         bins++;  
  17.         m_uiValue -= scaledRange;  
  18.       }  
  19.     }  
  20.     numBins -= 8;  
  21.   }  
  22.     
  23.   m_bitsNeeded += numBins;  
  24.   m_uiValue <<= numBins;  
  25.     
  26.   if ( m_bitsNeeded >= 0 )  
  27.   {  
  28.     m_uiValue += m_pcTComBitstream->readByte() << m_bitsNeeded;  
  29.     m_bitsNeeded -= 8;  
  30.   }  
  31.     
  32.   UInt scaledRange = m_uiRange << ( numBins + 7 );  
  33.   for ( Int i = 0; i < numBins; i++ )  
  34.   {  
  35.     bins += bins;  
  36.     scaledRange >>= 1;  
  37.     if ( m_uiValue >= scaledRange )  
  38.     {  
  39.       bins++;  
  40.       m_uiValue -= scaledRange;  
  41.     }  
  42.   }  
  43.     
  44.   ruiBin = bins;  
  45. }  

在该函数中读取了码流中的第四个字节(107),在for循环中比较m_uiValue和scaledRange的值并决定bins加1或倍增,最终返回值为13。该值赋予psSaoLcuParam->subTypeIdx。

自此,parseSaoOffset的任务便已完成。而类似的工作将循环三次以完成pcSbacDecoder->parseSaoOneLcuInterleaving的功能。


PS:跟踪了这么久的代码,但是现在的问题依然是知其然不知其所以然……看来接下来需要在标准文档和其他一些资料上面下些功夫了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值