您的位置:首页 > 其它

DirectShow学习之三媒体播放过程分析

2008-03-20 22:10 363 查看
作者: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等组件的指针;
[align=left]bool CDXGraph::QueryInterfaces(void)[/align]
[align=left]{[/align]
[align=left] if (mGraph)[/align]
[align=left] {[/align]
[align=left] HRESULT hr = NOERROR;[/align]
[align=left] hr |= mGraph->QueryInterface(IID_IMediaControl, (void **)&mMediaControl);[/align]
[align=left] hr |= mGraph->QueryInterface(IID_IMediaEventEx, (void **)&mEvent);[/align]
[align=left] hr |= mGraph->QueryInterface(IID_IBasicVideo, (void **)&mBasicVideo);[/align]
[align=left] hr |= mGraph->QueryInterface(IID_IBasicAudio, (void **)&mBasicAudio);[/align]
[align=left] hr |= mGraph->QueryInterface(IID_IVideoWindow, (void **)&mVideoWindow);[/align]
[align=left] hr |= mGraph->QueryInterface(IID_IMediaSeeking, (void **)&mSeeking);[/align]
[align=left] if (mSeeking)[/align]
[align=left] {[/align]
[align=left] mSeeking->SetTimeFormat(&TIME_FORMAT_MEDIA_TIME);[/align]
[align=left] }[/align]
[align=left] return SUCCEEDED(hr);[/align]
[align=left] }[/align]
[align=left] return false;[/align]
}
mFilterGraph->Create()成功创建Filter Graph Manager之后,就是
mFilterGraph->RenderFile(mSourceFile);
看如何实现:
[align=left]bool CDXGraph::RenderFile(const char * inFile)[/align]
[align=left]{[/align]
[align=left] if (mGraph)[/align]
[align=left] {[/align]
[align=left] WCHAR szFilePath[MAX_PATH];[/align]
[align=left] MultiByteToWideChar(CP_ACP, 0, inFile, -1, szFilePath, MAX_PATH);[/align]
[align=left] if (SUCCEEDED(mGraph->RenderFile(szFilePath, NULL)))[/align]
[align=left] {[/align]
[align=left] return true;[/align]
[align=left] }[/align]
[align=left] }[/align]
[align=left] return false;[/align]
}
先对字符串作一个转换,关键在mGraph->RenderFile(szFilePath, NULL),即IGraphBuilder::RenderFile函数,内部实现暂且不管。
mFilterGraph->RenderFile(mSourceFile);后是
mFilterGraph->SetDisplayWindow(mVideoWindow.GetSafeHwnd());即:
[align=left]bool CDXGraph::SetDisplayWindow(HWND inWindow)[/align]
[align=left]{ [/align]
[align=left] if (mVideoWindow)[/align]
[align=left] {[/align]
[align=left] mVideoWindow->put_Visible(OAFALSE);[/align]
[align=left] mVideoWindow->put_Owner((OAHWND)inWindow);[/align]
[align=left] [/align]
[align=left] RECT windowRect;[/align]
[align=left] ::GetClientRect(inWindow, &windowRect);[/align]
[align=left] mVideoWindow->put_Left(0);[/align]
[align=left] mVideoWindow->put_Top(0);[/align]
[align=left] mVideoWindow->put_Width(windowRect.right - windowRect.left);[/align]
[align=left] mVideoWindow->put_Height(windowRect.bottom - windowRect.top);[/align]
[align=left] mVideoWindow->put_WindowStyle(WS_CHILD|WS_CLIPCHILDREN|WS_CLIPSIBLINGS);[/align]
[align=left] [/align]
[align=left] mVideoWindow->put_MessageDrain((OAHWND) inWindow);[/align]
[align=left] if (inWindow != NULL)[/align]
[align=left] {[/align]
[align=left] mVideoWindow->put_Visible(OATRUE);[/align]
[align=left] }[/align]
[align=left] else[/align]
[align=left] {[/align]
[align=left] mVideoWindow->put_Visible(OAFALSE);[/align]
[align=left] }[/align]
[align=left] return true;[/align]
[align=left] }[/align]
[align=left] return false;[/align]
}
上面这个很好理解,无非就是播放窗口的设置,除了上面设置的,还有全屏啊什么的设置,不一一列出。
再往下就是事件通知处理mFilterGraph->SetNotifyWindow(this->GetSafeHwnd());,即:
[align=left]bool CDXGraph::SetNotifyWindow(HWND inWindow)[/align]
[align=left]{[/align]
[align=left] if (mEvent)[/align]
[align=left] {[/align]
[align=left] mEvent->SetNotifyWindow((OAHWND)inWindow, WM_GRAPHNOTIFY, 0);[/align]
[align=left] return true;[/align]
[align=left] }[/align]
[align=left] return false;[/align]
}
然后先暂停(代码不再详细列出)mMediaControl->Pause(),当然做这些操作之前应该先获得状态,例如:
[align=left]bool CDXGraph::IsPaused(void)[/align]
[align=left]{[/align]
[align=left] if (mGraph && mMediaControl)[/align]
[align=left] {[/align]
[align=left] OAFilterState state = State_Stopped;[/align]
[align=left] if (SUCCEEDED(mMediaControl->GetState(10, &state)))[/align]
[align=left] {[/align]
[align=left] return state == State_Paused;[/align]
[align=left] }[/align]
[align=left] }[/align]
[align=left] return false;[/align]
}
打开文件就做这么多,至于播放,就是一个:mMediaControl->Run()。
下面不使用CDXGraph类来创建我们自己的工程,用vc创建MFC对话框工程MyPlayer2,和MyPlayer一样做好各个设置,为对话框头文件增加#include <streams.h>,引进CDXGraph类的过程不要,设置好界面,打开按钮不要,只要一个播放按钮,为播放按钮添加函数,精简的代码(省略了一些细节)如下:
[align=left]void CMyPlayer2Dlg::OnBnClickedButtonplay()[/align]
[align=left]{[/align]
[align=left] // TODO: Add your control notification handler code here[/align]
[align=left] IGraphBuilder * mGraph; [/align]
[align=left] IMediaControl * mMediaControl;[/align]
[align=left] IVideoWindow * mVWindow;[/align]
[align=left] [/align]
[align=left] CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC_SERVER, IID_IGraphBuilder, (void **)&mGraph);[/align]
[align=left] if (mGraph)[/align]
[align=left] {[/align]
[align=left] mGraph->QueryInterface(IID_IMediaControl, (void **)&mMediaControl);[/align]
[align=left] mGraph->QueryInterface(IID_IVideoWindow, (void **)&mVWindow);[/align]
[align=left] }[/align]
[align=left] mGraph->RenderFile(L"g://cctv00.mpg", NULL);[/align]
[align=left] [/align]
[align=left] HWND inWindow=mVideoWindow.GetSafeHwnd();[/align]
[align=left] if (mVWindow)[/align]
[align=left] {[/align]
[align=left] mVWindow->put_Visible(OAFALSE);[/align]
[align=left] mVWindow->put_Owner((OAHWND)inWindow);[/align]
[align=left] [/align]
[align=left] RECT windowRect;[/align]
[align=left] ::GetClientRect(inWindow, &windowRect);[/align]
[align=left] mVWindow->put_Left(0);[/align]
[align=left] mVWindow->put_Top(0);[/align]
[align=left] mVWindow->put_Width(windowRect.right - windowRect.left);[/align]
[align=left] mVWindow->put_Height(windowRect.bottom - windowRect.top);[/align]
[align=left] mVWindow->put_WindowStyle(WS_CHILD|WS_CLIPCHILDREN|WS_CLIPSIBLINGS);[/align]
[align=left] [/align]
[align=left] mVWindow->put_MessageDrain((OAHWND) inWindow);[/align]
[align=left] if (inWindow != NULL)[/align]
[align=left] {[/align]
[align=left] mVWindow->put_Visible(OATRUE);[/align]
[align=left] }[/align]
[align=left] else[/align]
[align=left] {[/align]
[align=left] mVWindow->put_Visible(OAFALSE);[/align]
[align=left] }[/align]
[align=left] [/align]
[align=left] }[/align]
[align=left] mMediaControl->Run();[/align]
}
其中大部分代码是窗口的设置,我们甚至连那个窗口也不要,仅仅在对话框中添加一个播放按钮:
[align=left]void CMyPlayer2Dlg::OnBnClickedButtonplay()[/align]
[align=left]{[/align]
[align=left] IGraphBuilder * mGraph; [/align]
[align=left] IMediaControl * mMediaControl;[/align]
[align=left] CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC_SERVER, IID_IGraphBuilder, (void **)&mGraph);[/align]
[align=left] mGraph->QueryInterface(IID_IMediaControl, (void **)&mMediaControl);[/align]
[align=left] mGraph->RenderFile(L"g://cctv00.mpg", NULL); [/align]
[align=left] mMediaControl->Run();[/align]
}
上面六行代码告诉了我们播放一个媒体文件的必需步骤:
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 视频渲染器被销毁,或者从过滤器图中移除.
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: