[置顶] JM代码分析(二)

JM模型I帧帧内预测流程:

JM模型I帧帧内预测流程
I帧只存在帧内编码,没有帧间运动估计,不用参考其他的帧,所以I帧具有同步作用,.付出的代价就是效率稍差,不过也十分必要的。
I帧帧内编码分为亮度编码和色度编码,需要完成预测,计算RD代价,来判别宏块分块模式.
I帧亮度度分块模式分为16X16,8X8,4X4三种模式,色度分块模式只有一种8X8模式,
对于亮度的4X4预测时,分成9种模式。16X16有4种预测模式。
每种分块模式,又有不同的预测方式,在JM模型中,需要对这些模式进行RD代价计算,选择其中最小值作为最优模式。

下面对涉及分块模式的数据进行说明:
const int
mb_mode_table[9] = {0, 1, 2, 3, P8x8, I16MB, I4MB, I8MB, IPCM}; // DO NOT CHANGE ORDER !!!
0:16X16 Direct模式,在B帧中有效
1:Inter16X16,在帧间有效
2:Inter16X8,在帧间有效
3:Inter8X16,在帧间有效
P8X8:帧间有效
I16MB:Intra16X16帧内有效
I4MB:Intra有效
I8MB:Intra有效
IPCM:Intra有效,不要预测,直接对RAW数据编码.
其中P8X8模式和下面的数据又有关系:
const int b8_mode_table[6] = {0, 4, 5, 6, 7}; // DO NOT CHANGE ORDER !!!
上面的5种模式都归入P8X8模式,叫做亚宏块级,在码流TRACE文件有一个语法元素叫做b8mode,说的就是这个。
0:8X8 Direct模式,在B帧中有效。
4:Inter8X8,在帧间有效
5:Inter8X4,在帧间有效
6:Inter4X8,在帧间有效
7:Inter4X4,在帧间有效

下面举个例子,TRACE文件里面:
@45800 mb_type (B_SLICE) ( 7, 4) = 8 0000 ( 22)
@45804 8x8 mode/pdir( 0) = 4/0 000 ( 1) //Inter8X8
@45807 8x8 mode/pdir( 1) = 6/1 0000 ( 7) //6:Inter4X8
@45811 8x8 mode/pdir( 2) = 4/0 00 ( 1)
@45813 8x8 mode/pdir( 3) = 5/1 0000000 ( 6) //Inter8X4
意思就是位置(7,4)宏块是P8X8分块,其中:
第一个8X8块是8X8,list0预测,
第二个8X8块,是两个4X8块,list1方向预测,
第3个是8X8,list0预测,
第四个是两个8X4,list1预测,
这个好象有点跑题了,因为b8mode不是I帧分块模式,是P,B帧中分块模式。以上基本包括了264帧内,帧间要用到的所有分块模式.

下面开始分析帧内预测流程
色度预测(计算所有可能的预测模式的预测值)—->
亮度预测(所有的分块模式,要对3种分块,16X16,8X8,4X4 即:I16MB、I4MB、I8MB)—->
计算每种模式的RD值(包括各种预测方式)—>
获得最优。

下面可是一步一步进行说明:
1:色度预测
色度预测基于8X8块预测,8X8预测有4种预测模式,包括水平,垂直,DC预测,平坦预测。对色度帧内预测,必须要提到几个表格:
一个表格是:
static int block_pos[3][4][4]=
//[yuv][b8][b4]
{
{ {0, 1, 2, 3},{0, 0, 0, 0},{0, 0, 0, 0},{0, 0, 0, 0}},
{ {0, 1, 2, 3},{2, 3, 2, 3},{0, 0, 0, 0},{0, 0, 0, 0}},
{ {0, 1, 2, 3},{1, 1, 3, 3},{2, 3, 2, 3},{3, 3, 3, 3}}
};
其中yuv就是YUV采样比例,yuv = YUV format -1,因此YUV420,yuv值等于0,
b8宏块色度8X8块序号,YUV420,8X8块只有一个,b8为0,
b4有0,1,2,3,因此,很容易看明白上面的表格.
block_pos[yuv420][0] = {0, 1, 2, 3};

