Skip to main content.

Friday, March 21, 2008

DirectShow USBカメラの映像をAVIファイルへ出力

DirectShowでのUSBカメラの映像を(マイクが接続されている場合は音声も)AVIファイルへ出力するサンプルプログラムです。今回の開発環境はVisual C++ 2008 Express Editionで、OSはWindowsXP SP2です。

まずは、GraphEditで私のPCに手持ちのUSBカメラを接続して、今回のプログラムのグラフを書いてみると以下のようになります。左から、USBカメラ、マイク → ビデオコンプレッサ → AVI Mux → filewriterです。filewriterには、usbcam.aviというファイル名を設定しています。

映像のデータのみ圧縮しています。圧縮には、私の環境で標準で使えるようになっていたIndeoR video 5.10 Compression Filter を利用しています。このグラフではプレビューがありませんので、このまま実行すると画面上には映像は表示されません。映像は作成されたファイルで確認します。



ビデオキャプチャフィルタはGraphEditのメニュー → Insert Filters → DirectShow Filters → Video Capture Sources で追加出来ます。

オーディオキャプチャフィルタはGraphEditのメニュー → Insert Filters → DirectShow Filters → Audio Capture Sources で追加出来ます。

ビデオコンプレッサフィルタはGraphEditのメニュー → Insert Filters → DirectShow Filters → Video Compressors → IndeoR video 5.10 Compression Filter で追加出来ます。(※インストールされていればです。)

AVI MuxフィルタはGraphEditのメニュー → Insert Filters → DirectShow Filters → AVI Mux で追加出来ます。

File writerフィルタはGraphEditのメニュー → Insert Filters → DirectShow Filters → File writer で追加出来ます。

以下は、私のPCでのビデオコンプレッサフィルタを選択した画面です。


以上の機能でプレビューも出来るプログラムを作成します。単純なコンソールアプリケーションで、動作的にはプログラムを実行すると、USBカメラの映像とマイクが接続されている場合は音声をc:\usbcam.aviに保存します。メッセージボックスでプログラムが中断しますので、これをクリックするとプログラムが終了します。

プログラムの流れは以下のようになります。大きな流れとしては、以前書いたオーディオキャプチャのプログラムと同様に、上のグラフの機能をプログラムで実装しています。

COMの初期化

フィルタグラフマネージャの生成

ビデオ入力とオーディオ入力の追加

ビデオコンプレッサフィルタの追加

キャプチャグラフビルダの作成と設定

録画開始

メッセージボックスで終了待ち

終了処理

プログラムは以下になります。
// usbcam1.cpp : コンソール アプリケーションのエントリ ポイントを定義します。
//

#include "stdafx.h"

//DirectShowのインクルード
#include <dshow.h>

//
// DirectShowサンプルプログラム
// USBカメラの映像とマイクの音声(接続されている場合)をaviファイルへ保存する
// (出力ファイルは、以下の定義で固定→プログラムの130行目あたりに記述)
//

#define AVI_FILE_NAME L"C:\\usbcam.avi"

