DShow获取虚拟摄像头

1.公司有个需求,要获取虚拟摄像头画面实时监测,之前同事好像是用dshow创建的虚拟摄像机。有个问题 通过QT的Qcamera和opencv的VideoCapture存在问题,尤其就是在分辨率对不上的时候,拿不到画面。

2.最后看了下dshow,初学记录,参考https://www.cnblogs.com/linuxAndMcu/p/12068978.html

附带解决编译问题:https://blog.csdn.net/playstudy/article/details/6661868

感谢作者,自己在获取对应摄像机上做了些小修改
虚拟摄像头获取结果


image.png

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;

}


©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,163评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,301评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,089评论 0 352
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,093评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,110评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,079评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,005评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,840评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,278评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,497评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,667评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,394评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,980评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,628评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,796评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,649评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,548评论 2 352

推荐阅读更多精彩内容