低比特率视听会议压缩编码标准H.263
转载请标明是引用于 http://blog.csdn.net/chenyujing1234
欢迎大家提出意见,一起讨论!
需要源码的请与我联系。
1、H.263编码技术
H.263是国际电联ITU-T的一个标准草案,是为低码流通信而设计的。但实际上这个标准可用在很宽的码流范围,
而非只用于低码流应用。H.263的编码算法与H.261一样,但做了一些改善和改变,以提高性能和纠错能力。
H.263标准在低码率下能够提供比H.261更好的图像效果,两者的区别有:
(1)H.263的运动补偿使用半象素精度,而H.261则用全象素精度和循环滤波;
(2)数据流层次结构的某些部分在H.263中是可选的,使得编解码可以配置成更低的数据率或更好的纠错能力;
(3)H.263包含四个可协商的选项以改善性能;
(4)H.263采用无限制的运动向量以及基于语法的算术编码;
(5)采用事先预测和与MPEG中的P-B帧一样的帧预测方法;
(6)H.263支持5种分辨率,即除了支持H.261中所支持的QCIF和CIF外,还支持SQCIF、4CIF和16CIF,SQCIF。
H.263是为了满足诸如视频会议、视频电子邮件和可视电话等视频通信业务发展的需要,并且克服公用电话交换网(PSTN)和
无线网络传输速率的瓶颈制定的视频编码标准。
网络应用最重要的目标之一就是进行多媒体通信。多媒体信息主要包括图像、声音和文本三大类,其中视频、音频等信号的信息量非常大,
因此对这些数据进行有效的表达和适当处理是非常重要的。
尽管H.263编码的主体框架仍然延续H.261标准编码的主要框架,但在许多方面对H.261进行了改进和扩充,在编码算法复杂度增加很少的情况下,
能提供更好的图像质量、更低的速率。
1、1 H.263编码结构说明
H.263定义了视频编码的数据结构,以便解码器能从接收到的码流中根据数据结构的定义进行解码重建图像。
这种数据结构是一种分级的数据结构,从大到小依次是图像帧、块组、宏块和块这4层结构。
以CIF图像帧(352X288)为例:
一帧图像可以分为12个块组,分别为GOB0-GOB11。
GOB0 | GOB1 |
GOB2 | GOB3 |
GOB4 | GOB5 |
GOB6 | GOB7 |
GOB8 | GOB9 |
GOB10 | GOB11 |
每一个GOB又分为33个宏块。
MB0 | MB1 | MB2 | MB3 | MB4 | MB5 | MB6 | MB7 | MB8 | MB9 | MB10 |
MB11 | MB12 | MB13 | MB14 | MB15 | MB16 | MB17 | MB18 | MB19 | MB20 | MB21 |
MB22 | MB23 | MB24 | MB25 | MB26 | MB27 | MB28 | MB29 | MB30 | MB31 | MB32 |
每一宏块由4个亮度块(Y0-Y3)和2个色差块(CrCb)组成,块的大小是8X8,块是DCT变换的最小单元。
综上的分解应该得到: 352X288=101376 12 X 33 X(8X8X2+16*16*4)=456192
为什么本该理论本该相等的地方却相差四倍。
视频编码器生成的编码是自我格式化的。该数据流可以和其他多种源信号混合传输。视频解码器执行与视频编码器相逆的过程。
如下图所示,H.263对视频输出比特率没有进行限制,输出比特率既可以是恒定的,也可以是变化的。输入的图像信号可以在多种频率下采样处理,这一
采样频率和传输网络的数字时钟是异步的。
H.263采用的是混合编码技术,即用帧间预测减少时域冗余,用变换编码减少残差信号的空域冗余,相应的解码器具有运用补偿能力。
H.263采用的是半像素精度,与H.261采用的全像素精度和一个环路滤波器的设计不同。
被发送的各种符号采用可变长编码技术。
当H.263标准不采用任何高级选项时,称为H.263的基本编码模式,或称为H.263的缺省编码模式。其信源编码器仍然采用可减少时间冗余的帧间编码预测和
可减少空间冗余的DCT变换编码相结合的混合编码方法。
H.263比H.261在以下几个方面做了改进,以便适应极低码率的传输要求:
(1) H.263中包含4个基本模式:非限制运行矢量模式、基于语法的算术编码模式、高级预测模式、PB帧模式。
(2) H.263编码器除了支持H.261中的图像格式CIF和QCIF之外,还增加了另外3种图像格式sub-QCIF、4CIF、16CIF,从而使H.263具有更广的应用范围。
对每种图像采用YUV4:2:0的图像格式。即图像各分量的采样分辩率如下:如果亮度分量按照dx个像素点,每帧dy行采样,则每个色度分量按每行dx/2个像素点,
每帧dy/2行采样。整理成表如下:
图像格式 |
每行亮度 图样像素(Y) |
每帧图像亮度行数(Y) |
每行色差取样像素(U、V) | 每帧图像色差行数(U、V) |
Sub-QCIF(亚1/4的公共中间格式) | 128 | 96 | 64 | 48 |
QCIF(1/4的公共中间格式) | 176 | 144 | 88 | 72 |
CIF(公共中间格式) | 352 | 288 | 176 | 144 |
4CIF(4倍公共中间格式) | 704 | 576 | 352 | 288 |
16QCIF(16倍公共中间格式) | 1408 | 1152 | 704 | 576 |
H.263解码器要求能对Sub-QCIF、QCIF格式的图像码流进行解码,但是不强求能对CIF、4CIF、14CIF图像模式的码流进行解码。同样,
H.263编码器应该能够对Sub-QCIF和QCIF中任一格式的图像进行编码,是否支持其它格式由用户自己决定。
视频编码器框图如下,主要包括预测、分块变换和量化几部分。
H.263是基于块的运动编码标准,它采用减少时间冗余的帧间编码技术和减少空间冗余的DCT变换技术,
以获得较高压缩比,它的重要部分有基于块的DCT变换、量化、运动估计与帧间预测及VLC熵编码。
(1)块组(Group Of Block, GOB)、宏块(macroblock)和块(block)。
每帧图像又可以分为多个块组,每个块组包含kX16行,k的取值依赖于图像的格式,(对Sub-QCIF、QCIF、CIF格式,k=1;对于4CIF,k=2;
对于16CIF格式,k=4)。每帧图像包含的块组个数分别是:
Sub-QCIF格式为6;
QCIF格式为9;
CIF、4CIF、16CIF格式为18;
块组的标号顺序是自上而下的,由0开始。
每个块组分成多个宏块,每个宏块中亮度信号Y的分辨率为16*16,色度信号的分辨率为8*8。
每个宏块包含4个亮度块和2个色度块。宏块的标号顺序是先自左向右,再自上而下。宏块数据按宏块顺序发送,块数据也按块顺序发送。
(2)预测。
帧间预测是指图像间的预测,它可以通过运行补偿加以实现。采用了帧间预测方法的编码是帧间编码,没有采用帧间预测编码的为帧内预测。
H.263校标允许在图像层选择编码模式,此时,采用帧内编码模式的图像称为I帧,采用帧间编码模式的图像称为P帧;
P帧图像中还可以在宏块层选择编码,即允许宏块采用帧间编码和帧内编码。
在PB模式下,B帧图像中的所有宏块都采用帧间编码方式,而且部分宏块可以采用双向预测技术,采用双向预测技术的宏块称为B宏块。
(3)运动补偿。
在编码标准的默认框架下,每个宏块使用一个运动矢量作运行补偿,在高级预测模式下,一个宏块可以使用1个或4个运动矢量做运行补偿。
如果启用了PB模式,则每个宏块还要增加一个偏差矢量,用以估计B宏块的运动矢量。
宏块的运动矢量采用了差分编码技术。差分编码值是当前宏块的运动矢量和预测因子之差,而预测因子取自3个候选预测因子中的中值。
3个候选预测因子指3个相临宏块的运行矢量。
(4)量化
量化是指用规定范围内的一个值表示值的一个范围。例如把实数转成最接近的一个整数即是一种量化。量化范围可以被精确地表示成一个整数码,
该整数码在解码过程中用来恢复被量化的那个值,实际值与量化值之间的差值称为量化噪声。
在某些场合,人类视觉系统对量化噪声不敏感,量化噪声可以很大,因此量化可以提高编码效率。
量化在整个视频序列编码中占据很重要的地位,因为是先将DCT变换后的系数矩阵进行量化,然后再对这个量化矩阵编码,
量化后的非零系数越少,编码效果越好,而这是整个编码方案性能良好的主要原因。
也就是说,宏块经过DCT变换后,形成一个大幅度系统集中在低频区域,而高频区域系数都比较小,量化后许多高频系数0, 这使传输码率降低,从而达到压缩目的。
H.263默认框架下,帧内编码块的第一个系数使用统一的量化器,量化步长为8。其他各系数可选择的量化器有31个,但一个宏块内的量化器要统一。
1、2 H.263编码应用程序设计及源代码详细分析
1、2、1 应用程序效果展示
应用程序采用单文档框架。
开始编码时先弹出第1帧的lum、Cb、Cr峰值信噪比
转化过程:
转化结束:
1、2、2 源代码分析
首先介绍程序中定义的重要结构体Struct类型的数据结构,
其中MotionVector表示运动矢量,PictImage包含一帧图像的数据,MB_structure包含一个宏块的数据,这些数据结构与H.263标准中的图像构成要素直观地对应,方便了视频数据的读入与处理;
还有一些与控制相关的数据结构,如Pict。
typedef struct pict { int TR; /* Time reference */ int source_format; int picture_coding_type;/*PCT_INTRA 帧内编码 PCT_INTER帧间编码*/ int unrestricted_mv_mode; int PB; int QUANT; int BQUANT; /* 在PB帧中的B-MBs 量化器*/ int TRB; /* B图像的时间参考消息 */ int bit_rate; int src_frame_rate; float target_frame_rate; int DQUANT; int MB; int seek_dist; /* 运动矢量搜索窗口 */ int use_gobsync; /* gob_sync 的标志 */ int MODB; /* B-frame mode */ float QP_mean; /* mean quantizer */ } Pict;
执行编码的主要函数是CodeYUV()。它默认按CIF格式进行编码。它主要完成以下几个工作:
(1) 初始化界面,包括进度条
(2) 打开文件、建立输出文件
(3) 初始化帧间编码参数,为接受YUV文件中的数据的空间、Pict空间、 Bits空间申请空间,
初始化Pict:包括 PB帧中的B-MBs 量化器、运动矢量搜索窗口(即搜索距离)、指明不插入扩展的同步、
图像编码为帧内编码、编码格式为CIF、帧内初始化因子(为8)
(4) 从文件中读取352*288*3/2个数据
(5) 调用FillImage从文件流中先读取一帧图像数据。
(6) 对第一帧数据进行帧内编码,比较原始图像与构建后的图像寻找到信噪比,并显示信噪比 。
(7) 对所有帧数据根据条件选择帧内编码或帧间编码进行编码,并把数据转化为H.263保存在文件中。
判断采用帧内编码或帧间编码的方法是: if(((frame_no-1)%Pbetween)==0) // 帧内编码, 否则采用帧间编码
Pbetween为相邻I帧之间插入的P帧个数,在编码属性对话框中设置(默认设置为19),
这样帧号frame_no为21 41 81 161 321 、、、、、时会采用帧内编码,其它帧采用帧间编码。
// YUV转H.263编码 void CMyView::CodeYUV() { //率控制 #ifndef OFFLINE_RATE_CONTROL float DelayBetweenFramesInSeconds; int CommBacklog; #else PictImage *stored_image = NULL; int start_rate_control; #endif CMyDoc* pDoc = GetDocument(); ASSERT_VALID(pDoc); //初始化 CProgressBar bar(_T("Encode Progress"), 50, MaxFrame); m_pImageData=new unsigned char[352*288*3/2]; // 因为假设是按CIF格式来编码,CIF一帧是352*288 CFile file; if((file.Open(m_szFilePathName,CFile::modeRead|CFile::shareDenyNone))==NULL) { AfxMessageBox("Can not open the yuv file!"); return; } PictImage *prev_image = NULL; PictImage *curr_image = NULL; PictImage *curr_recon = NULL; PictImage *prev_recon = NULL; int frame_no,first_frameskip=0; int start=1;//从第1帧到第MaxFrame帧 int orig_frameskip=1;//输入图像原始偏移 int frameskip=1;//非发送帧 long_vectors =0;//帧间编码的参数 mv_outside_frame=0; Pict *pic = (Pict *)malloc(sizeof(Pict)); Bits *bits = (Bits *)malloc(sizeof(Bits)); //率控制 Bits *total_bits = (Bits *)malloc(sizeof(Bits)); Bits *intra_bits = (Bits *)malloc(sizeof(Bits)); pic->BQUANT = DEF_BQUANT; pic->seek_dist = DEF_SEEK_DIST; pic->use_gobsync = DEF_INSERT_SYNC;//=0 pic->PB = 0; pic->TR = 0; pic->QP_mean = (float)0.0; pic->unrestricted_mv_mode = 0; pic->picture_coding_type =0; // PCT_INTRA; m_orgHeight=288; m_orgWidth=352; pic->source_format = SF_CIF; // 按CI格式F编码 switch (pic->source_format) { case (SF_SQCIF): fprintf(stdout, "Encoding format: SQCIF (128x96)\n"); pels = 128; lines = 96; break; case (SF_QCIF): fprintf(stdout, "Encoding format: QCIF (176x144)\n"); pels = 176; lines = 144; break; case (SF_CIF): fprintf(stdout, "Encoding format: CIF (352x288)\n"); pels = 352; lines = 288; break; case (SF_4CIF): fprintf(stdout, "Encoding format: 4CIF (704x576)\n"); pels = 704; lines = 576; break; case (SF_16CIF): fprintf(stdout, "Encoding format: 16CIF (1408x1152)\n"); pels = 1408; lines = 1152; break; default: fprintf(stderr,"Illegal coding format\n"); exit(-1); } cpels = pels/2; curr_recon = InitImage(pels*lines); //率控制 #ifndef OFFLINE_RATE_CONTROL // rate control variables pic->bit_rate = targetrate; pic->src_frame_rate = (int)(ref_frame_rate / orig_frameskip); DelayBetweenFramesInSeconds = (float) 1.0/(float)pic->src_frame_rate; InitializeRateControl(); #endif #ifdef OFFLINE_RATE_CONTROL start_rate_control = 0;//DEF_START_RATE_CONTROL; #else pic->target_frame_rate = (float)DEF_TARGET_FRAME_RATE; #endif //建立输出文件 CString outfilename=m_szFileName.Left(m_szFileName.GetLength()-4); CFileDialog dlg(FALSE,".263",outfilename,OFN_OVERWRITEPROMPT,"263 Files(*.263)|*.263|",NULL); if (dlg.DoModal()==IDOK) { bFlag=1; pDoc->UpdateAllViews(NULL); CString tempname; tempname=dlg.GetPathName(); outfilename=tempname.Left(tempname.GetLength()-4); if((streamfile.Open(tempname,CFile::modeWrite|CFile::modeCreate))==FALSE) AfxMessageBox("Can't create file!"); streamfile.SeekToBegin(); initbits (); CTime StartTime=CTime::GetCurrentTime(); CTimeSpan ElapsedTime; file.Read(m_pImageData,sizeof(BYTE)*352*288*3/2); pic->QUANT=QPI; curr_image = FillImage(m_pImageData); // 填充第一帧图像数据 curr_recon = CodeOneIntra(curr_image, QPI, bits, pic); bits->header += alignbits (); // 图像应该是byte对齐的 //率控制 AddBitsPicture(bits); memcpy(intra_bits,bits,sizeof(Bits)); ZeroBits(total_bits); //* number of seconds to encode * int chosen_frameskip=1;//jwp //* compute first frameskip * #ifdef OFFLINE_RATE_CONTROL float seconds = (MaxFrame - start + chosen_frameskip) * orig_frameskip/ ref_frame_rate; first_frameskip = chosen_frameskip; frameskip = chosen_frameskip; #else CommBacklog = intra_bits->total -(int)(DelayBetweenFramesInSeconds * pic->bit_rate); if (pic->bit_rate == 0) { frameskip = chosen_frameskip; } else { //* rate control is used * frameskip = 1; while ( (int)(DelayBetweenFramesInSeconds*pic->bit_rate) <= CommBacklog) { CommBacklog -= (int) ( DelayBetweenFramesInSeconds * pic->bit_rate ); frameskip += 1; } } first_frameskip = frameskip; #endif CString kk,m_Spsnr; if(ifPsnr) { if(psnrfile.Open(outfilename+".doc",CFile::modeWrite|CFile::modeCreate)==FALSE) MessageBox("Cannot create the output psnr file!", "Error",MB_ICONERROR | MB_OK); SeekPsnr(curr_image,curr_recon,352,288,psnrs); // 比较原始图像与构建后的图像,寻找到信噪比 frame_no=1; kk.Format("第%d帧的lum峰值信噪比为%6.4fdB\n",frame_no,psnrs[0]); m_Spsnr=kk; kk.Format("第%d帧的Cb峰值信噪比为%6.4fdB\n",frame_no,psnrs[1]); m_Spsnr+=kk; kk.Format("第%d帧的Cr峰值信噪比为%6.4fdB\n",frame_no,psnrs[2]); m_Spsnr+=kk; MessageBox(m_Spsnr); psnrfile.SeekToBegin(); psnrfile.Write(m_Spsnr,m_Spsnr.GetLength()); } //第二帧 pic->QUANT=QP; // 对所有帧进行编码 for(frame_no=start+first_frameskip;frame_no<=MaxFrame;frame_no+=frameskip) { file.Seek((frame_no-1)*352*288*3/2,SEEK_SET); file.Read(m_pImageData,sizeof(BYTE)*352*288*3/2); if(m_pImageData==NULL) return; pic->picture_coding_type =1; // PCT_INTER; if(!ratecontrol) pic->QUANT=QP; prev_image=curr_image; prev_recon=curr_recon; curr_image = FillImage(m_pImageData); pic->TR+=(((frameskip+(pic->PB?98:0))*orig_frameskip)%256); if(frameskip+(pic->PB?98:0)>256) MessageBox("Warning:frameskip>256"); streamfile.SeekToEnd();// Pbetween为相邻I帧之间插入的P帧个数 // 即frame_no = 21 41 81 161、、、时进行帧内编码 if(((frame_no-1)%Pbetween)==0) // 帧内编码 { pic->picture_coding_type =0; // PCT_INTRA; pic->QUANT=QPI; curr_recon = CodeOneIntra(curr_image, QPI, bits, pic); AddBitsPicture(bits); memcpy(intra_bits,bits,sizeof(Bits)); } else // 帧间编码 { CodeOneInter(prev_image,curr_image,prev_recon,curr_recon,pic->QUANT,frameskip,bits,pic); AddBitsPicture(bits); } bits->header += alignbits (); //* pictures shall be byte aligned * if(ifPsnr) { SeekPsnr(curr_image,curr_recon,352,288,psnrs); kk.Format("第%d帧的lum峰值信噪比为%6.4f\n",frame_no,psnrs[0]); m_Spsnr=kk; kk.Format("第%d帧的Cb峰值信噪比为%6.4f\n",frame_no,psnrs[1]); m_Spsnr+=kk; kk.Format("第%d帧的Cr峰值信噪比为%6.4f\n",frame_no,psnrs[2]); m_Spsnr+=kk; psnrfile.SeekToEnd(); psnrfile.Write(m_Spsnr,m_Spsnr.GetLength()); } //率控制 AddBits(total_bits, bits); #ifndef OFFLINE_RATE_CONTROL if (pic->bit_rate != 0 && pic->PB) CommBacklog -= (int) ( DelayBetweenFramesInSeconds*pic->bit_rate ) * pdist; if (pic->bit_rate != 0) { UpdateRateControl(bits->total); CommBacklog += bits->total; frameskip = 1; CommBacklog -= (int) (frameskip * DelayBetweenFramesInSeconds *pic->bit_rate); while ( (int)(DelayBetweenFramesInSeconds*pic->bit_rate) <= CommBacklog) { CommBacklog -= (int) ( DelayBetweenFramesInSeconds * pic->bit_rate ); frameskip += 1; } } #else //* Aim for the targetrate with a once per frame rate control scheme * if (targetrate != 0) if (frame_no - start > (MaxFrame - start) * start_rate_control/100.0) pic->QUANT = FrameUpdateQP(total_bits->total + intra_bits->total, bits->total / (pic->PB?2:1), (MaxFrame-frame_no) / chosen_frameskip , pic->QUANT, targetrate, seconds); frameskip = chosen_frameskip; #endif CString kkk; kkk.Format("编码率为%d,%d帧的total_bits->total 为%d,intra_bits->total 为 %d, bits->total 为%d,new quant is %d.", targetrate,frame_no,total_bits->total,intra_bits->total,bits->total,pic->QUANT); if(ifPsnr) { psnrfile.SeekToEnd(); psnrfile.Write(kkk,kkk.GetLength()); } //显示进度信息 CString str; str.Format("%d%% complete", frame_no*100/MaxFrame); bar.SetText(str); bar.StepIt(); PeekAndPump(); //调用函数实现消息的转发 }// 所有帧的编码结束 // pDoc->SetModifiedFlag(TRUE); file.Close(); if(ifPsnr) psnrfile.Close(); streamfile.Close(); delete prev_image; delete prev_recon; delete curr_image; curr_recon=NULL; // delete curr_recon; free(bits); free(pic); long hours,minutes,second;//计算所用的时间 ElapsedTime = CTime::GetCurrentTime() - StartTime; bFlag=0; hours = ElapsedTime.GetTotalHours(); minutes = ElapsedTime.GetTotalMinutes(); second = ElapsedTime.GetTotalSeconds(); second = second + 60*minutes + 3600*hours; csTimeElapse.Format("编码%d帧视频序列,耗时:%d秒!",frame_no-1,second); MessageBox(csTimeElapse); pDoc->UpdateAllViews(NULL); } else MessageBox("No file is saved.","系统提示",MB_OK); // delete m_pImageData; }
如上所说,H.263编码包括帧内编码和帧间编码。现在对它们分别进行说明.
1、2、2、1 帧内编码
首先对第一帧进行帧内编码,利用ReadImage()函数从原始视频文件中读入一帧数据,FillIIMage()使用上面读入的数据填充一个PictureImage结构。
文件流的分布如下图:
// 填充Y、Cb、Cr到PictImage结构体中,按 4:1:1 PictImage *FillImage(unsigned char *in) { PictImage *Pict; Pict = InitImage(pels*lines); memcpy(Pict->lum, in, pels*lines); memcpy(Pict->Cb, in + pels*lines, pels*lines/4); memcpy(Pict->Cr, in + pels*lines + pels*lines/4, pels*lines/4); // free(in); return(Pict); }
CodeOneIntra()函数专门对一帧图像进行帧内编码,并返回编码结果。它主要完成以下功能:
(1)申请宏块数据空间、申请存放帧内编码结果的空间、
(2)指定帧内初始化因子、计算图像帧的头大小
(3)把读入的数据分为16*16像素的宏块分别处理(重点)。
CodeOneIntra方法的代码文件为CodeOneIntra.cpp、CodeOneIntra.h
//帧内编码 PictImage *CodeOneIntra(PictImage *curr, int QP, Bits *bits, Pict *pic) { PictImage *recon; MB_Structure *data = (MB_Structure *)malloc(sizeof(MB_Structure)); int * qcoeff; int Mode =3;// 帧内模式MODE_INTRA; int CBP,COD; int i,j; recon = InitImage(pels*lines); ZeroBits(bits); pic->QUANT = QP; bits->header += CountBitsPicture(pic); // 计算图像帧的头大小 COD = 0; for ( j = 0; j < lines/MB_SIZE; j++) { /* 如果允许GOB同步,则输出GOB同步头 */ if (pic->use_gobsync && j != 0) bits->header += CountBitsSlice(j,QP); for ( i = 0; i < pels/MB_SIZE; i++) { pic->MB = i + j * (pels/MB_SIZE); // 宏块序号 bits->no_intra++; FillLumBlock(i*MB_SIZE, j*MB_SIZE, curr, data);//写亮度图像16*16 curr:图像数据 data:宏块树组 FillChromBlock(i*MB_SIZE, j*MB_SIZE, curr, data);//写色度图像2*8*8 qcoeff = MB_Encode(data, QP, Mode); // 变换并量化后数据到qcoeff=16*16+2*8*8个单元 CBP = FindCBP(qcoeff,Mode,64); // 从一帧的DCT变换与量化处理后系数结果中得到CBR值 if (!syntax_arith_coding) { CountBitsMB(Mode,COD,CBP,0,pic,bits); // 输出宏块层信息 CountBitsCoeff(qcoeff, Mode, CBP,bits,64); // 量化系数进行可变长编码,并把结果输出到输出流中 } MB_Decode(qcoeff, data, QP, Mode); // 负责对DCT系数进行反量化和反DCT,重新构建宏块 Clip(data); // 使 0<=data<=255 ReconImage(i,j,data,recon); // 将重建宏块data输出到整个图像recon中 free(qcoeff); } } pic->QP_mean = (float)QP; free(data); return recon; }
下面对CodeOneIntra()函数中几个重要的功能进行讲解:
对每一宏块,先用FillLumBlock()和FillChromBlock()填充一个MB-Structure结构中的成员变量:lum、Cr、 Cb。
把一帧图像的亮度分量Y作个示意图如下:
void FillLumBlock( int x, int y, PictImage *image, MB_Structure *data) { int n; register int m; for (n = 0; n < MB_SIZE; n++) for (m = 0; m < MB_SIZE; m++) data->lum[n][m] = (int)(*(image->lum + x+m + (y+n)*pels)); return; }
把一帧图像的Cr或Cb分量作个示意图如下:
void FillChromBlock(int x_curr, int y_curr, PictImage *image, MB_Structure *data) { int n; register int m; int x, y; x = x_curr>>1; // 因为Cr、Cb分量的数据分别只有Y的1/4。x/2 、 y/2后刚好是1/4 y = y_curr>>1; for (n = 0; n < (MB_SIZE>>1); n++) for (m = 0; m < (MB_SIZE>>1); m++) { data->Cr[n][m] = (int)(*(image->Cr +x+m + (y+n)*cpels)); data->Cb[n][m] = (int)(*(image->Cb +x+m + (y+n)*cpels)); } return; }
MB-Encode()函数对宏块进行处理,对数据进行DCT变换并对变换系数进行量化,最后返回指向数据块的指针
(即存放在qcoeff中的64*6空间中 前面64*4是存放亮度变换后的结果系数;中间64个是存Cb变换后的结果系数;后面64个是存Cr变换后的结果系数)
// 对亮度图像数据、色度图像数据进行DCT变换,量化处理 // 结果存放在qcoeff中的64*6中 int *MB_Encode(MB_Structure *mb_orig, int QP, int I) { int i, j, k, l, row, col; int fblock[64]; int coeff[384]; int *coeff_ind; int *qcoeff; int *qcoeff_ind; // 为变换系数分配空间 if ((qcoeff=(int *)malloc(sizeof(int)*384)) == 0) { fprintf(stderr,"mb_encode(): Couldn't allocate qcoeff.\n"); exit(-1); } coeff_ind = coeff; qcoeff_ind = qcoeff; // 把16X16的亮度图像分为四次8X8来进行DCT变换和量化处理 // 结果存放在qcoeff_ind中的64X4的空间内 for (k=0;k<16;k+=8) { for (l=0;l<16;l+=8) { for (i=k,row=0;row<64;i++,row+=8) { for (j=l,col=0;col<8;j++,col++) { fblock[row+col] = mb_orig->lum[i][j]; // 亮度图像数据 } } Dct(fblock,coeff_ind); // 对8X8的块做DCT变换, 做系数的Z字形搜索 Quant(coeff_ind,qcoeff_ind,QP,I); // 量化 coeff_ind += 64; // 为下一个8X8的块准备存系数的空间 qcoeff_ind += 64; } } // 把8*8的色度图像数据Cb进行DCT变换和量化处理 // 结果存放在qcoeff_ind中从64X4开始的64个空间内 for (i=0;i<8;i++) { for (j=0;j<8;j++) { *(fblock+i*8+j) = mb_orig->Cb[i][j]; } } Dct(fblock,coeff_ind); Quant(coeff_ind,qcoeff_ind,QP,I); coeff_ind += 64; qcoeff_ind += 64; // 把8*8的色度图像数据Cr进行DCT变换和量化处理 // 结果存放在qcoeff_ind中从64X4+64开始的64个空间内 for (i=0;i<8;i++) { for (j=0;j<8;j++) { *(fblock+i*8+j) = mb_orig->Cr[i][j]; } } Dct(fblock,coeff_ind); Quant(coeff_ind,qcoeff_ind,QP,I); return qcoeff; }
CountBitsCoeff()对量化系数进行可变长编码,并把结果输出到输出流中(即输出到.H263文件中去)。
// 量化系数进行可变长编码,并把结果输出到输出流中 // 输入:qcoeff,量化系数 // 输出:bits中的Y、C void CountBitsCoeff(int *qcoeff, int Mode, int CBP, Bits *bits, int ncoeffs) { int i; if (Mode == MODE_INTRA || Mode == MODE_INTRA_Q) { for (i = 0; i < 4; i++) { bits->Y += CodeCoeff(Mode, qcoeff,i,ncoeffs); } for (i = 4; i < 6; i++) { bits->C += CodeCoeff(Mode, qcoeff,i,ncoeffs); } } else { for (i = 0; i < 4; i++) { if ((i==0 && CBP&32) || (i==1 && CBP&16) || (i==2 && CBP&8) || (i==3 && CBP&4) || (i==4 && CBP&2) || (i==5 && CBP&1)) { bits->Y += CodeCoeff(Mode, qcoeff, i, ncoeffs); } } for (i = 4; i < 6; i++) { if ((i==0 && CBP&32) || (i==1 && CBP&16) || (i==2 && CBP&8) || (i==3 && CBP&4) || (i==4 && CBP&2) || (i==5 && CBP&1)) { bits->C += CodeCoeff(Mode, qcoeff, i, ncoeffs); } } } return; }
int CodeCoeff(int Mode, int *qcoeff, int block, int ncoeffs) { int j, bits; int prev_run, run, prev_level, level, first; int prev_s, s, length; run = bits = 0; first = 1; prev_run = prev_level = level = s = prev_s = 0; for (j = block*ncoeffs; j< (block + 1)*ncoeffs; j++) { /* Do this block's DC-coefficient first */ if (!(j%ncoeffs) && (Mode == MODE_INTRA || Mode == MODE_INTRA_Q)) { /* DC coeff */ if (qcoeff[block*ncoeffs] != 128) putbits(8,qcoeff[block*ncoeffs]); else putbits(8,255); bits += 8; } else { /* AC coeff */ s = 0; /* Increment run if coeff is zero */ if ((level = qcoeff[j]) == 0) { run++; } else { /* code run & level and count bits */ if (level < 0) { s = 1; level = -level; } if (!first) { /* Encode the previous coefficient */ if (prev_level < 13 && prev_run < 64) length = put_coeff (0,prev_run, prev_level); else length = 0; if (length == 0) { /* Escape coding */ if (prev_s == 1) {prev_level = (prev_level^0xff)+1;} putbits(7,3); /* Escape code */ putbits(1,0); putbits(6,prev_run); putbits(8,prev_level); bits += 22; } else { putbits(1,prev_s); bits += length + 1; } } prev_run = run; prev_s = s; prev_level = level; run = first = 0; } } } /* Encode the last coeff */ if (!first) { if (prev_level < 13 && prev_run < 64) length = put_coeff (1,prev_run, prev_level); else length = 0; if (length == 0) { /* Escape coding */ if (prev_s == 1) {prev_level = (prev_level^0xff)+1;} putbits (7,3); /* Escape code */ putbits(1,1); putbits(6,prev_run); putbits(8,prev_level); bits += 22; } else { putbits(1,prev_s); bits += length + 1; } } return bits; }
MB_Decode()负责对DCT系数进行反量化和反DCT,重新构建宏块。它的操作与MB_Encode()的操作相反。
// 负责对DCT系数进行反量化和反DCT,重新构建宏块 // 输入:qcoeff, DCT系数 // 输出:mb_recon, 存放反量化和反DCT,重新构建的宏块 int MB_Decode(int *qcoeff, MB_Structure *mb_recon, int QP, int I) { int i, j, k, l, row, col; int *iblock; int *qcoeff_ind; int *rcoeff, *rcoeff_ind; if ((iblock = (int *)malloc(sizeof(int)*64)) == NULL) { fprintf(stderr,"MB_Coder: Could not allocate space for iblock\n"); exit(-1); } if ((rcoeff = (int *)malloc(sizeof(int)*384)) == NULL) { fprintf(stderr,"MB_Coder: Could not allocate space for rcoeff\n"); exit(-1); } // 宏块图像数据清零 for (i = 0; i < 16; i++) for (j = 0; j < 16; j++) mb_recon->lum[j][i] = 0; for (i = 0; i < 8; i++) for (j = 0; j < 8; j++) { mb_recon->Cb[j][i] = 0; mb_recon->Cr[j][i] = 0; } qcoeff_ind = qcoeff; rcoeff_ind = rcoeff; // 对qcoeff中的64*4大小的亮度图像数据进行反量化和反DCT for (k=0;k<16;k+=8) { for (l=0;l<16;l+=8) { Dequant(qcoeff_ind,rcoeff_ind,QP,I); idct(rcoeff_ind,iblock); qcoeff_ind += 64; rcoeff_ind += 64; for (i=k,row=0;row<64;i++,row+=8) { for (j=l,col=0;col<8;j++,col++) { mb_recon->lum[i][j] = *(iblock+row+col); } } } } // 对qcoeff中的64大小的色度图像数据Cb进行反量化和反DCT Dequant(qcoeff_ind,rcoeff_ind,QP,I); idct(rcoeff_ind,iblock); qcoeff_ind += 64; rcoeff_ind += 64; for (i=0;i<8;i++) { for (j=0;j<8;j++) { mb_recon->Cb[i][j] = *(iblock+i*8+j); } } // 对qcoeff中的64大小的色度图像数据Cb进行反量化和反DCT Dequant(qcoeff_ind,rcoeff_ind,QP,I); idct(rcoeff_ind,iblock); for (i=0;i<8;i++) { for (j=0;j<8;j++) { mb_recon->Cr[i][j] = *(iblock+i*8+j); } } free(iblock); free(rcoeff); return 0; }
1、2、2、2 帧间编码
void CodeOneInter(PictImage *prev,PictImage *curr,PictImage *pr,PictImage *curr_recon,int QP, int frameskip, Bits *bits, Pict *pic)
函数负责当前帧编码,它是帧间编码的核心。 它主要完成以下功能:
(1)对前一帧的编码后还原的数据中的高度数据进行图像/2插值,这是为了在运动估计时进行半像素搜索,以提高运动矢量的计算精度;
(2)初始化运动向量结果数组,为每个编码块标记MV;超出图象边界的MV置为0;
(3)把读入的数据分为16*16像素的宏块分别处理(重点)。
/********************************************************************** * * Name: CodeOneInter * Description: 正常情况下编码一个image 当做为一个PB帧时编码两个image(CodeTwoPB and CodeOnePred merged) * * Input: pointer to image, prev_image, prev_recon, Q * * Returns: pointer to reconstructed image * Side effects: memory is allocated to recon image * ************************************************************************/ void CodeOneInter(PictImage *prev,PictImage *curr, PictImage *pr,PictImage *curr_recon, int QP, int frameskip, Bits *bits, Pict *pic) { ZeroBits(bits); // pi : 对前一帧的编码后还原的数据中的高度数据进行图像1/2插值后的结果 unsigned char *prev_ipol,*pi,*orig_lum; PictImage *prev_recon=NULL; MotionVector *MV[6][MBR+1][MBC+2]; MotionVector ZERO = {0,0,0,0,0}; int i,j,k; int newgob,Mode; int *qcoeff_P; int CBP, CBPB=0; MB_Structure *recon_data_P; MB_Structure *diff; /* 缓冲器控制变量 */ float QP_cumulative = (float)0.0; int abs_mb_num = 0, QuantChangePostponed = 0; int QP_new, QP_prev, dquant, QP_xmitted=QP; QP_new = QP_xmitted = QP_prev = QP; /* 复制旧QP */ /* 图象插值 */ if(!mv_outside_frame) { // 对前一帧的编码后还原的数据中的高度数据进行图像1/2插值 // 这是为了在运动估计时进行半像素搜索,以提高运动矢量的计算精度 pi = InterpolateImage(pr->lum, pels, lines); prev_ipol = pi; prev_recon = pr; orig_lum = prev->lum; } /* 为每个编码块标记MV 352/16 = 22 */ for (i = 1; i < (pels>>4)+1; i++) { for (k = 0; k < 6; k++) { MV[k][0][i] = (MotionVector *)malloc(sizeof(MotionVector)); // 标记MV MarkVec(MV[k][0][i]); } // 标记模式为帧间编码 MV[0][0][i]->Mode = MODE_INTRA; } /* 超出图象边界的MV置为0 288/16 = 18*/ for (i = 0; i < (lines>>4)+1; i++) { for (k = 0; k < 6; k++) { MV[k][i][0] = (MotionVector *)malloc(sizeof(MotionVector)); // 初始化运动向量帧 ZeroVec(MV[k][i][0]); MV[k][i][(pels>>4)+1] = (MotionVector *)malloc(sizeof(MotionVector)); ZeroVec(MV[k][i][(pels>>4)+1]); } MV[0][i][0]->Mode = MODE_INTRA; MV[0][i][(pels>>4)+1]->Mode = MODE_INTRA; } /* 整数和半象素运动估值 */ // 计算出前一帧的重建和当前帧图像之间的运动向量。 // 它也是按宏块处理的,MotionEstimatePicture对当前帧的一个宏块算出运动矢量, // 再填充到前面定义的MV数组中 MotionEstimatePicture(curr->lum, prev_recon->lum, prev_ipol, pic->seek_dist, MV, pic->use_gobsync); #ifndef OFFLINE_RATE_CONTROL if (pic->bit_rate != 0) { /* 初始化码率控制 */ QP_new = InitializeQuantizer(PCT_INTER, (float)pic->bit_rate, (pic->PB ? pic->target_frame_rate/2 : pic->target_frame_rate), pic->QP_mean); QP_xmitted = QP_prev = QP_new; } else { QP_new = QP_xmitted = QP_prev = QP; /* 复制旧 QP */ } #else QP_new = QP_xmitted = QP_prev = QP; /* 复制旧 QP */ #endif dquant = 0; for ( j = 0; j < lines/MB_SIZE; j++) { #ifndef OFFLINE_RATE_CONTROL if (pic->bit_rate != 0) { AddBitsPicture(bits); /* 更新QP */ QP_new = UpdateQuantizer(abs_mb_num, pic->QP_mean, PCT_INTER, (float)pic->bit_rate, pels/MB_SIZE, lines/MB_SIZE, bits->total); } #endif newgob = 0; if (j == 0) { pic->QUANT = QP; bits->header += CountBitsPicture(pic);//计算图象层码字 } else if (pic->use_gobsync && j%pic->use_gobsync == 0) { bits->header += CountBitsSlice(j,QP); //输出GOB同步头 newgob = 1; } for ( i = 0; i < pels/MB_SIZE; i++) { /* 更新dquant */ dquant = QP_new - QP_prev; if (dquant != 0 && i != 0 && MV[0][j+1][i+1]->Mode == MODE_INTER4V) { dquant = 0; QP_xmitted = QP_prev; QuantChangePostponed = 1; } else { QP_xmitted = QP_new; QuantChangePostponed = 0; } if (dquant > 2) { dquant = 2; QP_xmitted = QP_prev + dquant;} if (dquant < -2) { dquant = -2; QP_xmitted = QP_prev + dquant;} pic->DQUANT = dquant; /* 当dquant != 0,修改宏块类型 (例如 MODE_INTER -> MODE_INTER_Q) */ Mode = ModifyMode(MV[0][j+1][i+1]->Mode,pic->DQUANT); MV[0][j+1][i+1]->Mode = Mode; pic->MB = i + j * (pels/MB_SIZE); if (Mode == MODE_INTER || Mode == MODE_INTER_Q || Mode==MODE_INTER4V) { /* 预测P-宏块 */ diff = Predict_P(curr,prev_recon,prev_ipol, i*MB_SIZE,j*MB_SIZE,MV,pic->PB); } else { diff = (MB_Structure *)malloc(sizeof(MB_Structure)); FillLumBlock(i*MB_SIZE, j*MB_SIZE, curr, diff);//写亮度图像 curr:图像数据 diff:宏块树组 FillChromBlock(i*MB_SIZE, j*MB_SIZE, curr, diff);//写色度图像 } /* P或INTRA宏块 */ qcoeff_P = MB_Encode(diff, QP_xmitted, Mode); //对宏块数据(P块为残差数据)进行DCT变换量化 CBP = FindCBP(qcoeff_P, Mode, 64); if (CBP == 0 && (Mode == MODE_INTER || Mode == MODE_INTER_Q)) ZeroMBlock(diff); //宏块数据设为0 else MB_Decode(qcoeff_P, diff, QP_xmitted, Mode);//反变换 recon_data_P = MB_Recon_P(prev_recon, prev_ipol,diff,i*MB_SIZE,j*MB_SIZE,MV,pic->PB);//重建P图象 Clip(recon_data_P); //使 0<=recon_data_P<=255 free(diff); if(pic->PB==0) ZeroVec(MV[5][j+1][i+1]); //PB帧矢量差置为0 if ((CBP==0) && (CBPB==0) && (EqualVec(MV[0][j+1][i+1],&ZERO)) && (EqualVec(MV[5][j+1][i+1],&ZERO)) && (Mode == MODE_INTER || Mode == MODE_INTER_Q)) { /* 当 CBP 和 CBPB 为0, 16x16 运动矢量为0,PB矢量差为0, 并且编码模式为MODE_INTER或MODE_INTER_Q,跳过该宏块编码*/ if (!syntax_arith_coding) CountBitsMB(Mode,1,CBP,CBPB,pic,bits);//输出宏块层信息 } else { /* 正常编码宏块 */ if (!syntax_arith_coding) { /* VLC */ CountBitsMB(Mode,0,CBP,CBPB,pic,bits); if (Mode == MODE_INTER || Mode == MODE_INTER_Q) { bits->no_inter++; CountBitsVectors(MV, bits, i, j, Mode, newgob, pic);//输出运动矢量数据 } else if (Mode == MODE_INTER4V) { bits->no_inter4v++; CountBitsVectors(MV, bits, i, j, Mode, newgob, pic); } else { /* MODE_INTRA 或 MODE_INTRA_Q */ bits->no_intra++; if (pic->PB) CountBitsVectors(MV, bits, i, j, Mode, newgob, pic); } if (CBP || Mode == MODE_INTRA || Mode == MODE_INTRA_Q) CountBitsCoeff(qcoeff_P, Mode, CBP, bits, 64);//输出系数 } // end VLC QP_prev = QP_xmitted; }//end Normal MB abs_mb_num++; QP_cumulative += QP_xmitted; ReconImage(i,j,recon_data_P,curr_recon);//重建图象 free(recon_data_P); free(qcoeff_P); }//end for i }//end for j pic->QP_mean = QP_cumulative/(float)abs_mb_num; /* 释放内存 */ free(pi); for (j = 0; j < (lines>>4)+1; j++) for (i = 0; i < (pels>>4)+2; i++) for (k = 0; k < 6; k++) free(MV[k][j][i]); return; }
细心的朋友可能看出帧内编码与帧间编码在对每个宏块的处理上的区别了:
(1)在帧内编码的第一步帧内编码直接把宏块数据复制到data,所以变换并量化的数据源是宏块源数据;
而帧间编码却对宏块数据预测P-宏块,所以变换并量化的数据源是宏块数据预测的结果diff;
(2)在帧内编码时对DCT系数进行反量化和反DCT、重新构建宏块 后是直接将将重建宏块data输出到整个图像recon中;
而帧间编码却对DCT系数进行反量化和反DCT、重新构建宏块 后对反变换的结果 重建P图象,然后再将重建结果输出。
下面对帧间编码的CodeOneIntra()函数中几个重要的功能进行讲解:
1、2、2、2、1 InterpolateImage对数据image进行插值
// 对数据image进行插值. // 这是为了在运动估计时进行半像素搜索,以提高运动矢量的计算精度 unsigned char *InterpolateImage(unsigned char *image, int width, int height) { unsigned char *ipol_image, *ii, *oo; int i,j; // 申请返回的空间 ipol_image = (unsigned char *)malloc(sizeof(char)*width*height*4); ii = ipol_image; oo = image; /* 主图像 */ for (j = 0; j < height-1; j++) { for (i = 0; i < width-1; i++) { *(ii + (i<<1)) = *(oo + i); *(ii + (i<<1)+1) = (unsigned char)((*(oo + i) + *(oo + i + 1) + 1)>>1); *(ii + (i<<1)+(width<<1)) = (unsigned char)((*(oo + i) + *(oo + i + width) + 1)>>1); *(ii + (i<<1)+1+(width<<1)) = (unsigned char)((*(oo+i) + *(oo+i+1) + *(oo+i+width) + *(oo+i+1+width) + 2)>>2); } /* 每行的最后一个像素 */ *(ii+ (width<<1) - 2) = *(oo + width - 1); *(ii+ (width<<1) - 1) = *(oo + width - 1); *(ii+ (width<<1)+ (width<<1)-2) = (unsigned char)((*(oo+width-1)+*(oo+width+width-1)+1)>>1); *(ii+ (width<<1)+ (width<<1)-1) = (unsigned char)((*(oo+width-1)+*(oo+width+width-1)+1)>>1); ii += (width<<2); oo += width; } /*最后的行 */ for (i = 0; i < width-1; i++) { *(ii+ (i<<1)) = *(oo + i); *(ii+ (i<<1)+1) = (unsigned char)((*(oo + i) + *(oo + i + 1) + 1)>>1); *(ii+ (width<<1)+ (i<<1)) = *(oo + i); *(ii+ (width<<1)+ (i<<1)+1) = (unsigned char)((*(oo + i) + *(oo + i + 1) + 1)>>1); } /*右下角像素 */ *(ii + (width<<1) - 2) = *(oo + width -1); *(ii + (width<<1) - 1) = *(oo + width -1); *(ii + (width<<2) - 2) = *(oo + width -1); *(ii + (width<<2) - 1) = *(oo + width -1); return ipol_image; }
1、2、2、2、2 MotionEstimatePicture 完成运动估值
// 计算出前一帧的重建和当前帧图像之间的运动向量。 // 它也是按宏块处理的,MotionEstimatePicture对当前帧的一个宏块算出运动矢量, // 再填充到前面定义的MV数组中 void MotionEstimatePicture(unsigned char *curr, unsigned char *prev, unsigned char *prev_ipol, int seek_dist, MotionVector *MV[6][MBR+1][MBC+2], int gobsync) { int i,j,k; int pmv0,pmv1,xoff,yoff; int curr_mb[16][16]; int sad8 = INT_MAX, sad16, sad0; int newgob; MotionVector *f0,*f1,*f2,*f3,*f4; /* 运动估计并存储结果MV */ for ( j = 0; j < lines/MB_SIZE; j++) { newgob = 0; // if (gobsync && j%gobsync == 0) { newgob = 1; } for ( i = 0; i < pels/MB_SIZE; i++) { // 每一个宏块存着6个宏块 for (k = 0; k < 6; k++) MV[k][j+1][i+1] = (MotionVector *)malloc(sizeof(MotionVector)); /* 整象素搜索 */ f0 = MV[0][j+1][i+1]; f1 = MV[1][j+1][i+1]; f2 = MV[2][j+1][i+1]; f3 = MV[3][j+1][i+1]; f4 = MV[4][j+1][i+1]; // 寻找PMV FindPMV(MV,i+1,j+1,&pmv0,&pmv1,0,newgob,0); if (long_vectors) { xoff = pmv0/2; /* 总是能被2整除 */ yoff = pmv1/2; } else { xoff = yoff = 0; } // 为宏块估算所有的运动向量 MotionEstimation(curr, prev, i*MB_SIZE, j*MB_SIZE, xoff, yoff, seek_dist, MV, &sad0); sad16 = f0->min_error; if (advanced) sad8 = f1->min_error + f2->min_error + f3->min_error + f4->min_error; f0->Mode = ChooseMode(curr,i*MB_SIZE,j*MB_SIZE, mmin(sad8,sad16)); /* 半象素精度搜索 */ if (f0->Mode != MODE_INTRA) { FindMB(i*MB_SIZE,j*MB_SIZE ,curr, curr_mb);//当前宏块放入curr_mb FindHalfPel(i*MB_SIZE,j*MB_SIZE,f0, prev_ipol, &curr_mb[0][0],16,0); sad16 = f0->min_error; if (advanced) { FindHalfPel(i*MB_SIZE,j*MB_SIZE,f1, prev_ipol, &curr_mb[0][0],8,0); FindHalfPel(i*MB_SIZE,j*MB_SIZE,f2, prev_ipol, &curr_mb[0][8],8,1); FindHalfPel(i*MB_SIZE,j*MB_SIZE,f3, prev_ipol, &curr_mb[8][0],8,2); FindHalfPel(i*MB_SIZE,j*MB_SIZE,f4, prev_ipol, &curr_mb[8][8],8,3); sad8 = f1->min_error +f2->min_error +f3->min_error +f4->min_error; sad8 += PREF_16_VEC; /* 选择0运动矢量, 基于8x8或16x16的运动矢量 */ if (sad0 < sad8 && sad0 < sad16) { f0->x = f0->y = 0; f0->x_half = f0->y_half = 0; } else { if (sad8 < sad16) f0->Mode = MODE_INTER4V; } } else { /* 选择0运动矢量或基于16x16的运动矢量 */ if (sad0 < sad16) { f0->x = f0->y = 0; f0->x_half = f0->y_half = 0; } } } else for (k = 0; k < 5; k++) ZeroVec(MV[k][j+1][i+1]); } } return; }
1、2、2、2、3 Predict_P 预测P-宏块