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

简介: 解析完成条带头之后下一步的工作是解析条带数据slice_segment_data。slice_segment_data数据主要由一个个的Coding_Tree_Unit(CTU)组成。

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

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

目录
相关文章
|
XML 监控 网络协议
云深处绝影四足机器人协议学习解析
本文详细介绍并解析了云深处绝影X20四足机器人的通信协议,包括TCP服务端端口号、基于Service的请求/响应通信机制、通信帧结构、消息类型、常见的通信示例如获取状态和导航请求,以及运动控制的参数和命令。文中还提出了对协议中某些未明确说明或可能存在的问题的疑惑。
314 0
云深处绝影四足机器人协议学习解析
|
6月前
|
机器学习/深度学习 存储 算法
【LeetCode 热题100】347:前 K 个高频元素(详细解析)(Go语言版)
这篇文章详细解析了力扣热题 347——前 K 个高频元素的三种解法:哈希表+小顶堆、哈希表+快速排序和哈希表+桶排序。每种方法都附有清晰的思路讲解和 Go 语言代码实现。小顶堆方法时间复杂度为 O(n log k),适合处理大规模数据;快速排序方法时间复杂度为 O(n log n),适用于数据量较小的场景;桶排序方法在特定条件下能达到线性时间复杂度 O(n)。文章通过对比分析,帮助读者根据实际需求选择最优解法,并提供了完整的代码示例,是一篇非常实用的算法学习资料。
356 90
|
12月前
|
存储 Java
深入探讨了Java集合框架中的HashSet和TreeSet,解析了两者在元素存储上的无序与有序特性。
【10月更文挑战第16天】本文深入探讨了Java集合框架中的HashSet和TreeSet,解析了两者在元素存储上的无序与有序特性。HashSet基于哈希表实现,添加元素时根据哈希值分布,遍历时顺序不可预测;而TreeSet利用红黑树结构,按自然顺序或自定义顺序存储元素,确保遍历时有序输出。文章还提供了示例代码,帮助读者更好地理解这两种集合类型的使用场景和内部机制。
135 3
|
12月前
|
存储 算法 Java
解析HashSet的工作原理,揭示Set如何利用哈希算法和equals()方法确保元素唯一性,并通过示例代码展示了其“无重复”特性的具体应用
在Java中,Set接口以其独特的“无重复”特性脱颖而出。本文通过解析HashSet的工作原理,揭示Set如何利用哈希算法和equals()方法确保元素唯一性,并通过示例代码展示了其“无重复”特性的具体应用。
197 3
|
7月前
|
域名解析 存储 缓存
深入学习 DNS 域名解析
在平时工作中相信大家都离不开 DNS 解析,因为 DNS 解析是互联网访问的第一步,无论是使用笔记本浏览器访问网络还是打开手机APP的时候,访问网络资源的第一步必然要经过DNS解析流程。
|
7月前
|
设计模式 SQL Java
【再谈设计模式】解释器模式~语法的解析执行者
解释器模式定义了一种语言的语法表示,并定义一个解释器来解释该语言中的句子。它使用类来表示每个语法规则,并且通过递归调用这些类的方法来解释表达式。本质上,它将一个复杂的表达式分解为一系列简单的部分,然后按照特定的语法规则进行解析和执行。
143 8
|
8月前
|
监控 算法 安全
内网桌面监控软件深度解析:基于 Python 实现的 K-Means 算法研究
内网桌面监控软件通过实时监测员工操作,保障企业信息安全并提升效率。本文深入探讨K-Means聚类算法在该软件中的应用,解析其原理与实现。K-Means通过迭代更新簇中心,将数据划分为K个簇类,适用于行为分析、异常检测、资源优化及安全威胁识别等场景。文中提供了Python代码示例,展示如何实现K-Means算法,并模拟内网监控数据进行聚类分析。
166 10
|
8月前
|
数据采集 Web App开发 JavaScript
DOMParser解析TikTok页面中的图片元素
DOMParser解析TikTok页面中的图片元素
|
8月前
|
存储 算法 安全
基于 Go 语言的公司内网管理软件哈希表算法深度解析与研究
在数字化办公中,公司内网管理软件通过哈希表算法保障信息安全与高效管理。哈希表基于键值对存储和查找,如用户登录验证、设备信息管理和文件权限控制等场景,Go语言实现的哈希表能快速验证用户信息,提升管理效率,确保网络稳定运行。
130 0
|
10月前
|
域名解析 负载均衡 安全
DNS技术标准趋势和安全研究
本文探讨了互联网域名基础设施的结构性安全风险,由清华大学段教授团队多年研究总结。文章指出,DNS系统的安全性不仅受代码实现影响,更源于其设计、实现、运营及治理中的固有缺陷。主要风险包括协议设计缺陷(如明文传输)、生态演进隐患(如单点故障增加)和薄弱的信任关系(如威胁情报被操纵)。团队通过多项研究揭示了这些深层次问题,并呼吁构建更加可信的DNS基础设施,以保障全球互联网的安全稳定运行。

热门文章

最新文章

推荐镜像

更多
  • DNS