010 Directshow中Filter開發基礎

Post date: 2015/4/9 上午 01:25:19

摘要: 關於開發自己的Filter, 我以前寫過一篇文章, 《利用Directshow開發自己的filter》, 裏面詳細介紹了開發filter一些步驟, 這裏我想介紹一些filter的基礎知識, 可以讓你更好的理解filter。 本篇文檔主要包括下面一些內容 1filter的連接 2filter間的數據流動 3pin連接時數據格式的動態改變 4Threads and Critical Sections 5質量控制管理 6Directshow和com 1filter的連接 Pin的連接 應用程序通過調用filter 圖表管理器的方法來連接filter, 並不是來調用filter或者pin本身的函數。 應用程序可以調用IFilterGraph::ConnectDirect or IGraphBuilder::Connect來指定不同的filter直接連接, 也可以通過IGraphBuilder::RenderFile間接連接。 只有兩個filter都在graph裏, 連接才能成功。 應用程序可以通過IFilterGraph::AddFilter將filter 添加graph中, 當一個filter被添加到graph中時, filter圖表管理器通過IBaseFilter::JoinFilterGraph來通知filter。 Pin連接的大致過程如下: 1圖表管理器首先調用輸出pin上的IPin::Connect, 然後傳遞一個指針給輸入pin。 2如果輸出pin接受連接的邀請, 它就調用輸入pin上的IPin::ReceiveConnection。 3如果輸入pin也接受連接邀請, 那麼連接成功, pin之間的連接ok。 當filter處於活動狀態的時候, 許多pin可以斷開連接和重新連接。 這種類型的連接稱為動態連接。 當然, 大多數的filter並不支持動態連接。 Filter通常採用從上遊到下遊的連接順序。 也就是說filter上的輸入pin總是比輸出pin先連接。 Filter應該支持這種連接順序。 然而有許多filter支持相反的連接順序, 輸出pin先連接, 輸入pin後連接。 例如:在連接MUX filter的輸入pin之前一定要將MUX filter的輸出pin和writer filter連接起來。 當pin的Connect or ReceiveConnection方法被調用的時候, pin必須檢查一下自己是否支持這個連接。 通常要進行下列檢查: 1 檢查媒體類型是否匹配。 2 就內存的分配達成一致。 3請求其他pin的其他接口。 媒體類型匹配 當一個filter 圖表管理器調用IPin::Connect方法時, 可能有下面的幾種媒體類型。 1 完整類型 如果媒體類型每一個部分都定義的很完成, 那麼pin就嚴格按照定義的類型類型進行連接。 如果不匹配, 連接失敗。 2 部分媒體類型 如果媒體類型的機構中, major type, subtype, or format type的值為GUID_NULL, 這個值是一個通配符號。 任何類型都可以匹配。 3沒有媒體類型 如果filter圖表管理器傳遞過來一個NULL的指針, 這個pin就可以和任意的類型的媒體類型匹配。 一般在連接過程中, 都有一個完整的媒體類型。 圖表管理器傳遞媒體類型的目的是為了限制連接類型。 一般來說, 都是輸出pin通過調用輸入pin IPin::ReceiveConnection提供一個媒體類型。 輸入pin可以拒絕也可以接受這個媒體類型。 這個過程一直重複, 直到輸入pin接受了一個類型, 或者輸出pin枚舉完了它支持的所有的媒體類型, 連接失敗。 輸出pin通過調用輸入pin上的IPin::EnumMediaTypes枚舉輸入pin所支持的媒體類型。 看看如何匹配媒體類型的吧。 if ((pmt->formattype == FORMAT_VideoInfo) && (pmt->cbFormat > sizeof(VIDEOINFOHEADER) && (pbFormat != NULL)) { VIDEOINFOHEADER *pVIH = (VIDEOINFOHEADER*)pmt->pbFormat; // Now you can dereference pVIH. } Pin連接中的內存分配 當兩個pin連接起來後, 他們需要一種機制來交換媒體數據。 大多數數據交換採用的局部內存交換機制。 所有的媒體數據都在主內存中。 DirectShow為局部存儲器傳輸定義了兩種機制: 推模式(push model)和拉模式(pull model)。 在推模式中, 源過濾器生成數據並提交給下一級過濾器。 下一級過濾器被動的接收數據, 完成處理後再傳送給再下一級過濾器。 在拉模式中, 源過濾器與一個分析過濾器相連。 分析過濾器向源過濾器請求數據後, 源過濾器才傳送數據以響應請求。 推模式使用的是IMemInputPin接口, 拉模式使用IAsyncReader接口, 推模式比拉模式要更常用。 在局部存儲器傳輸中, 負責分配內存的對象稱為allocator。 每個allocator都支持一個IMemAllocator接口。 所有的pin都共享一個allocator。 每個pin都提供一個allocator, 但是輸出pin選擇使用哪個allocator。 輸出pin可以設置allocator的屬性。 比如, 分配內存的大小, 在IMemInputPin連接中, allocator工作過程如下 1 首先, 輸出pin調用IMemInputPin::GetAllocatorRequirements, 這個方法檢查輸入pin對內存的要求, 比如內存的隊列, 一般來說, 輸出pin要滿足輸入pin對內存的要求。 2 輸出pin然後調用IMemInputPin::GetAllocator., 這個方法從輸入pin請求一個allocator, 3 輸出pin選擇一個allocator, 可以是輸入pin提供, 也可以是自己生產的。 4輸出pin調用IMemAllocator::SetProperties來設置allocator的屬性。 5然後輸出pin通過IMemInputPin::NotifyAllocator來通知輸入pin, 選擇的allocator。 6輸入pin通過IMemAllocator::GetProperties來檢查是否能夠接受allocator的屬性。 7當數據流開始和停止的時候, 輸出pin負責提交allocator。 在IAsyncReader連接過程如下: 1 輸入pin調用輸出pin上的IAsyncReader::RequestAllocator, 輸入pin確定內存的屬性, 並提供一個allocator。 2 輸出pin選擇一個allocator, 3 輸入pin檢查 如何提供一個自定義的allocator 這裏只講一下IMemInputPin連接, IAsyncReader類似。 首先, 定義一個C++類, 你的allocator應該從一個標準的allocator類中派生, 比如CBaseAllocator or CMemAllocator, 你也可以自己創建一個新的allocator類, 如果你是新建的類, 你必須支持IMemAllocator接口。 下面看看在輸入pin和輸出pin中如何使用你定義的allocator。 在輸入pin中提供allocator 在輸入pin中提供allcator, 必須重載CBaseInputPin::GetAllocator方法。 在這個方法裏, 首先檢查m_pAllocator是否可用, 如果為非空, 就表明allocator已經被選中, 所以直接返回這個allocator指針即可, 如果m_pAllocator為空, 表明allocator還沒有被選中, 所以, 就要返回輸入pin的allocator, 因此, 創建一個allcaotor的實例, 返回IMemAllocator接口。 看下面的代碼把 STDMETHODIMP CMyInputPin::GetAllocator(IMemAllocator **ppAllocator) { CheckPointer(ppAllocator, E_POINTER); if (m_pAllocator) { // We already have an allocator, so return that one. *ppAllocator = m_pAllocator; (*ppAllocator)->AddRef(); return S_OK; } // No allocator yet, so propose our custom allocator. The exact code // here will depend on your custom allocator class definition. HRESULT hr = S_OK; CMyAllocator *pAlloc = new CMyAllocator(&hr); if (!pAlloc) { return E_OUTOFMEMORY; } if (FAILED(hr)) { delete pAlloc; return hr; } // Return the IMemAllocator interface to the caller. return pAlloc->QueryInterface(IID_IMemAllocator, (void**)ppAllocator); } 當輸出pin選擇一個allocator, 它就調用輸入pin的IMemInputPin::NotifyAllocator, 因此, 要重載CBaseInputPin::NotifyAllocator方法來檢查allocator的屬性。 在輸出pin中如何提供一個定制的Allocator 在輸出pin中提供一個allcator, 要重載CBaseOutputPin::InitAllocator HRESULT MyOutputPin::InitAllocator(IMemAllocator **ppAlloc) { HRESULT hr = S_OK; CMyAllocator *pAlloc = new CMyAllocator(&hr); if (!pAlloc) { return E_OUTOFMEMORY; } if (FAILED(hr)) { delete pAlloc; return hr; } // Return the IMemAllocator interface. return pAlloc->QueryInterface(IID_IMemAllocator, void**)ppAllocator);} } 缺省情況下CBaseOutputPin首先從輸入pin中申請一個allocator, 2filter間的數據流動 1 傳遞Samples 本文講述了如何傳遞一個sample, 包括兩種模式下, 推模式下採用IMemInputPin的方法, 在拉模式下調用IAsyncReader的方法。 推模式 輸出pin通過調用IMemInputPin::Receive或者IMemInputPin::ReceiveMultiple方法來傳遞一個sample。 在Receive和ReceiveMultiple方法裏, 輸入pin可以阻塞數據流。 如果輸入pin阻塞, 那麼IMemInputPin::ReceiveCanBlock必須返回S_OK。 如果pin保證不會阻塞, 那麼ReceiveCanBlock方法要返回S_FALSE, 返回S_OK並不表明Receive方法阻塞, 只是表明可能阻塞。 儘管Receive可以阻塞一直等待某種資源變的可用, 但是它不能通過阻塞來等待數據流的到來。 因為如果上遊的filter正在等待下遊的filter正在等待下遊的filter釋放資源, 就會造成死鎖。 如果一個filter擁有多個輸入pin, 那麼其中的一個pin可以等待另外的一個pin接收數據。 例如AVI Mux filter就是通過這種方法來同步音頻和視頻流的。 如果有以下原因, pin可能拒絕sample。 1pin正在flushing 2pin沒有連接 3filter停止 4發生了其他錯誤 如果輸入pin拒絕了sample, 那麼Receive方法就要返回S_FALSE, 或者其它的錯誤碼, 如果Receive沒有返回S_OK, 那麼上遊的fitler就會停止發送sample。 前面三種錯誤都是可以預見的錯誤, 第四種是不可預見的錯誤, 即使pin正處於接收數據流德狀態, 當這種錯誤發生時, 接受pin就拒絕接受sample, 發送pin就給下遊的連接pin發送一個結束發送數據流的通知, 並且給Filter 圖表管理器發送一個EC_ERRORABORT事件通知。 在directshow的基類中CBaseInputPin::CheckStreaming方法用來檢查通常的數據流錯誤, 比如flushing, stopped, and so forth。 派生類要檢查所發生的錯誤。 在發生錯誤的時候, CBaseInputPin::Receive方法將發送一個結束數據流的通知和一個EC_ERRORABORT事件通知。 在拉模式下, IAsyncReader接口中, 輸入pin將通過以下方法從輸出pin中請求samples。 ? IAsyncReader::Request ? IAsyncReader::SyncRead ? IAsyncReader::SyncReadAligned Reques方法是異步的, 輸入pin調用IAsyncReader::WaitForNext來等待請求數據傳遞結束。 另外兩個方法是同步。 2數據處理 //略 3數據流結束的通知 當一個源filter結束發送數據流時, 它調用和它連接的filter的輸入pin的IPin::EndOfStream, 然後下遊的filter再依次通知與之相連的filter。 當EndOfStream方法一直調用到renderer filter的時候, 最後的一個filter就給filter圖表管理器發送一個EC_COMPLETE事件通知。 如果renderer有多個輸入pin, 當所有的輸入pin都接收到end of stream通知的時候, 它才會給filter圖表管理器發送一個EC_COMPLETE事件通知。 Filter必須在其他函數調用之後調用EndOfStream函數, 比如IMemInputPin::Receive.。 在一些情況下, 下遊的filter可能比源filter更早的發現數據流的結束。 在這種情況下, 下遊filter發送 結束stream的通知, 同時, IMemInputPin::Receive函數返回S_FALSE直到圖表管理器停止。 這個返回值提示源filter停止發送數據。 對EC_COMPLETE事件的缺省處理 缺省的情況下, filter圖表管理器並不將EC_COMPLETE事件通知發送給應用程序, 當所有的數據流都發送了EC_COMPLETE事件通知後, 它才給應用程序發送一個EC_COMPLETE事件通知。 所以, 應用程序只有在所有的數據流停止的時候才能接收到這個通知。 filter圖表管理器通過計算支持seeking接口的filter, 並且具有一個renderer pin, 沒有相應的輸出pin, 就可以確定數據流的數目。 Filter圖表管理器通過下面的方法來決定一個pin是否是個renderer 。 1 pin的IPin::QueryInternalConnections方法通過nPin參數返回0; 2 filter保露一個IAMFilterMiscFlags接口, 並且返回一個AM_FILTER_MISC_FLAGS_IS_RENDERER標誌。 在拉模式下的數據流結束通知 在IAsyncReader連接中, 源filter並不發送數據流結束的通知, 相應的發送數據流結束的通知是有renderer filter發出的。 4New Segments(本節翻譯的不好, 我自己都不理解, 亂七八糟) 一個段就是一組media samples, 這些sample具有共同的開始時間, 結束時間, 播放速率。 The IPin::NewSegment 方法用來通知一個new segments的開始。 源filter通過這種方法來通知下遊的filter segment的開始時間和播放速率。 例如, 如果源filter在數據流中改變了新的開始點, 它就用新的時間做參數來通知下遊的filter。 下遊的filter在處理sample的時候需要segment。 例如, 在楨間壓縮的時候, if the stop time falls on a delta frame, the source filter may need to send additional samples after the stop time. This enables the decoder to decode the final delta frame.為了確定正確的結束楨, 解碼器指向色gement的停止時間。 另外一個例子, 在音頻播放的過程中, 播放filter利用segment的速度和音頻sample速度來產生正確的輸出。 在推模式中, 源filter 產生一個新的segment, 並初始化。 在拉模式, 這個工作是由剖析器(parser)來完成的。 兩種情況下, filter都調用下遊filter的輸入pin上的NewSegment, 一直到達renderfilter。 當filters調用數據流時候, 必須序列化NewSegment。 當每一個新的segment, 數據流的時間都被重新設置為零, 當segment從零開始的時候, samples 重新貼上了time標籤。 5 Flushing 當graph運行的時候, 在整個graph中會有大量的數據流動。 同時也有一些數據排在隊列裏等到傳遞。 當graph移動這些未決的數據, 並在該內存塊中寫入新的數據是需要一定的時間的。 例如, 在seek命令後, 源filter在生成新的sample, 這些是需要一定時間的。 為了減小延遲, 下遊的filter在seek命令必須丟掉以前的sample。 這個拋棄sample的過程就叫flushing。 當事件改變了數據的流向時, 這可以使garph響應的更及時一些。 推模式和拉模式在處理flushing的時候有點不同。 我們先討論一下推模式, 然後再討論拉模式。 下面兩種情況下發生flushing 1 源filter調用下遊filter輸入pin的IPin::BeginFlush方法, 然後下遊的filter就開始拒絕從上遊filter接收數據流。 然後它開始拋棄它正在處理的samples, 繼續調用下遊filter的IPin::BeginFlush方法 2 當源filter準備好新的數據時, 它調用輸入pin的IPin::EndFlush方法, 這就告訴下遊的filter可以接收新的samples, 然後繼續調用下遊的filter的IPin::EndFlush。 在BeginFlush方法中, 輸入pin進行了下列工作 1 首先調用下遊filter的輸入pin上的Calls BeginFlush方法 2拒絕處理數據流, 包括Receive和endofstream方法 3 取消那些正在阻塞等待filter釋放allocator的等待, 4 如果filter正處於阻塞數據狀態, 那麼filter就退出阻塞。 例如當停止的時候Renderer filter 就阻塞, 此時, filter就要取消阻塞。 在EndFlush方法中, 輸入pin做了下列工作 1等待所有正在隊列中的samples被拋棄 2 釋放存放數據的buffer, 這一步也可能在BeginFlush方法裏, 但是, beginflush方法streaming 線程是不同步的。 Filter在BeginFlush和EndFlush方法之間不能夠處理如何數據 3清除所有的EC_COMPLETE通知 4調用下遊filter的EndFlush方法 此時, filter可以再次接收sample。 在拉模式中, parser Filter初始化flushing, 而不是由source filter, 它不僅調用了下遊filterIPin::BeginFlush and IPin::EndFlush方法, 它又調用了源filter輸出pin上的IAsyncReader::BeginFlush and IAsyncReader::EndFlush, 如果此時, 源filter有未決的read請求, 它就拋棄 6Seeking Filter通過IMediaSeeking接口支持seeking。 應用程序從filter 圖表管理器請求IMediaSeeking接口, 然後通過這個接口, 執行seek命令。 Filter圖表管理器傳遞到graph裏所有的renderer filter。 每個renderer 通過上遊filter的輸出pin來傳遞seek命令, 直到某一個filter可以執行這個seek命令。 一般來說, 源filter, 或者parser filter可以執行seek 命令。 當一個filter執行seek命令的時候, 它就flushes所有的未決的數據, 這是為了減少seek命令的遲延。 當一個seek命令後, stream time設置為零。 下面的圖表演示了seek過程 Directshow中Filter開發基礎 圖1 如果一個parser filter 的輸出pin不止一個的話, 它就指定一個pin來接收seek commands, 其他的pin當接收到seek 命令時, 就拒絕或者忽略seek 命令。 這樣, parser就保持了所有的數據流同步。 IMediaPosition接口(略) 3pin連接時數據格式的動態改變 當兩個filter連接的時候, 他們會就某種媒體類型達成協議。 這種數據類型用來描述上遊filter將傳遞什麼格式的數據。 大多數情況下, 在連接的持續過程中, 這個媒體類型是不變的, 但是, directshow也支持動態改變媒體類型。 Directshow定義了一些機制來支持動態改變媒體類型。 關鍵在於filter graph的狀態和將要改變的類型。 如果graph處於停止狀態, pin可以重新連接, 重新就某個媒體類型達成協議。 一些filter還支持動態的連接, 當graph處於活動狀態, 並且不支持動態的重新連接的時候, 有三種機制支持媒體類型的改變。 QueryAccept (Downstream) 適用於輸出pin向下遊的filter提出改變媒體類型, 並且新的媒體格式的大小不超過原來的buffer。 QueryAccept (Upstream) 適用於輸入pin首先向上遊的filter提出媒體類型的改變, 新的媒體類型格式可以和原來的大小一致, 也可以大於。 ReceiveConnection適用於輸出pin首先提出改變媒體類型, 但是新的媒體類型的格式大於原來的buffer。 4 Threads and Critical Sections 本章講述一下dshow filters的線程和臨界區, 這樣, 你在開發filter的過程中就可以避免死鎖和系統崩潰。 1 The Streaming and Application Threads 任何一個Directshow的應用程序中都至少包括兩個重要的線程, 一個是應用程序線程, 一個或者多個的Streaming線程。 在streaming線程中, samples不斷的傳遞, 然後在應用程序中, **的狀態不斷的變化。 Streaming的主線程一般都是由源filter或者parser Filter來創建, 其他的filter可以創建用來傳遞samples的工作線程, 所有的這些線程都稱為streming線程。 下面我們看看應用程序線程和streaming線程經常用到的 Streaming thread(s):IMemInputPin::Receive, IMemInputPin::ReceiveMultiple, IPin::EndOfStream, IMemAllocator::GetBuffer. Application thread: IMediaFilter::Pause, IMediaFilter::Run, IMediaFilter::Stop, IMediaSeeking::SetPositions, IPin::BeginFlush, IPin::EndFlush. Either IPin::NewSegment. 當我們的用戶應用程序在等待用戶輸入的同時, 數據流可以通過streaming線程在graph圖中流動。 在多線程中, filter在暫停的時候, 創建了資源, 然後在Streaming方法中使用這些資源, 當這個filter停止的時候, 就銷毀這些資源, 這樣就很危險, 如果你不小心, streaming線程有可能使用了已經被銷毀的資源。 解決的方法就是通過臨界區來保護這些資源, 來同步這些線程。 一個filter需要一個臨界區來保護filter的狀態。 CBaseFilter類有一個成員變量來保護這個filter的狀態, CBaseFilter::m_pLock。 這個臨界區稱為filter 鎖。 同時, 每一個輸入pin都需要一個臨界區來保護streaming線程使用的資源, 這些臨界區成為streaming鎖。 你必須在你派生的pin類中(輸入pin)中來設置這些變量。 使用一個CCritSec類很容易實現臨界區。 這個CCritSec類中包含一個CRITICAL_SECTION窗口對象。 可以使用CAutoLock類來鎖住這個臨界區。 當一個filter停止或者flushes的時候, 就必須同步應用線程和streaming線程。 為了避免死鎖, 你必須要解鎖streaming線程。 Streaming線程加鎖的原因主要有以下; 1 當streaming線程通過IMemAllocator::GetBuffer方法來請求samples的時候, 如果此時所有的allocators 分配的samples都在使用, 那麼此時線程就要等到 2 等到其他的filter從streaming線程方法返回, 比如, Receive 3 在streaming方法中等待其他的資源的釋放。 4 renderer Filter正在Receive方法等待, 如果filter停止, 就處於死鎖狀態了 因此, 當filter停止或者flushes時候, 必須做下列事情 1 一定要釋放它正在佔用的任何資源 2盡可能快地從streaming方法中返回, 如果這個方法正在等到某種資源, 就放棄等待, 立即返回 3在Receive方法開始停止接受samples, 這樣就不會再請求新的資源了 4 Stop方法一定要decommit所有filter的內存分配器。 (當然了, CBaseInputPin會自動地處理這個事情) Flushing和stoping方法在應用程序中都會發生。 IMediaControl::Stop方法可以使filter停止運行。 Filter管理器一般讓stop命令從render filter開始, 逆流向上到源filter逐漸停止。 通過CBaseFilter::Stop方法, 在這個方法返回的時候, filter的狀態就轉變為停止狀態。 Filter在seek命令時一般都會Flushing。 Flush命令一般都從源filter或者parser filter開始, 然後向下遊傳遞執行。 Flushing有兩個狀態, IPin::BeginFlush方法通知一個filter開始拋棄所有的正在接受到的數據IPin::EndFlush方法通知filterflush結束, 可以開始再次的接收數據。 Flushing之所以有兩個階段, 因為 Beginflush方法是由應用程序調用的, 同時有可能還在傳遞數據。 這樣, 在調用beginflush方法後, 還有數據在filter中流動, 此時filter就要拋棄這些數據。 在調用endflush後, 就能保證filter接受的數據都是新的, 可以處理了。 下面的一些臨界區的例子, 演示了如何保護filter的重要的方法, 比如Pause, receive等 2 Pausing 在filter的狀態發生變化的時候, 必須要加鎖, 在Pause方法中, 要創建filter需要的資源 HRESULT CMyFilter::Pause() { CAutoLock lock_it(m_pLock); /* Create filter resources. */ return CBaseFilter::Pause(); } CBaseFilter::Pause方法設置filter處於State_Paused)狀態, 然後調用和filter相連接的pin上的CBasePin::Active方法, Active方法通知pin:filter開始處於激活狀態了。 如果pin需要創建資源, 那麼就要派生Active方法了 HRESULT CMyInputPin::Active() { // You do not need to hold the filter lock here. It is already held in Pause. /* Create pin resources. */ return CBaseInputPin::Active() } 3 Receiving and Delivering Samples 下面的偽代碼演示了如何派生輸入pin的Receive方法 HRESULT CMyInputPin::Receive(IMediaSample *pSample) { CAutoLock cObjectLock(&m_csReceive); // Perhaps the filter needs to wait on something. WaitForSingleObject(m_hSomeEventThatReceiveNeedsToWaitOn, INFINITE); // Before using resources, make sure it is safe to proceed. Do not // continue if the base-class method returns anything besides S_OK. hr = CBaseInputPin::Receive(pSample); if (hr != S_OK) { return hr; } /* It is safe to use resources allocated in Active and Pause. */ // Deliver sample(s), via your output pin(s). for (each output pin) pOutputPin->Deliver(pSample); return hr; } Receive方法設置了一個streaming鎖, 不是filter鎖。 Filter在開始處理數據之前可能需要等待其他事件發生, 這裏就調用了WaitForSingleObject。 當然並不是所有的filter都需要這樣做。 CBaseInputPin::Receive方法驗證一些基本的streaming條件, 如果filter停止舊返回VFW_E_WRONG_STATE, 如果filter flushing該方法返回s_FALSE;如果沒有返回S_OK就表示Receive方法應該立即返回, 不能處理sample。 在sample被處理後, 就調用CBaseOutputPin::Deliver.方法向下傳遞。 依次類推。 數據開始傳遞下去。 4 Delivering the End of Stream 當一個輸入pin接收到一個數據流停止的通知後, 它就會向下傳播下去。 下遊的可以從這個輸入pin接收數據的filter也應該接收到數據流停止的通知。 如果filter還有未決的數據沒有處理, filter應該在它傳遞停止通知之前一定要將數據傳遞下去, 在end tream消息發佈以後, 不能再向下遊filter發送數據了 HRESULT CMyInputPin::EndOfStream() { CAutoLock lock_it(&m_csReceive); /* If the pin has not delivered all of the data in the stream (based on what it received previously), do so now. */ // Propagate EndOfStream call downstream, via your output pin(s). for (each output pin) { hr = pOutputPin->DeliverEndOfStream(); } return S_OK; } CBaseOutputPin::DeliverEndOfStream調用了與輸出pin連接的輸入pin的IPin::EndOfStream方法來通知下遊的filter數據流即將停止了。 5 Flushing Data 下面的偽代碼演示IPin::BeginFlush方法 HRESULT CMyInputPin::BeginFlush() { CAutoLock lock_it(m_pLock); // First, make sure the Receive method will fail from now on. HRESULT hr = CBaseInputPin::BeginFlush(); // Force downstream filters to release samples. If our Receive method // is blocked in GetBuffer or Deliver, this will unblock it. for (each output pin) { hr = pOutputPin->DeliverBeginFlush(); } // Unblock our Receive method if it is waiting on an event. SetEvent(m_hSomeEventThatReceiveNeedsToWaitOn); // At this point, the Receive method can't be blocked. Make sure // it finishes, by taking the streaming lock. (Not necessary if this // is the last step.) { CAutoLock lock_2(&m_csReceive); /* Now it's safe to do anything that would crash or hang if Receive were executing. */ } return hr; } 當開始Flushing的時候, BeginFlush方法使用了filter鎖。 如果使用streaming鎖不是很安全, 因為flush是發生在應用程序中, 有可能此時streaming 線程正處於Receive方法中。 Pin要保證Receive方法沒有被阻塞, 否則, 後續調用Receive方法都會失敗。 CBaseInputPin::BeginFlush設置了一個標誌位CBaseInputPin::m_bFlushing., 如果這個標誌為TRUE, Receive方法就會失敗。 在下遊filter傳遞Beginflush方法的時候, pin一定要保證下遊的filter釋放他們的samples並且從Receive方法中返回。 這就保證了輸入pin不會阻塞在GetBuffer和Receive方法中。 如果你的pin的Receive方法正在等待某種資源, 那麼GeginFlush方法就通知設置某些特定事件來結束這種等待, 這就保證Receive方法能夠立即返回, m_bFlushing標誌就阻止Receive方法調用。 對於一些filter來說, 這些都是必須要做的, EndFlush方法通知filter可以重新接收數據了。 Endflush方法使用的是filter鎖。 然後向下傳播。 HRESULT CMyInputPin::EndFlush() { CAutoLock lock_it(m_pLock); for (each output pin) hr = pOutputPin->DeliverEndFlush(); return CBaseInputPin::EndFlush(); } CBaseInputPin::EndFlush重新設置m_bFlushing標誌為FALSE。 這樣Receive方法就可以開始接收數據了, 必須在最後調用這個方法。 6 Stopping Stop方法必須unblock Receive方法並且decommit filter的內存分配器。 這樣就使GetBuffer返回。 Stop方法使用了filter鎖, 然後調用了CBaseFilter::Stop, 這個stop方法調用了filter pins上的CBasePin::Inactive方法。 HRESULT CMyFilter::Stop() { CAutoLock lock_it(m_pLock); // Inactivate all the pins, to protect the filter resources. hr = CBaseFilter::Stop(); /* Safe to destroy filter resources used by the streaming thread. */ return hr; } Override the input pin's Inactive method as follows: HRESULT CMyInputPin::Inactive() { // You do not need to hold the filter lock here. // It is already locked in Stop. // Unblock Receive. SetEvent(m_hSomeEventThatReceiveNeedsToWaitOn); // Make sure Receive will fail. // This also decommits the allocator. HRESULT hr = CBaseInputPin::Inactive(); // Make sure Receive has completed, and is not using resources. { CAutoLock c(&m_csReceive); /* It is now safe to destroy filter resources used by the streaming thread. */ } return hr; } 7 Getting Buffers 如果你有一個內存分配器來分配資源, 那麼GetBuffer方法採用streaming鎖。 HRESULT CMyInputAllocator::GetBuffer( IMediaSample **ppBuffer, REFERENCE_TIME *pStartTime, REFERENCE_TIME *pEndTime, DWORD dwFlags) { CAutoLock cObjectLock(&m_csReceive); /* Use resources. */ return CMemAllocator::GetBuffer(ppBuffer, pStartTime, pEndTime, dwFlags); } 8 Streaming Threads and the Filter Graph Manager 當filter圖表管理器停止graph, 它要等待所有的streaming線程停止, 9 Summary of Filter Threading Streaming線程調用的函數 IMemInputPin::Receive IMemInputPin::ReceiveMultiple IPin::EndOfStream IPin::NewSegment IMemAllocator::GetBuffer 看看應用程序調用的函數把 狀態改變 IBaseFilter::JoinFilterGraph, IMediaFilter::Pause, IMediaFilter::Run, IMediaFilter::Stop, IQualityControl::SetSink. 參考時鐘 IMediaFilter::GetSyncSource, IMediaFilter::SetSyncSource. Pin的操作 IBaseFilter::FindPin, IPin::Connect, IPin::ConnectedTo, IPin::ConnectionMediaType, IPin::Disconnect, IPin::ReceiveConnection. 內存的分配 IMemInputPin::GetAllocator, IMemInputPin::NotifyAllocator. Flushing IPin::BeginFlush, IPin::EndFlush. 5質量控制管理 DirectShow Base Classes提供了一些缺省的方法來控制視頻的質量。 質量消息從renderer開始, 這個renderer基類為CBaseVideoRenderer,這個基類有下列一些行為 1 當視頻render接收到sample的時候, 它就將sample上的時間戳和當前的參考時間比較 2 視頻render產生一個質量消息, 在基類中, 質量消息的比例值被限制在500(50%)~~2000(200%), 超出這個範圍, 質量就變得不好 3缺省的時候, render就給上遊的filter輸出pin發送一個質量消息, 應用程序可以同過setsink方法來override這種行為。 當消息被逆流傳遞到上一個filter時會發生什麼呢?一般來說, 上遊的filter都是一個transform 過濾器。 這種transformfilter都有一個CTransformInputPin and CTransformOutputPin。 1 CTransformOutputPin::Notify方法調用CTransformFilter::AlterQuality 2transformfilte可以通過重載AlterQuality方法來處理質量消息, 缺省的情況下, AlterQuality忽略質量消息 3如果AlterQuality不處理質量消息, 輸出pin就調用輸入pin上的CBaseInputPin::PassNotify, 4PassNotify方法就將質量消息傳遞給上遊的其他的filter。 假定沒有傳輸filter來處理質量消息, 質量消息就最後到達了源filter。 在基類中, CBasePin::Notify 返回 E_NOTIMPL。 源filter如何處理質量消息取決於源filter的nature。 一些filter可以調整他們發送sample的速率。 Directshow中Filter開發基礎 圖2 6Directshow和com 微軟的Directshow是基於COM的, 如果你開發自己的filter, 你一定實現一個com對象。 Directshow的基類提供了一個框架可以實現com接口, 你不一定適用基類, 但是使用基類可以減輕你的開發任務。 下面我們講講dshow和com關係 我們假設你瞭解如何開發com的客戶端, 也就是說你瞭解IUnknown方法, 但是並不詳細瞭解如何開發一個com對象, Directshow將如何開發com的一切細節都替你處理好了。 如果你有開發com的經驗, 你應該讀一下Using CUnknown一章。 Com是一個協議, 它規定了所有組件必須遵循的規則, 如何應用這些規則就留給開發者了。 在Directshow中, 所有的對象都是從一系列的基類中繼承而來, 這些基類的函數和構造器將com對象的構造工作都基本做完了, 例如引用計數, 接口等功能。 你將你的filter從基類中繼承, 你就繼承了基類的函數, 同時你的filter就是一個com對象了。 為了好好繼承基類, 你必須要瞭解基類是如何實現com對象的。 本章要求你瞭解一下三個內容 How IUnknown Works How to Create a DLL. How to Register DirectShow Filters. 1 How IUnknown Works 通過IUnknown接口的方法應用程序可以請求對象的接口, 管理組件的引用計數。 引用計數 引用是個內部變量, 通過addref方法可以是變量增加, 通過release方法可以使該變量值減少。 基類管理者這個引用計數, 並且是多線程同步。 接口請求 接口請求也是簡單易懂的。 調用者傳遞兩個參數, 一個是接口的ID, 和接口的指針的地址。 如果組件支持該接口, 就將該指針指向接口, 引用計數增加1, 返回S_OK, 否則, 將該指針設置為NULL, 返回E_NOINTERFACE。 下面的偽代碼演示了QueryInterface函數 if (IID == IID_IUnknown) set pointer to (IUnknown *)this AddRef return S_OK else if (IID == IID_ISomeInterface) set pointer to (ISomeInterface *)this AddRef return S_OK else if ... else set pointer to NULL return E_NOINTERFACE 兩個組件的querinterface函數的區別就在於IIDS的列表不同, 對於組件支持的每一個接口, 都要檢查IID是否正確。 聚合和委託 組件的聚合對於調用者必須是透明的, 因此, 聚合體必須只能暴露一個IUnknown接口, 被聚合組件的接口聽從外部的組件的調用, 否則客戶端就看到兩個不同的IUnknown接口, 如果組件不被聚合, 他們都有自己的實現。 為了支持聚合, 組件必須增加一個額外的間接的指針, 用來分別指向外部接口和內部接口, nondelegating IUnknown接口就可以完成這項工作。 外部的接口是公開的, 還叫做IUnknown, 內部的接口叫做INonDelegatingUnknown., 這個名字不是com定義的, 因為它不是一個公開的接口 當客戶端創建了一個組件的實例, 它調用IClassFactory::CreateInstance方法, 一個參數就指向組件的IUnknown接口, 組件就利用這個參數來保存一個變量用來標示需要採用哪個IUnknown接口, 看看下面的代碼 CMyComponent::CMyComponent(IUnknown *pOuterUnkown) { if (pOuterUnknown == NULL) m_pUnknown = (IUnknown *)(INonDelegatingUnknown *)this; else m_pUnknown = pOuterUnknown; [ ... more constructor code ... ] } 委託的IUnknown接口調用的它的副本, 如下 HRESULT QueryInterface(REFIID iid, void **ppv) { return m_pUnknown->QueryInterface(iid, ppv); } Using CUnknown Directshow在CUnknown.基類中實現了IUnkown接口, directshow的其他基類也多是從CUnknown類中繼承過來的, 所以, 你也可以從這個類派生一個新類或者從其他基類派生你自己的類。 INonDelegatingUnknown CUnknown類還實現了一個INonDelegatingUnknown接口, 這個接口是用來管理引用計數的。 大多數情況下, 你的派生類可以直接這個接口的兩個管理引用計數的函數而不用做任何的修改。 但是你要記住, 當引用計數等於0的時候, CUnknown就要銷毀自己。 有時候你必須要派生CUnknown::NonDelegatingQueryInterface函數, 因為在基類中, 如果它發現請求接口的ID不是IID_IUnknown的時候就返回E_NOINTERFACE,在你的派生類中, 你就要自己來測試你所支持的接口ID, 看下面的例子 STDMETHODIMP NonDelegatingQueryInterface(REFIID riid, void **ppv) { if (riid == IID_ISomeInterface) { return GetInterface((ISomeInterface*)this, ppv); } // default return CUnknown::NonDelegatingQueryInterface(riid, ppv); } GetInterface將接口指針設置為所請求的接口, 返回s_ok, 增加引用計數, 缺省的情況下, 要調用你繼承類的NonDelegatingQueryInterface IUnknown 日前所述, 任何一個組件的IUnknown接口的處理流程都是一樣的。 因為它僅僅是根據客戶端的請求返回正確的接口即可。 因此, 為了方便, 在Combase.h頭文件中聲明了一個宏DECLARE_IUNKNOWN,這種宏中定義了三個內聯函數, 如下 STDMETHODIMP QueryInterface(REFIID riid, void **ppv) { return GetOwner()->QueryInterface(riid,ppv); }; STDMETHODIMP_(ULONG) AddRef() { return GetOwner()->AddRef(); }; STDMETHODIMP_(ULONG) Release() { return GetOwner()->Release(); }; CUnknown::GetOwner函數返回擁有這個IUnknown接口的組件, 對於聚合組件, ower指的外部的組件。 否則, 指的就是自己。 在你自己的類中的公共部分, 包含DECLARE_IUNKNOWN宏聲明。 類的構造 你自己的類的構造函數一定要從基類的構造函數繼承過來, 如下 CMyComponent(TCHAR *tszName, LPUNKNOWN pUnk, HRESULT *phr) : CUnknown(tszName, pUnk, phr) { /* Other initializations */ }; 構造函數一般都有三個參數, 這三個參數傳遞到CUnknown類的構造函數中, 其實你的構造函數基本不用作任何工作, 一切都在CUnknown函數的構造函數給你做好了。 TszName指定組件的名字 pUnk指向一個IUnknown指針 pHr指向一個HRESULT值, 表示成功還是失敗。 下面的例子演示了, 如何派生你自己的類, 這個類支持Iunknown接口還有一個ISomeInterface接口 class CMyComponent : public CUnknown, public ISomeInterface { public: DECLARE_IUNKNOWN; STDMETHODIMP NonDelegatingQueryInterface(REFIID riid, void **ppv) { if( riid == IID_ISomeInterface ) { return GetInterface((ISomeInterface*)this, ppv); } return CUnknown::NonDelegatingQueryInterface(riid, ppv); } CMyComponent(TCHAR *tszName, LPUNKNOWN pUnk, HRESULT *phr) : CUnknown(tszName, pUnk, phr) { /* Other initializations */ }; // More declarations will be added later. }; 這個例子假定了一下情況 1, CUnknown類實現了IUnknown接口, 如何新的組件從CUnknown基類繼承過來, 那麼同時也就繼承了基類所支持的接口。 你可以從其他繼承於CUnknown的類派生你自己的類, 而不一定要從CUnknown派生 2, DECLARE_IUNKNOWN宏將IUnknown接口的三個函數聲明為內聯函數 3, CUnknown類提供了INonDelegatingUnknown.的實現 4, 如果新的組件支持的接口不僅僅是IUnknown, 那麼就一定要重新繼承INonDelegatingUnknown.函數, 測試新接口的ID, 如上代碼 5, 派生類的構造函數觸發了CUnknown的構造函數 下一步就是使得應用程序創建組件的一個實例, 這就要求瞭解DLLs和類廠, 以及構造函數的關係, 請看How to Create a DLL. 2How to Create a DLL. 在客戶端創建一個com對象的實例以前, 它首先通過CoGetClassObject方法創建一個對像類廠的實例。 然後客戶端調用類廠的IClassFactory::CreateInstance方法。 類廠自動創建一個組件然後返回一個指向組件對像接口的指針。 CoCreateInstance方法是上面兩步的綜合。 下面的圖演示了對象創建 Directshow中Filter開發基礎 圖3 CoGetClassObject方法調用了DLL中的DllGetClassObject方法, 這個方法創建了一個類廠對象, 並且返回一個類廠對象的接口。 Directshow已經替你完成了DllGetClassObject方法 為了瞭解他們是如何工作, 你必須瞭解directshow是如何實現類廠的。 類廠也是一個com對象, 它可以用來創建其他的com組件。 每個類廠只能用來創建特定的com組件對象。 在directshow中, 每一個類廠都是C++類CClassFactory的一個實例, 類廠是通過一個叫做類廠模板CFactoryTemplate,來實現的。 每個類廠類都有一個指向類廠模板的指針, 類廠模板包含了要創建的組件的信息, 比如CLSID, 和一個指向創建對像函數的指針。 DLL中定義了一個全局的類廠模板的數組, 每一個動態的DLL中都有這麼一個全局的模版數組, 當DllGetClassObject函數創建組件對象的時候, 它就搜索全局數組裏的匹配的CLSID, 如果它找到匹配的數組, 它就創建一個包含一個指向匹配模板指針的類廠對象, 當客戶端調用IClassFactory::CreateInstance 方法的時候, 類廠就調用模版中的函數來創建組件對象, 下面的DllGetClassObject函數是我從dshow的基類中找到的, 我們仔細看看吧 STDAPI DllGetClassObject( REFCLSID rClsID, REFIID riid, void **pv) { if (!(riid == IID_IUnknown) && !(riid == IID_IClassFactory)) { return E_NOINTERFACE; } // 首先在類廠模板數組中查找相應的CLSID // class id for (int i = 0; i < g_cTemplates; i++) { const CFactoryTemplate * pT = &g_Templates[i]; if (pT->IsClassID(rClsID)) { //如果找到, 就用這個類廠模板作參數生成一個類廠對像 // found a template - make a class factory based on this // template *pv = (LPVOID) (LPUNKNOWN) new CClassFactory(pT); if (*pv == NULL) { return E_OUTOFMEMORY; } ((LPUNKNOWN)*pv)->AddRef(); return NOERROR; } } return CLASS_E_CLASSNOTAVAILABLE; } Directshow中Filter開發基礎 圖4 下面我們看看類廠模板數組 類廠模板數組包含以下變量 const WCHAR * m_Name; // Name const CLSID * m_ClsID; // CLSID LPFNNewCOMObject m_lpfnNew; // Function to create an instance of the component LPFNInitRoutine m_lpfnInit; // Initialization function (optional) const AMOVIESETUP_FILTER * m_pAMovieSetup_Filter; // Set-up information (for filters) 兩個函數指針m_lpfnNew and m_lpfnInit使用如下的定義 typedef CUnknown *(CALLBACK *LPFNNewCOMObject) (LPUNKNOWN pUnkOuter, HRESULT *phr); Typedef void (CALLBACK *LPFNInitRoutine) (BOOL bLoading, const CLSID *rclsid); 第一個用來創建對象的實例函數, 第二個函數是初始化函數, 如果你定義了這個函數, 在動態庫DLL的進入點函數就會調用這個初始化函數。 假設你定義了一個DLL, 這個DLL包含了一個叫做CMYComponent組件, 它繼承與CUnknown, 你必須在你的DLL定義下面的幾個東西 1 一個創建你的組件的實例的公有函數。 2一個全局的類廠模板數組, 名字一定要命名為g_Templates, 這個數組包含創建組件的類廠模板 3一個叫做全局變量g_cTemplates, 這個變量用來表示類廠模板數組的大小。 下面是示例代碼 // Public method that returns a new instance. CUnknown * WINAPI CMyComponent::CreateInstance(LPUNKNOWN pUnk, HRESULT *pHr) { CMyComponent *pNewObject = new CMyComponent(NAME("My Component"), pUnk, pHr ); if (pNewObject == NULL) { *pHr = E_OUTOFMEMORY; } return pNewObject; } CFactoryTemplate g_Templates[1] = { { L"My Component", // Name &CLSID_MyComponent, // CLSID CMyComponent::CreateInstance, // Method to create an instance of MyComponent NULL, // Initialization function NULL // Set-up information (for filters) } }; int g_cTemplates = sizeof(g_Templates) / sizeof(g_Templates[0]); 類廠的CreateInstance方法調用組件的構造函數, 並返回一個指向新類實例的一個指針。 參數punk指向IUnknown接口指針, 看看我從基類裏找到的Createinstance函數的代碼 STDMETHODIMP CClassFactory::CreateInstance( LPUNKNOWN pUnkOuter, REFIID riid, void **pv) { CheckPointer(pv,E_POINTER) ValidateReadWritePtr(pv,sizeof(void *)); /* Enforce the normal OLE rules regarding interfaces and delegation */ if (pUnkOuter != NULL) { if (IsEqualIID(riid,IID_IUnknown) == FALSE) { return ResultFromScode(E_NOINTERFACE); } } /* Create the new object through the derived class's create function */ HRESULT hr = NOERROR; CUnknown *pObj = m_pTemplate->CreateInstance(pUnkOuter, &hr); if (pObj == NULL) { if (SUCCEEDED(hr)) { hr = E_OUTOFMEMORY; } return hr; } /* Delete the object if we got a construction error */ if (FAILED(hr)) { delete pObj; return hr; } /* Get a reference counted interface on the object */ /* We wrap the non-delegating QI with NDAddRef & NDRelease. */ /* This protects any outer object from being prematurely */ /* released by an inner object that may have to be created */ /* in order to supply the requested interface. */ pObj->NonDelegatingAddRef(); hr = pObj->NonDelegatingQueryInterface(riid, pv); pObj->NonDelegatingRelease(); /* Note that if NonDelegatingQueryInterface fails, it will */ /* not increment the ref count, so the NonDelegatingRelease */ /* will drop the ref back to zero and the object will "self-*/ /* destruct". Hence we don't need additional tidy-up code */ /* to cope with NonDelegatingQueryInterface failing. */ if (SUCCEEDED(hr)) { ASSERT(*pv); } return hr; } 我們發現, 類廠就是通過全局變量g_Templates and g_cTemplates來創建組件的, 所以, g_Templates and g_cTemplates的名字不能改變 下面看看dll的導出函數 DLL Functions 一個動態庫必須導出如下的函數, 才能夠註冊, 註銷, 加載。 ? DllMain: The DLL entry point. The name DllMain is a placeholder for the library-defined function name. The DirectShow implementation uses the name DllEntryPoint. For more information, see the Platform SDK. Dshow沒有DllMain, 改用了DllEntryPoint。 進入點函數 ? DllGetClassObject: Creates a class factory instance. Described in the previous sections. ? DllCanUnloadNow: Queries whether the DLL can safely be unloaded. ? DllRegisterServer: Creates registry entries for the DLL. ? DllUnregisterServer: Removes registry entries for the DLL. 當然了, 前面的三個函數, directshow已經替我們完成了, 如果你的類廠模板數組中的m_lpfnInit指針有一個初始化函數, 那麼在DLL的入口函數就會被調用。 你自己必須要提供DllRegisterServer and DllUnregisterServer函數來實現組件的註冊和反註冊, 但是Directshow提供了提供了一個函數叫做AMovieDllRegisterServer2已經替你做完了必要的工作, 所以你要做的就很簡單了, 只需如下就可以了 STDAPI DllRegisterServer() { return AMovieDllRegisterServer2( TRUE ); } STDAPI DllUnregisterServer() { return AMovieDllRegisterServer2( FALSE ); } 當然, 在DllRegisterServer and DllUnregisterServer 函數中, 你可以根據需要定制自己的註冊信息, 如果你的組件包含一個過濾器, 你就要自己來做一下額外的工作了, 具體的你可以下節How to Register DirectShow Filters. 在你的module-definition (.def) file文件中, 除了進入點函數, 你要導出下面的函數, 實例如 EXPORTS DllGetClassObject PRIVATE DllCanUnloadNow PRIVATE DllRegisterServer PRIVATE DllUnregisterServer PRIVATE 你可以用Regsvr32.exe來註冊你的組件 3How to Register DirectShow Filters. Directshow的filter一般都註冊在兩個地方 1 包含filter的DLL一般都註冊為filter的COM 服務器, 當用戶調用CoCreateInstance來創建一個filter的時候, 微軟的COM庫就從這個註冊表的入口加載DLL。 2 另外, filter可以註冊到filter 種類裏, 這樣, System Device Enumerator and the Filter Mapper就可以找到filter了。 第二種註冊不是必須的, 只要filter註冊成為com服務器, 一個應用程序就可以創建一個filter 並將它加入到 filter Graph中, 但是, 如果你想要你的filter 可以被System Device Enumerator and the Filter Mapper發現, 你必須註冊到filter 種類裏。 Com服務器的入口註冊有下列以些鍵值 HKEY_CLASSES_ROOT CLSID Filter CLSID REG_SZ: (Default) = Friendly name InprocServer32 REG_SZ: (Default) = File name of the DLL REG_SZ: ThreadingModel = Both 註冊成filter 種類裏需要下面的鍵值 HKEY_CLASSES_ROOT CLSID Category Instance Filter CLSID REG_SZ: CLSID = Filter CLSID REG_BINARY: FilterData = Filter information REG_SZ: FriendlyName = Friendly name Category is the GUID of a filter category. 所有的filter信息在註冊表的filter種類都如下所示 HKEY_CLASSES_ROOT\CLSID\{DA4E3DA0-D07D-11d0-BD50-00A0C911CE86}\Instance 1 聲明filter信息Declaring Filter Information 第一步要聲明filter的信息, directshow定義了如下的結構來聲明filter , pin和media Types Structure Description AMOVIESETUP_FILTER Describes a filter. AMOVIESETUP_PIN Describes a pin. AMOVIESETUP_MEDIATYPE Describes a media type. 這些結構是必須的。 AMOVEIESETUP_FILTER結構包含一個指針指向AMOVIESETUP_PIN結構數組, 這兩個結構中都有一個指針指向AMOVEIESETUP_MEDIATYPE。 這些結構提供了足夠的信息可以讓IFilterMapper2指針找到filter 的位置。 但是, 這並不能完全描述一個filter, 例如, 如果一個filter創建了一個pin的多個實例, 你只需要聲明一個AMOVIESETUP_PIN結構即可。 同樣, 一個filter 沒有必須支持註冊的所有的媒體類型, 也沒有必要註冊所有的媒體類型。 在你的DLL中聲明一些全局的Set_up結構變量, 如下 static const WCHAR g_wszName[] = L"Some Filter"; AMOVIESETUP_MEDIATYPE sudMediaTypes[] = { { &MEDIATYPE_Video, &MEDIASUBTYPE_RGB24 }, { &MEDIATYPE_Video, &MEDIASUBTYPE_RGB32 }, }; AMOVIESETUP_PIN sudOutputPin = { L"", // Obsolete, not used. FALSE, // Is this pin rendered? TRUE, // Is it an output pin? FALSE, // Can the filter create zero instances? FALSE, // Does the filter create multiple instances? &GUID_NULL, // Obsolete. NULL, // Obsolete. 2, // Number of media types. sudMediaTypes // Pointer to media types. }; AMOVIESETUP_FILTER sudFilterReg = { &CLSID_SomeFilter, // Filter CLSID. g_wszName, // Filter name. MERIT_NORMAL, // Merit. 1, // Number of pin types. &sudOutputPin // Pointer to pin information. }; Filter的名字被聲明成靜態全局變量, 因為它有可能在其它地方用到。 2 聲明類廠模板數組Declaring the Factory Template CFactoryTemplate g_Templates[] = { { g_wszName, // Name.上面定義的全局變量 &CLSID_SomeFilter, // CLSID. CSomeFilter::CreateInstance, // Creation function. NULL, &sudFilterReg // Pointer to filter information. } }; int g_cTemplates = sizeof(g_Templates) / sizeof(g_Templates[0]); 3生成DllRegisterServer 最後一步是生成DllRegisterServer函數, 包含組件的DLL必須導出這個函數, 這個函數在安裝的時候被調用, 或者當用戶運行Regsvr32.exe時調用到。 簡單的實現如下 STDAPI DllRegisterServer(void) { return AMovieDllRegisterServer2(TRUE); } AMovieDllRegisterServer2對於g_Templates數組中的所有組件都創建註冊表入口, 但是這個函數有一些限制, 第一, 它將所有的filter都註冊到"DirectShow Filters"類下 (CLSID_LegacyAmFilterCategory), 其實並非所有的filter都屬於這個種類。 例如, 捕捉filter和壓縮filter就有他們自己的種類, 第二, 如果你的filte支持一個硬件設備, 你必須要註冊額外的兩個信息the medium and the pin category., 但是AMovieDLLRegisterServer2並不支持, pin 的種類定義了一個pin的函數方法。 Mediums和硬件的驅動有關。 如果你要註冊filter的種類, medium或者pin的種類, 你可以在DllRegisterServer()中調用IFilterMapper2::RegisterFilter, 這個方法有個REGFILTER2結構, 包含了filter的信息。 為了支持複雜的情況, REGFILTER2結構支持兩種不同格式pin的註冊, dwVersion表示兩種格式 如果dwVersion為1, pin的類型就是AMOVIESETUP_PIN 如果dwVersion為2, 拼得類型就是REGFILTERPINS2. REGFILTERPINS2.結構中包含mediums和pin的categories 下面的例子演示了, 如何在DllRegistServer中調用IFilterMapper2::RegisterFilter REGFILTER2 rf2FilterReg = { 1, // Version 1 (no pin mediums or pin category). MERIT_NORMAL, // Merit. 1, // Number of pins. &sudPins // Pointer to pin information. }; STDAPI DllRegisterServer(void) { HRESULT hr; IFilterMapper2 *pFM2 = NULL; hr = AMovieDllRegisterServer2(TRUE); if (FAILED(hr)) return hr; hr = CoCreateInstance(CLSID_FilterMapper2, NULL, CLSCTX_INPROC_SERVER, IID_IFilterMapper2, (void **)&pFM2); if (FAILED(hr)) return hr; hr = pFM2->RegisterFilter( CLSID_SomeFilter, // Filter CLSID. g_wszName, // Filter name. NULL, // Device moniker. &CLSID_VideoCompressorCategory, // Video compressor category. g_wszName, // Instance data. &rf2FilterReg // Pointer to filter information. ); pFM2->Release(); return hr; } 4filter註冊指南 Filter的註冊信息決定了, 在filter Graph管理器中如何Intelligent Connect.。 因此, 你必須要遵從下列的規則, 使得你的filter能夠正常運行。 1 你是否需要在註冊表中保存你的filter數據, 對於許多filter來說, 沒有必要讓filter Mapper和System Device Enumerator來發現你的filter, 只要你註冊了你的filter, 你的應用程序通過 ConCreateInstance方法來創建你的filter, 此時, 忽略了類廠模板中的AMOVIESETUP_FILTER結構, 缺點是, 在GraphEdit中看不到你的filter。 2選擇正確的filter 種類, 缺省的Directshow Filters可能適用於大多數的filter, 但是如果你的filter有特殊的用處, 你要選擇一個恰當的種類 3避免在pin的AMOVIESETUP_MEDIATYPE結構中使用MEDIATYPE_None, MEDIASUBTYPE_None, or GUID_NULL, IFilterMapper2會將這些視做通配符。 4下面是一些建議的最小******不明白 Type of filter Recommended merit Default renderer MERIT_PREFERRED. For standard media types, however, a custom renderer should never be the default. Non-default renderer MERIT_DO_NOT_USE or MERIT_UNLIKELY Mux MERIT_DO_NOT_USE Decoder MERIT_NORMAL Spitter, parser MERIT_NORMAL or lower Special purpose filter; any filter that is created directly by the application MERIT_DO_NOT_USE Capture MERIT_DO_NOT_USE "Fallback" filter; for example, the Color Space Converter Filter MERIT_UNLIKELY 5不要將接受24位RGB的filter註冊到Directshow filter, 你的filter將會幹擾Color Space Converter filter.工作 5 反註冊 為了反註冊filter, 要提供一個DllUnregisterServer方法, 在這個方法中, 調用AMovieDllRegisterServer2, 注意傳遞參數, FASLE, 如果你是使用IFilterMapper2::RegisterFilter註冊的你的filter, 那麼你必須要用IFilterMapper2::UnregisterFilter方法來反註冊你的filter。 如下 STDAPI DllUnregisterServer() { HRESULT hr; IFilterMapper2 *pFM2 = NULL; hr = AMovieDllRegisterServer2(FALSE); if (FAILED(hr)) return hr; hr = CoCreateInstance(CLSID_FilterMapper2, NULL, CLSCTX_INPROC_SERVER, IID_IFilterMapper2, (void **)&pFM2); if (FAILED(hr)) return hr; hr = pFM2->UnregisterFilter(&CLSID_VideoCompressorCategory, g_wszName, CLSID_SomeFilter); pFM2->Release(); return hr; } 作者簡介:李強, 目前暫時供職於山大聯潤信息科技有限公司, 從事網絡視頻會議軟件的開發, 目前的感興趣的方向, 移動設備上多媒體的開發。 aooang@hotmail.com 歡迎轉載本文檔 。 (王朝網路 wangchao.net.cn)