皓月云天

紧张中保持一份松弛

松弛中保持一份紧张

  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

【转载】编写DirectShow Filters—编写transform filters

Posted on 2011-08-02 05:36  皓月云天  阅读(1908)  评论(0编辑  收藏  举报

本节描述如何编写一个transform filter,这种filter一般要有一个输入PIN和一个输出PIN。为了演示这些步骤,本节描述一个假定的transform filter,它输出run-length编码(RLE)视频。并不描述RLE-编码算法本身。(DirectShow已经通过avi compressor filter提供一个RLE codec。)
本章假定你将使用DirectShow基类库来建立filter。虽然你可以写一个不用它的filter,但强烈推荐使用基库。
注意 在写一个transform filter之前,认为是否一个DirectX Media Object(DMO)可以满足你的需求。DMOs可以做和filter同样的事情,并且DMOs的编码模型更简单。DMO是在DirectShow中通过DMO wrapper filter,但也可以在DirectShow之外使用。DMOs现在推荐为编码器和解码器的解决方案。
步骤1。选择一个基类
假定你决定写一个filter,第一步是选择一个基类来使用。transform filter的基类如下:
•   CTransformFitler设计用于使用单独的输出和输入缓冲区的transform filter。这种类型的filter有时叫做copy-transform filter。当一个copy-transform filter接收一个输入采样时,它改写新的数据到一个输出采样并且传递这个输出采样到下一个filter。
•   CTransInPlaceFilter是为修改在原始缓冲区中的数据而设计的filter,也叫做trans-in-place filters。当一个trans-in-place filter接收一个采样,它在内部改变采样数据并且传递这个采样到下一级。这个filter的输入PIN和输出PIN一直用匹配的媒体类型连接。
•   CVieoTransformFilter主要为视频decoders设计的。它继承自CTransformFilter,但包括如downstram renderer在接下来失败将丢帧的功能性。
•   CBaseFitler是一个一般的类。在这个列表中其它类全继承自CBaseFilter。如果上面的基类没有合适的,你可以用这个类。当然,这需要做更多的工作。
重要的 in-place video transform 在render性能上可能有一个严重影响。in-place transform需要在缓冲区上的read-modify-write操作。如果这个内存位置是在一个图形卡上,读操作比较慢。而且,如果不小心的使用它即使一个copy transform也可能引起无法预测的读操作。因此,写一个视频transform应该做好性能测试。
对于RLE编码器例子,最好选择CTransformFilter或者CVideoTransformFilter。实际上,在内部二者区别很大,所以容易从一种转换成另一种。因为在两个PIN上媒体类型必须不同,这个filter不适合用CTransInPlaceFilter。这个例子将使用CTransformFilter。
步骤2.声明这个filter类
通过声明一个继承这个基类的C++类开始:
class CRleFilter : public CTransformFilter
{
    /* Declarations will go here. */
};
这种fitler类有关联的PIN类。根据你的filter需要来指定,可以重载PIN类。在CTransformFilter情况下,PIN聚合了这个filter的大部分工作,所以可以不需要去重载这个PINs。
必须为这个filter生成一个独一无二的CLSID。可以使用Guidgen或者unidgen工具,不能拷贝一个存在的GUID。这里有几种方式声明一个CLSID。下列代码使用DEFINE_GUID宏:
 [RleFilt.h]
// {1915C5C7-02AA-415f-890F-76D94C85AAF1}
DEFINE_GUID(CLSID_RLEFilter,
0x1915c5c7, 0x2aa, 0x415f, 0x89, 0xf, 0x76, 0xd9, 0x4c, 0x85, 0xaa, 0xf1);
 [RleFilt.cpp]
#include <initguid.h>
#include "RleFilt.h"
接下来,为这个filter编写一个构造器方法:
CRleFilter::CRleFilter()
  : CTransformFilter(NAME("My RLE Encoder"), 0, CLSID_RLEFilter)
{
   /* Initialize any private variables here. */
}
注意CTransformFilter构造器一个参数是先前定义的CLSID。
步骤3.支持媒体类型协商
当两个PIN连接时,这个连接的媒体类型必须一致。媒体类型描述数据的格式。
协商媒体类型的基本机制是IPin::ReceiveConnection方法。这个输出PIN用被推荐的媒体类型在输入PIN调用这个方法。输入PIN接受或拒绝这个连接。如果拒绝这个连接,输出PIN尝试另一个媒体类型。 如果没有合适的类型,连接失败。作为可选的,输入PIN可以列举它接受的类型一个列表,通过IPin::EnumMediaTypes方法。当它使用媒体类型时这个输出PIN可以使用这个列表,虽然它不必这么做。
如下CTransformFilter类为这个处理实现一个基本框架:
•   输入PIN没有首选的媒体类型。它全部依赖上一级filter所使用的的媒体类型。对于视频数据,这个容易理解,因为这个媒体类型包括图形的尺寸和帧率。典型的,这些信息必须由上一级source filter或parser filter提供。在音频数据的情况中,格式的设置更简单,所以这个输入PIN部分提供倾向的类型。在这种情况下,重载在这个输入PIN上的CBasePin::GetMediaType。
•   当upstream filter提交一个媒体类型,输入PIN调用CTransformFitler::CheckInputType方法,它接收或拒绝这个类型。
•   除非输入PIN首选被连接否则输出PIN将不连接。这是transform filters的典型行为。在大多数情况下,filter必须在设置输出类型前决定输入类型。
•   当输出PIN连接时,它有一个媒体类型列表,它提交到downstream filter。调用CTransformFilter::GetMediaType方法来产生这个列表。这个输出PIN将尝试downsram fitler倾向的任何媒体类型。
•   为了检查一个选定输出类型是否兼容于输入类型,输出PIN调用CTransformFilter::CheckTransform方法。
前面列表的三个CTransformFilter方法是纯虚方法,所以继承类必须实现它们。它们不属于COM接口;基类提供了简单的部分实现。
步骤3A 实现CheckInputType方法
当上一级 filter发送一个媒体类型到transform filter时,CTransformFilter::CheckInputType方法被调用。这个方法有一个指向一个CMediaType对象的指针,此对象是对于AM_MEDIA_TYPE结构的简单包装。在这个方法中,应该查检每个AM_MEDIA_TYPE结构的相关值,包括在格式块。可以使用在CMediaType中定义的accessor方法,或者直接引用结构成员。如果有任何值不是有效的,返回VFW_E_TYPE_NOT_ACCEPTED。如果全部媒体类型是有效的,返回S_OK。
例如,在RLE编码器filter中,输入类型必须是8位或4位未压缩RGB视频。不支持其它输入格式,如16或24位RGB,因为此filter转换为一个更低的位深,因为DirectShow已经提供Color space converter filter。下列代码假定这个编码器支持8位视频而不是4位视频:
HRESULT CRleFilter::CheckInputType(const CMediaType *mtIn)
{
    if ((mtIn->majortype != MEDIATYPE_Video) ||
        (mtIn->subtype != MEDIASUBTYPE_RGB8) ||
        (mtIn->formattype != FORMAT_VideoInfo) ||
        (mtIn->cbFormat < sizeof(VIDEOINFOHEADER)))
    {
        return VFW_E_TYPE_NOT_ACCEPTED;
    }
 
    VIDEOINFOHEADER *pVih =
        reinterpret_cast<VIDEOINFOHEADER*>(mtIn->pbFormat);
    if ((pVih->bmiHeader.biBitCount != 8) ||
        (pVih->bmiHeader.biCompression != BI_RGB))
    {
        return VFW_E_TYPE_NOT_ACCEPTED;
    }
 
    // Check the palette table.
    if (pVih->bmiHeader.biClrUsed > PALETTE_ENTRIES(pVih))
    {
        return VFW_E_TYPE_NOT_ACCEPTED;
    }
    DWORD cbPalette = pVih->bmiHeader.biClrUsed * sizeof(RGBQUAD);
    if (mtIn->cbFormat < sizeof(VIDEOINFOHEADER) + cbPalette)
    {
        return VFW_E_TYPE_NOT_ACCEPTED;
    }
 
    // Everything is good.
    return S_OK;
}
在这个例子中,首选查检主要类型和子类型,然后它查检格式类型,确保这个格式块是一个VIEOINFOHEADER结构。这个filter也可以支持VIDEOINFOHEADER2,但本例中不提供支持。如果这个格式类型是正确的,查检VIDEOINFOHEADER结构中biBitCount和biCompression成员,为了验证这个格式是8位未压缩RGB。此例中,基于格式类型,必须强制转换pbFormat指向正确的结构,在转换这个指针之前一直检查格式类型GUID(formattype)和这个格式块的尺寸(cbFormat)。
这个例子也验证调色板的数目是否兼容于位深和格式块对于保存调色板是否足够大。如果这个信息也是正确的,这个方法返回S_OK。
步骤3B 实现GetMediaType方法
注意 这个步骤对于继承自CTransInPlaceFilter的filter是不必需的。
通过索引数目引用,CTransformFilter::GetMediaType方法返回filter的输出类型。除非这个filter的输入PIN已经被连接,否则这个成员从不会调用。因此,你可以使用从上一级连接的媒体类型来决定要输出的类型。
一个编码器一般提供一个单独的类型来表现目的格式。解码器一般支持一个范围的输出格式,并且根据质量或效率的递减来提供。例如,这个列表可能是UYVY,Y211,RGB-24,RGB-565,RGB-555,和RGB-8,在这个次序中。有效的filter要求输出格式和输入格式有一个精确的匹配。
下列例子返回一个单独的输出类型,它是由修改输入类型到指定RLE8压缩来构造的:
HRESULT CRleFilter::GetMediaType(int iPosition, CMediaType *pMediaType)
{
    ASSERT(m_pInput->IsConnected());
    if (iPosition < 0)
    {
        return E_INVALIDARG;
    }
    if (iPosition == 0)
    {
        HRESULT hr = m_pInput->ConnectionMediaType(pMediaType);
        if (FAILED(hr))
        {
            return hr;
        }
        FOURCCMap fccMap = FCC('MRLE');
        pMediaType->subtype = static_cast<GUID>(fccMap);
        pMediaType->SetVariableSize();
        pMediaType->SetTemporalCompression(FALSE);
 
        ASSERT(pMediaType->formattype == FORMAT_VideoInfo);
        VIDEOINFOHEADER *pVih =
            reinterpret_cast<VIDEOINFOHEADER*>(pMediaType->pbFormat);
        pVih->bmiHeader.biCompression = BI_RLE8;
        pVih->bmiHeader.biSizeImage = DIBSIZE(pVih->bmiHeader);
        return S_OK;
    }
    // else
    return VFW_S_NO_MORE_ITEMS;
}
在这个例子里,调用IPin::ConnectionMediaType从输入PIN得到输入类型。然后它改变一些值来显示压缩格式,如下:
•   它分配一个新子类型GUID,这个GUID构造于FOURCC代码‘MRLE’,使用这个FOURCCMap类。
•   调用CMediaType::SetVariableSize方法,设置bFixedSizeSampel标志为FALSE和lSampleSize成员为0,显示变量尺寸采样。
•   用值FALSE调用CMediaType::SetTemporalCompression方法,显示每个帧是一个关键帧(这只是个信息值,所以你可以忽略它)
•   它设置biCompression领域为BI_RLE8
•  它设置biSizeImage领域为图像尺寸
步骤3C 实现CheckTransform方法
注意 这个步骤对于继承自CTransInPlaceFilter的fitler不是必需的。
CTransformFitler::CheckTransform方法查检如果一个被提交的输出类型是兼容于当前输入类型。如果这个输入PIN在输出PIN连接后重新连接这个方法也被调用。
下面例子验证格式是否是RLE8视频;图像尺寸匹配输入格式;并且调色板是相同的。如果图像尺寸不匹配它也拒绝源和目标。
HRESULT CRleFilter::CheckTransform(
    const CMediaType *mtIn, const CMediaType *mtOut)
{
    // Check the major type.
    if (mtOut->majortype != MEDIATYPE_Video)
    {
        return VFW_E_TYPE_NOT_ACCEPTED;
    }
 
    // Check the subtype and format type.
    FOURCCMap fccMap = FCC('MRLE');
    if (mtOut->subtype != static_cast<GUID>(fccMap))
    {
        return VFW_E_TYPE_NOT_ACCEPTED;
    }
    if ((mtOut->formattype != FORMAT_VideoInfo) ||
        (mtOut->cbFormat < sizeof(VIDEOINFOHEADER)))
    {
        return VFW_E_TYPE_NOT_ACCEPTED;
    }
 
    // Compare the bitmap information against the input type.
    ASSERT(mtIn->formattype == FORMAT_VideoInfo);
    BITMAPINFOHEADER *pBmiOut = HEADER(mtOut->pbFormat);
    BITMAPINFOHEADER *pBmiIn = HEADER(mtIn->pbFormat);
    if ((pBmiOut->biPlanes != 1) ||
        (pBmiOut->biBitCount != 8) ||
        (pBmiOut->biCompression != BI_RLE8) ||
        (pBmiOut->biWidth != pBmiIn->biWidth) ||
        (pBmiOut->biHeight != pBmiIn->biHeight))
    {
        return VFW_E_TYPE_NOT_ACCEPTED;
    }
 
    // Compare source and target rectangles.
    RECT rcImg;
    SetRect(&rcImg, 0, 0, pBmiIn->biWidth, pBmiIn->biHeight);
    RECT *prcSrc = &((VIDEOINFOHEADER*)(mtIn->pbFormat))->rcSource;
    RECT *prcTarget = &((VIDEOINFOHEADER*)(mtOut->pbFormat))->rcTarget;
    if (!IsRectEmpty(prcSrc) && !EqualRect(prcSrc, &rcImg))
    {
        return VFW_E_INVALIDMEDIATYPE;
    }
    if (!IsRectEmpty(prcTarget) && !EqualRect(prcTarget, &rcImg))
    {
        return VFW_E_INVALIDMEDIATYPE;
    }
 
    // Check the palette table.
    if (pBmiOut->biClrUsed != pBmiIn->biClrUsed)
    {
        return VFW_E_TYPE_NOT_ACCEPTED;
    }
    DWORD cbPalette = pBmiOut->biClrUsed * sizeof(RGBQUAD);
    if (mtOut->cbFormat < sizeof(VIDEOINFOHEADER) + cbPalette)
    {
        return VFW_E_TYPE_NOT_ACCEPTED;
    }
    if (0 != memcmp(pBmiOut + 1, pBmiIn + 1, cbPalette))
    {
        return VFW_E_TYPE_NOT_ACCEPTED;
    }
 
    // Everything is good.
    return S_OK;
}
PIN重新连接
应用程序可以断开连接和重新连接PIN。建议一个应用程序连接两个PIN,断开这个输入PIN,并且然后使用新图像尺寸重新连接这个输入PIN。在这种情况下,CheckTransform失败因为这个图像的尺寸不再匹配。这是有原因的,虽然这个filter可以也试图用一个新媒体类型重新连接输出PIN。
步骤4 设置allocator属性
注意 这个步骤对于继承自CTransInPlaceFilter的filter不是必需的。
在两个PIN在一个媒体类型达成一致之后,它们为这个连接和协商alloctor属性选择一个allocator,如缓冲区尺寸和缓冲区数目。
在CTransformFilter类中,有两个allocator,一个是对于upstream pin连接一个是对于downstream pin连接。upstream filter选择upstream allocator并且也选择allocator属性。输入PIN接受upstream filer的选择。如果你需要去修改这个行为,重载CBaseInputPin::NotifyAllocator方法。
transform filter的输出PIN选择downstream filter。它执行下列步骤:
 
1.  如果downstream filer可以提供一个allocator,输出PIN就使用它。否则,输出PIN建立一个新的allocator。
2.  通过调用IMemInputPin::GetAllocatorRequirements,输出PIN得到downstream filter的allocatro需求(如果可以)
3.  输出PIN调用transform filter上的CTransformFilter::DecideBufferSize方法,这个方法是纯虚函数。这个方法的参数是一个指向allocator的指针和符合downstream filter需求的一个ALLOCATOR_PROPERTIES结构。如果downstream filter不需要allocator,这个结构是0。
4.  在DecideBufferSize方法中,继承类通过调用IMemAllocator::SetProperties设置allocator属性。
一般的,继承类基于输出格式选择allocator的属性,下一级filters的需求,和filter自己的需求。试图去选择兼容于downstream filters要求的属性。否则,这个downstram filter可能拒绝这个连接。
下面例子中,RLE编码器设置缓冲区尺寸最小值,缓冲区对齐,和缓冲区计数。对于前缀,它使用downstream filter请求的任何值。这个前缀是典型的0字节。但一些filter需要它。例如,AVI Mux filter使用这个前缀来写RIFF头。
HRESULT CRleFilter::DecideBufferSize(
    IMemAllocator *pAlloc, ALLOCATOR_PROPERTIES *pProp)
{
    AM_MEDIA_TYPE mt;
    HRESULT hr = m_pOutput->ConnectionMediaType(&mt);
    if (FAILED(hr))
    {
        return hr;
    }
 
    ASSERT(mt.formattype == FORMAT_VideoInfo);
    BITMAPINFOHEADER *pbmi = HEADER(mt.pbFormat);
    pProp->cbBuffer = DIBSIZE(*pbmi) * 2;
    if (pProp->cbAlign == 0)
    {
        pProp->cbAlign = 1;
    }
    if (pProp->cBuffers == 0)
    {
        pProp->cBuffers = 1;
    }
    // Release the format block.
    FreeMediaType(mt);
 
    // Set allocator properties.
    ALLOCATOR_PROPERTIES Actual;
    hr = pAlloc->SetProperties(pProp, &Actual);
    if (FAILED(hr))
    {
        return hr;
    }
    // Even when it succeeds, check the actual result.
    if (pProp->cbBuffer > Actual.cbBuffer)
    {
        return E_FAIL;
    }
    return S_OK;
}
allocator可能不需要去精确的匹配你的请求。因此,SetProperties方法在一个分离ALLOCATOR_PROPERTIES结构中(这个实际参数,在前面的例子中)返回一个实际结果。甚至当SetProperties成功时,你应该查检这个结果来确保它们符合你的filters的最小需求。
自定义allocators
By default, all of the filter classes use the CMemAllocator class for their allocators. This class allocates memory from the virtual address space of the client process (using VirtualAlloc). If your filter needs to use some other kind of memory, such as DirectDraw surfaces, you can implement a custom allocator. You can use the CBaseAllocator class or write an entirely new allocator class. If your filter has a custom allocator, override the following methods, depending on which pin uses the allocator:

默认的,所有的filter类的allocator使用CMemAllocator类。这个类allocator从客户进行(使用VirtualAlloc)的虚地址空间分配内存。如果你的filter需要去使用一些其它类型的内存,如DirectDraw surfaces,可以实现一个自定义allocator。使用CBaseAllocator类或者写一个全新的allocator类。如果filter有一个自定义allocator,重载下列方法,依赖哪个PIN使用这个allocator:
• Input pin: CBaseInputPin::GetAllocator and CBaseInputPin::NotifyAllocator.
• Output pin: CBaseOutputPin::DecideAllocator.
如果其它filter拒绝连接使用自定义allocator,这个filter可能连接失败,或者用其它filter的allocator连接。在后面的情况中,你可能需要去设置一个内部标志显示allocator的类型。这个方法的例子,查看CDrawImage class。
步骤5 transform这个图像
通过调用在transform filters的输入PIN上的IMemInputPin::Receive方法,上一级 filter传递媒体采样到transform filter。为了处理这个数据,transform filter调用Transform方法,它是一个纯虚函数。CTransformFitler和CTransInPlaceFilter类使用这个方法两个不同版本:
•   CTransformFilter::Transform有一个指向输入采样和一个指向输出采样的指针。在这个filter被调用之前,它从输入采样拷贝这个采样属性到输出采样,包括时间戳。
•   CTransInPlaceFilter::Transform有一个指向输出采样的指针。filter在适当的位置修改数据.
如果transform方法返回S_OK,filter传递采样到下一级。为了忽略一帧,返回S_FALSE。如果这里有一个流错误,返回一个失败代码。
 
The following example shows how the RLE encoder might implement this method. Your own implementation might differ considerably, depending on what your filter does.

下列例子显示RLE编码器如何实现这个方法。根据filter的功能不同,实现可能有较大区别。
HRESULT CRleFilter::Transform(IMediaSample *pSource, IMediaSample *pDest)
{
    // Get pointers to the underlying buffers.
    BYTE *pBufferIn, *pBufferOut;
    hr = pSource->GetPointer(&pBufferIn);
    if (FAILED(hr))
    {
        return hr;
    }
    hr = pDest->GetPointer(&pBufferOut);
    if (FAILED(hr))
    {
        return hr;
    }
    // Process the data.
    DWORD cbDest = EncodeFrame(pBufferIn, pBufferOut);
    KASSERT((long)cbDest <= pDest->GetSize());
 
    pDest->SetActualDataLength(cbDest);
    pDest->SetSyncPoint(TRUE);
    return S_OK;
}
这个例子假定EncodeFrame是一个私有方法,它实现RLE编码。这个编码算法它自己不在这里描述;细节的,查看在平台SDK中的bitmap compression。
首先,这个例子调用IMediaSample::GetPointer来接收底层缓冲区的地址。并传递到私有EncoderFrame方法。然后调用IMediaSample::SetActualDataLength来指定这个编码数据的长度。下一级filter需要这个信息以便管理这个缓冲区。最后,这个方法调用IMediaSample::SetSyncPoint来设置关键帧标志为TRUE。run-length编码不使用任何delta帧,所以每个帧都是关键帧。对于delta帧,设置这个值为FALSE。
其它需要考虑的包括:
•   时间戳。 CTransformFilter类在调用Transform方法之前在输出采样上打时间戳。它从输入采样里拷贝时间戳值,而不修改它们。如果你的filter需要去改变时间戳,调用在输出采样上的IMediaSample::SetTime。
•   格式改变。 上一级filter可以通过附加一个媒体类型到这个采样来更改格式mid-stream。在这么做之前,调用filter输入PIN上的IPin::QueryAccept。在CTransformFilter类中,导致CheckTranform后调用CheckInputType。使用同样机制,下一级filter也更改媒体类型。在自定义的filter中,需要查看
•   确保QueryAccept不返回false接受值
•   如果filter不接受格式改变,通过调用IMediaSample::GetMediaType在Transform方法的内部查检它们。如果这个方法返回S_OK, filter必须响应于格式改变。
• 线程。在CTransformFitler和CTransInPlaceFilter中,transform fitler在Receive方法的内部同步传递输出采样。filter不建立任何工作线程来处理这个数据。典型的,不需要为一个transform filter来建立工作线程。
步骤6 增加支持COM
最终步骤是增加支持COM
引用计数
不必实现AddRef或Release。所有的filter和PIN类继承自CUnknown,它处理引用计数。
QueryInterface
所有的filter和PIN类为他们继承的COM接口实现QueryInterface。例如,CTransformFilter继承自IBaseFilter(通过CBaseFilter)。如果filter不暴露任何额外的接口,就不必须做任何事情。
To expose additional interfaces, override the CUnknown::NonDelegatingQueryInterface method. For example, suppose your filter implements a custom interface named IMyCustomInterface. To expose this interface to clients, do the following:

为了暴露其它的接口,重载CUnknown::NonDelegatingQueryInterface方法。例如,支持自定义filter实现一个命名为IMyCustomInterface自定义接口。为了把这个接口暴露给客户端,如下操作:
•   从这个接口继承你的filter类
•   放置DELCARE_IUNKNOWN宏在公共声明节内。
•   重载NonDelegatingQueryInterface来检查接口的IID和返回一个指向filter的指针。
The following code shows these steps:
CMyFilter : public CBaseFilter, public IMyCustomInterface
{
public:
    DECLARE_IUNKNOWN
    STDMETHODIMP NonDelegatingQueryInterface(REFIID iid, void **ppv);
};
STDMETHODIMP CMyFilter::NonDelegatingQueryInterface(REFIID iid, void **ppv)
{
    if (riid == IID_IMyCustomInterface) {
        return GetInterface(static_cast<IMyCustomInterface*>(this), ppv);
    }
    return CBaseFilter::NonDelegatingQueryInterface(riid,ppv);
}
对象的建立
如果计划在一个DLL打包filter并且对于其它客户端可用,必须支持CoCreateInstance和其它相关的COM函数。这个基类库实现大多数细节;现在只需要提供一些关于自定义filter的信息。这节给出一个要做什么的简单概述。细节,查看how to create a dll。
首选,写一个静态类方法,它返回一个filter的新实例。可以命令喜欢的任何名字,但这个署名必须匹配下列例子中显示:
CUnknown * WINAPI CRleFilter::CreateInstance(LPUNKNOWN pUnk, HRESULT *pHr)
{
    CRleFilter *pFilter = new CRleFilter();
    if (pFilter== NULL)
    {
        *pHr = E_OUTOFMEMORY;
    }
    return pFilter;
}
接着,声明CFactoryTemplate类实例的一个全局数组,命名g_Templates。每个CFactoryTemplate类包含一个filter的注册信息。几个filter保存在一个单独的DLL;简单的包括额外的CFactoryTemplate。也可以声明其它COM对象。如属性页。
static WCHAR g_wszName[] = L"My RLE Encoder";
CFactoryTemplate g_Templates[] =
{
  {
    g_wszName,
    &CLSID_RLEFilter,
    CRleFilter::CreateInstance,
    NULL,
    NULL
  }
};
定义一个全局整形变量g_cTemplates,它的值等于g_Templates数组的长度。
int g_cTemplates = sizeof(g_Templates) / sizeof(g_Templates[0]); 
最后,实现DLL注册函数。下列例子显示这些函数的最小实现。
STDAPI DllRegisterServer()
{
    return AMovieDllRegisterServer2( TRUE );
}
STDAPI DllUnregisterServer()
{
    return AMovieDllRegisterServer2( FALSE );
}
filter注册入口
先前例子显示如何为COM去注册一个filter的CLSID。对于许多filter,这已经足够了,客户端然后使用CoCreateInstance来建立这个filter,并且通过调用IFilterGraph::AddFilter把它增加到filter graph。在一些情况下,可能想提供增加在注册表中关于这个filter的信息。这个信息如下:
•   客户端使用Filter Mapper或者system device enumerator来查找到这个filter。
•   filter graph manager在自动graph生成时查找到这个filter。
The following example registers the RLE encoder filter in the video compressor category. For details, see How to Register DirectShow Filters. Be sure to read the section Guidelines for Registering Filters, which describes the recommended practices for filter registration.

下列例子在视频compressor类别中注册RLE编码器filter。对于细节,查看how to register directshow filters。对于注册filter,本节描述了filter注册的推荐方式。
// Declare media type information.
FOURCCMap fccMap = FCC('MRLE');
REGPINTYPES sudInputTypes = { &MEDIATYPE_Video, &GUID_NULL };
REGPINTYPES sudOutputTypes = { &MEDIATYPE_Video, (GUID*)&fccMap };
 
// Declare pin information.
REGFILTERPINS sudPinReg[] = {
    // Input pin.
    { 0, FALSE, // Rendered?
         FALSE, // Output?
         FALSE, // Zero?
         FALSE, // Many?
         0, 0,
         1, &sudInputTypes  // Media types.
    },
    // Output pin.
    { 0, FALSE, // Rendered?
         TRUE, // Output?
         FALSE, // Zero?
         FALSE, // Many?
         0, 0,
         1, &sudOutputTypes      // Media types.
    }
};
 
// Declare filter information.
REGFILTER2 rf2FilterReg = {
    1,                // Version number.
    MERIT_DO_NOT_USE, // Merit.
    2,                // Number of pins.
    sudPinReg         // Pointer to pin information.
};
 
STDAPI DllRegisterServer(void)
{
    HRESULT hr = AMovieDllRegisterServer2(TRUE);
    if (FAILED(hr))
    {
        return hr;
    }
    IFilterMapper2 *pFM2 = NULL;
    hr = CoCreateInstance(CLSID_FilterMapper2, NULL, CLSCTX_INPROC_SERVER,
            IID_IFilterMapper2, (void **)&pFM2);
    if (SUCCEEDED(hr))
    {
        hr = pFM2->RegisterFilter(
            CLSID_RLEFilter,                // Filter CLSID.
            g_wszName,                       // Filter name.
            NULL,                            // Device moniker.
            &CLSID_VideoCompressorCategory,  // Video compressor category.
            g_wszName,                       // Instance data.
            &rf2FilterReg                    // Filter information.
            );
        pFM2->Release();
    }
    return hr;
}
 
STDAPI DllUnregisterServer()
{
    HRESULT hr = AMovieDllRegisterServer2(FALSE);
    if (FAILED(hr))
    {
        return hr;
    }
    IFilterMapper2 *pFM2 = NULL;
    hr = CoCreateInstance(CLSID_FilterMapper2, NULL, CLSCTX_INPROC_SERVER,
            IID_IFilterMapper2, (void **)&pFM2);
    if (SUCCEEDED(hr))
    {
        hr = pFM2->UnregisterFilter(&CLSID_VideoCompressorCategory,
            g_wszName, CLSID_RLEFilter);
        pFM2->Release();
    }
    return hr;
}
Also, filters do not have to be packaged inside DLLs. In some cases, you might write a specialized filter that is designed only for a specific application. In that case, you can compile the filter class directly in your application, and create it with the new operator, as shown in the following example:

同样,filter不需要必须在DLL内部打包。在一些情况下,可以为一些特定的应用程序写一个指定的fitler。在这个情况下,可以在应用程序中直接编译这个filter类,并且用new操作符创建它,如下例子中显示:
include "MyFilter.h"  // Header file that declares the filter class.
// Compile and link MyFilter.cpp.
int main()
{
    IBaseFilter *pFilter = 0;
    {
        // Scope to hide pF.
        CMyFilter* pF = new MyFilter();
        if (!pF)
        {
            printf("Could not create MyFilter./n");
            return 1;
        }
        pF->QueryInterface(IID_IBaseFilter,
            reinterpret_cast<void**>(&pFilter));
    }
   
    /* Now use pFilter as normal. */
   
    pFilter->Release();  // Deletes the filter.
    return 0;
}