在C#中用DirectShow做的媒体播放机

概述: 我的这个程序仅仅只是告诉大家如何用DirectShow 在C#中做一个播放机,世上并不能 有太多的功能.也许你只要花上五分种就可以解决问题.是的如果你用的是IDE,我感保证 一切都只是用你的鼠标在你的设计器中点点属性设置一些东西就可以简单的完成了.当然 了还是要那么一点点编码的.至少是关于DirectShow 接口的.例如,视屏和声音.
程序中的小问题: 1.如何从你的磁盘上打开媒体文件 2.如何让工具条上的按钮起用和禁用 3.如何设置状态栏的显示文字 4.如何控制时间 5.如何使用时间控件的事件 6.如何用DirectShow来播放媒体文件 7.如何确定播放状态等等... ...
用户界面如下图: 很简单是吗?是的,我说了是个简单的程序,我只是让它具备了基本功能而已.工具栏上 的三个button控制播放,停止和暂停.一个文件菜单用来打开媒体文件和关闭程序.当然 还有一个介绍程序的Info毫无疑问这是最基本的界面配置.


下面介绍DirectShow 接口
播放视屏和声音文件我们要用到DiectX为我们提供的DirectShow组件.使用这个接口 可以让你方便的播放那些共用的影像和声音文件.你要做的仅仅只是安装DirectShow接口 和使用它的功能函数和配置正确的接口参数而已.

不幸的是.NET并不正式支持DirectX.是的也许你听说DirectX9支持是吗?是的,不过在 最终版敲定的那一天还没来,我们都得不到最好的效果.但无论如何我们还是要用的不是吗? 要不这篇文章得作废了.是的,也许你用过VB,对了,就是它,我们正是要用到那个.
好了,在此之前我们还必须要做件事情.我想你已经猜到了,引用对吗?还记得XCopy吗?是的 .NET的优势.来吧,快点把这个"Interop.QuartzTypeLib.dll"DLL引用进来,就象这样 看见下面的图示了吗?很简单.

