这个程序中用到了前面的一篇文章中的函数。这些函数在前面的那篇文章中已经给出来了,就不再在这里详细的叙述了。下面给出源代码和一些简单的注释。
// VideoCap1Dlg.h : 头文件------------------------------
//
#pragma once
#include "afxwin.h"
#include "DShowUtilities.h"
// CVideoCap1Dlg 对话框
class CVideoCap1Dlg : public CDialog
{
// 构造
public:
CVideoCap1Dlg(CWnd* pParent = NULL); // 标准构造函数
// 对话框数据
enum { IDD = IDD_VIDEOCAP1_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);
//用于设置显示的一些属性等等
IVideoWindow* m_pVidWin;
afx_msg void OnBnClickedPreview();
//用于对创建好的filter graph进行控制的,即stop ,run和pause
IGraphBuilder* m_pGraph;
//在视频流捕捉的时候,可以方便的生成fiter graph
ICaptureGraphBuilder2* m_pGraphBuilder2;
afx_msg void OnBnClickedSaveGrf();
//由于程序中既能进行预览又能进行捕捉,
//所以有下面的这两个变量的设置,表示现在创建的是何种模式的filter graph
bool m_fCaptureGraphBuilt;
bool m_fPreviewGraphBuilt;
// the capture filter
IBaseFilter* m_pVCap;
IBaseFilter* m_pMux;
//表示现在处于何种工作状态中
bool m_fCapturing;
bool m_fPreviewing;
IMediaControl* m_pMediaControl;
// we use this interface to set the frame rate and get the capture size
IAMStreamConfig *m_pVSC;
afx_msg void OnClose();
afx_msg void OnBnClickedCapture();
IFileSinkFilter *m_pSink;
IConfigAviMux *m_pConfigAviMux;
afx_msg void OnBnClickedButton1();
//为了释放资源的时候方便些,添加的这个函数
void ReleaseFilter(void);
afx_msg void OnBnClickedSetPin();
};
// VideoCap1Dlg.cpp : 实现文件---------------------
//
CVideoCap1Dlg::CVideoCap1Dlg(CWnd* pParent /*=NULL*/)
: CDialog(CVideoCap1Dlg::IDD, pParent)
, m_pVidWin(NULL)
, m_pGraph(NULL)
, m_pGraphBuilder2(NULL)
, m_fCaptureGraphBuilt(false)
, m_fPreviewGraphBuilt(false)
, m_pVCap(NULL)
, m_fCapturing(false)
, m_fPreviewing(false)
, m_pMediaControl(NULL)
, m_pVSC(NULL)
, m_pSink(NULL)
, m_pMux(NULL)
, m_pConfigAviMux(NULL)
{
m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
}
void CVideoCap1Dlg::DoDataExchange(CDataExchange* pDX)
{
CDialog::DoDataExchange(pDX);
DDX_Control(pDX, IDC_VIDEO_WINDOW, m_VideoWindow);
}
在下面的函数中,枚举了系统中的视频流捕捉设备,并且建立了一个用于预览的filter graph。也许我应该把这些代码单独分出来。这里在枚举视频流捕捉设备的时候,仅仅是得到了第一个设备就放手了。
BOOL CVideoCap1Dlg::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: 在此添加额外的初始化代码
// Initialize the COM library.
CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
// Create the Filter Graph Manager.
HRESULT hr;
hr = CoCreateInstance(CLSID_FilterGraph, NULL,
CLSCTX_INPROC_SERVER, IID_IGraphBuilder, (void **)&m_pGraph);
if (SUCCEEDED(hr))
{
// Create the Capture Graph Builder.
hr = CoCreateInstance(CLSID_CaptureGraphBuilder2, NULL,
CLSCTX_INPROC_SERVER, IID_ICaptureGraphBuilder2,
(void **)&m_pGraphBuilder2);
m_pGraphBuilder2->SetFiltergraph(m_pGraph);
if (SUCCEEDED(hr))
{
AfxMessageBox(TEXT("m_pGraph success"));
}
else AfxMessageBox(TEXT("m_pGraph failed"));
};
//下面开始了进行设备枚举!!!Need more notice
// ---------------------------------------------------------------------------
// enumerate all video capture devices
// Create the System Device Enumerator.
ICreateDevEnum *pCreateDevEnum=0;
hr = CoCreateInstance(CLSID_SystemDeviceEnum, NULL, CLSCTX_INPROC_SERVER,
IID_ICreateDevEnum, (void**)&pCreateDevEnum);
if(hr != NOERROR)
{
AfxMessageBox(TEXT("Error Creating Device Enumerator"));
return FALSE;
}
//Create an enumerator for the video capture category
IEnumMoniker *pEnum=0;
hr = pCreateDevEnum->CreateClassEnumerator(CLSID_VideoInputDeviceCategory, &pEnum, 0);
if(hr != NOERROR)
{
AfxMessageBox(TEXT("Sorry, you have no video capture hardware./r/n/r/n")
TEXT("Video capture will not function properly."));
return FALSE;
}
IMoniker *pMoniker;
if (pEnum->Next(1, &pMoniker, NULL) == S_OK) //just get the first one
{
hr = pMoniker->BindToObject(0, 0, IID_IBaseFilter, (void**)&m_pVCap);
if (SUCCEEDED(hr))
{
hr = m_pGraph->AddFilter(m_pVCap, L"Capture Filter");
if(SUCCEEDED(hr))
AfxMessageBox(TEXT("add capture filter to the filter graph success"));
}
if(SUCCEEDED(hr))
{
AfxMessageBox(TEXT("device init success"));
}
}
pMoniker->Release();
// 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);
return TRUE; // 除非设置了控件的焦点,否则返回 TRUE
}
下面的函数是必需的,否则窗口重画的时候,会产生问题
BOOL CVideoCap1Dlg::OnEraseBkgnd(CDC* pDC)
{
// TODO: 在此添加消息处理程序代码和/或调用默认值
CRect rc;
m_VideoWindow.GetWindowRect(&rc);
ScreenToClient(&rc);
pDC->ExcludeClipRect(&rc);
return CDialog::OnEraseBkgnd(pDC);
}
下面的函数是建立一个用于预览的filter graph,并对预览的窗口位置进行了设置。刚开始的时候,还要判断是否是已经在预览,或者是正在捕捉,然后相应的有不同的反应,比如清除捕捉图像用的fiter graph,新建自己的fiter graph,或者直接退出来。
void CVideoCap1Dlg::OnBnClickedPreview()
{
// TODO: 在此添加控件通知处理程序代码
HRESULT hr=0;
AM_MEDIA_TYPE *pmt;
if(!m_fPreviewGraphBuilt) //if not have one then could build one
{
// No rebuilding while we're running
//如果在capture的时候,用户点击了preview按钮
//虽然在capture的时候,用户也能进行预览的,但是在预览的同时也是在采集的
//如果点击了preview,那么让其单独预览好了,停止capture。
if(m_fCapturing)
{
m_pMediaControl->Stop();
m_fCapturing=FALSE;
}
// We don't have the necessary capture filters
if(m_pVCap == NULL)
return;
// we already have another graph built... tear down the old one
if(m_fCaptureGraphBuilt)
{
::TearDownGraph(m_pGraph,m_pVidWin,m_pVCap);
m_fCaptureGraphBuilt=FALSE;
}
//下面的这条语句是自动的从Source filter生成了一条filter graph
hr = m_pGraphBuilder2->RenderStream(&PIN_CATEGORY_PREVIEW,
&MEDIATYPE_Video, m_pVCap, NULL, NULL);
if (SUCCEEDED(hr))
{
AfxMessageBox(TEXT("preview graph build success"));
}
else AfxMessageBox(TEXT("preview graph build failed"));
//--------------------------------------------------------------------------
//对视频预览进行设置
UpdateData(TRUE);
m_fPreviewGraphBuilt = TRUE; //成功建立了preview filter graph
// ----------------------------------------------------------------------
//get the Media Control object.
hr = m_pGraph->QueryInterface(IID_IMediaControl, (void **)&m_pMediaControl);
if (SUCCEEDED(hr))
{
AfxMessageBox(TEXT("m_pControl success"));
}
else AfxMessageBox(TEXT("m_pControl failed"));
//开始运行filter graph了
m_pMediaControl->Run();
m_fPreviewing=TRUE;
}
//---------------------------------------------------------------------
//show out in the screen,进行一些设置,使捕获的图像能显示出来
//Setting the Video Window,
//注意这些设置必须是当filter graph创建后才能进行设置的,否则不行
hr = m_pGraph->QueryInterface(IID_IVideoWindow, (void**)&m_pVidWin);
if (SUCCEEDED(hr))
{
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);
}
}
}
下面的函数是用于建构一个由于视频流捕捉的filter graph,和上面遇到的情况一样,如果,已经存在一个不符合要求的filter graph,则要先删除掉,然后再建立新的。
void CVideoCap1Dlg::OnBnClickedCapture()
{
// TODO: 在此添加控件通知处理程序代码
HRESULT hr;
AM_MEDIA_TYPE *pmt=0;
// we have one already
//m_fCaptureGraphBuilt和m_fPreviewGraphBuilt同时只能有一个为TRUE
if(m_fCaptureGraphBuilt)
{
if(!m_fCapturing)
{
m_pMediaControl->Run();
m_fCapturing=TRUE;
}
return;
}
// No rebuilding while we're running
//正在运行其中的一个filter graph的时候就不能新建
if(m_fPreviewing)
{
m_pMediaControl->Stop();
m_fPreviewing=FALSE;
}
// 删除旧有的filter graph
if(m_fPreviewGraphBuilt)
{
::TearDownGraph(m_pGraph,m_pVidWin,m_pVCap);
m_fPreviewGraphBuilt=FALSE;
}
//设置保存文件的地方
CFileDialog dlg(TRUE);
if(dlg.DoModal()==IDOK)
{
WCHAR wFileName[MAX_PATH];
MultiByteToWideChar(CP_ACP, 0, dlg.GetPathName(), -1, wFileName, MAX_PATH);
hr=m_pGraphBuilder2->SetOutputFileName(&MEDIASUBTYPE_Avi,wFileName,
&m_pMux,&m_pSink);
if (hr!=NOERROR)
{
AfxMessageBox(TEXT("Can not set output file"));
return;
}
hr=m_pMux->QueryInterface(IID_IConfigAviMux,(void**)&m_pConfigAviMux);
if ((hr==NOERROR)&&(m_pConfigAviMux))
{
m_pConfigAviMux->SetOutputCompatibilityIndex(TRUE);
}
//捕捉链流
hr=m_pGraphBuilder2->RenderStream(&PIN_CATEGORY_CAPTURE,&MEDIATYPE_Video,
m_pVCap, NULL, m_pMux);
if(hr!=NOERROR)
{
AfxMessageBox(TEXT("Can not render capture stream"));
}
m_fCaptureGraphBuilt=TRUE;
//下面是视频预览链路的建立
hr = m_pGraphBuilder2->RenderStream(&PIN_CATEGORY_PREVIEW,
&MEDIATYPE_Video, m_pVCap, NULL, NULL);
if(FAILED(hr))
{
AfxMessageBox(TEXT("Can not render preview stream"));
}
// get the Media Control object.因为重新建立了filter graph,所以。。。
hr = m_pGraph->QueryInterface(IID_IMediaControl, (void **)&m_pMediaControl);
if (SUCCEEDED(hr))
{
AfxMessageBox(TEXT("m_pControl success"));
}
else AfxMessageBox(TEXT("m_pControl failed"));
m_pMediaControl->Run();
m_fCapturing=TRUE;
//---------------------------------------------------------------------
//show out in the screen,进行一些设置,使捕获的图像能显示出来
//Setting the Video Window
hr = m_pGraph->QueryInterface(IID_IVideoWindow, (void**)&m_pVidWin);
if (SUCCEEDED(hr))
{
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);
}
}
}
}
下面的函数用于显示Source Filter的属性页,通过这个属性页,可以直接进行修改了
void CVideoCap1Dlg::OnBnClickedButton1() //打开Capture filter的设置的属性页
{
// TODO: 在此添加控件通知处理程序代码
CWnd tt;
tt.GetActiveWindow();
ISpecifyPropertyPages *pProp;
HRESULT hr = m_pVCap->QueryInterface(IID_ISpecifyPropertyPages, (void **)&pProp);
if (SUCCEEDED(hr))
{
// Get the filter's name and IUnknown pointer.
FILTER_INFO FilterInfo;
hr = m_pVCap->QueryFilterInfo(&FilterInfo);
IUnknown *pFilterUnk;
m_pVCap->QueryInterface(IID_IUnknown, (void **)&pFilterUnk);
// Show the page.
CAUUID caGUID;
pProp->GetPages(&caGUID);
pProp->Release();
OleCreatePropertyFrame(
tt.m_hWnd, // Parent window
0, 0, // Reserved
FilterInfo.achName, // Caption for the dialog box
1, // Number of objects (just the filter)
&pFilterUnk, // Array of object pointers.
caGUID.cElems, // Number of property pages
caGUID.pElems, // Array of property page CLSIDs
0, // Locale identifier
0, NULL // Reserved
);
// Clean up.
pFilterUnk->Release();
FilterInfo.pGraph->Release();
CoTaskMemFree(caGUID.pElems);
}
下面的函数代码用于pin属性页的打开和设置
void CVideoCap1Dlg::OnBnClickedSetPin()
{
// TODO: 在此添加控件通知处理程序代码
CWnd tt;
tt.GetActiveWindow();
HRESULT hr;
IAMStreamConfig *pSC;
//只有停止后,才能进行pin属性的设置
if(m_fCapturing||m_fPreviewing)
{
m_pMediaControl->Stop();
if(m_fCapturing) m_fCapturing=FALSE;
if(m_fPreviewing) m_fPreviewing=FALSE;
}
if(m_fCaptureGraphBuilt||m_fPreviewGraphBuilt)
{
::TearDownGraph(m_pGraph,m_pVidWin,m_pVCap); // graph could prevent dialog working
if(m_fCaptureGraphBuilt) m_fCaptureGraphBuilt=FALSE;
if(m_fPreviewGraphBuilt) m_fPreviewGraphBuilt=FALSE;
}
hr = m_pGraphBuilder2->FindInterface(&PIN_CATEGORY_CAPTURE,
&MEDIATYPE_Video, m_pVCap,
IID_IAMStreamConfig, (void **)&pSC);
ISpecifyPropertyPages *pSpec;
CAUUID cauuid;
hr = pSC->QueryInterface(IID_ISpecifyPropertyPages,
(void **)&pSpec);
if(hr == S_OK)
{
hr = pSpec->GetPages(&cauuid);
//显示属性页
hr = OleCreatePropertyFrame(tt.m_hWnd, 30, 30, NULL, 1,
(IUnknown **)&pSC, cauuid.cElems,
(GUID *)cauuid.pElems, 0, 0, NULL);
// !!! What if changing output formats couldn't reconnect
// and the graph is broken? Shouldn't be possible...
CoTaskMemFree(cauuid.pElems);
pSpec->Release();
pSC->Release();
}
}
下面的这个函数仅仅是为了方便才写的,不过这是一种好的编程方法,前面就有些没有处理好的地方。
void CVideoCap1Dlg::ReleaseFilter(void)
{
SAFE_RELEASE(m_pVidWin);
SAFE_RELEASE(m_pGraph);
SAFE_RELEASE(m_pGraphBuilder2);
SAFE_RELEASE(m_pVCap);
SAFE_RELEASE(m_pMux);
SAFE_RELEASE(m_pMediaControl);
SAFE_RELEASE(m_pVSC);
SAFE_RELEASE(m_pSink);
SAFE_RELEASE(m_pConfigAviMux);
}
这个函数是做最后的收尾处理的
void CVideoCap1Dlg::OnClose()
{
// TODO: 在此添加消息处理程序代码和/或调用默认值
if(m_pMediaControl)
{
m_pMediaControl->Stop();
}
CoUninitialize();
ReleaseFilter();
CDialog::OnClose();
}
下面的这个函数在曾经的文章中也有介绍,是为了调试,保存filter graph的
这个函数大部分是从MS DirectShow for Digital Video & TV原样copy下来的,感觉不错的呀。
void CVideoCap1Dlg::OnBnClickedSaveGrf()
{
// 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;
m_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();
}
}
这样,一个既可以Preview又可以Capture的程序,就做好了,是不是不太难的?