這是一個基於對話方塊的程式。原始碼參照了微軟的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刪除掉的,但是,這裡沒有作。並且,還有考慮不太周到的地方。將來改進。