一、介绍

摄像头图像采集处理在业界有着多种成熟的方案。从老的DirectShow、Grabber技术,到新的Windows Media Foundation框架,网络上都有着丰富的参考资料。OpenCV库里面甚至提供了非常简洁的接口,用户只要一两行代码即可实现数据采集、编解码等功能,使用起来甚是方便。但是,如果把数据采集的任务放到我们自己的程序中来实现的话,CPU的占用率会比较高。这在某些情况下不太可取。虽然可以实现,但是在客户端使用时效率非常低下。而公司恰好有一项开发任务:要求采集到摄像头数据后,对图像数据进行各种变换处理,然后传递给底层驱动程序,实现虚拟摄像头功能。具体的效果如果CamMask或者CamTwist:

尝试过自己写代码采集摄像头数据,然后再进行图像处理。但是换了多种方式都不太理想。要么CPU占用率达到百分之五六十,要么内存占用率达到六七百兆。采用DirectShow Filter似乎就成了唯一的一种方式。实际测试下来,3K分辨率的视频CPU占用率保持在30%上下,内存在150M上下。这个数据还是可以接受的。

二、DirectShow基础

DirectShow是Microsoft DirectX技术体系中的一员,其他成员还包括DirectSound, DirectInput, DirectSetup, DirectX Graphics等。DirectShow技术是微软为了解决多媒体应用开发中的一些难题而提出的。例如:如何保证数据量巨大的多媒体数据处理的高效性?如何让音视频时刻保持同步?如何处理各种式样的媒体格式问题?如何支持目标系统中不可预知的硬件?DirectShow的设计初衷就是尽量让应用程序开发人员从复杂的数据传输、硬件差异、同步性等工作中解脱出来,总体应用框架和底层工作由DirectShow来完成。DirectShow技术的总体运行流程如下:

Filter是DirectShow技术体系中最基本的概念。如上图所示,DirectShow中的Filter分成三大类:Source Filter、Transform Filter、Render Filter。Source Filter就是提供数据源的Filter,所有的数据都是从Source Filter流出去的。不管是多媒体文件还是多媒体设备,Source Filter都进行了封装统一了接口,在使用方式上保持了一致。Transform Filter则是对数据进行操作处理的Filter,所有的图像操作都应该在这里进行。而Render Filter则是用来渲染图像的Filter,不管是保存到文件还是输出到其他地方,都由这个Render Filter来实现。Windows系统本身提供了非常多的Filter,我们在开发的时候可以直接使用。

DirectShow使用Filter Graph来管理Filter。Filter Graph是Filter的容器,所有Filter如果想要起作用,就必须加入到Filter Graph当中。Filter是Filter Graph当中最小的功能模块。

Filter加入到Filter Graph中后,还需要进行连接。Filter的连接,实际上就是Filter上的Pin的连接。连接的方式一般总是由上一级Filter的输出Pin指向下一级Filter的输入Pin。如下图:

图中总共出现了5个Filter。其中MJPEG DecompressorColor Space Converter是系统提供的Filter,分别用于MJPG流的解码和颜色空间转换。Video Control则是GraphStudio自动绑定到相机的Filter,剩下的两个是我们自己编写的Filter,分别属于Transform Filter和Render Filter。Filter之间的绿线就是表示Filter上Pin之间的连接。这样连接来之后,整条Filter Graph就跑通了。这个工具名叫GraphStudioNext,用于测试Filter的编写是否正确。更多的DirectShow基础介绍,可以参考《DirectShow开发指南:陆其明著》这本书。正如其宣传所言:全面深刻通俗易懂

三、编写DirectShow Filter

那么,代码中如何编写Filter呢?我们需要参考例子。网络上及上面介绍的那本书中,都提到了DirectShow Samples这个玩意儿。但是我把Windows 10系统的SDK目录翻了个底朝天也没发现Samples在哪。后来经过研究才发现,貌似Windows 7的SDK中才附带了Samples。也就是说,想要参考Samples里面的样例工程,还得安装个Windows 7的SDK。

Filters目录下面就是一些简单的样例工程。这其中要介绍一下的是baseclasses目录。这下面的是一些C++类文件,是微软实现的对DirectShow Filter API的封装。这些类替我们把一些通用操作给抽象出来实现了,然后我们在实现自己的Filter时,直接从baseclasses里面的类继承就好了,简洁方便。如果不用Baseclasses里面的类的话,也可以进行DirectShow Filter的开发,但是需要自己实现很多重复、繁杂的代码,还容易出错。baseclasses里面有一个vs工程,需要我们用vs将baseclasses编译成静态库,使用时包括头文件即可。

