9.2.2 视频捕获类CCaptureClass的实现
在9.1.2节中讲述了CCaptureClass类的部分实现,现在详细讲述该类的成员变量和其他成员方法的实现,剖析其完成视频采集、保存的技术过程。
1)定义CCaptureClass类
class CCaptureClass { public: CCaptureClass(); //类构造器 virtual ~CCaptureClass(); //类析构器 int EnumDevices(HWND hList); void SaveGraph(TCHAR *wFileName); //保存滤波器链表 void ConfigCameraPin(HWND hwndParent); //配置摄像头的视频格式 void ConfigCameraFilter(HWND hwndParent); //配置摄像头的图像参数 HRESULT CaptureImages(CString inFileName); //捕获保存视频 HRESULT PreviewImages(int iDeviceID, HWND hWnd); //采集预览视频 private: HWND m_hWnd; //视频显示窗口的句柄 IGraphBuilder *m_pGB; //滤波器链表管理器 ICaptureGraphBuilder2 *m_pCapture; //增强型捕获滤波器链表管理器 IBaseFilter *m_pBF; //捕获滤波器 IMediaControl *m_pMC; //媒体控制接口 IVideoWindow *m_pVW; //视频显示窗口接口 IBaseFilter *pMux; //写文件滤波器 protected: bool BindFilter(int deviceId, IBaseFilter **pFilter); //把指定的设备滤波器捆绑到链表中 void ResizeVideoWindow(); //更改视频显示窗口 HRESULT SetupVideoWindow(); //设置视频显示窗口的特性 HRESULT InitCaptureGraphBuilder(); //创建滤波器链表 管理器,查询其各种控制接口 }; |
上述代码是类CCaptureClass的成员变量和成员函数,成员变量包括DirectShow 开发流媒体播放应用程序需要的各种接口指针变量。成员函数实现创建滤波器链表管理器、配置视频采集格式、配置图像参数和保存滤波器链表等。
在类的构造器和析构器中完成对COM库的初始化和卸载,以及接口指针的初始化和资源释放。
/*定义的资源释放操作宏*/ #ifndef srelease #define srelease(x) if ( NULL != x ) { x->Release( ); x = NULL; } #endif /*类构造函数实现*/ CCaptureClass::CCaptureClass() { CoInitialize(NULL); //COM 库初始化 m_hWnd = NULL; //视频显示窗口的句柄 m_pVW = NULL; //视频窗口接口指针清空 m_pMC = NULL; //媒体控制接口指针清空 m_pGB = NULL; //滤波器链表管理器接口指针清空 m_pBF = NULL; //捕获滤波器接口指针清空 m_pCapture = NULL; //增强型捕获滤波器链表管理器接口指针清空 } /*析构函数实现*/ CCaptureClass::~CCaptureClass() { if (m_pMC) m_pMC->Stop(); //首先停止媒体 if (m_pVW) { m_pVW->put_Visible(OAFALSE); //视频窗口不可见 m_pVW->put_Owner(NULL); //视频窗口的父窗口清空 } srelease(m_pCapture); //释放增强型捕获滤波器链表管理器接口 srelease(m_pMC); //释放媒体控制接口 srelease(m_pGB); //释放滤波器链表管理器接口 srelease(m_pBF); //释放捕获滤波器接口 CoUninitialize(); //卸载COM库 }
|
在类构造函数中,清空所有指针以便于清楚其后续操作的状态。析构函数释放各种资源、指针并清空指针,最后卸载COM库。
2)根据指定的设备ID,把基本滤波器与设备捆绑
首先枚举系统所有采集设备,直到列举的ID相同为止,最后BindToObject完成捆绑。
//把指定采集设备与滤波器捆绑 bool CCaptureClass::BindFilter(int deviceId, IBaseFilter **pFilter) { if (deviceId < 0) return false; //枚举所有的视频捕获设备 ICreateDevEnum *pCreateDevEnum; //生成设备枚举器pCreateDevEnum HRESULT hr = CoCreateInstance(CLSID_SystemDeviceEnum, NULL, CLSCTX_INPROC_SERVER, IID_ICreateDevEnum, (void**)&pCreateDevEnum); if (hr != NOERROR) return false; 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); //获取FriendlyName形式的信息 if (hr == NOERROR) { //采集设备与捕获滤波器捆绑 if (index == deviceId) pM->BindToObject(0, 0, IID_IBaseFilter, (void**)pFilter); SysFreeString(var.bstrVal); //释放二进制数据资源,必须释放 } pBag->Release(); } pM->Release(); index++; } return true; }
|
该函数的传入参数是采集设备的索引号和捕获设备的滤波器。根据索引号查询系统中的视频捕获设备。以友好名字(FriendlyName)的方式获取所选设备的信息,然后把查询成功的设备与传入的滤波器捆绑,返回捕获设备的滤波器。
3)设置视频显示窗口
DirectShow的显示窗口与IVideoWindow接口的设置基本相同,把传入的显示窗口的句柄捆绑到IvideoWindow接口上。
/*设置视频显示窗口的特性*/ HRESULT CCaptureClass::SetupVideoWindow() { HRESULT hr; //m_hWnd为类CCaptureClass的成员变量,在使用该函数前须初始化 hr = m_pVW->put_Visible(OAFALSE); //视频窗口不可见 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 CCaptureClass::ResizeVideoWindow() { if (m_pVW) { //让图像充满整个指定窗口 CRect rc; ::GetClientRect(m_hWnd,&rc); //获取显示窗口的客户区 m_pVW->SetWindowPosition(0, 0, rc.right, rc.bottom); //设置视频显示窗口的位置 } } |
在调用该函数前,需要把应用程序的显示窗口句柄传入以初始化m_hWnd。首先视频窗口不可见,捆绑传入的窗口句柄为视频窗口,设置窗口类型。ResizeVideoWindow函数获取显示窗口的客户区域,利用视频窗口接口的方法SetWindowPosition设置视频显示窗口的位置。
4)预览采集到的视频数据
使用上述有关的类成员函数初始化滤波器链表管理器,把指定采集设备的滤波器添加到链表中,然后渲染RenderStream方法把所有的滤波器链接起来,最后根据设定的显示窗口预览采集到的视频数据,具体实现过程如下。
/*开始预览视频数据*/ HRESULT CCaptureClass::PreviewImages(int iDeviceID, HWND hWnd) { HRESULT hr; //初始化视频捕获滤波器链表管理器 hr = InitCaptureGraphBuilder(); if (FAILED(hr)) { AfxMessageBox(_T("Failed to get video interfaces!")); return hr; } //把指定采集设备与滤波器捆绑 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 ); if( FAILED( hr ) ) { AfxMessageBox(_T("Can't build the graph")); return hr; } //设置视频显示窗口 m_hWnd = hWnd; //初始化窗口句柄 SetupVideoWindow(); //设置显示窗口 hr = m_pMC->Run(); //开始采集、预览视频,在指定窗口显示视频 if(FAILED(hr)) { AfxMessageBox(_T("Couldn't run the graph!")); return hr; } return S_OK; }
|
上述程序从最初的创建滤波器链表管理器、枚举系统视频采集设备、把采集设备与捕获滤波器捆绑,到添加滤波器到滤波器链表、设置视频显示窗口,最后开始运行媒体:采集、预览视频,包含了使用DirectShow SDK开发视频采集、预览的整个技术过程。函数功能独立而又完整。
5)保存采集到的数据
把捕获的视频以AVI格式写文件。注意设置前停止调用滤波器链表,设置完成后再运行链表。
/*设置捕获视频的文件,开始捕捉视频数据写文件*/ HRESULT CCaptureClass::CaptureImages(CString inFileName) { HRESULT hr=0; m_pMC->Stop(); //先停止视频 //设置文件名,注意第二个参数的类型 hr = m_pCapture->SetOutputFileName( &MEDIASUBTYPE_Avi,
inFileName.AllocSysString(), &pMux, NULL ); //渲染媒体,链接所有滤波器 hr = m_pCapture->RenderStream( &PIN_CATEGORY_CAPTURE,
&MEDIATYPE_Video, m_pBF, NULL, pMux ); pMux->Release(); m_pMC->Run(); //回复视频 return hr; } |
预览视频后,用户可以使用该函数存储捕获的视频数据。首先停止视频媒体,利用ICaptureGraphBuilder2的方法SetOutputFileName设置存储捕获数据的文件名,然后渲染视频媒体,RenderStream方法自动链接图表中的滤波器,最后开始运行视频媒体。
【责任编辑:
云霞 TEL:(010)68476606】