DirectShow技術描述與應用(2) (轉)

amyz發表於2007-10-17
DirectShow技術描述與應用(2) (轉)[@more@] 

建立過濾器表:namespace prefix = o ns = "urn:schemas--com::office" />

標準表的建立

為了建立過濾器表, 你必須首先建立一個過濾器表管理器的例項,取得一個IGraphBuilder介面指標。

IGraphBuilder* pIGB;

HRESULT hr;

hr = CoCreateInstance(CLSID_FilterGraph,

  NULL,

  CLSCTX_INPROC_SERVER,

  IID_IGraphBuilder,

   (void **)&pIGB);

IGraphBuilder介面,顧名思義(as its name suggests),其中包含了建立過濾器表的方法。這些方法提供三個最基本的方式來建立表。

1.應用可以讓過濾器表管理自動來建立一個完整的表。

2.應用程式和過濾器表可以共用參與表的建立工作。

3.應用程式手動連線每個過濾器來完成整個表的建立

Approach #1: The Filter Graph Manager builds the entire graph

方式1:過濾器表管理器自主完成整個表的建立

方式1是假定你只想完成幾種通常格式的而設計的,這些格式可以是AVI,MPEG,WAV,等等。在DIrectShow中,術語還原(render)經常被引用,它的意思是在影片在顯示器上顯示,在音響中播放的播放方式。為了能夠使過濾器表管理器可以自動為還原指定的媒體檔案建立過濾器表,應用程式應當使用IGraphBuilder::RenderFile方法。下面將描述一個過濾器表建立表的幾個基本步驟。

 

查詢相應的過濾器(Finding the Filters)

建立過濾器表的第一步就是查詢並建立所需的過濾器例項。過濾器選擇操作首先是要確定媒體檔案格式。這是因為如果兩個過濾器不使用同一種公共媒體格式,那麼它們就不可能被連線起來。過濾器表管理器可以透過對檔名的字尾的檢查來確定媒體格式(如.wma,.avi,.wav等等)。如果透過字尾名不能確定媒體格式,那麼過濾器表管理器就過在檔案中查詢標識位元組(check bytes)來確定媒體格式,這是透過使用不同源過濾器進行讀取檔案,直到有匹配的源過濾器來實現。而且,這是透過使用IGraphBuilder::AddFilter來完現這一個任務的。

所有DirectShow過濾器在登錄檔裡都有唯一的GUID,還記錄著包括過濾器的類別,支援的格式,過濾器的merit等其它資訊。過濾器表管理器調查所有這些資訊來確定過濾器,並建立其例項。過濾器類別描述過濾器的主要使用用途。媒體格式資訊描述過濾器可以接受什麼媒體格式,又可以向它下游的過濾器傳遞何種媒體格式。merit值記錄著過濾器在表自動建立中被考慮的優先順序。如果中有兩個媒體格式資訊相近的過濾器,那麼過濾器表管理器會選擇merit級別高的過濾器。(有些過濾器表有意地定義較低的優先順序值,因為它們原意是為自定義過濾表而設計,使得應用必須手動向過濾器表進行新增)

為了搜尋登錄檔,過濾器表管理器建立一個DirectShow Filter Mapper的,並其IFilterMapper2::EnumMatchingFilters方法來完成搜尋。這個方法可以透過種類或媒體格式來列舉過濾器。它會返回一個標準的IEnumMoniker物件,其包含所有被搜尋到的過濾器的名字物件(monilders)。

 

向表中新增過濾器

如果找到可以讀取相應檔案格式的源過濾器的名字物件,過濾器表管理器使用CoCreateInstance(透過GUID)來建立相應過濾器的例項。這個源過濾器將是用做檔案源過濾器。過濾器表管理器呼叫IFilterGraph::AddFilter方法來向過濾器表新增這個過濾器。當過濾器被通知要加入表後,過濾器就會建立相應的媒體格式的輸出針。當建立針後,過濾器表管理器會從登錄檔裡找到可以接受其輸出針所輸出的媒體格式的過濾器,當找到匹配的,則向表中新增它的一個例項。

 

連線過濾器

當下遊過濾器被成功地添輯後,它就設定它的針,來準備與其上游的過濾器進行連線。過濾器表管理器詢問每一個表中的過濾器,確定其適當的針,並呼叫IGraphBuilder::Connect方法來連線它們。上游過濾器的輸出針和下游過濾器的輸入針必須協商確定使用何種資料媒體格式、確定由哪個針來提供為媒體樣本物件分配和存放媒體樣本物件記憶體的管理物件的工作。

 

完成表的建立