第二个表格是
static const unsigned char subblk_offset_x[3][8][4] = //[yuv][b8][b4]
{
{
{0, 4, 0, 4},
{0, 4, 0, 4},
{0, 0, 0, 0},
{0, 0, 0, 0},
{0, 0, 0, 0},
{0, 0, 0, 0},
{0, 0, 0, 0},
{0, 0, 0, 0},
},
{
{0, 4, 0, 4},
{0, 4, 0, 4},
{0, 4, 0, 4},
{0, 4, 0, 4},
{0, 0, 0, 0},
{0, 0, 0, 0},
{0, 0, 0, 0},
{0, 0, 0, 0},
},
{
{0, 4, 0, 4},
{8,12, 8,12},
{0, 4, 0, 4},
{8,12, 8,12},
{0, 4, 0, 4},
{8,12, 8,12},
{0, 4, 0, 4},
{8,12, 8,12}
}
};

这两个表格和上面的那个block_ pos含义差不多,但是对U,V两个分量都包括进去了,
这个含义是帧内坐标偏移,不是上面序号的意思 ,色度帧内预测要用到临块A,B,C宏块情况做出判断,
ImageParameters结构里面有一个成员
mprr_c[2][4][16][16];//[uv][DC_PRED_8/HOR_PRED_8/VERT_PRED_8/PLANE_8][16][16]
这个成员,放置了各种模式下的预测值,问为什么要16X16呢,而不是8X8,当YUV444模式时,当然是16X16了,这个是预留得
1:DC预测,DC预测又是以4X4块为基本单位,对A,B,C块的象素取平均值,假如没有A,B,C块的时候,取128了
2:垂直预测,假如B宏块存在,直接把B宏块最下边的象素Copy
3:水平预测,假如A宏块存在,直接把A宏块最右边的象素Copy
4:平坦模式,需要A,B,C同时存在,平坦模式的预测象素值算法比较复杂,这里不讲了.
预测完了,色度值,暂时保存在mprr_c数组里面,等待后面使用,

在JM代码中,intra_chroma_prediction进行帧内色度预测
set_chroma_pred_mode:
先调用intra_chroma_prediction进行帧内色度预测,然后再intra_chroma_RD_decision进行最佳模式的的选择。

2:亮度预测:
对亮度的预测和代价值的计算都在函数compute_mode_RD_cost中,该函数又用RDCost_for_macroblocks来完成主要计算该函数说明如下:

int RDCost_for_macroblocks (
double   lambda,       // <-- lagrange multiplier,拉格朗日因子 
int      mode,         // <-- modus (0-COPY/DIRECT, 1-16x16, 2-16x8, 3-8x16, 4-8x8(+), 5-Intra4x4, //6-Intra16x16)mode 9种分块模式. 
double*  min_rdcost,   // <-> minimum rate-distortion cost 
double*  min_rate,     // --> bitrate of mode which has minimum rate-distortion cost. 
int i16mode           //16X16预测模式,仅对帧间16X16有效
)

该函数对mode取不同值选择不同的模式决策函数
I4MB————-Mode_Decision_for_Intra4x4Macroblock
I16MB————Intra16x16_Mode_Decision
I8MB————-Mode_Decision_for_new_Intra8x8Macroblock
下面对I4MB进行分析:
I4MB又调用Mode_Decision_for_8x8IntraBlocks函数,故名思义,该函数对8X8块进行模式选择,该函数又调用Mode_Decision_for_4x4IntraBlocks,该函数完成4X4帧内预测,残差计算,参差DCT,Zig排序,量化,RL编码,IDCT,反量化,存储重建象素,便于以后使用.

