50行代碼實現的一個最簡單的基於 DirectShow 的視頻播放器
本文介紹一個最簡單的基於 DirectShow 的視頻播放器。該播放器對於初學者來說是十分有用的,它包含了使用 DirectShow 播放視頻所有必備的函數。
直接貼上代碼,具體代碼的含義都寫在注釋中了:
/* 雷霄驊 * 中國傳媒大學/數字電視技術 * leixiaohua1020@126.com * */ // aviplayer.cpp : 定義控制台應用程序的入口點。 #include "stdafx.h" #include <dshow.h> // 用到的DirectShow SDK鏈接庫 #pragma comment(lib,"strmiids.lib") int _tmain(int argc, _TCHAR* argv[]) { IGraphBuilder *pGraph = NULL; IMediaControl *pControl = NULL; IMediaEvent *pEvent = NULL; // 初始化COM庫. HRESULT hr = CoInitialize(NULL); if (FAILED(hr)) { printf("錯誤 - 無法初始化 COM 組件"); return -1; } // 創建濾波器圖表管理器 hr=CoCreateInstance(CLSID_FilterGraph, NULL,CLSCTX_INPROC_SERVER,IID_IGraphBuilder, (void **)&pGraph); if (FAILED(hr)) { printf("錯誤 - 無法創建 Filter Graph Manager."); return -1; } // 查詢媒體控制和媒體事件接口 hr = pGraph->QueryInterface(IID_IMediaControl, (void **)&pControl); hr = pGraph->QueryInterface(IID_IMediaEvent, (void **)&pEvent); // 建立圖表,在這里你可以更改待播放的文件名稱 hr = pGraph->RenderFile(L"E:\\movie\\外婆.VOB", NULL); if (SUCCEEDED(hr)) { // 運行圖表. hr = pControl->Run(); if (SUCCEEDED(hr)) { //等待回放結束事件. long evCode; pEvent->WaitForCompletion(INFINITE, &evCode); // 切記: 在實際應用當中,不能使用INFINITE標識, 因為它會不確定的阻塞程序 } } // 釋放所有資源和關閉COM庫 pControl->Release(); pEvent->Release(); pGraph->Release(); CoUninitialize(); return 0; }
最簡單的基於DirectShow的示例:視頻播放器
本文記錄一個最簡單的基於DirectShow的視頻播放器。DirectShow是一個龐大的框架,可以在Windows下實現多種多樣的視頻處理需求。但是它的“龐大”也使得新手不太容易學習它的使用。本文的例子正是為解決這一問題而做的,它只包含了使用DirectShow播放一個視頻文件所需要的最重要的函數。
流程圖
最簡單的使用DirectShow播放視頻文件的流程如下圖所示。
流程圖中涉及到幾個接口如下所示。
IGraphBuilder:繼承自IFilterGraph,用於構建Filter Graph。相比於IFilterGraph來說IGraphBuilder提供了一些更加“智能”的方法,例如RenderFile()方法。
IMediaControl:提供和播放控制有關的一些接口。IMediaEvent:用來處理Filter Graph發出的事件。
流程圖中關鍵函數的作用如下所示。
CoInitialize() :初始化COM運行環境。
CoCreateInstance(…,pGraph) :用指定的類標識符創建一個Com對象。在該播放器中類標識符為“CLSID_FilterGraph”,用於創建IGraphBuilder。
pGraph->QueryInterface(…,pControl) :通過QueryInterface()查詢某個組件是否支持某個特定的接口。在這里查詢IMediaControl接口。
pGraph->QueryInterface(…,pEvent) :同上。在這里查詢IMediaEvent接口。
pGraph->RenderFile("xxx.mkv"):為指定的文件智能的構建一個Filter Graph。
pControl->Run() :開始運行Filter Graph中的所有Filter。
pEvent->WaitForCompletion() :等待Filter Graph處理完所有數據。CoUninitialize():釋放CoInitialize()初始化的COM運行環境。
注意上述幾個函數是構建一個基於DirectShow的視頻播放器所必須的函數,除了上述幾個接口之外還經常用到以下幾個接口:
IBasicVideo:提供和視頻有關的一些接口。
IBasicAudio:提供和音頻有關的一些接口。
IVideoWindow:提供和窗口有關的一些接口。
IMediaSeeking:提供和播放位置有關的一些接口。
源代碼
/** * 最簡單的基於DirectShow的視頻播放器 * Simplest DirectShow Player * * 雷霄驊 Lei Xiaohua * leixiaohua1020@126.com * 中國傳媒大學/數字電視技術 * Communication University of China / Digital TV Technology * http://blog.csdn.net/leixiaohua1020 * * 本程序是一個最簡單的基於DirectShow的播放器。 * 適合初學者學習DirectShow。 * * This example is the simplest Player based on DirectShow. * Suitable for the beginner of DirectShow. */ #include "stdafx.h" #include <dshow.h> #include <atlconv.h> #define OUTPUT_INFO 1 //Show Filter in FilterGpragh int show_filters_in_filtergraph(IGraphBuilder *pGraph){ printf("Filters in FilterGpragh=======\n"); USES_CONVERSION; IEnumFilters *pFilterEnum=NULL; if(FAILED(pGraph->EnumFilters(&pFilterEnum))){ pFilterEnum->Release(); return -1; } pFilterEnum->Reset(); IBaseFilter * filter = NULL; ULONG fetchCount = 0; //Pin Info while (SUCCEEDED(pFilterEnum->Next(1, &filter, &fetchCount)) && fetchCount){ if (!filter){ continue; } FILTER_INFO FilterInfo; if (FAILED(filter->QueryFilterInfo(&FilterInfo))){ continue; } printf("[%s]\n",W2A(FilterInfo.achName)); filter->Release(); } pFilterEnum->Release(); printf("==============================\n"); return 0; } int _tmain(int argc, _TCHAR* argv[]) { IGraphBuilder *pGraph = NULL; IMediaControl *pControl = NULL; IMediaEvent *pEvent = NULL; //Get some param-------------- HRESULT hr1; IBasicVideo *pVideo=NULL; IBasicAudio *pAudio=NULL; IVideoWindow *pWindow=NULL; IMediaSeeking *pSeeking=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); // RenderFile hr = pGraph->RenderFile(L"cuc_ieschool.mov", NULL); if (FAILED(hr)){ printf("Error - Can't Render File."); return -1; } #if OUTPUT_INFO //Get some information---------- long video_w=0,video_h=0,video_bitrate=0,audio_volume=0; long long duration_1=0,position_1=0; REFTIME avgtimeperframe=0; float framerate=0,duration_sec=0,progress=0,position_sec=0; //Video hr1=pGraph->QueryInterface(IID_IBasicVideo, (void **)&pVideo); pVideo->get_VideoWidth(&video_w); pVideo->get_VideoHeight(&video_h); pVideo->get_AvgTimePerFrame(&avgtimeperframe); framerate=1/avgtimeperframe; //pVideo->get_BitRate(&video_bitrate); //Audio hr1=pGraph->QueryInterface(IID_IBasicAudio, (void **)&pAudio); //Mute //pAudio->put_Volume(-10000); printf("Some Information:\n"); printf("Video Resolution:\t%dx%d\n",video_w,video_h); printf("Video Framerate:\t%.3f\n",framerate); //Window hr1=pGraph->QueryInterface(IID_IVideoWindow, (void **)&pWindow); pWindow->put_Caption(L"Simplest DirectShow Player"); //pWindow->put_Width(480); //pWindow->put_Height(272); //Seek hr1=pGraph->QueryInterface(IID_IMediaSeeking, (void **)&pSeeking); pSeeking->GetDuration(&duration_1); //time unit:100ns=0.0000001s duration_sec=(float)duration_1/10000000.0; printf("Duration:\t%.2f s\n",duration_sec); //pSeeking->SetPositions(); //PlayBack Rate //pSeeking->SetRate(2.0); //Show Filter in FilterGpagh show_filters_in_filtergraph(pGraph); //---------------------- #endif printf("Progress Info\n"); printf("Position\tProgress\n"); if (SUCCEEDED(hr)){ // Run hr = pControl->Run(); if (SUCCEEDED(hr)){ long evCode=0; //pEvent->WaitForCompletion(INFINITE, &evCode); while(evCode!=EC_COMPLETE){ //Info #if OUTPUT_INFO pSeeking->GetCurrentPosition(&position_1); position_sec=(float)position_1/10000000.0; progress=position_sec*100/duration_sec; printf("%7.2fs\t%5.2f%%\n",position_sec,progress); #endif //1000ms pEvent->WaitForCompletion(1000, &evCode); } } } // Release resource pControl->Release(); pEvent->Release(); pGraph->Release(); CoUninitialize(); return 0; }
運行結果
程序運行后即可開始播放一個“cuc_ieschool.mov”文件。程序運行時候的截圖如下所示。由圖可見運行的同時程序在控制台中打印出了兩種信息:
(1) 該視頻的相關信息
(2) 播放該視頻的 Filter Graph中的Filter(該功能通過函數show_filters_in_filtergraph()完成)。
可以通過定義在代碼最前面宏OUTPUT_INFO控制是否輸出視頻的信息。定義成“0”的話則不會輸出視頻的信息。如下所示。
#define OUTPUT_INFO 1
最簡單的基於DirectShow的示例:視頻播放器圖形界面版
本文記錄一個最簡單的基於DirectShow的圖形界面的視頻播放器。基於DirectShow的圖形界面的播放器的例子還是比較多的,但是大部分都是“層層封裝”的例子。“層層封裝”的例子相對來說更加穩定,但是卻不是很容易理解。因為DirectShow本身的接口函數的數量就比較多,如果再加上封裝DirectShow的函數,合起來的函數數量是非常大的,很容易讓人搞不清哪些才是真正的DirectShow接口函數。本播放器剝去了 DirectShow例子中的“層層封裝”,直接調用DirectShow的接口完成視頻的播放工作,更加適合DirectShow入門使用。
幾個功能的實現機制
整個工程的代碼比較多,不再詳細記錄。在這里簡單記錄一下代碼中的幾個關鍵點。
視頻的播放/暫停/繼續/停止
播放
視頻“播放”的源代碼如下所示。簡單來說,完成了以下視頻播放的初始化工作:
(1) 輸入的URL轉換為Unicode編碼(RenderFile()函數支持的輸入是Unicode字符串)。
(2) 調用RenderFile()“智能”創建Filter Graph。
(3) 調用IMediaControl的Run()方法開始播放視頻。
(4) 開啟定時器,用於更新視頻播放的進度(后文詳細記錄)
void CplayerGUIDlg::OnBnClickedStart() { CStringA cstr_urla; CStringW cstr_urlw; HRESULT hr; //Render #ifdef _UNICODE m_url.GetWindowText(cstr_urlw); #else USES_CONVERSION; m_url.GetWindowText(cstr_urla); cstr_urlw.Format(L"%s",A2W(cstr_urla)); #endif if(cstr_urlw.IsEmpty()){ AfxMessageBox(_T("Input URL is NULL!")); return; } hr = pGraph->RenderFile(cstr_urlw, NULL); if(FAILED(hr)){ AfxMessageBox(_T("Can't open input file!")); return; } //Set Window HWND screen_hwnd=NULL; RECT windowRect; screen_hwnd = this->GetDlgItem(IDC_SCREEN)->GetSafeHwnd(); ::GetClientRect(screen_hwnd, &windowRect); pWindow->put_Visible(OAFALSE); pWindow->put_Owner((OAHWND)screen_hwnd); pWindow->put_Left(0); pWindow->put_Top(0); pWindow->put_Width(windowRect.right - windowRect.left); pWindow->put_Height(windowRect.bottom - windowRect.top); pWindow->put_WindowStyle(WS_CHILD|WS_CLIPCHILDREN|WS_CLIPSIBLINGS|WS_THICKFRAME); pWindow->put_MessageDrain((OAHWND) screen_hwnd);//Receive Message pWindow->put_Visible(OATRUE); pEvent->SetNotifyWindow((OAHWND)screen_hwnd, WM_GRAPHNOTIFY, 0); // Run hr = pControl->Run(); playerstate=STATE_PLAY; SetBtn(STATE_PLAY); SetTimer(1,1000,NULL); }
暫停/繼續
視頻“暫停/繼續”的源代碼如下所示。其中調用了IMediaControl的Pause()和Run()設定“暫停”或者是“繼續”。
void CplayerGUIDlg::OnBnClickedPause() { HRESULT hr; if(playerstate==STATE_PLAY){ hr=pControl->Pause(); playerstate=STATE_PAUSE; GetDlgItem(ID_PAUSE)->SetWindowText(_T("Resume")); }else if(playerstate==STATE_PAUSE){ hr=pControl->Run(); playerstate=STATE_PLAY; GetDlgItem(ID_PAUSE)->SetWindowText(_T("Pause")); } }
停止
視頻的“停止”的源代碼如下所示。該部分代碼完成了以下工作:
(1) 把播放的位置重新調整為0
(2) 調用IMediaControl的Pause()
(3) 關閉定時器
(4) 刪除Filter Graph中的Filter
void CplayerGUIDlg::OnBnClickedStop() { long long position = 0; HRESULT hr; hr = pSeeking->SetPositions(&position, AM_SEEKING_AbsolutePositioning | AM_SEEKING_SeekToKeyFrame, 0, AM_SEEKING_NoPositioning); KillTimer(1); hr=pControl->Stop(); // Enumerate the filters And remove them IEnumFilters *pEnum = NULL; hr = pGraph->EnumFilters(&pEnum); if (SUCCEEDED(hr)) { IBaseFilter *pFilter = NULL; while (S_OK == pEnum->Next(1, &pFilter, NULL)) { // Remove the filter. pGraph->RemoveFilter(pFilter); // Reset the enumerator. pEnum->Reset(); pFilter->Release(); } pEnum->Release(); } SystemClear(); }
視頻播放進度在時間軸的顯示
隨着視頻的播放,需要在視頻播放進度的時間軸上更新播放進度信息。在程序中使用了一個定時器完成這個功能。
在視頻開始播放的時候,調用SetTimer()開啟定時器。時間間隔設置為1000ms。
SetTimer(1,1000,NULL);
在視頻停止播放的時候,調用KillTimer()結束定時器。
KillTimer(1);
在定時器的消息響應函數中,調用了IMediaSeeking的GetCurrentPosition()獲取視頻當前播放到的時間,調用了 IMediaSeeking的GetDuration ()獲取視頻的時長。根據以上函數得到的數值,計算后把結果設置到相應的控件上。這部分的代碼如下所示。
void CplayerGUIDlg::OnTimer(UINT_PTR nIDEvent) { if (nIDEvent == 1){ CString curtimestr,durationstr; long long curtime; long long duration; int tns, thh, tmm, tss; int progress; //ms pSeeking->GetCurrentPosition(&curtime); if(curtime!=0){ //change to second tns = curtime/10000000; thh = tns / 3600; tmm = (tns % 3600) / 60; tss = (tns % 60); curtimestr.Format(_T("%02d:%02d:%02d"),thh,tmm,tss); m_curtime.SetWindowText(curtimestr); } pSeeking->GetDuration(&duration); if(duration!=0){ tns = duration/10000000; thh = tns / 3600; tmm = (tns % 3600) / 60; tss = (tns % 60); durationstr.Format(_T("%02d:%02d:%02d"),thh,tmm,tss); m_duration.SetWindowText(durationstr); progress=curtime*100/duration; m_progress.SetPos(progress); } } CDialogEx::OnTimer(nIDEvent); }
視頻播放點的調整
當鼠標拖動滑動控制條(Slider Control)控件上的滑塊的時候,需要根據拖動的位置設置視頻的播放進度。此時調用IMediaSeeking的SetPositions()設定視頻的播放進度。消息響應函數中的代碼如下所示。
void CplayerGUIDlg::OnHScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar) { if (pScrollBar->GetSafeHwnd() == m_progress.GetSafeHwnd()){ float pos_bar=0.0; long long duration=0.0; long long pos_time=0.0; if(nSBCode==SB_THUMBPOSITION){ pos_bar=(float)nPos/100.0; pSeeking->GetDuration(&duration); pos_time=pos_bar*duration; long long position = (long long)(pos_time); HRESULT hr = pSeeking->SetPositions(&position, AM_SEEKING_AbsolutePositioning | AM_SEEKING_SeekToKeyFrame, 0, AM_SEEKING_NoPositioning); } } CDialogEx::OnHScroll(nSBCode, nPos, pScrollBar); }
“全屏播放”的問題
視頻的全屏播放通過IVideoWindow的put_FullScreenMode()實現,代碼如下所示。
void CplayerGUIDlg::OnBnClickedFullscreen() { pWindow->put_FullScreenMode(OATRUE); }
同時,在“全屏模式”啟動后,如果按“ESC”鍵的話,可以關閉“全屏模式”。這部分的代碼在PreTranslateMessage()中實現,如下所示。
//Exit Full Screen mode when push "ESC" BOOL CplayerGUIDlg::PreTranslateMessage(MSG* pMsg) { if (pMsg->message == WM_KEYDOWN){ if (pMsg->wParam == VK_RETURN || pMsg->wParam == VK_ESCAPE){ // Restore form fullscreen mode pWindow->put_FullScreenMode(OAFALSE); return 1; } } return CDialogEx::PreTranslateMessage(pMsg); }
在這里有一點需要注意,IVideoWindow的put_FullScreenMode()在Win7下是有問題的。只有在設置窗口樣式的的時候,在樣式中指定WS_THICKFRAME后才可以正常使用。例如如下代碼。
pWindow->put_WindowStyle(WS_CHILD|WS_CLIPCHILDREN|WS_CLIPSIBLINGS|WS_THICKFRAME);
如果沒有指定WS_THICKFRAME樣式的話,在退出“全屏”模式之后,視頻就顯示不出來了,取而代之的是一片黑色。
但是設定WS_THICKFRAME樣式之后,視頻窗口的外圍會有一層“白邊”,會影響到視頻顯示的美觀。因此我們如果希望正常使用全屏的話,可能需要找一種更好的方法,在這里我就沒有深入研究了。
運行結果
這是使用DirectShow基於MFC開發的一個示例播放器。實現了一個播放器的基本功能:播放,暫停/繼續,停止,播放時間軸的顯示,以及從任一點開始播放媒體。並且支持將媒體文件拖拽至播放器進行播放。播放前將媒體文件的路徑輸入到“URL”欄中,然后單擊“Start”即可開始播放。在軟件下方包含了“start”,“Pause”,“Stop”等按鈕用於控制媒體的播放。
播放時候的效果截圖如下所示。
單擊“Full Screen”可以全屏播放。單擊“Info”可以顯示正在播放媒體的信息,包括以下兩種信息:
(1) 該視頻的相關信息
(2) 播放該視頻的 Filter Graph中的Filter。
最簡單的基於DirectShow的示例:視頻播放器自定義版
流程圖
最簡單的基於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。
該流程圖大體上可以分成以下步驟:
(1) 初始化DirectShow
包括以下幾個步驟:
a) CoInitialize():初始化COM運行環境。
b) CoCreateInstance(…,pGraph):用指定的類標識符創建一個Com對象。在這里創建IGraphBuilder。
c) pGraph->QueryInterface(…,pControl):通過QueryInterface()查詢某個組件是否支持某個特定的接口。在這里查詢IMediaControl接口。
d) pGraph->QueryInterface(…,pEvent):同上。在這里查詢IMediaEvent接口。
(2) 添加Source Filter
包括以下幾個步驟:
a) CoCreateInstance(…,pF_source):創建Source Filter。
b) pGraph->AddFilter(pF_source,…):將Source Filter加入Filter Graph。
c) pF_source->QueryInterface(…,pFileSource):查找Source Filter的IFileSourceFilter接口。
d) pFileSource->Load(L"xxx.mpg",pF_source):調用IFileSourceFilter的Load()方法加載視頻文件。
(3) 添加Demuxer Filter
包括以下幾個步驟:
a) CoCreateInstance(…,pF_demuxer):創建Demuxer Filter。
b) pGraph->AddFilter(pF_demuxer,…):將Demuxer Filter加入Filter Graph。
(4) 添加Decoder Filter
包括以下幾個步驟:
a) CoCreateInstance(…,pF_decoder):創建Decoder Filter。
b) pGraph->AddFilter(pF_decoder,…):將Decoder Filter加入Filter Graph。
(5) 添加Render Filter
包括以下幾個步驟:
a) CoCreateInstance(…,pF_render):創建Render Filter。
b) pGraph->AddFilter(pF_render,…):將Render Filter加入Filter Graph。
(6) 連接Source Filter和Demuxer Filter
調用了一個函數connect_filters()用於連接2個Filter。
connect_filters()的執行步驟如下:
a) 調用get_unconnected_pin()從源Filter中選擇一個沒有鏈接的輸出Pin。
b) 調用get_unconnected_pin()從目的Filter中選擇一個沒有鏈接的輸入Pin。
c) 連接這兩個Pin
get_unconnected_pin()的執行步驟如下:
a) 枚舉Filter上的Pin。
b) 遍歷這些Pin,查找符合輸出方向(通過IPin的QueryDirection()方法),而且沒有在使用的Pin(通過IPin的ConnectedTo()方法)。
(7) 連接Demuxer Filter和Decoder Filter
過程同上。
(8) 連接Decoder Filter和Render Filter
過程同上。
(9) 開始播放
包括以下步驟:
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 * leixiaohua1020@126.com * 中國傳媒大學/數字電視技術 * 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 <dshow.h> //'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.\n"); return -1; } hr = pGraph->AddFilter(pF_source, L"Lei's Source"); if (FAILED(hr)){ printf("Failed to add File Source to Filter Graph.\n"); return -1; } IFileSourceFilter* pFileSource; pF_source->QueryInterface(IID_IFileSourceFilter, (void**)&pFileSource); pFileSource->Load(L"cuc_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.\n"); return -1; } hr = pGraph->AddFilter(pF_demuxer, L"Lei's Demuxer"); if (FAILED(hr)){ printf("Failed to add Demuxer to Filter Graph.\n"); 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.\n"); return -1; } hr = pGraph->AddFilter(pF_decoder, L"Lei's Decoder"); if (FAILED(hr)){ printf("Failed to add Decoder to Filter Graph.\n"); 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.\n"); return -1; } hr = pGraph->AddFilter(pF_render, L"Lei's Render"); if (FAILED(hr)){ printf("Failed to add Video Render to Filter Graph.\n"); return -1; } //2. Connect Filters======================= hr = connect_filters(pGraph, pF_source, pF_demuxer); if (FAILED(hr)){ printf("Failed to link Source and Demuxer.\n"); return -1; } hr = connect_filters(pGraph, pF_demuxer, pF_decoder); if (FAILED(hr)){ printf("Failed to link Demuxer and Decoder.\n"); return -1; } hr = connect_filters(pGraph, pF_decoder, pF_render); if (FAILED(hr)){ printf("Failed to link Decoder and Render.\n"); 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。
可以通過修改源文件首部的宏定義ADD_MANUAL來設定是否手動添加Filter,如下所示。
//'1':Add filters manually //'0':Add filters automatically #define ADD_MANUAL 1
最簡單的基於DirectShow的示例:獲取Filter信息
流程圖
該程序的流程圖如下所示。由於該圖的尺寸比較大,在頁面中顯示不下,所以在相冊中上傳了一份:
接口
該流程圖中涉及到以下接口:
ICreateDevEnum *pSysDevEnum:設備列舉接口。
IEnumMoniker *pEnumCat:Moniker(別名)枚舉接口。
IMoniker *pMoniker:Moniker(別名)接口。
IPropertyBag *pPropBag:存儲屬性值的接口。
IBaseFilter *pFilter:Filter接口。
IEnumPins * pinEnum:Filter枚舉接口。
IPin * pin: Pin接口。
PIN_INFO pinInfo:存儲Pin的信息的結構體。
IEnumMediaTypes *mtEnum:MediaType枚舉接口。AM_MEDIA_TYPE *mt:描述媒體類型的結構體。
流程圖
該流程圖中涉及到以下函數:
【初始化】
CoInitialize():初始化COM運行環境。
CoCreateInstance(…,pSysDevEnum):用指定的類標識符創建一個Com對象。在該示例中類標識符為“IID_ICreateDevEnum”,用於創建ICreateDevEnum。
【Filter的枚舉】
pSysDevEnum->CreateClassEnumerator(…,pEnumCat):通過ICreateDevEnum查詢IEnumMoniker枚舉接口,枚舉指定類型目錄下的設備Moniker(別名)。
pEnumCat->Next(…,pMoniker):通過IEnumMoniker查詢下一個IMoniker接口。
pMoniker->BindToStorage(…,pPropBag):通過IMoniker查詢IPropertyBag接口(用於獲取Filter信息)。
pPropBag->Read("FriendlyName"):通過IPropertyBag獲取“FriendlyName”屬性的值。pMoniker->BindToObject(…,pFilter):通過IMoniker查詢IBaseFilter接口(用於獲取Filter,注意和BindToStorage()區別)。
【Pin的枚舉】
pFilter->EnumPins(pinEnum):通過IBaseFilter查詢IEnumPins枚舉接口。
pinEnum->Next(…,pin):通過IEnumPins查詢下一個IPin接口。pin->QueryPinInfo(PinInfo):通過IPin獲取Pin的信息。
【MediaType的枚舉】
pin->EnumMediaTypes(&mtEnum):通過IPin查詢IEnumMediaTypes枚舉接口。
mtEnum->Next(…, &mt):通過IEnumMediaTypes查詢下一個AM_MEDIA_TYPE。GuidToString(mt->majortype):把AM_MEDIA_TYPE的GUID轉換成字符串(方便輸出)。
【釋放】
CoUninitialize():釋放CoInitialize()初始化的COM運行環境。
再附上一張代碼中涉及到的接口之間的關系:
可以看出從上到下他們之間順序的排列如下所示:
ICreateDevEnum-->IEnumMoniker-->IMoniker-->IBaseFilter-->IEnumPins-->IPin-->IEnumMediaTypes-->AM_MEDIA_TYPE
源代碼
/** * 最簡單的Directshow信息顯示例子 * Simplest DirectShow Info * * 雷霄驊 Lei Xiaohua * leixiaohua1020@126.com * 中國傳媒大學/數字電視技術 * Communication University of China / Digital TV Technology * http://blog.csdn.net/leixiaohua1020 * * 本程序是一段獲取DirectShow濾鏡信息的代碼。通過本代碼可以獲得 * DirectShow濾鏡信息。適合初學者學習DirectShow。 * * This code can be used to get Directshow Filter's information. * Suitable for the beginner of DirectShow. */ #include "stdafx.h" #include <dshow.h> #include <atlconv.h> #define OUTPUT_PIN 1 #define OUTPUT_MEDIATYPE 1 char* GuidToString(const GUID &guid) { int buf_len=64; char *buf =(char *)malloc(buf_len); _snprintf( buf, buf_len, "{%08X-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X}", guid.Data1, guid.Data2, guid.Data3, guid.Data4[0], guid.Data4[1], guid.Data4[2], guid.Data4[3], guid.Data4[4], guid.Data4[5], guid.Data4[6], guid.Data4[7]); //printf("%s\n",buf); return buf; } int _tmain(int argc, _TCHAR* argv[]) { USES_CONVERSION; // Init COM HRESULT hr=NULL; hr= CoInitialize(NULL); if (FAILED(hr)){ printf("Error, Can not init COM."); return -1; } printf("===============Directshow Filters ===============\n"); ICreateDevEnum *pSysDevEnum = NULL; hr = CoCreateInstance(CLSID_SystemDeviceEnum, NULL, CLSCTX_INPROC_SERVER, IID_ICreateDevEnum, (void **)&pSysDevEnum); if (FAILED(hr)){ return hr; } IEnumMoniker *pEnumCat = NULL; //Category /************************************************************************ Friendly Name CLSID ------------------------------------------------------------------------- Audio Capture Sources CLSID_AudioInputDeviceCategory Audio Compressors CLSID_AudioCompressorCategory Audio Renderers CLSID_AudioRendererCategory Device Control Filters CLSID_DeviceControlCategory DirectShow Filters CLSID_LegacyAmFilterCategory External Renderers CLSID_TransmitCategory Midi Renderers CLSID_MidiRendererCategory Video Capture Sources CLSID_VideoInputDeviceCategory Video Compressors CLSID_VideoCompressorCategory WDM Stream Decompression Devices CLSID_DVDHWDecodersCategory WDM Streaming Capture Devices AM_KSCATEGORY_CAPTURE WDM Streaming Crossbar Devices AM_KSCATEGORY_CROSSBAR WDM Streaming Rendering Devices AM_KSCATEGORY_RENDER WDM Streaming Tee/Splitter Devices AM_KSCATEGORY_SPLITTER WDM Streaming TV Audio Devices AM_KSCATEGORY_TVAUDIO WDM Streaming TV Tuner Devices AM_KSCATEGORY_TVTUNER WDM Streaming VBI Codecs AM_KSCATEGORY_VBICODEC ************************************************************************/ hr = pSysDevEnum->CreateClassEnumerator(CLSID_VideoCompressorCategory, &pEnumCat, 0); //hr = pSysDevEnum->CreateClassEnumerator(CLSID_VideoInputDeviceCategory, &pEnumCat, 0); //hr = pSysDevEnum->CreateClassEnumerator(CLSID_AudioCompressorCategory, &pEnumCat, 0); //hr = pSysDevEnum->CreateClassEnumerator(CLSID_AudioInputDeviceCategory, &pEnumCat, 0); //hr = pSysDevEnum->CreateClassEnumerator(CLSID_MediaMultiplexerCategory, &pEnumCat, 0); //hr = pSysDevEnum->CreateClassEnumerator(CLSID_LegacyAmFilterCategory, &pEnumCat, 0); if (hr != S_OK) { pSysDevEnum->Release(); return -1; } IMoniker *pMoniker = NULL; ULONG monikerFetched; //Filter while(pEnumCat->Next(1, &pMoniker, &monikerFetched) == S_OK){ IPropertyBag *pPropBag; VARIANT varName; IBaseFilter *pFilter; hr = pMoniker->BindToStorage(0, 0, IID_IPropertyBag,(void **)&pPropBag); if (FAILED(hr)){ pMoniker->Release(); continue; } VariantInit(&varName); hr = pPropBag->Read(L"FriendlyName", &varName, 0); //"FriendlyName": The name of the device. //"Description": A description of the device. //Filter Info================ printf("[%s]\n",W2A(varName.bstrVal)); VariantClear(&varName); //======================== #if OUTPUT_PIN hr = pMoniker->BindToObject(NULL, NULL, IID_IBaseFilter,(void**)&pFilter); if (!pFilter){ continue; } IEnumPins * pinEnum = NULL; IPin * pin = NULL; ULONG pinFetched = 0; if (FAILED(pFilter->EnumPins(&pinEnum))){ pinEnum->Release(); continue; } pinEnum->Reset(); //Pin Info while (SUCCEEDED(pinEnum->Next(1, &pin, &pinFetched)) && pinFetched){ if (!pin){ continue; } PIN_INFO pinInfo; if (FAILED(pin->QueryPinInfo(&pinInfo))){ continue; } printf("\t[Pin] "); switch(pinInfo.dir){ case PINDIR_INPUT:printf("Dir:Input \t");break; case PINDIR_OUTPUT:printf("Dir:Output \t");break; default:printf("Dir:Unknown\n");break; } printf("Name:%s\n",W2A(pinInfo.achName)); //MediaType #if OUTPUT_MEDIATYPE IEnumMediaTypes *mtEnum=NULL; AM_MEDIA_TYPE *mt=NULL; if( FAILED( pin->EnumMediaTypes( &mtEnum )) ) break; mtEnum->Reset(); ULONG mtFetched = 0; while (SUCCEEDED(mtEnum->Next(1, &mt, &mtFetched)) && mtFetched){ printf("\t\t[MediaType]\n"); //Video char *MEDIATYPE_Video_str=GuidToString(MEDIATYPE_Video); //Audio char *MEDIATYPE_Audio_str=GuidToString(MEDIATYPE_Audio); //Stream char *MEDIATYPE_Stream_str=GuidToString(MEDIATYPE_Stream); //Majortype char *majortype_str=GuidToString(mt->majortype); //Subtype char *subtype_str=GuidToString(mt->subtype); printf("\t\t Majortype:"); if(strcmp(majortype_str,MEDIATYPE_Video_str)==0){ printf("Video\n"); }else if(strcmp(majortype_str,MEDIATYPE_Audio_str)==0){ printf("Audio\n"); }else if(strcmp(majortype_str,MEDIATYPE_Stream_str)==0){ printf("Stream\n"); }else{ printf("Other\n"); } printf("\t\t Subtype GUID:%s",subtype_str); free(MEDIATYPE_Video_str); free(MEDIATYPE_Audio_str); free(MEDIATYPE_Stream_str); free(subtype_str); free(majortype_str); printf("\n"); } #endif pin->Release(); } pinEnum->Release(); pFilter->Release(); #endif pPropBag->Release(); pMoniker->Release(); } pEnumCat->Release(); pSysDevEnum->Release(); printf("=================================================\n"); CoUninitialize(); return 0; }
運行結果
程序運行的結果如下圖所示。從圖中可以看出,程序打印出了系統中DirectShow的Filter信息。每個Filter的信息中包含了它的Pin的信息。每個Pin中又包含了Pin中的MediaType信息。
可以通過定義在代碼最前面宏 控制輸出的Filter信息的類型。定義成“0”的話則不會輸出該類的信息。如下所示。
#define OUTPUT_PIN 1 #define OUTPUT_MEDIATYPE 1