Chinaunix首页 | 论坛 | 博客
  • 博客访问: 2087023
  • 博文数量: 909
  • 博客积分: 4000
  • 博客等级: 上校
  • 技术积分: 12260
  • 用 户 组: 普通用户
  • 注册时间: 2008-05-06 20:50
文章分类

全部博文(909)

文章存档

2008年(909)

我的朋友

分类:

2008-05-06 22:13:55

一起学习
利用 DirectShow 开发自己的 Filter

作者:智慧的鱼

源代码下载

  学习directshow已经有几天了,下面将自己的学习心得写下来,希望对其他的人有帮助。 Filter实质是个COM组件,所以学习开发Filter之前你应该对com的知识有点了解。Com组件的实质是一个实现了纯虚指针接口的C 对象。 关于com的东西,这里不多讲。

一、给vc配置DirectShow的开发环境

  无论开发Filter还是开发Dshow的应用程序都要配置一下开发环境的,其实就是包含一下dshow用到的头文件和动态库。 选择Tools菜单下面的Options。在弹出的Option对话框配置如下:



图1 添加头文件

选择动态库文件添加到工程中

图2 添加动态库

二、创建工程以及Filter的入口函数

创建工程:
   一般情况下,创建Filter使用一个普通的Win32 DLL项目。而且,一般Filter项目不使用MFC。这时,应用程序通过CoCreateInstance函数Filter实例; Filter与应用程序在二进制级别的协作。另外一种方法,也可以在MFC的应用程序项目中创建Filter。
在vc里新建一个工程,选择win32动态库,如下图

图3


图4

   这样生成了一个简单的DLL,只有一个Dllmain入口函数。 下面我要给这个filter添加入口函数了。 Filter是个基于DLL的com组件,所以一般的Filter都要实现下面几个入口函数:

DllMain 

DllGetClassObject 

DllCanUnloadNow 

DllRegisterServer 

DllUnregisterServer

首先定义导出函数:
  要导出这些函数有两种方法,一是在定义函数时使用导出关键字_declspec(dllexport),另外一种方法是在创建DLL文件时使用模块定义文件.Def。 使用导出函数关键字_declspec(dllexport)创建MyDll.dll就是在 .h文件中定义定义函数如下:

extern "C" _declspec(dllexport)BOOL DllRegisterServer; 等等 

   为了用.def文件创建DLL,往该工程中加入一个文本文件,命名为MyDll.def,再在该文件中加入如下代码:

LIBRARY MyFilter.ax 

EXPORTS

DllMain PRIVATE

DllGetClassObject PRIVATE

DllCanUnloadNow PRIVATE

DllRegisterServer PRIVATE

DllUnregisterServer PRIVATE

   其中LIBRARY语句说明该def文件是属于相应DLL的,EXPORTS语句下列出要导出的函数名称。我们可以在.def文件中的导出函数后加@n,如Max@1,Min@2, 表示要导出的函数顺序号,在进行显式连时可以用到它。该DLL编译成功后,打开工程中的Debug目录,同样也会看到MyDll.dll和MyDll.lib文件。
   然后要定义这些函数的实现了,其实这些工作dshow的基类里都已经替我们做好了,我们所要做的就拿来用就是了,最重要的三个函数的实现一般如下  

STDAPI DllRegisterServer()

{

    return AMovieDllRegisterServer2(TRUE);

}

 STDAPI DllUnregisterServer()

{

    return AMovieDllRegisterServer2(FALSE);

} 

 extern "C" BOOL WINAPI DllEntryPoint(HINSTANCE, ULONG, LPVOID);

BOOL APIENTRY DllMain(HANDLE hModule, DWORD  dwReason,   LPVOID lpReserved)

{

	 return DllEntryPoint((HINSTANCE)(hModule), dwReason, lpReserved);

 

}
   其中DllEntryPoint 是在C:\DX90SDK\Samples\C \DirectShow\BaseClasses\dllentry.cpp定义的,如果感兴趣我们可以去看看它的定义。 AMovieDllRegisterServer2函数是在下面 C:\DX90SDK\Samples\C \DirectShow\BaseClasses\dllsetup.cpp这个文件定义的,具体实现可以自己看看。
   到了这里你恐怕要做点工作,还是要设置一下你的项目环境,否则恐怕你编译是通不过的,因为你用到了基类的一些东西,所以你要将你的dshow基类的定义和库文件包含进来。 首先包含:
#include Streams.h 

其次在Project –Setting菜单下配置自己的Filter输出的名字和连接的lib文件


图5

