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
?