x264阅读记录-3
14. x264_macroblock_encode函数-1
这个函数主要根据已经选定的模式来对宏块残差进行编码。
(1)如果是P_SKIP模式,那么调用x264_macroblock_encode_pskip函数
在x264_macroblock_encode_pskip中先对亮度和色度进行运动补偿,调用的函数函数h->mc.mc_luma和 h->mc.mc_chroma。这两个是函数指针,根据需要对其进行初始化。一般常用的是函数mc_luma, 在mc.c文件中。在mc_luma中如果存在MV(x和y均不为0),那么调用pixel_avg;否则,调用mc_copy。pixel_avg位于mc.c文件中,是1/4搜索时需要临时插值函数。
dst[x] = ( src1[x] + src2[x] + 1 ) >> 1; //利用相邻半像素和两个像素取平均插值
mc_copy位于mc.c文件。
最后还要调用x264_macroblock_encode_skip函数,在这个函数中主要设置CBP值。
(2)如果是B_SKIP模式,先是调用x264_mb_mc函数完成运动补偿,然后调用x264_macroblock_encode_skip来进行CBP设置。
(3)接下来是帧内模式的编码,包括帧内I_16x16,帧内I_8x8和帧内I_4x4。在做每种模式的编码之前都要先计算预测帧。分别调用相应的预测函数 predict_16x16,predict_8x8(4个块)和predict_4x4(16个块)。
A. 对于I_16x16,调用x264_mb_encode_i16x16进行编码,如果是无损编码h->mb.b_lossless被置上,则以4x4为单位,进行无损压缩的量化和扫描。若是有损压缩方式,先调用 h-> dctf. sub16x16_dct计算宏块残差(p_src-p_dst)并进行4x4大小的DCT变换。 仍然,h-> dctf. sub16x16_dct是一个函数指针,其最常设置的值是sub16x16_dct,位于dct.c文件中。
在sub16x16_dct函数中,是对4个8x8块进行调用分别调用sub8x8_dct来完成残差计算和DCT变换的。而sub8x8_dct则是调用4个sub4x4_dct分别对4个4x4块进行残差DCT变换计算的。具体到sub4x4_dct,是调用pixel_sub_wxh来计算得到残差,然后再进行DCT变换,采用264中经典的4x4DCT变换的算法。
在DCT变换完成之后,对16个4x4块在for循环中进行处理:先拿出每个4x4块的DC系数(存放在一个数组dct4x4[0]中),然后对每个4x4块进行量化、扫描和反量化。分别由函数:x264_quant_4x4_trellis或quant_4x4、scan_zigzag_4x4和h->quantf.dequant_4x4完成。
接下来,对所有4x4块的在数组dct4x4 [0]中DCT系数进行:DCT变换、量化和扫描。分别由函数: h->dctf.dct4x4dc、quant_4x4_dc和scan_zigzag_4x4full完成。
然后,为了重建帧的需要,对DC系数进行反DCT变换、反量化(为什么不是先反量化后反变换,待解决)。分别由函数h->dctf.idct4x4dc和x264_mb_dequant_4x4_dc完成。对于直流系数,对每个4x4子块进行反DCT、反量化并加到预测宏块上得到重建的帧。由函数 h->dctf.add16x16_idct完成。
B. 对于I_8x8模式,将宏块分成4个8x8来分别进行。先调用predict_8x8函数来计算预测值,然后调用函数x264_mb_encode_i8x8进行编码。在该函数中,8x8DCT变换——h->dctf.sub8x8_dct8函数,量化—— x264_quant_8x8_trellis或quant_8x8函数、扫描——h->zigzagf.scan_8x8函数、反量化—— h->quantf.dequant_8x8函数和反DCT变换并加到预测宏块上——h->dctf.add8x8_idct8函数。
C.对16个4x4块分别进行。先h->predict_4x4[i_mode]进行帧内预测,得到预测帧。然后x264_mb_encode_i4x4编码。在x264_mb_encode_i4x4中,如果是无损压缩h->mb.b_lossless置位。调用sub_zigzag_4x4full计算残差,并扫描,返回。若是非无损编码,进行DCT、量化、Z字扫描、反量化、反IDCT并加到预测帧上。分别由函数:h->dctf.sub4x4_dct、 x264_quant_4x4_trellis或quant_4x4、scan_zigzag_4x4full、 h->quantf.dequant_4x4和h->dctf.add4x4_idct完成。
(4)如果是帧间模式编码,
A. 先调用x264_mb_mc进行运动补偿。根据宏块类型进行相应的运动补偿:使用列表0(h->mb.i_type == P_L0)的16x16预测模式,根据h->mb.i_partition的类型(D_16x16、D_16x8、D_8x16),分别调用 x264_mb_mc_0xywh函数进行前向宏块运动补偿。x264_mb_mc_0xywh分别对亮度和色度进行运动补偿: h->mc.mc_luma和h->mc.mc_chroma函数。如果宏块类型(h->mb.i_type)为P_8x8或B_8x8。对4个8x8块,分别调用x264_mb_mc_8x8进行运动补偿。 其根据字块类型(h->mb.i_sub_partition[i8])分别进行运动补偿。D_L0_8x8、D_L0_8x4、D_L0_4x8 和D_L0_4x4类型,调用x264_mb_mc_0xywh进行前向宏块运动补偿。D_L1_8x8、D_L1_8x4、D_L1_4x8和 D_L1_4x4类型,调用x264_mb_mc_1xywh进行后向宏块运动补偿。D_BI_8x8、D_BI_8x4、D_BI_4x8和 D_BI_4x4类型,分别调用x264_mb_mc_01xywh进行宏块双向运动补偿。D_DIRECT_8x8类型,调用x264_mb_mc_direct8x8 进行直接模式8*8块运动补偿。x264_mb_mc_direct8x8函数根据不同的条件调用x264_mb_mc_0xywh、x264_mb_mc_1xywh和x264_mb_mc_01xywh函数。如果宏块类型(h->mb.i_type)为B_SKIP或B_DIRECT。调用直接模式8*8块运动补偿函数x264_mb_mc_direct8x8。如果宏块类型(h->mb.i_type)为其它B帧模式。先初始化参考列表。根据子块类型(h->mb.i_partition) 为,D_16x16、D_16x8、D_8x16和前向、后项、双向参考类型,分别调用x264_mb_mc_0xywh、 x264_mb_mc_1xywh和x264_mb_mc_01xywh函数。
B. 运动补偿之后,进行编码。如果是无损编码(h->mb.b_lossless ==1),对16个4x4块,分别进行残差计算和z字扫描,由函数sub_zigzag_4x4full完成。如果要进行8x8子块编码( h->mb.b_transform_8x8),调用h->dctf.sub16x16_dct8对每个8x8块先计算残差,再DCT变换。然后对4个8x8块,分别调用x264_denoise_dct去噪、x264_quant_8x8_trellis或quant_8x8量化、字扫描。然后是反量化、将残差加到参考宏块上。由h->quantf.dequant_8x8和h->dctf.add8x8_idct8完成。然后对4个8x8块,分别调用x264_denoise_dct去噪、x264_quant_8x8_trellis或quant_8x8量化、scan_zigzag_8x8full Z字扫描。然后是反量化、将残差加到参考宏块上。由h->quantf.dequant_8x8和h->dctf.add8x8_idct8完成。
C. 如果不进行8x8子块编码。先用h->dctf.sub16x16_dct先算p_src-p_dst宏块残差,再对每个4x4模块进行 dct变换。然后对16个4x4块进行去噪——x264_denoise_dct、量化x264_quant_4x4_trellis或 quant_4x4、Z字扫描——scan_zigzag_4x4full。然后是反量化——h->quantf.dequant_4x4和 反DCT并加到参考帧上——h->dctf.add8x8_idct。
D. 亮度编码完成后,进行色度编码。x264_mb_encode_8x8_chroma函数。对Cb和Cr分量分别进行。
如果时无损编码,对4个4x4块进行残差计算、Z字扫描和DC系数计算。
非无损编码,用h->dctf.sub8x8_dct对每个4x4块进行残差计算和DCT转换。之后对每个4x4块,先取2x2DC系数,量 化——quant_4x4_chroma、Z字扫描——h->zigzagf.scan_4x4ac。对2x2DC系数进行DCT变换、量化和Z字 扫描,分别由函数h->dctf.dct2x2dc、quant_2x2_dc和scan_zigzag_2x2_dc。接着是反DCT变换和反量 化,h->dctf.idct2x2dc与x264_mb_dequant_2x2_dc函数。h->quantf.dequant_4x4 对4x4块反量化,h->dctf.add8x8_idct对每个4x4块反DCT变换并且加到色度预测帧上。
E. 亮度和色度编码完成后。计算亮度和色度模式和非零的个数,确定h->mb.i_cbp_chroma和h->mb.cbp的值。
15. x264_macroblock_encode函数-2
下面是该函数的调用情况,在上面对这个函数的分析中,已经对其中的大部分的函数都有了一个相对比较详细的介绍,
16. x264_macroblock_write_cabac函数
在宏块编码结束后,就要将编码生成的内容写到码流中。如果支持CABAC编码,就调用x264_macroblock_write_cabac。如果是CAVLC编码,就调用x264_macroblock_write_cavlc。
先调用函数x264_cabac_mb_type来将宏块类型写入码流,然后根据宏块的类型进行码流的写入操作:
A. 如果是I_PCM类型,那么直接对亮度和色度调用函数bs_write来将数据写入码流,然后返回。
B. 如果是帧内类型,如果采用了8x8DCT变换并且不是I_16x16,那么就调用x264_cabac_mb_transform_size来将变换的尺寸写入码流,如果是I_8x8或I_4x4,需要将预测模式写入码流,然后是将色度预测模式写入码流。
C. 如果是帧间16x16, 16x8 和 8x16 ,则根据相应的类型,调用x264_cabac_mb_ref将参考帧写入码流,调用x264_cabac_mb_mvd将运动矢量差写入码流
D. 如果是帧间P_8x8模式,首先将每一个子块的模式写入码流x264_cabac_mb_sub_p_partition,然后将每一个块用到的参考帧写入码流x264_cabac_mb_ref,最后在函数x264_cabac_mb8x8_mvd中将每一个8x8块的mvd写入码流。
E. 如果是B_8x8模式,同样写入子块模式x264_cabac_mb_sub_b_partition,然后前后参考帧写入码流x264_cabac_mb_ref,最后调用x264_cabac_mb8x8_mvd将L0和L1上的参考帧写入码流。
F. 如果是除B_DIRECT之外所有其他模式,将参考帧和mvd写入码流。
接下来将亮度和色度CBP写入码流:x264_cabac_mb_cbp_luma和x264_cabac_mb_cbp_chroma。
然后将qp_delta写入码流x264_cabac_mb_qp_delta, 同时根据宏块类型将残差系数写入码流block_residual_write_cabac。
17.x264_macroblock_write_cavlc函数
首先,根据帧类型计算i_mb_i_offset的值。依据宏块类型(i_mb_type)写相应的数据到流中:
A. 如果宏块类型是I_PCM,就把16x16个亮度值、2x8x8个色度值直接写到码流中。
B. 如果宏块类型是I_4x4或I_8x8,先写入是否是8x8模式。然后对16个4x4块和4个8x8块,先调用 x264_mb_predict_intra4x4_mode获取最可能的预测模式,然后比较当前的预测模式和最可能的预测模式,将判断结果写入码流中, 最后写入当前的预测模式值。
C. 如果宏块类型是I_16x16,将当前的预测模式写入码流。
D. 如果宏块类型是P_L0, 宏块子分区分别为D_16x16、D_16x8和D_8x16时,处理步骤相似,先将分区类型写入码流,然后预测运动向量(x264_mb_predict_mv)得到mvp,最后写入当前运动向量与mvp的差值(MVD)。
E. 如果宏块类型是P_8x8或B_8x8时,过程类似。先写入类型,接着写入4个子宏块类型,参考帧信息,调用cavlc_mb8x8_mvd计算 mvd。cavlc_mb8x8_mvd函数中根据不同的子分区类型(D_L0_8x8/D_L1_8x8/D_BI_8x8...)调用 cavlc_mb_mvd函数计算MVD并写入码流。cavlc_mb_mvd函数中也是计算mvp(x264_mb_predict_mv),算mvd 并写入码流中。
F. 如果宏块类型是非B_DIRECT——B帧的非直接模式,可能会双向参考。之后根据两个参考帧列表结合子分区类型(D_16x16、D_16x8、D_8x16),写入参考帧信息,计算mvp(x264_mb_predict_mv),计算mvd并写入码流中。
G. 如果宏块类型是B_DIRECT,就写个类型。
这样,在运动向量差写入完成后。写编码的块模式。写残差数据,分亮度残差数据和色度残差数据。
A. 如果类型是I_16x16,先计算qp-delta并写入码流,写亮度DC和AC系数(block_residual_write_cavlc)。
B. 如果不是I_16x16,且亮度和色度有一个不是零(h->mb.i_cbp_luma != 0 || h->mb.i_cbp_chroma != 0)。先计算qp-delta并写入码流,然后调用x264_macroblock_luma_write_cavlc进行亮度宏块编码。
对于色度残差数据的cavlc编码,先写入Cb,Cr的DC系数(block_residual_write_cavlc),后写入AC系数(block_residual_write_cavlc)。
这样就完成了熵编码。