其中library modules里的包含的动态库如下
c:\DX90SDK\Samples\C \DirectShow\BaseClasses\debug\strmbasd.lib msvcrtd.lib quartz.lib vfw32.lib winmm.lib kernel32.lib advapi32.lib version.lib largeint.lib user32.lib gdi32.lib comctl32.lib ole32.lib olepro32.lib oleaut32.lib uuid.lib

   此时你编译一下,好像还是通不过,它提示有一个全局的用于实现COM接口的变量没有定义,不着急,下面我们就开始实现Filter的com接口。

三、如何实现Filter 的类厂对象

   我们知道一个Filter是一个com组件,所以它com特性的实现其实在其基类中实现的,比如IUnknown接口,我们直接从基类派生出我们的Filter后,它就支持com接口了,它就是一个com组件了。
  所有的com组件为了实现二进制的封装,所以连创建的接口都封装了,因此每个com对象都有个类对象(也叫类厂对象,本身也是com对象,用来创建com组件)来创建com组件。
下面温习一下com组件的创建过程,其中涉及到几个函数:
 

  1. 当客户端要创建一个com组件时,它通过底层的COM API函数 CoGetClassObject()使用SCM的服务,这个函数请SCM把一个指针绑定到客户端请求的com组件的类对象上, 其实在CoGetClassObject()里它装载了该DLL的库,通过该dll的导出函数DllGetClassObject();DllGetClassObject根据客户端提供的com组件CLASSID,返回该com组件类对象的指针。下面com组件的创建和SCM无关了。
  2. 客户端利用组件的类对象(类厂对象)的IClassFactory::CreateInstance方法创建com组件。
    Filter在这里使用了一个类厂模板类来当作Filter的类厂对象。下面看看类厂在DShow是怎么工作的。  
   类厂对象也是一个com组件。本来DllGetClassObject是应该由我们自己完成一个函数,在directshow基类里已经完成了,我们不用管它了。它的功能就是来寻找这个DLL中的类厂对象,看是否有符合客户端请求的类厂对象。
  DLL里声明了一个全局的类厂模板数组,当DllGetClassObject请求类厂对象的时候,它就搜索这个数组,看是否有和CLSID匹配的类厂对象。当它找到一个匹配的CLSID,它就创建一个类厂对象,然后讲类厂指针返回给CoGetClassObject, 然后客户端可以根据返回去的类厂指针,调用 IClassFactory::CreateInstance方法创建组件,类厂就根据数组里定义的方法创建com组件。
factory template包含下列变量:
const WCHAR * m_Name; // Name

const CLSID * m_ClsID; // CLSID

LPFNNewCOMObject m_lpfnNew; // Function to create an instance of the component

LPFNInitRoutine m_lpfnInit; // Initialization function (optional)

const AMOVIESETUP_FILTER * m_pAMovieSetup_Filter; // Set-up information (for filters)

其中的两个函数指针m_lpfnNew and m_lpfnInit使用下面的定义:

typedef CUnknown *(CALLBACK *LPFNNewCOMObject)(LPUNKNOWN pUnkOuter, HRESULT *phr);

typedef void (CALLBACK *LPFNInitRoutine)(BOOL bLoading, const CLSID *rclsid);

你可以参照如下的方式定义你的类厂对象:

CUnknown * WINAPI CMyFilter::CreateInstance(LPUNKNOWN pUnk, HRESULT *pHr) 

{  

    CMyFilter *pFilter = new CMyFilter(NAME("my Filter"), pUnk, pHr);

    if (pFilter== NULL) 

    {

        *pHr = E_OUTOFMEMORY;

    }

    return pFilter;

}
你可以声明自己的类厂数组如下:
CFactoryTemplate g_Templates[1] = 

{

    { 

      L"my filter",                // Name

      &CLSID_MYFilter,             // CLSID

      CMyFilter::CreateInstance,   // Method to create an instance of MyComponent

      NULL,                           // Initialization function

      &sudInfTee                            // Set-up information (for filters)

    }

};

int g_cTemplates = sizeof(g_Templates) / sizeof(g_Templates[0]);  
如果在这个com组件中你要支持多个filter,你可以在这个数组中继续添加就是了。

四、如何实现自己的 Filter

   在这里就要讲如何创建自己的Filter了,下面我们以写一个CTransformFilter为例:

