如何写Transform Filters

  本章旨在说明如何写一个 transform filter。Transform Filter是具备一个 input pin 和一个 output pin 的 filter。以一个输出 run-length encoded(RLE) 视频的 filter 为例说明,因目的是描述 DirectShow 需要实现的任务,故不涉及 RLE 压缩算法。

 

  Step 1、Choose a Base Class:

  • CTransformFilter 适用于输入输出 buffer 分离的 filter,有时也称为 copy-transform filter。它接受一个 input sample,将形成的新数据写入一个 output sample,然后发送这个 output sample 给下游 filter。
  • CTransInPlaceFilter 适用于在同一 buffer 中修改数据的 filter,也被称为 trans-in-place filter。它接受一个 sample,在这个 sample 中改变数据,并将改变后的 sample(还是同一个)发送给下游。这种 filter 的 input 和 output pin 通常使用匹配的 media type。
  • CVideoTransformFIlter 主要用于 video decoders。它派生自 CTransformFilter,但增加了当下游 renderer 落后(即该帧的pts晚于当前时间)时丢弃帧(dropping frames)的功能。
  • 重点:In-place video transforms 会影响播放效果。它需要在 buffer 上进行 read-modify-write 操作。如果内存驻留在显卡中,读取操作会更慢。此外,如果没有仔细考虑,即使是实现一个拷贝变换,也会导致额外的读取操作。因此,如果写一个 video transform,必须进行播放效果测试。

   这个 RLE encoder 最好选择 CTransformFilter 或 CVideoTransformFilter。实际上,他们的差别主要是在内部实现,所以很容易从一个转换为另一个。因为两个 pin 的 media types 必须不同,CTransInPlaceFilter 类在此应用中不适宜。所以选择 CTransformFilter。

 

  Step 2、Declare the Filter Class

  首先声明一个派生自 base class 的类:

class CRleFilter : public CTransformFilter
{
    /* Declarations will go here. */
};

  每个 filter 类都有与之对应的 pin 类,需要根据 filter 的特定要求来 override pin 类。在本例中,由于大部分 pin 的工作都由 filter 完成,因此无需对 pin 做改动。

  必须赋予这个filter一个特定的 CLSID,通过 Guidgen 或 Uuidgen 工具就能得到;绝对不要用一个已被使用的 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. */
}

  注意,这个构造函数的第三个参数是之前定义的那个 CLSID。

 

  Step 3、Support Media Type Negotiation

  当两个 pin 连接时,他们必须对这个连接的 media type 达成一致。这个 media type 定义了 data 的格式。没有它,filter 可能会发送某种格式的数据,而另一个 filter 把这种数据当成别的格式。

  协商 media type 的基本方式是使用 IPin::ReceiveConnection。output pin 以目的格式为参数调用 input pin上的这个方法。input pin 决定是否接受这次 connection。如果拒绝,output pin 能继续尝试另外的 media type,直到所有的格式都试过,如果仍未找到,此次连接告以失败。input pin可以选择使用 IPin::EnumMediaTypes 来枚举支持的格式。output pin 在尝试连接时能使用这个枚举列表。

  CTransformFilter 类实现了处理过程的大体框架,如下:

  • input pin 没有推荐格式:格式主要由上游 filter 来提供。这种方式对视频数据很有用,因为他们的 media type 需要包括图像的大小和帧率(这就无法预先提供指定格式)。通常,这些属性由上游的 source 或 parser filter 提供。对于音频数据,因为可能的格式设置较少,所以 input pin 提供一些备选类型更为实际。在这种情况下,需要 override input pin上的 CBasePin::GetMediaType。
  • 上游 filter 提供 media type 时,input pin 调用 CTransformFilter::CheckInputType 来决定是否结束该格式。
  • output pin 只能在 input pin连接后才能连接,特别是对于 tansform filter。通常,filter 在决定输出格式前必须先确定输入格式。
  • 当 output pin 进行连接时,它会提供一个 media type 的列表给下游filter。该列表由它通过调用 CTransformFilter::GetMediaType 来得到。output pin 也会尝试下游filter提供的格式。
  • 要检查某个特定的 output type 是否与该 input type 兼容(即filter支持这种转换),output pin 需要调用 CTransformFilter::CheckTransform。

 

  之前列出的这三个 CTransformFilter 的方法都是纯虚函数,所以派生类必须实现他们。他们都不属于 COM 接口,只是 base classes 提供的实现。

  • CheckInputType:上游 filter 通过调用该函数来提供 media type 给 transform filter。该方法将提供的 AM_MEDIA_TYPE 结构封装为 CMediaType 对象,如果该对象不符合要求,就返回 VFW_E_TYPE_NOT_ACCEPTED,否则返回 S_OK。
  • GetMediaType:在该 filter 的 input pin 连接后才能调用这个函数,它以索引值的形式返回该 filter 允许的一种 output types。这样就能用上游连接的 media type 来决定 output type。通常,encoder 支持单一格式,而 decoder 会支持一系列输出格式,按照质量或者效率以递减的方式排列。
  • CheckTransform:检查提供的 output type 是否与当前的 input type 兼容。当 output pin 已经连接后,如果需要重连接 input pin,则也需要重新调用这个函数。

 

  Step 4、Set Allocator Properties

  •   当两个 pin 同意某个 media type 后,他们会为这个连接选择一个 allocator 并协商它的属性,比如 buffer size 和 buffer 的数量。
  •   对于 CTransformFilter 类,有两个 allocator,一个用于上游 pin 的连接,一个用于下游 pin 的连接。上游 filter 决定 upstream allocator 以及它的属性。input pin 接受上游 filter 的选择。如果你需要改变这个做法,就应 override CBaseInputPin::NotifyAllocator。
  •   transform filter 的 output pin 按照以下步骤来决定 downstream allocator:
  •   1)如果 downstream filter 能提供一个 allocator,output pin 就使用它提供的这个。
  •      否则,就创建一个。
  •   2)output pin 调用下游 pin 的 IMemInputPin::GetAllocatorRequirements 来得到
  •     下游 filter 的 allocator 要求。
  •   3)output pin 调用 transform filter 的 CTransformFilter::DecideBufferSize 来设置
  •      这个 allocator。这是纯虚函数,在其中调用 IMemAllocator::SetProperties 设置。

  通常,派生类会基于 output format,下游 filter 的要求以及 filter 自身的要求来选择 allocator 的属性。应选择和下游 filter 要求相符合的属性,否则下游 filter可能会拒绝这个连接。

  所有的 filter 类默认使用 CMemAllocator 类作为他们的 allocators。这个类使用 VirtualAlloc 在客户进程的虚拟内存上分配空间。如果你需要使用其他方式的内存,比如 DirectDraw surfaces ,就应该实现自己的 allocators。可以使用 CBaseAllocator 或者 实现一个完整的新类。

  对于通常的情况,根据使用该 allocator 的 pin 来决定 override 下面几个方法:

  • Input pin:CBaseInputPin::GetAllocator 和 CBaseInputPin::NotifyAllocator;
  • Output Pin:CBaseOutputPin::DecideAllocator.

 

  Step 5、Transform the Image

  上游 filter 调用 tranform filter 上 input pin 的 IMemInputPin::Receive 方法来发送一个 sample给它。transform filter 得到这个sample后,调用纯虚函数 Transform 来处理该数据。
