开发者社区> jerry.yin> 正文
阿里云
为了无法计算的价值
打开APP
阿里云APP内打开

【HEVC学习与研究】26、HEVC的算数编码实现

简介: 关于HEVC的前25篇博文全文发表在新浪博客,地址为:http://blog.sina.com.cn/s/articlelist_1376260467_0_1.html。
+关注继续查看

关于HEVC的前25篇博文全文发表在新浪博客,地址为:http://blog.sina.com.cn/s/articlelist_1376260467_0_1.html。从第26篇开始博客全文发在CSDN,新浪同步更新摘要和链接地址。


在第13篇博文中贴出了我们在调试代码时所采用的二进制码流的开头一部分数据,并根据这些数据进行了NAL Header解析、参数集合解析和条带头解析等信息的分析。今后的博文中如无特殊情况依然会采用这些数据作为学习材料。

通过对这段码流进行分析,我们可以看出,在对于一个slice进行解码的过程中,对slice header和slice data采取了不同的熵解码方法:slice header的语法元素采用了变长码(主要是指数哥伦布编码),而slice data的语法元素采用的是算术编码CABAC。由于对CABAC的基本概念不是非常熟悉,之前一段时间一直在回顾H.264中CABAC相关的知识,现在来看HEVC重的算术编码相对于H.264做了什么样的改变。

1、数据准备
同JM86中在每一个slice中为算术编码进行数据准备不同,HM 10.0 decoder在程序的一开始就为CABAC解码的过程在做准备。在main函数中的最开始会定义一个解码器应用的实例:
TAppDecTop cTAppDecTop; 
在TAppDecTop这个类的声明中可以看出,该类包含一个TDecTop类型的成员m_cTDecTop,这个对象将实现对编码码流的解码。在TDecTop这一层还将实现针对不同类型的NAL,如VPS, SPS, PPS, SEI和slice数据等类型的NAL进行分别处理等操作,在TDecTop::decode函数实现。

在TDecTop的声明中找到了一个TDecSbac类型的成员m_cSbacDecoder,根据TDecSbac.cpp开头的简介提示可知,这个类实现的就是上下文自适应的熵解码器类。并且在类的声明中可以得知,该类对码流中的多种不同语法元素定义了相应的解析方法,如parseMVPIdx(), parseSaoMaxUvlc()等。除了这些解析方法之外,TDecSbac中还定义了一个数组存放上下文模型:ContextModel m_contextModels[MAX_NUM_CTX_MOD];一共512个元素。这些元素的类型ContextModel是专门用来定义上下文模型的类,其成员包括了预定义的LPS和MPS的状态索引m_aucNextStateMPS[ 128 ]、m_aucNextStateLPS[ 128 ]以及表示当前状态的变量m_ucState。在该类的实现函数中,上述两个数组被分别赋予相应的初始值。

2、SbacDecoder的初始化
SbacDecoder类的构造函数内容几乎就是空的,但是该构造函数同时调用了SbacDecoder类成员的构造函数对其进行了初始化,其中数量最多的就是多个ContextModel3DBuffer类型的成员变量分别用于不同类型的语法元素的上下文模型。从命名来看就可以得知这是一个保存上下文模型的三维缓存,其中的成员变量也包含一个指向ContextModel类的指针m_contextModel和三个维度变量m_sizeX、m_sizeXY和m_sizeXYZ。在ContextModel3DBuffer的构造函数里将指定的上下文模型的指针指向m_contextModels[]的某个成员。这些ContextModel3DBuffer类型的语法元素上下文模型都将在TDecSbac的构造函数调用时进行初始化。这一步将完成上下文模型的初始化操作。

3、其他的初始化流程
在主函数decmain.cpp中,调用cTAppDecTop.decode()函数;该函数调用xInitDecLib();该函数会调用m_cTDecTop.init()函数实现TDecTop的初始化;TDecTop::init()函数分别调用了initROM()和m_cGopDecoder、m_cSliceDecoder和m_cEntropyDecoder的init函数进行初始化。在TGopGop的声明中可以看到该类声明了TDecEntropy、TDecSbac等类型的指针。m_cGopDecoder.init()函数的作用就是将TGopGop的各种指针指向了TDecTop的各个实例成员。

