最簡單的基於DirectShow的自定義的視頻播放器的流程如下圖所示。
該流程圖中包含如下變量:IGraphBuilder *pGraph:繼承自IFilterGraph,用於構建Filter Graph。該流程圖大體上可以分成以下步驟:
IMediaControl *pControl:提供和播放控制有關的一些接口。
IMediaEvent *pEvent:用來處理Filter Graph發出的事件。
IBaseFilter *pF_source:源Filter。
IFileSourceFilter* pFileSource:源Filter的暴露的接口,用於設置輸入文件的路徑。
IBaseFilter *pF_demuxer:解復用Filter。
IBaseFilter *pF_decoder:解碼Filter。
IBaseFilter *pF_render:渲染Filter。
IPin *pOut:輸出Pin。
IPin *pIn:輸入Pin。
IPin **pPin:內部變量Pin。
a) CoInitialize():初始化COM運行環境。(2) 添加Source Filter
b) CoCreateInstance(…,pGraph):用指定的類標識符創建一個Com對象。在這裡創建IGraphBuilder。
c) pGraph->QueryInterface(…,pControl):通過QueryInterface()查詢某個組件是否支持某個特定的接口。在這裡查詢IMediaControl接口。
d) pGraph->QueryInterface(…,pEvent):同上。在這裡查詢IMediaEvent接口。
a) CoCreateInstance(…,pF_source):創建Source Filter。(3) 添加Demuxer Filter
b) pGraph->AddFilter(pF_source,…):將Source Filter加入Filter Graph。
c) pF_source->QueryInterface(…,pFileSource):查找Source Filter的IFileSourceFilter接口。
d) pFileSource->Load(Lxxx.mpg,pF_source):調用IFileSourceFilter的Load()方法加載視頻文件。
a) CoCreateInstance(…,pF_demuxer):創建Demuxer Filter。(4) 添加Decoder Filter
b) pGraph->AddFilter(pF_demuxer,…):將Demuxer Filter加入Filter Graph。
a) CoCreateInstance(…,pF_decoder):創建Decoder Filter。(5) 添加Render Filter
b) pGraph->AddFilter(pF_decoder,…):將Decoder Filter加入Filter Graph。
a) CoCreateInstance(…,pF_render):創建Render Filter。(6) 連接Source Filter和Demuxer Filter
b) pGraph->AddFilter(pF_render,…):將Render Filter加入Filter Graph。
a) 調用get_unconnected_pin()從源Filter中選擇一個沒有鏈接的輸出Pin。get_unconnected_pin()的執行步驟如下:
b) 調用get_unconnected_pin()從目的Filter中選擇一個沒有鏈接的輸入Pin。
c) 連接這兩個Pin
a) 枚舉Filter上的Pin。(7) 連接Demuxer Filter和Decoder Filter
b) 遍歷這些Pin,查找符合輸出方向(通過IPin的QueryDirection()方法),而且沒有在使用的Pin(通過IPin的ConnectedTo()方法)。
pControl->Run():開始運行Filter Graph中的所有Filter。
pEvent->WaitForCompletion():等待Filter Graph處理完所有數據。
上述步驟可以理解為在GraphEdit軟件中分別按照步驟添加以下控件。其中(1)、(2)、(3)、(4)為先添加的4個Filter,(5)、(6)、(7)為Filter之間的連接線。
/** * 最簡單的基於DirectShow的視頻播放器(Custom) * Simplest DirectShow Player (Custom) * * 雷霄骅 Lei Xiaohua * [email protected] * 中國傳媒大學/數字電視技術 * Communication University of China / Digital TV Technology * http://blog.csdn.net/leixiaohua1020 * * 本程序是一個簡單的基於DirectShow的視頻播放器。該播放器通過逐個添加 * 濾鏡並連接這些濾鏡實現了視頻的播放。適合初學者學習DirectShow。 * * This software is a simple video player based on DirectShow. * It Add DirectShow Filter Manually and Link the Pins of these filters * to play videos.Suitable for the beginner of DirectShow. */ #include stdafx.h #include//'1':Add filters manually //'0':Add filters automatically #define ADD_MANUAL 1 //Find unconnect pins HRESULT get_unconnected_pin( IBaseFilter *pFilter, // Pointer to the filter. PIN_DIRECTION PinDir, // Direction of the pin to find. IPin **ppPin) // Receives a pointer to the pin. { *ppPin = 0; IEnumPins *pEnum = 0; IPin *pPin = 0; HRESULT hr = pFilter->EnumPins(&pEnum); if (FAILED(hr)) { return hr; } while (pEnum->Next(1, &pPin, NULL) == S_OK) { PIN_DIRECTION ThisPinDir; pPin->QueryDirection(&ThisPinDir); if (ThisPinDir == PinDir) { IPin *pTmp = 0; hr = pPin->ConnectedTo(&pTmp); if (SUCCEEDED(hr)) // Already connected, not the pin we want. { pTmp->Release(); } else // Unconnected, the pin we want. { pEnum->Release(); *ppPin = pPin; return S_OK; } } pPin->Release(); } pEnum->Release(); // Did not find a matching pin. return E_FAIL; } //Connect 2 filters HRESULT connect_filters( IGraphBuilder *pGraph, IBaseFilter *pSrc, IBaseFilter *pDest) { if ((pGraph == NULL) || (pSrc == NULL) || (pDest == NULL)) { return E_POINTER; } //Find Output pin in source filter IPin *pOut = 0; HRESULT hr = NULL; hr=get_unconnected_pin(pSrc, PINDIR_OUTPUT, &pOut); if (FAILED(hr)){ return hr; } //Find Input pin in destination filter IPin *pIn = 0; hr = get_unconnected_pin(pDest, PINDIR_INPUT, &pIn); if (FAILED(hr)){ return hr; } //Connnect them hr = pGraph->Connect(pOut, pIn); pIn->Release(); pOut->Release(); return hr; } int _tmain(int argc, _TCHAR* argv[]) { IGraphBuilder *pGraph = NULL; IMediaControl *pControl = NULL; IMediaEvent *pEvent = NULL; // Init COM HRESULT hr = CoInitialize(NULL); if (FAILED(hr)){ printf(Error - Can't init COM.); return -1; } // Create FilterGraph hr=CoCreateInstance(CLSID_FilterGraph, NULL,CLSCTX_INPROC_SERVER,IID_IGraphBuilder, (void **)&pGraph); if (FAILED(hr)){ printf(Error - Can't create Filter Graph.); return -1; } // Query Interface hr = pGraph->QueryInterface(IID_IMediaControl, (void **)&pControl); hr = pGraph->QueryInterface(IID_IMediaEvent, (void **)&pEvent); //1. Add Filters======================= //Source IBaseFilter *pF_source = 0; hr = CoCreateInstance(CLSID_AsyncReader, 0, CLSCTX_INPROC_SERVER,IID_IBaseFilter, (void**)(&pF_source)); if (FAILED(hr)){ printf(Failed to create File Source. ); return -1; } hr = pGraph->AddFilter(pF_source, LLei's Source); if (FAILED(hr)){ printf(Failed to add File Source to Filter Graph. ); return -1; } IFileSourceFilter* pFileSource; pF_source->QueryInterface(IID_IFileSourceFilter, (void**)&pFileSource); pFileSource->Load(Lcuc_ieschool.mpg, NULL); pFileSource->Release(); #if ADD_MANUAL //Demuxer IBaseFilter *pF_demuxer = 0; hr = CoCreateInstance(CLSID_MPEG1Splitter, 0, CLSCTX_INPROC_SERVER,IID_IBaseFilter, (void**)(&pF_demuxer)); if (FAILED(hr)){ printf(Failed to create Demuxer. ); return -1; } hr = pGraph->AddFilter(pF_demuxer, LLei's Demuxer); if (FAILED(hr)){ printf(Failed to add Demuxer to Filter Graph. ); return -1; } //Decoder IBaseFilter *pF_decoder = 0; hr = CoCreateInstance(CLSID_CMpegVideoCodec, 0, CLSCTX_INPROC_SERVER,IID_IBaseFilter, (void**)(&pF_decoder)); if (FAILED(hr)){ printf(Failed to create Decoder. ); return -1; } hr = pGraph->AddFilter(pF_decoder, LLei's Decoder); if (FAILED(hr)){ printf(Failed to add Decoder to Filter Graph. ); return -1; } //Render IBaseFilter *pF_render = 0; hr = CoCreateInstance(CLSID_VideoRenderer, 0, CLSCTX_INPROC_SERVER,IID_IBaseFilter, (void**)(&pF_render)); if (FAILED(hr)){ printf(Failed to create Video Render. ); return -1; } hr = pGraph->AddFilter(pF_render, LLei's Render); if (FAILED(hr)){ printf(Failed to add Video Render to Filter Graph. ); return -1; } //2. Connect Filters======================= hr = connect_filters(pGraph, pF_source, pF_demuxer); if (FAILED(hr)){ printf(Failed to link Source and Demuxer. ); return -1; } hr = connect_filters(pGraph, pF_demuxer, pF_decoder); if (FAILED(hr)){ printf(Failed to link Demuxer and Decoder. ); return -1; } hr = connect_filters(pGraph, pF_decoder, pF_render); if (FAILED(hr)){ printf(Failed to link Decoder and Render. ); return -1; } pF_source->Release(); pF_demuxer->Release(); pF_decoder->Release(); pF_render->Release(); #else IPin* Pin; ULONG fetched; // get output pin IEnumPins* pEnumPins; hr = pF_source->EnumPins(&pEnumPins); hr = pEnumPins->Reset(); hr = pEnumPins->Next(1, &Pin, &fetched); pEnumPins->Release(); // render pin, graph builder automatically complete rest works hr = pGraph->Render(Pin); #endif if (SUCCEEDED(hr)){ // Run hr = pControl->Run(); if (SUCCEEDED(hr)){ long evCode=0; pEvent->WaitForCompletion(INFINITE, &evCode); } } //Release pControl->Release(); pEvent->Release(); pGraph->Release(); CoUninitialize(); return 0; }
程序的運行結果如下圖所示。運行後會播放“cuc_ieschool.mpg”文件。需要注意的是,本程序並沒有加入音頻解碼和播放的Filter,所以播放視頻的時候是沒有聲音的。
除了手動一個一個添加Filter之外,也可以在獲得“源”Filter的Pin之後,直接調用IFilterGraph的Render()方法“智能”自動構建Filter Graph。注意Render()方法和RenderFile()方法是不一樣的。RenderFile()是指定一個文件路徑後,自動構建整個Filter Graph,相對來說更加簡單些;而Render()方法則是首先要創建一個Source Filter之後,才可以自動構建整個Filter Graph。//'1':Add filters manually //'0':Add filters automatically #define ADD_MANUAL 1