DirectShow Animated Header -- Creating a Transform Filter DirectShow Animated Header -- Creating a Transform Filter* Microsoft DirectShow SDK
*Index  *Topic Contents
*Previous Topic: Transform Filters
*Next Topic: Using the CTransformFilter and CTransInPlaceFilter Transform Base Classes

Creating a Transform Filter


Transform filters transform the media data that comes into their input pins and send the transformed data out their output pins. Transform filters can be used to compress and decompress data, to split audio and visual data, or to apply effects, such as contrast or warbling, to media data. DirectShow contains several sample transform filters that perform different kinds of transformations. See DirectShow Filters for a description of the transform filters supplied by DirectShow. See Write a Transform Filter in C/C++ for instructions on how to write your own transform filters in C++. See Using the CTransformFilter and CTransInPlaceFilter Transform Base Classes for a discussion of the CTransformFilter and CTransInPlaceFilter transform filter base classes. See Connecting Transform Filters for a discussion of connecting to a transform filter.

This article steps through the process of creating a transform filter for a Microsoft® DirectShow™ filter graph that uses the DirectShow C++ class library. It covers five basic steps, and in the last step shows how to override the required member functions in your derived class to implement the transform filter. It answers two common questions that arise when creating transform filters: Which base class do I use? and How do I override member functions?

Contents of this article:

Writing a Transform Filter

Writing a transform filter can be broken into the following discrete steps.

  1. Determine if the filter must copy media samples or can handle them in place.

    The fewer copies in the media stream, the better. However, some filters require a copy operation; this influences the choice of base classes.

  2. Determine which base classes to use and derive the filter class (and pin classes, if necessary) from the base classes.

    In this step, create the header or headers for your filter. In many cases, you can use the transform base classes, derive your class from the correct transform filter class, and override a few member functions. In other cases, you can use the more generic base classes. These classes implement most of the connection and negotiation mechanism; but these classes also allow more flexibility at the cost of overriding more member functions.

  3. Add the code necessary to instantiate the filter.

    This step requires adding a static CreateInstance member function to your derived class and also a global array that contains the name of the filter, a CLSID, and a pointer to that member function.

  4. Add a NonDelegatingQueryInterface member function to pass out any unique interfaces in your filter.

    This step addresses the Component Object Model (COM) aspects of implementing interfaces, other than those in the base classes.

  5. Override the appropriate base class member functions.

    This includes writing the transform function that is unique to your filter and overriding a few member functions that are necessary for the connection process, such as setting the allocator size or providing media types.

Determine if the Filter Must Copy Media Samples

Because every copy operation uses valuable CPU cycles, filter developers are encouraged to avoid copying the media samples, if possible. It is best to write the filter to modify media samples in place on an allocator acquired from another filter. In some cases, this is not possible, and a copy operation must be performed.

Where no copy is needed, the run-time overhead of a transform-inplace filter is scarcely more than that of a function; however, by packaging the transform as a filter, you get the full flexibility of the filter graph architecture.

Some reasons that a filter might be written as a copy transform filter rather than a transform-inplace filter include the following:

Determine Which Base Classes to Use

Before choosing a base class for your transform filter, you must first decide whether your filter needs more than one input and output pin. If it does, you should derive your filter class from CBaseFilter.

If your filter needs to perform a video transform, you should derive your filter class from CVideoTransformFilter.

Otherwise, you should derive your filter class from CTransformFilter or CTransInPlaceFilter. To determine which one to use, you must decide whether your filter must copy media samples or can transform them in place. Because every copy operation uses valuable CPU cycles, filter developers should avoid copying media samples, if possible. It is best to write a filter to modify media samples in place on an allocator acquired from another filter. In some cases, this isn't possible, and you must perform a copy operation.

Where no copy is needed, the run-time overhead of a transform-inplace filter isn't much more than that of a function. However, by packaging the transform as a filter, you get the full flexibility of the filter graph architecture.

Some reasons that you might write a filter as a copy transform filter rather than a transform-inplace filter are:

Once you determine whether the transform filter will copy media samples or transform them in place, you must decide which base class or classes to use and which member functions you must override and implement. You can then define your derived classes.

Some member functions in the base classes must be overridden in your derived class because they are either declared as pure virtual in the base classes (they have no implementation), or have default implementations that do nothing but return an error value.

