DirectShow 进行视频预览和录制
这一篇讲怎么采集摄像头图像并预览,以及录制视频到本地。
程序实现流程
这里通过使用 CaptureGraphBuilder 来简化 Graph 的创建流程。
具体流程如下:
- 初始化 COM 库
- 创建各 Filter
- 找到视频采集设备,也就是通过 USB 连接的摄像头
- 渲染并预览视频
- 销毁先前创建的 Filter
- 释放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;
}
实现效果
代码下载
参考:
(一) DirectShow简单采集程序——使用CaptureGraphBuilder
分类:
DirectShow
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 没有源码,如何修改代码逻辑?
· 一个奇形怪状的面试题:Bean中的CHM要不要加volatile?
· [.NET]调用本地 Deepseek 模型
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· .NET Core 托管堆内存泄露/CPU异常的常见思路
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· NetPad:一个.NET开源、跨平台的C#编辑器
· PowerShell开发游戏 · 打蜜蜂
· 凌晨三点救火实录:Java内存泄漏的七个神坑,你至少踩过三个!
2018-12-19 [C++进阶] 数据结构与算法