Multimedia dedicated weblog.

Beginner’s guide: How to make a DirectShow filter VC++ project

June 5th, 2008 Posted in DirectShow

If you’re in need of writing a DirectShow filter and you’ve never done it before you might be interested in reading the following article. It explains how to configure all the necessary stuff in Visual C++ to make a very simple DirectShow filter.

Prerequisites

Let’s sum up what must be done before you can start dealing with DirectShow filters. First you really need to have a working Visual Studio with all updates and service packs installed. I believe that most people use at least VS2003 or newer version so I won’t be dealing with VS6.0 anymore. All examples and pictures are taken from a VS 2005. You should also be familiar with how C++ works and with the concept of libraries and linking. If you’re not, you don’t need to read this article anymore.

I strongly recommend to have the latest Platform SDK (also known as Windows SDK). You should also use specific SDK for your OS = not to use Vista SDK with Windows XP etc. You can visit www.microsoft.com to download e.g. Windows SDK for Vista.

Since DirectShow has been separated from the DirectX SDK it is not really necessary to have DX SDK installed but if you’re also concerned with 3D graphics and stuff you might also consider installing it.

Microsoft has developed a set of base classes to support the development of DS filters that are now a part of Window SDK and are located within the SDK in the {SDK}\Windows\v6.0\Samples\Multimedia\DirectShow\BaseClasses directory. Usually the library is configured without MFC support. If you’re familiar with MFC and would like to take advantage of the MFC library you might considering making your own builds of the baseclasses. To get more info on this issue try reading this article. If you are developing with VS2005 you should be aware that it requires a new runtime package that is not currently present on all systems and if you are linking MFC as shared library your applications won’t work unless the user installs the SP1 Redistributable Package. To avoid this you should simply use MFC as static libraries. I would also recommend building all 4 types of baseclasses library :

  • MFC Shared, Debug as strmbasdu.lib
  • MFC Shared, Release as strmbaseu.lib
  • MFC Static, Debug as strmbasdus.lib
  • MFC Static, Release as strmbaseus.lib

Suffix *u means unicode builds, *s means MFC static, *d means debug. The baseclasses library from the Windows SDK contains an entry point named "DllEntryPoint". I’ve created a function named "FilterDllMain" that also initializes MFC properly. If you see these names appear don’t get confused - they both actually mean the same.

Then you need to add the include and lib path to Visual Studio so you won’t have to specify full path to headers and .lib files for each filter. You can do this by opening Tools -> Options. And then add the folder where baseclasses sources are kept just as on the following image. I’m using my own build of baseclasses that is named "libMonoDShow".

And in the same manner add also path to the "Library files".

New Project

Start a new project by clicking File -> New -> Project… and select Win32 / Win32 Project. Select some folder to put the filter in and also enter some name. In this tutorial we’ll be writing a simple audio processing filter so I’ve named the project "audio_volume".

In the following page of the wizard select DLL as application type and make it an empty project.

Writing The Filter

Now we should have an empty project created so it’s time to add some stuff. It is always a good thing to have a header file with all the filter-specific stuff like CLSID, interface IIDs and interface definitions or configuration structures. So let’s add one such file.

Use the Tools -> Create GUID tool to generate a class ID for the new filter. I prefer using the third type - the one with "static const struct GUID ….". Use some proper name such as "CLSID_AudioVolume" for the newly generated GUID. Once this is done the file should look like this :

// {212A25F7-7000-4f74-9E4C-6FFB3F595EE4}
static const GUID CLSID_AudioVolume =
{ 0×212a25f7, 0×7000, 0×4f74, { 0×9e, 0×4c, 0×6f, 0xfb, 0×3f, 0×59, 0×5e, 0xe4 } };

Now it’s time to create the header file for the filter itself so add a new .H file and save it as "audio_filter.h". Insert the following code into the "audio_filter.h" file :

#pragma once

class AudioVolume : public CTransformFilter
{
private:
    CMediaType        mtIn;
public:
    // constructor & destructor
    AudioVolume(LPUNKNOWN pUnk, HRESULT *phr);
    virtual ~AudioVolume();
    static CUnknown *WINAPI CreateInstance(LPUNKNOWN pUnk, HRESULT *phr);

   // CTransformFilter overriden
    virtual HRESULT CheckInputType(const CMediaType *mtIn);
    virtual HRESULT CheckTransform(const CMediaType *mtIn, const CMediaType *mtOut);
    virtual HRESULT DecideBufferSize(IMemAllocator *pAlloc,
                                                      ALLOCATOR_PROPERTIES *pProp); 
    virtual HRESULT GetMediaType(int iPosition, CMediaType *pmt);
    virtual HRESULT SetMediaType(PIN_DIRECTION direction, const CMediaType *pmt);
    virtual HRESULT Transform(IMediaSample *pIn, IMediaSample *pOut);
};