二。264编一个I帧多麻烦—JM 9.7中I帧编码分析
编码一帧,首先当然得知道从哪儿开始编,自打有了容错这种东西之后,FMO(Flexible macroblock ordering)就成了选择编码块的第一步了.当然,最平常的编码方法中,一帧就是一个slice,一个slice从一帧的第一个宏块开始算,所以得到一帧的最左上角的宏块地址,I帧编码开始.
在初始化编码参数的时候,大都都是在configure文件中设置好的编码参数,比如说在Intra中4x4的某个方向的预测是不是被禁止的,8x8是可选的,还是说压根儿就不用等等.这些都被初始化到 enc_ mb->valid[] 这个诡异的小东西里面了,所以说如果要做单个block mode的时候,对里面的数据一定是要非常小心的.
对于一个I帧来说,编码总是先把一帧的U,V部分先进行Intra prediction,然后才考虑在Chroma最优之后,Luma的最优解.之后通过计算编码这一帧来说最小的RD cost的mode来得到当前luma分量的编码方法.核心函数:rdopt.c: int RDCost_for_macroblocks(…..)在该函数中,对于不同种的分块模式以及当前的编码status来分析,涉及到Intra coding的有I4MB,I8MB,I16MB以及IPCM(IPCM是个

关于H.264中DCT变换问题

以前写的一篇文章,现在放上来。解码器现在作到Huffman解码了。

窗外的烟花声音渐渐沉寂,开始工作了.
H.264标准DCT8X8变换问题,据流媒体MPEG4/H264版面斑竹,X264/MPEG4开发小组成员的chenm001讲已经废弃,所有

ABT(自适应块尺寸变换)部分,都从标准中拿掉.但是我从JM97测试模型中依然开始看到,这一部分还在工作,码流依然输出
了相关的DCT8X8 flag信息,所谓ABT(adaptive block
transform),就是讲在DCT变换的时候,选择4X4,8X8,16X16是可以自适应
的,自动选择其最合理的方式,听起来的挺好的事情,任何好的东西都是要付出代价的,天下没有掉下的馅饼。自适应是要
付出计算代价的。H.264
80%以上(对不起,没有数据参考,纯属个人估计)的DCT变换是基于4X4的,对I16MB模式下用16X16
变换.其实DCT8X8变换是很多压缩标准使用的变换方式,比如JPEG.
DCT8X8模式是JM的一个可选项.哪些项可以启用8X8DCT呢?
我们看JM对此决策:
if ((mode >= 1 &&
mode <= 3) && currMB->luma_transform_size_8x8_flag ==
0)
{
//try with
8x8 transform size
}
//=========== try
DIRECT-MODE with 8x8 transform ===========
else if (mode == 0
&& bslice &&
active_sps->direct_8x8_inference_flag &&
currMB->luma_transform_size_8x8_flag == 0)
{
//try with
8x8 transform size

continue;
}
//=========== try
mb_type P8x8 for mode 4 with 4x4/8x8 transform ===========
else if ((mode == P8x8)
&& (enc_mb.valid[4]) &&
(currMB->luma_transform_size_8x8_flag == 0))
{
//check 8x8
partition for transform size 8x8
}
可以看得出来当我们帧间预测的时候,模式为16X8,8X16,16X16,P8X8,I8MB时候,我们都要需要测试计算DCT8X8的RD大小.

为何其他的尺寸不做DCT8X8,比如I4MB,这是理所当然的,我们作DCT是为了块数据的去相关性,我们对I4MB是做4X4预测的

我们却对四个4X4的block合并一起作DCT8X8,数据之间的相关性不大,可以想象效果不会好.这里要指出,对宏块作I8MB子块预测

也是新加入标准的,据我对JM的了解,I8MB也和I4MB一样,有9种预测模式.值的注意的是,I8MB和其它的帧内子块有所不同,由于

DCT8X8变换尺寸比较大,大尺寸变换虽然去相关效果不错,但是会尺寸比较严重的块效应,尤其两块之间的边缘会形成比较大的梯度
所以在预测之前,I8MB子预测要做低通滤波,人工去除两块之间变换后引入的高频噪声,这样块边界看起来比较平滑。

诡异的东西,回头再说)对于I16MB来说:首先进行16x16块的Intra prediction.当然这是整个块的预测了,所以不涉及到ABT的问题,直接用DC,水平,垂直,Plane四种mode进行编码,就可以得到四个mode的预测结果.得到预测结果之后,我们用orignal的数据减去我们预测的数据,得到的就是residual,不同的mode得到的residual的值是不同的,结果就是他们所含的能量是不同的,它的结果是编码所消耗的码字是不同的,于是乎结果就是不同的.多少年来,做编码的人们为了这么几个bits费劲脑力,为了省bits,对比结果的这么些个功还是得做的.比较的内容就是他们各自的sad(sum of absolute difference),SAD最小的那个mode被称为I16MB分块方式中最佳的编码模式.预测结束之后,我们就使用这个编码模式,对residual进行DCT与Quantization,结果直接送给熵编码器进行处理,这样一个I16MB编码完毕.对于I4MB来说:将16x16的宏块先分成4个8x8的子块,再把各个子块各分成4个4x4的子块,对每一个4x4的块进行intra prediction,当然4x4的块进行预测有9个mode,是根据它们可能的纹理走向进行预测的,最符合纹理方向的mode得到的residual比较小,才会被选为是最佳编码的mode.用最佳的mode得到的最佳intra prediction的值求得相应的residual,计算各自的RD.方法大同小异,计算DCT,送给Quantization,结果送给熵编码器,编码收工.这样一直等到16个4x4的块编码完成算是完事.对于I8MB来说:把一个16x16的宏块分成4个8x8的子块,对于每一个子块都进行8x8的prediction.计算的结果,算得residual,将每一个residual利用Hadamard变换求得SATD,比较大小,小的那个为最佳的预测mode.保留最佳residual,进行8x8的dct变换,将变换系数保留,编码结束.这样一直进行操作,使得整个帧都被编码,这时I帧编码结束.写出MB Layer数据,写出Macroblock编码数据,I帧编码结束.可以看到对于I帧编码很容易想到更复杂的算法,比如说在一个宏块里面使用多种子块方法等,但是这样的做法可能会使得cbp的bits数过多,使得bitrate与PSNR的比值不划算,所以编码的一个中心的思想还是很重要的,就是复杂的不见得最优,数学理论与实验科学还是有一些区别的.这也为继续的研究给出来一个启示,多想想简单而出新的想法,不要把一切都归给数学,警示

