Date

プッシュモデルのソースフィルタを作成する。はじめに、ピンを持たず、何もしない最低限のソースフィルタを作成する。次に出力ピンを 1 つ持ったフィルタへと拡張する。

何もしないフィルタを実装する

フィルタ情報の定義

フィルタ作成のためのプロジェクトの設定は終わってるものとする。

TEMPLATE_NAME を次のように書き換える。

1
#define TEMPLATE_NAME (L"My Source Filter")

フィルタのクラス ID を次のように書き換える。なお GUID は各自で生成すること。

1
2
3
// {78A3788B-F360-45f9-8E1D-443D3C63BD0B}
DEFINE_GUID(CLSID_MySource,  // ← クラス ID は CLSID_XXXXX の書式が使われる。
0x78a3788b, 0xf360, 0x45f9, 0x8e, 0x1d, 0x44, 0x3d, 0x3c, 0x63, 0xbd, 0xb);

AMOVIESETUP_FILTER 構造体 afFilterInfo を次のように書き換える。

1
2
3
4
5
6
7
8
9
    // フィルタ情報
    const AMOVIESETUP_FILTER afFilterInfo=
    {
        &CLSID_MySource,        // フィルタのCLSID
        FILTER_NAME,            // フィルタ名 ( FILTER_NAME で呼び出させるようにしている )
        MERIT_DO_NOT_USE,       // メリット値
        0,                      // ピン数
        NULL                    // ピン情報
    };

フィルタクラスの定義

ソースフィルタのクラス CMySourceを定義する。CSourceクラスを派生しているが、これは BaseClasses の中にある。独自の処理を加えたいメソッドを CMySourceFilterクラスに実装していくことによりソースフィルタとして機能するようになる。何もしないフィルタなので、必要最低限のコードのみ記述している。

まず MySourceFilter.h にフィルタクラスを宣言しておく。

1
2
3
4
5
6
7
    // ソースフィルタクラス
    class CMySource : CSource{
    public:
        CMySource(LPUNKNOWN pUnk,HRESULT *phr);
        virtual ~CMySource();
        static CUnknown * WINAPI    CreateInstance(LPUNKNOWN pUnk, HRESULT *phr);
    };

次に、MySourceFilter.cpp を新規作成し、CMySourceクラスを実装する。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
#include"stdafx.h"
CMySource::CMySource(IUnknown *pUnk, HRESULT *phr) : CSource(FILTER_NAME, pUnk, CLSID_MySource) {
    OutputDebugString(TEXT("CMySource::CMySource\n"));
}
CMySource::~CMySource(){
    OutputDebugString(TEXT("CMySource::~CMySource\n"));
}
CUnknown * WINAPI CMySource::CreateInstance(IUnknown *pUnk, HRESULT *phr) {
    OutputDebugString(TEXT("CMySource::CreateInstance\n"));
    CMySource *pNewFilter = new CMySource(pUnk, phr );
    if (pNewFilter == NULL) {
        phr = E_OUTOFMEMORY;
    }
    return dynamic_cast<CUnknown *>(pNewFilter);
}

コンストラクタ・デストラクタと、CreasteInstanceのみ実装した。

CreateInstanceは、CoCreateInstance関数を呼び出したときに呼ばれる。

実際に動かす

OS へ登録する

コマンドプロンプトを開き、 regsvr32.exe を使って登録を行う。

1
regsvr32 MySourceFilter.DLL

DLL ファイルがレジストリに登録される。以降 CoCreateInstance 関数で クラス ID と インターフェイス ID を指定して呼び出すと、そのインターフェイスが取得できるようになる。

1
regsvr32 /u MySourceFilter.DLL

とすると、登録が解除される。

GraphEdit を使ってテストする

フィルタは DLL なので単独で実行できない。別途 DLL を使うプログラムを書いてもいいが、せっかく GraphEdit という便利なものがあるので、それを使うことにする。

  1. プロジェクトのプロパティを開いて [全ての構成] を表示させる。
  2. [構成プロパティ]-[全般]-[デバッグ]の[コマンド] に %programfiles%\Microsoft SDKs\Windows\v6.0\Bin\graphedt.exe と書く。