This should be quite enough for the tutorial. All methods will be explained later. Now add a CPP file where the filter implementation should be. Save it as "audio_filter.cpp" and write the following code :

AudioVolume::AudioVolume(LPUNKNOWN pUnk, HRESULT *phr) :
    CTransformFilter(_T("Audio Volume"), pUnk, CLSID_AudioVolume)
{
    if (phr) *phr = NOERROR;
}

AudioVolume::~AudioVolume()
{
}

CUnknown *WINAPI AudioVolume::CreateInstance(LPUNKNOWN pUnk, HRESULT *phr)
{
    AudioVolume *vol = new AudioVolume(pUnk, phr);
    if (!vol) {
        if (phr) *phr = E_OUTOFMEMORY;
    }
    return vol;
}

These were just plain constructor, destructor and a static method to provide instances of our audio filter. Now we need to deal with CTransformFilter virtual methods.

When the upstream filter connects to our audio processing filter it must first negotiate the proper media type. For the purpose of this tutorial we only accept 16-bit raw PCM audio data so the AudioVolume::CheckMediaType might look like this :

HRESULT AudioVolume::CheckInputType(const CMediaType *mtIn)
{
    // we only want raw audio
    if (mtIn->majortype != MEDIATYPE_Audio) return E_FAIL;
    if (mtIn->subtype != MEDIASUBTYPE_PCM) return E_FAIL;
    if (mtIn->formattype != FORMAT_WaveFormatEx) return E_FAIL;

   // and we only want 16-bit
    WAVEFORMATEX    *wfx = (WAVEFORMATEX*)mtIn->pbFormat;
    if (wfx->wBitsPerSample != 16) return E_FAIL;

    return NOERROR;
}

Our filter does not encode/decode or scramble the data so the output should also be PCM audio.

HRESULT AudioVolume::CheckTransform(const CMediaType *mtIn, const CMediaType *mtOut)
{
    HRESULT hr = CheckInputType(mtIn);
    if (FAILED(hr)) return hr;

   // must also be PCM audio
    if (mtOut->majortype != MEDIATYPE_Audio) return E_FAIL;
    if (mtOut->subtype != MEDIASUBTYPE_PCM) return E_FAIL;

    return NOERROR;
}

Our filter should offer exactly the same media type for the output so we keep a copy of the input type.

HRESULT AudioVolume::SetMediaType(PIN_DIRECTION direction, const CMediaType *pmt)
{
    // ask the baseclass if it’s okay to go
    HRESULT    hr = CTransformFilter::SetMediaType(direction, pmt);
    if (FAILED(hr)) return hr;

    // keep a local copy of the type
    if (direction == PINDIR_INPUT) {
        mtIn = *pmt;
    }

    return NOERROR;
}

HRESULT AudioVolume::GetMediaType(int iPosition, CMediaType *pmt)
{
    if (iPosition < 0) return E_INVALIDARG;
    if (iPosition > 0) return VFW_S_NO_MORE_ITEMS;

    // the input pin must be connected first
    if (m_pInput->IsConnected() == FALSE) return VFW_S_NO_MORE_ITEMS;

   // we offer only one type - the same as input
    *pmt = mtIn;
    return NOERROR;
}

We need to make clear how large buffers should be used when delivering data downstream. This might need more sophisticated solution but for this tutorial this can be solved easily :

HRESULT AudioVolume::DecideBufferSize(IMemAllocator *pAlloc,
        ALLOCATOR_PROPERTIES *pProp)
{
    WAVEFORMATEX    *wfx = (WAVEFORMATEX*)mtIn.pbFormat;

   // this might be put too simly but
    // we should be able to deliver max 1 second
    // of raw audio
    pProp->cbBuffer        = wfx->nAvgBytesPerSec;

    // when working with audio always try to have
    // some spare buffer free
    pProp->cBuffers        = 3;

    ALLOCATOR_PROPERTIES    act;
    HRESULT hr = pAlloc->SetProperties(pProp, &act);
    if (FAILED(hr)) return hr;

    if (act.cbBuffer < pProp->cbBuffer) return E_FAIL;
    return NOERROR;
}

And the final phase - audio processing itself. We only multiply each PCM sample by the factor of 0.5. That means we’re amplify the signal by -6.0205 dB.