h264的压缩方法

1.分组: 把几帧图像分为一组(GOP,也就是一个序列),为防止运动变化,帧数不宜取多。
2.定义帧: 将每组内各帧图像定义为三种类型,即I帧、B帧和P帧;
3.预测帧: 以I帧做为基础帧,以I帧预测P帧,再由I帧和P帧预测B帧;
4.数据传输: 最后将I帧数据与预测的差值信息进行存储和传输。

三种帧的说明

1、I帧
I帧:帧内编码帧 ,I帧表示关键帧,你可以理解为这一帧画面的完整保留;解码时只需要本帧数据就可以完成(因为包含完整画面)
I帧特点:
1)它是一个全帧压缩编码帧。它将全帧图像信息进行JPEG压缩编码及传输;
2)解码时仅用I帧的数据就可重构完整图像;
3)I帧描述了图像背景和运动主体的详情;
4)I帧不需要参考其他画面而生成;
5)I帧是P帧和B帧的参考帧(其质量直接影响到同组中以后各帧的质量);
6)I帧是帧组GOP的基础帧(第一帧),在一组中只有一个I帧;
7)I帧不需要考虑运动矢量;
8)I帧所占数据的信息量比较大。
2、P帧
P帧:前向预测编码帧。P帧表示的是这一帧跟之前的一个关键帧(或P帧)的差别解码时需要用之前缓存的画面叠加上本帧定义的差别,生成最终画面。(也就是差别帧,P帧没有完整画面数据,只有与前一帧的画面差别的数据)
P帧的预测与重构:P帧是以I帧为参考帧,在I帧中找出P帧“某点”的预测值和运动矢量,取预测差值和运动矢量一起传送。在接收端根据运动矢量从I帧中找出P帧“某点”的预测值并与差值相加以得到P帧“某点”样值,从而可得到完整的P帧。
P帧特点:
1)P帧是I帧后面相隔1~2帧的编码帧;
2)P帧采用运动补偿的方法传送它与前面的I或P帧的差值及运动矢量(预测误差);
3)解码时必须将I帧中的预测值与预测误差求和后才能重构完整的P帧图像;
4)P帧属于前向预测的帧间编码。它只参考前面最靠近它的I帧或P帧;
5)P帧可以是其后面P帧的参考帧,也可以是其前后的B帧的参考帧;
6)由于P帧是参考帧,它可能造成解码错误的扩散;
7)由于是差值传送,P帧的压缩比较高。
3、B帧
B帧:双向预测内插编码帧。B帧是双向差别帧,也就是B帧记录的是本帧与前后帧的差别(具体比较复杂,有4种情况,但我这样说简单些),换言之,要解码B帧,不仅要取得之前的缓存画面,还要解码之后的画面,通过前后画面的与本帧数据的叠加取得最终的画面。B帧压缩率高,但是解码时CPU会比较累。
B帧的预测与重构
B帧以前面的I或P帧和后面的P帧为参考帧,“找出”B帧“某点”的预测值和两个运动矢量,并取预测差值和运动矢量传送。接收端根据运动矢量在两个参考帧中“找出(算出)”预测值并与差值求和,得到B帧“某点”样值,从而可得到完整的B帧。
B帧特点
1)B帧是由前面的I或P帧和后面的P帧来进行预测的;
2)B帧传送的是它与前面的I或P帧和后面的P帧之间的预测误差及运动矢量;
3)B帧是双向预测编码帧;
4)B帧压缩比最高,因为它只反映丙参考帧间运动主体的变化情况,预测比较准确;
5)B帧不是参考帧,不会造成解码错误的扩散。
注:I、B、P各帧是根据压缩算法的需要,是人为定义的,它们都是实实在在的物理帧。一般来说,I帧的压缩率是7(跟JPG差不多),P帧是20,B帧可以达到50。可见使用B帧能节省大量空间,节省出来的空间可以用来保存多一些I帧,这样在相同码率下,可以提供更好的画质。

