Date

DirectShow を使って DV カメラ の映像をリアルタイムに取り込んで画面に表示するアプリケーションを作成する。

DirectShow では DV カメラも一つのフィルタとして表される。MSDV ドライバといわれる DV カメラ用のドライバがあり、それがフィルタとして表される。

※ 標準解像度の dvsd (NTSC もしくは PAL)のみサポートされる。その他のフォーマットは標準で処理を行うフィルタが用意されていないため扱うことができない。

画面に表示したりファイルへキャプチャしたりするにはフィルタグラフに MSDV ドライバのフィルタを追加する。

このフィルタはシンクフィルタ、ソースフィルタ両方の役割がある。画面に表示する場合はソースフィルタとして使う。出力ピンから映像と音声を取り出しレンダリングする。映像は Video Renderer, 音声は Default DirectSound Device へ接続する。

DVCamViewer としてプロジェクトを作成する。 各種インターフェイスを記憶しておく変数を宣言する。解放する関数 ReleaseInterfaces を実装しておく。今回はCoCreateInstance関数の呼び出しを簡略化するために CREATE_COM_INST マクロを定義した。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include "stdafx.h"

#define SAFE_RELEASE(p) { if((p)!=NULL) { (p)->Release(); (p)=NULL; }}
#define FAILED_RELEASE(r) if(FAILED(r)) { printf("%s:(%d) Failed. HRESULT=%x\n",__FILE__,__LINE__,hr); ReleaseInterfaces(); return 0; }
#define CREATE_COM_INST(clsid,iid,obj) CoCreateInstance(clsid, NULL, CLSCTX_INPROC_SERVER, iid,reinterpret_cast<void **>(obj))
ICaptureGraphBuilder2 *pCapGrph = NULL;
IGraphBuilder *        pGrph = NULL;
IMediaControl *        pCtrl = NULL;
IBaseFilter *          pDVCam= NULL; // DVcam を表すフィルタ
IBaseFilter *           pSplitter  =NULL;
IBaseFilter *           pRenderer  =NULL;
IBaseFilter *           pDSRenderer=NULL;
IBaseFilter *           pDecoder   =NULL;
void ReleaseInterfaces() {
    SAFE_RELEASE(pCapGrph);
    SAFE_RELEASE(pGrph);
    SAFE_RELEASE(pCtrl);
    SAFE_RELEASE(pDVCam);
    SAFE_RELEASE(pSplitter);
    SAFE_RELEASE(pRenderer);
    SAFE_RELEASE(pDSRenderer);
    CoUninitialize();
}

DV カメラのフィルタはCoCreateInstanceで直接作成することはできない。 PC に接続されている DV カメラを列挙し、そこからフィルタを作成する。

クラス IDCLSID_SystemDeviceEnum, インターフェイス ID IID_ICreateDevEnumCoCreateInstance関数を呼び出する。次にICreateDevEnum::CreateClassEnumeratorを呼び出す。これでビデオキャプチャデバイスが列挙できる。

IEnumMoniker::Nextで列挙する。ビデオキャプチャデバイスごとにIMonikerが得られるので、DV カメラかどうか判定しIMoniker::BindToObjectIBaseFilterを持つオブジェクトを取得する。DV カメラの判定にはImoniker::BindToStorageで取得したIPropertyBagで Description プロパティを取得できるかどうかで判定する。より厳密に判定するには DV フォーマットを出力するピンがあるかどうかで判定を行ったら良いだろう。

 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
35
36
37
38
39
40
41
HRESULT CreateDvCam() {
    ICreateDevEnum *pDevEnum = NULL;
    IEnumMoniker *pEnum = NULL;
    // システム デバイス列挙子を作成する。
    HRESULT hr=CREATE_COM_INST(CLSID_SystemDeviceEnum,IID_ICreateDevEnum,&pDevEnum);
    if(FAILED(hr)) {
        return hr;
    }
    // ビデオ キャプチャ カテゴリの列挙子を作成する。
    hr = pDevEnum->CreateClassEnumerator(CLSID_VideoInputDeviceCategory,&pEnum, 0);
    if(hr!=S_OK) {
        return hr;
    }
    IMoniker *pMoniker = NULL;
    while (pEnum->Next(1, &pMoniker, NULL) == S_OK && pDVCam==NULL)
    {
        IPropertyBag *pPropBag=NULL;
        hr = pMoniker->BindToStorage(0, 0, IID_IPropertyBag, reinterpret_cast<void**>(&pPropBag));
        if (SUCCEEDED(hr)) {
            VARIANT varName;
            VariantInit(&varName);
            hr = pPropBag->Read(TEXT("FriendlyName"), &varName, 0);
            if (SUCCEEDED(hr)) {
                wprintf(TEXT("%s\n"),varName.bstrVal);
            }
            // Description を取得できるのは、DV と D-VHS/MPEGのカムコーダデバイスのみ。
            VariantClear(&varName);
            VariantInit(&varName);
            hr = pPropBag->Read(TEXT("Description"), &varName, 0);
            if (SUCCEEDED(hr)) {
                wprintf(TEXT("%s\n"),varName.bstrVal);
                // IBaseFilter を取得する
                hr = pMoniker->BindToObject(0, 0, IID_IBaseFilter, reinterpret_cast<void**>(&pDVCam));
            }
            VariantClear(&varName);
        }
        SAFE_RELEASE(pPropBag);
        SAFE_RELEASE(pMoniker);
    }
    return pDVCam==NULL ? E_FAIL : S_OK;
}

