《电子技术应用》
您所在的位置:首页 > 其他 > 业界动态 > DirectShow过滤器组件开发技术及其应用实例

DirectShow过滤器组件开发技术及其应用实例

2009-08-14
作者:盛 健 季晓勇

  摘  要: 讨论了DirectShow过滤器组件的开发技术,给出了网络视频应用中的一个过滤器组件开发实例。

  关键词: DirectShow  过滤器  COM  视频应用

 

1  DirectShow概述

  DirectShow是Windows平台下流行的流媒体开发体系,可以实现高质量的音视频采集、编辑、编码、解码、格式转换、播放。它解决了网络音频及视频信息传输中数据量大、数据源种类多、客户端软硬件环境不确定、视频音频需要同步等问题,因此有着广泛的应用。

  DirectShow使用模块化的体系结构,最主要的组件是过滤器(Filter)。DirectShow把一系列过滤器组合起来形成DirectShow应用程序。每个过滤器提供一种功能,如获取数据源、编码、解码、播放等。DirectShow提供了很多标准过滤器,用户可以直接使用。但由于媒体格式、压缩方式、硬件属性等方面的特殊要求,用户经常需要自行开发过滤器来满足具体需求。

DirectShow应用程序中主要包含以下3种过滤器:源过滤器(Source Filter)、转换过滤器(Transform Filter)、呈现过滤器(Render Filter),分别负责获取数据流、处理数据流和播放数据流。有时还需要分解过滤器(Splitter Filter)和合并过滤器(Mux Filter)来分解和合并数据流。

  DirectShow是基于COM(组件对象模型)规范的。过滤器是一种COM组件。应用程序把多个过滤器组件组合起来,形成对媒体流的处理流程。这一整套过滤器集合被称为过滤器图(Filter Graph)。DirectShow提供FGM(Filter Graph Manager)组件来控制整个过滤器图。过滤器前后相连,连接点也是COM对象,被称为针脚(Pin)。

  DirectShow应用程序的原理图如图1所示。来自文件系统或外设的数据先由过滤器处理,再存储到文件系统或由外设播放。过滤器负责与文件系统和外设的交互。应用程序只需控制过滤器,不用关心其他软件和硬件的具体情况。

 

2 过滤器组件开发技术

  DirectShow为过滤器组件开发提供了一套基类库(Base Class Library),包括过滤器基类、针脚基类和一些辅助类。基类库为过滤器组件的开发提供了一个框架,省去了复杂的底层编码工作。用户可将开发工作集中到如下二个方面:(1)传输和处理媒体流。(2)将过滤器封装为COM组件。

2.1 媒体流的传输和处理

  为了传输数据,用户过滤器先要与过滤器图中其他过滤器连接起来。连接时要进行媒体格式和内存分配器的协调。过滤器之间通过针脚相连。过滤器之间媒体格式和内存分配器的协调实际上是通过针脚之间的通信来完成的。 

  主动连接方的过滤器的针脚首先获取自身支持的所有媒体格式,然后把其中一种格式送交给被动连接的一方。被动方的针脚进行判断:如果支持该格式,媒体格式协调成功;如果被动方不支持该格式,就通知主动方,主动方再提供1种不同的格式送交被动方,直到被动方支持被提供的格式,协调成功,否则,当主动方用完所有支持的格式,协调失败。

  DirectShow过滤器使用一种称作内存分配器(Allocator)的COM对象管理媒体流数据。当2个过滤器连接前,其中1个过滤器上的针脚提供1个内存分配器。另外1个过滤器上的针脚对这个内存分配器进行检测。当2个针脚都支持该内存分配器时,协调成功。

  媒体流传输开始之前,内存分配器负责创建一系列内存缓冲区。媒体流传输时,上游(Upstream)过滤器填充这些缓冲区,并把它们传送给下游(Downstream)过滤器。DirectShow使用一种称作媒体采样包(Media Sample)的COM对象管理单个缓冲区。通过控制媒体采样包对象,可以修改当前缓冲区中的媒体类型、时间戳等信息,也可以利用算法处理媒体数据,从而实现对媒体流的处理。

