9.3.3 视频图像捕获类CVMR_Capture的实现(2)
上述程序实现枚举系统所有的视频采集设备,然后把FriendlyName信息添加到组合框中,这里的设备枚举与"使用经典采集技术实现视频捕获"实例的设备枚举相同。
构建滤波器链表,添加各个滤波器、链接并运行链表。应用程序调用该函数实现视频采集、图像预览。
HRESULT CVMR_Capture::Init(int iDeviceID,HWND hWnd, int iWidth, int iHeight) { HRESULT hr; //再次调用函数,释放已经建立的链表 CloseInterfaces(); //创建IGraphBuilder hr = CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC_SERVER, IID_IGraphBuilder, (void **)&m_pGB); if (SUCCEEDED(hr)) { //创建VMR并添加到Graph中 InitializeWindowlessVMR(hWnd); //把指定的设备捆绑到一个滤波器 if(!BindFilter(iDeviceID, &m_pDF)) return S_FALSE; //添加采集设备滤波器到Graph中 hr = m_pGB->AddFilter(m_pDF, L"Video Capture"); if (FAILED(hr)) return hr; //获取捕获滤波器的引脚 IEnumPins *pEnum; m_pDF->EnumPins(&pEnum); hr |= pEnum->Reset(); hr |= pEnum->Next(1, &m_pCamOutPin, NULL); //获取媒体控制和事件接口 hr |= m_pGB->QueryInterface(IID_IMediaControl, (void **)&m_pMC); hr |= m_pGB->QueryInterface(IID_IMediaEventEx, (void **)&m_pME); //设置窗口通知消息处理 //hr = pME->SetNotifyWindow((OAHWND)hWnd, WM_GRAPHNOTIFY, 0); //匹配视频分辨率,对视频显示窗口设置 hr |= InitVideoWindow(hWnd,iWidth, iHeight); //为捕获图像帧申请内存 m_nFramelen=iWidth*iHeight*3; m_pFrame=(BYTE*) new BYTE[m_nFramelen]; //运行Graph,捕获视频 m_psCurrent = STOPPED; hr |= m_pGB->Render(m_pCamOutPin); hr |= m_pMC->Run(); if (FAILED(hr)) return hr; m_psCurrent = RUNNING; } return hr; } |
上述是一个独立而又完整的使用VMR-9技术实现视频的采集、存储任务的程序。首先如果再次调用该函数,则关闭所有接口、释放有关资源;接着创建IGraphBuilder作为滤波器链表管理器;然后添加VMR滤波器到链表中,把指定的采集设备索引与捕获滤波器捆绑,并把该滤波器添加到链表中;接着获取捕获滤波器的引脚、获取媒体控制接口和事件接口;设置窗口通知消息处理,根据输入的视频分辨率匹配采集设备的分辨率;最后使用自动渲染功能Render方法把滤波器链表链接起来,使用媒体控制接口的方法Run开始运行媒体。以下分别是该函数的子功能实现。
创建VMR滤波器,并添加到Graph链表中。
/*创建VMR,添加、设置VMR*/ HRESULT CVMR_Capture::InitializeWindowlessVMR(HWND hWnd) { IBaseFilter* pVmr = NULL; //创建VMR HRESULT hr = CoCreateInstance(CLSID_VideoMixingRenderer, NULL, CLSCTX_INPROC, IID_IBaseFilter, (void**)&pVmr); if (SUCCEEDED(hr)) { //添加VMR到滤波器链表中 hr = m_pGB->AddFilter(pVmr, L"Video Mixing Renderer"); if (SUCCEEDED(hr)) { //设置无窗口渲染模式 IVMRFilterConfig* pConfig; hr = pVmr->QueryInterface(IID_IVMRFilterConfig, (void**)&pConfig); if( SUCCEEDED(hr)) { pConfig->SetRenderingMode(VMRMode_Windowless); pConfig->Release(); } //设置传入的窗口为显示窗口 hr = pVmr->QueryInterface(IID_IVMRWindowlessControl, (void**)&m_pWC); if( SUCCEEDED(hr)) { m_pWC->SetVideoClippingWindow(hWnd); //设置视频剪辑窗口 } } pVmr->Release(); } return hr; } |
首先使用CoCreateInstance创建VMR的接口pVmr,然后把VMR滤波器添加到滤波器链表中。设置视频显示为无窗口模式,首先在pVmr接口下查询IVMRFilterConfig接口,以参数VMRMode_Windowless使用SetRenderingMode方法设置完成。最后设置传入的窗口为视频剪辑窗口。
设置捕获图像帧的格式,遍历所有格式是否有预定格式,若没有则以默认格式捕获。
HRESULT CVMR_Capture::InitVideoWindow(HWND hWnd,int width, int height) { HRESULT hr; //返回值 RECT rcDest; //矩形区域 IAMStreamConfig *pConfig; //流配置接口 IEnumMediaTypes *pMedia; //枚举媒体类型接口 AM_MEDIA_TYPE *pmt = NULL, *pfnt = NULL; //媒体类型 hr = m_pCamOutPin->EnumMediaTypes( &pMedia ); //获取捕获设备的所有媒体类型 if(SUCCEEDED(hr)) { //把视频的所有格式遍历一遍,看是否有预定的格式 while(pMedia->Next(1, &pmt, 0) == S_OK) { if( pmt->formattype == FORMAT_VideoInfo ) { VIDEOINFOHEADER *vih = (VIDEOINFOHEADER *)pmt->pbFormat; //当前的格式是否与预定格式相同,即宽和高是否相同 if( vih->bmiHeader.biWidth == width && vih->bmiHeader.biHeight == height ) { pfnt = pmt; //记录当前媒体格式 break; //退出循环 } DeleteMediaType( pmt ); //格式不匹配,则删除当前查询的媒体格式 } } pMedia->Release(); } //获取流配置接口 hr = m_pCamOutPin->QueryInterface( IID_IAMStreamConfig, (void **) &pConfig ); if(SUCCEEDED(hr)) { //有预定的媒体格式 if( pfnt != NULL ) { hr=pConfig->SetFormat( pfnt ); DeleteMediaType( pfnt ); } //没有预定的格式,读取默认媒体格式 hr = pConfig->GetFormat( &pfnt ); if(SUCCEEDED(hr)) { m_nWidth = ((VIDEOINFOHEADER *)pfnt->pbFormat)->bmiHeader.biWidth; //读取高 m_nHeight = ((VIDEOINFOHEADER *)pfnt->pbFormat)->bmiHeader.biHeight; //读取宽 DeleteMediaType( pfnt ); } } //获取传入窗口的区域,以设置显示窗口 ::GetClientRect (hWnd,&rcDest); hr = m_pWC->SetVideoPosition(NULL, &rcDest); //设置视频窗口位置 return hr; } /* 删除媒体类型*/ void CVMR_Capture::DeleteMediaType(AM_MEDIA_TYPE *pmt) { if (pmt == NULL) { //为空则直接返回 return; } if (pmt->cbFormat != 0) { //格式块大小不为零 //释放由CoTaskMemAlloc或CoTaskMemRealloc申请的内存块 CoTaskMemFree((PVOID)pmt->pbFormat); pmt->cbFormat = 0; pmt->pbFormat = NULL; } if (pmt->pUnk != NULL) { //IUnknown接口不为空 pmt->pUnk->Release(); //释放资源 pmt->pUnk = NULL; } CoTaskMemFree((PVOID)pmt); } |
无论采用CoTaskMemAlloc函数还是采用CreateMediaType函数分配的内存都可以用这个函数来释放。
从VMR中获取一帧图像数据,转换目标颜色空间为RGB24。m_pFrame指向RGB24格式的数据。
/* 从VMR中获取一帧图像*/ DWORD CVMR_Capture::GrabFrame() { if(m_pWC ) { BYTE* lpCurrImage = NULL; //获取当前的图像数据,实际的数据内容是BMP位图格式 if(m_pWC->GetCurrentImage(&lpCurrImage) == S_OK) { //读取图像帧数据lpCurrImage LPBITMAPINFOHEADER pdib = (LPBITMAPINFOHEADER) lpCurrImage; //判断图像的宽度和高度是否正确 if(m_pFrame==NULL || (pdib->biHeight * pdib->biWidth * 3) !=m_nFramelen ) { if(m_pFrame!=NULL) delete []m_pFrame; //删除以前申请的内存 m_nFramelen = pdib->biHeight * pdib->biWidth * 3; //重新申请内存 m_pFrame = new BYTE [pdib->biHeight * pdib->biWidth * 3] ; //申请的内存大小 } if(pdib->biBitCount == 32) { DWORD dwSize=0, dwWritten=0; BYTE *pTemp32; pTemp32=lpCurrImage + sizeof(BITMAPINFOHEADER); //由于采集的是32位RGB,目标要求24位,需要进行转换 this->Convert24Image(pTemp32, m_pFrame, pdib->biSizeImage); } //释放该图像lpCurrImage CoTaskMemFree(lpCurrImage); } else { return -1; } }else{ return -1; } return m_nFramelen; } |
上述程序实现获取当前显示图像,如果格式不正确则重新申请空间。由于采集的图像多数为32位,所以需要转换成24位以便于算法直接处理,捕获的图像帧放置在类CVMR_Capture的成员变量m_pFrame中,该函数返回图像帧的数据大小。
颜色空间转换ARGB32到RGB24。
/* ARGB32 to RGB24 */ bool CVMR_Capture::Convert24Image(BYTE *p32Img, BYTE *p24Img,DWORD dwSize32) { if(p32Img != NULL && p24Img != NULL && dwSize32>0) //确认指针合法 { DWORD dwSize24; dwSize24=(dwSize32 * 3)/4; //RGB32与RGB24的像素点空间只差了一个字节 BYTE *pTemp=p32Img,*ptr=p24Img; for (DWORD index = 0; index < dwSize32/4 ; index++) { unsigned char r = *(pTemp++); unsigned char g = *(pTemp++); unsigned char b = *(pTemp++); (pTemp++); //跳过alpha分量 *(ptr++) = b; *(ptr++) = g; *(ptr++) = r; 记录RGB 3分量 } } else { return false; //指针不合法 } return true; } |
RGB32与RGB24格式只差一个分量alpha,所以转换时把每个像素点的该分量丢掉,即可得到RGB24格式。另外类CVMR_Capture还有其他常见成员函数,如保存滤波器链表。
【责任编辑:
云霞 TEL:(010)68476606】