本文是本系列的第三篇博客,内容是分析从Slice到CTU的处理代码。
该系列相关博客为:
VVC/H.266代码阅读(VTM8.0)(一. NALU提取)
VVC/H.266代码阅读(VTM8.0)(二. non-VCLU解码)
VVC/H.266代码阅读(VTM8.0)(三. Slice到CTU的处理 )
VVC/H.266代码阅读(VTM8.0)(四. CU划分 )

VVC/H.266常见资源为:
VVC/H.266常见资源整理(提案地址、代码、资料等)

注:

  1. 考虑到从解码端分析代码,一是更加简单(解码流程无需编码工具和编码参数的择优),二是可以配合Draft文本更好地理解视频编解码的流程(解码端也都包含预测、量化、环路滤波、熵解码等流程),所以本系列从解码端入手分析VVC解码大致流程。等到解码端代码分析完后,再从编码端深入分析。
  2. 本文分析的bin文件是利用VTM8.0的编码器,以All Intra配置(IBC 打开)编码100帧得到的二进制码流(TemporalSubsampleRatio: 8,实际编码 ⌈100 / 8⌉ = 13帧)。
  3. 解码用最简单的:-b str.bin -o dec.yuv
  • 本系列的第一篇博客分析了解码端将收到的二进制码流bin文件提取成一个个NALU的过程。第一篇博客的最后写道 “调用DecLib::decode()进行当前NALU的核心解码流程。该函数内,会根据当前NALU的类型进行针对性地解码。”
  • 本系列的第二篇博客针对non-VCLU解码进行了分析,如SPS、PPS等non-VCLU,其中对u(n)、ue(v)的代码进行了详细分析。
  • 本篇博客将分析VCLU的解码代码,从Slice到CTU的处理过程。

1. 延续之前的博客,继续从DecLib::decode()分析。
根据nal_unit_type调用不同的函数针对性地解码。
以该NALU为例,前两个字节为0x00 和 0x41 (01000 001),所以nal_unit_type为 01000 = 8 (IDR_N_LP),时域ID为0最高级。

对于IDR_N_LP的描述,可以参考draft的定义:

instantaneous decoding refresh (IDR) picture: An IRAP picture for which each VCL NAL unit has nal_unit_type equal to IDR_W_RADL or IDR_N_LP.
An IDR picture does not use inter prediction in its decoding process, and may be the first picture in the bitstream in decoding order, or may appear later in the bitstream. Each IDR picture is the first picture of a CVS in decoding order. When an IDR picture for which each VCL NAL unit has nal_unit_type equal to IDR_W_RADL, it may have associated RADL pictures. When an IDR picture for which each VCL NAL unit has nal_unit_type equal to IDR_N_LP, it does not have any associated leading pictures. An IDR picture does not have associated RASL pictures.
//IDR帧有两种,IDR_W_RADL和IDR_N_LP。
//IDR帧解码过程不使用帧间预测(是I帧)。每个IDR帧是按解码顺序CVS的第一帧。
//IDR_W_RADL有RADL图像。IDR_N_LP没有前置图像。IDR帧都没有RASL图像。
//注:关于RADL、RASL等图像的叙述,可以参考万帅老师《新一代高效视频编码H.265HEVC原理、标准与实现》的第九章274页。


由于当前NALU是IDR_N_LP,所以调用xDecodeSlice()解码。

2. 进入DecLib::xDecodeSlice()
① 首先,通过之前non-VCLU解码的SPS、PPS等信息获取相关参数。再根据参数做出相应的设置。

 m_apcSlicePilot->setPicHeader( &m_picHeader );m_apcSlicePilot->initSlice(); // the slice pilot is an object to prepare for a new slice
……m_apcSlicePilot->setNalUnitType(nalu.m_nalUnitType);m_apcSlicePilot->setTLayer(nalu.m_temporalId);m_HLSReader.setBitstream( &nalu.getBitstream() );m_apcSlicePilot->m_ccAlfFilterParam = m_cALF.getCcAlfFilterParam();m_HLSReader.parseSliceHeader( m_apcSlicePilot, &m_picHeader, &m_parameterSetManager, m_prevTid0POC );……

② 再核心调用DecSlice::decompressSlice() 解码该slice。
(1) 首先设置了CodingStructure类cs,该CodingStructure结构非常重要,管理了一帧中所有的CU,PU和TU,方便读取等操作。

  // setup coding structureCodingStructure& cs = *pic->cs;