最后别忘了看看你的代码中是否有那么一句 using QuartzTypeLib; 有是吗?编译器为你加的,如果没有自己加上好了.
准备工作结束了,该是代码部分了,这可是程序员无法推卸的责任.否则我们都得下岗了.
程序实现部分:
如何打开你想要媒体文件?
还记得吗?"File -> Open..." 是的几乎每个使用windows的人都会这样操作.如何实现? 很简单看看下面的代码:
OpenFileDialog openFileDialog = new OpenFileDialog();
openFileDialog.Filter = "Media Files|*.mpg;*.avi;*.wma;*.mov;*.wav;*.mp2;*.mp3|All Files|*.*";
if (DialogResult.OK == openFileDialog.ShowDialog()) { . . . 看吧很简单是吗?记得写一个函数把它放进去.当你点击OK按钮的时候.DirectShow接口就会 得到你想要播放的文件. 看下面的图你就知道它是如何工作的.
DirectShow为多媒体流回放提供最基本的服务,这些多媒体流可以是本地文件,还可以是服务器传输过来的。特别的,DirectShow可以支持视频回放,支持以不同的文件和流格式压缩视频内容,包括Windows Media、MPEG、AVI和WAV。
在DirectShow的核心处,服务是组件的模块化集合,称为过滤器,可以根据媒体类型排列成过滤器图。过滤器可以操作数据流,如读入、分析、解码、格式化或渲染。
过滤器以树型进行排列,这棵树称为过滤器树,通过过滤器树管理器(Filter Graph Manager,简称FGM)进行管理。使用FGM应用程序可以通过使用Microsoft Windows Media Player控件间接控制过滤器树,还可以通过调用COM接口方法直接控制。DirectShow过滤器树(参阅图1)由从源到目标渲染器的有向过滤器序列组成,所有这些通过输入和输出过滤器引脚连接。过滤器引脚协商它们将支持哪些媒体类型。FGM控制树过滤器之间的多媒体数据流。因为DirectShow有一个灵活的、可重配置的过滤器树体系结构,因此DirectShow可以使用同样的软件成分支持多种媒体类型的回放和分流。开发人员还可以通过编写自己的过滤器扩展DirectShow多媒体支持。
图1. DirectShow过滤器树

过滤器 过滤器是注册的DirectShow类,它执行许多媒体信息处理任务。这些任务包括:
获得源信息(例如,获得媒体流) 分析(例如,在流上执行包读入、分离和格式化) 转换(例如,解码WMA和MPEG-4音频和视频流) 渲染(例如,在适当的时候产生音频PCM或者视频RGB/YUV输出,将数据传给DirectSound和DirectDraw) 过滤器使用几种类型的接口,例如引脚、计数器、传送器和时钟接口,来执行它们的任务。过滤器实现和开放了许多接口。FGM可以使用这些接口创建、连接和控制树。过滤器经常实现包含下列方法的IBaseFilter接口:
运行、停止和暂停过滤器状态。 恢复过滤器和厂商信息。 得到和设置参考时钟。 恢复过滤器状态信息。 枚举过滤器引线。 重建过滤器树时定位引脚
好了介绍了这么多,你的手也许已经闲不住了.看看下面的代码是如何实现的
CleanUp();
m_objFilterGraph = new FilgraphManager(); m_objFilterGraph.RenderFile(openFileDialog.FileName);
m_objBasicAudio = m_objFilterGraph as IBasicAudio;
try { m_objVideoWindow = m_objFilterGraph as IVideoWindow; m_objVideoWindow.Owner = (int) panel1.Handle; m_objVideoWindow.WindowStyle = WS_CHILD | WS_CLIPCHILDREN; m_objVideoWindow.SetWindowPosition(panel1.ClientRectangle.Left, panel1.ClientRectangle.Top, panel1.ClientRectangle.Width, panel1.ClientRectangle.Height); } catch (Exception ex) { m_objVideoWindow = null; }
m_objMediaEvent = m_objFilterGraph as IMediaEvent;
m_objMediaEventEx = m_objFilterGraph as IMediaEventEx; m_objMediaEventEx.SetNotifyWindow((int) this.Handle, WM_GRAPHNOTIFY, 0);
m_objMediaPosition = m_objFilterGraph as IMediaPosition;
m_objMediaControl = m_objFilterGraph as IMediaControl;
//
如何来播放,暂停,停止? 简单这些函数看字面也知道.
m_objMediaControl.Run();//播放
m_objMediaControl.Pause();//暂停
m_objMediaControl.Stop();//停止
OK,在来看我们是如何控制时间进度的? // private void timer1_Tick(object sender, System.EventArgs e) { if (m_CurrentStatus == MediaStatus.Running) { UpdateStatusBar(); } }
看见上面那个 UpdateStatusBar();这里是让它没100ms更新一次状态栏. 代码如下: private void UpdateStatusBar() { switch (m_CurrentStatus) { case MediaStatus.None : statusBarPanel1.Text = "Stopped"; break; case MediaStatus.Paused : statusBarPanel1.Text = "Paused "; break; case MediaStatus.Running: statusBarPanel1.Text = "Running"; break; case MediaStatus.Stopped: statusBarPanel1.Text = "Stopped"; break; }
if (m_objMediaPosition != null) { int s = (int) m_objMediaPosition.Duration; int h = s / 3600; int m = (s - (h * 3600)) / 60; s = s - (h * 3600 + m * 60);
statusBarPanel2.Text = String.Format("{0:D2}:{1:D2}:{2:D2}", h, m, s);
s = (int) m_objMediaPosition.CurrentPosition; h = s / 3600; m = (s - (h * 3600)) / 60; s = s - (h * 3600 + m * 60);
statusBarPanel3.Text = String.Format("{0:D2}:{1:D2}:{2:D2}", h, m, s); } else { statusBarPanel2.Text = "00:00:00"; statusBarPanel3.Text = "00:00:00"; } }
还有一个问题程序怎么能够知道它播放完了???? 这会有点麻烦了.好了,想想看有什么办法呢?对了,windows是消息驱动的.那找找看有什么消息 好了.有的就EC_COMPLETE 这个.还记得"WndProc" 它吗??是的,我的老朋友.这次我们必须 要改写它来捕获EC_COMPLETE消息.这个消息是DirectShow通知父窗体,播放结束了.
protected override void WndProc(ref Message m) { if (m.Msg == WM_GRAPHNOTIFY) { int lEventCode; int lParam1, lParam2;
while (true) { try { m_objMediaEventEx.GetEvent(out lEventCode, out lParam1, out lParam2, 0);
m_objMediaEventEx.FreeEventParams(lEventCode, lParam1, lParam2);
if (lEventCode == EC_COMPLETE) { m_objMediaControl.Stop(); m_objMediaPosition.CurrentPosition = 0; m_CurrentStatus = MediaStatus.Stopped; UpdateStatusBar(); UpdateToolBar(); } } catch (Exception) { break; } } }
base.WndProc(ref m); } 好了,一切都结束了,现在要做的事就是做些来找一部影片来享受一下自己的成果了. 
|