IPCM

I_PCM模式:
I_PCM是一种帧内编码模式,在该模式下,编码器直接传输图像的像素值,而不经过预测和变换。在一些特殊的情况下,特别是图像内容不规则或者量化参数非常低时,该模式比常规的操作(帧内预测–变换–量化–编码)效率更高。
I_PCM模式用于以下目的:
1.允许编码器精确地表示像素值
2.提供表示不规则图像内容的准确性,而不引起重大的数据量增加
3.严格限制宏块解码比特数,但不降低编码效率
写IPCM数据是在每个write_i_slice_MB_layer/write_p_slice_MB_layer/write_b_slice_MB_layer中的
writeIPCMData,而里面包含着writeIPCMLuma/writeIPCMChroma分别写亮度和色度。

  if (currMB->mb_type == IPCM)
  {
    //写IPCM数据
    no_bits += writeIPCMData(currMB, &(currSlice->partArr[partMap[SE_LUM_DC_INTRA]])); return no_bits; }

CAVLC残差数据编码

VLC :变长编码
1、编码coeff_ token 对应于:writeSyntaxElement_NumCoeffTrailingOnes函数中
coeff_token
是指一次变换系数幅值扫描中的非零变换系数幅值总数 和 拖尾变换系数幅值的个数。
拖尾变换系数幅值 是指 非零变换系数扫描结尾处绝对值为 1 的多至连续三个非零变换系数级别中的一个。
Totalcoeff 表示非零系数的总数
TrailingOnes 表示非零系数中±1的个数
4×4块中一共有16个系数,所以Totalcoeff 的范围是0-16,而TrailingOnes的范围是0-3,如果±1的个数大于3,则只有最后的3个作为特殊情况处理,其他的则作为正常系数编码。

而coeff_token则是使用Totalcoeff 和TrailingOnes当索引通过查表得到的。
查找表的选择还依赖一个系数nC,它取决于左边和上边已编码块(分别称为nA和nB)的Totalcoeff。
nC计算如下:
如果上边和左边块nB和nA都可以得到,则nC = round((nA+nB)/2);
如果只有上边块可以得到,nC = nB;
如果只有左边块可以得到,nC = nA;
如果两个块都得不到,则nC = 0。

如果调用CAVLC解析过程是为了ChromaDCLevel (色度直流系数),nC 的值如下:
— 如果 chroma_format_idc等于1,nC 等于 −1; 420
— 如果 chroma_format_idc等于 2,nC等于−2; 422
— 否则 chroma_format_idc 等于 3,nC 等于 0。 444

