DirectShow是微软公司提供的一套在Windows平台上进行流媒体处理的开发包,与DirectX开发包一起发布。DirectShow为多媒体的捕捉和回放提供了强有力的支持。运用DirectShow我们可以很方便地从支持WDM驱动模型的采集卡上捕获数据,并且进行相应的后期处理乃至存储到文件中。它广泛地支持各种媒体格式,包括Asf、Mpeg、Avi、Dv、Mp3、Wave等等,使得多媒体数据的回放变得轻而易举。 另外,DirectShow还集成了DirectX其它部分(比如DirectDraw、DirectSound)的技术,直接支持DVD播放,视频的非线性编辑,以及与数据摄像机的交换。更值得一提的是,DirectShow提供的是一种开放式的开发环境,我们可以根据自己的需要定制自己的组件。
本文将对DirectShow的应用进行入门级的介绍。
入门
DirectShow使用一种叫做Filter Graph的模型来管理整个数据流的处理过程,参与数据流处理的各个功能模块称做Filter,各个Filter在Filter Graph中按一定的顺序连成一条流水线协调工作,完成一些相对独立的功能,如Filter可以完成如下的一些功能:
读文件
从视频设备中获取视频
对视频流进行解码
将数据送往声卡或显卡
每个Filter都有输入端和输出端,例如一个MPEG-1解码Filter它的输入是MPEG编码的流数据,它的输出端是一解码过的流数据。DirectShow正是通过将不同的Filter连接在一起完成特定的功能的,我们将这些Filter的连接叫做Filter Graph,如下图A给出是播放AVI的Filter Graph:
图A 播放AVI文件的Graph Filter图
上图中每个模块分别代表了不同的Filter,媒体文件Filter从硬盘读取AVI文件,AVI分离Filter将文件分离为音频流和视频流,AVI解码Filter对视频流进行解码并送往Video表现Filter,由后者将各帧在显示器上显示,默认的DirectSound设备用DirectSound将音频流输出。
我们的应用并不需要对这当中的所以的数据流进行管理,在DirectShow提供一个称做Filter Graph管理器的高级组件。在我们的应用中只需要调用它的API即可,如Run、Stop等,如果你想对其中的数据流做更进一步的控制,你可以对这些Filter直接通过COM接口进行存取。
Filter Graph管理器同时也提供了另一个功能:应用程序可以通过管理器控制Filter Graph如何生成。
DirectShow应用
从广义上说,所有的DirectShow应用都必须完成三件事情,如下图B所示:
图B
1. 生成Filter Graph管理器的一个实例。
2. 利用Filter Grapth实例生成Filter Graph,具体应该由哪些Filter组成Filter Graph视我们的应用的需要而定。
3. 通过对Filter Graph管理器的方法调用和来自Filter Graph的消息的响应Filter Graph和数据流进行控制。
DirectShow是基于COM的,Filter Graph管理器和Filter都是COM对象 ,在开始着手之前你应该对COM有个基本的认识。
下面让我们着手开始做一个简单的DirectShow应用,在这个应用中我们实现这样的功能:打开一个媒体文件,并对其进行播放。
设置环境
在利用DirectShow进行流媒体的处理之前,必须正确安装DirectX的SDK,DirectX SDK可以到微软的网站上下载,目前,DirectX最新版本为9.0。
在正确安装好DirectX SDK后,我们必须设置DirectX SDK的头文件和库文件,使其在Visual Studio的搜索路径内。对于Visual Studio .NET 2003可如下进行设置:菜单→工具→选项→项目→VC++目录,在包含文件中加入D:/DXSDK/Include,在库文件中加入D:/DXSDK/lib(我的SDK的安装路径是D:/DXSDK):
头文件
文件名称 | 描述 |
Dshow.h | 所有的DirectShow应用都必须包含 |
库文件
文件名称 | 描述 |
Strmiids.lib | 此库文件中导出类标识(CLSID)和接口标识(IID),所有的DirectShow应用都必须包含此文件。 |
Quartz.lib | 此库文件中导出函数AMGetErrorText,如果你的程序中调用了此函数,则必须包含此库文件。 |
<script type="text/javascript">zmbbs=1;</script> 开始工程
打开Visual Studio .NET 2003,文件→新建→项目
1.生成基于MFC的应用程序,名称PlayWnd。
2.选择应用程序类型基于对话框,点击完成。
3.设置工程属性
项目→PlayWnd属性→配置属性→链接器→输入→附加依赖项,添加库文件Strmiids.lib 和Quartz.lib。
由于Dshow.h头文件是在任何DirectShow工程中都要用到的,因此我们stdafx.h加入如下行:
#include <Dshow.h> |
4.设计对话框,如下:
IDC_STATIC | 控件类型 | Static Text |
Caption | 媒体文件名: | |
IDC_MEDIAFILE_EDIT | 控件类型 | Edit Control |
IDC_BROWSE_BUTTON | 控件类型 | Button |
Caption | 浏览 | |
IDC_VW_FRAME | 控件类型 | Picture Control |
Type | Rectangle | |
IDC_PLAY_BUTTON | 控件类型 | Button |
Caption | 播放 | |
IDC_PAUSE_BUTTON | 控件类型 | Button |
Caption | 暂停 | |
IDCANCEL | 控件类型 | Button |
Caption | 关闭 |
5.COM的初始化和卸载,
修改PlayWnd.cpp添加初始化代码(加入的代码用粗黑体表示,下同)
BOOL CPlayWndApp::InitInstance() { // 如果一个运行在 Windows XP 上的应用程序清单指定要 // 使用 ComCtl32.dll 版本 6 或更高版本来启用可视化方式, //则需要 InitCommonControls()。否则,将无法创建窗口。 InitCommonControls(); //初始化COM接口 HRESULT hr = CoInitialize(NULL); if (FAILED(hr)) { TRACE("ERROR - Could not initialize COM library./n"); return FALSE; } CWinApp::InitInstance(); AfxEnableControlContainer(); |
修改PlayWnd.cpp添加卸载COM代码,注意需要对虚函数ExitInstance进行重载
int CPlayFileApp::ExitInstance() 6.定义媒体控制成员变量
在CPlayWndDlg的构造函数中添加初始化代码。
由于一些和窗体控制有关的初始化代码不能放在构造函数中进行,我们将其放在CPlayWndDlg::OnInitDialog()中,我们必须在此必须对CPlayWndDlg添加WS_CLIPCHILDREN 的Style,因为在我们的应用中把视频窗体作为CPlayWndDlg的一个子窗体来使用的,这是非常重要的,许多开发人员在刚开始使用DirectShow时,父窗体的Style没有设置正确,造成视频不能正确显示,代码如下:
添加相应的清除代码,重载CPlayWndDlg的DestoryWindow方法,如下:
|
void CPlayWndDlg::OnPaint() { if (IsIconic()) { CPaintDC dc(this); // 用于绘制的设备上下文 SendMessage(WM_ICONERASEBKGND, reinterpret_cast<WPARAM>(dc.GetSafeHdc()), 0); // 使图标在工作矩形中居中 int cxIcon = GetSystemMetrics(SM_CXICON); int cyIcon = GetSystemMetrics(SM_CYICON); CRect rect; GetClientRect(&rect); int x = (rect.Width() - cxIcon + 1) / 2; int y = (rect.Height() - cyIcon + 1) / 2; // 绘制图标 dc.DrawIcon(x, y, m_hIcon); } else { if(m_isPlaying == FALSE) { CClientDC dc(GetDlgItem(IDC_VW_FRAME)); dc.SetBkColor(RGB(0,0,0)); CRect rc; GetDlgItem(IDC_VW_FRAME)->GetClientRect(rc); //ClientToScreen(rc); dc.FillRect(rc, CBrush::FromHandle((HBRUSH)GetStockObject(BLACK_BRUSH))); GetDlgItem(IDC_VW_FRAME)->Invalidate(); } CDialog::OnPaint(); } } |
添加浏览、播放、暂停、关闭四按钮的相应事件响应函数,同时在CPlayWndDlg中添加如下四个私有方法:
void MoveVideoWindow(void); void CleanUp(void); BOOL Stop(void); BOOL Play(void); |
上述方法的实现如下:
// IDC_VW_FRAME控件Picture Control主要作用是控制Vedio Window的显示位置 void CPlayWndDlg::MoveVideoWindow(void) { IVideoWindow* pVideoWinow = NULL; if(m_pGraph) { m_pGraph->QueryInterface(IID_IVideoWindow, (void **)&pVideoWinow); CRect rc; GetDlgItem(IDC_VW_FRAME)->GetWindowRect(rc); ScreenToClient(rc); pVideoWinow->SetWindowPosition(rc.left, rc.top, rc.Width(), rc.Height()); pVideoWinow->Release(); pVideoWinow = NULL; } } void CPlayWndDlg::CleanUp(void) { long levCode; IVideoWindow *pVidWin = NULL; if(!m_pGraph) return; m_pGraph->QueryInterface(IID_IVideoWindow, (void **)&pVidWin); m_pEvent->WaitForCompletion(INFINITE, &levCode); pVidWin->put_Visible(OAFALSE); pVidWin->Release(); m_pMediaSeeking->Release(); m_pMediaControl->Release(); m_pEvent->Release(); m_pGraph->Release(); m_pMediaSeeking = NULL; m_pMediaControl = NULL; m_pEvent = NULL; m_pGraph = NULL; UpdateData(FALSE); CClientDC dc(GetDlgItem(IDC_VW_FRAME)); dc.SetBkColor(RGB(0,0,0)); CRect rc; GetDlgItem(IDC_VW_FRAME)->GetClientRect(rc); ClientToScreen(rc); dc.FillRect(rc, CBrush::FromHandle((HBRUSH)GetStockObject(BLACK_BRUSH))); Invalidate(); } BOOL CPlayWndDlg::Stop(void) { IVideoWindow *pVidWin = NULL; HRESULT hr; if(m_pMediaControl) { LONGLONG pos = 0; hr = m_pMediaControl->Stop(); hr = m_pMediaSeeking->SetPositions(&pos, AM_SEEKING_AbsolutePositioning ,&pos, AM_SEEKING_NoPositioning); m_pGraph->QueryInterface(IID_IVideoWindow, (void **)&pVidWin); pVidWin->put_Visible(OAFALSE); m_isPlaying = FALSE; GetDlgItem(IDC_PLAY_BUTTON)->EnableWindow(TRUE); GetDlgItem(IDC_PAUSE_BUTTON)->EnableWindow(FALSE); pVidWin->Release(); long levCode; m_pEvent->WaitForCompletion(INFINITE, &levCode); m_pMediaControl->Release(); return TRUE; } return FALSE; } BOOL CPlayWndDlg::Play(void) { // 运行 IVideoWindow *pVidWin = NULL; if(m_pGraph) { m_pGraph->QueryInterface(IID_IVideoWindow, (void **)&pVidWin); pVidWin->put_Visible(OATRUE); m_pGraph->QueryInterface(IID_IMediaControl, (void **)&m_pMediaControl); m_pMediaControl->Run(); m_isPlaying = TRUE; GetDlgItem(IDC_PLAY_BUTTON)->EnableWindow(FALSE); GetDlgItem(IDC_PAUSE_BUTTON)->EnableWindow(TRUE); return TRUE; } return FALSE; } |
void CPlayWndDlg::OnBnClickedBrowseButton() { CFileDialog dlgFile(TRUE, NULL, NULL, OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT, "Movie Files (*.avi;*.mpg;*.mpeg) |/ *.avi;*.mpg;*.mpeg |/ Audio Files (*.wav;*mp3;*.mpa;*.mpu;*.au) |/ *.wav;*.mp3;*.mpa;*.mpu;*.au |/ Midi Files (*.mid;*.midi;*.rmi) |/ *.mid;*.midi;*.rmi| | ", this); if(dlgFile.DoModal() == IDOK) { m_strMediaFile = dlgFile.GetPathName(); GetDlgItem(IDC_MEDIAFILE_EDIT)->SetWindowText(m_strMediaFile); } else return; CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC, IID_IGraphBuilder, (void **)&m_pGraph); HRESULT hr = m_pGraph->RenderFile(CA2W(m_strMediaFile), NULL); if(FAILED(hr)) { char szMsg[200]; AMGetErrorText(hr, szMsg, sizeof(szMsg)); AfxMessageBox(szMsg); } //指定父窗体 IVideoWindow* pVidWin = NULL; m_pGraph->QueryInterface(IID_IVideoWindow, (void **)&pVidWin); pVidWin->put_Owner((OAHWND)m_hWnd); pVidWin->put_WindowStyle(WS_CHILD | WS_CLIPSIBLINGS | WS_CLIPCHILDREN); CRect rc; GetDlgItem(IDC_VW_FRAME)->GetWindowRect(rc); ScreenToClient(rc); pVidWin->SetWindowPosition(rc.left, rc.top, rc.Width(), rc.Height()); // 注意此处Filter Graph Manager的事件以WM_GRAPHNOTIFY发出(用户定义的消息). m_pGraph->QueryInterface(IID_IMediaEventEx, (void **)&m_pEvent); m_pEvent->SetNotifyWindow((OAHWND)m_hWnd, WM_GRAPHNOTIFY, 0); // 设置Seeking m_pGraph->QueryInterface(IID_IMediaSeeking, (void **)&m_pMediaSeeking); } void CPlayWndDlg::OnBnClickedPlayButton() { Play(); } void CPlayWndDlg::OnBnClickedPauseButton() { m_pMediaControl->Pause(); m_isPlaying = TRUE; GetDlgItem(IDC_PLAY_BUTTON)->EnableWindow(TRUE); GetDlgItem(IDC_PAUSE_BUTTON)->EnableWindow(FALSE); } void CPlayWndDlg::OnBnClickedCancel() { // TODO: 在此添加控件通知处理程序代码 CleanUp(); OnCancel(); } |
8.添加对WM_GRAPHNOTIFY消息,及其响应函数
在PlayWndDlg添加消息ID定义:
#define WM_GRAPHNOTIFY WM_USER + 101 |
在PlayWndDlg.h中,代码如下:
// 实现 protected: HICON m_hIcon; // 生成的消息映射函数 virtual BOOL OnInitDialog(); afx_msg void OnSysCommand(UINT nID, LPARAM lParam); afx_msg void OnPaint(); afx_msg HCURSOR OnQueryDragIcon(); DECLARE_MESSAGE_MAP() afx_msg HRESULT OnGraphNotify(WPARAM wParam,LPARAM lParam); private: IGraphBuilder *m_pGraph; IMediaControl *m_pMediaControl; IMediaEventEx *m_pEvent; |
PlayWndDlg.cpp,如下:
ON_BN_CLICKED(IDC_BROWSE_BUTTON, OnBnClickedBrowseButton) ON_BN_CLICKED(IDC_PLAY_BUTTON, OnBnClickedPlayButton) ON_BN_CLICKED(IDC_PAUSE_BUTTON, OnBnClickedPauseButton) ON_BN_CLICKED(IDCANCEL, OnBnClickedCancel) ON_MESSAGE(WM_GRAPHNOTIFY, OnGraphNotify) END_MESSAGE_MAP() |
实现如下:
HRESULT CPlayWndDlg::OnGraphNotify(WPARAM wParam,LPARAM lParam) { long levCode, lparam1, lparam2; HRESULT hr; while (hr = m_pEvent->GetEvent(&levCode, &lparam1, &lparam2, 0), SUCCEEDED(hr)) { hr = m_pEvent->FreeEventParams(levCode, lparam1, lparam2); if ((EC_COMPLETE == levCode) || (EC_USERABORT == levCode)) { TRACE("End of the media file!!./n"); Stop(); //CleanUp(); break; } } return hr; } |