フィルタのプロジェクトをビルドし、実行する。GraphEdit が起動するので、今ビルドしたフィルタを置いてみる。

  1. メニュー[Graph]-[Insert Filters]を選択する。
  2. 一覧の中から[DirectShow Filters]を展開し、[My Source Filter]を探す。
  3. [Insert Filter]ボタンを押下すると、入力ピンも出力ピンもないフィルタが置かれる。

出力ピンを持たせる

何もしないプッシュモデルのソースフィルタを元に、出力ピンを1つ持たせる。

出力ピンからはGDIよって現在のストリーム時間が描かれた画像データが生成される。Video Rendererフィルタに接続して画面に表示する。

出力ピンの定義

MySourceFilter.h に出力ピンのメディアタイプを AMOVIESETUP_MEDIATYPE 構造体に定義する。ここでは 32bit RGB を定義している。

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

AMOVIESETUP_PIN 構造体にピンを定義する。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
// 入力用、出力用ピンの情報
const AMOVIESETUP_PIN sudPins =
{
    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_MySource,
    FILTER_NAME,
    MERIT_DO_NOT_USE,
    1,
    &sudPins
};

出力ピンクラスを実装する

出力ピンのクラス CPushPinを定義する。CSourceStreamから派生する。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
class CPushPin : CSourceStream {
 const REFERENCE_TIME m_rtFrameLength; //1フレームあたりの時間
 HBITMAP m_Bitmap;
 LPDWORD m_BmpData;
 HDC m_Hdc;
public:
 CPushPin(HRESULT *phr, CSource *pFilter);
 ~CPushPin();
 STDMETHODIMP Notify(IBaseFilter *pSelf, Quality q);
 // CSourceStreamの実装
 HRESULT GetMediaType(CMediaType *pMediaType);
 HRESULT CheckMediaType(const CMediaType *pMediaType);
 HRESULT DecideBufferSize(IMemAllocator *pAlloc, ALLOCATOR_PROPERTIES *pRequest);
 HRESULT FillBuffer(IMediaSample *pSample);
 // CBasePin
 HRESULT CompleteConnect(IPin *pReceivePin);
 STDMETHODIMP Disconnect();
};

MySourceFilter.cpp の CMySourceクラスのコンストラクタでCPushPinnewする。newすると、自動的に出力ピンが現れるようになる。deleteの必要はない。BaseClasses によって自動的にdeleteされる。

1
2
3
4
5
6
7
8
CMySource::CMySource(IUnknown *pUnk, HRESULT *phr) :
    CSource(FILTER_NAME, pUnk, CLSID_MySource)
{
    CPushPin *pPin = new CPushPin(phr, this);
    if(pPin==NULL) {
        *phr=E_OUTOFMEMORY;
    }
}

出力ピンクラスを実装するために PushPin.cpp を新規作成してプロジェクトへ追加する。

CPushPinクラスのコンストラクタを作成する。CSourceStreamから派生しているので、そのコンストラクタも呼び出すようにする。

メンバ変数の初期化も行っている。1フレームの長さを CPushPin::m_rtFrameLength に初期化している。66666は約1/15秒である。 DirectShow では 100 nanosec 単位で時刻を扱う。

1
2
3
4
5
6
7
#include"stdafx.h"
CPushPin::CPushPin(HRESULT *phr, CSource *pFilter) :
CSourceStream(NAME("CPushPin"), phr, pFilter, OUTPUT_PIN_NAME)
    ,m_rtFrameLength(666666)
    ,m_BmpData(NULL), m_Hdc(NULL), m_Bitmap(NULL)
{
}

デストラクタでは何もしないので省略。

CBasePin::Notifyメソッドをオーバーライドし常にE_NOTIMPLを返す。このメソッドは品質をコントロールする際に使われる。 CPU リソースが不足したり、ネットワークの速度が落ちて、レンダリングするフレームをスキップするときに使われる。ここでは何もしていない。

