摘要:《VisualC++音頻/視頻處理技術(shù)及工程實(shí)踐》第14章XviDCODEC實(shí)現MPEG-4編/解碼,本章首先介紹MPEG-4視頻編碼算法的原理,對MPEG-4編碼算法的工程實(shí)現XviDCODEC的工作過(guò)程做詳細的介紹。本節為MPEG-4編/解碼的概述。
第14章 XviD CODEC實(shí)現MPEG-4編/解碼
視頻編/解碼是視頻應用的核心技術(shù),如可視電話(huà)、視頻監控、DVD、視頻點(diǎn)播(VOD)等。ISO制定的MPEG-1編/解碼標準在VCD中應用,MPEG-2在DVD、數字電視等高質(zhì)量圖像中應用。為滿(mǎn)足多媒體技術(shù)的發(fā)展及人們更多的技術(shù)需求,ISO于1998年制定了MPEG-4音/視頻編/解碼標準,其碼流更低、檔級更豐富,如在兼容傳統視頻編碼的基礎上增加了基于對象的編碼,交互性更強。視頻監控DVR應用中以MPEG-4標準為主流,這些應用中有專(zhuān)用芯片(ASIC)、DSP可編程芯片、軟件編程CPU實(shí)現等技術(shù)方案。MPEG-4的工程實(shí)現從最早的微軟,到后來(lái)的DivX、XviD。目前大家移植開(kāi)發(fā)應用的都是XviD,它是公認的最好的開(kāi)源MPEG-4算法工程。
本章重點(diǎn)
MPEG-4編/解碼概述
XviD CODEC編/解碼分析
運行XviD CODEC系統
系統效果展示
14.1
視頻編/解碼已經(jīng)是一個(gè)非常熾熱的行業(yè),有許多公司、企業(yè)在基于各種平臺ASIC、DSP、FPGA、ARM或普通CPU,如Intel/AMD等,實(shí)現視頻編/解碼應用。視頻監控、視頻會(huì )議、網(wǎng)絡(luò )視頻、數字硬盤(pán)錄像機、MP4等影音設備、影音文件都無(wú)一不使用視頻編碼和解碼。視頻編/解碼是一個(gè)有廣大應用前景的行業(yè),未來(lái)的3G移動(dòng)通信就可以實(shí)現彼此視頻圖像的收發(fā)。數字電視中的MPEG-2標準、主流多媒體壓縮板卡、嵌入式視頻編碼卡、視頻會(huì )議等采用了MPEG-4、H.263等,以及國產(chǎn)的AVS、國際的H.264/AVC、微軟的WMV9等在視頻應用中視頻編/解碼算法占據了核心地位。
XviD是開(kāi)源的MPEG-4視頻編/解碼CODEC,標準C語(yǔ)言開(kāi)發(fā)、部分核心函數采用了MMX/SSE/SSE2媒體匯編指令優(yōu)化。XviD實(shí)現了MPEG-4標準中的ASP(AdvancedSimple Profile),編/解碼效率能夠在雙核Intel CPU1.6GB、1GB內存配置的計算機上,實(shí)時(shí)運行4路D1的視頻編碼。下面就介紹XviD實(shí)現MPEG-4編碼和解碼算法的過(guò)程。
14.1.1
MPEG-4(ISO/IEC14496)算法是ISO的運動(dòng)圖像專(zhuān)家組MPEG(Moving Pictures ExpertGroup)于1998年發(fā)布的。它設計的初衷是第二代圖像編碼標準,即對象編碼。不過(guò),該算法并不包含對象的分割方法,只是提供了對分割對象后的編碼方法。但是,對象分割目前還是一個(gè)難點(diǎn),還沒(méi)有較好的突破。所以,目前的MPEG-4視頻算法應用基本都是基于像素的傳統視頻編碼,即混合編碼技術(shù),編碼對象不是目標對象而是圖像宏塊,但算法中仍然有視頻編碼對象的概念VOP,是把整幀圖像作為一個(gè)對象進(jìn)行編碼的。
14.1.2
目前,網(wǎng)絡(luò )上DivX和XviD格式的電影、影音文件并存。系統只安裝DivX5解碼器不能播放XviD格式的文件。而只安裝XviD,則可以順利播放DivX 5格式的文件。只是在播放DivX5文件的時(shí)候,速度不是很令人滿(mǎn)意。XviD在播放DivX 5的文件還不是很完善。雖然XviD目前來(lái)講與DivX5相比,優(yōu)勢不是太明顯。但是大家很看好XviD,首先它的源代碼絕對公開(kāi),這就使得有更多的人投入到XviD的研發(fā)之中。另外,由于完全重寫(xiě)DivX的源代碼,新的XviD去除了DivX的Bug;目前XviD的開(kāi)發(fā)人員有很多都是當初DivX的研發(fā)人員,對DivX的錯誤了解得非常清楚,重寫(xiě)之后XviD的優(yōu)勢可見(jiàn)一斑。另外,DivX4、DivX5雖然版本不斷更新,但是功能并沒(méi)有提高多少,優(yōu)勢不明顯。流行的視頻CODEC都支持XviD:Transcode、Mencoder、Mplayer等。
14.2
XviD Video CODEC實(shí)現的是MPEG-4的SP(Simple Profile)和ASP(AdvancedSimple Profile)標準,CODEC包括視頻編碼算法和視頻解碼算法。XviDCODEC于2001年創(chuàng )立,是MPEG-4視頻編碼算法的實(shí)現。早期的版本(0.9.x)實(shí)現了MPEG-4SP版本的編碼和解碼,XviD 1.0版本及其子版本實(shí)現了MPEG-4ASP視頻壓縮,包括所有高級編碼工具,如碼流控制、B幀編碼、1/4像素運動(dòng)補償和全局運動(dòng)補償GMC等。即將開(kāi)始研發(fā)的XviD2.0增加了對MPEG-4/AVC(Advanced VideoCoding)等更高檔級的編/解碼支持,編碼壓縮性能相比早期版本將大幅提升。
XviDCODEC編/解碼算法的工作流程基本都包含3個(gè)過(guò)程:初始化CODEC、使用CODEC、銷(xiāo)毀CODEC。初始化CODEC過(guò)程完成CODEC的創(chuàng )建句柄、分配必須的內存空間;循環(huán)使用CODEC完成視頻圖像的編碼、MPEG-4碼流的解碼;銷(xiāo)毀CODEC完成初始化的反工作。XviDCODEC使用標準C語(yǔ)言編程,同時(shí)CODEC的核心模塊圖像塊類(lèi)型轉換復制、DCT變換、系數量化、SAD計算、運動(dòng)補償MC、CBP計算等都實(shí)現了多個(gè)CPU:X86的32/64位、ia64等的匯編優(yōu)化。
14.2.1
XviDCODEC以動(dòng)態(tài)庫和靜態(tài)庫的形式供應用程序使用。這里以靜態(tài)庫為對象,展現視頻編碼和解碼的設計過(guò)程。通過(guò)代碼分析深入掌握視頻編/解碼的工作原理。XviD的MPEG-4算法的視頻編碼和解碼在同一個(gè)工程中。視頻編碼使用一個(gè)函數,根據傳入的參數決定初始化、編碼和銷(xiāo)毀編碼器,視頻解碼函數調用與之類(lèi)似。
1.MPEG-4視頻編碼
XviD的MPEG-4視頻編碼支持ASP@L5檔級,并且絕大部分的視頻應用不超過(guò)720像素576像素的分辨率,盡管XviD最高可支持4096像素 4096像素分辨率的視頻編碼。而實(shí)際上在視頻監控應用中,CIF(352像素288像素)分辨率更常用。另外,盡管XviDCODEC支持高檔應用:B幀編碼、1/4精度像素和全局運動(dòng)補償GMC。但是在使用如DSP硬件編碼時(shí),這些功能是簡(jiǎn)化了的,即實(shí)現MPEG-4的SP檔級。而在PC上開(kāi)發(fā)XviDMPEG-4算法時(shí),這些高檔應用可以考慮打開(kāi),但是大量額外的計算量與獲得的編碼性能并不成比例。所以在后面的介紹中及在實(shí)際的應用中也確實(shí)如此,以MPEG-4的SP檔級為主要介紹對象。
MPEG-4視頻編碼是典型的混合編碼技術(shù),即使用變換、量化和編碼三步驟對圖像數據或圖像差值作處理。I幀(關(guān)鍵幀KeyFrame)是幀內編碼,即對圖像數據做編碼,利用了圖像的空間冗余;P幀編碼主要是利用前向預測,對宏塊差值編碼,利用了圖像的時(shí)間冗余。統計來(lái)看,P幀編碼是主流,因為I幀有一定的間隔。P幀編碼中的技術(shù)核心是搜索與當前宏塊的最佳匹配塊,即運動(dòng)估計,搜索的窗口越大,宏塊越匹配,但隨之運算量也就越大。運動(dòng)估計技術(shù)(MotionEstimation,ME)是任何視頻編碼算法的核心。這里以圖像大小為352像素288像素、格式為I420、編碼I、P幀為例介紹MPEG-4視頻編碼流程,如圖14-1所示。