當針們(pins)建立好連線後,過濾器表管理器檢查媒體格式和搜尋支援新加入的過濾器的輸出針的媒體格式的過濾器,並再次加入表當中去。直到所需的過濾器都被新增到表裡去了。在檔案回放表中,當讀取一個未加工資料流時,第二個過濾器通常是解析或者分解過濾器。如:AVI,MPEG,分解它成音訊流和影片流。併為每一個流建立一個輸出針。如果資料流中有任何一個是被的,那麼下一個過濾器必須是解壓裝置,之後才能新增還原過濾器。

當表建立好之後,應用程式只需從過濾器表管理器QueryInterface出IMediaControl介面,呼叫其Run方法就可以開始播放檔案。

 

方式2:應用程式和過濾器表管理器共同參與表的建立工作

當你的過濾器表需要比簡單的播放檔案更復雜的功能時,你的程式至少要完成表的建立工作中的一部分。諸如,如果你要建立一個將AVI轉換到MPEG的表,那麼你仍可以透過IGraphBuilder::RenderFile方法來建立一個AVI回放表。但你必須通知表將輸出設定為MPEG檔案而不是顯示器和音響。這包括斷開和移除音訊還原過濾器和影片還原過濾器(這可以使用IFilterGraph::Dinnect和IFilterGraph::RemoveFilter)和新增MPEG影片壓縮過濾器、MPEG音訊壓縮過濾器、MPEG混合過濾器和一個寫檔案過濾器(file writer filter)。

它也許需要新增一箇中間(intermediary)過濾器來轉換MPEG壓縮所支援的媒體格式。應用程式可以直接新增中間過濾器,也可以使用IGraphBuilder::Connect方法來新增。當這個方法被呼叫,過濾器表管理器首先嚐試直接連線過濾器,如果它們不支援相同的媒體格式,那麼過濾器表管理器會搜尋並新增相應中間過濾器。如果防止過濾器表管理器嘗試新增中間過濾器的話,則應該使用IFilterGraph::ConnectDirect方法。

在上面的一部分,講解了應用程式參與了建立表的輸出部分。如果,應用程式只參與表的輸入部分(即源部分)。則可以使用IGraphBuilder::Render方法來讓過濾器表管理器自動完成表的輸出部分的建立。

 

方式3:應用程式自己建立整個表。

在一些情況中,你的應用程式需要完成整個表的建立。當然,這裡包括過濾器的新增以及每個過濾器的連線。這一種狀況中,你應該知道有哪些過濾器需要被新增入表中,所以這裡是不需要使用Filter Mapper物件來搜尋過濾器的。你可以使用IGraphBuilder::AddFilter方法來向表新增過濾器,並使用Connect或者ConnectDirect來連線過濾器。

 

在過濾器表中的資料流動

 

這部分將進步地描述媒體資料是如何在過濾器表中移動的。通常,你在編寫DirectShow應用程式時並用不著這些。不過,在某些情況中,這些資料可能是會對你有所幫助的。如果你正在寫一個DirectShow過濾器的話,那麼你就需要理解這部分資料。

 

 

傳輸(Transports)

為了使媒體資料可以在過濾器表中流動,DirectShow過濾器必須支援幾種內部協議的其中的一個。這些協議被稱為傳輸協議。當兩個過濾器連線時,它必須支援一種相同的傳輸協議;否則它們就無法媒體資料。通常,傳輸器需要一個支援特殊介面的針。當過濾器連線時,針將查詢得到(QueryInterface)其它針的這個介面。

大多數過濾器在主存中處理媒體資料,透過針的連線來將媒體資料傳遞給其它的過濾器。這個傳輸的方式叫做本地傳輸協議(local memory transport)。這種本地儲存傳輸協議在DirectShow中很普遍,但並不是說所有的過濾器都使用這種協議。如:有些過濾器是透過途徑來傳送媒體資料的,它們只使用針來傳送控制資訊。

DirectShow為本地儲存傳輸協議定義了兩種機制,分別是pushpull模式。在push模式中,由源過濾器來產生媒體資料並傳遞給它的下游過濾器。push模式中,在下游的過濾器總是被動地接收資料,處理它,傳遞其下游的過濾器。在pull模式中,源過濾器連線解析過濾器。解析過濾器向源過濾器傳送資料請求,源過濾器則傳送資料來回應其請求。push模式使用ImemInputPin介面,pull模式使用IasyncReader介面。

在DirectShow中,push模式的應用要比pull模式更為普遍。因此,本文將只考慮push模式的應用。我們將本部分的最後一節, pull模式,來講述IAsyncReader介面與IMemInputPin介面有什麼不同。

 

 

樣本與分配器(Allocators)

