DirectShow 进行视频预览和录制

这一篇讲怎么采集摄像头图像并预览,以及录制视频到本地。


程序实现流程

这里通过使用 CaptureGraphBuilder 来简化 Graph 的创建流程。


具体流程如下:

  1. 初始化 COM 库
  2. 创建各 Filter
  3. 找到视频采集设备,也就是通过 USB 连接的摄像头
  4. 渲染并预览视频
  5. 销毁先前创建的 Filter
  6. 释放COM

视频采集类

先看一下视频采集类的头文件,而源文件就不一次性全部贴出了,而是只介绍几个重要的成员函数。captrue.h 的内容如下:

#pragma once

#include <Windows.h>
#include <dshow.h>

// 用于确保安全释放的宏
#define SAFE_RELEASE(x) { if (x) x->Release(); x = NULL; }

class CCapture
{
public:
	CCapture();
	~CCapture();

	HRESULT Init(HWND hwnd); // 初始化
	HRESULT FindCaptureDevice(); // 寻找视频采集设备
	HRESULT Render(); // 渲染并预览视频
	void DestroyGraph(); // 销毁先前创建的filter

	void ResizeWindow(); // 重设窗口

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
};

1.初始化

// 初始化
HRESULT CCapture::Init(HWND hwnd)
{
	HRESULT hr;

	// 创建filter graph manager
	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;

	// 将Win32窗口句柄赋给m_hwnd
	m_hwnd = hwnd;

	return hr;
}

进行初始化操作。


2.寻找视频采集设备

// 寻找视频采集设备
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;
	}

	// 使用第一个找到的视频采集设备(只适用于单摄像头的情况)
	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(); 

	return hr;
}

初始化之后,就要找到视频采集设备,即通过 USB 连接的摄像头。这里没有去循环枚举查找多个视频采集设备,固定选择了找到的第一个视频采集设备。


3.渲染并预览视频

// 渲染并预览视频
HRESULT CCapture::Render()
{
	HRESULT hr;
    
    // 将base filter添加到filter graph中
    hr = m_pGraph->AddFilter(m_pFilter, L"Video capture");	
	if (FAILED(hr))
	{
		m_pFilter->Release();
		return 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
	
	// 使用完就可以释放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;
}

实现效果


代码下载

Github - DShow_captruePreview


参考:

(一) DirectShow简单采集程序——使用CaptureGraphBuilder

2.使用DShow进行摄像头预览


posted @ 2019-12-19 17:20  fengMisaka  阅读(2290)  评论(0编辑  收藏  举报