每個(gè)模塊的功能簡(jiǎn)要介紹如下。
從文件中讀取一幀圖像,大小為352像素×288像素×3/2(包含了亮度和色度)。
以宏塊為單位進(jìn)行編碼,一幀圖像中所有宏塊編碼完成,也就完成了對當前幀的編碼。
對當前宏塊進(jìn)行運動(dòng)估計(如果是I幀,則不用),根據SAD值決定匹配塊。
對8 8塊進(jìn)行編碼,按照其在宏塊中排列的順序進(jìn)行。首先將當前宏塊同參考宏塊作差值,然后再對差值進(jìn)行編碼(I幀不用)。
對當前編碼塊(或者經(jīng)過(guò)運動(dòng)補償的差值)進(jìn)行DCT變換。
對DCT變換后的系數進(jìn)行量化。
反量化模塊是量化模塊的逆過(guò)程。
反DCT模塊是DCT模塊的逆過(guò)程。
AC/DC預測,即在編碼I幀時(shí),對當前宏塊的第一行或者第一列系數同它周?chē)哪骋粔K作一個(gè)差值,進(jìn)一步增加零系數,降低比特率。
反DCT后的宏塊作運動(dòng)補償得到當前宏塊的重建值,稱(chēng)為重建宏塊。
碼流合并模塊,形成最后的視頻編碼碼流。碼流由碼流頭開(kāi)始,然后是具體的幀。在編碼之前,模塊首先向輸出流文件中寫(xiě)入碼流頭信息。然后寫(xiě)入第一幀的內容,每一幀同樣由幀頭信息開(kāi)始,幀頭信息同當前的編碼內容是緊密相關(guān)的,后面的幀數據必須完全符合它規定的一些特性,譬如當前幀的量化值、運動(dòng)補償的搜索范圍等。接下來(lái)是具體的幀數據,并且是按照宏塊組織的。宏塊內容包含當前宏塊的編碼信息,例如當前宏塊是否編碼、編碼類(lèi)型等,接著(zhù)是運動(dòng)矢量的數據,最后是具體的6個(gè)塊的數據。
XviDCODEC為了檢測運行平臺的CPU,在編/解碼器初始化前,給開(kāi)發(fā)者提供了可以指定平臺的接口,另外程序也能夠自動(dòng)檢測CPU。然后針對不同的平臺初始化核心模塊的函數指針,編碼和解碼前都要做該項工作。
xvid_gbl_init.cpu_flags = 0;
xvid_global(NULL, XVID_GBL_INIT, &xvid_gbl_init,NULL);
//………………
int xvid_global(void *handle, int opt, void *param1, void*param2)
{
switch(opt)
case XVID_GBL_INIT:
return xvid_gbl_init((xvid_gbl_init_t*)param1);
case XVID_GBL_INFO:
return xvid_gbl_info((xvid_gbl_info_t*)param1);
case XVID_GBL_CONVERT:
return xvid_gbl_convert((xvid_gbl_convert_t*)param1);
default :
return XVID_ERR_FAIL;
}
}
上述代碼在調用初始化編碼前調用一次,主要是初始化內部的函數指針,如通過(guò)檢測CPU平臺,使用MMX或SSE指令優(yōu)化的函數初始化函數指針,創(chuàng )建VLC熵編碼的碼表等。主要函數為xvid_gbl_init。
XviD的MPEG-4編碼算法實(shí)現以一個(gè)函數及其3個(gè)不同的參數傳遞來(lái)完成。
#define XVID_ENC_CREATE0
#define XVID_ENC_DESTROY 1
#define XVID_ENC_ENCODE2
extern int xvid_encore(void *handle, int opt, void *param1, void *param2);
編碼器的入口函數xvid_encore的實(shí)現如下:
int xvid_encore(void *handle, int opt, void *param1, void*param2)
{
switch (opt) {
caseXVID_ENC_ENCODE:
return enc_encode((Encoder *) handle,(xvid_enc_frame_t *)
param1,(xvid_enc_ stats_t *) param2);
caseXVID_ENC_CREATE:
return enc_create((xvid_enc_create_t *) param1);
case XVID_ENC_DESTROY:
return enc_destroy((Encoder *) handle);
default:
return XVID_ERR_FAIL;
}
}
14.2.1
上述程序是編碼器的所有功能函數,創(chuàng )建編碼器實(shí)例、使用編碼器編碼圖像、銷(xiāo)毀編碼器。在應用層中,編碼器可以有多個(gè),這就通過(guò)編碼句柄handle來(lái)控制不同的編碼器。創(chuàng )建編碼器enc_create、銷(xiāo)毀編碼器enc_destroy均只調用一次,循環(huán)調用enc_encode編碼圖像幀。
下面就分別詳細介紹這3個(gè)函數的功能和實(shí)現過(guò)程。
1)XviD創(chuàng )建編碼器
創(chuàng )建MPEG-4編碼器,首先創(chuàng )建編碼器實(shí)例句柄,然后在該句柄下執行對編碼器的參數配置、圖像參數獲取和空間申請等工作,這樣在多路編碼時(shí),可以通過(guò)句柄來(lái)控制每一路的編碼。
函數enc_create()流程如圖14-2所示。
![]() |
| 圖14-2 |
intenc_create(xvid_enc_create_t * create)
{
Encoder *pEnc;
pEnc = (Encoder *) xvid_malloc(sizeof(Encoder), CACHE_LINE);
if (pEnc == NULL)return XVID_ERR_MEMORY;
memset(pEnc, 0, sizeof(Encoder));
pEnc->mbParam.profile = create->profile;
pEnc->mbParam.global_flags = create->global;
pEnc->mbParam.width = create->width;
pEnc->mbParam.height = create->height;
pEnc->mbParam.mb_width = (pEnc->mbParam.
width + 15) / 16;
pEnc->mbParam.mb_height = (pEnc->mbParam.
height + 15) / 16;
pEnc->mbParam.edged_width = 16 * pEnc->
mbParam.mb_width + 2 * EDGE_SIZE;
pEnc->mbParam.edged_height = 16 * pEnc->
mbParam.mb_height + 2 * EDGE_SIZE;
pEnc->mbParam.fincr = MAX(create->fincr,
0);
pEnc->mbParam.fbase = create->fincr
<= 0 ? 25 : create->fbase;
pEnc->mbParam.frame_drop_ratio = MAX
(create->frame_drop_ratio, 0);
pEnc->mbParam.iMaxKeyInterval = create->max_
key_interval <= 0 ? (10 * (int)pEnc->mbParam.fbase) /
(int)pEnc->mbParam.fincr : create->max_key_interval;
pEnc->current = xvid_malloc(sizeof(FRAMEINFO), CACHE_LINE);
pEnc->reference = xvid_malloc(sizeof(FRAMEINFO), CACHE_LINE);
if (pEnc->current == NULL || pEnc->reference == NULL)
goto xvid_err_memory1;
pEnc->current->mbs =
xvid_malloc(sizeof(MACROBLOCK) * pEnc->mbParam.mb_width *
pEnc->mbParam.mb_height, CACHE_LINE);
pEnc->reference->mbs =
xvid_malloc(sizeof(MACROBLOCK) * pEnc->mbParam.mb_width *
pEnc->mbParam.mb_height, CACHE_LINE);
if (pEnc->current->mbs == NULL || pEnc->reference->mbs == NULL)
goto xvid_err_memory2;
image_null(&pEnc->current->image);
image_null(&pEnc->reference->image);
image_null(&pEnc->vInterH);
image_null(&pEnc->vInterV);
image_null(&pEnc->vInterHV);
if (image_create(&pEnc->current->image, pEnc->mbParam.
edged_width,pEnc->mbParam. edged_height) < 0)
goto xvid_err_memory3;
if(image_create(&pEnc->reference->image,pEnc->mbParam.
edged_width,pEnc-> mbParam.edged_height)< 0)
goto xvid_err_memory3;
if (image_create(&pEnc->vInterH, pEnc->mbParam.
edged_width, pEnc->mbParam. edged_height) < 0)
goto xvid_err_memory3;
if (image_create(&pEnc->vInterV, pEnc->mbParam.
edged_width, pEnc->mbParam. edged_height) < 0)
goto xvid_err_memory3;
if (image_create(&pEnc->vInterHV, pEnc->mbParam.
edged_width, pEnc->mbParam. edged_height) < 0)
goto xvid_err_memory3;
pEnc->queue_head = 0;
pEnc->queue_tail = 0;
pEnc->queue_size = 0;
pEnc->queue = xvid_malloc((1) * sizeof(QUEUEINFO),CACHE_LINE);
if(image_create(&pEnc->queue[0].image,pEnc->mbParam.
edged_width,pEnc-> mbParam.edged_height) < 0)
goto xvid_err_memory5;
pEnc->mbParam.m_stamp = 0;
pEnc->current->stamp = 0;
pEnc->reference->stamp = 0;
pEnc->iFrameNum = 0;
create->handle = (void *) pEnc;
return 0;
xvid_err_memory5:
image_destroy(&pEnc->queue[0].image,pEnc->mbParam.
edged_width,pEnc->mbParam.edged_height);
xvid_free(pEnc->queue);
image_destroy(&pEnc->current->image, pEnc->mbParam.
edged_width,pEnc->mbParam. edged_height);
image_destroy(&pEnc->reference->image, pEnc->mbParam.
edged_width,pEnc->mbParam. edged_height);
image_destroy(&pEnc->vInterH, pEnc->mbParam.
edged_width,pEnc->mbParam.edged_ height);
image_destroy(&pEnc->vInterV, pEnc->mbParam.
edged_width,pEnc->mbParam.edged_ height);
image_destroy(&pEnc->vInterHV, pEnc->mbParam.
edged_width,pEnc->mbParam.edged_ height);
xvid_err_memory2:
xvid_free(pEnc->current->mbs);
xvid_free(pEnc->reference->mbs);
xvid_err_memory1:
xvid_free(pEnc->current);
xvid_free(pEnc->reference);
xvid_free(pEnc);
xvid_err_memory3:
create->handle = NULL;
return XVID_ERR_MEMORY;
}
上述代碼完成編碼器的創(chuàng )建、編碼參數的初始化、工作空間的申請等。編碼器的初始化函數可以不用過(guò)多地考慮優(yōu)化,因為這個(gè)函數只調用一次,不像編碼函數多次循環(huán)地調用。
聯(lián)系客服