當針傳遞資料給其它針時,它並不直接傳遞其資料記憶體的指標。而是,傳遞一個管理記憶體資料的COM物件指標。這個物件就是媒體樣本,其向外暴露ImediaSample介面。接收針通呼叫ImediaSample介面的方法來對記憶體進行訪問。比較經典的方法有IMediaSample::GetPointer,IMediaSample::GetSize和IMediaSample::GetActualDataLength方法。

媒體樣本總是透過輸出針到輸入針的途徑向下游傳播。在push模式中,輸出針呼叫IMemInputPin::Receive來向輸入針傳遞媒體樣本,輸入針同步地處理資料(實際上,這些動作都是在Receive方法中完成)。或者在工作執行緒(worker thread)中非同步處理。如果輸入針在處理資料時需要等待資源,則可以阻塞(block)Receive方法。

另一個名為分配器的COM物件用於建立和管理媒體樣本。分配器向外暴露IMemAllocator介面.當過濾器需要媒體樣本時,它可以呼叫IMemAllocator::GetBuffer方法,來返回這個樣本的指標。每兩個針的連線中都共享一個分配器。當兩個針連線時,它們會決定由哪個過濾器來提出供分配符,它們並設定諸如記憶體區的數量和其它記憶體的大小。

 

樣本的引用計數

分配符只可以建立有窮數量的媒體樣本。在當其它人呼叫GetBuffer時,一些樣本可能在被使用。為了避免這種情況,分配器使用引用記數來保留樣本的使用記錄。GetBuffer方法會返回一個引用記數為1的媒體樣本。當如果引用記數變成0時,樣本就被收入到分配符的回收箱(pool)中,它就可以在下一次的GetBuffer呼叫時被使用。當樣本的引用記數大於0時,樣本就不會被GetBuffer所返回。當所有屬於分配器的樣本都在被使用(即引用記數大於0),則GetBuffer方法被阻塞,直到有一個樣本變成可用。(即樣子被放棄使用,引用記數等於0)

例如,如果輸入針接受到一個媒體樣子。如果它是在Receive方法中同步地處理媒體樣本,它是不會增加引用記數的。當Receive返回後,輸出針放棄(Release)媒體樣本,引用記數降為0,媒體樣本進行分配器的回收箱。如果輸入針是在工作執行緒中處理媒體樣本,則它就是給在Receive方法返回前,增加引用記數。那麼現在的引用記數就是2。當輸出針放棄了樣本後,引用記數變成了1,但樣本仍然沒有回到回收箱中。在工作執行緒完成處理後,執行緒呼叫Release來釋放樣本。

當一個針接收到樣本後,它可以複製其資料來其它的樣本當中去,也可以修改這個樣本並傳給它下一個過濾器。因此,一個媒體樣本是可以在整個表中進行流動的。每個過濾器在改變其資料時,都會呼叫其AddRef和Release方法改變其引用記數。因此,輸出針在其呼叫完Receive方法後,不得現次使用媒體樣本,因為其下游過濾器也許也在使用這個樣本。輸出針可以呼叫GetBuffer方法來獲得新的樣本。

這種機制很好地避免了記憶體的使用數量,因為過濾器在重複使用同一個記憶體區域。它也可以避免過濾器在處理時,出現記憶體訪問出界的情況。因為分配器維護著一個可使用樣本的列表。

A filter can use separate allocators for input and output. It might do this if it expands the input data (for example, by decompressing it). If the output is no larger than the input, a filter might process the data in place, without copying it to a new sample. In that case, two or more pin connections can share one allocator.

過濾器可以為輸入和輸出建立不同的分配器。當輸出比輸入的資料量大時,通常使用這種方法(比如,解壓縮)。如果輸出資料量沒有輸入的大,過濾器可以直接修改樣本上的資料,傳到下一個過濾器。在這種情況中,兩個或更多針的連線可以共享於一個分配器。

 

提交與解除提交分配器(Committing and Decommitting Allocators)

當過濾器第一次建立分配器時,分配器並沒有記憶體區域可供管理。基於這一點,這時任何的GetBuffer呼叫操作都會失敗。當表開始執行時,輸出針會呼叫IMemAllocator::Commit方法,來提交分配器,使用其開始進行記憶體的分配。之後,針們就可以呼叫GetBuffer方法了。

當錶停止運作時,針則會呼叫IMemAllocator::Decommit方法,來解除提交分配器。之後,任何的GetBuffer呼叫操作就會遭受失敗,真到分配器再一次被提交。同樣地,那些被阻塞的GetBuffer,以期得到可用的樣本的呼叫,也會被立即返回。(Decomit方法並不能實際釋放記憶體。例如,CMemeAllocator類需要呼叫它的析構來釋放記憶體。)

 (待續...)


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/10752019/viewspace-977252/,如需轉載,請註明出處,否則將追究法律責任。

相關文章