网上流传很广的CCaptureVideo类有一些小问题,而且使用起来也不是特别方便。为了方便开发人员使用directshow的方便功能,我又重新封装了一下,代码也改了一些,修正了原有的bug,这个版本我相信是稍加修改就可以作为商业代码使用的,让它支持多次摄像头的打开,关闭,以及开始数据采集和关闭数据采集。本来可以把那些没必要的静态成员都放到类里面去的,但是考虑到大家对网上的那个CCaptureVideo类比较熟悉,不想破坏了大家的兴致,所以保留原有CCaptureVideo的全局变量。现将该类的代码贴出来,希望能够对想做摄像头应用软件开发的开发人员有用。
CCaptureVideo类的头文件:
#pragma once #endif // _MSC_VER > 1000 #include <atlbase.h> #include <windows.h> #include <dshow.h> #include <Qedit.h> // ISampleGrabberCB #ifndef SAFE_RELEASE #define SAFE_RELEASE( x ) \ if ( NULL != x ) \ { \ x->Release( ); \ x = NULL; \ } #endif
class CVdoFrameHandler { public: virtual void VdoFrameData(double dblSampleTime, BYTE * pBuffer, long lBufferSize) = 0 ; };
class CSampleGrabberCB : public ISampleGrabberCB { public: long lWidth ; long lHeight ; CVdoFrameHandler * frame_handler ; BOOL bGrabVideo ; public: CSampleGrabberCB(){ lWidth = 0 ; lHeight = 0 ; bGrabVideo = FALSE ; frame_handler = NULL ; } STDMETHODIMP_(ULONG) AddRef() { return 2; } STDMETHODIMP_(ULONG) Release() { return 1; } STDMETHODIMP QueryInterface(REFIID riid, void ** ppv) { if( riid == IID_ISampleGrabberCB || riid == IID_IUnknown ){ *ppv = (void *) static_cast<ISampleGrabberCB*> ( this ); return NOERROR; } return E_NOINTERFACE; } STDMETHODIMP SampleCB( double SampleTime, IMediaSample * pSample ) { return 0; } STDMETHODIMP BufferCB( double dblSampleTime, BYTE * pBuffer, long lBufferSize ){ if (!pBuffer) return E_POINTER; if(bGrabVideo && frame_handler) frame_handler->VdoFrameData(dblSampleTime, pBuffer, lBufferSize) ; return 0; } };
class CCaptureVideo : public CWnd { friend class CSampleGrabberCB;
public: void GrabVideoFrames(BOOL bGrabVideoFrames, CVdoFrameHandler * frame_handler); HRESULT Open(int iDeviceID,HWND hWnd); HRESULT Close(); int EnumDevices(HWND hList); CCaptureVideo(); virtual ~CCaptureVideo();
protected: HWND m_hWnd; IGraphBuilder * m_pGB; ICaptureGraphBuilder2* m_pCapture; IBaseFilter* m_pBF; IMediaControl* m_pMC; IVideoWindow* m_pVW; ISampleGrabber* m_pGrabber;
protected: void FreeMediaType(AM_MEDIA_TYPE& mt); bool BindFilter(int deviceId, IBaseFilter **pFilter); void ResizeVideoWindow(); HRESULT SetupVideoWindow(); HRESULT InitCaptureGraphBuilder(); };
CCaptrureVideo类的源文件:
#include "stdafx.h" #include "CaptureVideo.h" #ifdef _DEBUG #undef THIS_FILE static char THIS_FILE[]=__FILE__; #define new DEBUG_NEW #endif
CSampleGrabberCB mCB;
////////////////////////////////////////////////////////////////////// // Construction/Destruction ////////////////////////////////////////////////////////////////////// CCaptureVideo::CCaptureVideo() { //COM Library Intialization if(FAILED(CoInitialize(NULL))) /*, COINIT_APARTMENTTHREADED)))*/ { AfxMessageBox("CCaptureVideo CoInitialize Failed!\r\n"); return; } m_hWnd = NULL; m_pVW = NULL; m_pMC = NULL; m_pGB = NULL; m_pBF = NULL; m_pGrabber = NULL; m_pCapture = NULL; }
CCaptureVideo::~CCaptureVideo() { // Stop media playback // Stop media playback if(m_pMC)m_pMC->StopWhenReady(); if(m_pVW){ m_pVW->put_Visible(OAFALSE); m_pVW->put_Owner(NULL); } SAFE_RELEASE(m_pMC); SAFE_RELEASE(m_pVW); SAFE_RELEASE(m_pGB); SAFE_RELEASE(m_pBF); SAFE_RELEASE(m_pGrabber); SAFE_RELEASE(m_pCapture); CoUninitialize() ; }
int CCaptureVideo::EnumDevices(HWND hList) { if (!hList) return -1; int id = 0;
//枚举视频扑捉设备 ICreateDevEnum *pCreateDevEnum; HRESULT hr = CoCreateInstance(CLSID_SystemDeviceEnum, NULL, CLSCTX_INPROC_SERVER,IID_ICreateDevEnum, (void**)&pCreateDevEnum);
if (hr != NOERROR)return -1; CComPtr<IEnumMoniker> pEm; hr = pCreateDevEnum->CreateClassEnumerator(CLSID_VideoInputDeviceCategory,&pEm, 0);
if (hr != NOERROR)return -1; pEm->Reset(); ULONG cFetched; IMoniker *pM; while(hr = pEm->Next(1, &pM, &cFetched), hr==S_OK) { IPropertyBag *pBag; hr = pM->BindToStorage(0, 0, IID_IPropertyBag, (void **)&pBag); if(SUCCEEDED(hr)) { VARIANT var; var.vt = VT_BSTR; hr = pBag->Read(L"FriendlyName", &var, NULL); if (hr == NOERROR) { TCHAR str[2048]; id++; WideCharToMultiByte(CP_ACP,0,var.bstrVal, -1, str, 2048, NULL, NULL); ::SendMessage(hList, CB_ADDSTRING, 0,(LPARAM)str); SysFreeString(var.bstrVal); } pBag->Release(); } pM->Release(); } return id; }
HRESULT CCaptureVideo::Close() { // Stop media playback if(m_pMC)m_pMC->StopWhenReady(); if(m_pVW){ m_pVW->put_Visible(OAFALSE); m_pVW->put_Owner(NULL); } SAFE_RELEASE(m_pMC); SAFE_RELEASE(m_pVW); SAFE_RELEASE(m_pGB); SAFE_RELEASE(m_pBF); SAFE_RELEASE(m_pGrabber); SAFE_RELEASE(m_pCapture); return S_OK ; }
HRESULT CCaptureVideo::Open(int iDeviceID, HWND hWnd) { HRESULT hr; hr = InitCaptureGraphBuilder(); if (FAILED(hr)){ AfxMessageBox("Failed to get video interfaces!"); return hr; } // Bind Device Filter. We know the device because the id was passed in if(!BindFilter(iDeviceID, &m_pBF))return S_FALSE; hr = m_pGB->AddFilter(m_pBF, L"Capture Filter"); // hr = m_pCapture->RenderStream(&PIN_CATEGORY_PREVIEW, &MEDIATYPE_Video, // m_pBF, NULL, NULL); // create a sample grabber hr = CoCreateInstance( CLSID_SampleGrabber, NULL, CLSCTX_INPROC_SERVER, IID_ISampleGrabber, (void**)&m_pGrabber ); if(FAILED(hr)){ AfxMessageBox("Fail to create SampleGrabber, maybe qedit.dll is not registered?"); return hr; } CComQIPtr< IBaseFilter, &IID_IBaseFilter > pGrabBase( m_pGrabber );
//设置视频格式 AM_MEDIA_TYPE mt; ZeroMemory(&mt, sizeof(AM_MEDIA_TYPE)); mt.majortype = MEDIATYPE_Video; mt.subtype = MEDIASUBTYPE_RGB24; // MEDIASUBTYPE_RGB24 ; hr = m_pGrabber->SetMediaType(&mt);
if( FAILED( hr ) ){ AfxMessageBox("Fail to set media type!"); return hr; } hr = m_pGB->AddFilter( pGrabBase, L"Grabber" ); if( FAILED( hr ) ){ AfxMessageBox("Fail to put sample grabber in graph"); return hr; }
// try to render preview/capture pin hr = m_pCapture->RenderStream(&PIN_CATEGORY_PREVIEW, &MEDIATYPE_Video,m_pBF,pGrabBase,NULL); if( FAILED( hr ) ) hr = m_pCapture->RenderStream(&PIN_CATEGORY_CAPTURE, &MEDIATYPE_Video,m_pBF,pGrabBase,NULL);
if( FAILED( hr ) ){ AfxMessageBox("Can't build the graph"); return hr; }
hr = m_pGrabber->GetConnectedMediaType( &mt ); if ( FAILED( hr) ){ AfxMessageBox("Failt to read the connected media type"); return hr; }
VIDEOINFOHEADER * vih = (VIDEOINFOHEADER*) mt.pbFormat; mCB.lWidth = vih->bmiHeader.biWidth; mCB.lHeight = vih->bmiHeader.biHeight; mCB.bGrabVideo = FALSE ; mCB.frame_handler = NULL ; FreeMediaType(mt); hr = m_pGrabber->SetBufferSamples( FALSE ); hr = m_pGrabber->SetOneShot( FALSE ); hr = m_pGrabber->SetCallback( &mCB, 1 );
//设置视频捕捉窗口 m_hWnd = hWnd ; SetupVideoWindow(); hr = m_pMC->Run();//开始视频捕捉 if(FAILED(hr)){AfxMessageBox("Couldn't run the graph!");return hr;} return S_OK; }
bool CCaptureVideo::BindFilter(int deviceId, IBaseFilter **pFilter) { if (deviceId < 0) return false;
// enumerate all video capture devices CComPtr<ICreateDevEnum> pCreateDevEnum; HRESULT hr = CoCreateInstance(CLSID_SystemDeviceEnum, NULL, CLSCTX_INPROC_SERVER, IID_ICreateDevEnum, (void**)&pCreateDevEnum); if (hr != NOERROR) { return false; } CComPtr<IEnumMoniker> pEm; hr = pCreateDevEnum->CreateClassEnumerator(CLSID_VideoInputDeviceCategory,&pEm, 0); if (hr != NOERROR) { return false; } pEm->Reset(); ULONG cFetched; IMoniker *pM; int index = 0; while(hr = pEm->Next(1, &pM, &cFetched), hr==S_OK, index <= deviceId) { IPropertyBag *pBag; hr = pM->BindToStorage(0, 0, IID_IPropertyBag, (void **)&pBag); if(SUCCEEDED(hr)) { VARIANT var; var.vt = VT_BSTR; hr = pBag->Read(L"FriendlyName", &var, NULL); if (hr == NOERROR) { if (index == deviceId) { pM->BindToObject(0, 0, IID_IBaseFilter, (void**)pFilter); } SysFreeString(var.bstrVal); } pBag->Release(); } pM->Release(); index++; } return true; }
HRESULT CCaptureVideo::InitCaptureGraphBuilder() { HRESULT hr;
// 创建IGraphBuilder接口 hr=CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC_SERVER, IID_IGraphBuilder, (void **)&m_pGB); // 创建ICaptureGraphBuilder2接口 hr = CoCreateInstance (CLSID_CaptureGraphBuilder2 , NULL, CLSCTX_INPROC, IID_ICaptureGraphBuilder2, (void **) &m_pCapture); if (FAILED(hr))return hr; m_pCapture->SetFiltergraph(m_pGB); hr = m_pGB->QueryInterface(IID_IMediaControl, (void **)&m_pMC); if (FAILED(hr))return hr; hr = m_pGB->QueryInterface(IID_IVideoWindow, (LPVOID *) &m_pVW); if (FAILED(hr))return hr; return hr; } HRESULT CCaptureVideo::SetupVideoWindow() { HRESULT hr; hr = m_pVW->put_Owner((OAHWND)m_hWnd); if (FAILED(hr))return hr; hr = m_pVW->put_WindowStyle(WS_CHILD | WS_CLIPCHILDREN); if (FAILED(hr))return hr; ResizeVideoWindow(); hr = m_pVW->put_Visible(OATRUE); return hr; }
void CCaptureVideo::ResizeVideoWindow() { if (m_pVW){ //让图像充满整个窗口 CRect rc; ::GetClientRect(m_hWnd,&rc); m_pVW->SetWindowPosition(0, 0, rc.right, rc.bottom); } }
void CCaptureVideo::FreeMediaType(AM_MEDIA_TYPE& mt) { if (mt.cbFormat != 0) { CoTaskMemFree((PVOID)mt.pbFormat); // Strictly unnecessary but tidier mt.cbFormat = 0; mt.pbFormat = NULL; } if (mt.pUnk != NULL) { mt.pUnk->Release(); mt.pUnk = NULL; } } void CCaptureVideo::GrabVideoFrames(BOOL bGrabVideoFrames, CVdoFrameHandler * frame_handler) { mCB.frame_handler = frame_handler ; mCB.bGrabVideo = bGrabVideoFrames ; }
在这里,声明了一个CVdoFrameHandler的纯虚基类,里面有一个纯虚函数,用户使用的时候,只要继承这个类,然后重载这个纯虚函数,就可以自动采集视频图像了。
首先:
class CMyClass : pulic CVdoFrameHandler {
...
public:
// 重载它,这里的pBuffer指针里面放的就是BGR24的的采集结果,lBufferSize就是pBuffer长度 void VdoFrameData(double dblSampleTime, BYTE * pBuffer, long lBufferSize) ;
public:
CCaptureVideo m_cap_vdo; // 这个就是视频采集的类的对象了
};
采集的顺序:
HWND hWnd = m_cam_scr.GetSafeHwnd() ; // 这个m_cam_scr是一个CStatic的对象,只要是个hWnd就行,
// directshow要在上面绘制出全屏的摄像头数据预览的
打开摄像头:
m_cap_vdo.Open(0, hWnd) ;
开始视频采集:
m_cap_vdo.GrabVideoFrames(TRUE, this) ;
此时就会周期性的自动的调用上面重载的VdoFrameData函数了,然后再编码发送,呵呵,就没问题了。
停止视频采集:
m_cap_vdo.GrabVideoFrames(FALSE, NULL) ;
关闭摄像头:
m_cap_vdo.Close() ;
最后,需要说明的是上述程序需要安装directx 9b的sdk,然后加入对应的lib:
strmiids.lib Quartz.lib winmm.lib dsound.lib dxguid.lib
我顺便也把directsound的lib给加上了。
如果只使用视频的话,大家用的时候也可以去掉。 |