本周将查看JMVC的数据流动,认识在本模型代码中那些存储数据的类。 1. I/O文件 做编码实验的时候,不能直接编码有interview依赖关系的视频,例如双视点实验中,在没有编主视点0的时候,不能直接编辅视点1。观察一下载双视点编码中的输入输出文件(根据真实JMVC代码实验): (1) rcFilename=D:\workspace\project\jmvc\bin\recon_1.yuv 如上是被编码辅视点1时先后初始化的文件,除了本视点外,还有主视点0的重构文件被应用recon_0.yuv。给出以上四种情况的调用关系: (1) H264AVCEncoderTest::init()-> WriteYuvToFile::create()->WriteYuvToFile::xInit() (2) H264AVCEncoderTest::init()->ReadYuvFile::init() (3) H264AVCEncoderTest::init()->WriteBitstreamToFile::init() (4)H264AVCEncoderTest::go()->CreaterH264AVCEncoder::init()-> MultiviewReferencePictureManager :: AddVectorOfFilesToUseAsReference()-> MultiviewReferencePictureManager::AddViewFileToUseAsReference() <_references.push_back()> -> ReadYuvFile::init() 重点看以下(4),在PicEncoder中,会有个专门做inter-view管理的对象(MultiviewReferencePictureManager) m_MultiviewRefPicManager,在PicEncoder::process()的参考组织过程中,m_MultiviewRefPicManager. AddMultiviewReferencesPicturesToBuffer()负责把inter-view的帧放入相应的list0或list1中,便于以后的时差估计使用,这就是JMVC的独特之处。
图1 H.264编码流程 图1展示了H.264编码流程,JMVC把重构帧(就是入如上图1经过DCT、量化和他们的逆过程,做了运动补偿形成的帧)记录到文件中,在以后的依赖视点编码时,可以直接读入使用。使用重构帧为运动/视差估计是应为在解码端只能获得重构帧。 2. H264AVCEncoderTest::go()分析 这里是编码测试的入口,略过前面对SEI信息的处理。可以看到的是代码将逐帧从YUV文件读取数据。YUV数据在文件中按Y,U,V的顺序一帧帧的紧接存放,没有冗余数据,如下是对4:2:0的176x144帧数据举例: 176
下面来看看这个帧数据是如何分离成运动矢量和残差、如何进行输出重构帧,又如何管理参考帧的。
3.参考帧分析 编码端的参考帧管理在PicEncoder::xEncodePicture()-> PicEncoder::xStartPicture()->RecPicBuffer::getRefLists()。在这里根据帧类型,进行list0和list1的初始化,填值(包括了inter和inter-view过程)和重排序,下面的实验将调研标准hierarchical B结构(GOP=8)在编码端的参考帧管理。hierarchical B结构(GOP=8)在“MVC学习的第二周小结(2009.5.18-2009.5.24)-mvc解码概况和参考帧分析”中有附图,那里还实验分析了解码端的参考帧管理。 ¥¥¥¥¥¥¥¥¥¥JMVC编码端参考帧实验¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥ 如附图实验三视点情况。0为主视点,2为第一辅视点,1为第二辅视点。考虑到篇幅限制,这里只给出视点1的数据,既包含帧间也包含inter-view的典型例子。在***内部的是xRefListRemapping()前的参考帧情况。实验数据的答应顺序是printf( " %d/%d", rcList[uiIndex]->getViewId(), rcList[uiIndex]->getPOC() ); *************before****************** *************before****************** *************before****************** 从以上的实验结果来看,xRefListRemapping()不仅是重排序参考帧而且精简了参考帧,只留下hierarchical B结构中有直接关系的帧。重排序的控制和解码端一致符合标准文档中的状态控制 enum RplrOp 标准模型只做了很基础的参考帧控制以符合hierarchical B结构,没有进行复杂的优化,而在运动估计中也没有对同一宏块可以从两个前向或后向的参考帧同时取得预测信息,所以给大家留下了很多优化的空间。 4. 运动矢量和残差分离 在SliceEncoder::encodeSlice()中,有两个重要的编码端函数。MbEncoder::encodeMacroblock()是进行帧内、帧间和视差预测,并进行DCT变换和量化,而MbCoder::encode()将对码表里的组织数据进行熵编码(残差熵编码在MbEncoder::encodeMacroblock()中完成),它将根据配置文件选择使用cabac和cavlc(代码里的UvlcWriter类)完成具体编码。 在MbEncoder::encodeMacroblock()里,根据宏块模式,将进行xEstimateMb***或xEstimateMbIntra***系列的函数,进行运动视差估计或帧内估计。还是从最熟悉的xEstimateMb16x16()入手分析,在完成运动估计提取运动矢量之后,xSetRdCostInterMb()函数随之被调用,在该函数中将利用m_pcMotionEstimation->compensateMb()通过运动矢量和参考帧匹配求出残差值,然后在MbEncoder::xEncode4x4InterBlock()中进行残差值编码,最重要的就是要进行DCT整数变换和量化。这部分是在xEncode4x4InterBlock()中完成的。 其中,Transform::transform4x4Blk()被调用作为变换和量化,并且反变换和分量化重构帧的宏块成分。之后的MbCoder::xScanLumaBlock()就已经去读取残差的变换量化后的输出系数并做熵编码了。DCT整数变化使用蝶形图方法,已经在我5月的文章“H.264整数DCT公式推导和蝶形图分析”里有所叙述,量化部分留待以后分析。(todo: 量化部分分析) 在xEstimateMb16x16()的最后一个被调用函数是xCheckInterMbMode8x8(),该函数运用xSetRdCost8x8InterMb(),使用8x8的DCT。将其RDCost和4x4的比较,选择其最小值的模式作为最终DCT模式。从RDcost的定义公式可以知道,它完整的计算除了SAD部分还有数据编码的代价部分,在这里对每一种宏块分割模式严格使用经过DCT,量化和熵编码的实际值进行衡量,其计算代价相当高。是否可以通过SATD可以预测出一种最佳模式,再进行DCT,量化和熵编码过程?以后再查看X264相关代码时要注意。(todo:是否可以用简单运算SATD先预测最佳宏块分割) 本周把JMVC按图1的H.264编码数据流过程大致走了一遍,分析了编码参考帧,系统的I/O文件和运动估计后对残差的获得与处理。余下一些具体的帧内预测编码,熵编码运算和量化过程分析,将在下周(2009.6.29-2009.7.5)继续探讨。 |