首    页 界面/窗口 网络/通讯 数据库 组件开发 图像/多媒体 NET/Web 其它技术 源码下载 资料下载 软件共享 软件外包 曲艺杂谈
栏目导航:  首    页  |  图像/多媒体  |  DirectX   


Visual C++.NET DirectShow编程


原作者:不详    源出处:yesky    发布者:施昌权    发布类型:转载    发布日期:2008-11-14

  

  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,如果你的程序中调用了此函数,则必须包含此库文件。

zmbbs=1; 开始工程

  打开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()
{
  // TODO: 在此添加专用代码和/或调用基类
  //关闭COM

  CoUninitialize();
  return CWinApp::ExitInstance();
}

6.定义媒体控制成员变量

   修改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()

  //和媒体控制相关的成员变量

private:

  IGraphBuilder *m_pGraph; //IGraphBuilder 接口提供了生成Filter Graph相//关的方法
  IMediaControl *m_pMediaControl;
  //IMediaControl 接口提供了控制流经Filter //Graph数据流的相关方法

  IMediaEventEx *m_pEvent;
  //IMediaEventEx 继承自IMediaEvent,提供了从
  //Filter Graph 管理器获取事件消息的方法

  IMediaSeeking *m_pMediaSeeking; //IMediaSeeking 提供了控制流的播放位置和播放//速度的方法
  CString m_strMediaFile; //当前播放的媒体文件的名称
  BOOL m_isPlaying; //当前的播放状态

};

   在CPlayWndDlg的构造函数中添加初始化代码。

CPlayWndDlg::CPlayWndDlg(CWnd* pParent /*=NULL*/)
: CDialog(CPlayWndDlg::IDD, pParent)

{
  m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);

  m_pGraph = NULL;
  m_pMediaControl = NULL;
  m_pEvent = NULL;
  m_pMediaSeeking = NULL;
  m_strMediaFile = "";
  m_isPlaying = FALSE;


}

   由于一些和窗体控制有关的初始化代码不能放在构造函数中进行,我们将其放在CPlayWndDlg::OnInitDialog()中,我们必须在此必须对CPlayWndDlg添加WS_CLIPCHILDREN 的Style,因为在我们的应用中把视频窗体作为CPlayWndDlg的一个子窗体来使用的,这是非常重要的,许多开发人员在刚开始使用DirectShow时,父窗体的Style没有设置正确,造成视频不能正确显示,代码如下:

// 设置此对话框的图标。当应用程序主窗口不是对话框时,框架将自动
// 执行此操作

SetIcon(m_hIcon, TRUE); // 设置大图标
SetIcon(m_hIcon, FALSE); // 设置小图标

// TODO: 在此添加额外的初始化代码

ModifyStyle(0, WS_CLIPCHILDREN);
((CEdit*)GetDlgItem(IDC_MEDIAFILE_EDIT))->SetReadOnly(TRUE);

return TRUE; // 除非设置了控件的焦点,否则返回 TRUE

}

   添加相应的清除代码,重载CPlayWndDlg的DestoryWindow方法,如下:

BOOL CPlayWndDlg::DestroyWindow()
{
  // TODO: 在此添加专用代码和/或调用基类
 
  if(m_pGraph)
   m_pGraph->Release();
  if(m_pMediaControl)
   m_pMediaControl->Release();
  if(m_pEvent)
   m_pEvent->Release();
  if(m_pMediaSeeking)
   m_pMediaSeeking->Release();

  m_pGraph = NULL;
  m_pMediaControl = NULL;
  m_pEvent = NULL;
  m_pMediaSeeking = NULL;

  return CDialog::DestroyWindow();

}

7.修改CPlayWndDlg::OnPaint(),由于现在视频显示区域必须由我们自己进行重画:

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;
}


关于我们 版权声明 广告服务 联系我们 友情链接 加入收藏
站长:施昌权    Email:scq2099yt@163.com    MSN:scq2099yt@live.cn    QQ:14046300    本站QQ群:67202409
Copyright © 2008     卓为VC(www.joyvc.cn)    All Rights Reserved    建议分辨率 1024×768
本站由施昌权制作维护
京ICP备09012297号
lpadding="0" cellspacing="0"> Copyright © 2008     卓为VC(www.joyvc.cn)    All Rights Reserved    建议分辨率 1024×768
本站由施昌权制作维护
京ICP备09012297号