シンプルなビデオキャプチャアプリケーションの作成

 では,実際にプログラミングを行おう.ここでは,PCに接続されているUSBカメラが撮影している映像を,ウィンドウに表示するというアプリケーションを作成する.まず,ウィンドウが無いと始まらないので,ウィンドウを作成する記述を行う(説明は割愛).
 さて,Visual C++使ったこと無い人のために,我流ながらプログラムの作成方法について記述しておこう.私は関連性のあるプログラムは同じソリューションにまとめており,変更とかする場合は新しくプロジェクトを追加するようにしている.本HPでもこの方法で勧めていく.

プロジェクトの作成

 ソリューション&プロジェクトを作成するため,当然のことながらVisual Studio.NETを起動する.メニューより,「ファイル→新規作成→からのソリューション...」を選択.

出てきたウィンドウにて,プロジェクト名と場所を適宜設定し,OKをクリック.

これでソリューションが作成される.次に,プロジェクトを追加する.「ファイル→プロジェクトの追加→新しいプロジェクト...」を選択し,「カラのプロジェクト」を選択,プロジェクト名を適宜設定する.場所は変更する必要はない(はず).

すると,ソリューションに空のプロジェクトが生成される.後は,ソースファイルやヘッダーファイル,リソースファイル等を適宜追加していき,ビルドすればよい.

ウィンドウの作成

 ウィンドウを生成するだけのプログラムは以下の通り.ソースをプロジェクトに追加するには,ソリューションエクスプローラにて,「ソースファイル」を右クリックし,「追加→新しい項目の追加」をクリック.

「新しい項目の追加」ウィンドウが表示されるので,「Visual C++」の「C++ファイル」を選択し,ファイル名を適宜設定する.場所は変更する必要なし.

これで”シンプルなビデオキャプチャアプリケーション.cpp”が生成される.プログラムはここに記述する.

で実際のコードを記述する.以下をコピペ.

#include <windows.h> 
#define WNDCLASSNAME TEXT("1st Video Capture Application\0")

LRESULT CALLBACK WindowProc(
	HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	switch(uMsg){
		case WM_DESTROY:
			PostQuitMessage(0);
			break;
		}
	return DefWindowProc(hwnd, uMsg, wParam, lParam); 
}

int APIENTRY WinMain(
	HINSTANCE hInstance, HINSTANCE hPrevInstance,
	LPSTR lpCmdLine, int nCmdShow)
{
	MSG msg={0}; 
	WNDCLASSEX wcex; 
	HWND hWnd; 

	ZeroMemory(&wcex, sizeof(wcex)); 
	wcex.cbSize = sizeof(WNDCLASSEX);
	wcex.lpfnWndProc = WindowProc; 
	wcex.hInstance = hInstance; 
	wcex.lpszClassName = WNDCLASSNAME;
	wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
	wcex.hCursor = LoadCursor(NULL, IDC_ARROW); 
	wcex.hIcon = LoadIcon(NULL, IDI_APPLICATION); 

	if(!RegisterClassEx(&wcex)){
		CoUninitialize();
		exit(1);
	}
	hWnd = CreateWindowEx(NULL,
		WNDCLASSNAME,
		"ビデオキャプチャ",
		WS_OVERLAPPEDWINDOW,
		100, 100, 300, 300,
		NULL, NULL, hInstance, NULL);
	if(hWnd){
		ShowWindow(hWnd, nCmdShow);
		while(GetMessage(&msg,NULL,0,0)){
			TranslateMessage(&msg);
			DispatchMessage(&msg);
		}
	}
	return msg.wParam;
}

そうしたら,あとはビルドして実行すれば,ウィンドウが立ち上がる.がその前に,特にデバッグする必要が無い場合,構成マネージャでReleaseビルドにしておこう.こっちの方が速いので.

実行するときは,「デバッグ→開始」にするか,*.exeをそのまま実行するなら,「デバッグ→デバッグなしで開始」をクリックすればよい.するとウィンドウが立ち上がる.

