Date

自分で作成したウィンドウに対して映像を表示するビデオレンダラーフィルタを作成する。

フィルタ情報の定義

今回はCMyRendererというクラス名でレンダラークラスを定義する。CFactoryTemplate g_Templatesを書き換える。

1
2
3
4
CFactoryTemplate g_Templates [] = {
     { TEMPLATE_NAME , &CLSID_MyRenderer, CMyRenderer::CreateInstance, NULL, &afFilterInfo }
};
int g_cTemplates = sizeof(g_Templates) / sizeof(g_Templates[0]);

MyRendererFilter.h を新規作成する。ここにフィルタの情報やクラスを定義していく。(stdafx.h の末尾に MyRendererFilter.h を追加インクルードすること。) DllSetup.cpp で使われていたCFactoryTemplate g_Templatesを初期化するTEMPLATE_NAME, フィルタ名, 出力ピンの名前をdefineする。

1
2
3
#define TEMPLATE_NAME    (L"My Renderer Filter")
#define FILTER_NAME    (TEMPLATE_NAME)
#define OUTPUT_PIN_NAME (L"Input")

フィルタのクラス ID を決める。Playform SDK に付属している guidgen.exe を使って GUID を生成すること。次のような書式でクリップボードにコピーできる。

1
2
3
// {548DA892-F22C-408d-B759-CEF9487E4D81}
DEFINE_GUID(CLSID_MyRenderer,
0x548da892, 0xf22c, 0x408d, 0xb7, 0x59, 0xce, 0xf9, 0x48, 0x7e, 0x4d, 0x81);

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を定義する。今回は、入力ピンであるためAMOVIESETUP_PIN::bOutputFALSEを設定している。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
const AMOVIESETUP_PIN sudPins =
{
    INPUT_PIN_NAME,
    FALSE,                // Is it rendered
    FALSE,                // Is it an output (入力ピンのため FALSE を指定)
    FALSE,                // Allowed none
    FALSE,                // Allowed many
    &CLSID_NULL,
    NULL,
    1,                    // Number of types
    &sudPinTypes          // Pin information
};

AMOVIESETUP_FILTER 構造体 afFilterInfo を定義する。

1
2
3
4
5
6
7
8
const AMOVIESETUP_FILTER afFilterInfo=
{
    &CLSID_MyRenderer,      // フィルタのCLSID
    FILTER_NAME,            // フィルタ名
    MERIT_DO_NOT_USE,       // メリット値
    1,                      // ピン数
    &sudPins                // ピン情報
};

レンダラーフィルタクラスCMyRenderer を定義する。 CBaseVideoRenderer を継承する。画面表示用のウィンドウハンドルを保持するために m_Hwnd がある。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
class CMyRenderer : public CBaseVideoRenderer {
public:
    CMyRenderer(LPUNKNOWN pUnk,HRESULT *phr);
    virtual                        ~CMyRenderer();
    static CUnknown * WINAPI    CreateInstance(LPUNKNOWN pUnk, HRESULT *phr);
    HRESULT CheckMediaType(const CMediaType *pMediaType);
    HRESULT DoRenderSample(IMediaSample *pMediaSample);
    HRESULT SetMediaType(const CMediaType *pmt);
    STDMETHODIMP JoinFilterGraph(IFilterGraph * pGraph, LPCWSTR pName);
protected:
private:
    VIDEOINFOHEADER        m_Vih;
    HWND                m_Hwnd;
};

レンダラーフィルタのクラスを実装

MyRendererFilter.cpp を新規作成する。 ウィンドウを生成するので、そのためのプロシージャを先に実装する。デフォルトの処理で構わないので DefWindowProc に任せている。

1
2
3
4
5
#include"stdafx.h"
extern "C" LRESULT CALLBACK MyWndProc
(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) {
    return DefWindowProc(hWnd, message, wParam, lParam);
}

CMyRenderer クラスのコンストラクタとデストラクタ、 CreasteInstance を実装する。特に処理はない。ソースフィルタと違ってピンを生成しなくても BaseClasses クラスライブラリにより自動的に入力ピンが 1 つあるフィルタとして構成される。

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

CheckMediaType を実装する。ソースフィルタと同様、32bit RGB 以外受け付けないようにしている。もし音声データを入力しようとした場合は接続できない。また 16bit RGB などの場合は Color Space Converter フィルタを通しておけば接続可能だ。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
HRESULT CMyRenderer::CheckMediaType(const CMediaType *pMediaType) {
    if(!IsEqualGUID(*pMediaType->FormatType(), FORMAT_VideoInfo)) {
        return S_FALSE;
    }
    if(IsEqualGUID(*pMediaType->Type(), MEDIATYPE_Video)) {
        if(IsEqualGUID(*pMediaType->Subtype(), MEDIASUBTYPE_RGB32)) {
            return S_OK;
        }
    }
    return S_FALSE;
}

