Date

32bit RGB 画像の RGB の各要素を入れ替えるトランスフォームフィルタを実装する。ややサイケデリックな映像になる。

フィルタ情報の定義

今回はCMyTransformというクラス名でフィルタクラスを定義する。CFactoryTemplate g_Templatesを次のように書く。

1
2
3
CFactoryTemplate g_Templates [] = {
     { TEMPLATE_NAME , &CLSID_MyTransform, CMyTransform::CreateInstance, NULL, &afFilterInfo }
};

MyTransformFilter.hを新規作成し、フィルタの情報やクラスを定義していく。stdafx.h の末尾に#include" MyTransformFilter.h"を追加する。

まずCFactoryTemplate g_Templatesで使われていたTEMPLATE_NAME、フィルタ名、出力ピンの名前を定義する。

1
2
3
4
#define TEMPLATE_NAME    (L"My Transform Filter")
#define FILTER_NAME     (TEMPLATE_NAME)
#define INPUT_PIN_NAME  (L"Input")
#define OUTPUT_PIN_NAME (L"Output")

フィルタのクラス ID を決める。Playform SDK に付属している guidgen.exe を使って GUID を生成する。クリップボードにコピーしてソースコードに追記する。

1
2
3
// {B5FFC4E9-66E8-46a7-9C2C-3ACC963D1D1B}
DEFINE_GUID(CLSID_MyTransform,
0xb5ffc4e9, 0x66e8, 0x46a7, 0x9c, 0x2c, 0x3a, 0xcc, 0x96, 0x3d, 0x1d, 0x1b);

AMOVIESETUP_MEDIATYPE 構造体 sudPinTypesを宣言する。メジャーメディアタイプは MEDIATYPE_Video、マイナータイプはMEDIASUBTYPE_RGB32とする。

1
2
3
4
5
const AMOVIESETUP_MEDIATYPE sudPinTypes =
{
    &MEDIATYPE_Video,        // Major type
    &MEDIASUBTYPE_RGB32      // Minor type
};

AMOVIESETUP_PIN 構造体 sudPinsを宣言する。トランスフォームフィルタは入力ピンと出力ピンがあるので、それぞれ宣言する必要がある。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
const AMOVIESETUP_PIN sudPins[] =
{
    {
        INPUT_PIN_NAME,
        FALSE,
        FALSE,
        FALSE,
        FALSE,
        &CLSID_NULL,
        NULL,
        1,
        &sudPinTypes
    },
    {
        OUTPUT_PIN_NAME,
        FALSE,
        TRUE,
        FALSE,
        FALSE,
        &CLSID_NULL,
        NULL,
        1,
        &sudPinTypes
    },
};

AMOVIESETUP_FILTER 構造体 afFilterInfoにフィルタ情報を宣言する。

1
2
3
4
5
6
7
8
const AMOVIESETUP_FILTER afFilterInfo=
{
    &CLSID_MyTransform,
    FILTER_NAME,
    MERIT_DO_NOT_USE,
    2,
    sudPins
};

トランスフォームフィルタのクラス CMyTransformを定義する。CTransformFilter クラスを継承する。トランスフォームフィルタを作る場合の基底クラスはCTransformFilterCTransInPlaceFilterとなる。データをバッファ間でコピーせずに入力データを修正する場合はCTransInPlaceFilterを継承する。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
class CMyTransform : CTransformFilter {
public:
    CMyTransform(LPUNKNOWN pUnk,HRESULT *phr);
    ~CMyTransform();
    static CUnknown * WINAPI    CreateInstance(LPUNKNOWN pUnk, HRESULT *phr);
    HRESULT CheckInputType(const CMediaType *mtIn);
    HRESULT GetMediaType(int iPosition, CMediaType *pMediaType);
    HRESULT CheckTransform(const CMediaType *mtIn, const CMediaType *mtOut);
    HRESULT DecideBufferSize(IMemAllocator *pAlloc, ALLOCATOR_PROPERTIES *ppropInputRequest);
    HRESULT Transform(IMediaSample *pIn, IMediaSample *pOut);
    HRESULT CompleteConnect(PIN_DIRECTION direction, IPin *pReceivePin);
protected:
private:
    VIDEOINFOHEADER        m_Vih;
};

トランスフォームフィルタクラスの実装

MyTransformFilter.cpp を実装する。コンストラクタ、デストラクタ、CreateInstance メソッドはレンダラーフィルタとほぼ同じ。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
CMyTransform::CMyTransform(IUnknown *pUnk, HRESULT *phr) :
    CTransformFilter(FILTER_NAME, pUnk, CLSID_MyTransform)
{
}
CMyTransform::~CMyTransform() {
}
CUnknown * WINAPI CMyTransform::CreateInstance(LPUNKNOWN pUnk, HRESULT *phr) {
    CMyTransform *pNewFilter = new CMyTransform(pUnk, phr);
    if (pNewFilter==NULL) {
        *phr=E_OUTOFMEMORY;
    }
    return pNewFilter;
}

CheckInputType メソッドを実装する。このメソッドは上流フィルタからメディアタイプを提示されたときに、それを受け付けるかどうかの処理を実装する。ここでは、32bit RGB の映像を受け付けるようにしている。

1
2
3
4
5
6
7
8
9
HRESULT CMyTransform::CheckInputType(const CMediaType *mtIn) {
    if( *mtIn->Type()!=MEDIATYPE_Video ||
        *mtIn->Subtype()!=MEDIASUBTYPE_RGB32 ||
        *mtIn->FormatType()!=FORMAT_VideoInfo)
    {
        return VFW_E_TYPE_NOT_ACCEPTED;
    }
    return S_OK;
}