HRESULT AudioVolume::Transform(IMediaSample *pIn, IMediaSample *pOut)
{
    BYTE    *bufin, *bufout;
    long    sizein;

    // get the input and output buffers
    pIn->GetPointer(&bufin);
    pOut->GetPointer(&bufout);

    // and get the data size
    sizein = pIn->GetActualDataLength();

   // since we’re dealing with 16-bit PCM
    // it might be convenient to use "short"
    short    *src = (short*)bufin;
    short    *dst = (short*)bufout;
    int        samples = sizein / sizeof(short);

   // now deal with the samples and lower the volume
    for (int i=0; i<samples; i++) {
        dst[i] = src[i] * 0.5;
    }

   // and set the data size
    pOut->SetActualDataLength(samples * sizeof(short));
    return NOERROR;
}

That’s for the functionality. Now we need to describe the filter so the GraphBuilder can locate and insert the filter if necessary.

Registry Information

It is a good habit to create a special file for the purpose of registry information and DLL entry points. I use to follow the "*_reg.cpp" pattern so in this tutorial I’d create an "audio_reg.cpp" file and insert the following structures. I strongly recommend to use standard merit values and to use MERIT_UNLIKELY or MERIT_NORMAL unless really necessary. You can really screw many things by entering stupid values here.

// Media Types
const AMOVIESETUP_MEDIATYPE sudPinTypes[] =  
{
    { &MEDIATYPE_Audio, &MEDIASUBTYPE_PCM }
};

// Pins
const AMOVIESETUP_PIN psudPins[] =
{
    { L"Input", FALSE, FALSE, FALSE, FALSE, &CLSID_NULL, NULL, 1, &sudPinTypes[0] },
    { L"Output", FALSE, TRUE, FALSE, FALSE, &CLSID_NULL, NULL, 1, &sudPinTypes[0] }
};  

// Filters
const AMOVIESETUP_FILTER sudAudioVolume =
{
    &CLSID_AudioVolume, L"Audio Volume", MERIT_UNLIKELY, 2, psudPins
};                    

// Templates
CFactoryTemplate g_Templates[]=
{
    { L"Audio Volume", &CLSID_AudioVolume, AudioVolume::CreateInstance, NULL, &sudAudioVolume }
};

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

Since DirectShow filters are COM objects they should expose the functions to register and unregister the library. If you would ever need to perform some special tasks (e.g. registering file extensions) this is the place.

STDAPI DllRegisterServer()
{
    return AMovieDllRegisterServer2(TRUE);
}

STDAPI DllUnregisterServer()
{
    return AMovieDllRegisterServer2(FALSE);
}

 

Headers

Now create a file named "headers.h" and put the following inside :

#include <afxwin.h>
#include <afxtempl.h>
#include <atlbase.h>
#include <streams.h>
#include <initguid.h>
#include <mmsystem.h>
#include <mmreg.h>
#include "audio_types.h"
#include "audio_filter.h"

and include the header.h file in all *.cpp files.

External Symbols

Now we’re done with programming but we still need to specify a few things. First - each DLL library (whether named .dll or .ax) should expose some functions. COM libraries should expose at least 4 functions :

  • DllRegisterServer
  • DllUnregisterServer
  • DllGetClassObject
  • DllCanUnloadNow

the first 2 have been declared in the previous paragraph and the last 2 are implemented by the baseclasses library. But we need to make it clear to the linker that we really want to expose them so we should add a new item to the project - "Module-Definition File (.def)" and call it "audio_volume.def". Edit it’s content so it would look like this :

LIBRARY         audiovolume.ax
EXPORTS
    DllGetClassObject        PRIVATE
    DllCanUnloadNow         PRIVATE
    DllRegisterServer         PRIVATE
    DllUnregisterServer      PRIVATE

 

Project Options

And the last few touches are done in the project options dialog.

In the Configuration Properties -> General page select the proper MFC usage type.

In the Linker -> General page enter the desired output file name.

In the Linker -> Input page add the baseclasses libraries and also "strmiids.lib" and "winmm.lib" and make sure the module definition is valid.

And finally in the Linker -> Advanced page enter the proper entry point. If you are using MFC-enabled baseclasses as I was, you might be using the same function as I am - "FilterDllMain". If you’re using the original baseclasses library you might enter DllEntryPoint@12 instead.

Now you should be able to build the filter :)

Testing

You can test the filter very easily using either GraphStudio or GraphEdit tools. I’ve constructed a simple graph like the following. Try it run with and without the "Audio Volume" filter and you should hear the effect.