1
2
3
STDMETHODIMP CPushPin::Notify(IBaseFilter *pSelf, Quality q) {
    return E_NOTIMPL;
}

GetMediaTypeメソッドを実装する。出力ピンが出力するメディアタイプを CMediaTypeに設定する。240×180 32bit RGB に設定する。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
HRESULT CPushPin::GetMediaType(CMediaType *pMediaType) {
    VIDEOINFO *pvi=(VIDEOINFO *)pMediaType->AllocFormatBuffer(sizeof(VIDEOINFO));
    ZeroMemory(pvi, sizeof(VIDEOINFO));
    pvi->AvgTimePerFrame=m_rtFrameLength;
    BITMAPINFOHEADER *pBmi=&(pvi->bmiHeader);
    pBmi->biSize=sizeof(BITMAPINFOHEADER);
    pBmi->biWidth=240;
    pBmi->biHeight=180;
    pBmi->biPlanes=1;
    pBmi->biBitCount=32;
    pBmi->biCompression=BI_RGB;
    pvi->bmiHeader.biSizeImage=DIBSIZE(pvi->bmiHeader);
    pMediaType->SetType(&MEDIATYPE_Video);
    pMediaType->SetFormatType(&FORMAT_VideoInfo);
    const GUID subtype=GetBitmapSubtype(&pvi->bmiHeader);
    pMediaType->SetSubtype(&subtype);
    pMediaType->SetSampleSize(DIBSIZE(*pBmi));
    pMediaType->SetTemporalCompression(FALSE);
    return S_OK;
}

CheckMediaTypeを実装する。これは、引数のCMediaTypeにあるメディアタイプを出力できるかどうか DirectShow がフィルタに問い合わせるときに呼ばれる。今回は 32bit RGB 以外は出力できないので、そのように実装している。

1
2
3
4
5
6
7
8
9
HRESULT CPushPin::CheckMediaType(const CMediaType *pMediaType) {
    CheckPointer(pMediaType,E_POINTER);
    CMediaType mt;
    GetMediaType(&mt);
    if(mt==*pMediaType) {
        return S_OK;
    }
    return E_FAIL;
}

CompleteConnectDisconnectを実装する。それぞれ、ピンの接続を完了したとき、接続を解除したときに呼ばれるメソッドである。ここでは接続時にDIBやデバイスコンテキストなどのGDIリソースを作成する。接続解除時にそれらを解放する。

 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
30
// ピンの接続を完了する
HRESULT CPushPin::CompleteConnect(IPin *pReceivePin) {
    VIDEOINFO *pvi=(VIDEOINFO *)m_mt.Format();
    BITMAPINFOHEADER *pBmi=&(pvi->bmiHeader);
    m_BmpData = new DWORD[pBmi->biWidth * pBmi->biHeight];
    memset(m_BmpData,0,m_mt.GetSampleSize());
    HDC dwhdc = GetDC(GetDesktopWindow());
    m_Bitmap=
        CreateDIBitmap(dwhdc, pBmi, CBM_INIT, m_BmpData, (BITMAPINFO*)pBmi, DIB_RGB_COLORS);
    m_Hdc=CreateCompatibleDC(dwhdc);
    SelectObject(m_Hdc, m_Bitmap);
    ReleaseDC(GetDesktopWindow(), dwhdc);
    return __super::CompleteConnect(pReceivePin);
}
// ピンの切断を解除する
HRESULT CPushPin::Disconnect() {
    if(m_Bitmap) {
        DeleteObject(m_Bitmap);
        m_Bitmap=NULL;
    }
    if(m_Hdc) {
        DeleteDC(m_Hdc);
        m_Hdc=NULL;
    }
    if(m_BmpData) {
        delete m_BmpData;
        m_BmpData=NULL;
    }
    return __super::Disconnect();
}

