DirectShow开发快速入门之慨述
摘要:本篇文档概括性的介绍了DirectShow的主要组成部分,以及一些Directshow的基本概念。熟悉这些基本的知识对于Directshow的应用开发或者过滤器的开发者都会有所帮助。
DirectShow是微软公司提供的一套在Windows平台上进行流媒体处理的开发包,与DirectX开发包一起发布。那么,DirectShow能够做些什么呢?且看,DirectShow为多媒体流 的捕捉和回放提供了强有力的支持。运用DirectShow,我们可以很方便地从支持WDM驱动模型的采集卡上捕获数据,并且进行相应的后期处理乃至存储 到文件中。它广泛地支持各种媒体格式,包括Asf、Mpeg、Avi、Dv、Mp3、Wave等等,使得多媒体数据的回放变得轻而易举。另 外,DirectShow还集成了DirectX其它部分(比如DirectDraw、DirectSound)的技术,直接支持DVD的播放,视频的非线性编辑,以及与数字摄像机的数据交换。更值得一提的是,DirectShow提供的是一种开放式的开发环境,我们可以根据自己的需要定制自己的组件。
应用程序与DirectShow组件以及DirectShow所支持的软硬件之间的关系如图1所示。
图1 DirectShow系统框图
1、DirectShow的 Filter
Directshow是基于模块化,每个功能模块都采取COM组件方式,称为
Filter。Directshow提供了一系列的标准的模块可用于应用开发,开发者也可以开发自己的功能Filter来扩展Directshow的应
用。下面我们用一个例子来说明如何采取Filter来播放一个AVI的视频文件。
1) 首先从一个文件中读取AVI数据,形成字节流。(这个工作由源Filter完成)
2) 检查AVI数据流的头格式,然后通过AVI分割Filter将视频流和音频流分开。
3) 解码视频流,根据压缩格式的不同,选取不同的decoder filters 。
4) 通过Renderer Filter重画视频图像。
5) 音频流送到声卡进行播放,一般采用缺省的 DirectSound DeviceFilter。流程见下图。
图2 音频流播放Graph图
从上面的图表看,每一个filter都一个其他的一个或者两个filter相连接。两个Filter相连接的连接点也是com对象,我们称为Pin。
Filter通过pin将数据从一个filter传递到另一个filter中,从而可以使数据在由filter组成的链表中流动。图中的箭头表示
filter链表中的数据流的方向。在Directshow中,像上面的这样一个filter 链表我们称为filter Graph。
Filter具有三个状态,运行,停止,暂停。当一个filter运行时,它就处理媒体数据流,当停止时,filter就不在处理数据,暂停状态常用来
给运行状态之前cure data。Data Flow in the Filter Graph一章详细描述了这些概念,可以参考。
除了一些特别的例外, Filter graph中所有的filter的状态的改变都是统一的,也就说,filte graph中的所有的filter 的状态改变是一致协调的。也就是说,我们也可以用filter graph也可以有运行,停止,暂停三种状态。
Filter 一般分为下面几种类型。
(1)源过滤器(sourcefilter):源过滤器引入数据到过滤器图表中,数据来源可以是文件、网络、照相机等。不同的源过滤器处理不同类型的数据源。
(2)变换过滤器(transform filter):变换过滤器的工作是获取输入流,处理数据,并生成输出流。变换过滤器对数据的处理包括编解码、格式转换、压缩解压缩等。
(3)提交过滤器(renderer filter):提交过滤器在过滤器图表里处于最后一级,它们接收数据并把数据提交给外设。
(4)分割过滤器(splitter filter):分割过滤器把输入流分割成多个输出。例如,AVI分割过滤器把一个AVI格式的字节流分割成视频流和音频流。
(5)混合过滤器(mux filter):混合过滤器把多个输入组合成一个单独的数据流。例如,AVI混合过滤器把视频流和音频流合成一个AVI格式的字节流。
过滤器的这些分类并不是绝对的,例如一个ASF读过滤器(ASF Reader filter)既是一个源过滤器又是一个分割过滤器。
2、关于Filter Graph Manager
Filter Graph Manager也是一个com对象,用来控制Filter graph中的所有的filter,主要有以下的功能:
1) 用来协调filter之间的状态改变,从而使graph 中的所有的filter的状态的改变应该一致。
2) 建立一个参考时钟。
3) 将filter 的消息通知返回给应用程序
4) 提供用来建立 filter graph的方法。
这里只是简单的描述一下,详细地可以参考文档。
状态改变,Graph中的filter的状态改变应该一致,因此,应用程序并将状态改变的命令直接发给filter,而是将相应的状态改变的命令发送给
Filter graph
Manager,由manager将命令分发给graph中每一个filter。Seeking也是同样的方式工作,首先由应用程序将seek命令发送到
filter graph 管理器,然后由其分发给每个filter。
参考时钟,graph中的filter都采用的同一个时钟,称
为参考时钟(reference clock),参考时钟可以确保所有的数据流同步,视频桢或者音频桢应该被提交的时间称为presentation
time.presentation time 是相对于参考时钟来确定的。Filter graph
Manager应该选择一个参考时钟,可以选择声卡上的时钟,也可以选择系统时钟。
Graph事件, Graph 管理器采用事件机制将graph中发生的事件通知给应用程序,这个机制类似于windows的消息循环机制。
Graph构建的方法,graph管理器给应用程序提供了将filter添加进graph的方法,连接filter的方法,断开filter连接的方法。
但是,graph 管理器没有提供如何将数据从一个filter发送到另一个filter的方法,这个工作是由filter在内部通过pin来独立完成的。
3、媒体类型
因为Directshow是基于com组件的,就需要有一种方式来描述filter
graph每一个点的数据格式,例如,我们还以播放AVI文件为例,数据以RIFF块的形式进入graph中,然后被分割成视频和音频流,视频流有一系列
的压缩的视频桢组成,解压后,视频流由一系列的无压缩的位图组成,音频流也要走同样的步骤。
Media Types: How DirectShow Represents Formats
媒体类型是一种很普遍的,可以扩展的用来描述数字媒体格式的方法,当两个filter连接的时候,他们会就采用某一种媒体类型达成一致的协议。媒体类型
定义了处于源头的filter将要给下游的filter发送什么样的数据,以及数据的physical
layout。如果两个filter不能够支持同一种的媒体类型,那么他们就没法连接起来。
对于大多数的应用来说,也许你不用考虑媒体类型,但是,有些应用程序中,你会直接应用到媒体类型的。
媒体类型是通过AM_MEDIA_TYPE结构定义的,看看原始定义吧
typedef struct _MediaType {
GUID majortype;
GUID subtype;
BOOL bFixedSizeSamples;
BOOL bTemporalCompression;
ULONG lSampleSize;
GUID formattype;
IUnknown *pUnk;
ULONG cbFormat;
[size_is(cbFormat)] BYTE *pbFormat;
} AM_MEDIA_TYPE;
Major type:是一个GUID,用来定义数据的主类型,包括,音频,视频,unparsed字节流,MIDI数据,等等,具体可以参考msdn。
Subtype:子类型,也是一个GUID,用来进一步的细化数据格式,例如,在视频主类型中,还包括RGB-24, RGB-32,
UYVY等等一些子类型,在音频主类型中还包括PCM audio, MPEG-1
payload等类型,子类型提供了比主类型更详细的信息,但是并没有定义所有的格式,例如,视频的子类型并没有定义图像大小,桢率。这些由下面的字段定义。
bFixedSizeSamples当这个值为TRUE时,表示sample大小固定。
bTemporalCompression当这个值为TRUE时,表示sample采用了临时压缩格式,表明不是所有的桢都是关键桢,如果为FALSE,表明所有的都是关键桢。
lSampleSize 表示sample的大小。对于压缩的数据,这个值可能为零。
Formattype一个GUID值,用来表明内存块的格式。包括如
下:FORMAT_None,FORMAT_DvInfo,FORMAT_MPEGVideo,FORMAT_MPEG2Video,FORMAT_VideoInfo,FORMAT_VideoInfo2,FORMAT_WaveFormatEx,GUID_NULL。
pUnk该参数没有用到。
cbFormat内存块的大小。
pbFormat指向内存块的指针。
下面我们看一段代码,看看filter如何检测媒体类型的。
HRESULT CheckMediaType(AM_MEDIA_TYPE *pmt)
{
if (pmt == NULL) return E_POINTER;
// Check the major type. We’re looking for video.
if (pmt->majortype != MEDIATYPE_Video)
{
return VFW_E_INVALIDMEDIATYPE;
}
// Check the subtype. We’re looking for 24-bit RGB.
if (pmt->subtype != MEDIASUBTYPE_RGB24)
{
return VFW_E_INVALIDMEDIATYPE;
}
// Check the format type and the size of the format block.
if ((pmt->formattype == FORMAT_VideoInfo) && (pmt->cbFormat >= sizeof(VIDEOINFOHEADER) &&
(pmt->pbFormat != NULL))
{
// Now it’s safe to coerce the format block pointer to the
// correct structure, as defined by the formattype GUID.
VIDEOINFOHEADER *pVIH = (VIDEOINFOHEADER*)pmt->pbFormat;
// Examine pVIH (not shown). If it looks OK, return S_OK.
return S_OK;
}
return VFW_E_INVALIDMEDIATYPE;
}
下面简单介绍几个和 Media Type相关的函数:
AM_MEDIA_TYPE结构包含一个指向数据块的指针,因此,当你使用这个结构的时候,一定要小心内存分配,以防内存泄漏。
分配函数
1) AM_MEDIA_TYPE * WINAPI CreateMediaType(AM_MEDIA_TYPE const *pSrc );
这个函数分配一个新的AM_MEDIA_TYPE结构,包含特定格式的数据块。释放由这个函数分配的内存,可以调用DeleteMediaType函数
2) STDAPI CreateAudioMediaType(const WAVEFORMATEX *pwfx,AM_MEDIA_TYPE *pmt,BOOL bSetFormat);
该函数利用一个给定的WAVEFORMATIEX结构来初始化媒体类型,如果bsetFormat参数为TRUE,该函数就分配一块新的内存,如果原来
的pmt已经包含内存,就有可能发生内存泄漏。为了避免内存泄漏,在调用这个函数前要调用FreeMediaType(),在这个函数返回之后,再次调用
FreeMediaType(),释放format block。
3) HRESULT WINAPI CopyMediaType(AM_MEDIA_TYPE *pmtTarget,const AM_MEDIA_TYPE *pmtSource);
这个函数复制了一个结构到另一个结构中去。这个函数也要重新分配内存给目的结构,如果pmtTarget,已经包含一个内存块,就要内存泄漏,因此,在调用该函数前后都要调用FreeMediaType函数。
释放函数
4) void WINAPI DeleteMediaType( AM_MEDIA_TYPE *pmt);
无论是采用CoTaskMemAlloc函数还是用CreateMediaType函数分配的内存都可以用这个函数来释放,如果你没有连接基类的动态库,你可以用下面的代码
void MyDeleteMediaType(AM_MEDIA_TYPE *pmt)
{
if (pmt != NULL)
{
MyFreeMediaType(*pmt); // 见下面的 FreeMediaType 函数
CoTaskMemFree(pmt);
}
}
5) void WINAPI FreeMediaType( AM_MEDIA_TYPE& mt);
这个函数用来释放数据块的内存,如果要删除AM_MEDIA_TYPE结构,可以使用DeleteMediaType函数。
void MyFreeMediaType(AM_MEDIA_TYPE& mt)
{
if (mt.cbFormat != 0)
{
CoTaskMemFree((PVOID)mt.pbFormat);
mt.cbFormat = 0;
mt.pbFormat = NULL;
}
if (mt.pUnk != NULL)
{
// Unecessary because pUnk should not be used, but safest.
mt.pUnk->Release();
mt.pUnk = NULL;
}
}
4、媒体Samples和Allocators
Filters通过pin的连接来传递数据,数据流是从一个filter的输出pin流向相连的filter的输入pin。输出pin常用的传递数据的方式是调用输入pin上的IMemInputPin::Receive方法。
对于filter来说,可以有好几种方式来分配媒体数据使用的内存块,可以在堆上分配,可以在DirectDraw的表面,也可以采用GDI共享内存,
还有其他的一些方法,在Directshow中用来进行内存分配任务的是内存分配器(allocator),也是一个COM对象,暴露了一个
IMemAllocator接口。
当两个pin连接的时候,必须有一个pin提供一个allocator,Directshow定义了一系列函数调用用来确定由哪个pin提供allocator,以及buffer的数量和大小。
在数据流开始之前,allocator会创建一个内存池(pool of
buffer),在开始发送数据流以后,源filter就会将数据填充到内存池中一个空闲的buffer中,然后传递给下面的filter。但是,源
filter并不是直接将内存buffer的指针直接传递给下游的filter,而是通过一个media
samples的COM对象,这个sample是allocator创建的用来管理内存buffer。Media
sample暴露了IMediaSample接口,一个sample包含了下面的内容:
一个指向没有发送的内存的指针。
一个时间戳
一些标志
媒体类型。
时间戳表明了presentation time,Renderer
filter就是根据这个时间来安排render顺序的。标志是用来标示数据是否中断等等,媒体类型提供了中途改变数据格式的一种方法,不过,一般
sample没有媒体类型,表明它们的媒体类型一直没有改变。
当一个filter正在使用buffer,它就会保持一个sample
的引用计数,allocator通过sample的引用计数用来确定是否可以重新使用一个buffer。这样就防止了buffer的使用冲突,当所有的
filter都释放了对sample的引用,sample才返回到allocator的内存池,供重新使用。
5、硬件设备在graph中的作用
下面的这段话借用的是陆其明的一段文档,特此标记2005-1-26我觉得他对硬件的表述比较清楚。
大家知道,为了提高系统的稳定性,Windows操作系统对
硬件操作进行了隔离;应用程序一般不能直接访问硬件。DirectShow Filter工作在用户模式(User
mode,操作系统特权级别为Ring 3),而硬件工作在内核模式(Kernel mode,操作系统特权级别为Ring
0),那么它们之间怎么协同工作呢?
DirectShow解决的方法是,为这些硬件设计包装Filter;这种Filter能够工作在用户模式下,外观、控制方法跟普通Filter一样,而包装Filter内部完成与硬件驱动程序的交互。这样的设计,使得编写DirectShow应用程序的开发人
员,从为支持硬件而需做出的特殊处理中解脱出来。DirectShow已经集成的包装Filter,包括Audio Capture
Filter(qcap.dll)、VfW Capture Filter(qcap.dll,Filter的Class
Id为CLSID_VfwCapture)、TV Tuner Filter(KSTVTune.ax,Filter的Class
Id为CLSID_CTVTunerFilter)、Analog Video Crossbar Filter(ksxbar.ax)、TV
Audio Filter(Filter的Class
Id为CLSID_TVAudioFilter)等;另外,DirectShow为采用WDM驱动程序的硬件设计了KsProxy
Filter(Ksproxy.ax,)。我们可以看一下结构图:见图1
我们可以看出,Ksproxy.ax、Kstune.ax、
Ksxbar.ax这些包装Filter跟其它普通的DirectShow
Filter处于同一个级别,可以协同工作;用户模式下的Filter通过Stream
Class控制硬件的驱动程序minidriver(由硬件厂商提供的实现对硬件控制功能的DLL);Stream
Class和minidriver一起向上层提供系统底层级别的服务。值得注意的是,这里的Stream
Class是一种驱动模型,它负责调用硬件的minidriver;另外,Stream
Class的功能还在于协调minidriver之间的工作,使得一些数据可以直接在Kernel
mode下从一个硬件传输到另一个硬件(或同一个硬件上的不同功能模块),提高了系统的工作效率。(更多的关于底层驱动程序的细节,请读者参阅
Windows DDK。)
下面,我们分别来看一下几种常见的硬件。
VfW视频采集卡。这类硬件在市场上已经处
于一种淘汰的趋势;新生产的视频采集卡一般采用WDM驱动模型。但是,DirectShow为了保持向后兼容,还是专门提供了一个包装Filter支持这
种硬件。和其他硬件的包装Filter一样,这种包装Filter的创建不是像普通Filter一样使用CoCreateInstance,而要通过系统
枚举,然后BindToObject。
音频采集卡(声卡)。声卡的采集功能也是通过包装Filter来实现的;而且现在的声卡大部分
都有混音的功能。这个Filter一般有几个Input pin,每个pin都代表一个输入,如Line
In、Microphone、CD、MIDI等。值得注意的是,这些pin代表的是声卡上的物理输入端子,在Filter
Graph中是永远不会连接到其他Filter上的。声卡的输出功能,可以有两个Filter供选择:DirectSound Renderer
Filter和Audio Renderer (WaveOut)
Filter。注意,这两个Filter不是上述意义上的包装Filter,它们能够同硬件交互,是因为它们使用了API函数:前者使用了
DirectSound API,后者使用了waveOut
API。这两个Filter的区别,还在于后者输出音频的同时不支持混音。(顺便说明一下,Video Renderer
Filter能够访问显卡,也是因为使用了GDI、DirectDraw或Direct3D
API。)如果你的机器上有声卡的话,你可以通过GraphEdit,在Audio Capture
Sources目录下看到这个声卡的包装Filter。
WDM驱动的硬件(包括视频捕捉卡、硬件解压卡等)。这类硬件都使用
Ksproxy.ax这个包装Filter。Ksproxy.ax实现了很多功能,所以有“瑞士军刀”的美誉;它还被称作为“变色龙Filter”,因为
该Filter上定义了统一的接口,而接口的实现因具体的硬件驱动程序而异。在Filter Graph中,Ksproxy
Filter显示的名字为硬件的Friendly name(一般在驱动程序的.inf文件中定义)。我们可以通过GraphEdit,在WDM
Streaming开头的目录中找到本机系统中安装的WDM硬件。因为KsProxy.ax能够代表各种WDM的音视频设备,所以这个包装Filter的
工作流程有点复杂。这个Filter不会预先知道要代表哪种类型的设备,它必须首先访问驱动程序的属性集,然后动态配置Filter上应该实现的接口。
当Ksproxy
Filter上的接口方法被应用程序或其他Filter调用时,它会将调用方法以及参数传递给驱动程序,由驱动程序最终完成指定功能。除此以外,WDM硬
件还支持内核流(Kernel
Streaming),即内核模式下的数据传输,而无需经过到用户模式的转换。因为内核模式与用户模式之间的相互转换,需要花费很大的计算量。如果使用内
核流,不仅可以避免大量的计算,还避免了内核数据与主机内存之间的拷贝过程。在这种情况下,用户模式的Filter
Graph中,即使pin之间是连接的,也不会有实际的数据流动。典型的情况,如带有Video Port Pin的视频捕捉卡,Preview时显示的图像就是在内核模式下直接传送到显卡的显存的。所以,你也休想在VP Pin后面截获数据流。
讲到这里,我想大家应该对DirectShow对硬件的支持问题有了一个总体的认识。对于应用程序开发人员来说,这方面的内容不用研究得太透,而只需作为背景知识了解一下就好了。其实,大量繁琐的工作DirectShow已经帮我们做好了。
DirectShow开发快速入门之慨述的更多相关文章
- Transform组件C#游戏开发快速入门
Transform组件C#游戏开发快速入门大学霸 组件(Component)可以看作是一类属性的总称.而属性是指游戏对象上一切可设置.调节的选项,如图2-8所示.本文选自C#游戏开发快速入门大学霸 ...
- HealthKit开发快速入门教程之HealthKit数据的操作
HealthKit开发快速入门教程之HealthKit数据的操作 数据的表示 在HealthKit中,数据是最核心的元素.通过分析数据,人们可以看到相关的健康信息.例如,通过统计步数数据,人们可以知道 ...
- HealthKit开发快速入门教程之HealthKit框架体系创建健康AppID
HealthKit开发快速入门教程之HealthKit框架体系创建健康AppID HealthKit开发准备工作 在开发一款HealthKit应用程序时,首先需要讲解HealthKit中有哪些类,在i ...
- HealthKit开发快速入门教程之HealthKit开发概述简介
HealthKit开发快速入门教程之HealthKit开发概述简介 2014年6月2日召开的年度开发者大会上,苹果发布了一款新的移动应用平台,可以收集和分析用户的健康数据.该移动应用平台被命名为“He ...
- Apple Watch开发快速入门教程
Apple Watch开发快速入门教程 试读下载地址:http://pan.baidu.com/s/1eQ8JdR0 介绍:苹果为Watch提供全新的开发框架WatchKit.本教程是国内第一本A ...
- 游戏控制杆OUYA游戏开发快速入门教程
游戏控制杆OUYA游戏开发快速入门教程 1.2.2 游戏控制杆 游戏控制杆各个角度的视图,如图1-4所示,它的硬件规格是本文选自OUYA游戏开发快速入门教程大学霸: 图1-4 游戏控制杆各个角度的 ...
- Oracle PL/SQL入门之慨述
Oracle PL/SQL入门之慨述 一.PL/SQL出现的目的 结构化查询语言(Structured Query Language,简称SQL)是用来访问关系型数据库一种通用语言,它属于第四代语言( ...
- SpringBoot开发快速入门
SpringBoot开发快速入门 目录 一.Spring Boot 入门 1.Spring Boot 简介 2.微服务 3.环境准备 1.maven设置: 2.IDEA设置 4.Spring Boot ...
- WPF开发快速入门【7】WPF的拖放功能(Drag and Drop)
概述 本文描述WPF的拖放功能(Drag and Drop). 拖放功能涉及到两个功能,一个就是拖,一个是放.拖放可以发生在两个控件之间,也可以在一个控件自己内部拖放.假设界面上有两个控件,一个Tre ...
随机推荐
- RSA大会播报 – 2014最佳安全博客提名(国外篇)
最佳企业安全博客提名: Juniper(网络厂商,不用多介绍):http://forums.juniper.net/t5/Security-Mobility-Now/bg-p/networki ...
- MRPT笔记——使用编译好的MRPT库建立VS2013项目
接着上一篇<MRPT在VS2013中的配置>,下面接收如何使用编译好的MRPT建立工程项目. 一.设置环境变量 上一篇中,配置MRPT时,使用到了几个相关库,opencv.zlib.wxW ...
- Crowd 2.7汉化中文包(原创首发)
介绍:Crowd是用来集成Atlassian各类产品用户集成系统,如Jira,Confluence等的集中用户管理平台.可对组.成员关系.用户.目录.应用程序及权限进行综合管理,并可实现其他程序的单点 ...
- Fragment的onResume
需求:Fragment每次由不可见到可见时的回调. 可能最先想到的是onResume方法,实际使用中Fragment的onResume调用时机与其Activity一致,因此类似与viewPager搭配 ...
- VisualSVN5.0.1补丁原创发布
VisualSVN5.0.1补丁原创发布
- 纯js开发防win7日历控件
不久前项目开发中遇到需要用js实现选择日期的需求,百度了下,确实一大把一大把的,但多少还是有些不符合当前需求,遂down了一份最接近的,然后修修改改,基本符合了... 先上几张效果图~~~ 需要输入时 ...
- C++数据类型和变量类型。
数据类型 数字是自由的[不只属于某个类型]!但是它可以有不同的身份!int.char.float.double等身份.它以不同的身份[存储规则]存储在内存的某个位置内部! 变量类型 内存编号是不会变的 ...
- 关于css样式1
背景色 可以使用 background-color 属性为元素设置背景色.这个属性接受任何合法的颜色值. 这条规则把元素的背景设置为灰色: p {background-color: gray;} 如果 ...
- Devexpress DateEdit控件的值不反馈到数据源的处理方式。
如果在GridControl中要把编辑的值反馈到数据源,可以用Gridview1.PostEdit()方法. 可是在datalayout中使用就会遇到一些问题:比如说DateEdit控件,在保存数据的 ...
- Data Binding和INotifyPropertyChanged是如何协调工作的?
前言 WPF的一大基础就是Data Binding.在基于MVVM架构的基础上,只有通过实现INotifyPropertyChanged接口的ViewModel才能够用于Data Binding. 要 ...