int _tmain(int argc, _TCHAR* argv[])
{
    // COMの初期化
    CoInitialize(NULL);

    // フィルタグラフ作成
    IGraphBuilder *pGraph = NULL;
    CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC,IID_IGraphBuilder, (void **)&pGraph);
    
    // キャプチャデバイスをグラフに追加する一連の処理
    ICreateDevEnum *pDevEnum = NULL;
    IEnumMoniker *pClassEnum = NULL;
    ULONG cFetched;
    IMoniker *pMoniker = NULL;
    IBaseFilter *pVideoInput = NULL;
    HRESULT hr;

    // ビデオ入力から
    // ビデオ入力を列挙して最初のデバイスをグラフに追加する
    CoCreateInstance(CLSID_SystemDeviceEnum, NULL, CLSCTX_INPROC,IID_ICreateDevEnum, (void **)&pDevEnum);
    hr = pDevEnum->CreateClassEnumerator(CLSID_VideoInputDeviceCategory, &pClassEnum, 0);
    if(hr == S_FALSE){
        printf("ビデオ入力が接続されていません。\n");
        pDevEnum->Release();
        return 0;
    }

    // 最初のモニカを取得して(これがUSBカメラと想定)フィルタオブジェクトにバインドする
    hr = pClassEnum->Next(1, &pMoniker, &cFetched);
    if(hr == S_OK){
        pMoniker->BindToObject(0, 0, IID_IBaseFilter, (void **)&pVideoInput);
        pMoniker->Release();
    }
    pClassEnum->Release();
    
    // ビデオ入力のフィルタをグラフに追加する
    pGraph->AddFilter(pVideoInput, L"Video Capture");

    // 次にオーディオ入力
    // オーディオ入力を列挙して最初のデバイスをグラフに追加する
    bool bAudio = true;
    IBaseFilter *pAudioInput = NULL;

    hr = pDevEnum->CreateClassEnumerator(CLSID_AudioInputDeviceCategory,&pClassEnum, 0);
    if(hr == S_FALSE){
        // 接続されたオーディオ入力デバイスが一つも無い場合
        printf("オーディオ入力デバイスは接続されていません。\n");
        bAudio = false;
    }else{
        // 最初のモニカを取得して(これがマイクと想定)フィルタオブジェクトにバインドする
        hr = pClassEnum->Next(1, &pMoniker, &cFetched);
        if(hr == S_OK){
            pMoniker->BindToObject(0, 0, IID_IBaseFilter, (void **)&pAudioInput);
            pMoniker->Release();

            pGraph->AddFilter(pAudioInput, L"Audio Capture");
        }
    }
    pClassEnum->Release();
    
    // キャプチャデバイスをグラフに追加する処理はここまで


    // ビデオ圧縮フィルタを追加する処理
    // Compressorを列挙するためのCreateDevEnumを生成
    VARIANT varName;
    IBaseFilter *pVideoComp = NULL;
    hr = pDevEnum->CreateClassEnumerator(CLSID_VideoCompressorCategory, &pClassEnum, 0);
    bool bFound = false;

    // Compressorを列挙して"IndeoR video 5.10 Compression Filter"を探す
    while(pClassEnum->Next(1, &pMoniker, &cFetched) == S_OK)
    {
        IPropertyBag *pProp=NULL;
        pMoniker->BindToStorage(0, 0, IID_IPropertyBag, (void **)&pProp);

        VariantInit(&varName);
        pProp->Read(L"FriendlyName", &varName, 0); 

        BSTR bstr = varName.bstrVal;

        //"IndeoR video 5.10 Compression Filter"が見つかった場合
        if(wcscmp(L"IndeoR video 5.10 Compression Filter", varName.bstrVal) == 0)
        {
            //グラフビルダーにCompressorを追加
            pMoniker->BindToObject(0, 0, IID_IBaseFilter, (void**)&pVideoComp);
            pGraph->AddFilter(pVideoComp, L"Compress Filtter");

            bFound = true;
        }

        VariantClear(&varName);
        pMoniker->Release();
        if(pProp){
            pProp->Release();
        }

        if(bFound){
            break;
        }
    }
    pClassEnum->Release();
    pDevEnum->Release();

    // ビデオ圧縮フィルタを追加する処理はここまで

    // キャプチャグラフビルダの作成
    ICaptureGraphBuilder2 *pBuilder = NULL;
    CoCreateInstance(CLSID_CaptureGraphBuilder2, NULL, CLSCTX_INPROC,IID_ICaptureGraphBuilder2, (void **)&pBuilder);
    pBuilder->SetFiltergraph(pGraph);
  
    // ファイルライタフィルタの設定
    IBaseFilter *pMux = NULL;
    
    // aviファイルへ出力
    pBuilder->SetOutputFileName(&MEDIASUBTYPE_Avi, AVI_FILE_NAME, &pMux, NULL);

    pBuilder->RenderStream(&PIN_CATEGORY_CAPTURE, &MEDIATYPE_Video, pVideoInput, pVideoComp, pMux);
    pBuilder->RenderStream(&PIN_CATEGORY_CAPTURE, &MEDIATYPE_Audio, pAudioInput, NULL, pMux);
 
    // ディスプレイへ(必要な場合はスピーカにも)
    pBuilder->RenderStream( &PIN_CATEGORY_PREVIEW, &MEDIATYPE_Video, pVideoInput, NULL, NULL );
    //pBuilder->RenderStream( &PIN_CATEGORY_PREVIEW, &MEDIATYPE_Audio, pAudioInput, NULL, NULL );

    //キャプチャ開始から終了まで
    IMediaControl *pMediaControl;
    pGraph->QueryInterface(IID_IMediaControl, (void **)&pMediaControl);
    pMediaControl->Run();
    
    MessageBox(NULL, (LPCSTR)"Click to stop.", (LPCSTR)"DirectShow", MB_OK);
    pMediaControl->Stop();
    
    //終了処理
    pVideoInput->Release();
    if(pAudioInput) pAudioInput->Release();

    if(pVideoComp) pVideoComp->Release();
    
    pMux->Release();
    pMediaControl->Release();
    pBuilder->Release();
    pGraph->Release();
    
    CoUninitialize();
    
    return 0;
}


プログラミングのポイントとしては、キャプチャグラフビルダを利用しているところでしょうか。キャプチャグラフビルダはキャプチャグラフの作成を簡単にするために提供されているDirectShowのコンポーネントです。このキャプチャグラフビルダを使用すると、キャプチャアプリのプログラミングが簡単になるということです。詳細は参考リンク等を参照していただければと思います。

以前書いたオーディオキャプチャのプログラムではキャプチャグラフビルダを使用していませんので、今回のプログラムに比べてフィルタの接続部分等で細かいプログラミングになっています。

ソースファイル一式
プロジェクトファイル一式(Visual C++ 2008 Express Editionで作成)


参考リンク
キャプチャ アプリケーションの書き方(※Microsoft DirectX 8.0のドキュメントです)
AVI ファイルの再圧縮(Microsoft DirectX 9.0)

Windowsプログラミング関連記事