IGraphBuilderではなく、キャプチャグラフ向けの便利なメソッドが用意されているICaptureGraphBuilder2でフィルタグラフを構築する。

 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
int _tmain(int argc, _TCHAR* argv[]) {
    HRESULT hr=NOERROR;
    IBaseFilter *pBaseF=NULL;
    hr=CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
    //  graph
    FAILED_RELEASE(CREATE_COM_INST(CLSID_CaptureGraphBuilder2, IID_ICaptureGraphBuilder2,&pCapGrph));
    FAILED_RELEASE(CREATE_COM_INST(CLSID_FilterGraph, IID_IGraphBuilder,&pGrph));
    FAILED_RELEASE(pCapGrph->SetFiltergraph(pGrph));
    FAILED_RELEASE(pGrph->QueryInterface(IID_IMediaControl, (void**)&pCtrl));
    // add filter : DV cam
    FAILED_RELEASE(CreateDvCam());
    FAILED_RELEASE(pGrph->AddFilter(pDVCam,TEXT("DVCam")));
    // add filter : DV splitter
    FAILED_RELEASE(CREATE_COM_INST(CLSID_DVSplitter, IID_IBaseFilter,&pSplitter));
    FAILED_RELEASE(pGrph->AddFilter(pSplitter,TEXT("Splitter")));
    // add filter : DV decoder
    FAILED_RELEASE(CREATE_COM_INST(CLSID_DVVideoCodec, IID_IBaseFilter, &pDecoder));
    FAILED_RELEASE(pGrph->AddFilter(pDecoder,TEXT("DV Decoder")));
    // set decoder resolution
    IIPDVDec *pDD=NULL;
    FAILED_RELEASE(pDecoder->QueryInterface(IID_IIPDVDec,(void**)&pDD));
    pDD->put_IPDisplay(DVDECODERRESOLUTION_360x240);
    SAFE_RELEASE(pDD);
    // add filter : video renderer
    FAILED_RELEASE(CREATE_COM_INST(CLSID_VideoRenderer, IID_IBaseFilter, &pRenderer));
    FAILED_RELEASE(pGrph->AddFilter(pRenderer,TEXT("Video Renderer")));
    // add filter : directsound renderer
    FAILED_RELEASE(CREATE_COM_INST(CLSID_DSoundRender, IID_IBaseFilter, &pDSRenderer));
    FAILED_RELEASE(pGrph->AddFilter(pDSRenderer,TEXT("DirectSound Renderer")));

ICaptureGraphBuilder2::RenderStreamを使ってフィルタを接続する。このメソッドは、接続開始フィルタ、中間フィルタ、終端フィルタの 3 つの引数を指定し、インテリジェント接続で自動的にフィルタを接続していくれる便利なメソッドである。ピンカテゴリや、メジャーメディアタイプを指定できるので複数の出力ピンがあるフィルタでも選択が可能だ。

1
2
3
FAILED_RELEASE(pCapGrph->RenderStream(&PIN_CATEGORY_CAPTURE ,&MEDIATYPE_Interleaved,pDVCam,NULL,pSplitter));
FAILED_RELEASE(pCapGrph->RenderStream(NULL,&MEDIATYPE_Video,pSplitter,pDecoder,pRenderer));
FAILED_RELEASE(pCapGrph->RenderStream(NULL,&MEDIATYPE_Audio,pSplitter,NULL,pDSRenderer));

IMediaControl::Runでフィルタグラフを実行する。_getch関数を呼んで、任意のキーが押されるとフィルタグラフを停止するようにする。

1
2
3
4
5
6
    pCtrl->Run();
    _getch();
    pCtrl->StopWhenReady();
    ReleaseInterfaces();
    return 0;
}

実際に動かす前に、DV カメラをカメラモードにして電源を入れて IEEE1394 で接続しておく。OS に認識された状態になったら作成したアプリケーションを実行してください。

https://github.com/mahorigahama/DVCamViewer


Comments

comments powered by Disqus