调用Xvid编码器流程(基于xvid1.1.0)

xvid有两种编码方式:single pass和twopass
  single pass模式编码简单,速度也快,但最终效果不如twopass。
  twopass就是视频压制需要经过两次编码,分别为twopass-1st pass(简称1pass)和twopass-2nd pass(简称2pass)
  1pass时,编码器会用最高质量编码采集可供第2次运算参考的画面信息,而在2 pass时。编码器会根据第一次压缩获得的信息和用户指定的文件大小,自动分配比特率,使需要高流量的运动画面分配到更多的空间,更高的比特率来保证画面质量。相对的,对于那些不包含太多运动信息的静态画面则用较低的比特率。追求画质的朋友当然会选择这种方式,但运算比single pass更费时。

接下来介绍一些基本概念:
  Q值——量化值,它被用来描述1帧的质量,每帧都有一个Q值,取值范围在1-31之间。Q值越小,画质越好,比特率越大
  I-frame——关键帧,常被缩写为IF。关键帧是构成一个帧组的第一帧。IF保留了一个场景的所有信息
  P-frame——未来单项预测帧,缩写为PF,只储存与之前一个已解压画面的差值
  B-frame——双向预测帧,缩写为BF,除了参考之前解压的画面以外,也会参考后一帧的画面信息
 
编码流程:
 
   各变量的设置:创建xvid_enc_frame_t和xvid_enc_stats_t,分别用于传入参数和统计编码结果。
具体过程:
设置传入图像数据和图像色彩空间
设置传出的码流
设置vol的标志
设置帧的编码类型
设置量化因子
设置运动估计算法集合
设置vop的标志
编码器提供的函数
1,  xvid_global (NULL, XVID_GBL_INIT, &xvid_gbl_init, NULL);
含义:根据cpu的特性使用相应汇编优化的函数
 
2,  xvid_encore (NULL, XVID_ENC_CREATE, &xvid_enc_create, NULL);
含义:初始化编码器。
具体过程:
创建编码器句柄,并根据传入的参数设置各变量的值,并且分配要使用的内存,用于存放重建帧,参考帧( 1/2 像素精度)。以及各种临时变量。并且做好码率控制的初始化。
 
3,  xvid_encore (enc_handle, XVID_ENC_ENCODE, &xvid_enc_frame, &xvid_enc_stats);
目的:编码一帧
具体过程:
{
初始化写码流。
如果有必要,转换色彩空间,并且把原始图像拷贝到有边框的图像空间,但是没有扩展边框。
将重建帧交换成参考帧
从帧队列中获取当前帧
设置 Encoder 结构体的 current 结构体的 vol_flags vop_flags motion_flags fcode bcode quant 字段。
调用 call_plugins ,在里面调用 rc_single_before 做码率控制的初始化,以及对 current 结构体的其他变量进一步设置
通过帧号或者 MEanalysis 函数分析来确定编码类型,并且根据用户的设置作修正。
MEanalysis 的原理是,如果某个宏块的残差的 sad 大于该宏块的平均值的偏离,那么使用 intra 方式,否则使用 inter 方式,然后对这些宏块进行统计,得到整帧的编码方式。
 
如果编码类型是 I_VOP
{
设置 Encoder->mbParam->vol_flags
设置 Encoder->mbParam.par
根据 vol_flags 设置 vop_flags
调用 FrameCodeI I 帧的方式编码
调用 call_plugins ,在里面调用 rc_single_after ,进行码率控制。
}
 
如果编码类型是 P_VOP
{
mbParam.vol_flags 固定住 pEnc->current->vol_flags
调用 FrameCodeP P 帧的方式编码
调用 call_plugins ,在里面调用 rc_single_after ,进行码率控制。
}
}// xvid_encore
 
