C#中的DirectShow MediaPlayer






4.69/5 (95投票s)
2002 年 7 月 26 日
6分钟阅读

1688854

44990
本文演示了如何在 C# Windows 应用程序中播放媒体文件。
引言
由于这是我在 The Code Project 上的第一篇文章,我想为我糟糕的英语表示歉意。它只基于一些“学校英语”和我在教育期间作为滑雪教练和漂流向导的休闲工作中听到的一些词语。
我希望每个人都能理解这篇文章。如果仍然存在疑问,我很乐意回答。如果有人发现错误,请将错误文本的正确版本发送给我——感谢您在提高我的英语知识方面的帮助 ;-)。
那么,让我们开始吧...
这个小示例程序展示了使用 DirectShow 和 C# 播放视频或音频轨道是多么简单。
该程序使用以下控件:MainMenu、ToolBar、StatusBar、Panel、ImageList 和 Timer。
许多属性是使用设计器设置的,所以我建议您下载该项目,当然,除非您不是初学者。
另一方面,示例程序包含 DirectShow 接口来播放视频或音频轨道。
该程序演示了以下内容
- 如何使用 OpenFileDialog 实例类在磁盘上选择媒体文件。
- 如何启用或禁用 ToolBar 控件的按钮。
- 如何更改 StatusBar 控件中的文本。
- 如何使用基本计时器。
- 如何使用 Timer 控件的事件。
- 如何使用 MainMenu 控件的事件。
- 如何使用 ToolBar 控件的事件。
- 如何使用 Windows 窗体的事件。
- 如何使用 DirectShow 播放媒体文件。
- 如何确定媒体文件是否已播放完毕。
- ...
用户界面
除了播放和停止视频或音频轨道的 3 个按钮外,还有一个菜单选项可以选择所需的媒体文件。因此,在播放所需的媒体文件之前,您必须使用 MainMenu 控件中的“文件 -> 打开...”命令打开此文件。如果文件已正确加载,现在可以使用 ToolBar 控件中的“播放”按钮进行播放。在播放视频或音频轨道期间,应用程序会在 StatusBar 控件中显示媒体文件的当前位置和持续时间。如果媒体文件播放完毕,您可以使用“播放”按钮重新开始播放媒体文件。要选择另一个媒体文件,您可以使用 MainMenu 控件中的“文件 -> 打开...”命令。
使用 MainMenu 控件的“信息”命令将显示应用程序的信息对话框。
DirectShow 接口
要播放视频或音频文件,我们使用 DirectX 包的 DirectShow 组件。使用 DirectShow 接口播放视频或音频文件非常简单,大部分工作都由接口为您完成。您只需设置接口并使用正确的参数调用接口方法。
不幸的是,.NET 和 C# 并不是 DirectX 开发的官方支持平台,直到 DirectX 9 在今年年底发布。然而,在此期间我们可以使用 C# 与 DirectX,方法是使用 API 版本 7 和 8 随附的 Visual Basic 类型库。本文演示了如何在 C# 中使用 DirectX VB 类型库。
在开始 .NET DirectShow 应用程序之前,我们需要创建对 DirectShow COM DLL 的引用。因此,将 "Interop.QuartzTypeLib.dll" DLL 复制到您的项目文件夹中,并添加对此 DLL 的引用。在 Visual Studio.NET 中,通过从项目菜单中选择“添加引用”来完成此操作。接下来选择“添加引用”对话框的“浏览...”按钮并选择 DirectShow COM DLL。
因此,在创建对 DirectShow COM DLL 的引用后,将以下代码行添加到您的项目中,以使 DirectShow 接口类可见。
using QuartzTypeLib;
代码
如何选择媒体文件并创建 DirectShow 接口?
用户按下 MainMenu 控件的“文件 -> 打开...”命令后,会显示一个“打开文件”对话框,用户可以选择所需的媒体文件。在 C# 中,通过创建 `OpenFileDialog` 类的实例并调用 `ShowDialog()` 函数来完成此操作。
OpenFileDialog openFileDialog = new OpenFileDialog();
openFileDialog.Filter = "Media Files|*.mpg;*.avi;*.wma;*.mov;" +
"*.wav;*.mp2;*.mp3|All Files|*.*";
if (DialogResult.OK == openFileDialog.ShowDialog())
{
.
.
.
用户以 OK 终止对话框后,我们开始创建 DirectShow 接口并渲染选定的媒体文件。
这分三步完成
- 创建过滤器图管理器 (FGM)
- 创建过滤器图(通过 FGM)
- 运行图并响应事件
在以下代码中,您可以看到如何创建过滤器图管理器和过滤器图
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;
使用 `CleanUp()` 方法,我们尝试删除旧接口(如果存在)。在开始渲染文件之前,我们必须使用 `new` 方法创建 FilterGraphManager 的新实例。`RenderFile()` 方法构建一个渲染指定文件的过滤器图。`IBasicAudio` 接口用于设置声音的音量和平衡。使用 `IVideoWindow` 接口,您可以设置窗口样式、所有者窗口和视频窗口的位置。这些函数被 `try` 包含起来,因为如果您渲染音频文件并尝试设置所有者窗口,该方法会抛出异常。因此,要播放音频轨道,我们不需要 `IVideoWindow` 接口,我们将 `m_objVideoWindow` 成员设置为 `null`。`IMediaEvent` 和 `IMediaEventEx` 接口用于截获 DirectShow 发送给父窗口的消息。使用 `IMediaPosition` 接口可以确定文件的当前位置。要启动和停止视频或音频轨道,我们使用 `IMediaControl` 接口。
有关接口的更多信息,请阅读 MSDN 上的 DirectShow 文档。
如何播放媒体文件?
要启动视频或音频轨道,请使用 `IMediaControl` 接口的 `Run()` 方法。
m_objMediaControl.Run();
如何暂停媒体文件?
如果您想暂停播放视频或音频轨道,只需使用 `IMediaControl` 接口的 `Pause()` 方法。
m_objMediaControl.Pause();
如何停止媒体文件?
要停止视频或音频轨道,请使用 `IMediaControl` 接口的 `Stop()` 方法。
m_objMediaControl.Stop();
如何获取媒体文件的位置和持续时间?
当媒体文件播放时,我们在 StatusBar 控件中显示文件的当前位置和长度。此外,我们每 100 毫秒通过计时器读取 `IMediaPosition` 接口的 `CurrentPosition` 成员,并在状态栏中显示其值。为了获取文件的长度,我们读取 `IMediaPosition` 接口的 `Duration` 成员。
private void timer1_Tick(object sender, System.EventArgs e)
{
if (m_CurrentStatus == MediaStatus.Running)
{
UpdateStatusBar();
}
}
计时器函数每 100 毫秒调用一次 `UpdateStatusBar()` 方法,该方法在状态栏的面板中显示媒体文件的当前位置和持续时间。
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";
}
}
何时到达文件末尾?
为了确定文件何时播放完毕,我们重写了 `WndProc` 方法,以截获当文件末尾到达时 DirectShow 发送给父窗口的 `EC_COMPLETE` 消息。
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);
}
历史
- 2002年7月19日 - 发布(第一版)
- 2002年7月26日 - 设计和代码进行了一些更改
链接
- 使用 DirectShow 的基础知识
- DirectShow 简介 / 简单回放
- 使用 DirectShow
- DirectShow 过滤器
- VBDemo 示例
- Windows 中的 DirectShow 媒体播放 - 教程 I
- Windows 中的 DirectShow 媒体播放 - 教程 II
- Windows 中的 DirectShow 媒体播放 - 教程 III
错误和评论
如果您有任何意见或发现任何错误,我很乐意听取并改进它。