1、选择一个基类,声明自己的类。

   创建filter很简单,你只要根据自己的需要选择不同的基类Filter派生出自己的Filter,它就已经支持com特性了。
  从逻辑上考虑,在写Filter之前,选择一个合适的Filter基类是至关重要的。为此,你必须对几个Filter的基类有相当的了解。 在实际应用中,Filter的基类并不总是选择CBaseFilter的。相反,因为我们绝大部分写的都是中间的传输Filter(Transform Filter),所以基类选择CTransformFilter和CTransInPlaceFilter的居多。如果我们写的是源Filter,我们可以选择CSource作为基类;如果是Renderer Filter,可以选择CBaseRenderer或CBaseVideoRenderer等。
  总之,选择好Filter的基类是很重要的。当然,选择Filter的基类也是很灵活的,没有绝对的标准。能够通过CTransformFilter实现的Filter当然也能从CBaseFilter一步一步实现。
   下面笔者就从本人的实际经验出发,对Filter基类的选择提出几点建议供大家参考。首先,你必须明确这个Filter要完成什么样的功能,即要对Filter项目进行需求分析。请尽量保持Filter实现的功能的单一性。如果必要的话,你可以将需求分解,由两个(或者更多的)功能单一的Filter去实现总的功能需求。
   其次,你应该明确这个Filter大致在整个Filter Graph的位置,这个Filter的输入是什么数据,输出是什么数据,有几个输入Pin、几个输出Pin等等。你可以画出这个Filter的草图。弄清这一点十分重要,这将直接决定你使用哪种“模型”的Filter。比如,如果Filter仅有一个输入Pin和一个输出Pin,而且一进一处的媒体类型相同,则一般采用CTransInPlaceFilter作为Filter的基类;如果媒体类型不一样,则一般选择CTransformFilter作为基类。
   再者,考虑一些数据传输、处理的特殊性要求。比如Filter的输入和输出的Sample并不是一一对应的,这就一般要在输入Pin上进行数据的缓存,而在输出Pin上使用专门的线程进行数据处理。这种情况下,Filter的基类选择CSource为宜(虽然这个Filter并不是源Filter)。 当Filter的基类选定了之后,Pin的基类也就相应选定了。接下去,就是Filter和Pin上的代码实现了。有一点需要注意的是,从软件设计的角度上来说,应该将你的逻辑类代码同Filter的代码分开。下面,我们一起来看一下输入Pin的实现。你需要实现基类所有的纯虚函数,比如CheckMediaType等。在CheckMediaType内,你可以对媒体类型进行检验,看是否是你期望的那种。因为大部分Filter采用的是推模式传输数据,所以在输入Pin上一般都实现了Receive方法。有的基类里面已经实现了Receive,而在Filter类上留一个纯虚函数供用户重载进行数据处理。这种情况下一般是无需重载Receive方法的,除非基类的实现不符合你的实际要求。而如果你重载了Receive方法,一般会同时重载以下三个函数EndOfStream、BeginFlush和EndFlush。我们再来看一下输出Pin的实现。一般情况下,你要实现基类所有的纯虚函数,除了CheckMediaType进行媒体类型检查外,一般还有DecideBufferSize以决定Sample使用内存的大小,GetMediaType提供支持的媒体类型。
   最后,我们看一下Filter类的实现。首先当然也要实现基类的所有纯虚函数。除此之外,Filter还要实现CreateInstance以提供COM的入口,实现NonDelegatingQueryInterface以暴露支持的接口。如果我们创建了自定义的输入、输出Pin,一般我们还要重载GetPinCount和GetPin两个函数。
这里我主要为了举例,所以简单写的filter没有Pin接口,但在我的demo里的Filter,却是有个out pin和一个input pin。我的Filter类的定义如下:
class CMyFilter :  public CCritSec, public CBaseFilter

{

public:

	CMyFilter(TCHAR *pName,LPUNKNOWN pUnk,HRESULT *hr);

	virtual ~CMyFilter(); 

    static CUnknown * WINAPI CreateInstance(LPUNKNOWN pUnk, HRESULT *phr); 

    CBasePin *GetPin(int n);

    int GetPinCount();  

}  
注:因为基类是一个纯虚的基类,所以在你的filter一定要派生一个其中的纯虚函数,否则编译器会提示你的派生类也是一个纯虚类, 你在创建这个com组件对象的时候,纯虚类是没法创建对象的。

2、给自己的Filter生成一个CLSID

   你可以用Guidgen or Uuidgen给自己的Filter生成一个128位的ID号,然后利用DEFINE_GUID宏在Filter的头文件声明该Filter的CLSID;
[myFilter.h]

// {1915C5C7-02AA-415f-890F-76D94C85AAF1}

DEFINE_GUID(CLSID_MYFilter, 

0x1915c5c7, 0x2aa, 0x415f, 0x89, 0xf, 0x76, 0xd9, 0x4c, 0x85, 0xaa, 0xf1);

这个CLSID_MYFilter在类厂数组用到,在注册Filter时也要用到。

3、CMyFilter类的简单实现

   这个类纯粹为了演示用,所以特别简单,你可以参考我的demo,那个filter写的功能比较全。

