1.公司有个需求,要获取虚拟摄像头画面实时监测,之前同事好像是用dshow创建的虚拟摄像机。有个问题 通过QT的Qcamera和opencv的VideoCapture存在问题,尤其就是在分辨率对不上的时候,拿不到画面。
2.最后看了下dshow,初学记录,参考https://www.cnblogs.com/linuxAndMcu/p/12068978.html
附带解决编译问题:https://blog.csdn.net/playstudy/article/details/6661868
感谢作者,自己在获取对应摄像机上做了些小修改
虚拟摄像头获取结果
capture.h
#pragma once
#include <Windows.h>
#include <dshow.h>
#include <string>
#include <opencv2/opencv.hpp>
using namespace std;
// 用于确保安全释放的宏
#define SAFE_RELEASE(x) { if (x) x->Release(); x = NULL; }
class CCapture
{
public:
CCapture();
~CCapture();
HRESULT Init(HWND hwnd); // 初始化
HRESULT FindCaptureDevice(); // 寻找视频采集设备
HRESULT AddToGraph(); // 将base filter添加到filter graph中
HRESULT Render(); // 渲染并预览视频
void DestroyGraph(); // 销毁先前创建的filter
void ResizeWindow(); // 重设窗口
//BOOL GrabberPic();//截图
//抓取一帧,返回的IplImage不可手动释放!
//返回图像数据的为RGB模式的Top-down(第一个字节为左上角像素),即IplImage::origin=0(IPL_ORIGIN_TL)
//IplImage * QueryFrame();
private:
// 窗口句柄
HWND m_hwnd;
// 视频采集预览相关
IGraphBuilder *m_pGraph; // filter granph(manager)
ICaptureGraphBuilder2 *m_pCapture; // capture granph
IMediaControl *m_pMediaC; // 媒体控制接口
IMediaEventEx *m_pMediaE; // 媒体事件接口
IVideoWindow *m_pVideoW; // 视频窗口接口
IBaseFilter *m_pFilter; // 基类filter
IBasicVideo *m_pBasicVideo;
};
capture.cpp
#include "capture.h"
CCapture::CCapture()
{
}
CCapture::~CCapture()
{
}
// 初始化
HRESULT CCapture::Init(HWND hwnd)
{
HRESULT hr;
// 创建filter graph
hr = CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC, IID_IGraphBuilder, (void **)&m_pGraph);
if (FAILED(hr))
return hr;
// 创建capture granph
hr = CoCreateInstance(CLSID_CaptureGraphBuilder2, NULL, CLSCTX_INPROC,IID_ICaptureGraphBuilder2, (void **)&m_pCapture);
if (FAILED(hr))
return hr;
// 查询graph中各IID参数标识的接口指针
hr = m_pGraph->QueryInterface(IID_IMediaControl, (LPVOID *)&m_pMediaC);
if (FAILED(hr))
return hr;
hr = m_pGraph->QueryInterface(IID_IMediaEventEx, (LPVOID *)&m_pMediaE);
if (FAILED(hr))
return hr;
hr = m_pGraph->QueryInterface(IID_IVideoWindow, (LPVOID *)&m_pVideoW);
if (FAILED(hr))
return hr;
// 为capture graph指定要使用的filter graph
hr = m_pCapture->SetFiltergraph(m_pGraph);
if (FAILED(hr))
return hr;
// 获得窗口句柄
m_hwnd = hwnd;
return hr;
}
// 寻找视频采集设备
HRESULT CCapture::FindCaptureDevice()
{
HRESULT hr = S_OK;
ICreateDevEnum *pDevEnum = NULL;
IEnumMoniker *pClassEnum = NULL; // 用于视频采集设备的枚举
IMoniker* pMoniker = NULL; // 设备Moniker号
// 创建系统设备枚举
hr = CoCreateInstance(CLSID_SystemDeviceEnum, NULL, CLSCTX_INPROC,IID_ICreateDevEnum, (void **)&pDevEnum);
if (FAILED(hr))
return hr;
// 创建一个指定视频采集设备的枚举
hr = pDevEnum->CreateClassEnumerator(CLSID_VideoInputDeviceCategory, &pClassEnum, 0);
if (FAILED(hr) || pClassEnum == NULL)
{
SAFE_RELEASE(pDevEnum);
return hr;
}
#pragma region 找到想要的摄像机
while (pClassEnum->Next(1, &pMoniker, NULL) == S_OK)
{
IPropertyBag *pPropBag;
hr = pMoniker->BindToStorage(0, 0, IID_IPropertyBag,
(void**)(&pPropBag));
if (FAILED(hr)) {
pMoniker->Release();
continue; // Skip this one, maybe the next one will work.
}
// Find the description or friendly name.
VARIANT varName;
VariantInit(&varName);
hr = pPropBag->Read(L"Description", &varName, 0);
if (FAILED(hr)) hr = pPropBag->Read(L"FriendlyName", &varName, 0);
if (SUCCEEDED(hr))
{
hr = pPropBag->Read(L"FriendlyName", &varName, 0);
int count = 0;
char tmp[255] = { 0 };
//int maxLen = sizeof(deviceNames[0]) / sizeof(deviceNames[0][0]) - 2;
while (varName.bstrVal[count] != 0x00 && count < 255)
{
tmp[count] = (char)varName.bstrVal[count];
count++;
}
string cameName(tmp);
//RazerCAM_DM DM_AiliveCAM
if (cameName == "RazerCAM_DM")
{
break;
}
//deviceNames[deviceCounter][count] = 0;
//if (!silent) DebugPrintOut("SETUP: %i) %s\n", deviceCounter, deviceNames[deviceCounter]);
}
pPropBag->Release();
pPropBag = NULL;
}
#pragma endregion
// 使用第一个找到的视频采集设备(只适用于单摄像头的情况)
//hr = pClassEnum->Next(1, &pMoniker, NULL);
if (hr == S_FALSE)
{
SAFE_RELEASE(pDevEnum);
SAFE_RELEASE(pClassEnum);
return hr;
}
// 绑定找到摄像头的moniker到filter graph
hr = pMoniker->BindToObject(0, 0, IID_IBaseFilter, (void**)&m_pFilter);
if (FAILED(hr))
{
SAFE_RELEASE(pDevEnum);
SAFE_RELEASE(pClassEnum);
SAFE_RELEASE(pMoniker);
return hr;
}
// 增加filter graph的引用计数
m_pFilter->AddRef();
IAMStreamConfig *pSC = NULL;
VIDEOINFOHEADER *vinfo = 0;
hr = m_pCapture->FindInterface(&PIN_CATEGORY_CAPTURE,
&MEDIATYPE_Video, m_pFilter, IID_IAMStreamConfig, (void **)&pSC);
if (FAILED(hr))
{
return hr;
}
AM_MEDIA_TYPE * mmt = NULL;
pSC->GetFormat(&mmt); //取得默认参数
VIDEOINFOHEADER * pvih = (VIDEOINFOHEADER*)mmt->pbFormat;
pvih->bmiHeader.biHeight = 1920; //修改采集视频的高为240
pvih->bmiHeader.biWidth = 1080; //修改采集视频的宽为320
mmt->pbFormat = (unsigned char *)pvih;
pSC->SetFormat(mmt); //重新设置参数
return hr;
}
// 将base filter添加到filter graph中
HRESULT CCapture::AddToGraph()
{
HRESULT hr = m_pGraph->AddFilter(m_pFilter, L"Video capture");
if (FAILED(hr))
{
m_pFilter->Release();
return hr;
}
return hr;
}
// 渲染并预览视频
HRESULT CCapture::Render()
{
HRESULT hr;
// 用ICaptureGraphBuilder2接口构建预览的filter链路
hr = m_pCapture->RenderStream(&PIN_CATEGORY_PREVIEW, &MEDIATYPE_Video, m_pFilter, NULL, NULL);
if (FAILED(hr))
{
m_pFilter->Release();
return hr;
}
// 同时构建一个写文件的filter链路
IBaseFilter *pMux;
hr = m_pCapture->SetOutputFileName(&MEDIASUBTYPE_Avi, L"D:\\example.avi", &pMux, NULL); // 设置输出视频文件位置
hr = m_pCapture->RenderStream(&PIN_CATEGORY_CAPTURE, &MEDIATYPE_Video, m_pFilter, NULL, pMux); // 将m_pFilter的输出pin连接到pMux
//GrabberPic();
// 使用完就可以释放base filter了
pMux->Release();
m_pFilter->Release();
// 显示窗口 , 预览采集图形
hr = m_pVideoW->put_Owner((OAHWND)m_hwnd);
if (FAILED(hr))
return hr;
hr = m_pVideoW->put_WindowStyle(WS_CHILD | WS_CLIPCHILDREN);
if (FAILED(hr))
return hr;
ResizeWindow(); // 重设窗口
hr = m_pVideoW->put_Visible(OATRUE);
if (FAILED(hr))
return hr;
hr = m_pMediaC->Run();
return hr;
}
// 销毁先前创建的filter
void CCapture::DestroyGraph()
{
if (m_pMediaC)
m_pMediaC->StopWhenReady();
if (m_pVideoW)
{
m_pVideoW->put_Visible(OAFALSE);
m_pVideoW->put_Owner(NULL);
}
// 确保接口都安全释放了
SAFE_RELEASE(m_pMediaC);
SAFE_RELEASE(m_pMediaE);
SAFE_RELEASE(m_pVideoW);
SAFE_RELEASE(m_pGraph);
SAFE_RELEASE(m_pCapture);
}
// 重设窗口
void CCapture::ResizeWindow()
{
RECT rc;
if (m_pVideoW)
{
GetClientRect(m_hwnd, &rc);
m_pVideoW->SetWindowPosition(0, 0, rc.right, rc.bottom);
}
}
//
//BOOL CCapture::GrabberPic()
//{
//
// if (m_pGraph == NULL)
// return FALSE;
// else
// m_pGraph->QueryInterface(IID_IBasicVideo, (void **)&m_pBasicVideo);
//
// HRESULT hr;
//
// if (m_pBasicVideo == NULL)
// return FALSE;
// long bitmapSize = 0;
// BOOL bResult = FALSE;
// hr = m_pBasicVideo->GetCurrentImage(&bitmapSize, 0);
// if (SUCCEEDED(hr))
// {
// unsigned char * buffer = new unsigned char[bitmapSize];
//
// hr = m_pBasicVideo->GetCurrentImage(&bitmapSize, (long *)buffer);
// if (SUCCEEDED(hr))
// {
// BITMAPFILEHEADER hdr;
// LPBITMAPINFOHEADER lpbi;
// lpbi = (LPBITMAPINFOHEADER)buffer;
// int nColors = 0;
// if (lpbi->biBitCount <= 8)
// nColors = 1 << lpbi->biBitCount;
// hdr.bfType = ((WORD)('M' << 8) | 'B'); //always is "BM"
// hdr.bfSize = bitmapSize + sizeof(hdr);
// hdr.bfReserved1 = 0;
// hdr.bfReserved2 = 0;
// hdr.bfOffBits = (DWORD)(sizeof(BITMAPFILEHEADER) + lpbi->biSize +
// nColors * sizeof(RGBQUAD));
//
// // save DIB file as Bitmap.
// // This sample saves image as bitmap to help
// // understanding the sample.
// HANDLE fh;
// BITMAPFILEHEADER bmphdr;
// BITMAPINFOHEADER bmpinfo;
// DWORD nWritten;
// memset(&bmphdr, 0, sizeof(bmphdr));
// memset(&bmpinfo, 0, sizeof(bmpinfo));
// bmphdr.bfType = ('M' << 8) | 'B';
// bmphdr.bfSize = sizeof(bmphdr) + sizeof(bmpinfo) + bitmapSize;
// bmphdr.bfOffBits = sizeof(bmphdr) + sizeof(bmpinfo);
// bmpinfo.biSize = sizeof(bmpinfo);
// bmpinfo.biWidth = 1080;
// bmpinfo.biHeight = 1920;
// bmpinfo.biPlanes = 1;
// bmpinfo.biBitCount = 32;
//
// fh = CreateFile(L"D:\\result.bmp",
// GENERIC_WRITE, 0, NULL,
// CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
// WriteFile(fh, &bmphdr, sizeof(bmphdr), &nWritten, NULL);
// WriteFile(fh, &bmpinfo, sizeof(bmpinfo), &nWritten, NULL);
// WriteFile(fh, buffer, bitmapSize, &nWritten, NULL);
// CloseHandle(fh);
//
//
// bResult = TRUE;
// }
// bResult = FALSE;
// delete[] buffer;
// return bResult;
//
// }
// if (m_pBasicVideo != NULL)
// {
// m_pBasicVideo->Release();
// m_pBasicVideo = NULL;
// }
//
//
//
// return bResult;
//}
main
#include <Windows.h>
#include "capture.h"
// 窗口类名称
#define WIN_CLASS_NAME TEXT("VideoCapture\0")
// 窗口名称
#define WIN_NAME TEXT("Video capture Preview\0")
// 窗口宽和高
#define WIN_WIDTH 1080
#define WIN_HEIGHT 1920
LRESULT CALLBACK WndMainProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_SIZE:
//capture.ResizeWindow();
break;
case WM_WINDOWPOSCHANGED:
break;
case WM_CLOSE:
ShowWindow(hwnd, SW_HIDE); // 在屏幕中显示窗口
break;
case WM_DESTROY:
PostQuitMessage(0); // 将"退出"消息插入消息队列
return 0;
}
return DefWindowProc(hwnd, message, wParam, lParam); // 执行默认的消息处理
}
int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hInstP, PSTR szCmdLine, int nCmdShow)
{
HWND hwnd = 0;
CCapture capture;
HRESULT hr = S_OK;
WNDCLASS wc;
// 1、初始化COM库
hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
if (FALSE(hr))
return 1;
// 注册窗口类
ZeroMemory(&wc, sizeof(wc));
wc.lpfnWndProc = WndMainProc;
wc.hInstance = hInstance;
wc.lpszClassName = WIN_CLASS_NAME;
wc.lpszMenuName = NULL;
wc.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hIcon = NULL;
if (!RegisterClass(&wc)) // 为应用程序的窗口注册一个窗口类
{
CoUninitialize();
return 1;
}
// 基于窗口类创建一个窗口
hwnd = CreateWindow(WIN_CLASS_NAME, WIN_NAME,
WS_OVERLAPPEDWINDOW | WS_CAPTION | WS_CLIPCHILDREN,
CW_USEDEFAULT, CW_USEDEFAULT, WIN_WIDTH, WIN_HEIGHT, 0, 0, hInstance, 0);
if (hwnd)
{
// 2、创建各filter Graph
capture.Init(hwnd);
// 3、找到视频采集设备
capture.FindCaptureDevice();
// 4、将基类filter添加到filter granph Manager中
capture.AddToGraph();
// 5、渲染并预览视频
capture.Render();
ShowWindow(hwnd, SW_SHOW); // 在屏幕中显示窗口
// 从消息队列获取消息
MSG msg = { 0 };
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg); // 翻译一些键盘消息
DispatchMessage(&msg); // 将消息发送给窗口过程
}
}
// 6、销毁先前创建的filter Graph
capture.DestroyGraph();
// 7、释放COM
CoUninitialize();
return 0;
}