012 利用Directshow開發源Filter

Post date: 2015/4/9 上午 01:26:35

摘要: 我們一般不推薦自己開發音頻或者視頻捕捉過濾器, 因為diectshow對於音視頻的捕捉設備以經提供了支持。 所以, 這篇文檔, 對於某些用戶需要從特定設備捕捉一些數據提供一些幫助。 這篇文檔主要包括以下內容。 1捕捉filter 對pin的要求 2如何完成一個預覽pin 3如何產生源數據 1 對pin的要求Pin Requirements for Capture Filters Pin的名字 你可以給你的filter起任何名字, 如果你的pin的名字以~符號開頭, 那麼當應用程序調用IGraphBuilder::RenderFile方法時, filter圖表管理器不會自動render這個pin的。 例如, 如果一個filter具有一個捕捉pin和預覽pin, 相應的你給他們命名為」~Capture」和「Preview」。 如果一個應用程序在graph中render這個filter , 那麼預覽pin就會自動和它缺省的render相連接, 但是, capture pin上卻不連接任何東西, 這是一個合理的缺省行為。 這個也可以應用到那些傳輸不準備被rendered數據的pin, 也可以應用到需要屬性設置的pin上。 註:名字中含有~符號的pin是可以手動連接的。 Pin的種類 一個捕捉filter通常用一個捕捉pin, 也許還有一個預覽pin。 一些捕捉filter除了這兩種pin之外還有其他的pin, 用來傳遞其他的數據, 例如控制信息。 每一個輸出pin都必須暴露IKsPropertySet接口, 應用程序通過這些接口來判斷pin的種類, pin一般都會返回PIN_CATEGORY_CAPTURE or PIN_CATEGORY_PREVIEW。 下面的例子演示了一個捕捉pin如何通過IKsPropertySet來返回pin的種類 // Set: Cannot set any properties. HRESULT CMyCapturePin::Set(REFGUID guidPropSet, DWORD dwID, void *pInstanceData, DWORD cbInstanceData, void *pPropData, DWORD cbPropData) { return E_NOTIMPL; } // Get: Return the pin category (our only property). HRESULT CMyCapturePin::Get( REFGUID guidPropSet, // Which property set. DWORD dwPropID, // Which property in that set. void *pInstanceData, // Instance data (ignore). DWORD cbInstanceData, // Size of the instance data (ignore). void *pPropData, // Buffer to receive the property data. DWORD cbPropData, // Size of the buffer. DWORD *pcbReturned // Return the size of the property. ) { if (guidPropSet != AMPROPSETID_Pin) return E_PROP_SET_UNSUPPORTED; if (dwPropID != AMPROPERTY_PIN_CATEGORY) return E_PROP_ID_UNSUPPORTED; if (pPropData == NULL && pcbReturned == NULL) return E_POINTER; if (pcbReturned) *pcbReturned = sizeof(GUID); if (pPropData == NULL) // Caller just wants to know the size. return S_OK; if (cbPropData < sizeof(GUID)) // The buffer is too small. return E_UNEXPECTED; *(GUID *)pPropData = PIN_CATEGORY_CAPTURE; return S_OK; } // QuerySupported: Query whether the pin supports the specified property. HRESULT CMyCapturePin::QuerySupported(REFGUID guidPropSet, DWORD dwPropID, DWORD *pTypeSupport) { if (guidPropSet != AMPROPSETID_Pin) return E_PROP_SET_UNSUPPORTED; if (dwPropID != AMPROPERTY_PIN_CATEGORY) return E_PROP_ID_UNSUPPORTED; if (pTypeSupport) // We support getting this property, but not setting it. *pTypeSupport = KSPROPERTY_SUPPORT_GET; return S_OK; } 2如何完成一個預覽pin Implementing a Preview Pin (Optional) 如果你的filter有一個預覽pin, 預覽pin發送的數據是捕捉pin傳遞的數據的拷貝。 預覽pin發送的數據不會降低捕捉pin的楨率, 捕捉pin比預覽pin有優先權。 捕捉pin和預覽pin必須發送一個相同格式的數據。 這樣, 他們連接都是通過同一種媒體數據類型, 如果捕捉pin先連接, 預覽pin應該提供相同的媒體類型, 對於其他類型的數據媒體, 則拒絕。 如果預覽pin先連接, 然後, 如果捕捉pin以另一種媒體類型和其他pin連接, 那麼預覽pin就應該用新的媒體類型重新連接, 如果和filter的預覽pin連接的下遊filter拒絕新的數據類型, 捕捉pin應該拒絕新的媒體類型。 可以通過IPin::QueryAccept方法察看filter的預覽pin連接的下遊filter連接的數據媒體, 然後通過IFilterGraph::Reconnect方法重新連接pin。 這條規則也適用於圖表管理器重新連接捕捉pin。 下面的代碼大體上描述了上面的過程 // Override CBasePin::CheckMediaType. CCapturePin::CheckMediaType(CMediaType *pmt) { if (m_pMyPreviewPin->IsConnected()) { // The preview pin is already connected, so query the pin it is // connected to. If the other pin rejects it, so do we. hr = m_pMyPreviewPin->GetConnected()->QueryAccept(pmt); if (hr != S_OK) { // The preview pin cannot reconnect with this media type. return E_INVALIDARG; } // The preview pin will reconnect when SetMediaType is called. } // Decide whether the capture pin accepts the format. BOOL fAcceptThisType = ... // (Not shown.) return (fAcceptThisType? S_OK : E_FAIL); } // Override CBasePin::SetMediaType. CCapturePin::SetMediaType(CMediaType *pmt); { if (m_pMyPreviewPin->IsConnected()) { // The preview pin is already connected, so it must reconnect. if (m_pMyPreviewPin->GetConnected()->QueryAccept(pmt) == S_OK) { // The downstream pin will accept the new type, so it's safe // to reconnect. m_pFilter->m_pGraph->Reconnect(m_pMyPreviewPin); } else { return VFW_E_INVALIDMEDIATYPE; } } // Now do anything that the capture pin needs to set the type. hr = MyInternalSetMediaType(pmt); // And finally, call the base-class method. return CBasePin::SetMediaType(pmt); } CPreviewPin::CheckMediaType(CMediaType *pmt) { if (m_pMyCapturePin->IsConnected()) { // The preview pin must connect with the same type. CMediaType cmt = m_pMyCapturePin->m_mt; return (*pmt == cmt ? S_OK : VFW_E_INVALIDMEDIATYPE); } // Decide whether the preview pin accepts the format. You can use your // knowledge of which types the capture pin will accept. Regardless, // when the capture pin connects, the preview pin will reconnect. return (fAcceptThisType? S_OK : E_FAIL); } 3在源filter中產生數據 Producing Data in a Capture Filter 狀態改變 一個捕捉filter在運行時會產生數據流。 當filter paused的時候, 不要發送數據, 並且此時, 圖表管理器調用CBaseFilter::GetState方法會返回VFW_S_CANT_CUE, 這個返回值告訴圖表管理器, filter 正處於paused狀態, 停止發送數據。 下面的代碼顯示如何派生GetState方法 CMyVidcapFilter::GetState(DWORD dw, FILTER_STATE *pState) { CheckPointer(pState, E_POINTER); *pState = m_State; if (m_State == State_Paused) return VFW_S_CANT_CUE; else return S_OK; } 控制不同的數據流 一個捕捉filter應該支持IAMStreamControl接口, 因此應用程序應該分別的打開和關閉每一個pin, 例如, 一個應用程序可以只預覽而沒有捕捉, 然後可以轉換到捕捉模式不用重新構建graph圖表。 你可以通過CBaseStreamControl類來實現這個接口 時間戳 當一個filter捕捉了一個sample, 將在每一個sample上標上一個當前時間的時間戳。 結束時間是開始時間加上持續時間。 例如, 如果一個filter每秒捕捉十個sample, and the stream time is 200,000,000 units when the filter captures the sample, the time stamps should be 200000000 and 201000000. (There are 10,000,000 units per second.) 可以通過IReferenceClock::GetTime方法獲得當前的參考時間, 通過IMediaSample::SetTime給sample設置時間戳。 相連的sample上的時間戳必須是遞增的, 即使filter pauses也是這樣, 如果一個filter運行, 停止, 然後又運行, 重新開始後產生的sample上的時間戳必須比停止前的sample上的時間戳要大一些。 預覽pin上的sample沒有時間戳, 但是, 預覽pin一般比捕捉pin到達render pin稍晚, 因此, render filter將預覽pin上的sample視作遲到的sample, 為了趕上其他的時間進度, 也許會丟失一些數據。 (王朝網路 wangchao.net.cn)