Transform 在 CTransformFilter 和 CTransInPlaceFilter 类中有两种不同的版本:

  • CTransformFilter::Transform 的参数是 input sample 和 output sample 的指针。调用该方法前,filter 会将 input sample 的属性(包括 time stamps )复制到 output sample 中。
  • CTransInPlaceFilter::Transform 只有一个参数 input sample ,它在该 sample 中修改数据。

  如果 Transform 返回 S_OK,该 filter 就发送这个 sample 给下游。如果要 skip 这一帧,就返回 S_FALSE 。如果有 streaming error , 就返回一个失败码。

  可能涉及到的问题:

  • Time stamps:transform filter 在调用 tranform 前会从 input sample 中复制 timestamp 到 output sample中,但不会改变他们。如果你需要更改 timestamp,需要调用 output sample 上的 IMediaSample::SetTime。
  • Format changes:上游 filter 能通过在 sample 上附加一个 media  type 来在 mid-stream 中改变码流格式。要实现这种格式更改,需要先调用 input pin 的 IPin::QueryAccept ,这个调用会引发对 filter 上 CheckInputType 和 CheckTransform 的调用。下游 filter 也能使用相同的策略来改变 media type。
  • 线程:transform filter 在 Receive 中同步发送 output sample。filter 中不会生成 worker threads 来处理数据。通常,也没有必要在 transform 中使用 worker threads。

 

  Step 6、Add Support for COM

  最后一步是添加对 COM 的支持。

  • 引用计数
  •   不需要实现 IUnknown::AddRef 或 IUnknown::Release。所有的 filter 和 pin 类都派生自CUnkown,它已经处理了引用计数。
  • 接口查询
  •   filter 和 pin 类已经实现了他们派生的 COM 接口的 IUnkown::QueryInterface。所以,如果你的 filter 没有暴露额外的接口,就不需要再处理接口查询。
  •   如果暴露了额外的接口,应 override CUnknown::NonDelegatingQueryInterface。比如,假设你暴露名为 IMyCustomInterface 的接口,就应添加如下步骤:
  •   1) 从该接口继承你的 filter 类
  •   2) 将 DECLARE_IUNKNOWN 宏放置在 public 声明区域
  •   3) override NonDelegatingQueryInterface 来检测这个接口的IID
posted on 2012-04-18 19:30  海茉莉  阅读(1171)  评论(0编辑  收藏  举报