利用DirectShow开发自己的Filter(最简单,最基础,不带Pin的Filter)

学习directshow已经有几天了,下面将自己的学习心得写下来,希望对其他的人有帮助。

Filter实质是个COM组件,所以学习开发Filter之前你应该对com的知识有点了解。Com组件的实质是一个实现了纯虚指针接口的C++对象。关于com的东西,这里不多讲。

1 给VC配置DShow的开发环境

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

 添加头文件

ASPectratio="t" v:ext="edit">

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

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

创建工程

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

vc里新建一个工程,选择win32动态库,如下图;

 

这样生成了一个简单的DLL,只有一个Dllmain入口函数。

下面我要给这个filter添加入口函数了。

Filter是个基于DLLcom组件,所以一般的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@1Min@2,表示要导出的函数顺序号,在进行显式连时可以用到它。该DLL编译成功后,打开工程中的Debug目录,同样也会看到MyDll.dllMyDll.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文件

其中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接口的变量没有定义,不着急,下面我们就开始实现Filtercom接口。

3 如何实现Filter 的类厂对象

本节内容要讲一下Filter是如何实现com接口的,它和其他的com实现方式的不同。

我们知道一个Filter是一个com组件,所以它com特性的实现其实在其基类中实现的,比如Perlink>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包含下列变量:

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

 

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

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]);    

4 如何实现自己的Filter

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

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

创建filter很简单,你只要根据自己的需要选择不同的基类Filter派生出自己的Filter,它就已经支持com特性了。 

从逻辑上考虑,在写Filter之前,选择一个合适的Filter基类是至关重要的。为此,你必须对几个Filter的基类有相当的了解。在实际应用中,Filter的基类并不总是选择CBaseFilter的。相反,因为我们绝大部分写的都是中间的传输FilterTransform Filter),所以基类选择CTransformFilterCTransInPlaceFilter的居多。如果我们写的是源Filter,我们可以选择CSource作为基类;如果是Renderer Filter,可以选择CBaseRendererCBaseVideoRenderer等。
  
总之,选择好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的基类也就相应选定了。接下去,就是FilterPin上的代码实现了。有一点需要注意的是,从软件设计的角度上来说,应该将你的逻辑类代码同Filter的代码分开。下面,我们一起来看一下输入Pin的实现。你需要实现基类所有的纯虚函数,比如CheckMediaType等。在CheckMediaType内,你可以对媒体类型进行检验,看是否是你期望的那种。因为大部分Filter采用的是推模式传输数据,所以在输入Pin上一般都实现了Receive方法。有的基类里面已经实现了Receive,而在Filter类上留一个纯虚函数供用户重载进行数据处理。这种情况下一般是无需重载Receive方法的,除非基类的实现不符合你的实际要求。而如果你重载了Receive方法,一般会同时重载以下三个函数EndOfStreamBeginFlushEndFlush。我们再来看一下输出Pin的实现。一般情况下,你要实现基类所有的纯虚函数,除了CheckMediaType进行媒体类型检查外,一般还有DecideBufferSize以决定Sample使用内存的大小,GetMediaType提供支持的媒体类型。

最后,我们看一下Filter类的实现。首先当然也要实现基类的所有纯虚函数。除此之外,Filter还要实现CreateInstance以提供COM的入口,实现NonDelegatingQueryInterface以暴露支持的接口。如果我们创建了自定义的输入、输出Pin,一般我们还要重载GetPinCountGetPin两个函数。

  这里我主要为了举例,所以简单写的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的头文件声明该FilterCLSID;

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