2.2 COM组件的实现

  COM组件的实现包括如下内容:用接口规定过滤器组件对外提供的功能;提供类厂,用以创建COM对象的实例;提供COM对象所在dll文件的各个辅助函数,以完成COM组件在应用程序中的载入和释放,在注册表中的注册和注销。

  DirectShow中的过滤器、针脚等COM对象通过接口对外提供各种功能。除了提供标准的接口之外,DirectShow还提供了DECLARE_INTERFACE宏让用户自定义接口,从而满足用户对过滤器组件的指定要求。

  COM实现机制中用类厂创建COM对象实例。DirectShow提供了类厂类CClassFactory和类厂模板类CFactoryTemplate。通过将不同的类厂模板的内容填入类厂,实现不同的类厂对象,从而创建不同COM对象实例。

  过滤器是dll文件格式的COM组件,需要以下函数:DllMain(载入时的入口)、DllGetClassObject(创建类厂对象)、DllCanUnloadNow(判断是否释放dll)、DllRegisterServer(在注册表中注册dll)、DllUnregisterServer(在注册表中反注册dll)。DirectShow已经实现了前3个函数。后面的2个函数通常调用DirectShow中的函数AMovieDllRegisterServer2()来实现,即:

  STDAPI DllRegisterServer()

               {   return AMovieDllRegisterServer2(TRUE );}

    STDAPI DllUnregisterServer()

                {   return AMovieDllRegisterServer2(FALSE);}

3  过滤器组件开发技术应用实例

   下面介绍过滤器组件开发技术在网络视频服务中的一个应用实例。“网络数字摄像机”系统使用摄像机采集视频,经过编码压缩后发送到网络上。客户端程序接收数据并解码。用户过滤器利用这些数据生成视频流,进行播放或者录像。“网络数字摄像机”客户端程序的基本流程如图2所示。

 

  客户端程序采用多线程的方式,网络数据接收线程、解码线程与视频流生成线程同时运行。在线程之间使用队列存放数据。前一个线程将数据写入队列,后一个线程从队列中取出数据。要实现的过滤器组件例程位于视频流生成线程内,与解码线程共享一个数据队列。此队列放在一个自定义的类CDataAdmin中。解码线程把数据放到队列中。用户过滤器从队列中取出数据,生成视频流。

