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 FilterGraphhr=CoCreateInstance(CLSID_FilterGraph, NULL,CLSCTX_INPROC_SERVER,IID_IGraphBuilder, (void **)&pGraph);
if (FAILED(hr)){  printf("Error - Can't create Filter Graph.");
return -1;  }
//  Query Interfacehr = pGraph->QueryInterface(IID_IMediaControl, (void **)&pControl);  hr = pGraph->QueryInterface(IID_IMediaEvent, (void **)&pEvent);
// RenderFilehr = 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;
//Videohr1=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);
//Audiohr1=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);
//Windowhr1=pGraph->QueryInterface(IID_IVideoWindow, (void **)&pWindow);  pWindow->put_Caption(L"Simplest DirectShow Player");
//pWindow->put_Width(480);
//pWindow->put_Height(272);
//Seekhr1=pGraph->QueryInterface(IID_IMediaSeeking, (void **)&pSeeking);  pSeeking->GetDuration(&duration_1);
//time unit:100ns=0.0000001sduration_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 FilterGpaghshow_filters_in_filtergraph(pGraph);
//----------------------
#endifprintf("Progress Info\n");  printf("Position\tProgress\n");
if (SUCCEEDED(hr)){
// Runhr = pControl->Run();
if (SUCCEEDED(hr)){
long evCode=0;
//pEvent->WaitForCompletion(INFINITE, &evCode);
while(evCode!=EC_COMPLETE){
//Info
#if OUTPUT_INFOpSeeking->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
//1000mspEvent->WaitForCompletion(1000, &evCode);  }  }  }
// Release resourcepControl->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 _UNICODEm_url.GetWindowText(cstr_urlw);
#elseUSES_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 MessagepWindow->put_Visible(OATRUE);  pEvent->SetNotifyWindow((OAHWND)screen_hwnd, WM_GRAPHNOTIFY, 0);  // Runhr = 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 themIEnumFilters *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;
//mspSeeking->GetCurrentPosition(&curtime);
if(curtime!=0){
//change to secondtns = 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 modepWindow->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 filterIPin *pOut = 0;
HRESULT hr = NULL;  hr=get_unconnected_pin(pSrc, PINDIR_OUTPUT, &pOut);
if (FAILED(hr)){
return hr;  }
//Find Input pin in destination filterIPin *pIn = 0;  hr = get_unconnected_pin(pDest, PINDIR_INPUT, &pIn);
if (FAILED(hr)){
return hr;  }
//Connnect themhr = 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 FilterGraphhr=CoCreateInstance(CLSID_FilterGraph, NULL,CLSCTX_INPROC_SERVER,IID_IGraphBuilder, (void **)&pGraph);
if (FAILED(hr)){  printf("Error - Can't create Filter Graph.");
return -1;  }
// Query Interfacehr = pGraph->QueryInterface(IID_IMediaControl, (void **)&pControl);  hr = pGraph->QueryInterface(IID_IMediaEvent, (void **)&pEvent);  //1. Add Filters=======================
//SourceIBaseFilter *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
//DemuxerIBaseFilter *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;  }
//DecoderIBaseFilter *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;  }
//RenderIBaseFilter *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();
#elseIPin*    Pin;
ULONG    fetched;
//  get output pinIEnumPins* pEnumPins;  hr = pF_source->EnumPins(&pEnumPins);  hr = pEnumPins->Reset();  hr = pEnumPins->Next(1, &Pin, &fetched);  pEnumPins->Release();
//  render pin, graph builder automatically complete rest workshr = pGraph->Render(Pin);
#endifif (SUCCEEDED(hr)){
// Runhr = pControl->Run();
if (SUCCEEDED(hr)){
long evCode=0;  pEvent->WaitForCompletion(INFINITE, &evCode);  }  }
//ReleasepControl->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 1char* 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_AudioInputDeviceCategoryAudio Compressors                     CLSID_AudioCompressorCategoryAudio Renderers                       CLSID_AudioRendererCategoryDevice Control Filters                CLSID_DeviceControlCategoryDirectShow Filters                    CLSID_LegacyAmFilterCategoryExternal Renderers                    CLSID_TransmitCategoryMidi Renderers                        CLSID_MidiRendererCategoryVideo Capture Sources                 CLSID_VideoInputDeviceCategoryVideo Compressors                     CLSID_VideoCompressorCategoryWDM Stream Decompression Devices      CLSID_DVDHWDecodersCategoryWDM Streaming Capture Devices         AM_KSCATEGORY_CAPTUREWDM Streaming Crossbar Devices        AM_KSCATEGORY_CROSSBARWDM Streaming Rendering Devices       AM_KSCATEGORY_RENDERWDM Streaming Tee/Splitter Devices    AM_KSCATEGORY_SPLITTERWDM Streaming TV Audio Devices        AM_KSCATEGORY_TVAUDIOWDM Streaming TV Tuner Devices        AM_KSCATEGORY_TVTUNERWDM 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_PINhr = 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_MEDIATYPEIEnumMediaTypes *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");  }
#endifpin->Release();  }  pinEnum->Release();  pFilter->Release();
#endifpPropBag->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

转:最简单的基于 DirectShow 的视频播放器相关推荐

  1. 50行代码实现的一个最简单的基于 DirectShow 的视频播放器

    本文介绍一个最简单的基于 DirectShow 的视频播放器.该播放器对于初学者来说是十分有用的,它包含了使用 DirectShow 播放视频所有必备的函数. 直接贴上代码,具体代码的含义都写在注释中 ...

  2. 最简单的基于 DirectShow 的视频播放器

    源程序下载链接:https://download.csdn.net/download/bmjhappy/11250669 50行代码实现的一个最简单的基于 DirectShow 的视频播放器 本文介绍 ...

  3. 一个简单的基于 DirectShow 的播放器 1(封装类)

    DirectShow最主要的功能就是播放视频,在这里介绍一个简单的基于DirectShow的播放器的例子,是用MFC做的,今后有机会可以基于该播放器开发更复杂的播放器软件. 注:该例子取自于<D ...

  4. 一个简单的基于 DirectShow 的播放器 2(对话框类)

    上篇文章分析了一个封装DirectShow各种接口的封装类(CDXGraph):一个简单的基于 DirectShow 的播放器  1(封装类) 本文继续上篇文章,分析一下调用这个封装类(CDXGrap ...

  5. 最简单的基于libVLC的例子:最简单的基于libVLC的视频播放器

    最简单的基于libVLC的例子文章列表: 最简单的基于libVLC的例子:最简单的基于libVLC的视频播放器 最简单的基于libVLC的例子:最简单的基于libVLC的视频播放器(图形界面版) 最简 ...

  6. 最简单的基于DirectShow的示例:视频播放器自定义版

    ===================================================== 最简单的基于DirectShow的示例文章列表: 最简单的基于DirectShow的示例:视 ...

  7. 最简单的基于DirectShow的示例:视频播放器图形界面版

    ===================================================== 最简单的基于DirectShow的示例文章列表: 最简单的基于DirectShow的示例:视 ...

  8. 最简单的基于DirectShow的示例:视频播放器

    ===================================================== 最简单的基于DirectShow的示例文章列表: 最简单的基于DirectShow的示例:视 ...

  9. 最简单的基于DirectShow的示例:获取Filter信息

    ===================================================== 最简单的基于DirectShow的示例文章列表: 最简单的基于DirectShow的示例:视 ...

最新文章

  1. 鸿蒙升级名单确认最新,华为鸿蒙系统升级名单正式确认!全面替换安卓:流畅度比肩iOS...
  2. 拖动物体的时候,防止鼠标按下的时候对象突然跳动一下
  3. java ssh 常用术语
  4. 如何条码打印软件工具箱窗格进行重新定位
  5. VTK:Medical之MedicalDemo3
  6. [转]基于overlayfs的硬盘资源隔离工具troot
  7. Lucy-Richardson加速算法以及PSF计算MATLAB代码
  8. hadoop mapreduce开发实践之本地文件分发by streaming
  9. awksed story
  10. 代码实现:判断101-200之间有多少个素数(质数),并输出所有素数。 程序分析:判断素数的方法:用一个数分别去除2到sqrt(这个数),如果能被整除,则表明此数不是素数,反之是素数。...
  11. 怎么提高Origin烂橘子的下载速度
  12. java字体设置框_CSS样式更改——字体设置Font边框Border
  13. 项目 0: 预测泰坦尼克号乘客生还率
  14. 开机动画desc.txt描述文件的分析
  15. 用了python抢购京东茅台脚本,为什么你还是抢不到茅台?教你这样设置时间,提升成功概率
  16. with dlz mysql 条件_BIND+DLZ+MYSQL实现区域记录动态更新
  17. 2020年G1工业锅炉司炉考试总结及G1工业锅炉司炉试题及答案
  18. Java 控制反转和依赖注入模式【翻译】【整理】
  19. GPS授时服务器在电厂视频监控系统中的应用
  20. Linux更改文件的权限

热门文章

  1. EMD(经验模态分解)算法 五
  2. Geo Location 地理位置信息小结
  3. linux遍历目录删除指定文件,利用shell脚本遍历文件夹内所有的文件并作整理统计的方法-linux删除文件夹...
  4. Linux基础(7)Linux常用解压缩命令
  5. sohu读博:鱼顺顺
  6. 好莱坞系列电影全扫描
  7. 反编译工具(2)——推荐.Net、C# 逆向反编译四大工具利器
  8. 【JS算法】 排序算法及性能汇总
  9. linux实现表格数据的转置
  10. 建行U盾分类,制单盾、复核盾、主管盾。建行企业网银主管、复核、制单可以是一个人吗?