Conclusion

Congratulations for reading this far :) Even if all this might look scary it’s a simple routine that can be "clicked" within 2 minutes once you know what to do. I hope you’ll find this information useful.

You can download the full project source for this tutorial here : audio_volume.zip (7 KB)

Enjoy,

Igor

  1. 32 Responses to “Beginner’s guide: How to make a DirectShow filter VC++ project”

  2. By andy vt on Jun 6, 2008

    Thanks for the sample.

    Can you recommend a resource if I wanted to modify this to work with 24-bit PCM?

  3. By Igor Janos on Jun 7, 2008

    Hmm. Can’t think of anything that deals specifically with 24-bit PCM. But it should not be very difficult to modify. Just change the mediatype checking functions. And of course the Transform method would need to be done in a different manner since there’s not a native 24-bit integer type.

  4. By andy vt on Jun 9, 2008

    The transforms the bit that’s causing me grief.

    I don’t understand what’s in the media sample (not a media programmer by trade), so I don’t know what needs to be done to with the data to make it work. Can I multiply each BYTE in the array by .5, or is it necessary to work with the sample as a whole?

    I’m happy to read documentation, but I’m not sure where to start.

  5. By Igor Janos on Jun 9, 2008

    The IMediaSample object contains a buffer that contains raw audio data. If you are working with 2-channel@16-bit mode then each audible audio sample contains 2×16 bits of data = 4 bytes where the first 2 bytes represent the sample for the left channel and the next 2 bytes represent audio data for the right channel.

  6. By andy vt on Jun 9, 2008

    Ah, I get what I’m doing wrong. Thanks!

    Are the contents of the IMediaSample documented somewhere (how did you find out how it stores the audio data)?

  7. By Igor Janos on Jun 9, 2008

    You might want to check out http://msdn.microsoft.com/library and look for DirectShow. Hm…. basically I took a look and I saw :)

  8. By andy vt on Jun 9, 2008

    I’m familiar with the documentation provided with the platform SDK, but I don’t remember seeing anywhere how data is laid out in the IMediaSample’s buffer.

    Anyway, thanks again for the sample.

  9. By ksh8281 on Jun 12, 2008

    thank you…………!!!

  10. By Angel on Jun 18, 2008

    Thanks alot, your article really made it more clear to me.

    I have one question tough - how can I test the filter on Windows Mobile 6?

  11. By Igor Janos on Jun 18, 2008

    Can’t tell. I’ve never done any coding for windows mobile :-\

  12. By estee on Jun 24, 2008

    Error 1 fatal error C1083: Cannot open include file: ‘headers.h’: No such file or directory c:\users\st\documents\visual studio 2005\projects\rad\audio_filter.cpp 1
    I face this error when I build the solutions. May I know what is this problem?

  13. By Igor Janos on Jun 25, 2008

    when you unzip the audio_volume.zip file there should be several files inside and ‘headers.h’ should be one of them. How are you building the project ?

  14. By mason on Aug 15, 2008

    hi Igor Janos:

    i want to grab pics from the all kinds of clips supported by the K-lite. how to make sure my program is using the ffdshow filter?!
    how to insure my program’s flow is the same as the graph charted by the graphStudio.
    for example: 1st step:use a mp4 Spilitter,2nd step:use ffdshow video Decoder.last. video Renderer. how to arrange the sequence of the filter in my program? and could you please give me some suggestion on how to implement my program?

  15. By Igor Janos on Aug 17, 2008

    I think the best way to do this is to start reading the tutorials from the MSDN. You might start with this one : http://msdn.microsoft.com/en-us/library/ms783787(VS.85).aspx

    If you want to use ffdshow, you would need to insert it into the graph before calling RenderFile.

  16. By Alex on Oct 3, 2008

    Excellent article. Do you have a similar one for an asynchronous source file filter? Even a compilable one that will joint to a mpeg2 demultiplexer would be nice.

    Looking forward to hearing from you.

    Keep up with the good work.
    Alex

  17. By Igor Janos on Oct 5, 2008

    Not yet.. but perhaps in time. I already have some plans for articles in the future.

  18. By rev on Nov 15, 2008

    Thank u very much for the straightforward article !.

    I have a question here. How can i filter to modify the frequency of the input audio stream ? Should do i use a fft and then modify it ?

  19. By Igor Janos on Nov 15, 2008

    Changing input audio frequency is just a matter of math - try searching for polyphase filter bank. You might take a look at ffdshow audio filter source code. It contains a nice audio resampler.

  20. By Amir Eshaq on Dec 29, 2008

    Hi

    I seem to have managed to compile the code, and I can also see the filter in GraphEdit unfortunately I can’t seem be able to insert it to try it out. No response at all within GraphEdit. Any ideas why?

    Thanks in advance.

  21. By Amir Eshaq on Dec 29, 2008

    What I mean above is that:
    1. It compiled fine
    2. It registered fine
    3. It appears under the Filters list fine

    Problem is it does not insert into GraphEdit. So, I am not sure if it would work at all.

  22. By Igor Janos on Dec 29, 2008

    Hmm. Try creating the filter with CoCreateInstance call. If the filter registered fine it should be possible to load the AX library. Or try placing some breakpoints around to see if one gets hit.
    Also make sure you’re using proper graphedit - 32bit for 32bit filters.

  23. By Amir Eshaq on Dec 30, 2008

    Thanks Janos for your quick response.

    I am not really a C++ Developer but I do manage to understand what is going on in the code - I am currently reading through the Windows SDK - DirectShow part.

    Now yesterday I downloaded a Screen Capture filter from http://www.medialooks.com/products/directshow_filters/screen_capture.html and installed. To my surprise, the same issue. It appears in the list of filters but won’t insert into graphedit.

    Could it be an issue in Windows Vista, I wonder? I will give your suggestion above a try and see if I can load it in code.

    I tried with graphedit provided by the Windows SDK (graphedt) as well as yours (which is a great tool indeed).

  24. By Amir Eshaq on Dec 30, 2008

    I just downloaded your FLV Mux filter and registered it. It appears in the filters list and it adds onto graphedit. One thing I noticed in the properties is that for the FLV Mux, the File Name shows as mmflvmux.ax but for the filter I built based on your code on this page has the File Name as regsvr32.exe. Any ideas where I might be going wrong?

  25. By Abraham Lincoln on Feb 26, 2009

    i have downloaded “audio_volume” filter.after dll built.

    i am not able to find Audio_volume filter in GraphStudio

    How do i load/check this filter.

  26. By Igor Janos on Feb 26, 2009

    you must register it with regsvr32.exe.
    It is usually located in c:\windows\system32

  27. By Beatriz on Apr 16, 2009

    Good article! I has helped me a lot :-)

    Now I have new problem. I have compiled and registered the sample filter successfully. I can see it in the list of filters in GraphEdit. However, I would like to create my own directshow c++ application. So at some point in my code I write:

    IBaseFilter *pDummy = NULL;
    hr = CoCreateInstance (CLSID_AudioVolume, NULL, CLSCTX_INPROC_SERVER, IID_IBaseFilter, (void **)&pDummy);

    But I get the error that CLSID_AudioVolume is an unknown identifier. Am I instancing it correctly?

    I have checked the registry of this filter and it does not look like the ones you show in http://blog.monogram.sk/janos/2008/02/16/how-filters-are-described-in-the-registry-database/.

    I wonder if I missed something while creating the filter or when I registered it.

    Thanks for your help!

    Beatriz.

  28. By Igor Janos on Apr 20, 2009

    Are you able to create the filter with GraphEdit?

  29. By Beatriz on Apr 22, 2009

    Yes, I can see the filter in GraphEdit.

    The problem is that I have to include the header file which contains the GUID definition (audio_types.h in your sample code) and initguid.h in my application. Then, it should work :-)

  30. By Igor Janos on Apr 22, 2009

    :) Yes… I thought it was quite obvious. I’ll try to mention this next time.

  31. By Arun on Jul 17, 2009

    Nice Example dude… It works just fine…[:)]

    Could you provide a similar thing for source filters as well?

    I searched a lot for it but couldnt get any place where things are explained as well as in here…

  32. By Sreejith on Aug 28, 2009

    Thanks.. this article is just awesome and helped me get on the right track.. :)

    I was able to compile the project into a dll, but I’m unable to register it with regsvr32. I am using Visual studio professional 2008. The error I get is: “dll module was loaded, but the entry-point DllRegisterServer was not found”. Do you have any idea why this might be happening?

    Thanks..

  33. By john mccullough on Oct 5, 2009

    Can you explain the default DllEntryPoint - DllEntryPoint@12?

    I’ve been trying unsuccessfully to build a filter on the x64 platform using DllEntryPoint@12 - it returns “unresolved external symbol” for DllEntryPoint@12 - is there a different default entry point for the 64 bit baseclasses, or is there something else I should link against to get this (currently linking to strmbasd.lib winmm.lib strmiids.lib)

    cheers,
    j

Post a Comment