You derive your filter class from the transform base classes CTransformFilter, CTransInPlaceFilter, or CVideoTransformFilter, or from the more generic CBaseFilter filter class. Most of the connection, media type, and allocator negotiation code is handled in the base classes and inherited by the transform classes. The transform classes make it possible to create a filter by deriving just one filter class (no pin classes). The transform classes make assumptions about the workings of transform filters that make the process of creating a transform filter easier.

To learn more about CTransformFilter and CTransInPlaceFilter and which of their member functions are typically overridden by the derived class, see Using the CTransformFilter and CTransInPlaceFilter Transform Base Classes.

Instantiate the Filter

All filters must add code to let the base classes instantiate the filter. To instantiate a filter, you must include two pieces of code in your filter: a static CreateInstance member function in the derived filter class, and a means of informing the class factory in the base classes how to access this function.

Typically, the CreateInstance member function calls the constructor for the derived filter class. The following is the CreateInstance member function from the Gargle sample filter.


CUnknown *CGargle::CreateInstance(LPUNKNOWN punk, HRESULT *phr) {

    CGargle *pNewObject = new CGargle(NAME("Gargle Filter"), punk, phr);
    if (pNewObject == NULL) {
        *phr = E_OUTOFMEMORY;
    }

    return pNewObject;
} // CreateInstance

To communicate with the class factory, declare a global array of CFactoryTemplate objects as g_Templates and provide the name of your filter, the class identifier (CLSID) of your filter, and a pointer to the static CreateInstance member function that creates your filter object. The Gargle sample filter does this as follows:


// Needed for the CreateInstance mechanism
CFactoryTemplate g_Templates[2]=
    { { L"Gargle filter"              , &CLSID_Gargle , CGargle::CreateInstance          }
    , { L"Gargle filter Property Page", &CLSID_GargProp, CGargleProperties::CreateInstance}
    };

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

You can add additional parameters to the CFactoryTemplate templates if you want your filter to be self-registering. For more information on this, see Register DirectShow Objects.

Finally, link your filter to strmbase.lib and export DllGetClassObject and DllCanUnloadNow using a .def file.

Make Added Interfaces Available Through NonDelegatingQueryInterface

Only filters that add interfaces that are not in the base classes, such as those required for creating property pages, need implement the IUnknown member functions (called INonDelegatingUnknown in the base classes). The base classes provide default implementations of the IUnknown methods. IUnknown methods in any COM-based code retrieve interfaces from an object, and increment and decrement the reference counts of those interfaces. For example, the IUnknown::QueryInterface method retrieves interfaces from an object.

DirectShow defines a special IUnknown class called INonDelegatingUnknown, whose methods do the same thing as IUnknown. (The reason for the name change is so that objects can be aggregated.) The NonDelegatingQueryInterface method is called whenever some object or application wants to query a pin or filter for any interfaces it implements. If your filter implements any interface outside those listed in the base class implementation, you will need to override the NonDelegatingQueryInterface method to return a pointer to the implemented interface. For example, the following code example illustrates how the Gargle sample overrides the member function to distribute references to the ISpecifyPropertyPages and IPersistStream interfaces.


// Reveal our persistent stream, property pages, and IGargle interfaces
STDMETHODIMP CGargle::NonDelegatingQueryInterface(REFIID riid, void **ppv) {

    if (riid == IID_IGargle) {
        return GetInterface((IGargle *) this, ppv);
    } else if (riid == IID_ISpecifyPropertyPages) {
        return GetInterface((ISpecifyPropertyPages *) this, ppv);
    } else if (riid == IID_IPersistStream) {
        AddRef();     // Add a reference count to ourselves
        *ppv = (void *)(IPersistStream *)this;
        return NOERROR;

    } else {
        return CTransInPlaceFilter::NonDelegatingQueryInterface(riid, ppv);
    }
} // NonDelegatingQueryInterface

Note This sample calls the CTransInPlaceFilter implementation of the member function to finish up.

Override the Base Class Member Functions

When you determine which base class to use( see Determine Which Base Classes to Use), you write the header and define which member function to implement. You decide either to derive your filter class from the transform base classes (CTransformFilter or CTransInPlaceFilter), or from the more generic CBaseFilter filter class. In this section, you learn how to override the following member functions.

Overriding the Transform Member Function

