MPEG-4/H.264视频编解码工程实践
上QQ阅读APP看本书,新人免费读10天
设备和账号都新为新人

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的执行效率,需要对底层模块进行深度优化。下面介绍底层模块的优化方法。