GetMediaTypeメソッドを実装する。このメソッドは、このフィルタが出力するメディアタイプを提示する。iPosition はインデックス番号である。ここでは、上流フィルタのメディアタイプと同じものを出力するようにしている。フィルタの入力ピンが接続されてから呼び出される。m_pInputは基底クラスにあるメンバで入力ピンを表す。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
HRESULT CMyTransform::GetMediaType(int iPosition, CMediaType *pMediaType) {
    if(iPosition<0) {
        return E_INVALIDARG;
    }else if(iPosition>0) {
        return VFW_S_NO_MORE_ITEMS;
    }
    // 優先出力タイプは 入力メディアタイプと同じとする
    // ※ データフォーマットを変換するフィルタ(encoder,decoderなど) は異なるメディアタイプになる
    *pMediaType=m_pInput->CurrentMediaType();
    return S_OK;
}

CheckTransformメソッドを実装する。出力メディアタイプが現在の入力タイプと互換性があるかどうかを調べるときに呼び出される。入出力のメディアタイプ両方が受け入れられる場合はS_OK、それ以外はVFW_E_TYPE_NOT_ACCEPTEDを返す。ここでは、入出力のメディアタイプが一致した場合のみS_OKを返している。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
HRESULT CMyTransform::CheckTransform(const CMediaType *mtIn, const CMediaType *mtOut) {
    if( *mtIn->Type()!=MEDIATYPE_Video ||
        *mtIn->Subtype()!=MEDIASUBTYPE_RGB32 ||
        *mtIn->FormatType()!=FORMAT_VideoInfo)
    {
        return VFW_E_TYPE_NOT_ACCEPTED;
    }
    if(*mtIn!=*mtOut) {
        return VFW_E_TYPE_NOT_ACCEPTED;
    }
    return S_OK;
}

CompleteConnectメソッドを実装する。ピンの接続を完了したときに呼ばれる。ここでは、入力ピンのメディアタイプからVIDEINFOHEADERを取得している。

1
2
3
4
5
6
HRESULT CMyTransform::CompleteConnect(PIN_DIRECTION direction, IPin *pReceivePin) {
    // 入力ピンのメディアフォーマットを保存しておく
    CMediaType &type=m_pInput->CurrentMediaType();
    CopyMemory(&m_Vih,type.Format(), sizeof(VIDEOINFOHEADER));
    return S_OK;
}

DecideBufferSizeメソッドを実装する。基本的にソースフィルタのときと同じ。ここでは、入力メディアタイプのデータサイズを取得してアロケータにバッファを要求している。正しく確保されていればS_OKを返している。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
HRESULT CMyTransform::DecideBufferSize(IMemAllocator *pAlloc, ALLOCATOR_PROPERTIES *ppropInputRequest) {
    if(m_pInput!=NULL && m_pInput->IsConnected()==FALSE) {
        return E_FAIL;
    }
    // バッファサイズを取得し、アロケータプロパティに設定
    ppropInputRequest->cBuffers=1;
    ppropInputRequest->cbBuffer=this->m_pInput->CurrentMediaType().GetSampleSize();
    ALLOCATOR_PROPERTIES actual_prop;
    HRESULT hr=pAlloc->SetProperties(ppropInputRequest, &actual_prop );
    if(FAILED(hr)) {
        return hr;
    }
    // アロケータは要求に対して正確に一致できるとは限らないため、確保されたかチェックする
    if( ppropInputRequest->cBuffers>actual_prop.cBuffers ||
        ppropInputRequest->cbBuffer>actual_prop.cbBuffer)
    {
        return E_FAIL;
    }
    return S_OK;
}

最後にTransformメソッドを実装する。ここでは、実際に映像に加工する処理を書く。ピクセルごとに R,G,B の要素を取得している。R→G G→B B→R のように置き換えて出力している。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
HRESULT CMyTransform::Transform(IMediaSample *pIn, IMediaSample *pOut) {
    LPDWORD pSrc, pDest;
    pIn->GetPointer((BYTE**)&pSrc);
    pOut->GetPointer((BYTE**)&pDest);
    const long act_size=pIn->GetActualDataLength();

    for(LONG y=0;y<m_Vih.bmiHeader.biHeight;y++) {
        for(LONG x=0;x<m_Vih.bmiHeader.biWidth;x++) {
            const DWORD r = ((*pSrc )& 0xff0000) >> 16;
            const DWORD g = ((*pSrc )& 0xff00) >> 8;
            const DWORD b = (*pSrc )& 0xff;
            *pDest=0;
            *pDest= (b << 16) | (r << 8) | (g);
            pSrc++;
            pDest++;
        }
    }
    pOut->SetActualDataLength(act_size);
    pOut->SetSyncPoint(TRUE);

    LONGLONG media_start, media_end;
    pIn->GetMediaTime(&media_start, &media_end);
    pOut->SetMediaTime(&media_start, &media_end);
    REFERENCE_TIME ref_start, ref_end;
    pIn->GetTime(&ref_start, &ref_end);
    pOut->SetTime(&ref_start, &ref_end);
    pOut->SetSyncPoint(TRUE);
    return S_OK;
}

GraphEdit でテスト

ビルドして regsvr でフィルタを登録する。

GraphEdit を起動して、My Transform Filter を Insert する。 [File]-[Render Media File] で c:\windows\clock.avi を選択する。Vistaの場合clock.avi がないので適当にWMVファイルなどを指定する。

自動的に My Transform Filter 経由でビデオレンダラへ接続されるはず。

https://github.com/mahorigahama/MyTransformFilter


Comments

comments powered by Disqus