DirectShowを使用してカメラ名と解像度をリストボックスに表示しカメラと解像度の選択結果を表示する

icon 項目のみ表示/展開表示の切り替え

概要

DirectShowを用い、接続されているカメラの名称を取得し上段のリストボックスに表示します。複数ある場合はリストボックスで選択することができます。下段のリストボックスには選択されているカメラの解像度の一覧が表示されます。OKボタンをクリックするとリストボックスで選択されたカメラ番号と解像度が表示されます。
下記の図は、Windows 7でELECOM UCAM-C0220FとBUFFALO BSW32KM03が接続されている環境での実行結果です。
Windows XPで試したところ、USBビデオデバイスと表示されました。

COMは文字コードをUNICODEで表しますので、コンパイル時の文字セットはUNICODEとしてください。マルチバイトでコンパイルはできますが、実行するとデバイス名の1文字目しか表示されません。これは、UNICODEは2byteで1文字を表しており、英数字の場合は1byte目がANSIと同じコード、2バイト目が0なので、マルチバイトでは2バイト目がNULL文字となり文字の終了と判断されるからです。
マルチバイトでコンパイルしたい場合は、Readメンバ関数で取得したUNICODE文字列をマルチバイトに変換するコードを追加してください。

テスト環境

コンパイラ

Visual C++ 2008 Express(32bit)/Standard(32/64bit)
Visual C++ 2013 Express 32bit/64bit

実行環境

Windows 7 32/64bit

プログラムソースの概要

_tWinMain

DirectShowはCOMを使用しているので、CoInitializeでCOMの初期化を行います。
DialogBox APIによりカメラを選択するダイアログボックスを表示します。ダイアログボックスが終了するとグローバル変数cameraNumに選択されたカメラ番号が格納されています。
ダイアログボックスの終了アイコンがクリックされて終了したときは、カメラ番号を表示せずにプログラムを終了させます。
最後にCOMを終了させます。

DlgProc

WM_INITDIALOG

ダイアログの初期化時に呼び出されます。
cameraList関数によりカメラ名称と解像度を取得しcamera構造体に格納します。
リストボックスにLB_ADDSTRINGメッセージを送りデバイス名をリストボックスに追加します。
リストボックスのリソースファイルによる定義ではアイテムをソートしないように設定しておきます。
また、親ウィンドウへメッセージが届くようにLBS_NOTIFYを定義します。 カメラ番号0の解像度をリストボックスに設定します。
リストボックスにLB_SETCURSELメッセージを送信し最初のアイテムを選択した状態にします。

WM_COMMAND

IDC_LISTBOX100
HIWORD(wParam)==LBN_SELCHANGEの場合、リストボックスの選択が変更された場合ですので、リストボックスにLB_GETCURSELメッセージを送信し、カーソル位置を取得します。
グローバル変数cameraNumにカメラ番号を格納します。
選択されているカメラに応じた解像度をcamera構造体から取得し、リストボックスに設定します。
最後にカーソル位置を初期化します。
解像度の初期値をグローバル変数にresNumに格納します。
IDC_LISTBOX110
HIWORD(wParam)==LBN_SELCHANGEの場合、リストボックスの選択が変更された場合ですので、リストボックスにLB_GETCURSELメッセージを送信し、カーソル位置を取得します。
グローバル変数にresNumにカーソル位置を格納します。
IDOK
ダイアログボックスのOKボタンがクリックされたときに呼び出されます。
EndDialog APIを呼び出してダイアログボックスを終了させます。
IDCANCEL
ダイアログボックスの終了アイコンがクリックされたときに呼び出されます。
EndDialog APIを呼び出してダイアログボックスを終了させます。

cameraList