DEFINE_GUID(CLSID_MYFilter,

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

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的基本过程就时这样了,至于逻辑上的东西,比如Filterpin如何连接,数据流是如何流动的,你都要去看看sdk了,按照上面的步骤你就可以写一个Filter的框架出来。

 下面我们总结一下写一个Filter至少需要那些东西。

1 Filter的实现类

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

2 com组件的引出函数

五个全局函数

   DllMain                //dll的入口函数

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

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

   DllRegisterServer        //注册com组件   

   DllUnregisterServer      //卸载com组件

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

3 com组件的类厂对象

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

CFactoryTemplate g_Templates[1] =

{

    {

      L"my filter",                // Name

      &CLSID_MYFilter,             // FilterCLSID

      CMyFilter::CreateInstance,      // 创建Filter的方法

      NULL,                        // Initialization function

      &sudInfTee                   //Filter的信息(一个结构)

    }

};

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.

};

最后sudFilterReg会在类厂对象数组中用到。

CFactoryTemplate g_Templates[1] =

{

    {

      L"my filter",                // Name

      &CLSID_MYFilter,             // CLSID

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

      NULL,                           // Initialization function

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

    }

};

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

#include <streams.h>   #include <initguid.h>

#include <tchar.h>     #include <stdio.h>

以下提供Filter的.h和.CPP文件

/*FilterTest.h*/


#if !defined(AFX_FILTERTEST1_H__7401629D_FA1F_4728_9A3A_7CB605777860__INCLUDED_)
#define AFX_FILTERTEST1_H__7401629D_FA1F_4728_9A3A_7CB605777860__INCLUDED_

#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000

class FilterTest : public CCritSec, public CBaseFilter
{
public:
    int GetPinCount();
    CBasePin * GetPin(int n);
    static CUnknown * WINAPI CreateInstance(LPUNKNOWN pUnk, HRESULT *pHr);
    FilterTest(TCHAR *tszName, LPUNKNOWN punk, HRESULT *phr);
    virtual ~FilterTest();
};

#endif // !defined(AFX_FILTERTEST1_H__7401629D_FA1F_4728_9A3A_7CB605777860__INCLUDED_)


 


 

/*FilterTest.CPP*/

// FilterTest1.cpp: implementation of the FilterTest class.
//
//////////////////////////////////////////////////////////////////////
#include <streams.h>  
#include <initguid.h>
#include <tchar.h>    
#include <stdio.h>

#include "FilterTest.h"

//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////

// {7FBB207C-1861-4191-89F2-7A07F25F32DD}
DEFINE_GUID(CLSID_FilterTest,
0x7fbb207c, 0x1861, 0x4191, 0x89, 0xf2, 0x7a, 0x7, 0xf2, 0x5f, 0x32, 0xdd);


FilterTest::FilterTest(TCHAR *tszName, LPUNKNOWN punk, HRESULT *phr)
:CBaseFilter(NAME("FilterTest"), punk, this, CLSID_FilterTest)
{

}

FilterTest::~FilterTest()
{

}

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_FilterTest,      // Filter CLSID.
    L"FilterTest",              // Filter name.
    MERIT_NORMAL,           // Merit.
    1,                      // Number of pin types.
    &sudOutputPin           // Pointer to pin information.
};

CFactoryTemplate g_Templates[1] =

{   
    {   
        L"FilterTest",                    // Name
        &CLSID_FilterTest,                // CLSID
        FilterTest::CreateInstance,        // Method to create an instance of MyComponent
        NULL,                           // Initialization function
        &sudFilterReg                   // Set-up information (for filters)   
    }
};

int g_cTemplates = sizeof(g_Templates) / sizeof(g_Templates[0]);

CUnknown * WINAPI FilterTest::CreateInstance(LPUNKNOWN pUnk, HRESULT *pHr)
{
    FilterTest *pFilter = new FilterTest(NAME("FilterTest"), pUnk, pHr);
   
    if (pFilter== NULL)
    {
        *pHr = E_OUTOFMEMORY;
    }
   
    return pFilter;   
}

CBasePin * FilterTest::GetPin(int n)
{
    return NULL;
}

int FilterTest::GetPinCount()
{
    return 0;
}

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);
}

#pragma warning( disable:4514)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章