DirectShowフィルタ

ではいよいよキャプチャアプリケーションを作成する.まず,COMを初期化するためにCoInitializeを呼び出す.

HRESULT hr = CoInitialize(NULL);

次に,以下の手順でDirectShowフィルタを作成する.
@ フィルタグラフマネージャを作成する.

IGraphBuilder *pGraph=NULL;
hr = CoCreateInstance(
	CLSID_FilterGraph,
	NULL,
	CLSCTX_INPROC_SERVER,
	IID_IGraphBuilder,
	(void **)&pGraph
);

A Capture Graph Builderというヘルパーオブジェクトを使ってキャプチャグラフを作成・初期化する.

ICaptureGraphBuilder2 *pCapture=NULL;
hr = CoCreateInstance(
	CLSID_CaptureGraphBuilder2,
	NULL,
	CLSCTX_INPROC_SERVER,
	IID_ICaptureGraphBuilder2,
	(void**)&pCapture
);
hr = pCapture->SetFiltergraph(pGraph);

B グラフの開始や停止といった操作を行うため,IMediaControlインタフェースを問い合わせる.

IMediaControl *pMControl=NULL;
hr = pGraph->QueryInterface(IID_IMediaControl, (void **)&pMControl);

C グラフのイベントを通知するため,IMediaEventインタフェースを問いあわせ,メインウィンドウにイベント通知(WM_GRAPHNOTIFY)を設定する.ここで,イベントを通知するということは,メインウィンドウのウィンドウプロシージャがWM_GRAPHNOTIFYを受け取る,ということ.そこでプロシージャにWM_GRAPHNOTIFYに関する記述を追加する.

// WinMainにて
hr = pGraph->QueryInterface(IID_IMediaEvent, (void **)&g_pMEvent);
hr = g_pMEvent->SetNotifyWindow((OAHWND)hWnd, WM_GRAPHNOTIFY, 0);
// プロシージャにて
case WM_GRAPHNOTIFY:
	HandleGraphEvent();
	break;

 HandleGraphEvent()関数はイベントを監視する.以下をプログラムに追加しておく.

void HandleGraphEvent(){
if (g_pMEvent == NULL)
	return;
long evCode, param1, param2;
while (SUCCEEDED(g_pMEvent->GetEvent(&evCode, &param1, &param2, 0)))
	g_pMEvent->FreeEventParams(evCode, param1, param2);
}

D キャプチャ画像表示ビデオウィンドウを設定するため,IVideoWindowインタフェースを問い合わせる.

IVideoWindow *pVWindow=NULL;
hr = pGraph->QueryInterface(IID_IVideoWindow, (void **)&pVWindow);

E キャプチャデバイス(USBカメラ等)を選択するため,システムデバイス列挙子を作成する.つまり,PCに接続されているデバイスを列挙するってこと.

ICreateDevEnum *pSysDevEnum = NULL;
hr = CoCreateInstance(CLSID_SystemDeviceEnum, NULL, CLSCTX_INPROC_SERVER,
IID_ICreateDevEnum, (void **)&pSysDevEnum);

F 列挙したキャプチャデバイスのカテゴリ列挙子を取得する.音声なのか,映像なのか,とかそういうこと.ビデオキャプチャの場合は,CLSID_VideoInputDeviceCategoryを指定すればよい.

IEnumMoniker *pEMoniker = NULL;
hr = pSysDevEnum->CreateClassEnumerator(CLSID_VideoInputDeviceCategory, &pEMoniker, 0);

G カテゴリ列挙子のモニカ(名前,あだ名)を列挙する.一つのUSBカメラでビデオキャプチャする場合でも,いろんなモードがあったりするので,それらが全てここで列挙される.列挙したら,どのモードを利用するのかを選択する.ここの例では,一番最初に列挙されたキャプチャデバイスの一番最初に列挙されたビデオモードを選択(BindToObject)している.

