摘? 要: 討論了DirectShow過(guò)濾器組件的開發(fā)技術(shù),給出了網(wǎng)絡(luò)視頻應(yīng)用中的一個(gè)過(guò)濾器組件開發(fā)實(shí)例,。
關(guān)鍵詞: DirectShow? 過(guò)濾器? COM? 視頻應(yīng)用
?
1? DirectShow概述
DirectShow是Windows平臺(tái)下流行的流媒體開發(fā)體系,可以實(shí)現(xiàn)高質(zhì)量的音視頻采集,、編輯、編碼、解碼,、格式轉(zhuǎn)換、播放,。它解決了網(wǎng)絡(luò)音頻及視頻信息傳輸中數(shù)據(jù)量大,、數(shù)據(jù)源種類多、客戶端軟硬件環(huán)境不確定,、視頻音頻需要同步等問(wèn)題,因此有著廣泛的應(yīng)用,。
DirectShow使用模塊化的體系結(jié)構(gòu),最主要的組件是過(guò)濾器(Filter)。DirectShow把一系列過(guò)濾器組合起來(lái)形成DirectShow應(yīng)用程序。每個(gè)過(guò)濾器提供一種功能,如獲取數(shù)據(jù)源,、編碼,、解碼、播放等,。DirectShow提供了很多標(biāo)準(zhǔn)過(guò)濾器,用戶可以直接使用,。但由于媒體格式、壓縮方式,、硬件屬性等方面的特殊要求,用戶經(jīng)常需要自行開發(fā)過(guò)濾器來(lái)滿足具體需求,。
DirectShow應(yīng)用程序中主要包含以下3種過(guò)濾器:源過(guò)濾器(Source Filter)、轉(zhuǎn)換過(guò)濾器(Transform Filter),、呈現(xiàn)過(guò)濾器(Render Filter),分別負(fù)責(zé)獲取數(shù)據(jù)流,、處理數(shù)據(jù)流和播放數(shù)據(jù)流。有時(shí)還需要分解過(guò)濾器(Splitter Filter)和合并過(guò)濾器(Mux Filter)來(lái)分解和合并數(shù)據(jù)流,。
DirectShow是基于COM(組件對(duì)象模型)規(guī)范的,。過(guò)濾器是一種COM組件。應(yīng)用程序把多個(gè)過(guò)濾器組件組合起來(lái),形成對(duì)媒體流的處理流程,。這一整套過(guò)濾器集合被稱為過(guò)濾器圖(Filter Graph),。DirectShow提供FGM(Filter Graph Manager)組件來(lái)控制整個(gè)過(guò)濾器圖。過(guò)濾器前后相連,連接點(diǎn)也是COM對(duì)象,被稱為針腳(Pin),。
DirectShow應(yīng)用程序的原理圖如圖1所示,。來(lái)自文件系統(tǒng)或外設(shè)的數(shù)據(jù)先由過(guò)濾器處理,再存儲(chǔ)到文件系統(tǒng)或由外設(shè)播放。過(guò)濾器負(fù)責(zé)與文件系統(tǒng)和外設(shè)的交互,。應(yīng)用程序只需控制過(guò)濾器,不用關(guān)心其他軟件和硬件的具體情況,。
?
2?過(guò)濾器組件開發(fā)技術(shù)
DirectShow為過(guò)濾器組件開發(fā)提供了一套基類庫(kù)(Base Class Library),包括過(guò)濾器基類、針腳基類和一些輔助類,?;悗?kù)為過(guò)濾器組件的開發(fā)提供了一個(gè)框架,省去了復(fù)雜的底層編碼工作。用戶可將開發(fā)工作集中到如下二個(gè)方面:(1)傳輸和處理媒體流,。(2)將過(guò)濾器封裝為COM組件,。
2.1 媒體流的傳輸和處理
為了傳輸數(shù)據(jù),用戶過(guò)濾器先要與過(guò)濾器圖中其他過(guò)濾器連接起來(lái)。連接時(shí)要進(jìn)行媒體格式和內(nèi)存分配器的協(xié)調(diào),。過(guò)濾器之間通過(guò)針腳相連,。過(guò)濾器之間媒體格式和內(nèi)存分配器的協(xié)調(diào)實(shí)際上是通過(guò)針腳之間的通信來(lái)完成的?!?/P>
主動(dòng)連接方的過(guò)濾器的針腳首先獲取自身支持的所有媒體格式,然后把其中一種格式送交給被動(dòng)連接的一方,。被動(dòng)方的針腳進(jìn)行判斷:如果支持該格式,媒體格式協(xié)調(diào)成功;如果被動(dòng)方不支持該格式,就通知主動(dòng)方,主動(dòng)方再提供1種不同的格式送交被動(dòng)方,直到被動(dòng)方支持被提供的格式,協(xié)調(diào)成功,否則,當(dāng)主動(dòng)方用完所有支持的格式,協(xié)調(diào)失敗。
DirectShow過(guò)濾器使用一種稱作內(nèi)存分配器(Allocator)的COM對(duì)象管理媒體流數(shù)據(jù),。當(dāng)2個(gè)過(guò)濾器連接前,其中1個(gè)過(guò)濾器上的針腳提供1個(gè)內(nèi)存分配器,。另外1個(gè)過(guò)濾器上的針腳對(duì)這個(gè)內(nèi)存分配器進(jìn)行檢測(cè),。當(dāng)2個(gè)針腳都支持該內(nèi)存分配器時(shí),協(xié)調(diào)成功。
媒體流傳輸開始之前,內(nèi)存分配器負(fù)責(zé)創(chuàng)建一系列內(nèi)存緩沖區(qū),。媒體流傳輸時(shí),上游(Upstream)過(guò)濾器填充這些緩沖區(qū),并把它們傳送給下游(Downstream)過(guò)濾器,。DirectShow使用一種稱作媒體采樣包(Media Sample)的COM對(duì)象管理單個(gè)緩沖區(qū)。通過(guò)控制媒體采樣包對(duì)象,可以修改當(dāng)前緩沖區(qū)中的媒體類型,、時(shí)間戳等信息,也可以利用算法處理媒體數(shù)據(jù),從而實(shí)現(xiàn)對(duì)媒體流的處理,。
2.2 COM組件的實(shí)現(xiàn)
COM組件的實(shí)現(xiàn)包括如下內(nèi)容:用接口規(guī)定過(guò)濾器組件對(duì)外提供的功能;提供類廠,用以創(chuàng)建COM對(duì)象的實(shí)例;提供COM對(duì)象所在dll文件的各個(gè)輔助函數(shù),以完成COM組件在應(yīng)用程序中的載入和釋放,在注冊(cè)表中的注冊(cè)和注銷。
DirectShow中的過(guò)濾器,、針腳等COM對(duì)象通過(guò)接口對(duì)外提供各種功能,。除了提供標(biāo)準(zhǔn)的接口之外,DirectShow還提供了DECLARE_INTERFACE宏讓用戶自定義接口,從而滿足用戶對(duì)過(guò)濾器組件的指定要求。
COM實(shí)現(xiàn)機(jī)制中用類廠創(chuàng)建COM對(duì)象實(shí)例,。DirectShow提供了類廠類CClassFactory和類廠模板類CFactoryTemplate,。通過(guò)將不同的類廠模板的內(nèi)容填入類廠,實(shí)現(xiàn)不同的類廠對(duì)象,從而創(chuàng)建不同COM對(duì)象實(shí)例。
過(guò)濾器是dll文件格式的COM組件,需要以下函數(shù):DllMain(載入時(shí)的入口),、DllGetClassObject(創(chuàng)建類廠對(duì)象),、DllCanUnloadNow(判斷是否釋放dll)、DllRegisterServer(在注冊(cè)表中注冊(cè)dll),、DllUnregisterServer(在注冊(cè)表中反注冊(cè)dll),。DirectShow已經(jīng)實(shí)現(xiàn)了前3個(gè)函數(shù)。后面的2個(gè)函數(shù)通常調(diào)用DirectShow中的函數(shù)AMovieDllRegisterServer2()來(lái)實(shí)現(xiàn),即:
STDAPI DllRegisterServer()
???? ?? ?????? {?? return AMovieDllRegisterServer2(TRUE );}
????STDAPI DllUnregisterServer()
????????????? ? {?? return AMovieDllRegisterServer2(FALSE);}
3? 過(guò)濾器組件開發(fā)技術(shù)應(yīng)用實(shí)例
???下面介紹過(guò)濾器組件開發(fā)技術(shù)在網(wǎng)絡(luò)視頻服務(wù)中的一個(gè)應(yīng)用實(shí)例,?!熬W(wǎng)絡(luò)數(shù)字?jǐn)z像機(jī)”系統(tǒng)使用攝像機(jī)采集視頻,經(jīng)過(guò)編碼壓縮后發(fā)送到網(wǎng)絡(luò)上??蛻舳顺绦蚪邮諗?shù)據(jù)并解碼,。用戶過(guò)濾器利用這些數(shù)據(jù)生成視頻流,進(jìn)行播放或者錄像?!熬W(wǎng)絡(luò)數(shù)字?jǐn)z像機(jī)”客戶端程序的基本流程如圖2所示。
?
客戶端程序采用多線程的方式,網(wǎng)絡(luò)數(shù)據(jù)接收線程,、解碼線程與視頻流生成線程同時(shí)運(yùn)行,。在線程之間使用隊(duì)列存放數(shù)據(jù)。前一個(gè)線程將數(shù)據(jù)寫入隊(duì)列,后一個(gè)線程從隊(duì)列中取出數(shù)據(jù),。要實(shí)現(xiàn)的過(guò)濾器組件例程位于視頻流生成線程內(nèi),與解碼線程共享一個(gè)數(shù)據(jù)隊(duì)列,。此隊(duì)列放在一個(gè)自定義的類CDataAdmin中。解碼線程把數(shù)據(jù)放到隊(duì)列中,。用戶過(guò)濾器從隊(duì)列中取出數(shù)據(jù),生成視頻流,。
3.1 用戶過(guò)濾器的實(shí)現(xiàn)
(1)選擇合適的基類
用戶過(guò)濾器使用整個(gè)過(guò)濾器圖外部的數(shù)據(jù)生成視頻流,屬于源過(guò)濾器?;悗?kù)中的CSource類是源過(guò)濾器的基類,CSource使用CSourceStream基類作為它的針腳,。本例中從這2個(gè)類派生出CCustomFilter和CCustomPin,作為實(shí)際使用的過(guò)濾器類和針腳類,。
(2)通過(guò)自定義接口獲得隊(duì)列數(shù)據(jù)
為了獲得過(guò)濾器外部的隊(duì)列數(shù)據(jù),需要為CCustomFilter提供一個(gè)自定義的接口。下面的代碼定義了一個(gè)IDataSource接口:DECLARE_INTERFACE_(IDataSource,,IUnknown){STDMETHOD(SetData)(THIS_CDataAdmin*pData)PURE;},。CCustomFilter繼承該接口,對(duì)外提供了一個(gè)SetData()操作。SetData()將外部傳入的CDataAdmin*類型的指針賦值給CCustomFilter的成員變量,過(guò)濾器即獲取到外部隊(duì)列數(shù)據(jù),。
(3)協(xié)調(diào)媒體類型
CSourceStream基類完成了媒體類型協(xié)調(diào)中大部分的工作,用戶只需要指定過(guò)濾器針腳支持的媒體格式,。CSourceStream的成員函數(shù)GetMediaType()負(fù)責(zé)完成這個(gè)任務(wù),用戶必須在該函數(shù)中為過(guò)濾器指定媒體格式。媒體流的信息存放在一個(gè)VIDEOINFOHEADER的結(jié)構(gòu)中,指針pvi指向該結(jié)構(gòu),。函數(shù)GetMediaType()中指定媒體格式的代碼如下:
pMediaType->SetType(&MEDIATYPE_Video);
//設(shè)置媒體主類型
pMediaType->SetSubtype(&GetBitmapSubtype(&pvi->bmiHeader)); //設(shè)置媒體次類型
pMediaType->SetFormatType(&FORMAT_VideoInfo);
//設(shè)置媒體格式
pMediaType->SetTemporalCompression(FALSE);
//不壓縮媒體流
pMediaType->SetSampleSize(pvi->bmiHeader.biSizeImage); //設(shè)置媒體采樣包大小
(4)協(xié)調(diào)內(nèi)存分配器
CSourceStream基類完成了大多數(shù)內(nèi)存分配器的協(xié)調(diào)工作,。用戶還需要指定每個(gè)媒體采樣包的大小。CSourceStream基類的成員函數(shù)DecideBufferSize()負(fù)責(zé)完成此任務(wù),。下面是該函數(shù)中的主要代碼,。
pRequest->cbBuffer=pvi->bmiHeader.biSizeImage;
//獲取采樣包大小需求信息
ALLOCATOR_PROPERTIES Actual;
hr=pAlloc->SetProperties(pRequest,&Actual);
//指定采樣包大小,并返回實(shí)際的設(shè)置結(jié)果
(5)生成視頻流
CSourceStream基類的FillBuffer()成員函數(shù)負(fù)責(zé)把外部隊(duì)列數(shù)據(jù)加入到視頻流中。用戶可以在此函數(shù)內(nèi)部先處理數(shù)據(jù),再把處理過(guò)的數(shù)據(jù)加入視頻流中,。本例中經(jīng)用戶過(guò)濾器解碼后的數(shù)據(jù),不需要進(jìn)行處理,。函數(shù)FillBuffer()中的主要代碼如下。
//獲取當(dāng)前媒體采樣包對(duì)應(yīng)的緩沖區(qū)的地址和大小
BYTE*pData;
DWORD cbData;
pSample->GetPointer(&pData);
cbData=pSample->GetSize();
//獲取媒體信息
VIDEOINFOHEADER*pVih=(VIDEOINFOHEADER*)
m_mt.pbFormat;
//從數(shù)據(jù)隊(duì)列中取出數(shù)據(jù)填充到當(dāng)前緩沖區(qū)中
m_pFilePack=m_pPinData->GetDataBuffer();
memcpy(pData,,m_pFilePack,,min(pVih->
bmiHeader.biSizeImage,cbData));
//給媒體采樣包加上時(shí)間戳
REFERENCE_TIME rtStart=m_iFrameNumber
*m_rtFrameLength;
REFERENCE_TIME rtStop=rtStart+m_rtFrameLength;
pSample->SetTime(&rtStart,&rtStop);
?//幀計(jì)數(shù)器加1
m_iFrameNumber++;
(6)生成COM組件
過(guò)濾器開發(fā)工作的最后一步是將過(guò)濾器封裝成COM組件。此外,需要提供類廠模板,。代碼如下:
CFactoryTemplate g_Templates[]={g_wszCustomFilter,
&CLSID_CustomFilter,CCustomFilter∷CreateInstance,
NULL,NULL }; //將過(guò)濾器信息填入類廠模板
int g_cTemplates=sizeof(g_Templates)/sizeof(g_Templates[0]); //類廠模板個(gè)數(shù)
3.2 實(shí)際應(yīng)用效果
??? 在“網(wǎng)絡(luò)數(shù)字?jǐn)z像機(jī)”系統(tǒng)的客戶端應(yīng)用程序中使用上例的過(guò)濾器組件,若連接到視頻播放過(guò)濾器(Video Renderer)則可播放視頻,播放效果如圖3所示;若連接到寫文件過(guò)濾器(File Writer),可將視頻直接寫成硬盤文件,實(shí)現(xiàn)視頻錄像,。過(guò)濾器采用COM組件的形式,可方便地移植到其他機(jī)器和應(yīng)用程序中。
4? 結(jié)束語(yǔ)
過(guò)濾器組件在目前多種多樣的音頻視頻流媒體應(yīng)用中發(fā)揮著重要作用,。過(guò)濾器組件的開發(fā)具有較大的實(shí)用價(jià)值,但有一定的難度和復(fù)雜性,。本文討論了用戶過(guò)濾器開發(fā)中的原理和技術(shù)。文中過(guò)濾器組件例子的開發(fā)過(guò)程具有較大的通用性,可供其他開發(fā)者參考,。
?
參考文獻(xiàn)
1? Kruglinski D J.VC++技術(shù)內(nèi)幕(第4版).北京:清華大學(xué)出版社,1999
2? 潘愛民.COM原理和應(yīng)用.北京:清華大學(xué)出版社,1999