一個清華學子寫的關於directshow的學習心得【轉】
學習DirectShow有一段時間了,把這段學習過程中翻譯出來的SDK與大家分享,同時也希望專家們指出我理解上的錯誤,萬分感謝。1. DirectShow介紹 DirectShow是一個windows平臺上的流媒體框架,提供了高質量的多媒體流採集和回放功能。它支援多種多樣的媒體檔案格式,包括ASF、 MPEG、AVI、MP3和WAV檔案,同時支援使用WDM驅動或早期的VFW驅動來進行多媒體流的採集。DirectShow整合了其它的 DirectX技術,能自動地偵測並使用可利用的音視訊硬體加速,也能支援沒有硬體加速的系統。 DirectShow大大簡化了媒體回放、格式轉換和採集工作。但與此同時,它也為使用者自定義的解決方案提供了底層流控制框架,從而使使用者可以自行建立支援新的檔案格式或其它用途的DirectShow元件。 以下是幾個使用DirectShow編寫的典型應用:DVD播放器、視訊編輯應用、AVI到ASF轉換器、MP3播放器和數字視訊採集應用。 DirectShow是建立在元件物件模型(COM)上的,因此當你編寫DirectShow應用時,你必須具備COM客戶端程式編寫的知識。對於大部分 的應用,你不需要實現自己的COM物件,DirectShow提供了大部分你需要的DirectShow元件,但是假如你需要編寫自己的 DirectShow元件,你還需要具備編寫COM元件的知識。1.1. DirectShow支援的格式
*DirectShow開發需要什麼樣的編譯器? 任何能夠產生COM物件的編譯器都可以。 *DirectShow和DirectX的其它元件的關係 DirectShow和DirectX的其它元件在內部進行聯絡。DirectShow在硬體的支援下使用DirectSound和 DirectDraw。Video Renderer和Overlay Mixer使用DirectDraw 3和DirectDraw5表面(surfaces)。Video Mixing Renderer 7(只支援WINXP)使用DirectDraw7表面。Video Mixing Renderer 9使用最新的(目前是Directx9)Direct3D API函式。即便是某個應用程式包含了DirectX其它元件,你也不必使用其它元件的API去編寫它。參考SDK的例子:Texture3D Sample。 *DirectShow與ActiveMovie的關係? ActiveMovie是DirectShow原來的名稱,現已不再使用,但是一部分API仍保留了"AM"的字首,比如AM_MEDIA_TYPE和IAMVideoAccelerator。 *DirectShow是限於多媒體應用嗎? DirectShow預設包含的元件主要是為音視訊流設計的,但是,DirectShow框架已經成功地用於其它資料流的解決方案中。 *GraphEdit工具有原始碼嗎?GraphEdit.exe是否可再發布? 沒有原始碼,不可再發布。 *DMO可以代替DirectShow filter嗎? 在編寫編碼器、解碼器、效果器應用時,鼓勵用DMO代替DirectShow filter。在其它的應用中,使用DirectShow filter可能會比較合適。 1.2.2. 程式編寫問題 *如何設定編譯環境,需要哪些標頭檔案和庫? 參考"設定編譯環境"章節 *GraphEdit列示了很多沒有文件支援的filter,它們都是些什麼? GraphEdit枚舉了所有作為filter型別註冊在系統中的filter,包括由第三方應用程式安裝的filter,以及其它微軟技術如 Windows Media或NetMeeting安裝的,另外,一些DirectShow filter被用來做硬編碼或硬解碼驅動的外殼。Microsoft H.263 Video Codec用於NetMeeting,不再被DirectShow支援。 *如何知道DirectShow已經被安裝? 呼叫CoCreateInstance建立一個Filter Graph Manager例項,如果成功,表示DirectShow已經被安裝,下面是一個例子:
IGraphBuilder *pGraph;
HRESULT hr = CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC_SERVER, IID_IGraphBuilder, (void **) &pGraph); |
*如果不通過屬性設定頁來更改filter的設定? 當然是通過filter提供的介面羅。如果沒有提供,就沒有辦法啦 *DirectShow能通知應用程式當前回放位置嗎? 不提供回撥來通知位置,需要使用一個計時器定時呼叫IMediaSeeking::GetCurrentPosition方法來得到當前回放位置。 *filter執行在哪個特權級別下? 執行在Ring 3特權級別下,某些流控制驅動(如音視訊採集驅動)執行在Ring 0特權級別下。 *需要一個Kernel偵錯程式嗎? 這依據具體的專案。安裝DirectX除錯執行時庫(DirectX debug runtime library)意味著安裝除錯驅動(Debug driver)和其它核心元件(kernel mode component),因此如果你的應用程式在其中的某個元件中產生了一個除錯斷言(debug assert),你的機器就會自動重啟除非你擁有一個kernal偵錯程式。 *DEFINE_GUID巨集是怎麼工作的? 使用DEFINE_GUID巨集可以讓你通過包含同一個標頭檔案來定義GUID值而不必使用extern關鍵詞。比如,你的工程中有三個源文 件:src1.cpp,src2.cpp,src3.cpp,它們都使用一個相同的GUID值,而為了保證一致性,這個GUID只能在你的工程中定義一 次,這時,其它的原始檔必須定義外部引用來使用它。用了DEFINE_GUID,你可以使用在所有原始檔中包含同一個標頭檔案,在標頭檔案中這樣定義 GUID:
DEFINE_GUID(CLSID_MyObject, 0x00000000, 0x0000, 0x0000, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00); |
這個例子中GUID為0,實際程式設計中請用Guidgen工具來產生一個GUID,在其中的一個原始檔中,在你的標頭檔案前包含initguid.h,如:
// Src1.cpp #include #include "MyGuids.h" // Src2.cpp #include "MyGuids.h" // Src3.cpp #include "MyGuids.h" |
在沒有包含Initguid.h的地方,DEFINE_GUID巨集建立外部引用來使用GUID值,在包含Initguid.h的地方,DEFINE_GUID重定義DEFINE_GUID巨集以產生GUID的定義。 如是沒有在任何地方新增Initguid.h,你會得到一個連結錯誤:"unresolved external symbol." ,如果同樣的GUID包含Initguid.h兩次,會得到編譯錯誤"redefinition; multiple initialization."要解決這些問題,請確認Initguid.h只包含一次。同樣的,不要包含Initguid.h到預編譯標頭檔案中去,因 為預編譯標頭檔案會被每個原始檔包含。
2. 開始DirectShow旅程 這個章節的內容主要是編寫DirectShow應用所需的一些基本概念,可以把它當作一個高階介紹,理解這些內容只需具備一般的程式設計和有關多媒體的知識。2.1. 設定DirectShow開發的編譯環境 這節內容描述瞭如何來編譯DirectShow應用。你可以使用命令列形式來編譯一個工程,也可以在Microsoft Visual Studio整合環境下(包含VC++)實現。 標頭檔案: 所有的DirectShow應用都需要Dshow.h這個標頭檔案,某些DirectShow介面需要附加的標頭檔案,參考介面的說明視具體情況定。 庫檔案: DirectShow使用以下庫檔案: Strmiids.lib 輸出類標識(CLSID)和介面標識(IID),所有DirectShow應用均需此庫。 Quartz.lib 輸出AMGetErrorText函式,如果不呼叫此函式,此庫不是必需的。 有了以上這些標頭檔案和庫檔案,你已經可以編寫DirectShow應用了,但是微軟建議使用DirectShow基類庫來編寫filter,這樣可以大大 減少程式編寫的工作量。要使用DirectShow基類庫,需要先編譯它,基類庫位於SDK的Samples\Multimedia \DirectShow\BaseClasses資料夾下,包含兩個版本的庫:釋出版(retail version)Strmbase.lib和除錯版(debug version)Strmbasd.lib。具體參見"建立DirectShow Filter"一節。2.2. DirectShow應用程式程式設計簡介 這節介紹DirectShow用到的一些基本術語和概念,看完這節後,你將能夠編寫你的第一個DirectShow應用程式。 Filter和Filter Graph 一個DirectShow應用程式是由一個個稱為filter的軟體構件組合而成的,filter執行一些多媒體流的操作,如:讀檔案、從視訊採集裝置中獲得視訊、將不同的格式的流解碼如MPEG1、將資料送到圖形卡或音效卡中去。 Filter接收輸入併產生輸出。舉個例子,一個解碼MPEG1視訊流的filter,輸入MPEG1格式的視訊流,輸出一系列未壓縮的視訊幀。 在DirectShow中,應用程式要實現功能就必須將這些filter連結在一起,因而一個filter的輸出就變成了另一個filter的輸入。這一系列串在一起的filter稱為filter graph。例如,下圖就顯示了一個播放avi檔案的filter graph: File Source(Async) filter從硬碟中讀取avi檔案;AVI Splitter filter分析檔案並將其分解成兩個流:一個壓縮的視訊流和一個音訊流;AVI Decompressor filter將視訊幀解碼,Video Renderer filter將解碼後的視訊幀通過DirectDraw或GDI顯示出來;Default DirectSound Device filter使用DirectSound播放音訊流。 應用程式沒有必要對這些資料流進行管理,而是通過一個叫Filter Graph Manager這個上層元件來控制這些filter。應用程式呼叫上層API如"Run"(通過graph移動資料)或"Stop"(停止移動資料)。如 果你需要對資料流作更多的操作,你可以通過COM介面直接進入filter。Filter Graph Manager同樣也輸出事件通知給應用程式。 Filter Graph的另一個用途是將filter連在一起建立一個filter graph。 編寫一個DirectShow應用程式大體需要三個步驟: 1.建立一個Filter Graph Manager的例項 2.使用Filter Graph Manager建立一個filter graph,此時,需要已經具備所有必需的filter。 3.使用Filter Graph Manager控制filter graph和通過這些filter的流,在這個過程中,應用程式會收到Filter Graph Manager傳送的事件。 完成這些後,應用程式需釋出這個Filter Graph Manager和所有的filter。2.3. 播放一個檔案 這一章以本節這個有趣的例子來結束,這個例子是一個播放音訊或視訊檔案的簡單控制檯程式。程式只有寥寥數行,但卻展示了DirectShow程式設計的強大能力。 正如上一節所講的建立DirectShow應用程式的三個步驟,第一步,首先,需要呼叫CoInitialize來作初始化,然後呼叫CoCreateInstance建立Filter Graph Manager:
HRESULT hr = CoInitialize(NULL); if (FAILED(hr)) { return; } IGraphBuilder *pGraph; HRESULT hr = CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC_SERVER, IID_IGraphBuilder, (void **)&pGraph); |
如上所示,類識別符號(CLSID)是CLSID_FilterGraph。Filter Graph Manager由程序內DLL(in-process DLL)提供,因此引數3,dwClsContext的值為CLSCTX_INPROC_SERVER。由於DirectShow執行自由執行緒模式 (free-threading model),所以你同樣可以使用COINIT_MULTITHREADED引數來呼叫CoInitializeEx。 第二步是建立filter graph,呼叫CoCreateInstance得到的IGraphBuilder介面包含了大部分建立filter graph的方法。在這個例子中還需要另外兩個介面:IMediaControl和IMediaEvent。 IMediaControl控制資料流,它包含開啟和停止graph的方法;IMediaEvent包含從Filter Graph Manager獲取事件的方法,在這個例子中,這個介面用來得到回放結束事件。 所有這些介面由Filter Graph Manager提供,使用得到的IGraphBuiler介面指標來查詢得到。
IMediaControl *pControl; IMediaEvent *pEvent; hr = pGraph->QueryInterface(IID_IMediaControl, (void **)&pControl); hr = pGraph->QueryInterface(IID_IMediaEvent, (void **)&pEvent); |
現在你可以建立filter graph了,對於檔案回放只需要一個簡單的呼叫:
hr = pGraph->RenderFile(L"C:\\Example.avi", NULL); |
IGraphBuilder::RenderFile方法建立了一個能夠播放指定檔案的filter graph,事實上,原本需要做的一些如建立filter例項及將這些filter連線起來的工作,都由這個方法自動完成了,如果是視訊檔案,這個 filter graph看起來應該是這個樣子: [file source]->[如果是縮格式,這裡是個解碼器]->[Video Renderer] 要開始回放,呼叫IMediaControl::Run方法:
hr = pControl->Run(); |
當filter graph執行時,資料經過各個filter最後回放為視訊或音訊。回放發生在一個單獨的執行緒中。你可以通過呼叫IMediaEvent::WaitForCompletion方法來等待回放的結束:
long evCode = 0; pEvent->WaitForCompletion(INFINITE, &evCode); |
這個方法在播放期間被阻塞,直至播放結束或超時。 當應用程式結束時,需要釋放介面指標並關閉COM庫:
pControl->Release(); pEvent->Release(); pGraph->Release(); CoUninitialize(); |
下面是這個例子的完整程式碼:
#include
void main(void)
{
IGraphBuilder *pGraph = NULL;
IMediaControl *pControl = NULL;
IMediaEvent *pEvent = NULL;
// Initialize the COM library. HRESULT hr = CoInitialize(NULL); if (FAILED(hr)) { printf("ERROR - Could not initialize COM library"); return; } // Create the filter graph manager and query for interfaces. hr = CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC_SERVER, IID_IGraphBuilder, (void **)&pGraph); if (FAILED(hr)) { printf("ERROR - Could not create the Filter Graph Manager."); return; } hr = pGraph->QueryInterface(IID_IMediaControl, (void **)&pControl); hr = pGraph->QueryInterface(IID_IMediaEvent, (void **)&pEvent); // Build the graph. IMPORTANT: Change this string to a file on your system. hr = pGraph->RenderFile(L"C:\\Example.avi", NULL); if (SUCCEEDED(hr)) { // Run the graph. hr = pControl->Run(); if (SUCCEEDED(hr)) { // Wait for completion. long evCode; pEvent->WaitForCompletion(INFINITE, &evCode); // Note: Do not use INFINITE in a real application, because it // can block indefinitely. } } pControl->Release(); pEvent->Release(); pGraph->Release(); CoUninitialize(); } |
3. 關於DirectShow 3.1. DirectShow體系概述 多媒體的難題 處理多媒體有幾個主要的難題: *多媒體流包含了巨大的資料量,而這些資料都必須非常快地被處理 *音訊和視訊必須同步,因此它們必須在同一時間開始或停止,並以同一速率播放 *資料可能來自很多的源,如本地檔案、網路、電視廣播和視訊攝像機 *資料有各種各樣的格式,如AVI、ASF、MPEG和DV *程式設計師無法預知終端使用者使用什麼樣的硬體裝置 DirectShow的解決方案 DirectShow被設計成用來解決所有這些難題,它主要的設計目的就是通過將複雜的資料轉輸、硬體的多樣性和同步問題從應用程式中獨立出來,從而簡化在windows平臺上數字媒體應用程式的開發任務。 要實現資料高效地被處理,需要流化音視訊資料,而DirectShow會盡可能地使用DirectDraw和DirectSound,從而高效地將資料送 到使用者的聲音和圖形裝置中進行播放。同步則是通過在媒體資料中加入時間戳來實現。而DirectShow模組化的架構,使其可以輕鬆操縱變化多端的源、格 式和硬體裝置,在這樣的架構裡,應用程式只需組合和匹配多個filter來實現功能。 DirectShow提供的filter支援基於WDM的採集和調諧裝置,也支援早先的VFW採集卡和為ACM和VCM介面編寫的編碼器。 下圖顯示了應用程式、DirectShow元件和DirectShow支援的硬體和軟體元件之間的關係:
如圖,DirectShow將應用程式與眾多複雜的裝置隔離開來,通訊和控制這些裝置均出DirectShow的filter來完成。DirectShow同樣為某種檔案格式提供與之對應的編解碼器。
3.2. Filter Graph和它的元件 這一節描述了DirectShow的主要元件,為DirectShow應用程式和DirectShow Filter開發者提供一個介紹。應用程式開發者可以忽略掉很多底層部分,但是,瞭解底層對於理解DirectShow架構還是很有幫助的。 3.2.1. 關於DirectShow Filter DirectShow使用一個模組化的架構,每個處理過程都由一個叫做filter的COM物件來實現。DirectShow為應用程式提供了一系列標準的filter,開發者也可以編寫自己的filter來擴充套件DirectShow的功能。下面是播放一個AVI檔案的各個步驟: *從檔案中讀取資料並轉成位元組流(File Source filter) *檢查AVI頭,分析位元組流並將它們分離成視訊和音訊(AVI Aplitter filter) *將視訊解碼(不同的解碼filter,取決於不同的壓縮格式) *將視訊顯示出來(Video Renderer filter) *將音訊送入音效卡(Default DirectSound Device filter)
如圖所示,每個filter與一個或多個其它的filter相連,其中的連線點也是一個COM物件,稱作Pin,filter使用Pin將資料從一個filter轉移到另一個,圖中的箭頭指示了資料流動的方向。在DirectShow中,這一系列連線在一起的filter稱作filter graph。 Filter可能處於有三種不同的狀態:執行、停止和暫停狀態。filter在執行狀態時處理資料,停止狀態時停止處理資料,暫停狀態則是表示就緒,可以 開始進入執行狀態。除了極個別的情況,一個filter Graph中的所有filter通常都處理同一個狀態下,因此,filter graph也可以稱其處於執行、停止、暫停狀態。 Filter可以被分成幾個大的種類: *source filter - filter graph的資料來源,這些資料可以來自檔案、網路、攝像頭或任何其它東西。每一個source filter操縱不同型別的資料來源。 *transform filter - 接收資料,處理資料並將它送入下一個filter。編碼filter和解碼filter都屬於這個種類。 *Renderer filter - 處於filter鏈的未端,接受資料並將其展現給使用者。比如,一個視訊renderer在顯示器上繪製視訊影象;一個音訊renderer將音訊資料送入 音效卡;一個寫檔案filter(file-writer filter)將資料存檔。 *splitter filter - 分析輸入的資料流並將其分解成兩路或多路,比如,AVI splitter分析位元組流並將其分解成視訊流和音訊流。 *mux filter - 將多路輸入流合併成一路。比如,AVI Mux正好與AVI splitter做相反的工作,它將視訊和音訊流合成為一個AVI格式的位元組流。 以上的分類並不是絕對的,比如,ASF Reader Filter同時充當了source filter和splitter filter的角色。 所有的DirectShow filter都提供IBaseFilter介面,所有的Pin也都提供IPin介面。DirectShow也定義了許多其它的介面以實現特定的功能。
3.2.2. 關於Filter Graph Manager Filter Graph Manager是一個用以控制filter graph中的filter的COM物件。它提供了許多功能,包括: *協調filter之間的狀態變化 *建立參考時鐘(reference clock) *將事件返回給應用程式 *提供應用程式建立filter graph的方法 這裡先簡單地描述一個這些功能。 狀態變化:filter們的狀態變化必須遵照一個特定的次序,因此,應用程式不能將狀態變化的命令直接發給filter,而是將一個簡單的命令發給 filter graph manager,由它來將命令分發給各個filter。定位命令同樣使用這種方式,應用程式傳送一個定位命令給filter graph manager,由它來分發。 參考時鐘:在filter graph中的所有filter都使用一個相同的時鐘,稱為參考時鐘(reference clock)。參考時鐘保證了所有流的同步。一個視訊幀或一段音訊樣本被播放的時間鈔稱作呈現時間(presentation time)。呈現時間精確地相對於參考時鐘。Filter Graph Manager通常選擇的參考時鐘是音效卡參考時鐘或系統時鐘。 Graph事件:filter graph manager使用一個訊息佇列來通知應用程式發生在filter graph中的事件。 Graph-buliding 方法:filter graph manager提供給應用程式將filter加入到filter graph中的方法,以及將filter與filter連線或斷開連線的方法。 Filter graph manager不提供操縱在filter之間流動資料的功能,這個功能由filter通過pin連線在一個單獨的執行緒中自行完成。 3.2.3. 關於媒體型別(Media Type) 因為DirectShow是模組化的,因此需要有一個在filter graph各個點之間描述格式的方法。比如說,AVI回放,資料輸入時是一個RIFF塊的流,然後被分解成視訊和音訊流。視訊流由一個個可能被壓縮的視訊 幀組成,解壓後,視訊流又變成了一系列未壓縮的點陣圖。音訊與視訊類似。 Media Type:DirectShow怎樣來描述格式 Media Type是描述數字媒體格式的常用方式。當兩個filter連線時,它們需要協商決定同一個Media Type。Media Type標識了從上一個filter遞交到下一個filter或物理層的資料流格式。如果兩個filter對Media Type不能協商一致,則不能連線。 對於某些應用程式,你不必去關心Media type,比如檔案回放,DirectShow做了所有有關它的事情。 Media type使用AM_MEDIA_TYPE結構體來定義,這個結構體包含了以下內容: *Major type:主型別,是一個GUID,定義了資料的整體型別,包括了:視訊、音訊、未分析的位元組流、MIDI等。 *Subtype:子型別,另一個GUID,進一步定義了資料格式。比如,如果主型別是視訊,則子型別可以是RGB-24、RGB-32、UYVY等格 式,如果主型別是音訊,則可能是PCM或MPEG-1 payload等。子型別提供了比主型別更多的內容,但仍未提供完整的格式定義,比如,子型別沒有定義影象尺寸和幀率,這些都將在Format block中被定義。 *Format block:格式塊,定義了具體的格式。格式塊是AM_MEDIA_TYPE結構體中一個單獨被分配的記憶體空間,pbFormat成員指向這塊記憶體空間。 因為不同的格式會有不同的格式描述,所以pbFormat成員的型別是void*。比如,PCM音訊使用WAVEFORMATEX結構體,視訊使用不同的 結構體包括:VIDEOINFOHEADER和VIDEOINFOHEADER2。formattype成員是一個GUID,指定了格式塊包含了哪種結構 體,每一種格式的結構體都被分配了GUID。cbFormat成員定義了格式式塊的長度。 當格式塊被定義時,主型別和子型別包含的資訊就顯得有點多餘了。其實,主型別和子型別為識別格式提供了一個便利的方法,比方說,你可以指定一個普通的24 位RGB格式(MEDIASUBTYPE_RGB24),而不需去關心VIDEOINFOHEADER結構體中諸如影象尺寸和幀率這些資訊。 下面是一個filter檢查媒體型別的例子:
HRESULT CheckMediaType(AM_MEDIA_TYPE *pmt)
{
if (pmt == NULL) return E_POINTER;
// 檢查主型別,我們需要的是視訊 if (pmt->majortype != MEDIATYPE_Video) { return VFW_E_INVALIDMEDIATYPE; } // 檢查子型別,我們需要的是24-bit RGB. if (pmt->subtype != MEDIASUBTYPE_RGB24) { return VFW_E_INVALIDMEDIATYPE; } // 檢查format type和格式塊的大小. if ((pmt->formattype == FORMAT_VideoInfo) && (pmt->cbFormat >= sizeof(VIDEOINFOHEADER) && (pmt->pbFormat != NULL)) { // 現在可以安全地將格式塊指標指向正確的結構體。 VIDEOINFOHEADER *pVIH = (VIDEOINFOHEADER*)pmt->pbFormat; // 檢查pVIH (未展示). 如果正確,返回S_OK. return S_OK; } return VFW_E_INVALIDMEDIATYPE; } |
AM_MEDIA_TYPE結構體還包含了一些任選項,用來提供附加的資訊,filter不需要這些資訊: *ISampleSize,如果這個欄位非零,表示這是每個sample的尺寸,如果是零,則表示sample的尺寸會改變。 *bFixdSizeSamples,如果這個布林型別的標記是TRUE,表示ISampleSize有效,否則,你可以忽略ISampleSize。 *bTemporalCompression,如果這個布林型別的標記是FALSE,表示所有幀都是關鍵幀。
3.2.4. 關於媒體樣本(Media Sample)和分配器(Allocator) Filter通過Pin與Pin之間的連線來遞交資料,資料從一個filter的輸出Pin轉移到另一個filter的輸入Pin,除了個別情況,實現這種功能通常的方法是呼叫輸入Pin上的IMemInputPin::Receive方法。 依靠filter,媒體資料的記憶體空間可以通過多個途徑來分配:在堆上、在DirectDraw表面(surface)、在共享GDI記憶體或使用其它的分配機制。這個負責分配記憶體空間的物件稱為分配器(Allocator),是一個暴露IMemAllocator介面的COM物件。 當兩個Pin相連時,其中的一個Pin必須提供一個分配器。DirectShow定義了一個方法呼叫序列來決定到底由哪個Pin來提供分配器。Pin還負責協商分配器建立的緩衝數和每個緩衝的尺寸。 在資料流開始之前,分配器建立了一個緩衝池。在資料流動過程中,上游filter在緩衝中填入資料並遞送給下游filter,但是,上游filter遞送給下游filter的並不是原始的緩衝區指標,而是一個稱為媒體樣本(Media Sample)的COM物件,它由分配器建立並用來管理緩衝區,暴露IMediaSample介面。一個媒體樣本包含: *指向下層緩衝區的指標 *時間戳 *各種標記 *可選的媒體型別 時間戳定義了呈現時間(presentation time),用以讓renderer filter確定播放的合適時機。各種標記可以用來指示很多事情,比如,資料在上一個sample後是否被打段過(如重新定位、掉幀)等。媒體型別為流中 間改變資料格式提供了途徑,通常,沒有媒體型別的sample,被認為從上一個sample以來資料格式沒有被改變過。 當filter使用一個緩衝時,它儲存了sample上的參考計數。分配器使用參考計數來決定什麼時候可以重用這個緩衝,這防止了一個filter在寫一 個緩衝時另一個filter還在使用這個緩衝,除非所有的filter都釋放了這個緩衝,否則sample不會將其返回給分配器的緩衝池。 3.2.5. 硬體如何參與Filter Graph 這一節描述了DirectShow如何與音訊和視訊硬體互動。 外殼filter(Wrapper Filter) 所有的DirectShow filter都是使用者模式的軟體元件。為了使象視訊採集卡這樣的核心模式的硬體驅動加入到filter graph中,必須使其象使用者模式的filter那樣。DirectShow提供外殼filter來完成這個功能,這類filter包括:Audio Capture filter、VFW Capture filter、TV Tuner filter、TV Audio filter和Analog Video Crossbar filter。DirectShow也提供一個叫KsProxy的filter,它可以實現任何型別的WDM流驅動。硬體商通過提供一個Ksproxy plug-in來擴充套件KsProxy,以使其支援自己的功能,ksproxy plug-in是一個被KsProxy聚合的COM物件。 外殼filter通過暴露COM介面來實現裝置的功能。應用程式使用這些介面將資訊傳遞給filter,filter再把這些COM呼叫轉化為裝置驅動調 用,將資訊傳遞到核心模式下的裝置中去,然後返回結果給應用程式。TV Tuner、TV Audio、Analog Video Crossbar和KsProxy filter都通過IKsPropertySet介面來支援驅動的自定義屬性,VFW Capture filter和Audio Capture filter不支援這種方式。 外殼filter使應用程式可以象控制其它directshow filter一樣來控制裝置,filter已經封裝了與核心驅動通訊的細節。 Video for Windows Devices VFW Capture filter支援早期的VFW採集卡,當一個裝置加入到目標系統中支後,它可以被directshow使用系統裝置列舉器(System Device Enumerator)發現並加入到filter graph中去。 音訊採集(Audio Capture)和混音裝置(音效卡)(Mixing Device/Sound Card) 較新的音效卡都有麥克風等裝置的插口,而且大多數這類音效卡都有板級的混頻能力,可單獨控制每一個連線裝置的音量及高低音。在directshow中,音效卡的 輸入和混頻裝置被Audio Capture filter封裝。每個音效卡都能被系統裝置列舉器發現。要檢視你的系統中的所有音效卡,只需開啟GraphEdit,從Audio Capture Sources一類中選擇即可,每個在這個類裡的filter都是一個單獨的Audio Capture filter。
WDM流裝置 較新的硬解碼裝置和採集卡都遵照WDM規範。這些裝置和比VFW裝置更強大的功能,以及可以應用於多種系統 (winxp,winNT,win2000,win98/me)。WDM視訊採集卡支援許多VFW所沒有的功能,包括列舉採集的格式、程式設計控制視訊引數 (如對比度、亮度)、程式設計選擇輸入端和電視調諧支援。 為了支援WDM流裝置,directshow提供了KsProxy filter(ksproxy.ax)。KsProxy被稱為“瑞士軍刀",因為它可以做很多不同的事情。filter上pin的數量,以及COM介面的 數量,取決於底層驅動的能力。KsProxy不以"KsProxy"這個名字顯示在filter graph中,而是使用一個已在登錄檔中登記的裝置名稱。要檢視你係統中的WDM裝置,可以執行GraphEdit然後從WDM Streaming這個類別中選擇。即使你的系統中只有一塊WDM卡,這塊卡也可能包含多個裝置,而每一個裝置都表現為一個filter,每個 filter是實際意義上的KsProxy。 應用程式使用系統裝置列舉器在系統中尋找WDM裝置moniker,然後呼叫moniker的BindToObject來例項化。因為KsProxy能夠 表現所有型別的WDM裝置,因此它必須通過詢問驅動來決定哪些屬性是驅動所支援的。屬性集是一組資料結構的集合,被WDM裝置使用,也被諸如MPEG2軟 解碼filter這樣的使用者模式filter使用。KsProxy通過暴露COM介面來配置自己,硬體商則通過提供外掛來擴充套件KsProxy,外掛暴露硬 件商自定義的一些介面,用以實現特殊的功能。所有這些細節對於應用程式來說都是不可見的,應用程式通過KsProxy控制裝置就象控制其它的 DirectShow filter一樣。 核心流 WDM裝置支援核心流,在核心流中資料在核心模式下被徹底流化而永遠不需要切換到使用者模式下去,從而避免了在核心模式和使用者模式之間切換的巨大開銷,核心 流允許高的位元率而不消耗CPU的時間。基於WDM的filter能夠使用核心流將多媒體資料一個硬體裝置送入到另一箇中去,既可以是在同一塊卡中也可以 在不同的卡中,而不需要將資料拷入系統主存。 從應用程式的視點來看,資料好象是從一個使用者模式的filter傳到另一箇中去,但是實際上,資料根本就沒有傳到使用者模式下過,而是可能支接從核心模式的 裝置中傳到下一個中去直至被呈現(render)在顯示卡上。某些情況,比如採集視訊到一個檔案中去,在某些點上需要將資料從核心模式傳入到使用者模式,但 是,仍然沒有必要將資料拷貝到記憶體的一個新位置中去。 應用程式開發者通常只需瞭解一個核心流的背景知識而不需要深究它的細節。
3.3. 構建Filter Graph 3.3.1. 用於構建Graph的元件 DirectShow提供了一系列用於構建filter graph的元件,包括: *Filter Graph Manager。這個物件用於控制filter graph,支援IGraphBuilder、IMediaControl和IMediaEventEx等許多介面。所有的directshow應用程式 都需要在某些地方用到這個物件,雖然在有些情況下,是其它的物件為應用程式建立了filter graph manager。 *Capture Graph Builder。這個物件為構建filter graph提供附加的方法。它最初是為構建提供視訊採集的graph而設計的(這正是它的名字由來),但是對於構建許多另外型別的filter graph也是很有用的。它支援ICaptureGraphBuilder2介面。 *Filter Mapper和System Device Enumerator。這些物件用於查詢在系統中註冊的或代表硬體驅動的filter。 *DVD Graph Builder。這個物件構建用以回放和導航DVD的filter graph。它支援IDvdGraphBuilder介面。基於指令碼的應用程式能夠使用MSWebDVD ActiveX控制元件來控制DVD回放。 *Video Control。WinXP提供這個ActiveX控制元件,用於操縱directshow中的資料和模擬電視。 智慧連線(Intelligent Connect) 智慧連線這個術語覆蓋了一系列Filter Graph Manager用於構建所有或部份filter graph的演算法。任何時候,當Filter Graph Manager需要新增filter來完成graph時,它大致做以下幾件事情: 1.如果有一個filter存在於graph中,而且這個filter有至少一個沒有連線的input pin,Filter Graph Manager試著去試用這個filter。 2.否則,Filter Graph Manager在已註冊的filter中尋找連線時可以接受合適的媒體型別的filter。每一個filter都註冊有一個Merit值,這個值用以標記 哪個filter最容易被Filter Graph Manager選中來完成graph。Filter Graph Manager按Merit值的順序來選擇filter,Merit值越大,被選中的機會越大。對於每種流型別(如音訊、視訊、MIDI),預設的 renderer具有一個很高的Merit值,解碼器同樣是,專用filter具有低Merit值。 如果Filter Graph Manager因選擇的filter不合適而被困,它會返回來嘗試另外的filter組合。
3.3.2 Grap構建概述 建立一個filter graph,從建立一個Filter Graph Manager例項開始:
IGraphBuilder* pIGB; HRESULT hr = CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC_SERVER, IID_IGraphBuilder,(void **)&pIGB); |
Filter Graph Manager支援下列Graph構建方法: *IFilterGraph::ConnectDirect,在兩個pin之間進行直接連線,如果連線失敗,則返回失敗 *IFilterGraph::Connect,連線兩個Pin,如果可能的話,直接連線它們,否則,在中間加入其它的filter來完成連線。 *IGraphBuilder::Render,從某個輸出Pin處開始完成餘下的graph構建。該方法會自動在輸出pin後面新增必須的filter,直到renderer filter為止。 *IGraphBuilder::RenderFile,構建一個完整的檔案回放graph。 *IGraphBuilder::AddFilter,將一個filter新增到graph中。它不連線 filter,並且在呼叫此方法前,filter必須已經被建立。建立filter可以是用CoCreateInstance方法或使用Filter Mapper或系統裝置列舉器(System Device Enumerator)。 這些方法提供了三種構建graph的途徑: 1.filter graph manager構建整個graph 2.filter graph manager構建部分graph 3.應用程式構建整個graph Filter Graph Manager構建整個graph 如果你僅僅是想回放一個已知格式的檔案,如AVI、MPEG、WAV或MP3,使用RenderFile方法。 RenderFile方法首先尋找註冊在系統中能分析原始檔的filter,它使用協議名(如http://),副檔名或檔案的頭幾個位元組來決定選擇哪一個源filter。 Filter Graph Manager使用一個迭代過程來完成餘下的graph構建。在這個迭代過程中,它逐個列出filter的輸出pin上支援的媒體型別,並搜尋哪個已註冊 的filter的輸入Pin接受該媒體型別。它使用一系列的規則來縮小filter的範圍並排定優先順序: *filter類別(category)標識的filter的一般功能 *媒體型別描述filter能在接受或能輸出哪種資料型別 *merit值決定filter被嘗試的次序。如果兩個filter具有相同的filter類別並且同時支援相同的輸入型別,Filter Graph Manager選擇merit值大的那一個。一些filter故意給出一個小merit值是因為它是為特殊用途設計的,僅能由應用程式來將其新增到 graph。 Filter Graph Manager使用Filter Mapper物件來搜尋已註冊的filter。 每個filter被新增時,filter graph manager試著將其與前一個filter的輸出pin連線。它們協商決定他們是否能連線,如果能,哪一種媒體型別被用來連線。如果新filter不能 連線,filter graph manager丟棄它並嘗試別一個,這個過程一直繼續到每個流都被render為止。 Filter Graph Manager構建部分graph 如果不僅僅是播放一個檔案,那麼你的應用程式就必須做一些graph的構建工作。比如,一個視訊採集應用程式必須先選擇一個source filter並將其新增到graph中去。如果你需要將資料寫入到一個AVI檔案中,你必須新增一個AVI Mux和File Write filter。不過,也經常有可能讓filter graph manager來完成整個graph,比如,你可以通過Render方法來render一個pin進行預覽。 應用程式構建整個graph 在某些場合,你的應用程式需要新增和連線每個filter來構建graph。在這種情況下,你很可能明確地知道哪些filter需要加到graph中去。 使用這種方式,應用程式通過呼叫AddFilter方法新增每個filter,然後列舉filter上的pin,呼叫Connect或 ConnectDirect來連線它們。
3.3.3. 智慧連線 智慧連線是filter graph manager用以構建filter graph的機制。它包含了一系列相關的用以選擇filter和將它們新增到graph中去的演算法。作為應用程式開發者,你並不需要很具體地瞭解智慧連線 的細節。如果你在構建某個filter graph時遇到問題並希望能解決它,或者你正在編寫你自己的filter並希望它能自動地被graph構建,請閱讀這一節。 智慧連線涉及以下IGraphBuilder方法: *IGraphBuilder::Render *IGraphBuilder::AddSourceFilter *IGraphBuilder::RenderFile *IGraphBuilder::Connect Render方法構建一部分graph,它從一個尚未連線的輸出pin開始順著資料流的方向往下,新增必要的filter,起始的那個filter必須已 被新增到了graph中。Render方法每一步都搜尋一個能夠連線到前一個filter的filter,如果新連線上的filter有多個輸出pin, 資料流能自動分流,搜尋直到每個流都被renderer為止。如果Render方法搜尋到的filter無法使用,它會返回去嘗試另一個filter。 要連線每一個輸出pin,Render方法做以下工作: 1.如果pin支援IStreamBuilder介面,Filter Graph Manager讓pin的IStreamBuilder::Render方法來完成整過程。通過暴露這個介面,pin承擔了構建graph剩餘部分的全部 工作。但是,只有很少數的filter支援此介面。 2.Filter Graph Manager嘗試使用任何在快取中的filter。在智慧連線的整個過程中,filter graph manager可以在早期將filter快取起來。 3.如果filter graph包含了任何有未連線的輸入pin的filter,filter graph manager會將其當作下一個filter來嘗試連線。你可以通過在呼叫Render之前新增特定的filter來強制讓Render方法來嘗試這個 filter。 4.最後,filter graph manager使用IFilterMapper2::EnumMatchingFilters方法在所有註冊的filter中尋找,依據已註冊的媒體型別列表來逐個試著匹配輸出pin的各個媒體型別(按優先順序高低排列)。 每個已註冊的filter都有一個merit值,這是一個用來表示filter優先順序的數字,最大優先順序越高,EnumMatchingFilters方 法返回的filter集依據merit值來排列,直至最小的merit值MERIT_DO_NOT_USE+1,它忽略merit為 MERIT_DO_NOT_USR或更小的filter。filter也通過GUID來歸類,類別本身也有merit 值,EnumMatchingFilters方法忽略任何merit值為MERIT_DO_NOT_USE或更小的類別,即使在那個類別中的filter 有較高的merit值。 總結一下,Render方法以下列步驟嘗試filter 1.使用IStreamBuilder 2.嘗試被快取的filter 3.嘗試已新增在graph中的filter 4.在已註冊的filter中尋找 AddSourceFilter方法新增一個能render特定檔案的source filter。首先,它依據協議名(如Http://)、副檔名、或檔案頭在已註冊的filter中尋找匹配的那個。如果此方法定位到了一個合適的 source filter,它便立刻建立一個這個filter的例項,並將其新增到graph中,然後呼叫filter的 IFileSourceFilter::Load方法。 RenderFile方法依據一個檔名來構建一個預設的回放graph,在其內部,RenderFile方法呼叫AddSourceFilter來定位source filter,並且用Render來構建Graph的餘下部分。 Connect方法將輸出pin連線到輸入pin上去,這個方法自動新增必要的中間filter到graph中去,使用在Render方法中描述的那一系列演算法: 1.使用IStreamBuilder 2.嘗試被快取的filter 3.嘗試已新增在graph中的filter 4.在已註冊的filter中尋找
3.4. Filter Graph中的資料流 這一節主要描述媒體資料是如何在filter graph中流動的。如果你只是為了編寫DirectShow應用程式,你不需要知道這些細節,當然,知道這些細節對於編寫directshow應用程式 仍然是有幫助的。但是如果你要編寫directshow filter,那麼你就必須掌握這部分知識了。3.4.1. DirectShow資料流概述 在這一部分先粗略地描述一下DirectShow中資料流是如何工作的。 資料首先是被儲存在緩衝區裡的,在緩衝區裡,它們僅僅是一個位元組陣列。每一個緩衝區被一個稱作媒體樣本(media sample)的COM物件所包容,media sample提供IMediaSample介面。media sample由另一個稱作分配器(allocator)的COM物件建立,allocator提供IMemAllocator介面。每一個pin連線都指定有一個allocator,當然,兩個或多個pin連線也可以共享幾個allocator。
每一個allocator都建立一個media sample池,併為每個sample分配緩衝區。一旦一個filter需要一個緩衝區來填充資料,它就呼叫IMemAllocator::GetBuffer方 法來請求一個sample。只要allocator有一個sample還沒有被任何filter使用,GetBuffer方法就立即返回一個sample 的指標。如果allocator所有的sample已經被用完,這個方法就阻塞在那裡,直到有一個sample變成可用的了。GetBuffer返回一個 sample後,filter就將資料寫入到sample的緩衝區中去,並在sample上設定適當的標記(如時間戳),然後將它遞交到下一個 filter去。 當一個renderer filter接收到了一個sample時,renderer filter檢查時間戳,並將sample先儲存起來,直到filter graph的參考時鐘指示這個sample的資料可以被render了。當filter將資料render後,它就將sample釋放掉,此時 sample並不立即回到allocator的sample池中去,除非這個sample上的參考計數已經變為0,表示所有的filter都已釋放這個 sample。
上游的filter可能在renderer之前執行,這就意味著,上游的filter填充緩衝的速度可能快於renderer銷燬它們。但是儘管如 此,samples也並無必要更早地被render,因為renderer將一直儲存它們直到適當的時機去render,並且,上游filter也不會意 外地將這些samples的緩衝覆蓋掉,因為GetSample方法只會返回那些沒有被使用的sample。上游filter可以提前使用的sample 的數量取決於allocator分配池中的sample的數量。 前面的圖表只顯示了一個allocator,但是通常的情況下,每個流中都會有多個allocator。因此,當renderer釋放了一個sample 時,它會產生一個級聯效應。如下圖所示,一個decoder儲存了一個視訊壓縮幀,它正在等待renderer釋放一個sample,而parser filter也正在decoder去釋放一個sample。
當renderer釋放了一個sample後,decoder完成尚未完成的GetBuffer呼叫。然後decoder便可以對壓縮的視訊幀進行解碼並釋放它儲存的sample,從而使parser完成它的GetBuffer呼叫。 3.4.2. 傳輸協議(Transports) 為了使媒體資料能在filter graph中流動,Directshow filter必須能支援多個協議中的一個,這些協議被稱作傳輸協議(transports)。當兩個filter連線後,它們必須支援同一個傳輸協議,否則,它們將不能交換資料。通常,一個傳輸協議要求某個pin支援一個特定的介面,當兩個filter連線時,另一個pin來呼叫這個pin的這個介面。 大多數的directshow filter在主存中儲存媒體資料,並且通過pin連線向另一個filter遞交資料,這種型別的傳輸協議被稱作本地記憶體傳輸協議(local memory transport)。儘管這類傳輸協議在directshow中應用最普遍,但並非所有的filter都使用它。例如,某些filter通過硬體途徑來傳遞資料,使用pin僅僅是為了傳遞控制資訊,如IOverlay介面。 DirectShow為本地記憶體傳輸協議定義了兩種機制,推(push)模式和拉(pull)模式。在推模式中,source filter產生資料,並將其遞交給下游的filter,下游的filter被動地接收資料並處理它們,再將資料傳遞給它的下游filter。在拉模式中,source filter與一個parser filter連線,parser filter向source filter請求資料,source filter迴應請求並傳遞資料。推模式使用IMemInputPin介面,而拉模式使用IAsyncReader介面。 推模式比拉模式應用更廣泛。 3.4.3. 媒體樣本(sample)和分配器(allocator) 當一個pin向另一個pin傳遞媒體資料時,它並不是直接傳遞一個記憶體緩衝區的指標,而是傳遞一個COM物件的指標,這個COM物件管理著記憶體緩衝,被稱為媒體樣本(media sample),暴露IMediaSample介面。接收方pin通過呼叫IMediaSample介面的方法來訪問記憶體緩衝,如IMediaSample::GetPointer,IMediaSample::GetSize和IMediaSample::GetActualDataLength。 sample總是從輸出pin到輸入pin向下傳輸。在推模式中,輸出pin通過在輸入pin上呼叫IMemInputPin::Receive方法來傳 遞一個sample。輸入pin或者在Receive方法內部同步地處理資料,或者另開一個工作執行緒進行非同步處理。如果輸入pin需要等待資源,允許在 Receive中阻塞。 另一個用來管理媒體樣本的COM物件,被稱作分配器(allocator),它暴露IMemAllocator介面。一旦一個filter需要一個空閒的媒體樣本,它呼叫IMemAllocator::GetBuffer方法來獲得sample的指標。每一個pin連線都共享一個allocator,當兩個pin連線時,它們協商決定哪個filter來提供allocator。pin可以設定allocator的屬性,比如緩衝的數量和每個緩衝的大小。 下圖顯示了allocator、media sample和filter的關係:
媒體樣本參考計數(Media Sample Reference Counts) 一個allocator建立的是一個擁有有限個sample的sample池。在某一時刻,有些sample正在被使用,有些則可被GetBuffer方 法使用。allocator使用參考計數來跟蹤sample,GetBuffer方法返回的sample參考計數為1,如果參考計數變為0,sample 就可以返回到allocator的sample池中去了,這樣它就可以再次被GetBuffer方法使用。在參考計數大於0期間,sample是不能被 GetBuffer使用的。如果每個從屬於allocator的sample都在被使用,則GetBuffer方法會被阻塞直至有sample可以被使 用。 舉個例子,假設一個輸入pin接收到一個sample。如果它同步地在Receive方法內部處理它,sample的參考計數不增加,當Receive返 回時,輸出pin釋放這個sample,參考計數歸0,sample就返回到sample池中去了。另一種情況,如果輸入pin非同步地處理sample, 它就在Receive方法返回前將sample的參考計數加1,此時參考計數變為2。當輸出pin釋放這個sample時,參考計數變為1,sample 不能返回到sample池中去,直到非同步處理的工作執行緒完成工作,呼叫Release釋放這個sample,參考計數變為0時,它才可以返回到 sample池中去。 當一個pin接收到一個sample,它可以將資料拷貝到另一個sample中去,或者修改原始的sample並將其傳遞到下一個filter中去。一個 sample可能在整個graph長度內被傳遞,每個filter都依次呼叫AddRef和Release。因而,輸出pin在呼叫Receive後一定 不能重複使用同一個sample,因為下游的filter可能正在使用這個sample。輸出pin只能呼叫GetBuffer來獲得新的sample。 這個機制減少了總的記憶體分配過程,因為filter可以重複使用同樣的緩衝。它同樣防止了資料在被處理前意外地被覆蓋寫入。 當filter處理資料後資料量會變大(如解碼資料),一個filter可以為輸入pin和輸出pin分配不同的allocator。如果輸出資料並不比 輸入資料量要大,filter可以用替換的方式來處理資料而不用將其拷貝到新的sample中去,在這種情況下,兩個或多個pin連線共享一個 allocator。 提交(Commit)和反提交(Decommit)分配器 當一個filter首次建立一個allocator時,allocator並不為其分配記憶體緩衝,此時如果呼叫GetBuffer方法的話會失敗。當流開始流動時,輸出pin呼叫IMemAllocator::Commit來提交allocator,從而為其分配記憶體。此時pin可以呼叫GetBuffer了。 當流停止時,pin呼叫IMemAllocator::Decommit來 反提交allocator,在allocator被再次提交前所有後來的GetBuffer呼叫都將失敗,同樣,如果有阻塞的正在等待sample的 GetBuffer呼叫,也將立即返回失敗資訊。Decommit方法是否釋放記憶體取決於實現方式,如CMemAllocator類直至析構時才釋放內 存。 3.4.4. filter狀態 filter有三種可能的狀態:停止(stopped),就緒(paused)和執行(running)。就緒狀態的目的是為了讓graph提前做準備以便在run命令下達時可以立即響應。Filter Graph Manager控制所有的狀態轉換。當一個應用程式呼叫IMediaControl::Run,IMediaControl::Pause或IMediaControl::Stop時,Filter Graph Manager在所有filter上呼叫相應的IMediaFilter方法。在停止狀態和執行狀態之間轉換時總是要經過就緒狀態,即如果應用程式在一個處於停止狀態的graph上呼叫Run時,Filter Graph Manager在執行它之前先將其轉為pause狀態。 對於大多數filter來說,執行狀態和就緒狀態是等同的。看下面的這個graph: Source > Transform > Renderer 假設這個source filter不是一個實時採集源,當source filter就緒時,它建立一個執行緒來儘可能快地產生新資料並寫入到media sample中去。執行緒通過在transform filter的輸入pin上呼叫IMemInputPin方法將sample“推”到下游filter。transform filter在source filter的執行緒中接收資料,它可能也使用一個工作執行緒赤將sample傳遞給renderer,但是在通常情況下,它在同一個執行緒中傳遞它們。如 renderer處理就緒狀態下,它等待接收sample,當它接收到一個時,它或阻塞或儲存那個sample,如果這是一個Video renderer,則它將sample顯示為一個靜態的圖片,只在必要的時候重新整理它。 此時,流已經準備充分去被render,如果graph仍然處理就緒狀態下,sample會在每一個sample後堆積,直至每個filter都被阻塞在 Receive或GetBuffer下。沒有資料會被丟失。一旦source執行緒的阻塞被解除時,它只是簡單地從阻塞點那裡進行恢復。 source filter和transform filter忽略從就緒狀態轉到執行狀態——它們僅僅是儘可能快地繼續處理資料。但是當renderer執行時,它就要開始render sample了。首先,它render在就緒狀態下儲存的那個sample,接著,每接收到一個新的sample,它計算這個sample的呈現時 間,renderer儲存每個sample直至到了它們的呈現時間再render它們。在等待合適的呈現時間時,它或者阻塞在Receive方法上,或者 在一個工作執行緒中接收資料並將其放入佇列中去。renderer的上一個filter不關心這些問題。 實時源(live source),如採集裝置,是通常情況中的一個例外。在實時源中,不適合提前準備資料。應用程式可能將graph置於就緒狀態下,然後等很長時間才再運 行它。graph不應該再render就緒期間的sample,因此,一個實時源在就緒狀態時不產生新的sample。要將這種情況通知給filter graph manager,source filter的IMediaFilter::GetState方法返回VFW_S_CANT_CUE。這個返回值表示filter已切換到就緒狀態下,即 使renderer還沒有收到任何資料。 當一個filter停止時,它不再接收任何傳遞給它的資料。source filter關閉它們的流執行緒,別的filter關閉所有它們建立的工作執行緒。pin反提交(decommit)它們的allocator。 狀態轉換 filter graph manager按從下游filter到上游filter的次序來完成所有的狀態轉換,從renderer開始逐個向上直至source filter,這個次序是必要的,可以防止資料丟失或graph死鎖。最重要狀態轉換是就緒狀態和停止狀態間的轉換: *停止狀態到就緒狀態:當每一個filter被置為就緒態時,它便準備好從上一個filter接收sample。source filter是最後一個被置為就緒態的filter,它建立資料流執行緒並開始傳遞sample。因為所有下游filter都處於就緒狀態,所以沒有一個 filter會拒絕接收sample。當graph中所有的renderer都接收到一個sample後,filter graph manager才徹底完成狀態轉換工作(實時源除外)。 *就緒狀態到停止狀態:當一個filter停止時,它釋放了所有它儲存的sample,就將解除所有上游filter呼叫GetBuffer時的阻塞。如 果filter正在Receive方法中等待資料,則它停止等待並從Receive中返回,從而解除阻塞。因而,此時當filter graph manager再去將上游filter轉換為停止狀態時,它已經不再阻塞於GetBuffer和Receive,從而可以響應停止命令。上游filter 在得到停止命令前可能會傳遞下一些過時的sample,但下游filter不再接收它們,因為此時下游filter已處於停止狀態了。 3.4.5. 拉模式 在IMemInputPin介面中,上游filter決定哪些資料要被髮送,然後將資料推到下游filter中去。但是在某些情況下,拉模式會更加合適。 在拉模式中,只有當下遊filter從上游filter中請求資料時,資料才被傳遞下去,資料流動由下游filter發起。這種型別的連線使用IAsyncReader介面。 典型的拉模式應用是檔案回放。比如,在一個AVI回放graph中,Async File Source filter完成一般的檔案讀操作並將資料作為位元組流傳遞下去,沒有什麼格式資訊。AVI Splitter filter讀取AVI頭並將資料流分解成視訊和音訊sample。AVI Splitter比Async File Source filter更能決定它們需要哪些資料,因此需用IAsyncReader介面來代替IMemInputPin介面。 要從輸出pin請求資料,輸入pin呼叫下面方法中的一個: *IAsyncReader::Request *IAsyncReader::SyncRead *IAsyncReader::SyncReadAligned 第一個方法是非同步的,支援多重讀操作。其餘的是同步的。 理論上,任一個filter都能支援IAsyncReader,但是實際上,它僅僅在連線有一個parser filter的source filter上使用。分析器(parser)非常象一個推模式的source filter,當它就緒時,它建立一個數據流執行緒,從IAsyncReader連線中拉資料並將其推到下一遊filter中去。它的輸出pin使用 IMemInputPin,graph餘下的部分使用標準的推模式。
3.5 DirectShow中的事件通告 這一節主要描述在directshow filter graph中事件是怎樣發生的,以及應用程式如何接收事件通告並響應它們。3.5.1 概述 一個filter