本文介绍一个最简单的基于 DirectShow 的视频播放器。该播放器对于初学者来说是十分有用的,它包含了使用 DirectShow 播放视频所有必备的函数。
直接贴上代码,具体代码的含义都写在注释中了:
/* 雷霄骅 * 中国传媒大学/数字电视技术 * [email protected] * */ // 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是一个庞大的框架,可以在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 * [email protected] * 中国传媒大学/数字电视技术 * 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; }
(2) 播放该视频的 Filter Graph中的Filter(该功能通过函数show_filters_in_filtergraph()完成)。
可以通过定义在代码最前面宏OUTPUT_INFO控制是否输出视频的信息。定义成“0”的话则不会输出视频的信息。如下所示。
#define OUTPUT_INFO 1
本文记录一个最简单的基于DirectShow的图形界面的视频播放器。基于DirectShow的图形界面的播放器的例子还是比较多的,但是大部分都是“层层封装”的例子。“层层封装”的例子相对来说更加稳定,但是却不是很容易理解。因为DirectShow本身的接口函数的数量就比较多,如果再加上封装DirectShow的函数,合起来的函数数量是非常大的,很容易让人搞不清哪些才是真正的DirectShow接口函数。本播放器剥去了 DirectShow例子中的“层层封装”,直接调用DirectShow的接口完成视频的播放工作,更加适合DirectShow入门使用。
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); }
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")); } }
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(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); }
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); }
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的自定义的视频播放器的流程如下图所示。
该流程图中包含如下变量:
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(L"xxx.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 <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
该流程图中涉及到以下接口:
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:描述媒体类型的结构体。
该流程图中涉及到以下函数:
【初始化】
【Filter的枚举】CoInitialize():初始化COM运行环境。
CoCreateInstance(…,pSysDevEnum):用指定的类标识符创建一个Com对象。在该示例中类标识符为“IID_ICreateDevEnum”,用于创建ICreateDevEnum。
pSysDevEnum->CreateClassEnumerator(…,pEnumCat):通过ICreateDevEnum查询IEnumMoniker枚举接口,枚举指定类型目录下的设备Moniker(别名)。【Pin的枚举】
pEnumCat->Next(…,pMoniker):通过IEnumMoniker查询下一个IMoniker接口。
pMoniker->BindToStorage(…,pPropBag):通过IMoniker查询IPropertyBag接口(用于获取Filter信息)。
pPropBag->Read("FriendlyName"):通过IPropertyBag获取“FriendlyName”属性的值。pMoniker->BindToObject(…,pFilter):通过IMoniker查询IBaseFilter接口(用于获取Filter,注意和BindToStorage()区别)。
pFilter->EnumPins(pinEnum):通过IBaseFilter查询IEnumPins枚举接口。【MediaType的枚举】
pinEnum->Next(…,pin):通过IEnumPins查询下一个IPin接口。pin->QueryPinInfo(PinInfo):通过IPin获取Pin的信息。
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 * [email protected] * 中国传媒大学/数字电视技术 * 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; }
可以通过定义在代码最前面宏 控制输出的Filter信息的类型。定义成“0”的话则不会输出该类的信息。如下所示。
#define OUTPUT_PIN 1 #define OUTPUT_MEDIATYPE 1