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
在函数st