기본 Direct Show Filter 만들기 (변환필터)


첨부파일audio_volume-sampoo00.zip

1. 새 프로젝트

File -> New -> Project… 

 

 

Win32 / Win32 Project 선택 , 폴더를 선택하고 Name 을 입력합니다. 여기서는 “audio_volume” 프로젝트로
프로젝트 명을 정하겠습니다.

다음 페이지에서 Application type을 DLL 로 Application option 은 Empty project 로
선택합니다.

2. Filter 만들기

CLSID, interface IID, interface 정의 등을  가지는 헤더파일을 하나 만듭니다.

프로젝트의 헤더파일에서 추가> 새항목으로 새로운 헤더파일을 하나 추가합니다.

헤더파일의 이름은 audio_types.h” 로 합니다.

 

새로운 필터를 위한 class ID를 생성하기 위해서는 도구 -> GUID 만들기 를 사용합니다.

 필자는 “static const struct GUID ….” 항목을 주로 사용합니다.

새롭게 생성된 GUID를 위해 “CLSID_AudioVolume” 라는 이름을 사용하도록 하겠습니다.

아래는 audio_types.h 의 내용입니다.

// {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 }
};

filter 를 위한 기본 헤더파일을 만듭니다. 파일명은 “audio_filter.h” 로 하고 아래의 코드를 추가시킵니다.

#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);
};

다음은 filter 구현을 위해 cpp 파일을 추가합니다. 파일명은 “audio_filter.cpp” 로 하고 다음 코드를
써줍니다.

아래는 생성자와 소멸자 그리고 audio filter 의 인스턴스를 넘겨주는 정적 메소드 입니다.

AudioVolume::AudioVolume(LPUNKNOWN pUnk, HRESULT *phr) :
   
CTransformFilter(NAME(“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;

}

이제 우리는 CTransformFilter 의 함수들을 오버라이딩 해야되는데요..

upstream 필터가 현재 필터에 연결되기 위해서는 첫번째로 적당한 media type을 협상하게 됩니다.

여기서는 16-bit raw PCM audio data만 허용할 것입니다.

그래서 AudioVolume::CheckMediaType 함수를 아래와 같이 override 합니다.

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;

}

우리 필터는 encode/decode 또는 데이터를 스크렘블 하지 않고 그냥 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;

}

입력 media type과 출력 media type을 동일하게 하기 위해 입력 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;

}

얼마나 큰 버퍼를 사용할 지 명시적으로 만들어 줍니다.

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;

}

 

마지막 단계 – audio 처리를 합니다. 우리는 각 PCM sample 에 0.5 를 곱하도록 하고 있습니다. 이것은 -6.0205 dB
만큼 신호를 증폭시키는 것입니다.

(0.5를 곱하면 크게 차이가 나지 않는다. 실제 필터를
만들때 0을 곱해서 음소거를 알아볼수도 있고 2 정도를 곱해서 소리가 엄청 크게 됨을 알수도 있다.)

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] = (short)(src[i] * 0.5);
    }

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

}

기능적인 구현은 끝났습니다.

다음은 GraphBuilder 가 필터를 인식할수 있도록 여러가지를 추가시켜야 됩니다.

3. 레지스트리 정보

레지스트리 정보와 DLL entry points 를 위해 따로 파일을 만드는 것은 좋은 습관입니다.

필자는 “*_reg.cpp” 형태를 사용하곤 합니다. 그래서 여기서는 “audio_reg.cpp” 파일을 만들었습니다. 그리고 아래 코드를
추가합니다.

필자는 표준 merit 값을 사용하기를 강력하게 추천합니다. 특별한 일이 없으면 MERIT_UNLIKELY or
MERIT_NORMAL 을 사용하세요.

// 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]);

DirectShow filter는 COM 객체입니다. 따라서 라이브러리를 등록하고 등록 해제 하기 위해서는 아래 코드를 추가하세요

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

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

extern “C” BOOL WINAPI DllEntryPoint(HINSTANCE, ULONG, LPVOID);

BOOL APIENTRY DllMain(HANDLE hModule,
       DWORD  dwReason,
      
LPVOID lpReserved)
{
      return
DllEntryPoint((HINSTANCE)(hModule), dwReason, lpReserved);
}

#pragma warning( disable:4514)

 

4. Headers

다음은 “headers.h” 파일을 만들고 아래 코드를 입력합니다.

#include <streams.h>

#include <initguid.h>
#include
“audio_types.h”
#include “audio_filter.h”

 

그리고 모든 *.cpp 파일에 header.h 파일을 include 시킵니다.

5. 외부기호

프로그래밍 하는 것은 다 했습니다. 이제 DLL 라이브러리는 외부로 함수를 함수를 노출시켜 주어야 되는데 COM 라이브러리는 아래와 같이
최소 4개의 함수를 노출시켜야 됩니다.

  • DllRegisterServer
  • DllUnregisterServer
  • DllGetClassObject
  • DllCanUnloadNow

 

처음 두개는 앞에서 이미 선언했고 뒤의 두개는 bassclasses 라이브러리에 구현되어 있습니다.

그러나 우리는 링커에게 우리가 진짜로 이 함수들을 노출시키고 싶다는 걸 알려야 됩니다.

그래서 새항목 추가를 통해 프로젝트에 “모듈 정의 파일(.def) 파일을 추가하고 “audio_volume.def” 파일이름을
정합니다.

그리고 아래와 같이 편집해 줍니다.

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

 

 6. 프로젝트 옵션

  빌드 환경 설정 : 프로젝트 속성에서

   1) C/C++ > 고급 > 호출규칙: __stdcall 으로 변경

   2) 일반 > 문자집합: 멀티바이트 문자집합 사용 으로 변경

  
3) C/C++ 추가포함 디렉토리 에서 디렉토리 추가

  
    – C:\SKD\WSDK\Samples\Multimedia\DirectShow\BaseClasses

  
    – C:\SKD\WSDK\Include

      
– C:\SKD\WSDK\lib

  
    : BaseClasses를 맨 상위로 오도록 해야된다 그렇지 않으면

  
     
C:\Program Files\Microsof
SDKs\Windows\v6.0\Samples\Multimedia

        
\DirectShow\BaseClasses\refclock.h(80) : error C2061: syntax error :  identifier
‘CAMSchedule’

  
    에러가 발생할 수도 있습니다.

  
4) C/C++ > 코드생성 > 런타임 라이브러리 : 다중 쓰레드 디버그 DLL (/MDd) 으로 변경

  
5) 링커 > 입력 > 추가 종속성

  
    * Debug Build: Strmbasd.lib strmiids.lib  Msvcrtd.lib Winmm.lib

  
    * Debug Build: Strmbase.lib strmiids.lib  Msvcrt.lib Winmm.lib

  
6) 링커 > 출력파일의 확장자를 dll 에서 ax로 변경 ( dll 로 해도 상관은 없습니다만 .def 파일의 내용도 바꿔 주어야
됩니다.)      

  
7) C/C++ > 미리 컴파일된 헤더: 미리 컴파일된 헤더 사용 안함 으로 설정

   8) 링커 > 입력 > 모든 기본 라이브러리 무시:
예(/NODEFAULTLIB)

7. 테스트

GraphEdit 툴로 “Audio Volume” 필터를 추가하기 전에 음원 파일을 동작시키고 추가한 다음에 동작시키면 audio 볼륨에
대한 효과를 알수 있을 것입니다.


답글 남기기

이메일 주소는 공개되지 않습니다.