H.264编一个I帧多麻烦---JM 9.7中I帧编码分析

来源:百度文库 编辑:神马文学网 时间:2024/04/30 02:19:05

264编一个I帧多麻烦---JM 9.7中I帧编码分析

JM模型I帧帧内预测流程 
 
 I帧只存在帧内编码,没有帧间运动估计,不用参考其他的帧,所以I帧具有同步作用,.付出的代价就是效率稍差,不过也十分必要的。
 I帧帧内编码分为亮度编码和色度编码,需要完成预测,计算RD代价,来判别宏块分块模式.
 I帧亮度度分块模式分为16X16,8X8,4X4三种模式,色度分块模式只有一种8X8模式,每种分块模式,又有不同的
预测方式,在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帧中有效 应在在B帧和P帧中都有效,包含P_Skip(16×16)、B_Direct_16x16和B_Skip(8×8)三种宏块类型
1:Inter16X16,在帧间有效
2:Inter16X8,在帧间有效
3:Inter8X16,在帧间有效
P8X8:帧间有效 8 包含了P8X8和B8X8宏块类型
I16MB:Intra16X16帧内有效 10
I4MB:Intra有效 9
I8MB:Intra有效 13
IPCM:Intra有效,不要预测,直接对RAW数据编码. 14

其中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)
@45807 8x8 mode/pdir( 1) =   6/1                                  0000 (  7)
@45811 8x8 mode/pdir( 2) =   4/0                                    00 (  1)
@45813 8x8 mode/pdir( 3) =   5/1                               0000000 (  6)
意思就是位置(7,4)宏块是P8X8分块,
其中第一个8X8块是8X8,list0预测,第二个8X8块,是两个4X8块,list1方向预测,第3个是8X8,list0预测,第四个是两个8X4,list1预测,
这个好象有点跑题了,因为b8mode不是I帧分块模式,是P,B帧中分块模式。
以上基本包括了264帧内,帧间要用到的所有分块模式.
  下面开始分析帧内预测流程:
 色度预测(计算所有可能的预测模式的预测值)---->亮度预测(所有的分块模式,要对3种分块)---->计算每种模式的RD值(包括各种预测方式)--->获得最优。
下面可是一步一步进行说明:
1:色度预测
    色度预测基于8X8块预测,8X8预测有4种预测模式,包括水平,垂直,DC预测,平坦预测。 与帧内16x16预测的4种预测模式类似
对色度帧内预测,必须要提到几个表格:
一个表格是:
  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一个宏块中chroma8X8块只有一个,b8为0;
b4为chroma4x4块的预测位置,采用不同的预测方法,为0,1,2,3。
YUV format (0=4:0:0, 1=4:2:0, 2=4:2:2, 3=4:4:4)
b4中,0:TOP_LEFT;1:TOP_RIGHT;2:BOTTOM_LEFT;3:BOTTOM_RIGHT
YUV420: YUV422: YUV444:
0 1 0 1 0 1 1 1
2 3 2 3 2 3 3 3
2 3 2 3 3 3
2 3 2 3 3 3

  第二个表格是
const unsigned char subblk_offset_x[3][8][4] =  //[yuv][b8][b4]
{
  { {0, 4, 0, 4}, 这一组是代表一个chroma8x8块中的4个4x4子块的x偏移量(与最左边块的以像素为单位的距离)
    {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}, 第一个chroma8x8块
    {0, 4, 0, 4}, 第二个chroma8x8块
    {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}, 第一个chroma8x8块
    {8,12, 8,12}, 第二个chroma8x8块
    {0, 4, 0, 4}, 第三个chroma8x8块
    {8,12, 8,12}, 第四个chroma8x8块
    {0, 4, 0, 4}, 
    {8,12, 8,12}, 
    {0, 4, 0, 4}, 
    {8,12, 8,12}  }
};
   
x偏移量:
YUV420: YUV422: YUV444:
0 4 0 4 0 4 8 12
0 4 0 4 0 4 8 12
0 4 0 4 8 12
0 4 0 4 8 12

const unsigned char subblk_offset_y[3][8][4] =//[yuv][b8][b4]
{ { {0, 0, 4, 4},
    {0, 0, 4, 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, 0, 4, 4},
    {8, 8,12,12},
    {0, 0, 4, 4},
    {8, 8,12,12},
    {0, 0, 0, 0},
    {0, 0, 0, 0},
    {0, 0, 0, 0},
    {0, 0, 0, 0}  },
  { {0, 0, 4, 4},
    {0, 0, 4, 4},
    {8, 8,12,12},
    {8, 8,12,12},
    {0, 0, 4, 4},
    {0, 0, 4, 4},
    {8, 8,12,12},
    {8, 8,12,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数组里面,等待后面使用,
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[]这个诡异的小东西里面了,所以说如果要做单个blockmode的时候,对里面的数据一定是要非常小心的.对于一个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的比值不划算,所以编码的一个中心的思想还是很重要的,就是复杂的不见得最优,数学理论与实验科学还是有一些区别的.这也为继续的研究给出来一个启示,多想想简单而出新的想法,不要把一切都归给数学,警示.