4, static int FrameCodeI(Encoder * pEnc, Bitstream * bs)
目的:将一帧图像编码成一个 I
具体过程:
XVID_PLG_FRAME 参数调用 call_plugins ,该函数目前的作用是设置 dquant ,可以在该函数中设置最好质量。
调用 SetMacroblockQuants ,为每个宏块设置量化因子,所以也可以在这里设置最好质量
调用 BitstreamWriteVolHeader ,写 vol
调用 set_timecodes ,设置时间编码。
调用 BitstreamPad ,填充 bit 至字节对齐
调用 BitstreamWriteVopHeader ,填写 vop
 
依次读取每一个宏块,进行编码
{
调用 CodeIntraMB 设置编码模式为 intra ,将所有和运动有关的变量设为 0
调用 MBTransQuantIntra 进行变换编码
{
调用 MBTrans8to16 将像素的表示方法从 8bit 扩大到 16bit
调用 MBfDCT 对像素进行变换编码
调用 MBQuantIntra dct 系数进行 intra 方式的量化
调用 MBDeQuantIntra dct 系数进行 intra 方式的反量化
调用 MBiDCT 将恢复的 dct 系数进行反变换
调用 MBTrans16to8 将恢复的 16bit 像素饱和到 8bit ,组成重建宏块
}//MBTransQuantIntra
 
调用 MBPrediction acdc 预测
{
调用 get_dc_scaler 函数得到量化系数
调用 predict_acdc 得到预测方向以及在该预测方向上的和当前块的同一量化水平的预测值
调用 calc_acdc_bits 以确定是只使用 DC 预测,还是 DCAC 预测。原理是分别作 DC 预测和 DCAC 预测,分别计算在这 2 种情况下需要的码流长度,以确定哪种方式更节约码流。
调用 CodeCoeffIntra_CalcBits ,用于确定各种方式下的码流长度
根据预测模式的不同,恢复成相应的系数
最后计算该宏块的 cbp
}//MBPrediction
 
调用 MBCoding 将宏块编制成码流
{
调用 CodeBlockIntra intra 宏块编制成码流
{
编码 mcbpc
编码 ac 预测标记
编码 cbpy
对于 6 个块里的每个块
首先编码 DC 系数
调用 CodeCoeffIntra 对剩下的 63 个系数进行编码
}//CodeBlockIntra
}//MBCoding
 
}// 依次读取每一个宏块,进行编码
 
