摘要:本篇文档概括性的介绍了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开发快速入门之慨述的更多相关文章

  1. Transform组件C#游戏开发快速入门

    Transform组件C#游戏开发快速入门大学霸 组件(Component)可以看作是一类属性的总称.而属性是指游戏对象上一切可设置.调节的选项,如图2-8所示.本文选自C#游戏开发快速入门大学霸   ...

  2. HealthKit开发快速入门教程之HealthKit数据的操作

    HealthKit开发快速入门教程之HealthKit数据的操作 数据的表示 在HealthKit中,数据是最核心的元素.通过分析数据,人们可以看到相关的健康信息.例如,通过统计步数数据,人们可以知道 ...

  3. HealthKit开发快速入门教程之HealthKit框架体系创建健康AppID

    HealthKit开发快速入门教程之HealthKit框架体系创建健康AppID HealthKit开发准备工作 在开发一款HealthKit应用程序时,首先需要讲解HealthKit中有哪些类,在i ...

  4. HealthKit开发快速入门教程之HealthKit开发概述简介

    HealthKit开发快速入门教程之HealthKit开发概述简介 2014年6月2日召开的年度开发者大会上,苹果发布了一款新的移动应用平台,可以收集和分析用户的健康数据.该移动应用平台被命名为“He ...

  5. Apple Watch开发快速入门教程

     Apple Watch开发快速入门教程  试读下载地址:http://pan.baidu.com/s/1eQ8JdR0 介绍:苹果为Watch提供全新的开发框架WatchKit.本教程是国内第一本A ...

  6. 游戏控制杆OUYA游戏开发快速入门教程

    游戏控制杆OUYA游戏开发快速入门教程 1.2.2  游戏控制杆 游戏控制杆各个角度的视图,如图1-4所示,它的硬件规格是本文选自OUYA游戏开发快速入门教程大学霸: 图1-4  游戏控制杆各个角度的 ...

  7. Oracle PL/SQL入门之慨述

    Oracle PL/SQL入门之慨述 一.PL/SQL出现的目的 结构化查询语言(Structured Query Language,简称SQL)是用来访问关系型数据库一种通用语言,它属于第四代语言( ...

  8. SpringBoot开发快速入门

    SpringBoot开发快速入门 目录 一.Spring Boot 入门 1.Spring Boot 简介 2.微服务 3.环境准备 1.maven设置: 2.IDEA设置 4.Spring Boot ...

  9. WPF开发快速入门【7】WPF的拖放功能(Drag and Drop)

    概述 本文描述WPF的拖放功能(Drag and Drop). 拖放功能涉及到两个功能,一个就是拖,一个是放.拖放可以发生在两个控件之间,也可以在一个控件自己内部拖放.假设界面上有两个控件,一个Tre ...

随机推荐

  1. RSA大会播报 – 2014最佳安全博客提名(国外篇)

    最佳企业安全博客提名:     Juniper(网络厂商,不用多介绍):http://forums.juniper.net/t5/Security-Mobility-Now/bg-p/networki ...

  2. MRPT笔记——使用编译好的MRPT库建立VS2013项目

    接着上一篇<MRPT在VS2013中的配置>,下面接收如何使用编译好的MRPT建立工程项目. 一.设置环境变量 上一篇中,配置MRPT时,使用到了几个相关库,opencv.zlib.wxW ...

  3. Crowd 2.7汉化中文包(原创首发)

    介绍:Crowd是用来集成Atlassian各类产品用户集成系统,如Jira,Confluence等的集中用户管理平台.可对组.成员关系.用户.目录.应用程序及权限进行综合管理,并可实现其他程序的单点 ...

  4. Fragment的onResume

    需求:Fragment每次由不可见到可见时的回调. 可能最先想到的是onResume方法,实际使用中Fragment的onResume调用时机与其Activity一致,因此类似与viewPager搭配 ...

  5. VisualSVN5.0.1补丁原创发布

    VisualSVN5.0.1补丁原创发布

  6. 纯js开发防win7日历控件

    不久前项目开发中遇到需要用js实现选择日期的需求,百度了下,确实一大把一大把的,但多少还是有些不符合当前需求,遂down了一份最接近的,然后修修改改,基本符合了... 先上几张效果图~~~ 需要输入时 ...

  7. C++数据类型和变量类型。

    数据类型 数字是自由的[不只属于某个类型]!但是它可以有不同的身份!int.char.float.double等身份.它以不同的身份[存储规则]存储在内存的某个位置内部! 变量类型 内存编号是不会变的 ...

  8. 关于css样式1

    背景色 可以使用 background-color 属性为元素设置背景色.这个属性接受任何合法的颜色值. 这条规则把元素的背景设置为灰色: p {background-color: gray;} 如果 ...

  9. Devexpress DateEdit控件的值不反馈到数据源的处理方式。

    如果在GridControl中要把编辑的值反馈到数据源,可以用Gridview1.PostEdit()方法. 可是在datalayout中使用就会遇到一些问题:比如说DateEdit控件,在保存数据的 ...

  10. Data Binding和INotifyPropertyChanged是如何协调工作的?

    前言 WPF的一大基础就是Data Binding.在基于MVVM架构的基础上,只有通过实现INotifyPropertyChanged接口的ViewModel才能够用于Data Binding. 要 ...