CMyFilter::CMyFilter(TCHAR *pName,LPUNKNOWN pUnk,HRESULT *hr)

			:CBaseFilter(NAME("my filter"), pUnk, this, CLSID_MYFilter) 

{ }

CMyFilter::~CMyFilter()

{} 



// Public method that returns a new instance. 

CUnknown * WINAPI CMyFilter::CreateInstance(LPUNKNOWN pUnk, HRESULT *pHr) 

{  

    CMyFilter *pFilter = new CMyFilter(NAME("my Filter"), pUnk, pHr);

    if (pFilter== NULL) 

    {

        *pHr = E_OUTOFMEMORY;

    }

    return pFilter;

} 

 

CBasePin * CMyFilter::GetPin(int n) 

{

   return NULL;

}

int CMyFilter::GetPinCount() 

{

	 return 0;

}
   这样基本上就实现了一个filter,但是这个filter没有与之相联系的PIN,但是实现Filter的基本过程就时这样了,至于逻辑上的东西,比如Filter和pin如何连接,数据流是如何流动的,你都要去看看sdk了,按照上面的步骤你就可以写一个Filter的框架出来。
   下面我们总结一下写一个Filter至少需要那些东西。

1、Filter的实现类
   在这里就是CMyFilter类,在这个类里你可以实现自己的逻辑上的功能,包括定义你的filter的特性,给你的filter配备pin接口等。

2 com组件的引出函数, 五个全局函数:

DllMain //dll的入口函数

DllGetClassObject //获得com组件的类厂对象

DllCanUnloadNow //com组件是否可以卸载 

DllRegisterServer //注册com组件 

DllUnregisterServer //卸载com组件

其中DllGetClassObject 已经由基类完成,你自己只要完成三个函数即可,

DllMain,DllRegisterServer,DllUnregisterServer。

3、com组件的类厂对象

   类厂对象是用来生成Filter对象的,用的模板类定义了一个全局的模板类对象数组,一般格式如下:

CFactoryTemplate g_Templates[1] = 

{

    { 

      L"my filter",                // Name

      &CLSID_MYFilter,             // CLSID

      CMyFilter::CreateInstance,   // Method to create an instance of MyComponent

      NULL,                           // Initialization function

      &sudInfTee                            // Set-up information (for filters)

    }

};

int g_cTemplates = sizeof(g_Templates) / sizeof(g_Templates[0]);  
4、关于你自己定义的Filter以及Pin的信息

   这些是一个全局的结构变量,用于描述你的Filter和你定义的pin,在注册Filter的时候会用到,如下:
AMOVIESETUP_FILTER 描述一个Filter

AMOVIESETUP_PIN 描述pin

AMOVIESETUP_MEDIATYPE 描述数据类型

下面的代码描述了一个Filter带有一个output PIN:

static const WCHAR g_wszName[] = L"Some Filter";

AMOVIESETUP_MEDIATYPE sudMediaTypes[] = {

    { &MEDIATYPE_Video, &MEDIASUBTYPE_RGB24 },

    { &MEDIATYPE_Video, &MEDIASUBTYPE_RGB32 },

};

AMOVIESETUP_PIN sudOutputPin = {

    L"",            // Obsolete, not used.

    FALSE,          // Is this pin rendered?

    TRUE,           // Is it an output pin?

    FALSE,          // Can the filter create zero instances?

    FALSE,          // Does the filter create multiple instances?

    &GUID_NULL,     // Obsolete.

    NULL,           // Obsolete.

    2,              // Number of media types.

    sudMediaTypes   // Pointer to media types.

};



AMOVIESETUP_FILTER sudFilterReg = {

    &CLSID_SomeFilter,      // Filter CLSID.

    g_wszName,              // Filter name.

    MERIT_NORMAL,           // Merit.

    1,                      // Number of pin types.

    &sudOutputPin           // Pointer to pin information.

};
最后如果你还是调试通不过,看看你是否包含了下面的头文件:
#include  

#include 

#include 

#include 

智慧的鱼 aoosang 2004-09-01

下载本文示例代码


利用 DirectShow 开发自己的 Filter利用 DirectShow 开发自己的 Filter利用 DirectShow 开发自己的 Filter利用 DirectShow 开发自己的 Filter利用 DirectShow 开发自己的 Filter利用 DirectShow 开发自己的 Filter利用 DirectShow 开发自己的 Filter利用 DirectShow 开发自己的 Filter利用 DirectShow 开发自己的 Filter利用 DirectShow 开发自己的 Filter利用 DirectShow 开发自己的 Filter利用 DirectShow 开发自己的 Filter
阅读(258) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~