この関数はカメラの一覧を取得しcameraのvectorコンテナに格納します。
CoCreateInstanceによりデバイスの列挙子(オブジェクト)へのポインタを取得します。
取得したオブジェクトのCreateClassEnumeratorメンバ関数によりビデオ・キャプチャ・デバイスの列挙子(オブジェクト)へのポインタを取得します。
引数CLSID_VideoInputDeviceCategoryがビデオ・キャプチャ・デバイスを示しています。
CLSID_AudioInputDeviceCategoryを指定するとオーディオ・キャプチャ・デバイスが指定されます。
取得したオブジェクトのNextメンバ関数により順番にデバイスのモニカを取得します。
モニカのメンバ関数であるBindToStorageによりIPropertyBagインターフェースを取得します。
IPropertyBagインターフェースのReadメンバー関数に文字列FriendlyNameを与えて呼び出すとデバイス名が取得できます。
VariantClearによりデイバス名が保存されているメモリを解放します。
BindToObjectメンバ関数によりモニカをフィルタオブジェクトにバインドし、resList関数を呼び出し、解像度の一覧を取得します。
Releaseによりモニカを解放します。
Nextメンバー関数がS_OK以外を返すまで繰り返します。
Nextメンバー関数がS_OK以外を返した場合、デバイスの列挙が終了したので、列挙子のメモリを開放します。

resList

CoCreateInstance関数によりキャプチャグラフを作成します。
FindInterface関数によりビデオフォーマットを示すポインタを取得します。
GetNumberOfCapabilitiesによりサポートしているピン数と構造体のサイズを取得しVIDEO_STREAM_CONFIG_CAPS構造体であれば、解像度が取得できます。
GetStreamCaps関数によりフォーマット機能のセットを取得し有効なものであれば、解像度を取り出しcamera構造体に格納します。

ソースコード

camera_sel2.cpp

// DirectShow カメラ名と解像度を選択するダイアログボックスを表示する

#include <windows.h>
#include <vector>

using std::vector;

#include <tchar.h>
#include <dshow.h>
#include <stdio.h>
#include "resource.h"

#pragma comment(lib, "strmiids.lib")

//      解像度を保存するクラス
struct RES{
        int     width;
        int height;
        double frame;
};

//      カメラ名を保存するクラス
struct CAMERA{
        TCHAR name[256];
        vector<RES> res;
};


vector<CAMERA> camera;

//      ダイアログボックスプロシージャー
LRESULT CALLBACK DlgProc(HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam);

//      カメラの一覧を取得しリストボックスに設定
int cameraList(vector<CAMERA>& camera,HWND hList);

//      サポートしている解像度・フレームレートを取得する
void resList(CAMERA& camera,IBaseFilter *pbf);

//      解像度等をvectorに保存する
void bitmap_set(vector<RES>& res, VIDEOINFOHEADER* video);

int cameraNum=0;        //      リストボックスで選択されたカメラ番号を保存
int cameraMax=0;        //      カメラ数
int resNum = 0;         //      リストボックスで選択された解像度


int WINAPI _tWinMain(HINSTANCE hCurInst, HINSTANCE hPrevInst, TCHAR* CmdLine, int nCmdShow){
        TCHAR buf[64];
        // COMの初期化
        if(FAILED(CoInitialize(NULL))){
                MessageBox(0,_TEXT("COMの初期化に失敗しました"),_TEXT("エラー"),MB_OK);
                return 1;
        }

        //      ダイアログボックスの表示
        if(DialogBox(hCurInst, TEXT("DLG1"), 0, (DLGPROC)DlgProc)==TRUE){
                vector<RES>& r = ::camera[::cameraNum].res;
                _stprintf_s(buf, sizeof(buf) / sizeof(TCHAR), _TEXT("%s(%d*%d %5.1ffps)が選択されました。"), ::camera[::cameraNum].name, r[::resNum].width, r[::resNum].height, r[::resNum].frame);
                MessageBox(0,buf,_TEXT("カメラ"),MB_OK);
        }
        CoUninitialize(); // COMを終了
        return 0;
}

//      ダイアログボックスプロシージャー