3.1 用户过滤器的实现

  (1)选择合适的基类

  用户过滤器使用整个过滤器图外部的数据生成视频流,属于源过滤器。基类库中的CSource类是源过滤器的基类,CSource使用CSourceStream基类作为它的针脚。本例中从这2个类派生出CCustomFilter和CCustomPin,作为实际使用的过滤器类和针脚类。

  (2)通过自定义接口获得队列数据

  为了获得过滤器外部的队列数据,需要为CCustomFilter提供一个自定义的接口。下面的代码定义了一个IDataSource接口:DECLARE_INTERFACE_(IDataSource,IUnknown){STDMETHOD(SetData)(THIS_CDataAdmin*pData)PURE;}。CCustomFilter继承该接口,对外提供了一个SetData()操作。SetData()将外部传入的CDataAdmin*类型的指针赋值给CCustomFilter的成员变量,过滤器即获取到外部队列数据。

  (3)协调媒体类型

  CSourceStream基类完成了媒体类型协调中大部分的工作,用户只需要指定过滤器针脚支持的媒体格式。CSourceStream的成员函数GetMediaType()负责完成这个任务,用户必须在该函数中为过滤器指定媒体格式。媒体流的信息存放在一个VIDEOINFOHEADER的结构中,指针pvi指向该结构。函数GetMediaType()中指定媒体格式的代码如下:

  pMediaType->SetType(&MEDIATYPE_Video);

    //设置媒体主类型

  pMediaType->SetSubtype(&GetBitmapSubtype(&pvi->bmiHeader));  //设置媒体次类型

  pMediaType->SetFormatType(&FORMAT_VideoInfo);

                                  //设置媒体格式

  pMediaType->SetTemporalCompression(FALSE);

                                  //不压缩媒体流

  pMediaType->SetSampleSize(pvi->bmiHeader.biSizeImage);     //设置媒体采样包大小

  (4)协调内存分配器

  CSourceStream基类完成了大多数内存分配器的协调工作。用户还需要指定每个媒体采样包的大小。CSourceStream基类的成员函数DecideBufferSize()负责完成此任务。下面是该函数中的主要代码。

  pRequest->cbBuffer=pvi->bmiHeader.biSizeImage;

                                  //获取采样包大小需求信息

  ALLOCATOR_PROPERTIES Actual;

  hr=pAlloc->SetProperties(pRequest,&Actual);

                                  //指定采样包大小,并返回实际的设置结果

  (5)生成视频流

  CSourceStream基类的FillBuffer()成员函数负责把外部队列数据加入到视频流中。用户可以在此函数内部先处理数据,再把处理过的数据加入视频流中。本例中经用户过滤器解码后的数据,不需要进行处理。函数FillBuffer()中的主要代码如下。

                                 //获取当前媒体采样包对应的缓冲区的地址和大小

  BYTE*pData;

  DWORD cbData;

  pSample->GetPointer(&pData);

  cbData=pSample->GetSize();

                                  //获取媒体信息

  VIDEOINFOHEADER*pVih=(VIDEOINFOHEADER*)

  m_mt.pbFormat;

                                 //从数据队列中取出数据填充到当前缓冲区中

  m_pFilePack=m_pPinData->GetDataBuffer();

  memcpy(pData,m_pFilePack,min(pVih->

  bmiHeader.biSizeImage,cbData));

                                 //给媒体采样包加上时间戳

  REFERENCE_TIME rtStart=m_iFrameNumber

  *m_rtFrameLength;

  REFERENCE_TIME rtStop=rtStart+m_rtFrameLength;

  pSample->SetTime(&rtStart,&rtStop);

                                  //帧计数器加1

  m_iFrameNumber++;

  (6)生成COM组件

  过滤器开发工作的最后一步是将过滤器封装成COM组件。此外,需要提供类厂模板。代码如下:

  CFactoryTemplate g_Templates[]={g_wszCustomFilter,

  &CLSID_CustomFilter,CCustomFilter∷CreateInstance,

  NULL,NULL };                          //将过滤器信息填入类厂模板

  int g_cTemplates=sizeof(g_Templates)/sizeof(g_Templates[0]);  //类厂模板个数

3.2 实际应用效果

    在“网络数字摄像机”系统的客户端应用程序中使用上例的过滤器组件,若连接到视频播放过滤器(Video Renderer)则可播放视频,播放效果如图3所示;若连接到写文件过滤器(File Writer),可将视频直接写成硬盘文件,实现视频录像。过滤器采用COM组件的形式,可方便地移植到其他机器和应用程序中。

4  结束语

  过滤器组件在目前多种多样的音频视频流媒体应用中发挥着重要作用。过滤器组件的开发具有较大的实用价值,但有一定的难度和复杂性。本文讨论了用户过滤器开发中的原理和技术。文中过滤器组件例子的开发过程具有较大的通用性,可供其他开发者参考。

 

参考文献

1  Kruglinski D J.VC++技术内幕(第4版).北京:清华大学出版社,1999

2  潘爱民.COM原理和应用.北京:清华大学出版社,1999

本站内容除特别声明的原创文章之外,转载内容只为传递更多信息,并不代表本网站赞同其观点。转载的所有的文章、图片、音/视频文件等资料的版权归版权所有权人所有。本站采用的非本站原创文章及图片等内容无法一一联系确认版权者。如涉及作品内容、版权和其它问题,请及时通过电子邮件或电话通知我们,以便迅速采取适当措施,避免给双方造成不必要的经济损失。联系电话:010-82306118;邮箱:aet@chinaaet.com。