CBaseRenderer::SetMediaTypeを実装する。ピンのメディアタイプを設定するときに呼び出される。表示用ウィンドウを作成し、表示している。

 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 CMyRenderer::SetMediaType(const CMediaType *pmt) {
    // データフォーマットを記憶しておく
    CopyMemory(&m_Vih,(const VIDEOINFOHEADER *)pmt->pbFormat,sizeof(VIDEOINFOHEADER));
    WNDCLASSEX wcex={sizeof(WNDCLASSEX)};
    //既にウィンドウが開いていたら、閉じる
    if(m_Hwnd!=NULL) {
        DestroyWindow(m_Hwnd);
    }
    // 表示ウィンドウの作成
    wcex.style            = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS;
    wcex.lpfnWndProc    = (WNDPROC)MyWndProc;
    wcex.cbClsExtra        = 0;
    wcex.cbWndExtra        = 0;
    wcex.hInstance        = GetModuleHandle(NULL);
    wcex.hIcon            = LoadIcon(NULL,IDI_APPLICATION);
    wcex.hCursor        = LoadCursor(NULL,IDC_ARROW);
    wcex.hbrBackground    = (HBRUSH)CreateSolidBrush(0);
    wcex.lpszClassName    = TEXT("myRendererClass");
    ATOM a=RegisterClassEx(&wcex);
    int height=m_Vih.bmiHeader.biHeight;
    if (height < 0) height = -height;
    m_Hwnd=CreateWindow(TEXT("myRendererClass"),TEXT("My Renderer Window"),
                WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT
                ,m_Vih.bmiHeader.biWidth, height,
                GetDesktopWindow(), NULL, GetModuleHandle(NULL), NULL );
    ShowWindow(m_Hwnd, SW_NORMAL);
    UpdateWindow(m_Hwnd);
    return S_OK;
}

CBaseFilter::JoinFilterGraph を実装する。もしフィルタグラフから切り離されたときに表示用ウィンドウを閉じるようにしている。

1
2
3
4
5
6
HRESULT CMyRenderer::JoinFilterGraph(IFilterGraph *pGraph,LPCWSTR pName) {
    if(pGraph==NULL) {
        DestroyWindow(m_Hwnd);
    }
    return CBaseVideoRenderer::JoinFilterGraph(pGraph,pName);
}

DoRenderSampleを実装する。1フレーム届くたびにこのメソッドが呼ばれるので、ウィンドウへ描画している。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
HRESULT CMyRenderer::DoRenderSample(IMediaSample *pMediaSample) {
    HRESULT hr=S_OK;
    LPBYTE pbSrc=NULL;
    const long lActualSize=pMediaSample->GetActualDataLength();
    pMediaSample->GetPointer(&pbSrc);
    if(IsWindow(m_Hwnd)==TRUE) {
        BITMAPINFO bi={m_Vih.bmiHeader};
        const HDC hdc=GetDC(m_Hwnd);
        RECT rect;
        GetClientRect(m_Hwnd,&rect);
        int height=m_Vih.bmiHeader.biHeight;
        if (height < 0) height = -height;
        StretchDIBits(hdc,0,0,rect.right,rect.bottom
            ,0,0,m_Vih.bmiHeader.biWidth , height
            ,pbSrc,&bi,DIB_RGB_COLORS, SRCCOPY);
        ReleaseDC(m_Hwnd,hdc);
    }
    return hr;
}

GraphEdit でテスト

  1. ビルドして regsvr32 でフィルタを登録する。
  2. GraphEdit を起動して、My Renderer Filter を Insert する。
  3. [File]-[Render Media File] で c:\windows\clock.avi を選択する。

この手順に従って操作すると、自動的に My Renderer Filter に接続されるはず。

フィルタグラフが実行されると、ウィンドウが現れ、映像が表示される。

※ 再描画処理を実装していないので、ウィンドウのサイズを変えたり隠したりすると映像が消える。

https://github.com/mahorigahama/MyRendererFilter

普通、ウインドウを持ったレンダラーフィルタを作る場合、IVideoWindow インターフェイスを実装する。また、ウインドウの生成・破棄は再生の開始・終了で行うと良い。ここではレンダラーフィルタの作り方の基本を説明するため簡略化した。


Comments

comments powered by Disqus