016 編寫DirectShow Filters—Filters如何連接

Post date: 2015/4/9 上午 01:29:59

1. PIN連接 filters通過IPin接口連接pin。 output pin連接到input pin。 每個pin連接有一個media sample, 通過am_media_type描述。 一個應用程序通過調用在filter graph manager上的方法來連接filter, 而不是通過filters或者pins上的方法。 應用程序可以直接指定哪些filters連接, 通過IFilterGraph::ConnectDirct或者IGraphBuilder::Connect。 或者使用如IGraphBuilder::RenderFile的graph-building方法間接連接filters。 如果連接成功, 兩個 filter必須在filter graph中, 應用程序可以通過IFilterGraph::AddFilter方法增加一個filter到graph中, filter graph manager可以增加filter到graph。 當一個filter被增加到graph中, filter graph manager 調用filter的IBaseFilter::JoinFilterGraph來通知這個filter。 一般連接過程的概述如下: 1) Filter graph manager調用在output pin上Ipin::Connect, 傳遞一個指針到input pin。 2) 如果output pin接受這個連接, 它調用在input pin上的Ipin::ReceiveConnection。 3) 如果output pin也接受這個連接, 連接成功並且pins連接。 一些pin可以在filter激活時斷開和重新連接。 這種連接類型叫dynamic reconnection。 大多數filter不支持動態重連 filter一般用downstream順序連接, 換句話, filter的input pin等於output pin連接, filter應該一直支持這種連接順序。 也有filter連接順序相反—output pin先連接, 再連接input pins。 如, 可能先連接一個MUX filter的output pin到file-writer filter, 再連接mux filter的input pin。 當pin的 connect或者ReceiveConnection方法被調用時, 這個pin必須驗證是否支持這個連接, 細節依賴於具體的filter, 大多數步驟如下: 1) 查檢media type是否可接受 2) 協商一個allocator 3) 查詢被請求接口的其它pin 2. 協商media type Filter graph manager調用Ipin::connect方法時, 有幾個選擇來指定media type 1) complete type : 如果media type全指定, pins試圖用這種類型連接。 如果不能連接, 連接失敗。 2) Partial media type :如果主類型、子類型、格式類型是GUID_NULL, media type是partial, GUID_NULL如同通配符, 表示可以接受任何值。 這個PIN協商於兼容於partial類型的類型。 3) No media type : 如果filter graph manager傳遞一個NULL指針, 兩個PINS允許接受任何media type。 如果pin連接了, 連接會一直有一個完整的media type, 它的目標是讓filter graph manager限制可能的連接類型 在協商過程中, output pin通過調用input pin的Ipin::ReceiveConnection來傾向一個media type, 這個input pin可能接受或拒絕這個類型, 這個過程重複直至有一個input pin接受這個連接, 或者output pin嘗試所有所有連接然後連接失敗。 一個output pin如何選擇media type依賴於它的實現。 在directshow基類中, output pin調用在input pin上Ipin::EnumMediaTypes, 它返回一個枚舉器枚舉input pin傾向的media type, 如果失敗, output pin枚舉自己傾向的類型 用media type工作 任何函數接受AM_MEDIA_TYPE參數, 在引用pbFormat成員前要一直驗證cbFormat和formattype值。 如下代碼是錯誤的: if(pmt->formattype==FORMAT_VideoInfo) { VIDEOINFOHEADER *pVIH = (VIDEOINFOHEADER*)pmt->pbFormat; //wrong } 正確的代碼是: if((pmt->formattype==FORMAT_VideoInfo) && (pmt->cbFormat>sizeof(VIDEOINFOHEADER)&& (pbFormat!=NULL) { VIDEOINFOHEADER *pVIH = (VIDEOINFOHEADER*)pmt->pbFormat; //now you can dereference pVIH } 3. 協商allocator 當兩個pin連接, 需要一個交換media數據的機制, 它叫做transport, 一般, directshow體系關於transport是不確定的。 兩個filter可以允許使用任何兩個都支持transport來連接。 大多數transport是local memory transport, 即 media數據保留在主內存中。 存在兩個flavor本地內存transport, 如push model / pull model, 在push模型中, source filter push數據到downstream filter, 使用在downstream filter的input pin上的IMemInputPin接口 , 在pull模型中, downstream filter從source filter請求數據, 使用在source filter output pin上的IAsyncReader接口 在local memory transport, 對像響應分配memory buffer被叫做allocator, 一個allocator支持IMemAllocator接口, 兩個PINs共享一個單獨的allocator, 任何一個pin可以提供一個allocator, 但output pin選擇哪個allocator來使用 Output pin也設置allocator的屬性, 屬性決定有allocator建立多少個buffers, 每個buffer的尺寸, 和內存對齊, output pin服從於input pin 的buffer請求。 在ImemInputput連接中, allocator協商工作如下: 1) 可選的, output pin調用IMemInputPin::GetAllocatorRequirements, 它返回input pin的buffer請求, 如內存對齊。 一般的, output pin應該尊重於 input pin的請求, 除非有好的原因不去這麼做。 2) 可選的, output pin調用ImemInputPin::GetAllocator, 它從input pin請求一個allocator, input pin提供一個或者返回錯誤代碼。 3) 輸出pin選擇一個allocator, 它能使用input pin提供的一個, 或者自己創建。 4) Output pin調用IMemAllocator::SetProperties設置allocator properties屬性。 可是, allocator可能不理會被請求的屬性(如, 如果input pin提供allocator時這種情況可能發生), 在SetProperties方法中allocator返回實際的屬性作為output參數。 5) outpin調用ImemInputPin::NotifyAllocator通知input pin的選擇 6) Input pin應該調用ImemAllocator::GetProperties驗證是否allocator屬性可接受的。 7) Output pin響應提供或反提交allocator, 這個發生在流開始或停止時。 在一個IAsyncReader連接中, allocator協商工作如下: 1) input pin調用在output pin上的IAsyncReader::RequestAllocaor, 這個input pin指定它的buffer請求, 可選的, 提供一個allcator。 2) Output pin選擇一個allocator, 它可能使用input pin提供的一個, 或者自己建立。 3) Output pin返回allocator作為在RequestAllocator中的outgoing參數, input pin應該查檢allocator屬性。 4) Input pin響應於提交或反提交allocator 5) 在allocator協商過程中任何時間, 任何pin可能連接失敗。 6) 如果output pin使用input pin的allocator, 它可能僅使用這個allocator傳遞sample到輸入pin, 所屬的filter必須不使用allocator來傳遞sample到其它的pins。 4. 提供一個自定義的allcator 本節描述如何為一個filter提供一個自定義allocator。 只描述了IMemInputPin連接, 但IAsyncReader連接步驟是類似的。 首先, 為allocator定義一個C++類。 自定義的allocator繼承自一個標準的allocator類, CBaseAllocator或者CMemAllocator, 或者你可以建立全新的allocator類, 如果這樣, 必須暴露ImemAllocator接口。 保留的步驟依賴於在自定義filter上的allocator是否屬於一個input pin或者一個output pin, 在allocator協商分析期間input pin扮演一個不同於output pin的角色, 因為output pin無條件選擇這個allocator。 1) 為input pin提供一個自定義allocator 為了為一個input pin提供一個allocator, 重載input pin上的CBaseInputPin::GetAllocator。 在這個方法中, 查檢m_pAllocator成員變量, 如果它為非空, 意味著allocator已經被選為這個連接, 所以GetAllocator必須返回一個指向allocator的指針。 如果為NULL, 意味著allocator沒被選擇, 所以GetAllocator返回一個指針指向input pin傾向的allocator, 在這個情況下, 建立一個自定義的Allocator的實例並且返回它的IMemAllocator指針。 下列代碼顯示如何實現GetAllocator方法: 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 coe here will depend on // your custom allocator class defininition 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); } 當upstream filter選擇一個allocator, 它調用input pin的IMemInputPin::NotifyAllocator方法。 重載CBaseInputPin::notifyAllocator方法來查檢allocator屬性, 在一些情況下, input pin可能拒絕這個allocator如果不是你自定義的allocator, 雖然可能引起所有pin連接失敗。 2) 為output pin提供一個自定義的allocator 為了為output pin提供一個allocator, 重載CBaseOutputPin::InitAllocator建立自定義的allocator的一個實例: HRESULT MyOutputPin::InitAllocator(IMemAllocator **pAlloc) { 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); } 默認的, CBaseOutpin類首先從input pin請求一個allocator, 如果allocator不合適, output 建立它自己的allocator, 為了強制連接使用你自定義的allocator, 重載CBaseOutputPin::DecideAllocator, 可是, 要意識到它可能阻止你的output pin連接到確定的filter, 因為其它fitlers也許請求自己所屬的自定義allocator。 5. 重新連接pin 在一個pin連接期間, 一個filter可能斷開連接和再連接一個或多個pin, 如下 1) filter調用在另一個filter pin的Ipin::QueryAccept, 並且指定新的媒體類型。 2) 如果queryAccept返回S_OK, FILTER調用IFilterGraph2::ReconnectEx重新連接此PIN。 下列例子需要可能重新連接pins: 1) Tee filter:tee filter分離input stream為多個輸出流而不改變流中數據。 tee fitler可能接受一定範圍的media type, 但這些type必須匹配貫穿所有pin連接。 因此, 當input pin連接時, filter可能需要重新協商在output pin上的任何存在的連接。 如InfTee Filter sample。 2) Trans-in-place filter : Trans-in-place filter修改在原始buffer的input數據代替拷貝數據到一個單獨的out buffer, 對於upstream / downstream 連接, 它必須使用同樣的allcaotor, 第一個pin連接(input / output ) 用一般方式中協商一個allcator, 當其它的pin連接, 可是, 第一個allcoator可能不被接受, 在這個情況下, 第二個pin選擇一個不同的allocator, 第一個pin重新連接使用新的allocator。 例如, 在CTransInPlaceFilter類 在ReconnectEx中, filter graph manager異步斷開連接和重新連接pin, filter必須不會試圖重新連接除非QueryAccept返回SˍOK, 另外, PIN將被斷開連接, 引起graph錯誤, filter也從Ipin::Connect內部請求重新連接, 在同樣的線程中, 如果連接返回一個線程, 當另一個線程有重新連接的請求, filter graph manager可能在它重新連接之前run, 引起graph錯誤。