3.3 Xvid视频解码分析
3.3.1 MPEG-4视频解码原理
视频解码工作相对于视频编码,运算量大大降低,一般情况下,后者是前者的1/3。大家都知道,MPEG-4视频算法是可兼容传统视频帧的面向对象编码的。但是目前视频对象分割是难点,因此几乎所有的MPEG-4视频应用都是基于传统的矩形视频帧来处理的。只不过MPEG-4把整个图像画面看做一个对象,即视频对象平面VOP(Video Object Plane)。
图3-6是VOP解码功能框图,频码流包含I-VOP、B-VOP、P-VOP纹理信息和运动信息。帧间(Inter)和帧内(Intra)解码处理单位仍然是16×16的宏块。其中Intra宏块是图像数据经过IDCT和AC/DC预测得到。Inter块是块数据间的残差作IDCT,然后加回到比较块,比较块的位置是根根码流中的运动向量MV(1个或4个)的值来确定的。运动向量的精度有1/2、1/4像素,这根据码流中的信息来决定。当然有可能指向参考帧的边界外。
图3-6 VOP解码功能框图
3.3.2 Xvid视频解码过程
Xvid解码算法也提供了对CPU平台的汇编优化支持。检测CPU、初始化核心模块函数指针的代码与编码工作完全完全相同。请参考前面的小节。
Xvid的MPEG-4解码算法实现以一个函数及其三个不同的参数传递来完成。
/*-------------------------解码器操作-----------------------------------*/ #define XVID_DEC_CREATE 0 /* 创建解码器实例;0代表成功*/ #define XVID_DEC_DESTROY 1 /* 销毁解码器实例;0代表成功*/ #define XVID_DEC_ENCODE 2 /* 解码一帧图像:返回已经解码的字节数(>=0)*/ /*---------------------------编码器入口函数---------------------------------*/ extern int Xvid_decore(void *handle, int opt, void *param1, void *param2);
解码器的入口函数Xvid_decore的代码实现如下:
int Xvid_decore(void *handle, int opt, void *param1, void *param2) { switch (opt) { case XVID_DEC_CREATE: /*创建解码器实例*/ return decoder_create((Xvid_dec_create_t *) param1); case XVID_DEC_DESTROY: /*销毁解码器实例*/ return decoder_destroy((DECODER *) handle); case XVID_DEC_DECODE: /*解码一帧图像*/ return decoder_decode((DECODER*)handle, /*解码器句柄*/ (Xvid_dec_frame_t*)param1, /*解码帧结构体*/ (Xvid_dec_stats_t*)param2); /*解码状态结构体*/ default: return XVID_ERR_FAIL; } }
上述代码是解码器的所有功能函数,创建解码器实例、使用解码器解码图像、销毁解码器。在应用层,解码器可以有多个,这就通过解码句柄Handle来控制不同的解码器。创建解码器decoder_create、销毁解码器decoder_destroy均只调用一次。循环调用decoder_decode解码图像帧。
1.创建解码器实例
创建解码器实例,即解码器句柄。解码器的所有动作和使用配置都是通过该句柄来控制完成的。Xvid视频解码器可以同时解码多路视频。
/*图像分辨率大小发生变化,释放已申请的内存,并重新申请*/ static int decoder_resize(DECODER * dec) { /*释放存在的图像空间*/ image_destroy(&dec->cur, dec->edged_width, dec->edged_height); image_destroy(&dec->refn[0], dec->edged_width, dec->edged_height); image_destroy(&dec->refn[1], dec->edged_width, dec->edged_height); image_destroy(&dec->tmp, dec->edged_width, dec->edged_height); /*所有图像指针清零*/ image_null(&dec->cur); //当前解码帧的空间y/u/v指针清空 image_null(&dec->refn[0]); //参考帧0的空间y/u/v指针清空 image_null(&dec->refn[1]); //参考帧1的空间y/u/v指针清空 image_null(&dec->tmp); //tmp临时空间的y/u/v指针清空 Xvid_free(dec->last_mbs); //释放宏块结构空间 Xvid_free(dec->mbs); //释放宏块结构空间 dec->last_mbs=NULL; //指针清空 dec->mbs=NULL; //指针清空 /*重新申请内存*/ dec->mb_width=(dec->width+15)/16; /*图像帧宏块宽度,16的倍数*/ dec->mb_height=(dec->height+15)/16; /*图像帧宏块高度,16的倍数*/ dec->edged_width=16*dec->mb_width+2*EDGE_SIZE; /*图像边扩展了的图像宽度*/ dec->edged_height=16*dec->mb_height+2*EDGE_SIZE; /*图像边扩展了的图像高度*/ /*申请图像空间*/ if( image_create(&dec->cur,dec->edged_width,dec->edged_height) || image_create(&dec->refn[0], dec->edged_width, dec->edged_height) || image_create(&dec->refn[1], dec->edged_width, dec->edged_height) || image_create(&dec->tmp, dec->edged_width, dec->edged_height)) goto memory_error; //申请失败 /*申请所有宏块的解码信息*/ dec->mbs = Xvid_malloc(sizeof(MACROBLOCK) * dec->mb_width * dec->mb_height, CACHE_LINE); if(dec->mbs==NULL) goto memory_error; //申请失败 memset(dec->mbs, 0, sizeof(MACROBLOCK) * dec->mb_width * dec->mb_height); /*清空*/ /* 解码B帧时,用到的宏块解码信息 */ dec->last_mbs=Xvid_malloc(sizeof(MACROBLOCK)*dec->mb_width * dec->mb_height,CACHE_LINE); if(dec->last_mbs==NULL) goto memory_error; //申请失败 memset(dec->last_mbs, 0, sizeof(MACROBLOCK) * dec->mb_width * dec->mb_height); return 0; memory_error: /*释放图像空间、结构体,指针置空 */ Xvid_free(dec->mbs); image_destroy(&dec->cur,dec->edged_width,dec->edged_height); //释放当前解码帧空间 image_destroy(&dec->refn[0],dec->edged_width,dec->edged_height); //释放参考帧0空间 image_destroy(&dec->refn[1],dec->edged_width,dec->edged_height); //释放参考帧1空间 image_destroy(&dec->tmp,dec->edged_width,dec->edged_height); //释放临时帧空间 /*释放解码器句柄*/ Xvid_free(dec); return XVID_ERR_MEMORY; } /*创建编码器实例*/ int decoder_create(Xvid_dec_create_t * create) { DECODER *dec; dec = Xvid_malloc(sizeof(DECODER), CACHE_LINE); /*创建并初始化解码器句柄,使句柄完全可控*/ if(dec==NULL) return XVID_ERR_MEMORY; memset(dec, 0, sizeof(DECODER)); create->handle=dec; /*获取解码输入的参数*/ dec->width = create->width; dec->height = create->height; image_null(&dec->cur); /*图像空间指针清零*/ image_null(&dec->refn[0]); image_null(&dec->refn[1]); image_null(&dec->tmp); dec->mbs = NULL; dec->last_mbs = NULL; init_postproc(&dec->postproc); /*支持图像后处理,如滤波、去块效应*/ dec->frames=0; /*支持B帧解码,用来保存参考帧的时间*/ dec->time = dec->time_base = dec->last_time_base = 0; dec->low_delay = 0; dec->packed_mode = 0; dec->time_inc_resolution=1; /* 直到VOL头更改为其他数据 */ /*创建解码器时,已知图像宽度和高度*/ dec->fixed_dimensions = (dec->width > 0 && dec->height > 0); if(dec->fixed_dimensions) return decoder_resize(dec); else return 0; }
上述代码实现创建解码器,把图像空间的指针清空,如果已知图像的分辨率,则申请各种空间。如果不知道分辨率或分辨率设置不正确,则解码器在分析码流的头结构后,获取分辨率再重新申请空间。
创建解码器时,也可以不输入图像分辨率,因为码流中本身有分辨率Width、Height信息,能够从码流中解析到。然后再重新申请图像空间。
2.解码一帧图像
MPEG-4 SP视频编码算法中,编码帧类型是I帧和P帧。码流头结构中有帧类型信息、量化信息、fcode值、intra_dc_threshold等。Xvid提供的MPEG-4视频解码在解码到VOL时会返回应用程序,为了应用层更直接的得到解码图像,现在修改为只有解析并解码了图像帧数据时才返回应用程序。解码器解码图像decoder_decode()的流程如图3-7所示。
图3-7 decoder_decode()流程图
根据decoder_decode()的流程,代码实现如下:
int decoder_decode(DECODER * dec, Xvid_dec_frame_t * frame, Xvid_dec_stats_t * stats) { uint32_t rounding; /*图像饱和类型,在插值时四舍五入的饱和大小*/ uint32_t reduced_resolution; uint32_t quant=2; /*默认的量化步长初始化*/ uint32_t fcode_forward; /*运动估计中搜索窗口的大小,前向*/ uint32_t fcode_backward; /*运动估计中搜索窗口的大小,后向*/ uint32_t intra_dc_threshold; /*Intra块的直流DC阈值*/ WARPPOINTS gmc_warp; int coding_type; /*当前编码帧类型*/ if(frame->length<0) return XVID_ERR_END; /*检测输入码流长度的合法性*/ BitstreamInit(&g_bs,frame->bitstream,frame->length); /*初始化码流结构体*/ /*解析视频码流头结构、信息*/ repeat: coding_type = BitstreamReadHeaders(&g_bs, dec, &rounding, &reduced_resolution, &quant, &fcode_forward, &fcode_backward, &intra_dc_threshold, &gmc_warp); if(coding_type==-1){ /* 继续解析头信息,直到解析到图像数据 */ goto repeat; } if(coding_type==-2||coding_type==-3){ /* 当前是VOL头或需要修改图像空间大小 */ if(coding_type==-3) decoder_resize(dec); if(stats) goto repeat; /* 继续解析头信息,直到解析到图像数据 */ } /*确保解码的第一帧是I帧,如果不是I帧则继续解析后续码流*/ if(dec->frames==0&&coding_type!=I_VOP) goto repeat; if(coding_type!=B_VOP) { switch(coding_type) { case I_VOP: /*I帧解码*/ decoder_iframe(dec, &g_bs, reduced_resolution, quant, intra_dc_threshold); break; case P_VOP: /*P帧解码*/ decoder_pframe(dec, &g_bs, rounding, reduced_resolution, quant, fcode_forward, intra_dc_threshold, NULL); break; case S_VOP: /*S帧解码*/ decoder_pframe(dec, &g_bs, rounding, reduced_resolution, quant, fcode_forward, intra_dc_threshold, &gmc_warp); break; } dec_image_swap(&dec->refn[0],&dec->refn[1]); /*交换两个参考帧的指针*/ dec_image_swap(&dec->cur,&dec->refn[0]); /*交换当前图像和参考帧的指针*/ dec->last_reduced_resolution = reduced_resolution; dec->last_coding_type=coding_type; /*保存当前帧类型*/ dec->frames++; /*解码帧计数器*/ } done : return(BitstreamPos(&g_bs)+7)/8; /* 已解码的码流,单位B*/ }
BitstreamReadHeaders( )函数根据MPEG-4标准码流协议,解析编码信息。如果用户在编码端对VOL、VOP的写头信息做了修改,则在解码端的该函数中,要一一对应起来。如为了加密需要,就可以在编码端对VOL/VOP信息做特定的修改。SP档级的MPEG-4视频帧有I帧和P帧。本节对I帧和P帧的解码过程做剖析。I帧解码由decoder_iframe函数完成、P帧解码由decoder_pframe函数完成。
1)decoder_iframe
解码是其编码的逆工作。获得mcbpc、acpred_flag、cbpy等。对运动向量初始化为0。
static void decoder_iframe(DECODER * dec, Bitstream * bs, int quant, int intra_dc_threshold) { uint32_t x, y; const uint32_t mb_width = dec->mb_width; const uint32_t mb_height = dec->mb_height; /*循环解码所有宏块*/ for (y = 0; y < mb_height; y++) for (x = 0; x < mb_width; x++) { MACROBLOCK *mb; uint32_t mcbpc, cbpc, acpred_flag, cbpy, cbp; mb = &dec->mbs[y * dec->mb_width + x]; mcbpc=get_mcbpc_intra(bs); /*解码得到mcbpc*/ mb->mode=mcbpc&7; /*宏块编码模式*/ cbpc=(mcbpc>>4); /*组合得到cbpc*/ acpred_flag=BitstreamGetBit(bs); /*AC预测方向*/ cbpy=get_cbpy(bs,1); /*解码得到cbpy*/ cbp=(cbpy<<2)|cbpc; /*组合得到cbp*/ mb->quant = quant; mb->mvs[0].x = mb->mvs[0].y = mb->mvs[1].x = mb->mvs[1].y = mb->mvs[2].x = mb->mvs[2].y = mb->mvs[3].x = mb->mvs[3].y =0; decoder_mbintra(dec, mb, x, y, acpred_flag, cbp, bs, quant, intra_dc_threshold, 0); // Intra宏块解码 } }
上述代码实现I帧图像的解码,首先从码流中获取mcbpc、AC预测方向、cbpy,对运动量清零,调用核心函数decoder_mbintra进行解码。以宏块为单位循环处理。
2)decoder_mbintra,对Intra宏块做正真的解码。
该函数是Intra块的核心解码函数,进行熵解码、反量化、反变换,最后把解码后的宏块数据更新到当前解码图像空间。Intra块的decoder_mbintra()流程图,如图3-8所示。
图3-8 decoder_mbintra()流程图
根据解码decoder_mbintra()流程,代码实现如下:
/* decode an intra macroblock */ static void decoder_mbintra(DECODER * dec, MACROBLOCK * pMB, const uint32_t x_pos, const uint32_t y_pos, const uint32_t acpred_flag, const uint32_t cbp, Bitstream * bs, const uint32_t quant, const uint32_t intra_dc_threshold,const unsigned int bound) { DECLARE_ALIGNED_MATRIX(block,6,64,int16_t,CACHE_LINE); /*DCT系数*/ DECLARE_ALIGNED_MATRIX(data,6,64,int16_t,CACHE_LINE); /*图像数据*/ uint32_t stride=dec->edged_width; /*图像边做扩展的图像Y宽度*/ uint32_t stride2=stride/2; /*图像边做扩展的图像U宽度*/ uint32_t next_block=stride*8; /*修改指向块的横跨宽度*/ uint32_t i; uint32_t iQuant = pMB->quant; uint8_t *pY_Cur, *pU_Cur, *pV_Cur; /*获取指向图像空间的指针*/ pY_Cur = dec->cur.y + (y_pos << 4) * stride + (x_pos << 4); pU_Cur = dec->cur.u + (y_pos << 3) * stride2 + (x_pos << 3); pV_Cur = dec->cur.v + (y_pos << 3) * stride2 + (x_pos << 3); memset(block,0,6*64*sizeof(int16_t)); /* 图像DCT系数清空 */ /*循环对6个块处理*/ for (i = 0; i < 6; i++) { uint32_t iDcScaler = get_dc_scaler(iQuant, i < 4); /*获取DC分量的量化步长*/ int16_t predictors[8]; /*预测器*/ int start_coeff; /*预测DCT系数,得到预测器*/ predict_acdc(dec->mbs,x_pos,y_pos,dec->mb_width,i,&block[i * 64],iQuant, iDcScaler, predictors, bound); if(!acpred_flag) pMB->acpred_directions[i]=0; /*得到DCT的直流分量*/ if (quant < intra_dc_threshold) { int dc_size; int dc_dif; dc_size = i < 4 ? get_dc_size_lum(bs) : get_dc_size_chrom(bs); dc_dif = dc_size ? get_dc_dif(bs, dc_size) : 0; if(dc_size>8) BitstreamSkip(bs,1);/*marker*/ block[i * 64 + 0] = dc_dif; start_coeff = 1; } else start_coeff = 0; if(cbp&(1<<(5-i)))/* 宏块做编码标识*/ { int direction = dec->alternate_vertical_scan ? 2 : pMB->acpred_directions[i]; get_intra_block(bs, &block[i * 64], direction, start_coeff); /*获取当前块的系数*/ } /*使用预测器,恢复编码前系数值*/ add_acdc(pMB, i, &block[i * 64], iDcScaler, predictors, dec->bs_version); /*H.263反量化*/ dequant_h263_intra(&data[i * 64], &block[i * 64], iQuant, iDcScaler, dec->mpeg_quant_matrices); /*反DCT变换*/ idct((short * const)&data[i * 64]); }/*循环对6个块处理*/ /*把解码后的宏块图像复制到当前解码图像空间*/ transfer_16to8copy(pY_Cur, &data[0 * 64], stride); transfer_16to8copy(pY_Cur + 8, &data[1 * 64], stride); transfer_16to8copy(pY_Cur + next_block, &data[2 * 64], stride); transfer_16to8copy(pY_Cur + 8 + next_block, &data[3 * 64], stride); transfer_16to8copy(pU_Cur, &data[4 * 64], stride2); transfer_16to8copy(pV_Cur, &data[5 * 64], stride2); }
上述代码实现Intra块的解码工作,首先初始化必要的参数和变量,解码得到DC直流分量的量化步长,接着预测DCT系数以获得预测器,解码直流分量、交流分量,反量化、反变换。最后把解码的宏块数据更新到当前解码图像空间。
3)decoder_pframe
P帧是主要的编码类型,解码器可以根据编码器的功能支持把多余的代码屏蔽。Xvid支持的解码器较复杂,如B帧解码、GMC等。这里主要讨论解码SP档级的码流。P帧的解码decoder_pframe()的流程如图3-9所示。
图3-9 decoder_pframe()流程图
根据decoder_pframe()的流程,代码实现如下:
void decoder_pframe(DECODER*dec, Bitstream*bs,int rounding,int reduced_resolution, int quant,int fcode,int intra_dc_threshold) { uint32_t x, y; uint32_t bound; VECTOR mv; const VECTOR zerovec={0,0}; uint32_t mb_width = dec->mb_width; uint32_t mb_height = dec->mb_height; uint32_t width = dec->width; uint32_t edged_width = dec->edged_width; /*循环所有宏块*/ for (y=0; y<mb_height; y++) { for (x = 0; x < mb_width; x++) { MACROBLOCK *mb; mb = &dec->mbs[y * dec->mb_width + x]; if(!(BitstreamGetBit(bs))) /*当前宏块已经被编码*/ { uint32_t mcbpc, cbpc, cbpy, cbp; uint32_t intra, acpred_flag = 0; mcbpc=get_mcbpc_inter(bs); /*解码得到mcbpc*/ mb->mode = mcbpc & 7; cbpc=(mcbpc>>4); /*组合得到cbpc*/ intra=(mb->mode==MODE_INTRA); /*是否Intra宏块*/ if(intra) acpred_flag=BitstreamGetBit(bs); /*Intra块的AC预测*/ cbpy=get_cbpy_asm_dm642(bs,intra); /*解码得到cbpy*/ cbp=(cbpy<<2)|cbpc; /*组合得到CBP*/ mb->quant=quant; /*量化初始化*/ if (mb->mode == MODE_INTER){ /*编码模式是MODE_INTER*/ get_motion_vector(dec, bs, x, y, 0,&mv, fcode, bound);/*解码得到MV.x , MV.y*/ mb->mvs[0] = mb->mvs[1] = mb->mvs[2] = mb->mvs[3] = mv;/*初始化MVS*/ decoder_mbinter(dec,mb,x,y,cbp,bs, mv,0,x); /*Inter 解码*/ } else{ /*编码模式是MODE_INTRA*/ mb->mvs[0].x = mb->mvs[1].x = mb->mvs[2].x = mb->mvs[3].x = 0;/*MVS置0 */ mb->mvs[0].y = mb->mvs[1].y = mb->mvs[2].y = mb->mvs[3].y = 0;/* MVS置0*/ decoder_mbintra(dec,mb,x,y,acpred_flag,cbp, /*解码Intra块*/ bs, quant, intra_dc_threshold, bound); continue; } }else{ /*当前宏块没有被编码not_coded*/ mb->mode = MODE_NOT_CODED; mb->quant = quant; mb->mvs[0].x = mb->mvs[1].x = mb->mvs[2].x = mb->mvs[3].x = 0;/* MVS置0*/ mb->mvs[0].y = mb->mvs[1].y = mb->mvs[2].y = mb->mvs[3].y = 0;/* MVS置0*/ decoder_mbinter(dec,mb,x,y,0,bs,zerovec,0,x); /*Inter解码*/ } } //x loop } // y loop }
上述代码实现P帧的解码,根据前面编码的流程知道,宏块有三种编码模式:Inter、Intra和not_coded。而对于not_coded同样是属于Inter块的编码范畴,在解码中同样要做运动补偿,只是不再做实质的解码工作。
4)decoder_mbinter对Inter块实现解码。
下面的Inter块解码支持1/2像素精度。Inter块的decoder_mbinter()的流程如图3-10所示。
图3-10 decoder_mbinter ()流程图
根据decoder_mbinter()的流程,代码实现如下:
void decoder_mbinter(DECODER * dec, const MACROBLOCK * pMB, const uint32_t x_pos, const uint32_t y_pos, const uint32_t cbp, Bitstream * bs, const uint32_t rounding, const int ref, const int bvop) { uint32_t stride = dec->edged_width; uint32_t stride2 = stride / 2; uint32_t i; uint8_t *pY_Cur, *pU_Cur, *pV_Cur; int uv_dx, uv_dy; VECTOR mv[4]; /* local copy of mvs */ /*获取指向图像空间的指针*/ pY_Cur = dec->cur.y + (y_pos << 4) * stride + (x_pos << 4); pU_Cur = dec->cur.u + (y_pos << 3) * stride2 + (x_pos << 3); pV_Cur = dec->cur.v + (y_pos << 3) * stride2 + (x_pos << 3); for(i=0;i<4;i++) mv[i]=pMB->mvs[i]; /*获取运动向量*/ validate_vector(mv,x_pos,y_pos,dec); /*检查运动向量的合法性*/ /******************************运动补偿*****************************/ if ((pMB->mode != MODE_INTER4V)) {/* 编码模式是INTER、INTER_Q、NOT_CODED、FORWARD、BACKWARD*/ uv_dx = mv[0].x; uv_dy = mv[0].y; uv_dx = (uv_dx >> 1) + roundtab_79[uv_dx & 0x3]; uv_dy = (uv_dy >> 1) + roundtab_79[uv_dy & 0x3]; /*亮度插值*/ interpolate16x16_switch(dec->cur.y,dec->refn[ref].y,16*x_pos,16*y_pos,mv[0].x, mv[0].y, stride, rounding); } /*色度插值 */ interpolate8x8_switch(dec->cur.u, dec->refn[ref].u, 8 * x_pos, 8 * y_pos,uv_dx, uv_dy, stride2, rounding); interpolate8x8_switch(dec->cur.v, dec->refn[ref].v, 8 * x_pos, 8 * y_pos,uv_dx, uv_dy, stride2, rounding); /******************************运动补偿*****************************/ /*根据cbp*/ if (cbp) decoder_mb_decode(dec, cbp, bs, pY_Cur, pU_Cur, pV_Cur, pMB); }
上述代码实现Inter块的解码,首先做运动补偿,然后根据cbp编码模式决定是否做宏块解码。
5)decoder_mb_decode实现宏块的实际解码工作
首先VLD熵解码、反量化和IDCT变换,最后根据cbp编码模式决定块是否更新到当前解码帧图像。Inter块的decoder_ mb_decode ()的流程如图3-11所示。
图3-11 decoder_mb_decode()流程图
根据decoder_mb_decode ()的流程,代码实现如下:
void decoder_mb_decode(DECODER * dec, const uint32_t cbp, Bitstream * bs, uint8_t * pY_Cur, uint8_t * pU_Cur, uint8_t * pV_Cur, const int reduced_resolution,const MACROBLOCK * pMB) { DECLARE_ALIGNED_MATRIX(block, 1, 64, int16_t, CACHE_LINE); /*系数*/ DECLARE_ALIGNED_MATRIX(data,6,64,int16_t,CACHE_LINE); /*图像*/ int stride=dec->edged_width; /*图像边扩展后的宽度*/ int next_block=stride*8; /*亮度的下一个块*/ const int stride2=stride/2; /*色度的下一个快*/ int i; const uint32_t iQuant=pMB->quant; /*量化步长*/ const int direction=dec->alternate_vertical_scan?2:0; /*AC扫描方式*/ for (i = 0; i < 6; i++) { if(cbp&(1<<(5-i))){ /*块做了编码*/ memset(block,0,64*sizeof(int16_t)); /*清零*/ get_inter_block(bs,block,direction); /*VLD熵解码*/ dequant_h263_inter(&data[i*64],block,iQuant,NULL); /*H263反量化*/ idct(&data[i*64]); /*IDCT变换*/ } } /*根据CBP值决定是否加到当前图像*/ if (cbp & 32) transfer_16to8add(pY_Cur, &data[0 * 64], stride); if (cbp & 16) transfer_16to8add(pY_Cur + 8, &data[1 * 64], stride); if(cbp&8) transfer_16to8add(pY_Cur+next_block,&data[2*64],stride); if(cbp&4) transfer_16to8add(pY_Cur+8+next_block,&data[3*64],stride); if(cbp&2) transfer_16to8add(pU_Cur,&data[4*64],stride2); if(cbp&1) transfer_16to8add(pV_Cur,&data[5*64],stride2); }
上述代码为实现Inter块的解码工作,根据cbp值决定是否做解码。首先解码宏块空间block清零,VLD熵解码,然后反量化、反变换。最后根据cbp值决定是否更新当前解码帧图像。
为了提高解码器解码速度,解码器的最底层模块get_inter_block、dequant_h263_inter、idct、transfer_16to8add等,要使用平台的汇编来优化和设计。下一节将介绍上述核心模块的MMX/SSE/DM642汇编优化。
3.销毁解码器实例
对应创建编码器工作,释放申请的所有内存,删除解码器句柄。
int decoder_destroy(DECODER * dec) { Xvid_free(dec->last_mbs); //释放宏块结构体 Xvid_free(dec->mbs); //释放宏块结构体 image_destroy(&dec->refn[0], dec->edged_width, dec->edged_height); //释放参考帧0的空间 image_destroy(&dec->refn[1],dec->edged_width,dec->edged_height); //释放参考帧1的空间 image_destroy(&dec->tmp,dec->edged_width,dec->edged_height); //释放临时帧的空间 image_destroy(&dec->cur,dec->edged_width,dec->edged_height); //释放当前帧的空间 Xvid_free(dec); //释放、删除解码器 return 0; }
Xvid的MPEG-4视频编解码算法结构清晰,且使用定点数据/结构,算法框架适于硬件平台。可以说Xvid是目前MPEG-4算法开源代码中最优秀的工程实现。一方面Xvid已经针对主流的CPU做了汇编优化,另一方面为其他平台移植提供了技术方向。如表3-1所示是Xvid视频编解码算法的最底层模块。
表3-1 Xvid视频CODEC底层模块
从表3-1分析可以清晰的看出,编码器的工作包含了解码器的任务。为提高CODEC的执行效率,需要对底层模块进行深度优化。下面介绍底层模块的优化方法。