注:此贴参考了众多文章,其中雷霄骅大神写的最为详细。这里就重点介绍下jm的lencode.c
参考博客: H.264官方软件JM源代码简单分析-编码器lencod
1.int main(int argc,char **argv)
argc为参数的个数,*argv为命令行参数。
2. init_time();
3. alloc_encoder(&p_Enc);
p_Enc为全局变量,里面有4个变量:*p_Enc->p_Inp,*p_Enc->p_Vid,*p_Enc->p_trace,*p_Enc->bufferSize.
该函数就是为p_Enc这个全局变量中的p_Inp,p_Vid,p_trace,bufferSize分配相应的内存空间。
4. Configure(p_Enc->p_Vid, p_Enc->p_Inp, argc, argv);
解析命令行参数,读取配置文件。根据配置文件初始化*p_Enc->p_Inp中的部分参数。p_Vid: pointer to the VideoParameters structure;
p_Inp: pointer to theInputParameters structure.
1> memset(&cfgparams, 0, sizeof (InputParameters));
为已开辟内存空间的cfgparams的前sizeof(InputParameters)个字节的值设为0。
2>InitParams(Map);
设置编码参数的初始值。
3>content = GetConfigFileContent(filename);
分配内存buf,读取文件filename(第一路视频,主配置文件),将文件中的内容读到buf中,返回buf。
4>ParseContent(p_Inp, Map, content, (int) strlen(content));
解析符号序列buf,将全局输入变量写入buf里,全局输入变量在configfile.h里定义。
5>content= GetConfigFileContent ( p_Inp->View1ConfigName );
如果p_Inp->num_of_views>1(双目编码,为2),要读取第二路的文件内容(第二路视频的配置文件),p_Inp->View1ConfigName里就是第二路视频的配置文件。
函数功能同3>。
6>ParseContent (p_Inp,MapView1, content, (int) strlen(content));
函数同4>。
7>PatchInp(p_Vid,p_Inp);
检查输入参数,以保证一致性。
5. init_encoder(p_Enc->p_Vid,p_Enc->p_Inp);
初始化编码器。读取输入视频文件,并初始化编码参数,如输入视频文件的格式、量化参数、预测模式、搜索范围、失真准则等等,完成序列级头信息的写入。调用之前,p_Enc->p_Inp的部分参数根据配置文件进行了相应的初始化(在configure函数中完成),但p_Enc->p_Vid中几乎是空白的。
1>level_check(p_Vid,p_Inp);
检查编码器的级别限制是否满足。
2>OpenFiles(&p_Inp->input_file1);
打开包含全部的视频序列的文件。Input_file1指的是第一路视频。
3>OpenFiles(&p_Inp->input_file2);
如果是双目视频编码,则还需打开包含第二路视频序列的文件。
4> p_Vid->output_format[0] =p_Inp->output;
set_storage_format(p_Vid,&(p_Inp->source),&(p_Vid->p_Dpb_layer[0]->storage_format));
第一路的输出格式,设置保存输出文件的格式。 第二路也类似。
5>init_qmatrix(p_Vid, p_Inp);
初始化Q矩阵的值(量化参数)。 分配内存,并且赋值。
6>init_qoffset(p_Vid);
初始化Q偏移矩阵的值。 分配内存。
InitOffsetParam(p_Vid->p_Quant, p_Inp);
初始化Q偏移矩阵的值。
7>init_poc(p_Vid);
初始化POC type0和1。(?)
8>generate_parameter_sets(p_Vid);
生成一个序列和图片参数集,并将它们保存在全局变量active_sps和active_pps中。sps--sequence parameter set; pps--picture parameter set.
(1)sps =AllocSPS();
为一个序列参数集(sps)分配内存。
(2)GenerateSequenceParameterSet(sps,p_Vid, 0);
生成序列参数集:从全局变量中提取信息,生成序列参数集结构。保存在sps中。
(3)AllocPPS();
为图片参数集分配内存。
(4)GeneratePictureParameterSet(p_Vid->PicParSet[0], sps, p_Vid, p_Inp, 0, 0, 0,p_Inp->cb_qp_index_offset, p_Inp->cr_qp_index_offset);
生成图片参数集:生成图片参数集结构。
(5)如果为双目视频,还有为p_Vid->sps[1]分配内存。
9>generate_encode_parameters(p_Vid);
生成编码参数。 生成编码参数的内存空间,分双路来分配,并且初始化。
10>set_level_indices(p_Vid);
根据level_idc来生成合适的指标来满足合适的level限制。
11>init_img(p_Vid);
用合适的参数来初始化图像结构。
(1)RandomIntraInit(p_Vid, p_Vid->PicWidthInMbs, p_Vid->FrameHeightInMbs,p_Inp->RandomIntraMBRefresh);
InitSEIMessages(p_Vid,p_Inp);
(2)initInput(p_Vid, &p_Inp->source, &p_Inp->output);
(3)AllocateFrameMemory(p_Vid,p_Inp, &p_Inp->source);
创建帧内存缓冲buffer。
p_Vid->buf;p_Vid->ibuf。只是分配了内存。
(4)create_context_memory(p_Vid, p_Inp);
创建上下文记忆表。分配内存并且初始化。
12>init_dpb(p_Vid,p_Vid->p_Dpb_layer[i]);
对解码图像buffer分配内存,并赋给合理的值。P_Dpb(decoded picture buffer)
13>init_out_buffer(p_Vid);
初始化输出buffer用来直接输出。
14>init_stats(p_Inp, p_Vid->p_Stats);
15>init_dstats(p_Vid->p_Dist);
16>init_global_buffers(p_Vid,p_Inp);
动态分配帧大小的内存,和全局的buffer有关,这个buffer是在global.h中定义的。
对帧编码图像、场编码图像分配内存,并且对输入的待编码图像初始化内存数据。
(1)Init_orig_buffers(p_Vid, &p_Vid->imgData);
对输入的原始图像p_Vid->imgData。。。buffers分配内存,并初始化。
memory_size +=get_mem2Dpel(&(imgData->frm_data[0]), p_Vid->height, p_Vid->width); 分配一个2维的内存序列array2D[dim0][dim1],dim0=p_Vid->height,dim1=p_Vid->width。 保存在imgData->frm_data[0][][],
imgData->frm_data[1][][],imgData->frm_data[2][][]里。——YUV420格式的。
memset(&(imgData->frm_data[k][0][0]),128, p_Vid->height_cr * p_Vid->width_cr * sizeof(imgpel)); 对imgData->frm_data[0][][],imgData->
frm_data[1][][],imgData->frm_data[2][][]赋值,都为128。
(2)Init_orig_buffers(p_Vid,&p_Vid->imgData0);
对输入的原始图像p_Vid->imgData(是第二路??)buffers分配内存,并初始化。
(3)p_Vid->PicPos[j].x p_Vid->PicPos[j].y——宏块在当前帧中的位置
(4)分配并设置运动估计的内存。 memory_size+= EPZSInit(p_Vid);
chroma_mc_setup(p_Vid);设置色度补偿的变量
(5)wpxInitWPXObject(p_Vid);
初始化WPRobject的结构。分配内存p_Vid->pWPX->wp_ref_list[LIST_0]和p_Vid->pWPX->wp_ref_list[LIST_1]。??参考帧??
(6)init_process_image(p_Vid, p_Inp );
如果是双目视频,init_orig_buffers(p_Vid, &p_Vid->tempData3);
(7)init_seq_structure(p_Vid, p_Inp, &memory_size );
初始序列结构。分配序列内存,设置帧buffer的长度,初始化预测结构(p_seq_struct->p_prd的内存大小,预测结构的长度,初始化)——init_pred_struct
( p_Vid, p_Inp, p_seq_struct, memory_size);
init_gop_struct ( p_Inp, p_seq_struct, 1,memory_size );// IDR GOPs
init_gop_struct ( p_Inp, p_seq_struct, 0,memory_size );// Intra GOPs 这两个也类似
填充帧结构——populate_frm_struct()
利用establish_random_access()来判断是否是IDR帧,
如果为IDR帧,则填充帧内编码/随机访问的预测结构元素,然后(帧内帧间设置,预测帧个数,最近的预测帧,选择预测结构,设置flag,对一个单独的帧单元设置参数,如果帧编码,对一幅单独的待编码图像设置参数付值完毕,更新帧指数)。
否则,检查是否帧内,是否SP帧。如果都不是,填充常规的预测结构元素。
填充帧预测MV结构——populate_frm_struct_mvc(p_Vid, p_Inp, p_seq_struct, start, end ); 双目时执行。 分别设置两路,每一路单独一个帧单元的参数。
17>wpxInitWPXPasses(p_Vid,p_Inp);
初始化WPR编码通道。 WP参考帧的数目,及算法。
18>init_motion_search_module(p_Vid, p_Inp);
初始化运动搜索。 创建保存搜索范围的序列p_Vid->spiral_search,p_Vid->
spiral_hpel_search,p_Vid->spiral_qpel_search,搜索矢量的字节数p_Vid->mvbits,参考帧的字节数p_Vid->refbits。 初始化序列中的元素,初始化MVbits:p_Vid->
mvbits[-i]=p_Vid->mvbits[i] =bits。初始化参考帧字节数p_Vid->refbits[i] =bits。 初始化各搜索模式的MV,然后赋值。 选择失真准则。 初始化快速全像素搜索。
19>information_init(p_Vid, p_Inp, p_Vid->p_Stats);
打印协议头文件。
20>start_sequence(p_Vid,p_Inp);
打开输出文件,生成合适的序列头文件。
21>setup_dpb_layer(p_Vid->p_Dpb_layer[i],p_Vid, p_Inp);
6. encode_sequence(p_Enc->p_Vid,p_Enc->p_Inp);
编码视频序列。这个是编码视频的主要的函数。
1>双目视频,设置待编码的视频帧数frames_to_code =p_Inp->no_frames<< 1;(帧数*2), p_frm帧预测结构, frm_struct_buffer帧结构缓冲。
2>在所有编码帧中循环。
1)for(curr_frame_to_code = 0; curr_frame_to_code < frames_to_code;curr_frame_to_code++) //双目,在两路所有的帧中循环
2)如果当前帧为第一路(第二路), 设置p_Vid->curr_frm_idx当前帧序号,p_Vid->p_curr_frm_struct当前帧的预测结构,p_Vid->number,p_Vid->view_id第几路。
3)set_dpb_layer_id(p_Vid, p_Vid->view_id); 设置当前帧是第几路
4)如果为第一路视频,则p_Vid->curr_frm_idx= p_Vid->number = curr_frame_to_code >> 1;
如果为第二路视频,则p_Vid->curr_frm_idx= p_Vid->number = (curr_frame_to_code - 1) >> 1; 并且更新QP值。
5)更新帧数目统计;
6)prepare_frame_params(p_Vid, p_Inp,curr_frame_to_code);
为当前帧准备参数。
设置编码层,sps和cps,所有编码中要用到的参数。设置宏块,选择使用的失真准则。
设置slice类型。
计算当前块的编码参数(frame_no, slice_type, nal_ref_idc, poc, resetframe_num)。
7)encode_one_frame(p_Vid, p_Inp); ——下面有详细解析
编码一帧视频。
8)一直在所有编码帧中循环,进行每一帧的编码。
7. free_encoder_memory(p_Enc->p_Vid, p_Enc->p_Inp);
释放编码器内存。
8. free_params(p_Enc->p_Inp);
释放Input structures。
9. free_encoder(p_Enc);
释放Encoder Structure。
encode_one_frame(p_Vid, p_Inp)函数解析
1、gettime(&start_time);
设置开始编码的计时。
2、put_buffer_frame(p_Vid);
设置指向帧结构的指针。
p_Vid->pCurImg = p_Vid->imgData.frm_data[0];
p_Vid->pImgOrg[0] =p_Vid->imgData.frm_data[0]; //亮度分量 Y
p_Vid->pImgOrg[1] =p_Vid->imgData.frm_data[1]; //色度分量 U
p_Vid->pImgOrg[2] =p_Vid->imgData.frm_data[2]; //色度分量 V
3、init_frame (p_Vid, p_Inp);
初始化一幅新的视频帧的参数。
p_Vid->current_mb_nr = 0;
p_Vid->current_slice_nr = 0;
p_Vid->p_Stats->bit_slice = 0;
p_Vid->mb_data_JV[j][i].slice_nr =-1; j=0-MAX_PLANE,i=0-p_Vid->
FrameSizeInMbs.为了保证在FMO下,正确编码。
init_fixed_qp(p_Vid, p_Inp); 对新的一帧视频,初始化量化参数。
init_mb_line_intra_up(p_Vid, p_Inp); 对新的一帧视频,初始化量化参数。
init_dec_ref_pic_marking_buffer(p_Vid); 初始化memorymanagement control operation。
p_Vid->direct_spatial_mv_pred_flag 设置直接MV空间预测的标志。
如果为第一路视频,设置一下三个参数:
p_Vid->DFDisableIdc ,p_Vid->DFAlphaC0Offset ,p_Vid->DFBetaOffset
如果为第二路视频,
p_Vid->DFDisableIdc ,p_Vid->DFAlphaC0Offset ,p_Vid->DFBetaOffset
另外设置p_Vid->AdaptiveRounding
4、read_input_data(p_Vid);
1>如果为第一路视频,
file_read =read_one_frame (p_Vid, &p_Inp->input_file1,p_Vid->frm_no_in_file, p_Inp->infile_header, &p_Inp->source,&p_Inp->output, p_Vid->imgData0.frm_data); —————— ### 从文件中读取一帧视频——ReadFrameConcatenated(在级联的原始文件中读取一帧视频,ReadData(从vfile里读取read_size个字节,并保存在缓冲区cur_buf里,并且文件的当前读写位置向后移,,读Y.U.V三个通道,最终读取的数据保存在缓冲区p_Vid->buf里))p_Vid->buf2img(pImage[0],p_Vid->buf, source->width[0], source->height[0], output->width[0],output->height[0], symbol_size_in_bytes, bit_scale)(将从文件读到缓冲区p_Vid->buf的内容转存到图像源结构pImage[0]中,利用memcpy函数将buf中的内容保存到pImage[0]起始的地址中,,pImage[0]指Y通道,另外pImage[1],pImage[2]为UV两个通道
p_Vid->imgData0.frm_data) ###
如果为第二路视频
file_read = read_one_frame(p_Vid, &p_Inp->input_file2, p_Vid->frm_no_in_file,p_Inp->infile_header, &p_Inp->source, &p_Inp->output,p_Vid->imgData0.frm_data); —————— ###执行过程同第一路###
2>pad_borders(....); 如果帧大小不是宏块大小的整数倍,就扩充帧使之为宏块大小的整数倍。
5、process_image(p_Vid, p_Inp);
利用memcpy函数将imgIn->frm_data[0][0]起始的数据保存到imgOut->
frm_data[0][0]起始的内存里,其实就是利用输入的数据对输出数据进行初始化。imgOut->frm_data[0][0],imgOut->frm_data[1][0],imgOut->frm_data[2][0]YUV三个通道的数据
6、pad_borders(....);
7、如果为第一路视频,p_Vid->field_pic_ptr =p_Vid->field_pic1;
如果为第二路视频,p_Vid->field_pic_ptr =p_Vid->field_pic2;
8、wpxInitWPXPasses(p_Vid, p_Inp);
初始化WPR编码通道。 初始化参考帧,算法。
9、根据是帧编码还是场编码来确定是执行下面两个函数的哪一个?(这里为帧编码)
perform_encode_field(p_Vid); or perform_encode_frame(p_Vid);
上面两个函数也很重要,编码的主要函数。以帧编码为例(下面有详细解析)。
10、write_frame_picture(p_Vid);
写编码帧。
11、如果为双目,则第二路视频会使用第一路视频的anchor_pic_lag的值。
12、free_slice_data(p_Vid);
释放所有片的内存和数据结构。
13、compute_distortion(p_Vid,&p_Vid->imgData);
计算失真。
14、store_coded_picture(p_Vid->p_Dpb_layer[p_Vid->view_id]);
保存编码图像。
15、gettime(&end_time);
结束编码统计时间。
16、第一帧编码完毕,开始输出编码第一帧的各种信息。
17、刷新输出数据。
18、update_bitcounter_stats(p_Vid); 更新码字统计器。
19、update_idr_order_stats(p_Vid);
至此,encode_one_frame结束。
帧编码——perform_encode_frame(p_Vid):
如果if((p_Vid->type == B_SLICE || p_Vid->type == P_SLICE ||p_Vid->type==I_SLICE) && p_Inp->RDPictureDecision),
I帧、P帧并且满足RDPictureDecision则执行
frame_picture_mp (p_Vid, p_Inp); //同样很重要的函数。这里执行这一个
否则,执行
frame_picture (p_Vid, p_Vid->frame_pic[0], &p_Vid->imgData,0);
p_Vid->p_frame_pic =p_Vid->frame_pic[0];//编码中重要的函数
frame_picture_mp (p_Vid, p_Inp);
p_Vid->type == I_SLICE,则执行frame_picture_mp_i_slice(p_Vid, p_Inp)
(
frame_picture——编码一帧图像(更新MV限制);( 初始化权重参数功能; 重设WP;准备并且分配内存给编码帧图像结构;code_a_picture(p_Vid, frame);编码一幅图像,主要编码函数;calc_picture_bits(frame)——计算当前picture的字节数; find_distortion (p_Vid, imgData);寻找YUV三个分量的distortion;)
store_coding_and_rc_info(p_Vid,&coding_info); (保存编码信息)
frame_picture_mp_exit(p_Vid,&coding_info);(重设编码状态信息)
Frame_picture_mp_i_slice()结束。
)
p_Vid->type == P_SLICE,则执行frame_picture_mp_p_slice(p_Vid, p_Inp)
(
frame_picture——编码一帧图像(更新MV限制);( 初始化权重参数功能; 重设WP;准备并且分配内存给编码帧图像结构;code_a_picture(p_Vid, frame);编码一幅图像,主要编码函数; calc_picture_bits(frame)——计算当前picture的字节数; find_distortion (p_Vid, imgData);寻找YUV三个分量的distortion;)
store_coding_and_rc_info(p_Vid,&coding_info); (保存编码信息)
frame_picture_mp_exit(p_Vid,&coding_info);(重设编码状态信息)
Frame_picture_mp_i_slice()结束。
)——frame_picture_mp()结束。
——frame_picture_mp()结束。
——perform_encode_frame(p_Vid)结束。
code_a_picture(.....)——code_a_plane(p_Vid, p_Inp)(
1.FmoInit();//FMO initialization:Generatesp_Vid->MapUnitToSliceGroupMap and p_Vid->MBAmap.
2.FmoStartPicture (p_Vid); //在每一幅图像开始初始化FMO 3.CalculateQuant4x4Param (p_Vid);CalculateOffset4x4Param(p_Vid); //在帧这一级上计算4*4的量化参数和量化偏移参数 如果8*8模式允许执行,则4.CalculateQuant8x8Param(p_Vid); CalculateOffset8x8Param(p_Vid); //和上一步含义类似
5.reset_pic_bin_count(p_Vid)
6.在所有宏块中循环,一帧中所有宏块未编码完,就一直循环
while (NumberOfCodedMBs < p_Vid->PicSizeInMbs)
encode_one_slice 编码一片
7.FmoSetLastMacroblockInSlice (p_Vid, p_Vid->current_mb_nr);
8.接着对下一片的处理 p_Vid->current_slice_nr++; p_Vid->p_Stats->bit_slice =0; 9.第6.步当前片组的所有片的循环完成 SliceGroup++——处理下一个片组; 10.FmoEndPicture (); 结束片模块。
11.DeblockFrame(p_Vid,p_Vid->enc_picture->imgY,p_Vid->enc_picture->imgUV);//去块滤波
12.code_a_plane()结束。
)——code_a_picture结束。
encode_one_slice()
[1].CurrentMbAddr = FmoGetFirstMacroblockInSlice(p_Vid, SliceGroupId);
得到当前片中的第一个宏块。
[2].init_slice (p_Vid, &currSlice, CurrentMbAddr);
初始化当前片的参数,为当前图像结构中的待编码片分配内存。
[3].init_bipred_enabled(p_Vid);
初始化双向预测时的参数。
[4].init_quant_4x4(currSlice);
初始化4*4量化矩阵。
[5].init_quant_8x8(currSlice);
初始化8*8量化矩阵。
[6].init_quant_Chroma(currSlice);
初始化色度值量化矩阵。
[7].currSlice->set_lagrangian_multipliers(currSlice);
设置拉格朗日乘子。
[8].SetCtxModelNumber (currSlice);
设置语义模型(??)。
[9].len = start_slice(currSlice, cur_stats);
生成片头文件,返回头文件需要的字节数。为当前的slice分配内存,并且自增slice(Picture->no_slices)。
[10].while (end_of_slice == FALSE) //在宏块之间循环
[11].start_macroblock(currSlice, &currMB, CurrentMbAddr, FALSE);
初始化当前的宏块。设置当前编码宏块的下一宏块的坐标。根据亮度的QP和位深度更新色度QP。去块滤波的参数。
[12].currSlice->encode_one_macroblock(currMB); //编码宏块主要的函数
[13].end_encode_one_macroblock(currMB);
编码完一个宏块后,更新宏块参数。
1>.update_qp_cbp(currMB);
更新QP参数值(在SKIP MBsor MBAFF情况下)。
2>.更新min_rdcost min_dcost min_rate
[14].write_macroblock (currMB, 1);
将所选的句法元素传输到NAL(网络自适应层)。
1>.初始并更新帧内宏块的个数。
2>.写一个宏块。currSlice->write_MB_layer(currMB, 0, &i);
写宏块类型se.value1 = MBType2Value(currMB);——将宏块类型转换为编码值。
currSlice->writeMB_typeInfo (currMB,&se, dataPart);——算术编码一个给定宏块的宏块类型信息。
currSlice->writeMB_transform_size(currMB,&se, dataPart);算术编码宏块的帧内预测大小的标志信息。
no_bits += writeIntraModes(currMB);编码帧内预测模式所用的字节数。
no_bits += write_chroma_intra_pred_mode(currMB);编码色度帧内预测模式所用的字节数。
*coeff_rate = write_CBP_and_Dquant(currMB);编码当前宏块的CBP、量化值。
*coeff_rate += currSlice->writeCoeff16x16(currMB, PLANE_Y);写16x16宏块的系数。
3>.设置一共的字节统计器。
4>.记录所有MB的总数目。
[15].end_macroblock (currMB, &end_of_slice,&recode_macroblock);
根据所用的片模式停止对当前宏块的处理。
[16].如果对当前宏块的最后处理已经完成,则
将去找当前宏块的下一个宏块的地址FmoGetNextMBNr (p_Vid, CurrentMbAddr);如果当前片已经处理完毕,则设置end_of_slice=TRUE;如果当前片还未完,则NumberOfCodedMBs++,next_macroblock (currMB)(更新下一个宏块的坐标和统计参数)。
如果对当前宏块的最后处理还未完成,则
找到并记录该宏块。
[17].第[10]步在当前片的所有宏块之间循环结束。
[18].终止当前片,返回已编码的宏块数。
encode_one_macroblock (currMB)函数解析
void encode_one_macroblock_high(Macroblock *currMB)。该函数针对帧内还是帧间宏块进行所有模式下的编码,得到率失真代价最小的一种模式,来作为该块的编码模式。
1、init_md_best(&md_best);
初始化最佳的模式信息(最佳块编码模式,最小代价,最小率失真代价,最小码率,最优模式等等)。
最优参考帧。。
2、intra |= RandomIntra (p_Vid, currMB->mbAddrX);
判断是帧内还是帧间?
3、init_enc_mb_params(currMB, &enc_mb, intra);
初始化当前宏块的编码参数(设置帧间搜索范围,各编码模式是否有效,设置并保存拉格朗日参数,设置默认的帧内色度预测模式)。
4、currSlice->store_coding_state (currMB,currSlice->p_RDO->cs_cm);
保存CABAC编码状态。
如果为帧间,执行下列步骤(5-8):// if (!intra)--帧间 else--帧内
5、如果SKIP模式有效,则
FindSkipModeMotionVector(currMB); 寻找SKIP模式下的运动矢量
6、计算16x16、16x8、8x16三种帧间预测模式下的运动估计。
for (mode = 1; mode < 4; mode++)
后面帧间运动估计(帧间预测)部分详细介绍。
7、如果P8x8模式有效,则执行P8x8模式的帧内预测。
ResetRD8x8Data(p_Vid,p_RDO->tr8x8); 初始化8x8块的rd_data数据块。
for (block = 0;block < 4; block++) 将一个宏块分为4个8x8的子宏块。
submacroblock_mode_decision(...);选择8x8下的最优模式决定。
在P8x8模式预测的几种预测方法中循环,得最优的预测,和该预测下的运动矢量等信息。
后面帧间运动估计(帧间预测)部分详细介绍。
8、执行8x8、8x4、4x8、4x4下的帧间预测模式下的运动估计。
后面帧间运动估计(帧间预测)部分详细介绍。
如果为帧内,执行下列步骤(9):// if (!intra)--帧间 else--帧内
9、min_cost =DISTBLK_MAX; 初始化min_cost。
帧内、帧间都要执行
10、set_chroma_pred_mode(currMB, enc_mb, mb_available,chroma_pred_mode_range);
设置色度预测模式。(1.计算当前宏块所有的帧内色度预测模式,得预测值[DC预测、垂直预测、水平预测、平面预测],UV两个通道;2.对当前宏块的色度预测使用RDcost方法来决定最优预测模式,当前宏块的UV两个通道的色度值分别保存在p_Vid->pImgOrg[1]和p_Vid->pImgOrg[2]里面;3、保存色度预测模式的范围chroma_pred_mode_range[0]和[1],应该是UV两个通道各自最佳的预测模式(??)。)
11、choosebest macroblock mode
for (currMB->c_ipred_mode =chroma_pred_mode_range[0];currMB->c_ipred_mode<=chroma_pred_mode_range[1];currMB->c_ipred_mode++)
根据色度预测决定的范围,在所有的亮度预测模式下循环,得亮度块编码的最优的编码模式和相应的RDcost等等信息。
12、for(index=0; index < max_index; index++)
在所有的宏块编码模式中循环,得最优的宏块编码模式。
Index=0-8,9中编码模式。这9种模式与index的对应关系为:
(1)index=0 mode=0PSKIP模式——Skip mode
(2)index=1 mode=1 P16*16 ——帧间16*16
(3)index=2 mode=2 P16*8 ——帧间16*8
(4)index=3 mode=3 P8*16 ——帧间8*16
(5)index=4 mode=8 P8*8 ——帧间8*8
(6)index=5 mode=10 I16MB ——帧内16*16
(7)index=6 mode=9 I4MB ——帧内4*4
(8)index=7 mode=13 I8MB ——帧内8*8
(9)index=8 mode=14 IPCM ——IPCM mode
对于帧间编码模式,index=0-8都这几种都执行。(详细介绍见下面帧间编码部分)
对于帧内编码模式,index=0-4时都不执行,
<1>.index=5时(帧内16x16),执行compute_mode_RD_cost(currMB,&enc_mb, (short) mode, &inter_skip);
compute_mode_RD_cost()——执行16*16帧内编码,并计算RDcost,与当前最小的RDcost比较,更新最小代价和最优编码模式等等信息。
(1).currSlice->set_modes_and_refs_for_blocks(currMB, (short) mode);
设置当前块的编码模式为该循环下的模式(即帧内16*16模式),并且设置相应模式下的宏块的参考块。
(2).bslice_16x16_termination_control(p_Inp,p_Vid->b8x8info, &ctr16x16, mode, bslice);
对B帧的P16x16模式,更新预测方向以便能够检查所有的预测方向。
(3).CheckPredictionParams(currMB,p_Vid->b8x8info, mode)
检查预测参数是否在有效的范围内,如果有效,则执行预测(下一步),无效就跳出。
(4).RDCost_for_macroblocks(currMB, enc_mb->lambda_mdfp, mode)
计算该循环下对应模式的RDcost。
[1].set_modes_and_refs_for_blocks();
设置该宏块的预测模式和该模式下宏块的参考帧。
[2].set_motion_vectors_mb();
设置当前块的运动矢量,如果为I帧,则执行空函数。
[3].mode_decision_for_I16x16_MB();
进行量化、编码等步骤得到编码系数,重建值,CBP等等。
[4].currSlice->chroma_residual_coding (currMB);
色度残差编码。UV两通道。(设置模式和参考帧,进行预测,计算残差,对残差进行整数DCT变换、DC系数Hadamard变换、DC系数量化、反Hadamard变换、AC系数量化、反DCT变换、重建)
[5].计算失真Distortion;
distortion = currSlice->getDistortion(currMB); //分别计算亮度和色度分量的失真。
[6].保存编码状态;
currSlice->store_coding_state(currMB, currSlice->p_RDO->cs_cm);
[7].计算码率rate;
rate = currSlice->write_MB_layer(currMB, 1, &coeff_rate);
[8].重设编码状态;
currSlice->reset_coding_state(currMB, currSlice->p_RDO->cs_cm);
[9].计算rdcost;
rdcost = distortion + (rate>0?(weight_cost(lambda, rate)): (weight_cost(lambda, 1)/2));
[10].更新最小Cost;
currMB->min_rdcost = rdcost;
currMB->min_dcost = distortion;
currMB->min_rate =weight_cost(lambda, coeff_rate);
currMB->min_bits = rate;
(5).store_macroblock_parameters(currMB, mode);
保存宏块参数。保存最优模式,预测值,重建值等等信息。
(6).terminate_trans = transform_termination_control(currMB, mode);
<2>.index=6时(帧内4x4),执行compute_mode_RD_cost(currMB,&enc_mb, (short) mode, &inter_skip);
compute_mode_RD_cost()——执行4x4帧内编码,并计算RDcost,与当前最小的RDcost比较,更新最小代价和最优编码模式等等信息。
(1).currSlice->set_modes_and_refs_for_blocks(currMB, (short) mode);
设置当前块的编码模式为该循环下的模式(即帧内4x4模式),并且设置相应模式下的宏块的参考块。
(2).bslice_16x16_termination_control(p_Inp,p_Vid->b8x8info, &ctr16x16, mode, bslice);
对B帧的P16x16模式,更新预测方向以便能够检查所有的预测方向。
(3).CheckPredictionParams(currMB,p_Vid->b8x8info, mode)
检查预测参数是否在有效的范围内,如果有效,则执行预测(下一步),无效就跳出。
(4).RDCost_for_macroblocks(currMB, enc_mb->lambda_mdfp, mode)
计算该循环下对应模式的RDcost。
[1].set_modes_and_refs_for_blocks();
设置该宏块的预测模式和该模式下宏块的参考帧。
[2].set_motion_vectors_mb();
设置当前块的运动矢量,如果为I帧,则执行空函数。
[3].mode_decision_for_I4x4_MB();
进行量化、编码等步骤得到编码系数,重建值,CBP等等。
[4].currSlice->chroma_residual_coding (currMB);
色度残差编码。UV两通道。(设置模式和参考帧,进行预测,计算残差,对残差进行整数DCT变换、DC系数Hadamard变换、DC系数量化、反Hadamard变换、AC系数量化、反DCT变换、重建)
[5].计算失真Distortion;
distortion = currSlice->getDistortion(currMB); //分别计算亮度和色度分量的失真。
[6].保存编码状态;
currSlice->store_coding_state(currMB, currSlice->p_RDO->cs_cm);
[7].计算码率rate;
rate = currSlice->write_MB_layer(currMB, 1, &coeff_rate);
[8].重设编码状态;
currSlice->reset_coding_state(currMB, currSlice->p_RDO->cs_cm);
[9].计算rdcost;
rdcost = distortion + (rate>0?(weight_cost(lambda, rate)): (weight_cost(lambda, 1)/2));
[10].更新最小Cost;
currMB->min_rdcost = rdcost;
currMB->min_dcost = distortion;
currMB->min_rate = weight_cost(lambda,coeff_rate);
currMB->min_bits = rate;
(6).store_macroblock_parameters(currMB, mode);
保存宏块参数。保存最优模式,预测值,重建值等等信息。
(7).terminate_trans = transform_termination_control(currMB, mode);
9、restore_nz_coeff(currMB);
保存非零系数的值。
10、设置最后的宏块参数。
(1).update_qp_cbp_tmp(currMB,p_RDO->cbp);
(2).currSlice->set_stored_mb_parameters (currMB);
设置已保存宏块的参数。 保存重建值,系数和CBP,宏块类型,变换大小的标志,参考帧,帧内预测模式,MV等等。
至此,encode_one_macroblock()函数执行完毕。
帧间运动估计(帧内预测)的具体执行过程
1、16x16、16x8、8x16三种模式——在一个循环里面
for (mode = 1; mode < 4; mode++)
mode=1——16x16
mode=2——16x8
mode=3——8x16
以下函数,这三种模式执行过程都一样,只是16x16的循环一遍(分为一个16x16块),另外两种模式循环执行两遍(分为两个8x8块)。
[1].update_lambda_costs(currMB, &enc_mb, lambda_mf); 更新lambda_mf;
[2].PartitionMotionSearch(currMB, mode, block, lambda_mf); 运动搜索函数
1).gettime( &me_time_start ); 开始运动估计计时。
2).设置搜索块的大小和搜索步长,参考帧数目,mv_block,m_cost。
3).init_mv_block(currMB, &mv_block, (short)blocktype, list, (char) ref, bx, by); 初始化MV块。 (更新mv块的位置信息,初始化参考帧,运动矢量WP参数)
4).设置全像素搜索使用的算法。
5).get_original_block(p_Vid, &mv_block); 得原始块数据。
6).在参考帧中循环,进行运动搜索。
for (list = 0; list < numlists; list++) //参考列表个数,如果为B帧,有两个参考列表;其余情况,一个参考帧列表 numlists=1 or 2
for (ref=0; ref < currSlice->listXsize[list+list_offset]; ref++)//在每一个参考列表里循环时,参考帧个数由currSlice->listXsize[list+list_offset]决定,由此参数决定每一帧进行运动搜索时的参考帧的个数。
若当前配置文件里NumberReferenceFrames = 2,则双目之间参考帧的关系为下图。每一路参考帧为其前面编码的前两帧,另外第二路视频还有多加一路来自第一路相应位置的参考帧。
m_cost = &p_Vid->motion_cost[blocktype][list][ref][block8x8];
7).get_search_range(&mv_block, p_Inp, ref,blocktype);
设置搜索范围。
8).在所有宏块间循环,得到与当前块m_cost最小的宏块。
*m_cost = BlockMotionSearch (currMB,&mv_block, bx<<2, by<<2, lambda_factor); (得当前块的领域,准备ME参数,得到初始宏块值,得MV预测器,由此得预测值,整像素搜索,计算全像素搜索下的min_mcost,亚像素搜索,计算亚像素搜索下的min_mcost,设置MV,返回最小cost)
9).设置运动矢量和参考帧。
set_me_parameters(motion,&currSlice->all_mv[list][ref][blocktype][by][bx], list, (char) ref,step_h, step_v, pic_block_y, pic_block_x);
10).释放存储MV的缓存。
11).gettime(&me_time_end); 结束ME的统计时间。由timediff()计算用于ME的时间。
[3].得cost和参考帧。
list_prediction_cost(currMB, LIST_0, block, mode, &enc_mb,bmcost, best.ref);
[4].assign_enc_picture_params(currMB, mode, &best, 2 * block);
保存最优mv预测的信息。
[5].set_block8x8_info(b8x8info, mode, block, &best);
设置参考帧和方向参数。
[6].设置参考帧和运动矢量(if (mode>0&&block==0))
currSlice->set_ref_and_motion_vectors(currMB, motion, &best, block);
[7].保存这三种模式下的最优模式,最小cost。
md_best.mode = (byte) mode;
md_best.cost = cost;
currMB->best_mode = (short) mode;
min_cost = cost;
[8].至此这三种帧间预测模式搜索完毕。
2、P8x8模式
[1].ResetRD8x8Data(p_Vid, p_RDO->tr8x8); 初始化8x8块的rd_data数据块。
[2].for (block =0; block < 4; block++) 将一个宏块分为4个8x8的子宏块。
submacroblock_mode_decision(...);选择8x8下的最优模式决定。
在P8x8模式预测的几种预测方法中循环,得最优的预测,和该预测下的运动矢量等信息。submacroblock_mode_decision_p_slice(...);
1).设置坐标,初始化8x8预测下该块的预测模式和信息。
2).保存编码状态。currSlice->store_coding_state(currMB,currSlice->p_RDO->cs_tmp);
3).在一个宏块的4个8x8块中循环,分别计算每一块的最优预测模式。
4).SubPartitionMotionSearch(currMB, mode, block, lambda_mf); 对子宏块的运动搜索。
<1>.gettime(&me_time_start ); 开始运动估计计时;
<2>.设置搜索块的大小和搜索步长,参考帧数目,mv_block,m_cost。
<3>.全像素搜索。
currMB->IntPelME =EPZS_motion_estimation;
初始化mv_block. init_mv_block(...);
得原始块。 get_original_block(p_Vid,&mv_block);
在参考帧中循环,寻找最佳的预测和匹配块。 执行过程大概同16x16、16x8、8x16的过程。
5).得到cost和参考帧,并保存。
6).计算rdcost。
rdcost =rdcost_for_8x8blocks (currMB, dataTr, &cnt_nonz, &curr_cbp_blk,enc_mb->lambda_mdfp, block, (short) mode, &best, min_rdcost);
7).保存最优模式和最小rdcost等值。
min_cost8x8 = *cost;
min_rdcost = rdcost;
*partition = best;
partition->mode = (char) mode;
currMB->b8x8[block].mode = (char)mode;
8).保存MV值,参考帧,重设编码状态。
9).至此一个8x8块的mv搜索完成。
set_subblock8x8_info(b8x8info,P8x8, block, p_RDO->tr8x8); 设置P8x8模式下的8x8块的模式信息。
3、8x8,8x4,4x8,4x4四种模式
[1].for (block =0; block < 4; block++) 将一个宏块分为4个8x8的子宏块。
submacroblock_mode_decision();选择8x8下的最优模式决定。
在8x8块里,根据这四种预测模式来预测,得最优的预测模式,和该预测下的运动矢量等信息。submacroblock_mode_decision_p_slice(...);
1).设置坐标,初始化8x8预测下该块的预测模式和信息。
2).保存编码状态。currSlice->store_coding_state(currMB,currSlice->p_RDO->cs_tmp);
3).对8x8块中的这4种预测模式,计算该块中的最优的预测模式。
4).SubPartitionMotionSearch(currMB, mode, block, lambda_mf); 对子宏块的运动搜索。
<1>.gettime(&me_time_start ); 开始运动估计计时;
<2>.设置搜索块的大小和搜索步长,参考帧数目,mv_block,m_cost。
<3>.全像素搜索。
currMB->IntPelME =EPZS_motion_estimation;
初始化mv_block. init_mv_block(...);
得原始块。 get_original_block(p_Vid,&mv_block);
在参考帧中循环,寻找最佳的预测和匹配块。 执行过程大概同16x16、16x8、8x16的过程。
5).得到cost和参考帧,运动矢量,并保存。
6).计算rdcost。
rdcost =rdcost_for_8x8blocks (currMB, dataTr, &cnt_nonz, &curr_cbp_blk,enc_mb->lambda_mdfp, block, (short) mode, &best, min_rdcost);
7).保存最优模式和最小rdcost等值。
min_cost8x8 = *cost;
min_rdcost = rdcost;
*partition = best;
partition->mode = (char) mode;
currMB->b8x8[block].mode = (char)mode;
8).保存MV值,参考帧,重设编码状态。
9).至此一个8x8块的mv搜索完成。
set_subblock8x8_info(b8x8info,P8x8, block, p_RDO->tr8x8); 设置P8x8模式下的8x8块的模式信息。
帧内预测模式的具体执行过程
1、I16MB——mode_decision_for_I16x16_MB()
[1].find_best_mode_I16x16_MB(currMB, lambda, DISTBLK_MAX)
return (int) currSlice->find_sad_16x16 (currMB);
找到16x16帧内预测模式中最优的预测模式。
1>.set_intrapred_16x16(currMB, PLANE_Y, &left_avail,&up_avail, &left_up_avail);
设置16x16预测模式,得预测值:
PredPel[i] = (imgpel)p_Vid->dc_pred_value;
2>.在16x16所有的预测模式中循环;
如果该宏块周围有相邻的宏块可以来预测,则生成16x16的帧内预测块: get_intrapred_16x16(currMB, PLANE_Y, k, left_avail, up_avail);
然后计算当前块与预测块的残差,保存在i16blk4x4[][][][]里,对残差进行Hadamard变换(?变换了两次??),计算变换后所有系数的绝对值之和——distI16x16(currMB,p_Vid->pCurImg, curr_mpr_16x16[k], best_intra_sad2);根据每种预测模式的current_intra_sad_2,选择最小的最为16x16预测模式下的最优预测模式。
[2].return currMB->residual_transform_quant_luma_16x16 (currMB,PLANE_Y);
选择出16x16下的最优预测模式后,对该模式下残差值进行整数 DCT变换、直流系数Hadamard变换、直流系数量化、直流系数反Hadamard变换、直流系数反量化、AC系数量化,AC系数IDCT变换。然后得到重建值sample_reconstruct(&img_enc[currMB->pix_y],curr_mpr_16x16[new_intra_mode], currSlice->tblk16x
16, 0,currMB->pix_x, 16, 16, max_imgpel_value, DQ_BITS)。
2、I4MB——mode_decision_for_I4x4_MB()
[1].for (*cost=0, b8=0; b8<4;b8++)
将16x16宏块分为4个8x8的子宏块,分别计算这4个8x8块的cost,然后相加,作为该16x16宏块的cost。
Mode_Decision_for_IntraSubMBlocks (currMB, b8, lambda, &cost8x8,non_zero);计算一个8x8块的最优预测模式。
[2].for (b4=0; b4<4; b4++)
将8x8块分为4个4x4的子宏块,分别计算这4个4x4块的cost,然后相加,作为该8x8块的cost。
mode_decision_for_I4x4_blocks (currMB, b8, b4, lambda, &cost4x4);
[3].mode_decision_for_I4x4_blocks_JM_High()
4x4块的模式决定。
求领域,得4x4块的预测值(9种预测方式下的预测值),在所有4x4的9种预测模式下循环,检查这9种模式中各个模式是否为有效模式,如果为有效模式,生成4x4的预测块,计算预测块与原始块的残差,然后计算该块的rdcost,使用的函数为:rdcost_for_4x4_intra_blocks();
整数DCT变换、量化、反量化、反DCT变换,重建,并且计算该4x4块的失真(distortion):
distortion += compute_SSE4x4(&p_Vid->pCurImg[pic_opix_y],&p_Vid->
enc_picture->imgY[pic_pix_y],pic_pix_x, pic_pix_x);(如果当前失真已经大于最小失真,则跳出,不再计算后面的部分),计算该4x4块相应预测模式下编码的码率currSlice->writeIntraPredMode(&se, dataPart);rate = se.len;加上编码亮度系数分量的码率rate += writeCoeff4x4_CABAC (currMB,PLANE_Y, b8, b4, 1)。计算rdcost,rdcost = distortion + weighted_cost(lambda,rate)。 重设编码状态,返回rdcost。
如果rdcost比min_cost小,则保存系数,保存重建值,保存该预测模式为最佳模式。
在9种循环模式下执行完后,设置帧内最佳模式预测,保存编码系数,保存重建值和预测值。
[4].*cost += cost4x4;
计算完4个4x4块的预测编码、模式决定后,计算总的cost。
[5].*cost += cost8x8;
计算完4个8x8块的预测编码、模式决定后,计算总的cost。
编码最优模式选择的具体执行过程
for (index=0; index <max_index; index++)
在所有的宏块编码模式中循环,得最优的宏块编码模式。
Index=0-8,9中编码模式。这9种模式与index的对应关系为:
(10)index=0 mode=0 PSKIP模式——Skip mode
(11)index=1 mode=1 P16*16 ——帧间16*16
(12)index=2 mode=2 P16*8 ——帧间16*8
(13)index=3 mode=3 P8*16 ——帧间8*16
(14)index=4 mode=8 P8*8 ——帧间8*8
(15)index=5 mode=10 I16MB ——帧内16*16
(16)index=6 mode=9 I4MB ——帧内4*4
(17)index=7 mode=13 I8MB ——帧内8*8
(18)index=8 mode=14 IPCM ——IPCM mode
一、帧间
帧间预测,上述所有编码模式都执行,在所有编码模式中寻找最优的模式。
<1>.index=0时(PSKIP),执行compute_mode_RD_cost(currMB,&enc_mb, (short) mode, &inter_skip);
compute_mode_RD_cost()——执行Skip mode模式下的编码,并计算RDcost,与当前最小的RDcost比较,更新最小代价和最优编码模式等等信息。
(1).currSlice->set_modes_and_refs_for_blocks(currMB, (short) mode);
设置当前块的编码模式为该循环下的模式(即Skip mode模式),并且设置相应模式下的宏块的参考块。
(2).bslice_16x16_termination_control(p_Inp,p_Vid->b8x8info, &ctr16x16, mode, bslice);
对B帧的P16x16模式,更新预测方向以便能够检查所有的预测方向。
(3).CheckPredictionParams(currMB,p_Vid->b8x8info, mode)
检查预测参数是否在有效的范围内,如果有效,则执行预测(下一步),无效就跳出。
(4).RDCost_for_macroblocks(currMB, enc_mb->lambda_mdfp, mode)
计算该循环下对应模式的RDcost。
[1].set_modes_and_refs_for_blocks();
设置该宏块的预测模式和该模式下宏块的参考帧。
[2].set_motion_vectors_mb();
设置当前块的运动矢量。
[3].currSlice->luma_residual_coding(currMB);
设置参考帧,亮度预测,由预测值得残差,保存预测值。
[4].currSlice->chroma_residual_coding (currMB);
色度残差编码。UV两通道。(设置模式和参考帧,进行预测,计算残差,对残差进行整数DCT变换、DC系数Hadamard变换、DC系数量化、反Hadamard变换、AC系数量化、反DCT变换、重建)
[5].计算失真Distortion;
distortion = currSlice->getDistortion(currMB); //分别计算亮度和色度分量的失真。
[6].保存编码状态;
currSlice->store_coding_state(currMB, currSlice->p_RDO->cs_cm);
[7].计算码率rate;
rate = currSlice->write_MB_layer(currMB, 1, &coeff_rate);
[8].重设编码状态;
currSlice->reset_coding_state(currMB, currSlice->p_RDO->cs_cm);
[9].计算rdcost;
rdcost = distortion + (rate>0?(weight_cost(lambda, rate)): (weight_cost(lambda, 1)/2));
[10].更新最小Cost;
currMB->min_rdcost = rdcost;
currMB->min_dcost = distortion;
currMB->min_rate =weight_cost(lambda, coeff_rate);
currMB->min_bits = rate;
(7).store_macroblock_parameters(currMB, mode);
保存宏块参数。保存最优模式,预测值,重建值等等信息。
(6).terminate_trans = transform_termination_control(currMB, mode);
<2>.index=1,,2,3时(P16x16,P16x8,P8x16),执行compute_mode_RD_cost(currMB,&enc_mb, (short) mode, &inter_skip);
compute_mode_RD_cost()——执行P16x16(P16x8,P8x16)模式下的编码,并计算RDcost,与当前最小的RDcost比较,更新最小代价和最优编码模式等等信息。
(1).currSlice->set_modes_and_refs_for_blocks(currMB, (short) mode);
设置当前块的编码模式为该循环下的模式(即P16x16(P16x8,P8x16)模式),并且设置相应模式下的宏块的参考块。
(2).bslice_16x16_termination_control(p_Inp,p_Vid->b8x8info, &ctr16x16, mode, bslice);
对B帧的P16x16模式,更新预测方向以便能够检查所有的预测方向。
(3).CheckPredictionParams(currMB,p_Vid->b8x8info, mode)
检查预测参数是否在有效的范围内,如果有效,则执行预测(下一步),无效就跳出。
(4).RDCost_for_macroblocks(currMB, enc_mb->lambda_mdfp, mode)
计算该循环下对应模式的RDcost。
[1].set_modes_and_refs_for_blocks();
设置该宏块的预测模式和该模式下宏块的参考帧。
[2].set_motion_vectors_mb();
设置当前块的运动矢量。
[3].currSlice->luma_residual_coding(currMB);
设置参考帧,亮度预测,由预测值得残差,对残差进行变换、量化、反量化、反变换、重建,保存重建值。
[4].currSlice->chroma_residual_coding (currMB);
色度残差编码。UV两通道。(设置模式和参考帧,进行预测,计算残差,对残差进行整数DCT变换、DC系数Hadamard变换、DC系数量化、反Hadamard变换、AC系数量化、反DCT变换、重建)
[5].计算失真Distortion;
distortion = currSlice->getDistortion(currMB); //分别计算亮度和色度分量的失真。
[6].保存编码状态;
currSlice->store_coding_state (currMB,currSlice->p_RDO->cs_cm);
[7].计算码率rate;
rate = currSlice->write_MB_layer(currMB, 1, &coeff_rate);
[8].重设编码状态;
currSlice->reset_coding_state(currMB, currSlice->p_RDO->cs_cm);
[9].计算rdcost;
rdcost = distortion + (rate>0? (weight_cost(lambda,rate)): (weight_cost(lambda, 1)/2));
[10].更新最小Cost;
currMB->min_rdcost = rdcost;
currMB->min_dcost = distortion;
currMB->min_rate =weight_cost(lambda, coeff_rate);
currMB->min_bits = rate;
(8).store_macroblock_parameters(currMB, mode);
保存宏块参数。保存最优模式,预测值,重建值等等信息。
(6).terminate_trans = transform_termination_control(currMB, mode);
<3>.index=1,2,3时(P16x16,P16x8,P8x16),执行compute_mode_RD_cost(currMB,&enc_mb, (short) mode, &inter_skip);