LRESULT CALLBACK DlgProc(HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam){
        static HWND hList;
        static HWND REShList;
        int n,m;
        TCHAR buf[64];
        switch (msg) {
                case WM_INITDIALOG:{
                        hList = GetDlgItem(hDlg, IDC_LISTBOX100);
                        REShList = GetDlgItem(hDlg, IDC_LISTBOX110);
                        ::cameraMax = cameraList(::camera, hList);
                        if (::cameraMax == 0){
                                MessageBox(0, _TEXT("カメラが存在しません"), _TEXT("エラー"), MB_OK);
                                EndDialog(hDlg, FALSE);
                        }
                        for (n = 0; n < (int)::camera.size(); n++){
                                SendMessage(hList, LB_ADDSTRING, 0, (LPARAM)::camera[n].name);
                        }
                        vector<RES>& r = ::camera[::cameraNum].res;
                        m = (int)r.size();
                        for (n = 0; n < m; n++){
                                _stprintf_s(buf, sizeof(buf) / sizeof(TCHAR), _TEXT("%5d*%5d %5.1ffps"), r[n].width, r[n].height, r[n].frame);
                                SendMessage(REShList, LB_ADDSTRING, 0, (LPARAM)buf);
                        }
                        SendMessage(hList, LB_SETCURSEL, 0, 0);
                        SendMessage(REShList, LB_SETCURSEL, 0, 0);
                        return TRUE;
                }
                case WM_COMMAND:
                        switch (LOWORD(wParam)) {
                                case IDC_LISTBOX100:    //      リストボックスへのメッセージ
                                        if (HIWORD(wParam)==LBN_SELCHANGE){
                                                n = (int)SendMessage(hList, LB_GETCURSEL, NULL, NULL);
                                                if (0 <= n){
                                                        ::cameraNum = n;
                                                        vector<RES>& r = ::camera[::cameraNum].res;
                                                        m = (int)r.size();
                                                        SendMessage(REShList, LB_RESETCONTENT, 0, 0);
                                                        for (n = 0; n < m; n++){
                                                                _stprintf_s(buf, sizeof(buf) / sizeof(TCHAR), _TEXT("%5d*%5d %5.1ffps"), r[n].width, r[n].height, r[n].frame);
                                                                SendMessage(REShList, LB_ADDSTRING, 0, (LPARAM)buf);
                                                        }
                                                        SendMessage(REShList, LB_SETCURSEL, 0, 0);
                                                        ::resNum = n;
                                                }
                                        }
                                        break;
                                case IDC_LISTBOX110:    //      リストボックスへのメッセージ
                                        if (HIWORD(wParam)==LBN_SELCHANGE){
                                                n = (int)SendMessage(REShList, LB_GETCURSEL, NULL, NULL);
                                                if (0 <= n){
                                                        ::resNum = n;
                                                }
                                        }
                                        break;
                                case IDOK:
                                        EndDialog(hDlg,TRUE);
                                        return TRUE;
                                case IDCANCEL:
                                        EndDialog(hDlg,FALSE);
                                        return TRUE;
                                default:
                                        return FALSE;
                        }
                default:
                        return FALSE;
        }
        return TRUE;
}

//      カメラの一覧を取得しリストボックスに設定

int cameraList(vector<CAMERA>& camera,HWND hList){
        // システムデバイス列挙子の作成
        ICreateDevEnum *pDevEnum = NULL;
        CoCreateInstance(CLSID_SystemDeviceEnum, 
        NULL, CLSCTX_INPROC, IID_ICreateDevEnum, (void **)&pDevEnum);

        // 列挙子の取得
        IEnumMoniker *pClassEnum = NULL;
        pDevEnum->CreateClassEnumerator(CLSID_VideoInputDeviceCategory, &pClassEnum, 0);
        if(pClassEnum == NULL){
                pDevEnum->Release();
                return 0;
        }

        ULONG cFetched;
        IMoniker *pMoniker = NULL;
        IBaseFilter *pbf = NULL;
        int n=0;
        while(pClassEnum->Next(1, &pMoniker, &cFetched) == S_OK){
                IPropertyBag *pP = NULL;
                VARIANT var;

                pMoniker->BindToStorage(0,0,IID_IPropertyBag,(void**)&pP);
                var.vt=VT_BSTR;
                pP->Read(_TEXT("FriendlyName"),&var,0);
                //      デバイス名をリストボックスに登録
                CAMERA* c=new CAMERA;
                _tcscpy_s(c->name,sizeof(c->name)/sizeof(TCHAR),var.bstrVal);
                VariantClear(&var);

                pMoniker->BindToObject(0, 0, IID_IBaseFilter, (void**)&pbf); // モニカをフィルタオブジェクトにバインド
                resList(*c, pbf);

                camera.push_back(*c);

                pMoniker->Release();
                ++n;
        }       
        pDevEnum->Release(); // 以後に不要なメモリーをリリース
        pClassEnum->Release();
        return n;
}