填充 bit ,直到字节对齐
 
 
5, static int FrameCodeP(Encoder * pEnc, Bitstream * bs)
含义:将一帧图片编码成P帧
具体过程:
{
如果参考帧还没有设置边框,那么就调用 image_setedges设置边框
如果需要半像素运动估计,那么就调用 image_interpolate 进行插值
将一帧填充边框后的参考帧,分成 8*8 的小块,对于每个小块进行插值,如下:
调用 interpolate8x8_halfpel_h 进行水平插值
调用 interpolate8x8_halfpel_v 进行垂直插值
调用 interpolate8x8_halfpel_hv 进行对角线插值
用参数 XVID_PLG_FRAME 调用 call_plugins ,该函数目前的作用是设置 dquant ,可以在该函数中设置最好质量。
调用 SetMacroblockQuants ,为每个宏块设置量化因子,所以也可以在这里设置最好质量
调用 MotionEstimation 做运动估计
{
使用 MotionFlags 变量保存要使用的运动算法集合
使用 skip_thresh 保存要达到 skip 模式的阀值
使用 Data 保存运动估计要用到的相应变量
对于每个宏块,依次执行如下操作
{
调用 sad16v 计算本宏块与参考帧对应位置宏块的亮度的残差,将其保存在 pMB->sad16 中,并按照 4 个块的方式分别存放 pMB->sad8[0-3]
sad00 记录最大亮度块残差的 4
如果还需要考虑色差块的因素
调用 sad8 两次,分别计算 u 分量和 v 分量的残差,都加入 pMB->sad16 中,并且也加入 sad00
如果该宏块的量化差值为 0 ,并且 sad00 又没有超过 skip 模式的阀值
如果已经考虑了色差因素,或者使用 xvid_me_SkipDecisionP 确认符合 skip 模式。
调用 ZeroMacroblockP 将其编码为 skip 模式,并置标记 pMB->mode = MODE_NOT_CODED
根据采用的运动估计算法不同,做相应的设置
调用 SearchP 做该宏块的运动估计
{
确定是否使用 inter4v 模式,并记录之
调用 get_range 确定运动搜索的范围,并记录在 Data
调用 get_pmvdata2 ,以获得左,上,右上的运动向量,以及它们对应的 sad ,存入 pmv[1-3] Data->temp[1-3] 。然后计算它们的中值,并且存放于 pmv[0] ,并且把最小的 sad 存放于 Data->temp[0]
设置 Data 的当前宏块的 yuv 字段。设置 Data->RefP[0-5] 为参考帧的同一宏块的整像素 y ,水平半象素 y ,垂直半象素 y ,对角线 y u v
设置 Data->lambda16 Data->lambda8 ,其含义可能是运动向量对带宽的占用折合到 sad 的值
设置 qpel 和方向
如果采用 qpel ,调用 get_qpmv2 计算用 qple 方式下的估计中值,存入 ata->predMV ;否则, Data->predMV 0
调用 d_mv_bits 计算 mv 需要的编码 bit ,用于修正 pMB->sad16 pMB->sad8[0] ,并将 Data->iMinSAD[0-4] 设置为 pMB->sad16 pMB->sad8[0-3] ,也就是 0 向量对应的各 SAD
如果不采用率失真决策模型,并且不是当前帧的第一宏块,那么使用一种方法设置阀值 threshA ,否则阀值 threshA 512
 
调用 PreparePredictionsP ,对 pmv 作进一步的设置,做运算前的准备。
{
设置 pmv[0] 0 向量
设置 pmv[1] 为中值向量的偶数值
设置 pmv[2] 为参考帧相同位置宏块的第 0 块运动向量的偶数值
如果该宏块有左边宏块,设置 pmv[3] 为左边宏块的第 1 块的运动向量的偶数值,否则为 0
如果该宏块有上面宏块,设置 pmv[4] 为上面宏块的第 2 块的运动向量的偶数值,否则为 0
如果该宏块有右上宏块,设置 pmv[5] 为右上宏块的第 2 块的运动向量的偶数值,否则为 0
如果该宏块有右下宏块,设置 pmv[6] 为参考帧的相同宏块的右下宏块的第 0 块的运动向量的偶数值,否则为 0
}//PreparePredictionsP
 
如果使用 inter4v ,设置 CheckCandidate CheckCandidate16 ,否则设置为 CheckCandidate16no4v
 
逐一检查 mpv[1-6] 这六个最可能运动向量,如果发现他们与以前的运动不同,就调用 CheckCandidate 做运动估计,过程如下:
{
检查要做运动估计的运动向量是否越界
通过该运动向量获得所指向数据块的指针
调用 sad16v ,记录下 4 8*8 块的 SAD 值,存入 data->temp[0-3] 中,并将他们的和存入临时变量 sad 中。
sad data->temp[0] 做基于运动向量的修正。
如果要考虑色差因素,调用 xvid_me_ChromaSAD 计算额外的 SAD ,累加至 sad 中。
如果 sad 小于 data->iMinSAD[0] ,那么设置 data->iMinSAD[0] data->currentMV[0] ,和 data->dir 。注意,此时的 data->dir 记录的不是钻石搜索的方向,而是当前向量是 pmv 数组的第几个元素。
逐一检查 data->temp[0-3] ,如果他们小于 data->iMinSAD[1-4] ,那么修改 data->iMinSAD[1-4] data->currentMV[1-4]
}//CheckCandidate
 
如果当前最优运动向量,即 Data->iMinSAD[0] ,小于 threshA ?或者当前最优运动向量等于参考帧相同位置宏块的运动向量,并且对应的 SAD 值又比他的小?
就不再做 inter4v 的搜索
否则,就做 inter4v 的搜索
{
使用 make_mask 逐一检查存放于 pmv 的所有运动向量,察看是否位于欲搜索的钻石形的顶点。如果是,则在 mask 变量中标记之。
根据 MotionFlags 确定使用的搜索函数,根据当前设置, MainSearchPtr = xvid_me_AdvDiamondSearch
调用 xvid_me_AdvDiamondSearch 进行搜索,过程如下:
{
bDirection 既表明了上次尝试的方向,又表明本次可以尝试的方向
x y 为钻石搜索的位置的中心点坐标
for(;;)
{
如果可以尝试左边,那么调用 CheckCandidate 尝试左边
如果可以尝试右边,那么调用 CheckCandidate 尝试右边
如果可以尝试上边,那么调用 CheckCandidate 尝试上边
如果可以尝试下边,那么调用 CheckCandidate 尝试下边
如果有更好的方向
{
bDirection = 更好的方向
如果更好的方向是左右方向,那么测试该位置的上下方向
否则,那么测试该位置的左右方向
如果这次又找到了更好的方向
将更好的方向累加到 bDirection
将更好的位置存入 x y
}
否则
{
根据去搜索临近未搜索的点,具体规则如下:
如果 bDirection = = 2 ,表明搜索方向是趋向右边的,那么搜索当前中心点的右上点和右下点。
如果 bDirection = = 1 ,表明搜索方向是趋向左边的,那么搜索当前中心点的左上点和左下点。
如果 bDirection = = 2+4 ,表明搜索方向是趋向右上的,那么再搜索当前中心点的左上点,右上点和右下点。
如果 bDirection = = 4 ,表明搜索方向是趋向上边的,那么搜索当前中心点的左上点和右上点。
如果 bDirection = = 8 ,表明搜索方向是趋向下边的,那么搜索当前中心点的左下点和右下点。
如果 bDirection = = 1+4 ,表明搜索方向是趋向左上的,那么再搜索当前中心点的左下点,左上点和右上点。
如果 bDirection = = 2+8 ,表明搜索方向是趋向右下的,那么再搜索当前中心点的左下点,左上点和右上点。
如果 bDirection = = 1+8 ,表明搜索方向是趋向左下的,那么再搜索当前中心点的左上点,左下点和右下点。
否则的话,则认为本轮搜索没有找到更好的点,那么再搜索当前中心点的左上点,左下点,右上点,右下点。
}
如果没有找到更好的方向,从函数中返回
更新 bDirection 为更好的方向
更新 x y 为更好的位置
}//for(;;)
 
}//xvid_me_AdvDiamondSearch
 
如果运动估计算法使用了 XVID_ME_EXTSEARCH16 ,那么
{
设置 startMV = Data->predMV
设置 backupMV 为当前最佳运动向量
如果 startMV backupMV 不相等
{
调用 CheckCandidate 计算位置为 startMV SAD
调用 xvid_me_DiamondSearch 做以 startMV 为起点的搜索,过程如下:
{
for(;;)
{
如果可以尝试左边,那么调用 CheckCandidate 尝试左边
如果可以尝试右边,那么调用 CheckCandidate 尝试右边
如果可以尝试上边,那么调用 CheckCandidate 尝试上边
如果可以尝试下边,那么调用 CheckCandidate 尝试下边
如果没有更好的方向,退出
bDirection = 更好的方向
x y = 更好的位置
如果更好的方向是左右方向,那么测试该位置的上下方向
否则,那么测试该位置的左右方向
如果这次又找到了更好的方向
{
bDirection += 更好的方向
x y = 更好的位置
}
}
}//xvid_me_DiamondSearch
 
将这次搜索结果和上次搜索结果比较,记录最佳的 SAD 和位置。
}// 如果 startMV backupMV 不相等
 
设置 startMV = {1 1}
设置 backupMV 为当前最佳运动向量
如果 startMV backupMV 不相等
{
调用 CheckCandidate 计算位置为 startMV SAD
调用 xvid_me_DiamondSearch 做以 startMV 为起点的搜索,过程如下:
将这次搜索结果和上次搜索结果比较,记录最佳的 SAD 和位置。
}
}// 如果运动估计算法使用了 XVID_ME_EXTSEARCH16
}// 否则,就做 inter4v 的搜索
 
如果没有采用 1/4 像素运动估计算法
{
如果采用了 XVID_ME_HALFPELREFINE16 算法
调用 xvid_me_SubpelRefine
按顺时针方向 8 次调用 CheckCandidate16 ,得到最好的 1/2 像素位置
}
否则
 
如果当前 SAD 足够小,那么 inter4v = 0
如果采用 inter4v
{
4 次调用 Search8 来搜索 4 8*8 块的最佳运动向量,每一次搜索的规则如下:
{
如果采用 1/4 像素运动估计,略。否则
调用 get_pmv2 取得本块的中值
计算第一块以外快的 d_mv_bits
Data->lambda8 修正该块当前的 SAD ,但是第 0 块是不用修正的。
如果使用了 XVID_ME_EXTSEARCH8 | XVID_ME_HALFPELREFINE8 | XVID_ME_QUARTERPELREFINE8 ,那么
{
Data->RefP[0-3] = 参考帧的整像素,水平半象素,垂直半象素,对角线半象素的对应宏块的对应块的起始地址。
Data->Cur = 当前帧的当前宏块的当前块的起始地址
利用 get_range 得到运动搜索的范围
根据 MotionFlags 的指示,设定运动估计 MainSearchPtr 的算法,当前设置为 MainSearchPtr = xvid_me_AdvDiamondSearch
调用 xvid_me_AdvDiamondSearch 做运动估计,其中做 SAD 的函数是 CheckCandidate8 ,该函数类似于 CheckCandidate16
如果不采用 1/4 像素运动估计,并且又采用了 XVID_ME_HALFPELREFINE8 ,那么调用 xvid_me_SubpelRefine
按顺时针方向 8 次调用 CheckCandidate8 ,得到最好的 1/2 像素位置
如果采用了 1/4 像素运动估计,略
}// XVID_ME_EXTSEARCH8 | XVID_ME_HALFPELREFINE8 | XVID_ME_QUARTERPELREFINE8
 
如果采用 1/4 运动估计
否则
记录 pMB->pmvs[block] = 当前找到的最佳位置与预测位置的差值
 
将这次的搜索存入相应 OldData 的字段,以及 pMB 的相应字段
}// Search8
如果考虑色差的因素,并且又不考虑率失真算法
{
根据是否采用 1/4 像素运动估计算出色差的运动向量
计算 u v SAD ,将其作为 Data->iMinSAD[1] 的修正
}
} // 如果采用 inter4v
否则, Data->iMinSAD[1] 为足够大的值
}//SearchP
 
调用 ModeDecision_SAD 确定该宏块的类型
判断该宏块要采取的编码方式, MODE_INTER MODE_INTER4V MODE_NOT_CODED MODE_INTRA
调用 motionStatsPVOP 做一些统计工作
具体过程略
}// 对于每个宏块,依次执行如下操作
做一些最后的设置
}//MotionEstimation
 
调用 set_timecodes 设置时间戳
调用 BitstreamWriteVopHeader VOP
具体过程略
 
对于每一个宏块,依次执行如下操作
{
如果该宏块的编码模式是 MODE_INTRA 或者 MODE_INTRA_Q
{
调用 CodeIntraMB 设置编码模式为 intra ,将所有和运动有关的变量设为 0
调用 MBTransQuantIntra 进行变换编码
调用 MBCoding 将该宏块编制成码流
Continue
}
 
调用 MBMotionCompensation 做运动补偿
{
如果编码模式是 MODE_NOT_CODED
用参考帧的相应宏块替代当前帧的当前宏块
Return
 
如果编码模式是 MODE_NOT_CODED 或者 MODE_INTER 或者 MODE_INTER_Q
{
如果 mb->mcsel 不为 0
GMC 的处理
Return
计算运动向量 dx dy
调用 compensate16x16_interpolate 进行运动补偿
{
如果采用 1/4 像素运动估计
否则,调用 get_ref 计算用于运动补偿的参考宏块的指针
调用 4 transfer_8to16sub 做亮度块的运动补偿,使得临时数组里存放的是残差,而原始图像里存放的是参考快的数据。
}//compensate16x16_interpolate
计算出用于色差运动补偿的 dx dy
}//MODE_NOT_CODED 或者 MODE_INTER 或者 MODE_INTER_Q
 
否则,那就是 MODE_INTER4V
{
根据是否使用 1/4 像素运动估计,计算出 4 个色度块的运动向量
以这 4 个运动向量为参数,调用 4 compensate8x8_interpolate ,该操作类似于 compensate16x16_interpolate ,不同在于一次只计算一个块。
计算出用于色差运动补偿的 dx dy
}
 
调用 CompensateChroma 计算色差块的运动补偿
{
调用 interpolate8x8_switch2 计算出 u 的插值
调用 interpolate8x8_halfpel_v 或者 interpolate8x8_halfpel_h 或者 interpolate8x8_halfpel_hv 做实际的插值操作,或者直接返回
调用 transfer_8to16sub_c u 份量的运动补偿
 
调用 interpolate8x8_switch2 计算出 v 的插值
调用 interpolate8x8_halfpel_v 或者 interpolate8x8_halfpel_h 或者 interpolate8x8_halfpel_hv 做实际的插值操作,或者直接返回
调用 transfer_8to16sub_c v 份量的运动补偿
}//CompensateChroma
 
}//MBMotionCompensation
 
如果需要编码,那么用 MBTransQuantInter 进行编码,并把结果返回给 pMB->cbp
{
调用 MBfDCT 进行宏块变换编码
调用 6 fdct
 
调用 MBQuantInter 进行量化
{
对于宏块里的每一块
{
调用 quant_h263_inter 进行量化
如果在量化后,前三个系数为 0 ,并且系数的绝对值之和小于阀值,那么标记该块为全 0 块,将标记存入 cbp 。否则,标记为非全 0 块,也将标记存入 cbp
}
}//MBQuantInter
 
调用 MBDeQuantInter 反量化
{
确定要使用的反量化函数
对于六个块里的每个块,如果 cbp 表示许可,都调用 dequant_h263_inter 反量化
}//MBDeQuantInter
 
调用 MBiDCT 做反离散余弦变换
对于六个块里的每个块,如果 cbp 表示许可,都调用 idct_int32 反量化
 
调用 MBTrans16to8 将恢复出的残差构成重建图像
{
确定具体执行的函数,分为 transfer_16to8copy transfer_16to8add
找到该宏块的 y u v 分量起始地址
对于六个块里的每个块,如果 cbp 表示许可,调用相应得函数执行重建。
}// MBTrans16to8
}//MBTransQuantInter
 
如果无残差,并且编码方式为 MODE_INTER ,并且帧方式是 P 帧,并且向量 2 分量都为 0 ,那么可以考虑 skip 模式
如果可以考虑 skip 模式,则做进一步检验,如果检验通过,那么
{
编码模式为 MODE_NOT_CODED ,并且在码流里做标记
Continue
}
 
调用 MBCoding 将这个宏块写入码流
{
写入非 NOT_CODED 标记
调用 CodeBlockInter 写入码流
{
编码 mcbpc
编码 cbpy
调用 CodeVector 编码运动向量
对六个块,如果 cbp 只是需要编码,调用 CodeCoeffInter 进行编码
}//CodeBlockInter
}// MBCoding
 
}// 对于每一个宏块,依次执行如下操作
 
更新 fcode
为下一帧的编码做简单的更新设置
统计该帧编码长度
}// FrameCodeP
 

你可能感兴趣的:(算法,image,测试,null,search,plugins)