The Transform member function in your derived class is called each time the IMemInputPin::Receive method on the input pin of the filter is called to transfer another sample. Place the code that performs the actual purpose of the filter in this member function, or in the functions called from here. Copy transform filters will likely have a private Copy member function associated with the transform code, while transform-inplace functions will simply modify the code in one buffer.

Overriding the CheckInputType Member Function

During the pin connection, the CheckMediaType member function of the input pin is called to determine whether the proposed media type is acceptable. The CTransformInputPin::CheckMediaType member function is implemented to call the CheckInputType member function of the derived filter class with the media type. You must implement this to accommodate the media types your filter can handle. The following code sample outlines part of the CGargle::CheckInputType member function, which rejects any media type but MEDIATYPE_Audio.

HRESULT CGargle::CheckInputType(const CMediaType *pmt) {    
	...
    // reject non-Audio type
    if (pmt->majortype != MEDIATYPE_Audio) {
        return E_INVALIDARG;
    }

Overriding the CheckTransform Member Function

Copy transform filters can transform the media type from the input pin to output pin. Therefore, if the output pin is connected (so its media type is known), when the CTransformInputPin::CheckMediaType member function is called during connection, the CheckTransform member function of the derived class is called to verify that the transform from the input type to the output type is valid. It is also called when CTransformOutputPin::CheckMediaType is called.

In the CTransInPlaceFilter class, this member function is implemented in the base class header file to simply return S_OK, because the functions from CTransformFilter that call this member function are overridden in CTransInPlaceFilter to call CheckInputType instead. This assumes that the media type doesn't change in a transform-inplace filter, as it might in a copy transform filter.

Overriding the DecideBufferSize Member Function

Copy transform filters might be required to set the properties of the allocator into which they are copying. This is likely if the downstream filter has provided a newly created allocator (that is, one that hasn't passed an allocator from farther downstream), or if the output pin is forced to create its own allocator. In this case, the pure virtual CBaseOutputPin::DecideBufferSize member function is called from the CBaseOutputPin::DecideAllocator member function, and the derived class fills in the requirements for the buffer by calling the IMemAllocator::SetProperties method on the allocator object to which it has a reference.

The CTransInPlaceFilter::DecideBufferSize method is never called, because the allocator of another filter is always in use. It is implemented in the base class header file to return E_UNEXPECTED.

Overriding the GetMediaType Member Function

Pins provide enumerators to enable other objects to determine the pin's media type. A pin provides the media type enumerator (the IEnumMediaTypes interface), which the pin base classes implement to call the GetMediaType member function in the pin class. In the copy transform filter classes, each pin's CTransformOutputPin::GetMediaType member function simply calls the virtual CTransformFilter::GetMediaType member function in the filter class. Your derived class must implement this member function to provide each supported media type in a list of media types.

In the transform-inplace classes, the enumerators form a transparent channel between the filters upstream and downstream from the transform filter. If the transform filter's input pin must perform an enumeration, it obtains an enumerator from the downstream filter's input pin. If the output pin must perform an enumeration, it obtains an enumerator from the upstream filter's output pin. One consequence of this is that transform-inplace filters can't connect to each other unless at least one of them is connected to something else, because neither of the transform-inplace filters can propose any media type for the connection.

Overriding Pin Member Functions

If you derived your filter class from the transform classes and want more than one input or output pin, you must override the pin class (for example, CTransformInputPin or CTransformOutputPin). If you override the pin class, you must also override the GetPin member function of CTransformFilter or CTransInPlaceFilter, so that you can create pin objects from your derived classes. If you override one of the pin classes (for example, CTransformInputPin) and override GetPin to create the pin object, you must also override GetPin to create the other pin object of the same base class (for example, CTransformOutputPin).

If you want more than one input or output pin, it is often simpler to derive your filter from CBaseFilter rather than from one of the transform classes.

Overriding the CBaseOutput::DecideAllocator Member Function

The base classes implement CBaseOutputPin::DecideAllocator to let the output pin automatically use the downstream pin's allocator. One of the most common alterations in the derived class is to force the use of an object's own allocator (or one from an upstream filter). In the DirectShow model, for example, a source filter pushes media samples onto the next filter and requires its own allocator. For example, if you write a transform-inplace filter and insert it between a source filter and a decompressor filter, the transform filter must present the source filter's allocator to the decompressor. Therefore, you must override the CBaseOutputPin::DecideAllocator member function.

© 1998 Microsoft Corporation. All rights reserved. Terms of Use.

*Top of Page