织梦CMS - 轻松建站从此开始!

罗索

用VS2005建立DirectShow Filter

落鹤生 发布于 2012-11-26 22:30 点击:次 
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.
TAG:

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".


图片1

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".

 


图片4

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


图片2


图片3

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.


图片1

Use the Tools -> Create GUID ("C:\Program Files\Microsoft Visual Studio\Common\Tools\GUIDGEN.EXE")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.


图片5

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


图片6

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


图片7

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.


图片8

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.


图片9

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.

(caiweihui)
本站文章除注明转载外,均为本站原创或编译欢迎任何形式的转载,但请务必注明出处,尊重他人劳动,同学习共成长。转载请注明:文章转载自:罗索实验室 [http://www.rosoo.net/a/201201/15605.html]
本文出处:新浪博客 作者:caiweihui 原文
顶一下
(3)
100%
踩一下
(0)
0%
------分隔线----------------------------
发表评论
请自觉遵守互联网相关的政策法规,严禁发布色情、暴力、反动的言论。
评价:
表情:
用户名: 验证码:点击我更换图片
栏目列表
将本文分享到微信
织梦二维码生成器
推荐内容