实验内容
- 熟悉H.264参考编码器JM software编码参数(本文采用JM18.6);
- 输出foreman_qcif序列第0、1帧指定宏块如下信息:
- 编码模式(mode);
- 运动矢量(MV);
- 量化参数(QP);
H264参考编码器参数设置
JM编码器的参数通过cfg文件设置,运行编码器时调用配置文件的命令(以Windows平台的vs项目为例)为:
-d [配置文件路径]
示例:
配置文件encoder.cfg的关键参数为:
InputFile = "foreman_part_qcif.yuv" # Input sequence
# 输入序列,为420采样的YUV文件
StartFrame = 0 # Start frame for encoding. (0-N)
# 起始帧号
FramesToBeEncoded = 3 # Number of frames to be coded
# 编码的帧数
FrameRate = 30.0 # Frame Rate per second (0.1-100.0)
SourceWidth = 176 # Source frame width
SourceHeight = 144 # Source frame height
# 输入图像尺寸
SourceResize = 0 # Resize source size for output
OutputWidth = 176 # Output frame width
OutputHeight = 144 # Output frame height
# 输出图像尺寸
TraceFile = "trace_enc.txt" # Trace file
ReconFile = "test_rec.yuv" # Recontruction YUV file
OutputFile = "test.264" # Bitstream
StatsFile = "stats.dat" # Coding statistics file
IntraPeriod = 0 # Period of I-pictures (0=only first)
# Intra帧周期(I帧,不表示一个GOP结束)
IDRPeriod = 0 # Period of IDR pictures (0=only first)
# Intra IDR帧周期(I帧,表示一个GOP结束)
# 该选项确定了一个GOP的长度
EnableIDRGOP = 1 # Support for IDR closed GOPs (0: disabled, 1: enabled)
# 使用对IDR封闭的GOP
QPISlice = 28 # Quant. param for I Slices (0-51)
QPPSlice = 28 # Quant. param for P Slices (0-51)
# 在不启用码率控制时,该选项指定
# I帧与P帧的QP
NumberBFrames = 1 # Number of B coded frames inserted (0=not used)
# B帧数,与IntraPeriod、IDRPeriod共同确定GOP结构
QPBSlice = 30 # Quant. param for B slices (0-51)
# B帧的QP
RateControlEnable = 0 # 0 Disable, 1 Enable
# 禁用码率控制
编码模式与运动预测信息输出
Trace文件分析
JM编码器自带trace功能,启用trace即可生成trace文件输出编码过程中的关键参数。启用条件如下:
- 在cfg配置文件中TraceFile指定输出的文件名;
- 在defines.h文件中将TRACE的宏定义为1;
#if defined _DEBUG # define TRACE 1 #else # define TRACE 0 #endif
运行程序对3帧的实验序列foreman_part_qcif.yuv进行编码,查看Trace文件获得第2行第3个宏块的mode、MV、QP等信息:
对于第一帧I帧
通过计算可知该宏块编号为13,在Trace文件中找到如下内容:
*********** Pic: 0 (I/P) MB: 13 Slice: 0 **********
@4456 mb_type (I_SLICE) ( 2, 1) = 9 ( 0)
@4456 Intra 4x4 mode = 7 (context: 0) ( 7)
@4458 Intra 4x4 mode = 7 (context: 1) ( 7)
@4460 Intra 4x4 mode = predicted (context: 2) ( -1)
@4461 Intra 4x4 mode = predicted (context: 3) ( -1)
@4463 Intra 4x4 mode = 5 (context: 4) ( 5)
@4467 Intra 4x4 mode = 7 (context: 5) ( 7)
@4468 Intra 4x4 mode = 7 (context: 6) ( 7)
@4470 Intra 4x4 mode = predicted (context: 7) ( -1)
@4471 Intra 4x4 mode = 2 (context: 8) ( 2)
@4478 Intra 4x4 mode = 7 (context: 9) ( 7)
@4480 Intra 4x4 mode = predicted (context: 10) ( -1)
@4481 Intra 4x4 mode = predicted (context: 11) ( -1)
@4482 Intra 4x4 mode = predicted (context: 12) ( -1)
@4483 Intra 4x4 mode = predicted (context: 13) ( -1)
@4484 Intra 4x4 mode = predicted (context: 14) ( -1)
@4485 Intra 4x4 mode = 6 (context: 15) ( 6)
@4490 intra_chroma_pred_mode ( 0)
@4490 CBP ( 2, 1) = 23 ( 23)
@4497 Delta QP ( 2, 1) = 0 ( 0)
@4497 Luma4x4 sng( 0) level = -1 run = 0 ( -1)
@4497 Luma4x4 sng( 1) level = 1 run = 1 ( 1)
@4497 Luma4x4 sng( 2) level = -1 run = 5 ( -1)
(此处省略熵编码相关信息)
@4671 DC Chroma 0: level = 1 run = 0 ( 1)
@4671 DC Chroma 1: level = 0 run = 1 ( 0)
(此处省略熵编码相关信息)
CABAC terminating bit = 0
程序中对于各个模式的定义如下:
// Available MB modes
enum {
PSKIP = 0,
BSKIP_DIRECT = 0,
P16x16 = 1,
P16x8 = 2,
P8x16 = 3,
SMB8x8 = 4,
SMB8x4 = 5,
SMB4x8 = 6,
SMB4x4 = 7,
P8x8 = 8,
I4MB = 9,
I16MB = 10,
IBLOCK = 11,
SI4MB = 12,
I8MB = 13,
IPCM = 14,
MAXMODE = 15
} MBModeTypes;
对各个帧内预测模式的规定如下:
// 4x4 intra prediction modes
enum {
VERT_PRED = 0,
HOR_PRED = 1,
DC_PRED = 2,
DIAG_DOWN_LEFT_PRED = 3,
DIAG_DOWN_RIGHT_PRED = 4,
VERT_RIGHT_PRED = 5,
HOR_DOWN_PRED = 6,
VERT_LEFT_PRED = 7,
HOR_UP_PRED = 8
} I4x4PredModes;
// 16x16 intra prediction modes
enum {
VERT_PRED_16 = 0,
HOR_PRED_16 = 1,
DC_PRED_16 = 2,
PLANE_16 = 3
} I16x16PredModes;
// 8x8 chroma intra prediction modes
enum {
DC_PRED_8 = 0,
HOR_PRED_8 = 1,
VERT_PRED_8 = 2,
PLANE_8 = 3
} I8x8PredModes;
H264标准中对帧内预测模式的定义如下:
以上信息中:
- mb_type字段表明该宏块使用的mode;
- Intra 4x4 mode字段表明对于4x4宏块使用的帧内预测mode选择;
- 其中predicted模式表明该块的预测模式由相邻块预测得到。
仔细观察发现非predicated的块输出结果的模式均比实际块的模式小1(参考上图码率分析软件结果和输出结果,如第一个块采用HOR_UP_PRED模式,结果应该为8但实际输出7),其原因是因为编码器中存在如下语句:
currMB->intra_pred_modes[4*b8+b4] = (char) mostProbableMode == best_ipmode ? -1 : (best_ipmode < mostProbableMode ? best_ipmode : best_ipmode-1)
当最优模式大于预测得到的模式时,模式的编号将被-1。
此外,在TRACE文件中并未输出QP,但该参数在后续的代码分析中可以看到。
实验采用的QP在之前的配置文件中设置为28。
对于第二帧P帧
该帧编码顺序为第2帧,显示顺序为第3帧!
Trace文件输出:
*********** Pic: 2 (I/P) MB: 13 Slice: 0 **********
@21949 mb_skip_flag ( 0)
CABAC terminating bit = 0
表明该MB采用P_SKIP编码。
对于第三帧B帧
该帧编码顺序为第3帧,显示顺序为第2帧!
Trace文件输出:
*********** Pic: 1 (I/P) MB: 13 Slice: 0 **********
@29408 mb_skip_flag ( 0)
CABAC terminating bit = 0
表明该MB采用BSKIP_DIRECT编码。
程序分析
存储macroblock关键信息的结构体中需要关注的点:
//! Macroblock
typedef struct macroblock_enc
{
// 省略其它信息
int mbAddrX; //!< current MB address
short mb_type; //!< current MB mode type
short mb_x; //!< current MB horizontal
short mb_y; //!< current MB vertical
short qp; //!< QP luma
short qpc[2]; //!< QP chroma
short mvd[2][BLOCK_MULTIPLE][BLOCK_MULTIPLE][2];
//!< indices correspond to [list][block_y][block_x][x,y]
char i16mode;
Info8x8 b8x8[4];
byte write_mb; // 为1表明该MB当前进行正式编码
byte is_intra_block;
int skip_flag;
char intra_pred_modes [MB_BLOCK_PARTITIONS];
char intra_pred_modes8x8[MB_BLOCK_PARTITIONS];
//!< four 8x8 blocks in a macroblock
short best_mode;
char best_i16mode;
// 省略其它信息
} Macroblock;
由于编码器执行mode decision需要进行预编码计算代价,在预编码过程中宏块的信息会不断改变。当write_mb==1 时表明该宏块的相关参数已经确定,为最终输出结果。
帧内编码相关
程序计算各个帧内编码相关代价后,best_mode将指示当前的模式(模式编号见上述定义),确定采用I16x16、I8x8或I4x4编码模式,其中:
- 对于I16x16,使用best_i16mode指示最佳编码模式;
- 对于I8x8,使用intra_pred_modes8x8指示16个4x4子块的编码模式,调用writeIntra8x8Modes()写入相关信息;
- 对于I4x4,使用intra_pred_modes指示16个4x4子块的编码模式,调用writeIntra4x4Modes()写入相关信息;
对于第2行第3个宏块,可在程序中观察到mode和QP,与Trace文件输出一致:
帧间编码相关(P帧为例)
程序计算各个帧内编码相关代价后,best_mode将指示当前16x16宏块的模式(模式编号见上述定义),如果采用了小于等于8x8的宏块划分,则b8x8将依次指示4个8x8子块的划分方式。
write_p_slice_motion_info_to_NAL()函数将对当前currMB的运动信息进行编码,其中的两个for循环会依次遍历每个4x4的子宏块:
//===== write forward motion vectors =====
for (j0=0; j0<4; j0+=step_v0)
{
for (i0=0; i0<4; i0+=step_h0)
{
k = j0 + (i0 >> 1);
if (currMB->b8x8[k].mode !=0 && (currMB->b8x8[k].pdir == 0 || currMB->b8x8[k].pdir == 2))//has forward vector
{
no_bits += writeMotionVector8x8 (currMB, i0, j0, i0 + step_h0, j0 + step_v0, motion[currMB->block_y + j0][currMB->block_x + i0].ref_idx[LIST_0],
LIST_0, currMB->b8x8[k].mode, currMB->b8x8[k].bipred);
}
}
}
其中:
- i0指示水平方向4x4块的起始坐标,i1指示水平4x4块的结束坐标;
- j0指示垂直方向4x4块的起始坐标,j1指示垂直4x4块的结束坐标;
比如,当前16x16宏块的最佳划分模式为P8x8,需要对每个8x8的块(包含4个4x4块,位置由i0 i1 j0 j1指示)调用writeMotionVector8x8()进一步确定每个4x4块运动矢量。
writeMotionVector8x8()函数将计算每个4x4小块的运动矢量,该函数中:
- cur_mv->mv_x、cur_mv->mv_y指示实际运动矢量;
- predMV.mv_x、predMV.mv_y指示预测运动矢量;
计算得到的运动矢量差值mvd被保存在currMB->mvd[list][88index][44index]当中。
以编码顺序第二帧第0个宏块为例:
Trace输出:
*********** Pic: 2 (I/P) MB: 0 Slice: 0 **********
@21136 mb_skip_flag ( 1)
@21137 mb_type (P_SLICE) ( 0, 0) = 8 ( 4)
@21140 8x8 mode/pdir( 0) = 7/0 ( 3)
@21144 8x8 mode/pdir( 1) = 4/0 ( 0)
@21145 8x8 mode/pdir( 2) = 6/0 ( 2)
@21147 8x8 mode/pdir( 3) = 4/0 ( 0)
@21148 mvd_l0 (0) = 0 (org_mv 0 pred_mv 0) ( 0)
@21149 mvd_l0 (1) = 0 (org_mv 0 pred_mv 0) ( 0)
@21150 mvd_l0 (0) = 0 (org_mv 0 pred_mv 0) ( 0)
@21151 mvd_l0 (1) = 0 (org_mv 0 pred_mv 0) ( 0)
@21151 mvd_l0 (0) = -1 (org_mv -1 pred_mv 0) ( -1)
@21154 mvd_l0 (1) = -3 (org_mv -3 pred_mv 0) ( -3)
@21160 mvd_l0 (0) = -3 (org_mv -3 pred_mv 0) ( -3)
@21166 mvd_l0 (1) = 0 (org_mv 0 pred_mv 0) ( 0)
@21167 mvd_l0 (0) = 0 (org_mv 0 pred_mv 0) ( 0)
@21168 mvd_l0 (1) = 0 (org_mv 0 pred_mv 0) ( 0)
@21169 mvd_l0 (0) = 1 (org_mv 0 pred_mv -1) ( 1)
@21172 mvd_l0 (1) = -1 (org_mv -1 pred_mv 0) ( -1)
@21175 mvd_l0 (0) = -3 (org_mv -3 pred_mv 0) ( -3)
@21180 mvd_l0 (1) = 1 (org_mv 1 pred_mv 0) ( 1)
@21183 mvd_l0 (0) = -1 (org_mv -4 pred_mv -3) ( -1)
@21186 mvd_l0 (1) = 1 (org_mv 1 pred_mv 0) ( 1)
@21189 CBP ( 0, 0) = 1 ( 1)
@21193 Delta QP ( 0, 0) = 0 ( 0)
@21194 Luma4x4 sng( 0) level = 0 run = 0 ( 0)
@21195 Luma4x4 sng( 0) level = 0 run = 0 ( 0)
@21196 Luma4x4 sng( 0) level = 1 run = 1 ( 1)
@21196 Luma4x4 sng( 1) level = -2 run = 4 ( -2)
@21196 Luma4x4 sng( 2) level = 1 run = 2 ( 1)
@21196 Luma4x4 sng( 3) level = 0 run = 1 ( 0)
@21216 Luma4x4 sng( 0) level = 0 run = 1 ( 0)
CABAC terminating bit = 0
程序调试:
通过对分析,该16x16宏块模式为P8x8:
- 第一个8x8块使用SMB4x4划分,四个子宏块运动矢量分别为:
- (0,0), (0,0), (-1,-3), (-3,0);
- 第二个8x8块使用SMB8x8划分,运动矢量为:
- (0,0);
- 第三个8x8块使用SMB4x8划分,二个子宏块运动矢量分别为:
- (0,-1), (-3,1);
- 第四个8x8块使用SMB8x8划分,运动矢量为:
- (-4,1);