IBaseFilter *pBFilter = NULL;
if(hr == S_OK){
	IMoniker *pMoniker = NULL;
	ULONG cFetched;
	if(pEMoniker->Next(1, &pMoniker, &cFetched) == S_OK){
		hr = pMoniker->BindToObject(0, 0, IID_IBaseFilter, (void **)&pBFilter);
	}
}

H 選択したデバイス・モードの情報(フィルタ)をフィルタグラフマネージャを使ってフィルタグラフに追加する.

hr = pGraph->AddFilter(pBFilter, L"USB Camera Capture");

I キャプチャ画像を表示するためのビデオプレビューグラフを作成する.

hr = pCapture->RenderStream(&PIN_CATEGORY_PREVIEW, &MEDIATYPE_Video,
pBFilter, NULL, NULL);

J IBaseFilterを解放する.IBaseFilterを問い合わせたときは,必ず後で解放しなくてはならないので注意.

pBFilter->Release();

K プレビューウィンドウの設定を行う.メインウィンドウを親ウィンドウとし,キャプチャ画像の表示エリアを子ウィンドウに設定し,描画エリアを指定,プレビューを可視化する.

hr = pVWindow->put_Owner((OAHWND)hWnd);
hr = pVWindow->put_WindowStyle(WS_CHILD | WS_CLIPCHILDREN);
RECT rc;
GetClientRect(hWnd, &rc);
pVWindow->SetWindowPosition(0, 0, rc.right, rc.bottom);
hr = pVWindow->put_Visible(OATRUE);

L フィルタグラフを実行する.

hr = pMControl->Run();

これでウィンドウにキャプチャ画像が表示される.うまくいかない場合は,おそらくキャプチャデバイスの設定とかがうまくいってないのだろう.
 アプリケーションの終了時には,フィルタグラフで利用した各インタフェースを解放し,最後にCOMも解放する.
以上より,PCに接続されたビデオキャプチャデバイスのデフォルト設定によるプレビューを行うプログラムを以下に示す.

#include <dshow.h>
#include <windows.h>
#include <streams.h>

#define WNDCLASSNAME TEXT("1st Video Capture Application\0")
#define WM_GRAPHNOTIFY WM_APP+1

#pragma comment(lib,"strmiids.lib")
#pragma comment(lib,"quartz.lib") 
#pragma comment(lib,"Strmbase.lib")
#pragma comment(lib,"Msvcrt.lib")
#pragma comment(lib,"Winmm.lib")

IMediaEventEx *g_pMEvent=NULL;

void HandleGraphEvent()
{
	if (g_pMEvent == NULL)
		return;
	long evCode, param1, param2;
	while (SUCCEEDED(g_pMEvent->GetEvent(&evCode, &param1, &param2, 0)))
		g_pMEvent->FreeEventParams(evCode, param1, param2);
}

LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	switch(uMsg){
		case WM_GRAPHNOTIFY:
			HandleGraphEvent();
			break;
		case WM_DESTROY:
			PostQuitMessage(0);
			break;
	}
	return DefWindowProc(hwnd, uMsg, wParam, lParam); 
}