Filter的编写在samples里面提供的工程基础修改即可。关键的关键是Filter之间的连接,Filter上的Pin之间需要协商好,否则的话无法顺利建立连接。协商过程主要是包括媒体类型、尺寸、颜色模型、压缩方式等。这个过程需要在实际开发中去研究尝试。Filter编写好之后的工作,就剩下连接了。连接操作非常通俗简单,简而言之就是:实例化Filter->创建Filter Graph->往Filter Graph中加入Filter->查找Filter上的Pin->连接Pin->运行Filter Graph

// instantialize filters
IBaseFilterPtr m_pVCamRenderer;
IBaseFilterPtr m_pInsta360TestFilter;
IBaseFilterPtr m_pJpegDecoder;
IBaseFilterPtr m_pColorConverter;
HRESULT hr = CoCreateInstance(CLSID_VCamRenderer, NULL, CLSCTX_INPROC, IID_IBaseFilter,
reinterpret_cast<void**>(&m_pVCamRenderer));
if (FAILED(hr)) {
LOGERR("Failed to create VCam Renderer filter!");
m_initialStatus = FALSE;
} hr |= CoCreateInstance(CLSID_Insta360TestSticher, NULL, CLSCTX_INPROC, IID_IBaseFilter,
reinterpret_cast<void**>(&m_pInsta360TestFilter));
if (FAILED(hr)) {
LOGERR("Failed to create Insta360 test sticher filter!");
m_initialStatus = FALSE;
} hr |= CoCreateInstance(CLSID_Colour, NULL, CLSCTX_INPROC, IID_IBaseFilter,
reinterpret_cast<void**>(&m_pColorConverter));
if (FAILED(hr)) {
LOGERR("Failed to create color space converter filter!");
m_initialStatus = FALSE;
} hr |= CoCreateInstance(CLSID_MjpegDec, NULL, CLSCTX_INPROC, IID_IBaseFilter,
reinterpret_cast<void**>(&m_pJpegDecoder));
if (FAILED(hr)) {
LOGERR("Failed to create jpeg decoder filter!");
m_initialStatus = FALSE;
} // declare variables
HRESULT hr;
IGraphBuilderPtr m_pGraph;
IMediaSeekingPtr m_pMS;
IMediaControlPtr m_pMC; CComPtr<IPin> m_pCameraOutput; CComPtr<IPin> m_pDecoderInput;
CComPtr<IPin> m_pDecoderOutput; CComPtr<IPin> m_pSticherInput;
CComPtr<IPin> m_pSticherOutput; CComPtr<IPin> m_pColorConverterInput;
CComPtr<IPin> m_pColorConverterOutput; CComPtr<IPin> m_pVCamInput; // query interfaces
hr = CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC, IID_IGraphBuilder, (void **)&m_pGraph);
// Media control
hr = m_pGraph->QueryInterface(IID_IMediaControl, (void**)&m_pMC); hr = m_pGraph->AddFilter(pCapSrcFilter, L"Source");
hr = m_pGraph->AddFilter(m_pJpegDecoder, L"Decoder");
hr = m_pGraph->AddFilter(m_pInsta360TestFilter, L"Sticher");
hr = m_pGraph->AddFilter(m_pColorConverter, L"Converter");
hr = m_pGraph->AddFilter(m_pVCamRenderer, L"Renderer"); // Enumerate pins to make connections
CComPtr<IEnumPins> pEnum = NULL;
hr = pCapSrcFilter->EnumPins(&pEnum);
hr = pEnum->Reset();
m_pCameraOutput = NULL;
hr = pEnum->Next(1, &m_pCameraOutput, NULL); // decoder
pEnum = NULL;
m_pJpegDecoder->EnumPins(&pEnum);
pEnum->Reset();
m_pDecoderInput = NULL;
hr = pEnum->Next(1, &m_pDecoderInput, NULL); pEnum = NULL;
m_pJpegDecoder->EnumPins(&pEnum);
pEnum->Reset();
pEnum->Skip(1);
m_pDecoderOutput = NULL;
hr = pEnum->Next(1, &m_pDecoderOutput, NULL); // sticher
pEnum = NULL;
m_pInsta360TestFilter->EnumPins(&pEnum);
pEnum->Reset();
m_pSticherInput = NULL;
hr = pEnum->Next(1, &m_pSticherInput, NULL); pEnum = NULL;
m_pInsta360TestFilter->EnumPins(&pEnum);
pEnum->Reset();
pEnum->Skip(1);
m_pSticherOutput = NULL;
hr = pEnum->Next(1, &m_pSticherOutput, NULL); // color space converter
pEnum = NULL;
m_pColorConverter->EnumPins(&pEnum);
pEnum->Reset();
m_pColorConverterInput = NULL;
hr = pEnum->Next(1, &m_pColorConverterInput, NULL); pEnum = NULL;
m_pColorConverter->EnumPins(&pEnum);
pEnum->Reset();
pEnum->Skip(1);
m_pColorConverterOutput = NULL;
hr = pEnum->Next(1, &m_pColorConverterOutput, NULL); // vcam
pEnum = NULL;
m_pVCamRenderer->EnumPins(&pEnum);
pEnum->Reset();
m_pVCamInput = NULL;
hr = pEnum->Next(1, &m_pVCamInput, NULL);
if (FAILED(hr))
{
LOGERR("Error occured while enumerating filter pins.");
SAFE_RELEASE(pCapSrcFilter);
SAFE_RELEASE(pM);
return hr;
} hr |= m_pGraph->Connect(m_pCameraOutput, m_pDecoderInput);
if (FAILED(hr))
{
LOGERR("Failed to connect filter pins: 0x%X", hr);
SAFE_RELEASE(pCapSrcFilter);
SAFE_RELEASE(pM);
return hr;
}
hr |= m_pGraph->Connect(m_pDecoderOutput, m_pSticherInput);
if (FAILED(hr))
{
LOGERR("Failed to connect filter pins: 0x%X", hr);
SAFE_RELEASE(pCapSrcFilter);
SAFE_RELEASE(pM);
return hr;
}
hr |= m_pGraph->Connect(m_pSticherOutput, m_pColorConverterInput);
if (FAILED(hr))
{
LOGERR("Failed to connect filter pins: 0x%X", hr);
SAFE_RELEASE(pCapSrcFilter);
SAFE_RELEASE(pM);
return hr;
}
hr |= m_pGraph->Connect(m_pColorConverterOutput, m_pVCamInput);
if (FAILED(hr))
{
LOGERR("Failed to connect filter pins: 0x%X", hr);
SAFE_RELEASE(pCapSrcFilter);
SAFE_RELEASE(pM);
return hr;
} // run the graph
if (NULL != m_pMC)
{
m_pMC->Run();
} SAFE_RELEASE(pCapSrcFilter);
SAFE_RELEASE(pM);

  至此,整条Filter Graph就跑起来了。我们的DirectShow应用程序也就编写完成了。实际编写过程中可能会遇到更多的问题,此时多尝试多搜索,通常都可以解决掉。在本人编写Transform Filter的过程中,有一个需要改变输出尺寸的需求。也就是说,输入的是2:1的视频的话,我要改成1:1的输出。这里要一定要注意CTransformFilter类的CheckTransform方法。一般如果输入输出的媒体类型不变的话,实现如下:

// Check a transform can be done between these formats
HRESULT Insta360TestFilter::CheckTransform(const CMediaType* mtIn, const CMediaType* mtOut)
{
CheckPointer(mtIn, E_POINTER);
CheckPointer(mtOut, E_POINTER); if (CanPerformTransform(mtIn))
{
if (*mtIn == *mtOut)
{
return NOERROR;
}
} return E_FAIL;
}

  但是如果Filter的输入输出媒体类型发生改变了的话,这里就不能这么写了。要么在这里对mtIn和mtOut进行修改保证相等,要么直接返回NOERROR。否则编写出来的Filter是无法和其他Filter进行连接的!

四、参考链接

五、模板工程

Transform Filter模板工程

DirectShow Filter的开发实践的更多相关文章

  1. DirectShow Filter 开发典型例子分析 ——字幕叠加 (FilterTitleOverlay)1

    本文分析一下<DirectShow开发指南>中的一个典型的Transform Filter的例子:字幕叠加(FilterTitleOverlay).通过分析该例子,我们可以学习到Direc ...

  2. Redis的Python实践,以及四中常用应用场景详解——学习董伟明老师的《Python Web开发实践》

    首先,简单介绍:Redis是一个基于内存的键值对存储系统,常用作数据库.缓存和消息代理. 支持:字符串,字典,列表,集合,有序集合,位图(bitmaps),地理位置,HyperLogLog等多种数据结 ...

  3. Xamarin.Android开发实践(五)

    原文:Xamarin.Android开发实践(五) 一.服务的生命周期 服务与活动一样,在它的整个生命周期中存在着一些事件,下图可以很好解释整个过程以及涉及到的方法: 在真实的使用中,Service来 ...

  4. Filter组件开发中的SDK基类分析

    DirectShow SDK提供了一套开发Filter的基类源代码.基于这些基类开发Filter将大大简化开发过程. 1.CBaseObject 大部分SDK类都从CBaseObject类(参见com ...

  5. 一小时完成后台开发:DjangoRestFramework开发实践

    DjangoRestFramework开发实践 在这之前我写过一篇关于Django与Drf快速开发实践的博客,Django快速开发实践:Drf框架和xadmin配置指北,粗略说了一下Drf配置和基本使 ...

  6. 《JavaScript设计模式与开发实践》整理

    最近在研读一本书<JavaScript设计模式与开发实践>,进阶用的. 一.高阶函数 高阶函数是指至少满足下列条件之一的函数. 1. 函数可以作为参数被传递. 2. 函数可以作为返回值输出 ...

  7. Android游戏开发实践(1)之NDK与JNI开发03

    Android游戏开发实践(1)之NDK与JNI开发03 前面已经分享了两篇有关Android平台NDK与JNI开发相关的内容.以下列举前面两篇的链接地址,感兴趣的可以再回顾下.那么,这篇继续这个小专 ...

  8. TFS 2015 敏捷开发实践 – 在Kanban上运行一个Sprint

    前言:在 上一篇 TFS2015敏捷开发实践 中,我们给大家介绍了TFS2015中看板的基本使用和功能,这一篇中我们来看一个具体的场景,如何使用看板来运行一个sprint.Sprint是Scrum对迭 ...

  9. Android游戏开发实践(1)之NDK与JNI开发01

    Android游戏开发实践(1)之NDK与JNI开发01 NDK是Native Developement Kit的缩写,顾名思义,NDK是Google提供的一套原生Java代码与本地C/C++代码&q ...

随机推荐

  1. POI

    一.简介 POI(Point of Interest),中文可以翻译为“兴趣点”.在地理信息系统中,一个POI可以是一栋房子.一个商铺.一个邮筒.一个公交站等. 1.POI检索 百度地图SDK提供三种 ...

  2. Java基础系列——IO流

    ---恢复内容开始--- Java对数据的操作都是通过流的方式,数据的输入和输出是相对内存来说的,将外设的数据读到内存:输入流:将内存的数据写到外设:输出流. 流按操作数据分为两种:字节流,字符流. ...

  3. ReactJs笔记

    中文教程:http://reactjs.cn/ 实例: http://www.ruanyifeng.com/blog/2015/03/react.html

  4. ABP理论学习之领域服务

    返回总目录 本篇目录 介绍 IDomainService接口和DomainService类 样例 创建一个接口 服务实现 调用应用服务 一些讨论 何不只使用应用服务 如何强制使用领域服务 介绍 领域服 ...

  5. Unity3D游戏开发初探—2.初步了解3D模型基础

    一.什么是3D模型? 1.1 3D模型概述 简而言之,3D模型就是三维的.立体的模型,D是英文Dimensions的缩写. 3D模型也可以说是用3Ds MAX建造的立体模型,包括各种建筑.人物.植被. ...

  6. 字符串匹配算法 - KMP

    前几日在微博上看到一则微博是说面试的时候让面试者写一个很简单的字符串匹配都写不出来,于是我就自己去试了一把.结果写出来的是一个最简单粗暴的算法.这里重新学习了一下几个经典的字符串匹配算法,写篇文章以巩 ...

  7. zookeeper分布式锁原理

    一.分布式锁介绍分布式锁主要用于在分布式环境中保护跨进程.跨主机.跨网络的共享资源实现互斥访问,以达到保证数据的一致性. 二.架构介绍在介绍使用Zookeeper实现分布式锁之前,首先看当前的系统架构 ...

  8. python & mongo问题记录

    背景介绍 使用python操作mongo进行的一些操作记录,为了方便日后可以快速的解决类似问题. 准备工作 为了尽可能简单的说明,我将插入几条简单的数据. from pymongo import Mo ...

  9. 兼容各浏览器的js判断上传文件大小

    由于项目需要,在网上找了一个JS判断上传文件大小的程序,经测试兼容IE6-,Firefox10,Opera11.,safari5.,chrome17 <!DOCTYPE html> < ...

  10. Uiautomator 2.0之UiWatcher类学习小记

    1. 主要功能 使用此方法可以处理中断问题,从而保证测试用例的正常运行. 2. 相关API API 说明 registerWatcher (String name, UiWatcher watcher ...