//      サポートしている解像度・フレームレートを取得する

void resList(CAMERA& camera,IBaseFilter *pbf){
        HRESULT hr;

        // キャプチャグラフ作成
        ICaptureGraphBuilder2 *pCapture = NULL;
        CoCreateInstance(CLSID_CaptureGraphBuilder2, 
        NULL, CLSCTX_INPROC, IID_ICaptureGraphBuilder2, (void **) &pCapture);
        // ビデオフォーマットの取得
        IAMStreamConfig *pConfig = NULL;
        hr = pCapture->FindInterface(&PIN_CATEGORY_CAPTURE, 
        0, pbf, IID_IAMStreamConfig, (void**)&pConfig); // インターフェイスのポインタを取得

        int iCount=0;
        int     iSize=0;
        hr = pConfig->GetNumberOfCapabilities(&iCount, &iSize);
        if(iSize == sizeof(VIDEO_STREAM_CONFIG_CAPS)){// VIDEO_STREAM_CONFIG_CAPS構造体かサイズを確認
                for(int iFormat=0; iFormat<iCount; iFormat++){
                        VIDEO_STREAM_CONFIG_CAPS scc;
                        AM_MEDIA_TYPE *pmtConfig;
                        hr = pConfig->GetStreamCaps(iFormat, &pmtConfig, (BYTE*)&scc);
                        VIDEOINFOHEADER *pVih2;
                        int u=-1;
                        if(SUCCEEDED(hr)){
                                if((pmtConfig->majortype == MEDIATYPE_Video)
                                        && (pmtConfig->formattype == FORMAT_VideoInfo)
                                        && (pmtConfig->cbFormat >= sizeof(VIDEOINFOHEADER))
                                        && (pmtConfig->pbFormat != NULL)){

                                        pVih2 = (VIDEOINFOHEADER*)pmtConfig->pbFormat;
                                        bitmap_set(camera.res , pVih2);
                                        u = (int)camera.res.size();
                                }
                        }
                }
        }
        pConfig->Release();
        pCapture->Release();
}

//      解像度等をvectorに保存する

void bitmap_set(vector<RES>& res, VIDEOINFOHEADER* video){
        double ns = 100 * 1.0e-9;
        double frame = 1 / (double(video->AvgTimePerFrame)*ns);
        RES* r = new RES;
        r->frame = frame;
        r->width = video->bmiHeader.biWidth;
        r->height = video->bmiHeader.biHeight;
        res.push_back(*r);
}

resource.h

#define IDC_LISTBOX100          100
#define IDC_LISTBOX110          110

resource.rc

#include <windows.h>
#include "resource.h"


DLG1 DIALOG DISCARDABLE 0, 0, 238, 175
EXSTYLE WS_EX_DLGMODALFRAME
STYLE WS_POPUP | WS_CAPTION | WS_SYSMENU | DS_SETFONT
CAPTION "カメラの一覧"
FONT 9, "MS 明朝"
{
 CONTROL "カメラの選択", -1, "STATIC", WS_CHILD | WS_VISIBLE | SS_NOTIFY, 7, 7, 78, 12
 CONTROL "ListBox100", IDC_LISTBOX100, "LISTBOX", WS_CHILD | WS_VISIBLE | WS_VSCROLL | WS_BORDER | LBS_NOTIFY, 7, 19, 224, 32

 CONTROL "解像度の選択", -1, "STATIC", WS_CHILD | WS_VISIBLE | SS_NOTIFY, 7, 57, 78, 12
 CONTROL "ListBox100", IDC_LISTBOX110, "LISTBOX", WS_CHILD | WS_VISIBLE | WS_VSCROLL | WS_BORDER | LBS_NOTIFY, 7, 69, 224, 71

 CONTROL "OK", IDOK, "BUTTON", WS_CHILD | WS_VISIBLE | BS_DEFPUSHBUTTON, 92, 154, 54, 14
}

実行ファイルとソースファイルのダウンロード(camera_sel2.zip)