カメラ機能の実装
DirectShowにおけるカメラについて
WindowsMobileのDirectShowでは、Video Capture Filterを使ってカメラの画像を取り込みます。フィルタには3つの出力ピンがあり、それぞれ役割が異なります。
![図1. Video Capture Filter 図1. Video Capture Filter](http://cz-cdn.shoeisha.jp/static/images/article/2220/fig02.gif)
ピンの名前 | 役割 |
Preview | 画面プレビュー用 |
Capture | 動画キャプチャ用 |
Still | 静止画キャプチャ用 |
動画を取り出せるのは、PreviewピンとCaptureピンのみです。本稿では画面に表示するだけなので、Previewピンを使うことにします。
フィルタグラフの構築
DirectShowを使ってカメラ画像を表示するフィルタグラフを構築します。フィルタグラフのイメージを図2に示します。
![図2. フィルタグラフ 図2. フィルタグラフ](http://cz-cdn.shoeisha.jp/static/images/article/2220/fig03.gif)
まずフィルタグラフを構築するために、キャプチャグラフビルダを用意します。
#define SAFE_RELEASE(p) { if((p)) { (p)->Release(); (p)=NULL; }} #define NEW_INTERNAL_COCLASS(coclass) new coclass class WMCamera { IPersistPropertyBag *pPSBag; ICaptureGraphBuilder2 *pGB; IGraphBuilder *pFG; IBaseFilter *pCam; IBaseFilter *pRender; IVideoWindow *pVW; IMediaControl *pMC; public: WMCamera() { } ~WMCamera() { } void Build(HWND hwnd) { CoCreateInstance(CLSID_CaptureGraphBuilder, NULL , CLSCTX_INPROC, IID_ICaptureGraphBuilder2, (void**)&pGB); CoCreateInstance(CLSID_FilterGraph, NULL , CLSCTX_INPROC, IID_IGraphBuilder, (void**)&pFG); pGB->SetFiltergraph(pFG); pFG->QueryInterface(IID_IMediaControl, (void**)&pMC);
次にVideo Capture Filterを作成します。手順を以下に示します。
CLSID_VideoCapture
を指定してフィルタのインスタンスを作成する。- カメラデバイスの名前を取得する。
- Video Capture Filterのプロパティにカメラデバイスの名前を設定する。
カメラデバイスの名前はFindFirstDevice
を使って取得します。名前を取得したら、Video Capture Filterのプロパティに名前を設定します。プロパティはフィルタからIID_IPersistPropertyBag
インターフェイスを取得し、Load
メソッドで設定します。このメソッドは引数にIPropertyBag
インターフェイスへのポインタを渡すのですが、そのクラスは自分で実装する必要があります(後述)。
// Build() の続き const GUID CameraID = {0xCB998A05,0x122C,0x4166,0x84,0x6A,0x93,0x3E,0x4D,0x7E,0x3C,0x86}; //Video Capture Filterの取得 CoCreateInstance(CLSID_VideoCapture, NULL , CLSCTX_INPROC, IID_IBaseFilter, (void**)&pCam); HANDLE handle=NULL; DEVMGR_DEVICE_INFORMATION di={sizeof(di)}; handle=FindFirstDevice(DeviceSearchByGuid, &CameraID, &di ); _variant_t var; if(handle==NULL || di.hDevice==NULL) { return; } var=_bstr_t(di.szLegacyName); FindClose( handle ); PropertyBag *bag=NEW_INTERNAL_COCLASS(PropertyBag); bag->Write(L"VCapName", &var); pCam->QueryInterface(IID_IPersistPropertyBag, (void**)&pPSBag); pPSBag->Load(bag, NULL); SAFE_RELEASE(bag);
次に画像を表示するためのフィルタであるVideo Rendererを作成します。
Video Capture FilterとVideo RendererはRenderStream
で接続します。引数に&PIN_CATEGORY_PREVIEW
を指定するとPreviewピンからVideo Rendererに向かって接続されます。
// Build() の続き CoCreateInstance(CLSID_VideoRenderer, NULL , CLSCTX_INPROC, IID_IBaseFilter, (void**)&pRender); pFG->AddFilter(pCam, L""); pFG->AddFilter(pRender, L""); pGB->RenderStream(&PIN_CATEGORY_PREVIEW , &MEDIATYPE_Video, pCam, NULL, pRender); pMC->Run();
プロパティクラスの実装
IPropertyBag
インターフェイスを実装したクラスPropertyBag
を実装します。IPropertyBag
のメソッドを表2に示します。
名前 | 役割 |
Read | 引数で指定された名前のプロパティの値を返す。 |
Write | 引数で指定された名前のプロパティに対して、値をセットする。 |
これはSTLのstd::map
を使うと簡単に実現できます。また、_bstr_t
や_variant_t
を使えば、型もそれほど気にしなくて済みます。
// 末尾に追加する #pragma comment(lib, "comsuppw.lib") #pragma comment(lib, "xlock.lib") #include "propertybag.h"
#pragma once #include <map> class PropertyBag : public IPropertyBag { ULONG m_RefCount; std::map<_bstr_t, _variant_t> m_Props; public: // (IUnknownの実装は略) // IPropertyBag HRESULT STDMETHODCALLTYPE Read(LPCOLESTR pszPropName , VARIANT *pVar, IErrorLog *pErrorLog) { _bstr_t arg(pszPropName); if(m_Props.find(arg)==m_Props.end()){ return E_INVALIDARG; } return VariantCopy(pVar, &m_Props[arg]); } HRESULT STDMETHODCALLTYPE Write(LPCOLESTR pszPropName, VARIANT *pVar) { m_Props.insert( std::map<_bstr_t, _variant_t>::value_type(_bstr_t(pszPropName) , _variant_t(*pVar, true))); return S_OK; } };
std::map
を使うと、リンカーエラーLNK2019が発生します。これは、「xlock.lib」をリンクすると解決できます。Previewピンの設定
Previewピンは、QVGAとVGAのサイズをサポートしています。QVGAの方が若干フレームレートが高いので、RenderStream
を呼び出す直前に設定しておきます。手順を以下に示します。
- 出力ピンを列挙する
IEnumPins
インターフェイスを取得する。 - Previewという名前が見つかるまでピンを列挙する。
IAMStreamConfig::GetNumberOfCapabilities
でサポートしているフォーマットの数を取得する。GetStreamCaps
で画像フォーマットを取得し、サイズがQVGAであればSetFormat
を呼び出して設定する。
カメラ画像は縦長になっています。そのため、QVGAの場合は320×240ではなく240×320であることに注意してください。ちなみにVGAも480×640となります。
// RenderStream の直前で、このメソッドを呼び出す HRESULT SetPinProp(void) { // IEnumPinsインターフェイス取得 IEnumPins *pEP=NULL; pCam->EnumPins(&pEP); IPin *pPin = NULL; while(pEP->Next(1,&pPin,NULL)==S_OK) { PIN_INFO pInfo; pPin->QueryPinInfo(&pInfo); if(lstrcmp(pInfo.achName,L"Preview")!=0){ SAFE_RELEASE(pPin); continue; } // Previewピンであれば続きの処理 IAMStreamConfig *pConfig = NULL; int count, size; pPin->QueryInterface(IID_IAMStreamConfig, (void**)&pConfig); pConfig->GetNumberOfCapabilities(&count,&size); if(size==sizeof(VIDEO_STREAM_CONFIG_CAPS)) { for(int i=0;i<count;i++) { VIDEO_STREAM_CONFIG_CAPS scc; AM_MEDIA_TYPE *pmtConfig=NULL; pConfig->GetStreamCaps(i, &pmtConfig, (BYTE*)&scc); if(pmtConfig->majortype==MEDIATYPE_Video && pmtConfig->formattype == FORMAT_VideoInfo) { VIDEOINFOHEADER *vih =(VIDEOINFOHEADER *)pmtConfig->pbFormat; BITMAPINFOHEADER *bih=&vih->bmiHeader; BITMAPINFO *bi=(BITMAPINFO*)&vih->bmiHeader; if(bih->biHeight==320) { //フォーマットの設定 pConfig->SetFormat(pmtConfig); } } if(pmtConfig->cbFormat!=0) { CoTaskMemFree((PVOID)pmtConfig->pbFormat); } SAFE_RELEASE(pmtConfig->pUnk); } } SAFE_RELEASE(pConfig); SAFE_RELEASE(pPin); } SAFE_RELEASE(pEP); return S_OK; }
BITMAPINFOHEADER
の内容をデバッガで覗くと分かりますが、RGBフォーマットではありません。biBitCount
が12となっていること、biCompression
が0x3231d659となっていることからYUV12のようです。実行方法
ビルドして実機へ転送すると、カメラの画像が表示されます。画面左下の[OK]ボタンをタップすると終了します。
横画面にすると表示が遅くなりますので、縦画面でご使用ください。
おわりに
本稿では、DirectShowを使って、カメラから画像を取り込んで表示するWindows Mobile用アプリケーションを作成しました。
Windows MobileではVideo Capture Filterを使うとカメラ画像を取り込むことができます。フィルタのプロパティに、カメラデバイスの名前を設定する方法を紹介しました。出力ピンのフォーマットを変更し、プレビューの解像度を変更できることを確認しました。
本稿のサンプルプログラムは、プレビューするだけのシンプルなものでしたが、次回は撮影機能を実装していきたいと思います。
参考文献
- MSDN 『Encoded Media』(英語)
- Windows Mobile 6 SDK サンプルコード CameraCapture