作者:liguisen
Blog:http://blog.csdn.net/liguisen
上一篇做了一个简单的媒体文件播放器,它到底与其它程序有什么不同呢?很大的一个关键就在于我们使用了
CDXGraph.h和CDXGraph.cpp这两个文件,只有这两个东西不是我们自己的,现在就把它变成我们自己的。
先看看我们做的程序,要播放媒体,就两个步骤,打开文件,播放。跟踪代码的执行过程,无非就是这样:
通过“打开对话框”得到文件:
mSourceFile = dlgOpen.GetPathName();
然后
CreateGraph(),看:
void CMyPlayerDlg::CreateGraph(void)
{
DestroyGraph();//老套路了,先破坏,确保
mFilterGraph是NULL,先看DestroyGraph()
mFilterGraph = new CDXGraph();
if (mFilterGraph->Create())
{
mFilterGraph->RenderFile(mSourceFile);
mFilterGraph->SetDisplayWindow(mVideoWindow.GetSafeHwnd());
mFilterGraph->SetNotifyWindow(this->GetSafeHwnd());
mFilterGraph->Pause();
}
}
看
DestroyGraph():
void CMyPlayerDlg::DestroyGraph(void)
{//这个函数的作用就是:如果“打开”了就“关闭”,否则什么也不干,
//就是运行了这个函数,一定是“关闭”的
if (mFilterGraph)//CDXGraph * mFilterGraph,是
CDXGraph指针
{
mFilterGraph->Stop();
mFilterGraph->SetNotifyWindow(NULL);
delete mFilterGraph;
mFilterGraph = NULL;
}
}
CreateGraph函数我们从
new CDXGraph()开始“研究”,就是看其构造函数,什么都没做,就是初始化,往下看mFilterGraph->Create():
bool CDXGraph::Create(void)
{
if (!mGraph)
{
if (SUCCEEDED(CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC_SERVER,IID_IGraphBuilder, (void **)&mGraph)))
{
//AddToObjectTable();这个是用来做
GraphEdit调试的,暂且去掉
return QueryInterfaces();
}
mGraph = 0;
}
return false;
}
第一个不认识的是
mGraph,mGraph是什么?是IGraphBuilder * mGraph;
DirectShow是基于模块化的,使用一个
Filter Graph Manager来管理整个数据流的处理过程,参与处理的各个功能模块叫做Filter(国内目前的翻译有滤波器、过滤器、滤镜、筛选器等等乱七八糟的),各个Filter在Filter Graph中按一定的顺序连接成一条流水线协调工作,大致分为3类:Source Filters(负责获取数据)、Transform Filters(负责数据的格式转换,例如数据流的分离/合成、解码/编码)、Rendering Filters(负责数据的去向,显卡、声卡、文件等)。
DirectShow建立在
COM组件技术基础上,DirectShow与COM紧密相连,它所有的部件和功能都由COM接口来构造和实现,其中几个重要的接口经常需要用到的:IGraphBuilder接口,用来创建Filter Graph Manager;IMediaControl接口,用来控制流媒体在Filter Graph中的流动,例如流媒体的启动和停止;IMediaEvent接口,该接口在Filter Graph发生一些事件时用来创建事件的标志信息并传送给应用程序(参考附2);IVideoWindow: 用于设置多媒体播放窗口的属性。(更多接口请参考附1)
所以,在
CDXGraph类中有如下一些定义:
IGraphBuilder * mGraph;
IMediaControl * mMediaControl;
IMediaEventEx * mEvent;
IVideoWindow *
mVideoWindow;
因此,程序中
if (!mGraph)判断还没有创建Filter Graph Manager,然后SUCCEEDED(CoCreateInstance(CLSID_FilterGraph,NULL,CLSCTX_INPROC_SERVER,IID_IGraphBuilder, (void **)&mGraph))创建Filter Graph Manager。
外面的
SUCCEEDED是一个宏:
#define SUCCEEDED(Status) ((HRESULT)(Status) >= 0)
CoCreateInstance是
com的api函数。
接着
QueryInterfaces()查询各接口,获取Filter Graph 和IMediaEvent等组件的指针;
bool CDXGraph::QueryInterfaces(
void)
{
if (mGraph)
{
HRESULT hr = NOERROR;
hr |= mGraph->QueryInterface(IID_IMediaControl, (void **)&mMediaControl);
hr |= mGraph->QueryInterface(IID_IMediaEventEx, (void **)&mEvent);
hr |= mGraph->QueryInterface(IID_IBasicVideo, (void **)&mBasicVideo);
hr |= mGraph->QueryInterface(IID_IBasicAudio, (void **)&mBasicAudio);
hr |= mGraph->QueryInterface(IID_IVideoWindow, (void **)&mVideoWindow);
hr |= mGraph->QueryInterface(IID_IMediaSeeking, (void **)&mSeeking);
if (mSeeking)
{
mSeeking->SetTimeFormat(&TIME_FORMAT_MEDIA_TIME);
}
return SUCCEEDED(hr);
}
return false;
}
mFilterGraph->Create()成功创建
Filter Graph Manager之后,就是
mFilterGraph->RenderFile(mSourceFile);
看如何实现:
bool CDXGraph::RenderFile(
const char * inFile)
{
if (mGraph)
{
WCHAR szFilePath[MAX_PATH];
MultiByteToWideChar(CP_ACP, 0, inFile, -1, szFilePath, MAX_PATH);
if (SUCCEEDED(mGraph->RenderFile(szFilePath, NULL)))
{
return true;
}
}
return false;
}
先对字符串作一个转换,关键在mGraph->RenderFile(szFilePath, NULL),即IGraphBuilder::RenderFile函数,内部实现暂且不管。
mFilterGraph->RenderFile(mSourceFile);后是
mFilterGraph->SetDisplayWindow(mVideoWindow.GetSafeHwnd());即:
bool CDXGraph::SetDisplayWindow(HWND inWindow)
{
if (mVideoWindow)
{
mVideoWindow->put_Visible(OAFALSE);
mVideoWindow->put_Owner((OAHWND)inWindow);
RECT windowRect;
::GetClientRect(inWindow, &windowRect);
mVideoWindow->put_Left(0);
mVideoWindow->put_Top(0);
mVideoWindow->put_Width(windowRect.right - windowRect.left);
mVideoWindow->put_Height(windowRect.bottom - windowRect.top);
mVideoWindow->put_WindowStyle(WS_CHILD|WS_CLIPCHILDREN|WS_CLIPSIBLINGS);
mVideoWindow->put_MessageDrain((OAHWND) inWindow);
if (inWindow != NULL)
{
mVideoWindow->put_Visible(OATRUE);
}
else
{
mVideoWindow->put_Visible(OAFALSE);
}
return true;
}
return false;
}
上面这个很好理解,无非就是播放窗口的设置,除了上面设置的,还有全屏啊什么的设置,不一一列出。
再往下就是事件通知处理
mFilterGraph->SetNotifyWindow(this->GetSafeHwnd());,即:
bool CDXGraph::SetNotifyWindow(HWND inWindow)
{
if (mEvent)
{
mEvent->SetNotifyWindow((OAHWND)inWindow, WM_GRAPHNOTIFY, 0);
return true;
}
return false;
}
然后先暂停(代码不再详细列出)
mMediaControl->Pause(),当然做这些操作之前应该先获得状态,例如:
bool CDXGraph::IsPaused(
void)
{
if (mGraph && mMediaControl)
{
OAFilterState state = State_Stopped;
if (SUCCEEDED(mMediaControl->GetState(10, &state)))
{
return state == State_Paused;
}
}
return false;
}
打开文件就做这么多,至于播放,就是一个:
mMediaControl->Run()。
下面不使用
CDXGraph类来创建我们自己的工程,用vc创建MFC对话框工程MyPlayer2,和MyPlayer一样做好各个设置,为对话框头文件增加#include ,引进CDXGraph类的过程不要,设置好界面,打开按钮不要,只要一个播放按钮,为播放按钮添加函数,精简的代码(省略了一些细节)如下:
void CMyPlayer2Dlg::OnBnClickedButtonplay()
{
// TODO: Add your control notification handler code here
IGraphBuilder * mGraph;
IMediaControl * mMediaControl;
IVideoWindow * mVWindow;
CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC_SERVER, IID_IGraphBuilder, (void **)&mGraph);
if (mGraph)
{
mGraph->QueryInterface(IID_IMediaControl, (void **)&mMediaControl);
mGraph->QueryInterface(IID_IVideoWindow, (void **)&mVWindow);
}
mGraph->RenderFile(L"g://cctv00.mpg", NULL);
HWND inWindow=mVideoWindow.GetSafeHwnd();
if (mVWindow)
{
mVWindow->put_Visible(OAFALSE);
mVWindow->put_Owner((OAHWND)inWindow);
RECT windowRect;
::GetClientRect(inWindow, &windowRect);
mVWindow->put_Left(0);
mVWindow->put_Top(0);
mVWindow->put_Width(windowRect.right - windowRect.left);
mVWindow->put_Height(windowRect.bottom - windowRect.top);
mVWindow->put_WindowStyle(WS_CHILD|WS_CLIPCHILDREN|WS_CLIPSIBLINGS);
mVWindow->put_MessageDrain((OAHWND) inWindow);
if (inWindow != NULL)
{
mVWindow->put_Visible(OATRUE);
}
else
{
mVWindow->put_Visible(OAFALSE);
}
}
mMediaControl->Run();
}
其中大部分代码是窗口的设置,我们甚至连那个窗口也不要,仅仅在对话框中添加一个播放按钮:
void CMyPlayer2Dlg::OnBnClickedButtonplay()
{
IGraphBuilder * mGraph;
IMediaControl * mMediaControl;
CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC_SERVER, IID_IGraphBuilder, (void **)&mGraph);
mGraph->QueryInterface(IID_IMediaControl, (void **)&mMediaControl);
mGraph->RenderFile(L"g://cctv00.mpg", NULL);
mMediaControl->Run();
}
上面六行代码告诉了我们播放一个媒体文件的必需步骤:
1,通过
API函数CoCreateInstance()创建一个Filter Graph Manager 实例;
2,通过调用QueryInterface ( )函数来获取组件的指针;
3,对Filter Graph进行控制和对事件作出响应。
附
1:
转载:
DirectShow之接口实战篇(一)作者:
pejaq(子墨书屋)
http://hi.baidu.com/pejaq/blog/item/98537931af869b19eac4af00.html
现今自己编程做一个多媒体播放工具是一件很令人开心愉悦的事情,但如果使用
MediaPlay控件开发则会受到很多限制,自己的很多好的创意想法都无法或者很难实现,如果利用微软的DirectX接口开发则可以充分的将作者的独特想法付诸于实现,何乐而不为呢!!不过关于DirectShow接口的开发说明文档实在是少之又少,仅有的一些不是英文的就是一些关于理论方面的,真正关于接口实战编程而且是用Delphi开发工具实现的更是凤毛麟角,使很多人都望而却步。在这里,我把我应用Directshow开发的心得以及我搜集到一些资料重新整理编辑出来公布,希望对所有由此兴趣的同仁有所帮助,就算达到了我的目的。废话少说,进入正文。
既然是接口实战篇,就先把一些常用的接口列出来,让大家有一些基本的认识,都是用来做什么的,什么时候我们会需要用到此接口。
IFilterGraph
|
过滤通道接口
|
IFilterGraph2
|
增强的
IFilterGraph
|
IGraphBuilder
|
最为重用的
COM接口,用于手动或者自动构造过滤通道Filter Graph Manager
|
IMediaControl
|
用来控制流媒体,例如流的启动和停止暂停等
,播放控制接口
|
IMediaEvent
|
播放事件接口
,该接口在Filter Graph发生一些事件时用来创建事件的标志信息并传送给应用程序
|
IMediaEventEx
|
扩展播放事件接口
|
IMediaPosition
|
播放的位置和速度控制接口
(控制播放位置只能为设置时间控制方式)
|
IMediaSeeking
|
另一个播放的位置和播放速度控制接口
,在位置选择方面功能较强.设置播放格式,多种控制播放方式.常用的有:(1)TIME_FORMAT_MEDIA_TIME单位100纳秒。(2)TIME_FORMAT_FRAME按帧播放
|
IBasicAudio
|
声音控制接口
|
IBasicVideo
|
图像控制接口
(波特率,宽度,长度等信息)
|
IVideoWindow
|
显示窗口控制接口
(有关播放窗口的一切控制,包括caption显示,窗口位置控制等)
|
ISampleGrabber
|
捕获图象接口
(可用于抓图控制)
|
IVideoFrameStep
|
控制单帧播放的接口
|
好了,熟悉了应用
DirectShow应用开发常用的接口后,我们就通过一个实例媒体播放器来熟悉掌握这些接口,实例的代码虽然简单,但五脏俱全,功能强大,同时也了解一下应用DirectShow开发一般常用的步骤。
附
2:
EC_ACTIVATE 视频窗口被激活或者转为非激活状态
EC_BUFFERING_DATA 过滤图形包含缓冲数据
EC_CLOCK_CHANGED 参考时钟被改变
EC_CLOCK_UNSET 时钟提供者被断开
EC_COMPLETE 所有数据被渲染完毕
EC_DEVICE_LOST 一个即插即用设备被移除或者变为有效.
EC_DISPLAY_CHANGED 显示模式被改变
EC_END_OF_SEGMENT 到达段的末尾.
EC_ERROR_STILLPLAYING 一个异步命令失败
EC_ERRORABORT 一个操作被放弃
EC_EXTDEVICE_MODE_CHANGE 不支持
EC_FULLSCREEN_LOST 一个视频渲染窗口被切换出全屏模式.
EC_GRAPH_CHANGED 过滤器图被改变
EC_LENGTH_CHANGED 源的长度被改变.
EC_NEED_RESTART 过滤器请求过滤图重新开始.
EC_NOTIFY_WINDOW 通报一个视频渲染窗口的过滤器
EC_OLE_EVENT 过滤器传递一个字符串给应用程序。.
EC_OPENING_FILE 过滤图打开一个文件,或者已经完成了打开文件操作
EC_PALETTE_CHANGED 视频调色板被改变.
EC_PAUSED 一个暂停请求被处理.
EC_QUALITY_CHANGE 过滤图为了质量控制丢桢
EC_REPAINT 一个视频渲染器要求重绘.
EC_SEGMENT_STARTED 一个新段开始
EC_SHUTTING_DOWN 过滤器图被关闭
EC_SNDDEV_IN_ERROR 一个音频设备的输入引脚错误.
EC_SNDDEV_OUT_ERROR 一个音频设备的输出引脚错误.
EC_STARVATION 过滤器没有得到足够的数据.
EC_STATE_CHANGE 过滤器图状态改变
EC_STEP_COMPLETE 一个过滤器执行了单桢渐进
EC_STREAM_CONTROL_STARTED 流控制开始命令产生效果.
EC_STREAM_CONTROL_STOPPED 一个流控制的停止命令产生效果
EC_STREAM_ERROR_STILLPLAYING 在流中产生了一个错误,但流还是在运行中.
EC_STREAM_ERROR_STOPPED 一个流因错误而停止
EC_TIMECODE_AVAILABLE 不支持
EC_USERABORT 用户中断回放.
EC_VIDEO_SIZE_CHANGED 本地视频尺寸改变.
EC_WINDOW_DESTROYED 视频渲染器被销毁,或者从过滤器图中移除.