目标:
-
对协议各个部分有比较深入的了解
-
对JM代码有深入了解,重点了解intra、inter、loopfilter、dpb管理这几个模块
-
分析 调试码流问题的一些方法
H264 协议了解
重点了解解码部分,而解码实际上是编码的反过程,所以也需要理解编码过程的一些概念。
- 分层次、分块
视频实际是由一帧帧图像有序的组合在一起而形成,而在帧图像这一层,一帧图像可以分成一个个slice,每个slice在滤波之前都是可以进行独立的解码。 因为在编码的时候会将熵编码,预测等限制在一个slice内。一个slice又可以分为一个个宏块MB,264里面宏块的大小都是固定为16x16的。MB的大小决定了预测、变换、量化等模块处理的最大的块的大小,MB的大小实际决定了压缩处理过程的精细力度。
预测块(PU)的大小,预测块是在宏块下继续划分的,根据预测方式是帧内还是帧间可以有不同的分割方式。其中 帧内预测块大小可以为4x4, 8x8 和16x16 也就是pu块可以分成4个8x8 或者16个4x4。帧间预测分块大小相对于帧内多了一些非正方形的分块情况如16x8。 帧间的块大小可以为16x8,8x16,16x16,8x8,8x4,4x8,4x4。在协议中会有相对应的语法元素去描述分slice,分块的情况。
- 帧内预测
帧内预测将预测的范围限制在一帧图像内,总的原理就是利用块上边和左边的像素来预测块内的像素,其目的是为了消除空间上的冗余性。根据所利用像素位置的不同可以分为水平、垂直、角度、DC等。4x4、8x8有多几个角度预测的情况,而16x16 只有DC,垂直 水平和PLANE模式。此外还有IPCM预测模式,在这种模式下像素不经过任何预测,滤波 直接进行熵编码。
帧内预测解码流程: 1、预测模式获取:帧内预测的预测模式不是直接在比特流中传输,需要通过周围宏块的预测模式来获取当前宏块的预测模式。2、参考像素的获取:参考变有可能出现不可用情况, 这个时候需要进行填充。一般式利用上边或左边最后一个元素来扩展之后的元素。3、进行预测运算:根据不同的预测模式,利用周围的像素来填充块中的所有像素。
- 帧间预测
帧间预测模式相对于帧内复杂很多,其基本原理利用已经编码好的图像来预测当前图像,其目的是为了消除时间上的冗余。具体来说从时间轴上取1帧或者2帧,然后从这些帧中取一些块来预测当前帧中的块。传递当前块和参考块的很重要的一个信息是当前块和参考块的位移也就是MV的信息。帧间预测处理的粒度是一个pu块的大小,一个pu可能为16x16 或者 16x8,具体多大是在编码端进行决策,编码端遍历各种块的大小和预测模式的组合,取其中失真和码流大小平衡的模式。
帧间预测解码重点需要解决的几个问题:1、预测模式的获取: 这其中包括宏块的分割、pu块大小和预测模式 也就是p帧还是b帧,参考的是哪个参考帧列表,具体参考了哪些帧。2、mv的获取:mv的数据量占据码流中的很大一部分,所以说在编码端用了很多方法来减小这个数据量。但总的原理跟预测一样,也是利用时间域或者空间域的mv 来预测当前的mv。3、参考块的获取:获取到mv,也就知道当前块和参考块的位移,但是位移不一定为整数,也就是需要先对参考块进行插值操作,补齐不是整数位置的像素。然后才把参考块取出来。4、预测过程:就是将预测块的值填充到当前块,如果是p帧,只取一个预测块,如果是b帧,那么取两个预测块的值相加/2。如果是是加权的情况 还需要乘上相应的加权系数。
- 环路滤波
H264的环路滤波 相对比较简单,只用了去块滤波的方法。块效应是由基于块的方法进行编码而导致的。块与块之间采用不同的量化、编码方式而引入的。
环路滤波需要解决的几个问题: 1、确定边界:块效应是由于4x4 或者8x8残差块经过变换量化后 导致块和块之间的不平滑。所以边界就是用变换的最小块来进行划分的。 不管对于luma还是chroma,用4x4 将宏块进行分割,4x4 相互间的边界就是要进行滤波的边界。在所有边界中上边界和左边界是需要用到周围的宏块的,其他边界用的都是内部的宏块。2、确定边界强度:边界强度总共由5个等级 0-4,0不滤波,4 强滤波。 其中bs为123,最多只改变边界两边总共4个点的像素,最少改变边界两边2个点的像素。BS 为4的时候最多改变边界两边8个点的像素,最少边界两边2个点的像素。
JM代码理解
jm处理的基本流程:jm处理的基本单位是一个一个宏块,首先是读宏块的基本信息,不管宏块怎么划分,都会把整个宏块的信息给读下来,会根据熵编码的方式 和 I/B/P的情况 分别去调用不同的函数来读。 如果是I帧会读预测模式,块的划分等等信息。 如果是P/B帧会读取mv的信息。 然后进行解码 按照I/p/b 调用不同的函数进行解码,实际就是进行预测,反变换 反量化等操作。 如果是B/P帧会根据mv 从对应参考帧的位置读取数据 进行预测。然后加预测的数据和 反变换 反量化后的残差数据进行相加得到重构数据 这个就是mc,运动估计的过程。
帧内预测
帧内预测jm的流程如上,解码Ipb的时候都可能会有INTRA 块,遇到INTRA块根据不同的pu块和调用不同的函数,每个pu块的预测模式也不一样。具体的预测过程取 当前宏块周围可以利用的宏块,然后取像素进行运算,运算结果存到pred里面,用到候选和残差数据进行操作。
帧间预测
理解几个概念
- co-located
colPic可以确定在当前图像的第一个后向参考图像RefPicList1[ 0 ]内,也就是从时间上看最靠近当前图像的那个。
- skip direct
p skip 宏块、 b skip宏块、 b direct 宏块、 b direct 预测模式。其中p skip b skip 宏块里面没有MVD、也没有残差数据,而b direct宏块 有残差数据,没有MVD。b direct预测模式 是用在b skip 和 b direct 宏块里面的,跟其他其他预测模式的区别是,这种预测模式需要用到co-loated的位置处的mv,如果是p skip模式 就不会用到 co-loated位置的mv,但会用到空间周围的mv。b skip 和 b direct 宏块都是采用direct 预测的方式进行MV的预测,得到MV后就可以得到预测值。 skip 预测值就等于重构值,而 direct 预测值加上残差的数据才等于重构值。
- direct 预测
direct 预测包括时间和空间的预测。
1、空间预测 就是利用周围的MV来预测当前的MV, 但是空间预测中照样也要用到co-loated。因为有可能周围的块是运动的,而当前块是静止的。比如周围块是前景,当前块是背景。 这个时候要用co-loated的mv来判断当前块是不是静止的。
2、时间预测 就是在当前帧的colref 的mv 来预测当前的帧的mv。
考虑到当前代码中 有可能存在的情况是
1、空间预测的时候,如果col ref 是长期参考帧的话,那么不会去考虑mv的情况,就认为当前宏块是静止的,长期参考帧,有参考长期参考帧说明当前帧的变化十分缓慢,长期参考帧就类似于运动物体的背景,所以才能一直都在参考帧列表里面。
2、时间预测的时候,如果col ref 是帧内预测的时候,没有mv信息。
具体的实现可以分为如下的两个流程,
1、首先是读取预测模式,分块信息,mv等信息。其中比较重要的是mv,
mv传递到码流中只mvd和预测模式,解码端根据预测模式 如果是P宏块的话,1、是p skip直接从周围的宏块中预测当前的mv,预测得到的mv作为最后的mv。2、不是 p skip,同样从周边去预测当前的mv,然后从流中读到mvd,将预测到的mv + mvd才是最后的mv。如果是B宏块的话,1、b skip跟 p skip类似,但是区别于p skip的是预测当前mv的宏块可以是时间上面邻近的也就是colref。2、b diect mv 计算跟b skip的一样,direct和skip的区别是 dirct是像素残差数据,而skip没有。3、b宏块是两个参考帧,其会取两个mv信息。skip 和direct的mv 不是在read_one_component中实现的。
2、进行帧间预测计算,帧间预测根据b宏块和p宏块不同,p宏块 利用上个流程得到的mv 和参考帧信息,从对应的参考帧取出来参考数据,mv的信息实际是由mv_x, mv_y 指定了在参考帧中的起始位置,然后从(mv_x, mv_y)的起始位置取pu块大小的数据,取的时候,如果是在半像素或者1/4像素位置,还要先把这些位置的像素给构造出来,一般是通过整的像素点来插值出来的。如果是p帧 直接就将参考数据拷贝到预测的块中,B帧需要取两块数据然后相加除于2。而如果是加权预测,还要乘上加权的系数。
环路滤波
H264的滤波相对简单,只有deblock 去块这个流程,滤波是针对边界进行的,首先要确定怎么样是边界
然后边界是怎么样的一个情况决定了对边界应该进行怎么样的滤波,最后是滤波计算应该是多少像素参与。
- 滤波边界和滤波顺序
滤波是对一个一个宏块进行的, 将一个宏块分成 多个4X4 块,然后对于4X4 的边界进行滤波,因为一般出现块效应出现的地方 是在编码的一个宏块 和另一个宏块的交界处。滤波先进行亮度宏块滤波后进行色度宏块滤波,对一个宏块滤波边界的滤波也需要遵循一定顺序先进行垂直边界滤波,从左到右。后进行水平边界滤波,从上到下。
- 边界强度
确定BS 边界强度,BS跟边界两边的编码模式,残差系统等有关。在粗略地估算滤波边界强度后,我们需要区分这个边界强度是由于对块进行DCT变换量化引起的块效应(虚假边界)还是视频图像原有的边界(真实边界)。
- 滤波计算
在前面会计算得到5种边界强度BS,当边界强度不为0时,就需要进行边界滤波。h.264的边界滤波有两种滤波器 :1、BS = 1,2,3,采用强度较弱的滤波器,首先改变p0、q0两个像素点,接着用阈值β判断是否需要调整p1和q1。2、BS = 4,此时有两种强度的滤波器,强滤波器可以改变6个像素点(p0、p1、p2、q0、q1、q2),弱滤波器只改变边界上的两个点(p0、q0)。
具体实现
解码前面的帧间 帧内 反变换 反量化都结束之后,调用DeblckPicture来进行滤波,滤波都循环是一个宏块,对于mabff调用不同的函数,先计算边界强度,然后BS不为0,就按照先垂直 在水平的顺序来进行边界遍历滤波,真正边界滤波首先获取周围相关的宏块,在宏块边界的边界是跟周围的宏块有关系的。 这些边界计算会影响周围两边的像素,而处于内部的边界只影响宏块内部的像素。
问题调试
- 首先利用工具(visa/vega)查看码流的基本信息,8/10bit、yuv420/yuv422、mbaff/no mbaff/frame/field、每帧的类型和参考关系等。
- jm 运行打印信息,如果要调试dpb管理的,在jm中打开DUMP_DPB开关,这样在运行的时候 每解码完成一帧就会打印当前帧的类型以及dpb当中存储的所有帧,以及当前帧的参考了dpb中的哪些帧,通过运行多帧之后就能够看到哪些参考帧是存入到dpb中,然后哪些输出从参考帧中删除了。如后图
- 定位到具体的某一个宏块来进行问题调试,可以添加当前宏块的x和y坐标打印,但问题发生的时候,记录相关的坐标。后续可以在相关坐标的地方打印断点,进行单步调试。
- 定位到具体的解码模块,是帧内 帧间还是变换,滤波等等。然后在深入到每个模块去分析。
- 解码码流问题的调试相对一般的程序问题来说比较简单,基本上问题是可以稳定复现的。