CBaseOutputPin::DecideBufferSizeを実装する。ここでは、映像データをダウンストリーム(下流)フィルタへ送信するためのバッファを確保する。下流フィルタの入力ピンから、バッファの量を指定される場合があるが、今回はそれを無視し、自前で設定している。IMemAllocator::SetProperties でバッファ数・サイズの設定を行う。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
HRESULT CPushPin::DecideBufferSize(IMemAllocator *pAlloc,ALLOCATOR_PROPERTIES *pRequest) {
    HRESULT hr=NOERROR;
    VIDEOINFO *pvi=(VIDEOINFO*)m_mt.Format();
    ASSERT(pvi != NULL);
    pRequest->cBuffers=1;
    // バッファサイズはビットマップ1枚分以上である必要がある
    if (pvi->bmiHeader.biSizeImage>(DWORD)pRequest->cbBuffer) {
        pRequest->cbBuffer=pvi->bmiHeader.biSizeImage;
    }
    // アロケータプロパティを設定しなおす
    ALLOCATOR_PROPERTIES Actual;
    hr=pAlloc->SetProperties(pRequest, &Actual);
    if (FAILED(hr)) {
        return hr;
    }
    if (Actual.cbBuffer<pRequest->cbBuffer) {
        return E_FAIL;
    }
    return S_OK;
}

最後にFillBufferメソッドを実装する。ここで実際に映像データを書き込むことになる。IMediaSampleインターフェイスを通してバッファのポインタを取得したりバッファサイズを取得できる。

フィルタグラフは動的再接続といって、実行中にピンが再接続される場合がある。このとき、メディアタイプが変わる場合があるのででチェックを行っている。 また作成した映像データにタイムスタンプを付与する。これは IMediaSample::SetTimeを使う。タイムスタンプはフィルタクラス(CMySource)のCBaseFilter::StreamTimeを使ってストリーム時間を取得している。

SetSyncPointは時間圧縮を使っているフィルタで、出力するデータがキーフレームのときに TRUEを設定する。ΔフレームはFALSEである。このフィルタでは全てキーフレームなので TRUEを設定している。

 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
30
31
32
33
34
HRESULT CPushPin::FillBuffer(IMediaSample *pSample) {
    HRESULT hr=E_FAIL;
    CheckPointer(pSample,E_POINTER);
    // ダウンストリームフィルタが
    // フォーマットを動的に変えていないかチェック
    ASSERT(m_mt.formattype==FORMAT_VideoInfo);
    ASSERT(m_mt.cbFormat>=sizeof(VIDEOINFOHEADER));
    // フレームに書き込み
    LPBYTE pSampleData=NULL;
    const long size=pSample->GetSize();
    pSample->GetPointer(&pSampleData);
    CRefTime ref;
    m_pFilter->StreamTime(ref);
    TCHAR buffer[200];
    _snwprintf_s(buffer, _countof(buffer), _TRUNCATE, TEXT("%09d"), ref.Millisecs());
    TextOut(m_Hdc, 0, 0, buffer, lstrlen(buffer));
    VIDEOINFO *pvi=(VIDEOINFO *)m_mt.Format();
    GetDIBits(m_Hdc, m_Bitmap, 0, 180,
        pSampleData, (BITMAPINFO*)&pvi->bmiHeader, DIB_RGB_COLORS);
    const REFERENCE_TIME delta=m_rtFrameLength;
    REFERENCE_TIME start_time=ref;
    FILTER_STATE state;
    m_pFilter->GetState(0, &state);
    if(state==State_Paused) {
        start_time = 0;
    }
    REFERENCE_TIME end_time=(start_time+delta);
    pSample->SetTime(&start_time, &end_time);
    pSample->SetActualDataLength(size);
    pSample->SetSyncPoint(TRUE);
    // 全速力でプッシュしない場合はコメントを外す
    // Sleep(CRefTime(m_rtFrameLength).Millisecs());
    return S_OK;
}

GraphEdit で動作確認

早速 GraphEdit でテストしてみる。ビルドして regsvr32 でフィルタを登録する。My Source Filter をフィルタグラフへ追加すると、今度はフィルタに出力ピンが現れる。 これを、Video Renderer の入力ピンに接続する。

無事に接続された。再生ボタンをクリックするか Enter キーを押してフィルタグラフを実行すると表示される。

https://github.com/mahorigahama/mysourcefilter


Comments

comments powered by Disqus