4、解码过程
对条带头的解析在TDecEntropy::xDecodeSlice()中实现,在其中调用parseSliceHeader()函数。
码流的实际解析过程在TDecGop::decompressSlice()中实现。decompressSlice函数中,m_pcBinCABAC指向的就是TDecTop中定义的TDecSbac的实例m_cSbacDecoder;另外在对m_pcEntropyDecoder、m_pcSbacDecoder、m_pcSbacDecoders、m_pcBinCABACs完成设置之后,TDecGop::decompressSlice()调用了TDecSlice::decompressSlice()函数进行解码slice_segment_data部分。
slice_segment_data由多个coding_tree_unit( )组成,在色度或亮度sao flag设为true时,coding_tree_unit( )的第一个语法元素为sao(),解析过程由pcSbacDecoder->parseSaoOneLcuInterleaving()实现。该函数第一个实际调用的函数是TDecSbac::parseSaoTypeIdx(),其中进行cabac解码的函数为TDecBinCABAC::decodeBin()函数。
TDecBinCABAC::decodeBin()函数的实现可以参考标准文档的9.3.4.3.2部分。
TDecBinCABAC::decodeBin( UInt& ruiBin, ContextModel &rcCtxModel )
{
    //实现导出ivlLpsRange值的过程:通过选定的上下文模型获取当前状态,通过计算ivlCurrRange的值计算得到qRangeIdx(qRangeIdx =( ivlCurrRange >> 6 ) & 3);
  UInt uiLPS = TComCABACTables::sm_aucLPSTable[ rcCtxModel.getState() ][ ( m_uiRange >> 6 ) - 4 ];
  m_uiRange -= uiLPS; //m_uiRange(标准文档中描述为ivlCurrRange)初始化为510,并变更为ivlCurrRange − ivlLpsRange
  UInt scaledRange = m_uiRange << 7;//计算更新的ivlOffset,这个值是表示算数解码器引擎状态的变量之一,在解码引擎初始化的时候进行赋值
 
  if( m_uiValue < scaledRange )//根据ivlOffset与ivlCurrRange的对比判断当前属于MPS还是LPS
  {
    // MPS path
    ruiBin = rcCtxModel.getMps();//直接将valMps返回给输出值binVal
    rcCtxModel.updateMPS();//更新MPS的上下文索引,通过查询表m_aucNextStateMPS[]获得
   
    if ( scaledRange >= ( 256 << 7 ) )
    {
      return;
    }
   
    m_uiRange = scaledRange >> 6;
    m_uiValue += m_uiValue;
   
    if ( ++m_bitsNeeded == 0 )
    {
      m_bitsNeeded = -8;
      m_uiValue += m_pcTComBitstream->readByte();
    }
  }
  else
  {
    // LPS path
    Int numBits = TComCABACTables::sm_aucRenormTable[ uiLPS >> 3 ];
    m_uiValue = ( m_uiValue - scaledRange ) << numBits;
    m_uiRange = uiLPS << numBits;
    ruiBin = 1 - rcCtxModel.getMps();//输出值valMps设为1-valMps
    rcCtxModel.updateLPS();//更新LPS的上下文模型索引,通过查询表m_aucNextStateLPS[]获得。
   
    m_bitsNeeded += numBits;
   
    if ( m_bitsNeeded >= 0 )
    {
      m_uiValue += m_pcTComBitstream->readByte() << m_bitsNeeded;
      m_bitsNeeded -= 8;
    }
  }
}



版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

相关文章
【算法学习】剑指 Offer II 042. 最近请求次数(java / c / c++ / python / go / rust)
写一个 RecentCounter 类来计算特定时间范围内最近的请求。 请实现 RecentCounter 类: RecentCounter() 初始化计数器,请求数为 0 。 int ping(int t) 在时间 t 添加一个新请求,其中 t 表示以毫秒为单位的某个时间,并返回过去 3000 毫秒内发生的所有请求数(包括新请求)。确切地说,返回在 [t-3000, t] 内发生的请求数。 保证 每次对 ping 的调用都使用比之前更大的 t 值。
22 0
关于linux系统如何实现fork的研究(一)【转】
转自:http://www.aichengxu.com/linux/4157180.htm 引言 fork函数是用于在linux系统中创建进程所使用,而最近看了看一个fork()调用是怎么从应用到glibc,最后到内核中实现的,这片文章就聊聊最近对这方面研究的收获吧。
964 0
Sed&awk笔记之sed篇:实战
相信大家肯定用过grep这个命令,它可以找出匹配某个正则表达式的行,例如查看包含"the word"的行: $ grep "the word" filename 但是grep是针对单行作匹配的,所以如果一个短句跨越了两行就无法匹配。这就给我们出了一个题目,如何用sed模仿grep的行为,并且
2016 0
IOS7引入API之NSURLSession入门介绍
ios7引入了nsurlsession,通过它可以支持后台相关的网络操作的新特性: 1.后台上传下载; 2.不需要通过nsoperation,直接用nsurlsession的api就可以做到网络操作的暂停和恢复。
918 0
学习:java代码检测
转自:http://zh.wikipedia.org/wiki/%E4%BB%A3%E7%A0%81%E5%BC%82%E5%91%B3 对于Java开发语言,有些工具,比如Checkstyle、PMD和 FindBugs可以自动检测一些代码异味。
760 0
win7下让程序默认以管理员身份运行
在win7中用自己写的程序读取MBR时,突然提示无法对磁盘进行操作,而在xp下并没有这个问题;最后点右键以管理员身份运行才可以正常运行。于是想办法让程序在双击启动时默认以管理员身份运行。具体方法: 1.
726 0
LINUX ANSI C库函数FOPEN()的文件流指针结构体访问代码
1 [root@localhost ccode]# cat ptr_struct_file.c 2 #include 3 #include 4 #include 5 6 #define ptr(CONTENT, MSG) printf(CONTENT":\t%p\...
854 0
+关注
jerry.yin
毕业于上海大学通信与信息工程学院,从事流媒体和视频编解码的研究与开发工作; 研究领域包括视频编解码标准、视频处理和流媒体技术、移动互联网技术等。
182
文章
0
问答
文章排行榜
最热
最新
相关电子书
更多
低代码开发师(初级)实战教程
立即下载
阿里巴巴DevOps 最佳实践手册
立即下载
冬季实战营第三期:MySQL数据库进阶实战
立即下载