单个摄像头视频流捕捉的例子

这个程序中用到了前面的一篇文章中的函数。这些函数在前面的那篇文章中已经给出来了,就不再在这里详细的叙述了。下面给出源代码和一些简单的注释。

// 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的程序,就做好了,是不是不太难的?


 

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值