1. jm8.6中所涉及的几项关于比特分布的地方:
序列参数集SPS:
parset.c文件中的GernateSPS...
具体宏块编码中的比特分布:
#if TRACE
snprintf(currSE->tracestring, TRACESTRING_SIZE, "Intra mode = %3d %d",currSE->value1,currSE->context);
#endif
还有很相关的一个是和比特计数相关的:
bitCount[BITS_COEFF_Y_MB]+=currSE->len;
rate += currSE->len;
int bits;
printf ("%04d(IDR)%8d %1d %2d %7.3f %7.3f %7.3f %7d %5d %3s %3d\n",
frame_no, stat->bit_ctr - stat->bit_ctr_n,0,
img->qp, snr->snr_y, snr->snr_u, snr->snr_v, tmp_time, me_time,
img->fld_flag ? "FLD" : "FRM", intras);
编码中涉及到的片类型(程序中共设定了5个)
typedef enum {
P_SLICE = 0,
B_SLICE,
I_SLICE,
SP_SLICE,
SI_SLICE
} SliceType;
这是在程序中自动进行比特使用统计的.
// Update the statistics
stat->bit_use_mb_type [img->type] += bitCount[BITS_MB_MODE];
stat->bit_use_coeffY [img->type] += bitCount[BITS_COEFF_Y_MB] ;
stat->tmp_bit_use_cbp [img->type] += bitCount[BITS_CBP_MB];
stat->bit_use_coeffC [img->type] += bitCount[BITS_COEFF_UV_MB];
stat->bit_use_delta_quant[img->type] += bitCount[BITS_DELTA_QUANT_MB];
++stat->mode_use[img->type][currMB->mb_type];
stat->bit_use_mode[img->type][currMB->mb_type]+= bitCount[BITS_INTER_MB];
JM中将编码模式转为一个值, 这个值再去利用熵编码进行编码.
Int MBType2Value (Macroblock* currMB)
{
static const int dir1offset[3] = { 1, 2, 3};
static const int dir2offset[3][3] = {{ 0, 4, 8}, // 1. block forward
{ 6, 2, 10}, // 1. block backward
{12, 14, 16}}; // 1. block bi-directional
int mbtype, pdir0, pdir1;
if (img->type!=B_SLICE)
{
if (currMB->mb_type==I4MB) return (img->type==I_SLICE ? 0 : 6);
else if (currMB->mb_type==I16MB) return (img->type==I_SLICE ? 0 : 6) + img->i16offset;
else if (currMB->mb_type==P8x8)
{
if (input->symbol_mode==UVLC && ZeroRef (currMB)) return 5;
else return 4;
}
else return currMB->mb_type;
}
函数中的ZeroRef用来判断当前的宏块中的每一个4x4块是否存在参考,如果参考帧都是0的话, 说明他们没有参考帧.(代码中是对宏块的每一个4x4块都存储一个运动矢量的.)
在函数writeMotionInfo2NAL中是将当前宏块的运动矢量进行熵编码, 这个函数调用了函数writeReferenceFrame来写参考帧号, 调用 函数int writeMotionVector8x8来写运动矢量(一个宏块分为4个8x8块, 调用4次函数writeMotionVector8x8), 在函数writeMotionVector8x8中要对4个4x4块计算mvd然后进行相应的熵编码.
编码图像的编号
for (img->number=0; img->number < input->no_frames; img->number++)
这个编号指的是I/P帧的编号.
定义在global.h中的全局变量frame_no是编码图像的原始编号(属于播放顺序不是编码顺序)
在配置文件中给出的量:
FramesToBeEncoded = 10
这指的是要进行编码的I/P帧的数量, 不包括B帧
编码的过程:
for循环, 利用img->number来控制循环次数:
(0)编码I帧, 判断是否要编码B帧, 如果配置文件允许编码B帧, 此时也不能编码B帧, 因为现在只编码了一帧, B帧是双向参考的, 所以需要已经被编码的帧数(其实就是img->number代表的含义)必须大于1才行.
(1)编码P帧, 此时判断是否编码B帧, 发现符合条件(配置文件中允许编码B帧,并且被编码帧数大于1), 进行编码B帧. 在配置文件中的NumberBFrames=2指明的是编码B帧的数量, 所以在这儿要循环NumberBFrames次. 相当于在这儿编码NumberBFrames个B帧.
(2)编码P帧, 同样编码NumberBFrames个B帧
(3)编码P帧, 同样编码NumberBFrames个B帧
(4)编码P帧, 同样编码NumberBFrames个B帧
......
(FramesToBeEncoded-1)编码P帧, 同样编码NumberBFrames个B帧
循环结束的条件是I/P帧数之和等于FramesToBeEncoded
这种情况下的编码序列是(FramesToBeEncoded=10, NumberBFrames=2,FrameSkip=2,IntraPeriod=0,IDRIntraEnable=0):
img->number | 0 | 1 | 2 | 3 | 4 | ... | 9 | ||||||||||
编码顺序 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 15 | ..... | 25 | 26 | 27 |
帧类型 | I | P | B | B | P | B | B | P | B | B | P | B | B | ....... | P | B | B |
播放顺序(原始顺序)frame_no | 0 | 3 | 1 | 2 | 6 | 4 | 5 | 9 | 7 | 8 | 12 | 10 | 11 | 27 | 25 | 26 | |
img->frame_num | 0 | 1 | 2 | 2 | 2 | 3 | 3 | 3 | 4 | 4 | 4 | 5 | 5 | ....... | 9 | 10 | 10 |
img->number | 0 | 1 | 1 | 1 | 2 | 2 | 2 | 3 | 3 | 3 | 4 | 4 | 4 | ...... | 9 | 9 | 9 |
在配置文件中的FrameSkip是来制定两个P帧之间的间隔, 不包括之间的B帧数(B帧数量是由NumberBFrames来决定的), 在代码中frame_no = start_tr_in_this_IGOP + IMG_NUMBER * (input->jumpd + 1);
两个P(I)帧之间的间隔(跨越的帧数)是要大于等于B帧的数量的, 即frameSkip>=NumberBFrames的.
这种情况下的编码序列是(FramesToBeEncoded=10, NumberBFrames=2,FrameSkip=3,IntraPeriod=0,IDRIntraEnable=0):
img->number | 0 | 1 | 2 | 3 | 4 | ... | 9 | ||||||||||
编码顺序 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 15 | ..... | 25 | 26 | 27 |
帧类型 | I | P | B | B | P | B | B | P | B | B | P | B | B | ....... | P | B | B |
播放顺序(原始顺序) | 0 | 4 | 1 | 2 | 8 | 5 | 6 | 12 | 9 | 10 | 16 | 13 | 14 | ... | 36 | 33 | 34 |
img->frame_num | 0 | 1 | 2 | 2 | 2 | 3 | 3 | 3 | 4 | 4 | 4 | 5 | 5 | ....... | 9 | 10 | 10 |
img->number | 0 | 1 | 1 | 1 | 2 | 2 | 2 | 3 | 3 | 3 | 4 | 4 | 4 | ...... | 9 | 9 | 9 |
对于配置文件中的IntraPeriod理解也很简单的, 因为在编码的过程中img->framenum控制编码的I/P帧的顺序,并且IntraPeriod也是针对P帧来说的(是和FramesToBeEncoded相关的). 在编码的过程中, 利用SetImgType函数来确定当前要进行编码的帧的类型(不包括B帧,只是判断当前帧是设定为I帧还是P帧): 首先第一帧肯定是I帧, 然后在编码下一帧(不考虑B帧)的时候要根据IntraPeriod这个量来决定当前帧是否要编为I帧,如果IntraPeriod不进行设定的话, 只有第一帧是I帧,然后后面的(FramesToBeEncoded-1)帧都是P帧, 如果对IntraPeriod进行了设定的话,需要利用img->framenum和IntraPeriod进行计算判断是否设置当前帧为I帧还是P帧
配置文件中的IDRIntraEnable是用来设定在进行I帧编码的时候是否使用IDR刷新的方式, 这个设置是要和IntraPeriod一同起作用的, 即如果IntraPeriod没有设定(值为0)的话,即使设定了IDRIntraEnable(值为1)的话, 也不会进行IDR刷新的. 在代码中可以看到:
input->intra_period && input->idr_enable
在函数static int CalculateFrameNumber()中计算frame_no, 因为在进行编码的过程中, 是依据frame_no来获取要进行编码的帧的原始数据的. img->frame_num
img->number是I/P编码循环的控制变量
从上面的表中, 可以看到img->frame_num和img->number在I/P帧对应的值是一样的, 只是在B 帧的地方不一样.
total_frame_buffer可以计算出该编码过程一共编码了多少帧, 这个变量在函数encode_one_frame 函数中完成了递增的.
编码的过程:
for(img->number=0; img->number < input->no_frames(FramesToBeEncoded); img->number++)
{
计算img->frame_num
SetImgType:根据intra_period设定当前要编码的帧是设定为I帧还是P帧
encode_one_frame():编码一帧(I/P)
encode_one_frame
{
在函数CalculateFrameNumber中计算当前要编码帧在原始视频序列中的序号(frame_no)
获取要编码帧数据
frame_picture{
code_a_picture{
while()
{encode_one_slice()};
}
}
}
判断接下来是否要编码B帧
if ((input->successive_Bframe != 0) && (IMG_NUMBER > 0))
{
设定编码的类型:img->type = B_SLICE;
计算frame_num(其实让frame_num++)
编码一定数量的B帧
for(img->b_frame_to_code=1; img->b_frame_to_code<=input->successive_Bframe(NumberBFrames); img
->b_frame_to_code++)
{
encode_one_frame():编码B帧
}
}
}