这是一个基于对话框的程序。源代码参照了微软的directshow 的例子程序和一本英文介绍如何编写directshow程序的书里面的程序代码。
首先,因为要播放视频流,所以,要在对话框上给视频流的播放留个地方,这样就先弄个picture control控件到对话框上,将其ID设为IDC_VIDEO_WINDOW,并且让它的type是Rectangle,并且让一个类型为CStatic的名为m_VideoWindow的变量和它相关联,这样,就可以通过m_VideoWindow控制IDC_VIDEO_WINDOW了。也就是:
void CplayMediaDlg::DoDataExchange(CDataExchange* pDX)
{
CDialog::DoDataExchange(pDX);
DDX_Control(pDX, IDC_VIDEO_WINDOW, m_VideoWindow);
}
这可以通过用classvizard添加代码的方式实现,也可以自己手工添加代码。
由于directshow用到了com技术,所以,即使仅要编写directshow的应用程序,也要对组件对象模型有些了解。
通过自动的方式播放视频流和音频流是比较简单的,下面是一些源代码,并且其中会有些讲解。
// playMediaDlg.h : 头文件
//
#pragma once
#include "afxwin.h"
#include <Dshow.h>
#pragma comment(lib,"Strmiids.lib")
// CplayMediaDlg 对话框
class CplayMediaDlg : public CDialog
{
// 构造
public:
CplayMediaDlg(CWnd* pParent = NULL); // 标准构造函数
// 对话框数据
enum { IDD = IDD_PLAYMEDIA_DIALOG };
protected:
virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV 支持
// 实现
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()
public:
CStatic m_VideoWindow;
afx_msg BOOL OnEraseBkgnd(CDC* pDC);
afx_msg void OnBnClickedOpen();
// Graph builder interface,这是用于控制对filter graph创建等等的接口,其中有相应的函数
IGraphBuilder* pGraph;
// Media control interface,这是用于控制流的run,stop,pause的接口,里面有相应的函数
IMediaControl* pControl;
afx_msg void OnBnClickedPlay();
afx_msg void OnBnClickedPause();
afx_msg void OnBnClickedStop();
afx_msg void OnClose();
// 用于控制显示在什么地方的接口,要把视频流显示出来就要用到这个接口中的函数
IVideoWindow* m_pVidWin;
// 记录按下了几次pause按钮
int m_pausePress;
afx_msg void OnBnClickedSave();
};
在playMediaDlg.cpp中是具体的函数代码。
// playMediaDlg.cpp---------------------------------------
CplayMediaDlg::CplayMediaDlg(CWnd* pParent /*=NULL*/)
: CDialog(CplayMediaDlg::IDD, pParent)
, pGraph(NULL)
, pControl(NULL)
, m_pVidWin(NULL)
, m_pausePress(0)
{
m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
}
BOOL CplayMediaDlg::OnInitDialog()
{
CDialog::OnInitDialog();
// 将/“关于.../”菜单项添加到系统菜单中。
// IDM_ABOUTBOX 必须在系统命令范围内。
ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX);
ASSERT(IDM_ABOUTBOX < 0xF000);
CMenu* pSysMenu = GetSystemMenu(FALSE);
if (pSysMenu != NULL)
{
CString strAboutMenu;
strAboutMenu.LoadString(IDS_ABOUTBOX);
if (!strAboutMenu.IsEmpty())
{
pSysMenu->AppendMenu(MF_SEPARATOR);
pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu);
}
}
// 设置此对话框的图标。当应用程序主窗口不是对话框时,框架将自动
// 执行此操作
SetIcon(m_hIcon, TRUE); // 设置大图标
SetIcon(m_hIcon, FALSE); // 设置小图标
// TODO: 在此添加额外的初始化代码
// Since we're embedding video in a child window of a dialog,
// we must set the WS_CLIPCHILDREN style to prevent the bounding
// rectangle from drawing over our video frames.
//
// Neglecting to set this style can lead to situations when the video
// is erased and replaced with the default color of the bounding rectangle.
m_VideoWindow.ModifyStyle(0, WS_CLIPCHILDREN);
// Initialize the COM library.
HRESULT hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
if (FAILED(hr))
{
AfxMessageBox(TEXT("cannot initialize the com"));
}
//定义一些接口
// Create the Filter Graph Manager and query for interfaces.
//用CoCreateInstance也是一种获得接口的方法
hr = CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC_SERVER,
IID_IGraphBuilder, (void **)&pGraph);
if(FAILED(hr))
{
AfxMessageBox(TEXT("cannot create filter graph manager"));
}
// Use IGraphBuilder::QueryInterface (inherited from IUnknown)
// to get the IMediaControl interface.
hr = pGraph->QueryInterface(IID_IMediaControl, (void **)&pControl);
if(FAILED(hr))
{
AfxMessageBox(TEXT("Can not obtain the media control interface"));
pGraph->Release();
pGraph=NULL;
}
//for display
hr = pGraph->QueryInterface(IID_IVideoWindow, (void**)&m_pVidWin);
if(FAILED(hr))
{
AfxMessageBox(TEXT("can not obtain the video window interface"));
pControl->Release();
pControl=NULL;
pGraph->Release();
pGraph=NULL;
}
return TRUE; // 除非设置了控件的焦点,否则返回 TRUE
}
上面的函数其实也就是在创建对话框的时候获得接口,下面的函数是打开文件(音频或者视频文件)进行播放的函数。
void CplayMediaDlg::OnBnClickedOpen()
{
// TODO: 在此添加控件通知处理程序代码
//建立filter graph
HRESULT hr;
CFileDialog dlg(TRUE);
if (dlg.DoModal()==IDOK)
{
// To build the filter graph, only one call is required.
// We make the RenderFile call to the Filter Graph Manager
// to which we pass the name of the media file.
//下面的几行转换很重要,其实也就是把ASCII码转换成Unicode
WCHAR wFileName[MAX_PATH];
MultiByteToWideChar(CP_ACP, 0, dlg.GetPathName(), -1, wFileName,
MAX_PATH);
//这里其实就是让directshow自动根据要播放的文件的格式,生成filter graph
hr=pGraph->RenderFile((LPCWSTR)wFileName,NULL);
if(FAILED(hr))
{
AfxMessageBox(TEXT("can not open the file"));
}
//用于设置显示的位置的代码,这里就是进行显示的设置
HRESULT hr = m_pVidWin->put_Owner((OAHWND) m_VideoWindow.GetSafeHwnd());
if (SUCCEEDED(hr))
{
// The video window must have the WS_CHILD style
hr = m_pVidWin->put_WindowStyle(WS_CHILD);
// Read coordinates of video container window
RECT rc;
m_VideoWindow.GetClientRect(&rc);
long width = rc.right - rc.left;
long height = rc.bottom - rc.top;
// Ignore the video's original size and stretch to fit bounding rectangle
hr = m_pVidWin->SetWindowPosition(rc.left, rc.top, width, height);
m_pVidWin->put_Visible(OATRUE);
}
}
}
下面的三个函数是为一个按钮控件添加的消息响应函数
void CplayMediaDlg::OnBnClickedPlay()
{
// TODO: 在此添加控件通知处理程序代码
//run the graph
HRESULT hr=pControl->Run();
}
void CplayMediaDlg::OnBnClickedPause()
{
// TODO: 在此添加控件通知处理程序代码
HRESULT hr=pControl->Pause();
if(FAILED(hr))
{
AfxMessageBox(TEXT("can not pause the filter graph"));
}
}
void CplayMediaDlg::OnBnClickedStop()
{
// TODO: 在此添加控件通知处理程序代码
HRESULT hr=pControl->Stop();
if(FAILED(hr))
{
AfxMessageBox(TEXT("can not stop the filter graph"));
}
}
当对话框关闭时,要进行些收尾的工作
void CplayMediaDlg::OnClose()
{
// TODO: 在此添加消息处理程序代码和/或调用默认值
//// Now release everything and clean up.
pControl->Release();
pGraph->Release();
m_pVidWin->Release();
CoUninitialize();
CDialog::OnClose();
}
下面的这个函数从一本英文书上修改的,用途是把当前的filter graph保存起来,这样,对于调试会比较的方便的,到时候用graph edit看就可以了
void CplayMediaDlg::OnBnClickedSave()
{
// TODO: 在此添加控件通知处理程序代码
HRESULT hr;
CFileDialog dlg(TRUE);
if (dlg.DoModal()==IDOK)
{
WCHAR wFileName[MAX_PATH];
MultiByteToWideChar(CP_ACP, 0, dlg.GetPathName(), -1, wFileName, MAX_PATH);
IStorage* pStorage=NULL;
// First, create a document file that will hold the GRF file
hr = ::StgCreateDocfile(
wFileName,
STGM_CREATE|STGM_TRANSACTED|STGM_READWRITE|STGM_SHARE_EXCLUSIVE,
0, &pStorage);
if (FAILED(hr))
{
AfxMessageBox(TEXT("Can not create a document"));
return;
}
// Next, create a stream to store.
WCHAR wszStreamName[] = L"ActiveMovieGraph";
IStream *pStream;
hr = pStorage->CreateStream(
wszStreamName,
STGM_WRITE|STGM_CREATE|STGM_SHARE_EXCLUSIVE,
0, 0, &pStream);
if(FAILED(hr))
{
AfxMessageBox(TEXT("Can not create a stream"));
pStorage->Release();
return;
}
// The IpersistStream::Save method converts a stream
// into a persistent object.
IPersistStream *pPersist = NULL;
pGraph->QueryInterface(IID_IPersistStream,
reinterpret_cast<void**>(&pPersist));
hr = pPersist->Save(pStream, TRUE);
pStream->Release();
pPersist->Release();
if(SUCCEEDED(hr))
{
hr = pStorage->Commit(STGC_DEFAULT);
if (FAILED(hr))
{
AfxMessageBox(TEXT("can not store it"));é
}
}
pStorage->Release();
}
}
这样,一个简单的基于directshow的流媒体播放程序,就算完成了。
感觉上对于编写directshow的应用程序,各个程序不同的地方在于filter graph不一样的,不一样的filter graph所针对的流媒体的类型也是不一样的,别的如同显示视频流的代码都是一样的。而这里,由于是让directshow自动生成filter graph,所以,只用了一行代码hr=pGraph->RenderFile((LPCWSTR)wFileName,NULL);很多时候,需要自己组建filter graph,这就会复杂些。并且,这里当打开另一个文件的时候,应该把由原来文件生成的filter graph删除掉的,但是,这里没有作。并且,还有考虑不太周到的地方。将来改进。