(2) 再调用CABACReader::initCtxModels()进行了CABAC的上下文模型参数初始化。

  • 简单来说,就是根据qp和slice_type(I / B / P),从ContextSetCfg::sm_InitTables中选择了设定好的初值(该值是编解码两端商量好的,初值可以在draft中找到),用于CABAC的熵解码。具体初始化流程可以参考draft 9.3.2.2,对应代码如下:
void CtxStore<BinProbModel>::init( int qp, int initId )
{//initId 就是slice_typeconst std::vector<uint8_t>& initTable = ContextSetCfg::getInitTable( initId );//获取slice_type对应的初值const std::vector<uint8_t> &rateInitTable = ContextSetCfg::getInitTable(NUMBER_OF_SLICE_TYPES);int clippedQP = Clip3( 0, MAX_QP, qp );//qp限幅for( std::size_t k = 0; k < m_CtxBuffer.size(); k++ ){m_CtxBuffer[k].init( clippedQP, initTable[k] );//调用BinProbModel_Std::init()根据qp处理初值。m_CtxBuffer[k].setLog2WindowSize(rateInitTable[k]);}
}
void BinProbModel_Std::init( int qp, int initId )
{int slope = (initId >> 3) - 4;int offset = ((initId & 7) * 18) + 1;//对应draft中的描述的以下操作//slopeIdx = initValue  >>  3//offsetIdx = initValue & 7//m = slopeIdx − 4//n = ( offsetIdx  *  18 ) + 1int inistate = ((slope   * (qp - 16)) >> 1) + offset;int state_clip = inistate < 1 ? 1 : inistate > 127 ? 127 : inistate;//preCtxState = Clip3( 1, 127, ( ( m * ( Clip3( 0, 51, SliceQpY ) − 16 ) )  >>  1 ) + n )const int p1 = (state_clip << 8);m_state[0]   = p1 & MASK_0;m_state[1]   = p1 & MASK_1;//m_state[0..1]就是draft中的pStateIdx0/1//pStateIdx0 = preCtxState << 3//pStateIdx1 = preCtxState << 7}

(3) 将slice分割成若干个CTU,对每个CTU进行解码(对应draft内7.3.10.1 General slice data syntax)。(编码端没有设置多Slice和多Tile编码,部分代码省略)。核心调用coding_tree_unit() 和 decompressCtu()。

  • 关于该Slice中CTU的数量,是在DecLib::xDecodeSlice()的第①步解析相关参数时,调用HLSyntaxReader::parseSliceHeader() ==> HLSyntaxReader::parsePictureHeader() ==> PPS::initRectSlices() ==> addCtusToSlice() ,根据图像宽高、slice分割信息进行设置的。
 void  addCtusToSlice( uint32_t startX, uint32_t stopX, uint32_t startY, uint32_t stopY, uint32_t picWidthInCtbsY ) {CHECK( startX >= stopX || startY >= stopY, "Invalid slice definition");for( uint32_t ctbY = startY; ctbY < stopY; ctbY++ ) {for( uint32_t ctbX = startX; ctbX < stopX; ctbX++ ) {m_ctuAddrInSlice.push_back( ctbY * picWidthInCtbsY + ctbX );m_numCtuInSlice++;//m_numCtuInSlice就是该Slice中CTU的数目。}}}
};
  • 将slice分割成若干个CTU,对每个CTU进行解码(对应draft内7.3.10.1 General slice data syntax)。

  for( unsigned ctuIdx = 0; ctuIdx < slice->getNumCtuInSlice(); ctuIdx++ ){const unsigned  ctuRsAddr       = slice->getCtuAddrInSlice(ctuIdx);const unsigned  ctuXPosInCtus   = ctuRsAddr % widthInCtus;const unsigned  ctuYPosInCtus   = ctuRsAddr / widthInCtus;    //CTU位置信息……const unsigned  maxCUSize             = sps->getMaxCUWidth();Position pos( ctuXPosInCtus*maxCUSize, ctuYPosInCtus*maxCUSize) ;UnitArea ctuArea(cs.area.chromaFormat, Area( pos.x, pos.y, maxCUSize, maxCUSize ) );……cabacReader.coding_tree_unit( cs, ctuArea, pic->m_prevQP, ctuRsAddr );m_pcCuDecoder->decompressCtu( cs, ctuArea );……}

③ 调用CABACReader::coding_tree_unit() 解码该CTU,遵循draft内7.3.10.2 Coding tree unit syntax。该部分代码会在下一篇博客展开分析。

VVC/H.266代码阅读(VTM8.0)(三. Slice到CTU的处理 )相关推荐

  1. H.266代码学习:xIntraCodingTUBlock函数

    今天来继续学习帧内编码的重要函数xIntraCodingTUBlock,上次 H.266代码学习:xRecurIntraCodingLumaQT函数 学习中提到,xIntraCodingTUBlock ...

  2. H.266代码学习:decodeCtu和xDecodeCU函数

    之前 HEVC代码学习39:decodeCtu和xDecodeCU函数 中对HM中的CTU解码函数进行了学习,这里来学习一下JEM中的. 首先需要强调的是,这里只是用来解码flag.系数等,没有进行预 ...

  3. H.266代码学习:estIntraPredLumaQT函数

    之前 HEVC代码学习42:estIntraPredLumaQT函数 对HM中的estIntraPredLumaQT函数进行了学习,下面将对JEM中的该函数进行学习. estIntraPredLuma ...

  4. H.266代码学习:decompressCtu和xDecompressCU函数

    今天来学习一下JEM的decompressCtu和xDecompressCU函数.之前在 H.266代码学习:decodeCtu和xDecodeCU函数 学习了的学习中提到,decodeCtu和xDe ...

  5. H.266代码学习:JEM使用方法

    之前在HEVC代码学习0:HM使用+码流分析教程中详细介绍了HM使用方法,而H.266参考代码JEM已经成型,因此这里简单介绍下JEM的使用方法. 阅读建议: JEM使用方法与HM类似,使用中改动在于 ...

  6. 第一个将Palette Mode引入VVC(H.266),阿里云在JVET会议上引起关注

    从应用需求出发,帮助标准组织制定出更贴近云端业务需求的标准 视频压缩标准是一个重要且深具挑战的研究方向.从过去的存储到当前的网络带宽,视频标准每一代的更新进步对科技应用都有很大的影响.但同时随着几十年 ...

  7. 【十五】 VVC/H.266 | 帧内帧间联合预测技术CIIP详解

    文章目录 一.基本原理 二.具体技术细节 一.基本原理 CIIP是帧内帧间联合技术,这属于Merge系列技术的一个分支.该技术需要计算当前预测块的帧内预测值,即用传统的帧内预测模式去预测当前块的像素值 ...

  8. AVS3代码阅读HPM4.0(更新中)

    文章目录 结构体 ENC_PICO:原图缓冲结构 ENC_PINTRA:帧内预测结构 ENC_PINTER:帧间预测结构 ENC_PARAM:编码器参数 ENC_SBAC:稍后再议 ENC_CORE: ...

  9. VVC/H.266 项目文件的生成及有用网址

    H.266/VVC视频编码技术 小小柴的博客 VVCSoftware_VTM下载地址 生成项目文件 VVC与HEVC不同,从上述链接下载下来的参考软件并不包含build文件夹,需要使用cmake生成项 ...

最新文章

  1. 【组队学习】【29期】2. 计算机视觉
  2. 如何完全卸载 mysql 数据库
  3. 小型荧光驱动电路实验电路
  4. [转载] 2020最新Java面试题,常见面试题及答案汇总
  5. Java equals()方法和hashCode()方法
  6. 苹果公布iPhone 12屏幕更换价格,果然没让人失望!
  7. ELK和EFK的区别
  8. MyBatis→优缺点、select延迟加载、接口式MyBatis编程、一级缓存、二级缓存、集成Redis自定义缓存、Log4j
  9. java的equals什么作用_java当中equals函数的作用小结
  10. LABVIEW详细介绍:LABVIEW是什么软件?都可以干什么?
  11. 超级超级实用的整个网页截图技巧
  12. csgo服务器搭建 linux,Centos 搭建 CSGO KZ 服务器
  13. U盘图标自定义时不能修改图标?
  14. 目前最新《Thinkphp 5.0 仿百度糯米开发多商家电商平台》
  15. 【云原生】第二篇--容器管理工具 Docker生态架构及部署
  16. 关于微信小程序的多选和全选实现
  17. [admin]-01
  18. QRCode二维码相关资料
  19. 电力杆附挂轻型自承式光缆的施工与验收要点
  20. sql截取字段中一部分值

热门文章

  1. 消除 Buck 转换器中的 EMI 问题(一、产生原因)
  2. 小驼峰命名法与大驼峰命名法
  3. 驼峰命名法介绍及应用示例
  4. [Java笔记13] 日期与时间
  5. 卢京潮老师视频-自控原理PPT
  6. 火狐浏览器无法正常访问(ADsafe)
  7. Ucinet三天写论文!结构对等分析实战
  8. IIS7 SSL证书安装
  9. UnityShader之毛绒绒效果
  10. rsync的--daemon模式来同步数据