利用 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这个文件定义的,具体实现可以自己看看。
#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组件的创建过程,其中涉及到几个函数:
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,你可以在这个数组中继续添加就是了。
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组件对象的时候,纯虚类是没法创建对象的。
[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的框架出来。
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的信息
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 <streams.h> #include <initguid.h> #include <tchar.h> #include <stdio.h>
智慧的鱼 aoosang 2004-09-01