2、编码每个TrailingOnes的符号 writeSyntaxElement_VLC

3、编码余下非零系数的级别(符号和幅度)
按倒序进行,即从高频开始,对应的JM函数为
writeSyntaxElement_Level_VLC1
writeSyntaxElement_Level_VLCN
前者用来编码第一个非零系数,后者则是编码除第一个以外非零系数

每个级别的码字包含一个前缀(prefix)和后缀(suffix)。后缀的长度(suffixLength)可以是0到6比特,而suffixLength随着每个连续编码级别的幅度自适应变化。较小的suffixLength使用于较低幅度的级别,而较大的suffixLength适用于较大幅度的级别。

具体的编码算法比较复杂,可以参考标准文档的9.2.2小节表述的解析过程,从而推出其编码过程。

4、编码最后一个非零残差系数之前的零系数的个数,最高频非零系数前零的总数,对应的JM函数为writeSyntaxElement_TotalZeros
total_zeros
是指位于变换系数幅值扫描中最后的 非零变换系数幅值的位置之前的取值为 0 的变换系数幅值的总数。

total_zeros 的取值范围如 9.2.3 小节所述。

5、编码每个零游程,即每个非零系数前零的个数(run_ before ),也是按倒序进行。对应的JM函数为writeSyntaxElement_Run
run_before 是指扫描中位于非零变换系数幅值之前的连续的取值为 0 的变换系数幅值的个数

该步骤有2个例外,
1.如果余下的已经没有零需要被编码,则没有必要编码任何其他的run_before;
2.没有必要为最后一个非零系数编码run_before值。
参考:http://www.cnblogs.com/xkfz007/articles/2616398.html

coeff_token 的取值 范围在 9.2.1 节给出。
trailing_ones_sign_flag 是指拖尾的变换系数幅值的的正负符号,详情如下:
-— 如果 trailing_ones_sign_flag 等于 0,则相应的变换系数幅值解码值为+1。
-— 否则(trailing_ones_sign_flag 等于 1),则相应的变换系数幅值解码值为-1。
level_prefix 和 level_suffix 是指非零变换系数幅值的值。9.2.2 小节中描述了 level_prefix 和 level_suffix 的取值 范围。
total_zeros 是指位于变换系数幅值扫描中最后的非零变换系数幅值的位置之前的取值为 0 的变换系数幅值的 总数。total_zeros 的取值范围如 9.2.3 小节所述。
run_before 是指扫描中位于非零变换系数幅值之前的连续的取值为 0 的变换系数幅值的个数。run_before 的 取值范围如 9.2.3 小节所述。
coeffLevel 包含了当前的变换系数幅值列表的 maxNumCoeff 个变换系数幅值

GDB使用

1 监视for循环中的i,比如让i=50的时候停下来

首先用gdb的命令watch,监视i,命令是 watch i。
然后查询break命令,info breakpoints,查到watch i这个硬断点的序号,比如是2,
然后用跳过break的命令,ignore。命令是ignore 2 5,这个意思就是忽略2号断点5次。

eg:for(i=0;i<10;i++)
{
a+=2;
b-=2;
}

在for那行下break,然后我们watch i,接着我们会发现,i的值只要发生改变就会停住。
然后当i=2的时候,我们用命令,ignore 2 5,然后continue,程序会在i从7变到8的时候停下。

2 Watchpoint: 它的作用是让程序在某个表达式的值发生变化的时候停止运行,达到‘监视’该表达式的目的
(1)设置watchpoints:
a. watch expr: 设置写watchpoint,当应用程序写expr, 修改其值时,程序停止运行
b. rwatch expr: 设置读watchpoint,当应用程序读表达式expr时,程序停止运行
c. awatch expr: 设置读写watchpoint, 当应用程序读或者写表达式expr时,程序都会停止运行
(2)info watchpoints:
查看当前调试的程序中设置的watchpoints相关信息
(3)watchpoints和breakpoints很相像,都有enable/disabe/delete等操作,使用方法也与breakpoints的类似

你可能感兴趣的:(模式,编码,分块)