int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
	MSG msg={0}; 
	WNDCLASSEX wcex; 
	HWND hWnd; 

	//COMの初期化
	HRESULT hr = CoInitialize(NULL);

	ZeroMemory(&wcex, sizeof(wcex)); 
	wcex.cbSize = sizeof(WNDCLASSEX);
	wcex.lpfnWndProc = WindowProc; 
	wcex.hInstance = hInstance; 
	wcex.lpszClassName = WNDCLASSNAME;
	wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
	wcex.hCursor = LoadCursor(NULL, IDC_ARROW); 
	wcex.hIcon = LoadIcon(NULL, IDI_APPLICATION); 

	if(!RegisterClassEx(&wcex)){
		CoUninitialize();
		exit(1);
	}
	hWnd = CreateWindowEx(NULL, WNDCLASSNAME, "ビデオキャプチャ", WS_OVERLAPPEDWINDOW, 100, 100, 300, 300, NULL, NULL, hInstance, NULL);
	if(hWnd){
		// @
		// フィルタグラフマネージャの作成
		IGraphBuilder *pGraph=NULL;
		hr = CoCreateInstance(
			CLSID_FilterGraph,
			NULL,
			CLSCTX_INPROC_SERVER,
			IID_IGraphBuilder,
			(void **)&pGraph
		);
		// A
		// キャプチャグラフの作成・初期化
		ICaptureGraphBuilder2 *pCapture=NULL;
		hr = CoCreateInstance(
			CLSID_CaptureGraphBuilder2,
			NULL,
			CLSCTX_INPROC_SERVER,
			IID_ICaptureGraphBuilder2,
			(void**)&pCapture
		);
		hr = pCapture->SetFiltergraph(pGraph);
		// B
		// IMediaControlインタフェース(グラフの開始・停止)
		IMediaControl *pMControl=NULL;
		hr = pGraph->QueryInterface(IID_IMediaControl, (void **)&pMControl);
		// C
		// IMediaEventインタフェース(イベントの取得)
		hr = pGraph->QueryInterface(IID_IMediaEvent, (void **)&g_pMEvent);
		// イベントのウィンドウ通知設定
		hr = g_pMEvent->SetNotifyWindow((OAHWND)hWnd, WM_GRAPHNOTIFY, 0);
		// D
		// IVideoWindowインタフェース(ビデオウィンドウの設定)
		IVideoWindow *pVWindow=NULL;
		hr = pGraph->QueryInterface(IID_IVideoWindow, (void **)&pVWindow);
		// E
		// システムデバイス列挙子の作成
		ICreateDevEnum *pSysDevEnum = NULL;
		hr = CoCreateInstance(CLSID_SystemDeviceEnum, NULL, CLSCTX_INPROC_SERVER,
		IID_ICreateDevEnum, (void **)&pSysDevEnum);
		// F
		// カテゴリ列挙子を取得
		IEnumMoniker *pEMoniker = NULL;
		hr = pSysDevEnum->CreateClassEnumerator(CLSID_VideoInputDeviceCategory, &pEMoniker, 0);
		// G
		// モニカを列挙
		IBaseFilter *pBFilter = NULL;
		if(hr == S_OK){
			IMoniker *pMoniker = NULL;
			ULONG cFetched;
			if(pEMoniker->Next(1, &pMoniker, &cFetched) == S_OK){
				hr = pMoniker->BindToObject(0, 0, IID_IBaseFilter, (void **)&pBFilter);
			}
		}
		// H
		// キャプチャデバイスの設定をフィルタグラフに組み入れる
		hr = pGraph->AddFilter(pBFilter, L"USB Camera Capture");
		// I
		// ビデオプレビューグラフを作成
		hr = pCapture->RenderStream(&PIN_CATEGORY_PREVIEW, &MEDIATYPE_Video, pBFilter, NULL, NULL);
		// J
		// IBaseFilterの解放
		pBFilter->Release();
		// K
		// プレビューウィンドウの設定
		hr = pVWindow->put_Owner((OAHWND)hWnd);
		hr = pVWindow->put_WindowStyle(WS_CHILD | WS_CLIPCHILDREN);
		RECT rc;
		GetClientRect(hWnd, &rc);
		pVWindow->SetWindowPosition(0, 0, rc.right, rc.bottom);
		hr = pVWindow->put_Visible(OATRUE);
		// L
		// フィルタグラフの実行
		hr = pMControl->Run();
		ShowWindow(hWnd, nCmdShow);
		while(GetMessage(&msg,NULL,0,0)){
			TranslateMessage(&msg);
			DispatchMessage(&msg);
		}
		// DirectShowインタフェースの解放
		pMControl->Release();
		g_pMEvent->Release();
		pVWindow->Release();
		pGraph->Release();
		pCapture->Release();
	}

	// COMの解放
	CoUninitialize();

	return msg.wParam;
}

[前の章へ] [次の章へ] [先頭] [TOP]

SEO [PR] 爆速!無料ブログ 無料ホームページ開設 無料ライブ放送