Sony DFW-VL500(IEEE1394) + DirectShow(Windows) ・・・で,どうやって?
(注意)下記ドキュメントは2001年時点での情報です.例えば DirectShowは 2006年現在ではDirectXではなく Platform SDK ("Platform SDK" + "Web Install"などでウェブ全体からサーチ) に入っていたりします.
ん?まてまて.そもそも,VCすらほとんど使ったこと無いやん.
DirectX と ActiveX の区別がついたのがつい最近・・・香港映画との区別もつ
かなかった三年前に比べれば,えらい進歩やね
じゃあなんでDirectShowをやるの?・・・そこにDirectShowがあるから
というわけで,あまり無理せんとできるだけシンプルに理解する.サンプルコー
ドは main関数を用いたコンソールアプリケーションとし,1画面で収まることを
目指そう.
- DirectShowキャプチャプログラミングの準備
- DirectShow サンプルを眺める
- StillCap (Still, Video のキャプチャサンプル)
C:\mssdk\samples\Multimedia\DirectShow\Editing\StillCap - AMCap (Videoのキャプチャ,カメラのパラメタコントロール)
C:\mssdk\samples\Multimedia\DirectShow\Capture\AMCap
- StillCap (Still, Video のキャプチャサンプル)
- DirectShowでビデオキャプチャ
ICaptureGraphBuilder2とIFileSinkFilter - フィルタプロパティの取得
- DirectShowでスチルキャプチャ(その1:
GetCurrentBuffer編)
Streamから一枚取る ISampleGrabber + GetCurrentBuffer - DirectShowでスチルキャプチャ(その2:コールバック
編)
Streamから一枚取る ISampleGrabber + ISampleGrabberCB - カメラコントロール
- 番外編
- 複数カメラでのキャプチャ
- MDIなGUI(省略)
(注)ドキュメントの Help(...)は,DirectX 8.0 日本語へルプ をデフォルトのパス(C:\mssdk\doc\DX8JHelp\directx8_c.chm)にインストールし たものとして,ローカルディスクにリンクを張っている.
DirectShowキャプチャプログラミングの準備
Graph Editで遊ぶ
- スタート → プログラム → Microsoft DirectX 8 SDK → DirectX Utilities → Graph Edit を開く.
- Graph → Insert Filtersでフィルタを選ぶ
例えば- Video Capture Sources
- DirectShow Filter → Video Renderer
- フィルタのピンを接続する(マウスで入力ピンから出力ピンへドラッグ)
必要なフィルタはある程度自動的に挿入される(Graph → Connect Intelligent のチェックを入れておく). - 再生ボタンを押してグラフをプレイしてみる
- 詳しい遊び方
- Help(DirectShow の使い方 → GraphEditによるグラフ構築のシミュレーション)
COMの浅めの理解
まぁ,要はインタフェースが重要らしい
dll入れ替えられるんやね
CoCreateInstanceでインスタンスつくって,インタフェースのポインタは
QueryInterfaceで取得する
GUID(CLSID, IID)でインタフェースや実装クラスを識別する
こんなんで,ええか?
CComPtr ? ATL ? はあとまわし
DirectShowのHellow World
DirectXのへルプにあるDirectShowのファーストステップを参考に,まずは DirectShow のHellow World と呼ばれる サンプルコードを書いてみる.
- Win32コンソールアプリケーションとしてプロジェクトを作成
- プロジェクト→設定→リンクに,strmiids.libを追加する.
- dshow.hをインクルードする(あらかじめ設定→C/C++→プロジェクト オプションに,インクルードファイルのパス(/I"C:\mssdk\include")が通っているのを確認)
- COMの初期化と終了処理のために, CoInitialize(NULL), CoUninitialize()を始めと終わりに書く.
- 最後に参照カウンタをデクリメントするために (ポインタ)->Release を忘れずに.
#include <iostream> #include <dshow.h> using namespace std; int main() { cout << "Aiko 花火: [Enter]" << endl; getchar(); CoInitialize(NULL); IGraphBuilder *pGraph; IMediaControl *pMediaControl; IMediaEvent *pEvent; CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC_SERVER, IID_IGraphBuilder, (void **)&pGraph); pGraph->QueryInterface(IID_IMediaControl, (void **)&pMediaControl); pGraph->QueryInterface(IID_IMediaEvent, (void **)&pEvent); // グラフを作成 pGraph->RenderFile(L"D:\\aiko-hanabi.mpg", NULL); // グラフの実行 pMediaControl->Run(); // 終了を待つ long evCode; pEvent->WaitForCompletion(INFINITE, &evCode); // クリーンアップ pMediaControl->Release(); pEvent->Release(); pGraph->Release(); CoUninitialize(); return; }
あと,IVideoWindow や IMediaEventEx のサンプルもあるので試す. (WinMain で書かれているが,Dialog with MFC ぐらいにしてみる)
フィルタグラフ・・・フィルタとピン
ストリーム・・・
行く河の流れは絶えずしてしかももとの水にはあらず
うう,よくわからんが,
流れ(stream)を制(control)する者はDirectShowを制する
(グラフ内の)streamが全て絶えたときは EC_COMPLETE イベントがアプリケーションに送られるらしい
DirectShowでビデオキャプチャ
まずは,Help(DirectShow の使い方 → ビデオキャプチャ,DirectShowチュートリアル → AVIファイルの再圧縮) を参考にビデオキャプチャの 流れをまとめてみよう.
基本的流れ
(最初と最後にそれぞれ CoInitialize(NULL), CoUninitialize()を 呼ぶのを忘れないように・・・)
1. | フィルタグラフ作成 | CoCreateInstance( CLSID_FilterGraph ) |
2. | キャプチャデバイスの取得 | ICreateDevNum, IEnumMoniker, IMoniker, IBaseFilter |
3. | キャプチャビルダグラフ | ICaptureGraphBuilder2 |
3-1. キャプチャビルダグラフ作成 | CoCreateInstance( CLSID_CaptureGraphBuilder2 ) | |
3-2. フィルタグラフへの関連付け | ICaptureGraphBuilder2::SetFilterGraph((IFilterGraph)pGraph) | |
4. | ファイルライタフィルタ | IFileSinkFilter |
4-1. フィルタグラフへの追加 | ICaptureGraphBuilder2::SetOutputFileName(...) | |
4-2. ストリームのレンダリング設定 | ICaptureGraphBuilder2::RenderStream(...) | |
5. | キャプチャ開始 | IMediaControl::Run() |
2のデバイス列挙子の使用に付いては以下にもう少し詳 しくまとめる.
- ICaptureGraphBuilder2
- 一般的なグラフ構築は IGraphBuilderの様々なメソッドを用いて行う. しかし,ビデオキャプチャの場合はデバイスは多様であり, グラフの構築が複雑になりがちである. そこで,アプリケーション開発者の負担軽減のために Capture Graph Builder というオブジェクトが用意されている.一般的なグラフ構築は,Help ( DirectShowについて → フィルタグラフの構築 → 一般的なグラフの構築) 参照.
デバイスの列挙
Help ( DirectShowチュートリアル → デバイスとフィルタの列挙 → システムデバイスの列挙子の使用) 参照
2-1. | デバイスの列挙子を取得 | ICreateDevEnum | CoCreateInstanceによってインスタンス生成 |
2-2. | カテゴリの列挙子を取得 | IEnumMoniker | ICreateDevEnum::CreateClassEnumerator(カテゴリ名, ...) によって入手 |
2-3. | モニカを取得 | IMoniker | IEnumMoniker::Next によって列挙 |
2-4. | フィルタにバインド | IBaseFilter | IMoniker::BindToObject(..., (void **)pSrc) |
フレンドリ名を取得するには, 4で IMoniker::BindToStrage を用いてプロパティ バッグを取得し,IPropertyBag::Read を用いるらしい.
ソースリスト (videocap_console)
(注)簡略化のためエラー処理一切なし.#include <dshow.h> #include <iostream> using namespace std; int main(int argc, char* argv[]) { CoInitialize(NULL); // 1. フィルタグラフ作成 IGraphBuilder *pGraph = NULL; CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC, IID_IGraphBuilder, (void **)&pGraph); // 2. システムデバイス列挙子を作成 ICreateDevEnum *pDevEnum = NULL; CoCreateInstance(CLSID_SystemDeviceEnum, NULL, CLSCTX_INPROC, IID_ICreateDevEnum, (void **)&pDevEnum); IEnumMoniker *pClassEnum = NULL; pDevEnum->CreateClassEnumerator(CLSID_VideoInputDeviceCategory, &pClassEnum, 0); ULONG cFetched; IMoniker *pMoniker = NULL; IBaseFilter *pSrc = NULL; if (pClassEnum->Next(1, &pMoniker, &cFetched) == S_OK){ // 最初のモニカをフィルタオブジェクトにバインドする pMoniker->BindToObject(0, 0, IID_IBaseFilter, (void **)&pSrc); pMoniker->Release(); } pClassEnum->Release(); pDevEnum->Release(); pGraph->AddFilter(pSrc, L"Video Capture"); // 3. キャプチャビルダの作成 ICaptureGraphBuilder2 *pBuilder = NULL; CoCreateInstance(CLSID_CaptureGraphBuilder2, NULL, CLSCTX_INPROC, IID_ICaptureGraphBuilder2, (void **)&pBuilder); pBuilder->SetFiltergraph(pGraph); // 4. ファイルライタフィルタの設定 IBaseFilter *pMux = NULL; IFileSinkFilter *pSink = NULL; // ファイルへ pBuilder->SetOutputFileName(&MEDIASUBTYPE_Avi, L"C:\\dvcap_tmp.avi", &pMux, &pSink); pBuilder->RenderStream(&PIN_CATEGORY_CAPTURE, &MEDIATYPE_Video, pSrc, NULL, pMux); REFERENCE_TIME rtStart = 50000000, rtStop = 80000000; pBuilder->ControlStream( &PIN_CATEGORY_CAPTURE, &MEDIATYPE_Video, pSrc, &rtStart, &rtStop, 0, 0 ); // ディスプレイへ pBuilder->RenderStream( &PIN_CATEGORY_PREVIEW, &MEDIATYPE_Video, pSrc, NULL, NULL ); // 5. キャプチャ開始 IMediaControl *pMediaControl; pGraph->QueryInterface(IID_IMediaControl, (void **)&pMediaControl); pMediaControl->Run(); MessageBox(NULL, "Click to stop.", "DirectShow", MB_OK); // 6. 終了 pSrc->Release(); pMux->Release(); pSink->Release(); pMediaControl->Release(); pEvent->Release(); pBuilder->Release(); pGraph->Release(); CoUninitialize(); cout << "end\n"; return 0; }
(注)Preview を見ていると, キャプチャされた画像の色数がやたらと少ない場合(おそらく16bpp)がある.これは, YUVから RGBへ の変換方法に依存するようだ.この辺は,キャプチャ ドライバとグラフィックカードの組合わせによって異なる.
一般には,AVIデコンプレッサというのがこの変換を司るが,
グラフィックカードがハードウェア的にサポートしていれば(かつVideoRenderer
で YUV Overlays/Flipping/OffScreen等 がオンになっていれば),AVIデコンプレッサはYUVを素通りさせ,直接ビデオメモリのDirectDrawオーバレイサーフェイスにコピーする.グラフィックカードが
サポートしていない場合は,AVIデコンプレッサが MSYUV
CODECを用いてソフト的に変換を行う.しかし,このMSYUVはRGB16, RGB8へ
の変換しかできないらしく,この場合に色数が少なくなる.RGB24,32をサポート
するCODECを用いれば良いのだが・・・(例えば,Unibrain
Fire-i のキャプチャドライバでは,MSYUV CODECでなく,独自のYUV CODEC (fiyuv.dll,
fiyuv.ax(DirectShow Filter)) によってRGB32を実現している)(<--もはや昔
の話です.今のDirectshow9のフィルタはRGB32になります.)
まあ,あくまで表示(VideoRenderer)の話なので,ファイルに落とすとYUVのまま
保存されているようだ.
参照 Help( DirectShowリファレンス → フィルタ → MSYUVカラース
ペース変換 CODEC )
フィルタプロパティの取得
フィルタの列挙
Help ( DirectShowチュートリアル → フィルタグラフ内のオブジェクトの列挙 → フィルタの列挙) 参照
1. | フィルタ列挙子の取得 | IEnumFilters | IFilterGraph::EnumFilters |
2. | フィルタの列挙 | IBaseFilter | IEnumFilters::Next(1, (IBaseFilter **), (ULONG *)) |
3. | フィルタ名の取得 | FILTER_INFO | IBaseFilter::QueryFilterInfo(FILTER_INFO *) |
フィルタのプロパティページ表示
Help ( DirectShowチュートリアル → フィルタのプロパティページの表示) を参考に・・・
直接生成したフィルタ以外は,フィルタへのポインタを持っていない.そこで,
フィルタの列挙子を用いて,順にグラフ内のフィル
タ(へのポインタ)を取得する.もしくは,フィルタ名が分かっているときは
IFilterGraph::FindFilterByName メソッドを用いる.
4. | プロパティページインタフェース取得 | ISpecifyPropertyPages | IBaseFilter::QueryInterfase(IID_ISpecifyPropertyPages, (void **)) |
5. | プロパティページの取得 | CAUUID | ISpecifyPropertyPages::GetPages(CAUUID *) ISpecifyPropertyPages::Release() |
6. | プロパティページの表示 | OleCreatePropertyFrame | |
7. | GUID配列の解放 | CoTaskMemFree(CAUUID::pElems) |
CAUUID は GUIDのカウント配列が定義される構造体.初期化は要らない.各プロパティページ のクラス識別(CLSID)がCAUUID::pElemsに入る. OleCreatePropertyFrameによって作成されるダイアログはモーダル.
5. キャプチャ開始 の直前にプロパティページを表示させてみてもいいかも.
// フィルタを列挙し、それらのプロパティ ページを表示する。 IEnumFilters *pEnum; IBaseFilter *pFilter = NULL; HRESULT hr; pGraph->EnumFilters(&pEnum); while(pEnum->Next(1, &pFilter, NULL) == S_OK) { ISpecifyPropertyPages *pSpecify; hr = pFilter->QueryInterface(IID_ISpecifyPropertyPages, (void **)&pSpecify); if (SUCCEEDED(hr)) { FILTER_INFO FilterInfo; pFilter->QueryFilterInfo(&FilterInfo); CAUUID caGUID; pSpecify->GetPages(&caGUID); pSpecify->Release(); OleCreatePropertyFrame( NULL, // 親ウィンドウ 0, // x (予約済み) 0, // y (予約済み) FilterInfo.achName, // ダイアログ ボックスのキャプション 1, // フィルタの数 (IUnknown **)&pFilter, // フィルタへのポインタ caGUID.cElems, // プロパティ ページの数 caGUID.pElems, // プロパティ ページ CLSID へのポインタ 0, // ロケール識別子 0, // 予約済み NULL // 予約済み ); CoTaskMemFree(caGUID.pElems); FilterInfo.pGraph->Release(); } pFilter->Release(); } pEnum->Release();
どうも,ビデオキャプチャーだと動的に決まるパラメータが多いので,キャプチャー 前にモーダルダイアログでプロパティを表示させても意味無いな.後でモード レスなプロパティボックスにしよう.
DirectShowでスチルキャプチャ(その1:GetCurrentBuffer編)
流れのなかに網を仕掛けておくイメージで,上のビデオキャプチャのstream中にサ ンプルグラバを挿入する(ちょっと違うか.).これによって,フィルタを通過 していくサンプルを獲得できるそうだ.これは SampleGrabberフィルタを作成し, ISampleGrabberインタフェースを用いて行うが,次の2つの方法があるらしい.
- ISampleGrabber::SetBufferSample(TRUE)にし,ISampleGrabber::GetCurrentBuffer()を明示的に呼ぶ
- ISampleGrabberCBインタフェースを実装したクラスを作成し, ISampleGrabber::SetCallBack()によって関連付ける
ここでは,まずは前者を試してみることにしよう.(ちなみにDirectShowサンプルStillCap
は後者を用いている)
Help ( DirectShow
チュートリアル → メディアサンプルの獲得) 参照.
基本的に,1から3はビデオキャプチャと同じだ が,4を次のようにする.
基本的流れ
4-1. サンプルグラバの生成 | IBaseFilter ISampleGrabber |
CoCreateInstance(CLSID_SampleGrabber, ..., IID_IBaseFilter, ...) IBaseFilter::QueryInterfase(IID_ISampleGrabber, ...) |
4-2. メディアタイプの設定 | AM_MEDIA_TYPE | ISampleGrabber::SetMediaType(AM_MEDIA_TYPE *) |
4-3. フィルタグラフへ追加 | IFilterGraph::AddFilter(IBaseFilter *) | |
4-4. サンプルグラバの接続 | 様々な方法があるらしい | |
4-5. グラバのモードを適切に設定 | ISampleGrabber::SetBufferSample(TRUE) : 毎サンプルをバッファにコピーするかどうか ISampleGrabber::SetOneShot(FALSE) : 1サンプルでグラフを終了するか |
|
5. キャプチャ中にグラブを行う | ISampleGrabber::GetCurrentBuffer(...) |
サンプルグラバの接続は・・・次のようなやり方があるらしいが.
- IGraphBuilder::RenderFile (メディアタイプにしたがってグラフマネー ジャがサンプルグラバを接続する)
- IGraphBuilder::Connect (ピンで指定)
- ICaptureGraphBuilder2
1と3は,ある程度自動で構築するみたいやけど,今はどこに接続するかを理解す るのが大切なので,ここでは,2のピンで指定してサンプルグラバフィルタを挿入 するやり方をとる.フィルタを追加した際のピンの接続に関しては,Help ( DirectShow について → フィルタグラフの構築 → 一般的なグラフの構築) 参照.
(注) ISampleGrabberを用いるには, #include<qedit.h>をインクルードする必要がある.
ピンの列挙
まず,指定した方向のピンを得るための関数をつくる. Help ( DirectShowチュートリアル → フィルタグラフ内のオブジェクトの列挙 → ピンの列挙) 参照
1. | ピン列挙子の取得 | IEnumPins | IBaseFilter::EnumPins |
2. | ピンの列挙 | IPin | IEnumPins::Next(1, (IPin **), 0) |
3. | ピンの方向(IN,OUT)の取得 | FILTER_INFO | IPin::QueryDirection(PIN_DIRECTION *) |
IPin *GetPin(IBaseFilter *pFilter, PIN_DIRECTION PinDir) { BOOL bFound = FALSE; IEnumPins *pEnum; IPin *pPin; pFilter->EnumPins(&pEnum); while(pEnum->Next(1, &pPin, 0) == S_OK) { PIN_DIRECTION PinDirThis; pPin->QueryDirection(&PinDirThis); if (bFound = (PinDir == PinDirThis)) // 引数で指定した方向のピンならbreak break; pPin->Release(); } pEnum->Release(); return (bFound ? pPin : 0); }
ソースリスト ( stillcap_console1 )
ただし,ISampleGrabber を用いるために #include <qedit.h> を加える.
// 4. 一枚撮る // 4-1. サンプルグラバの生成 IBaseFilter *pF = NULL; ISampleGrabber *pSGrab; CoCreateInstance(CLSID_SampleGrabber, NULL, CLSCTX_INPROC_SERVER, IID_IBaseFilter, (LPVOID *)&pF); pF->QueryInterface(IID_ISampleGrabber, (void **)&pSGrab); // 4-2. メディアタイプの設定 AM_MEDIA_TYPE mt; ZeroMemory(&mt, sizeof(AM_MEDIA_TYPE)); mt.majortype = MEDIATYPE_Video; // Sample Grabber の入力ピン(Capture Device の出力ピン)はUYVY mt.subtype = MEDIASUBTYPE_UYVY; mt.formattype = FORMAT_VideoInfo; pSGrab->SetMediaType(&mt); // 4-3. フィルタグラフへ追加 pGraph->AddFilter(pF, L"Grabber"); // 4-4. サンプルグラバの接続 // [pSrc](o) -> (i)[pF](o) -> [VideoRender] // ↑A ↑B ↑C IPin *pSrcOut = GetPin(pSrc, PINDIR_OUTPUT); // A IPin *pSGrabIN = GetPin(pF, PINDIR_INPUT); // B IPin *pSGrabOut = GetPin(pF, PINDIR_OUTPUT); // C pGraph->Connect(pSrcOut, pSGrabIN); pGraph->Render(pSGrabOut); // 4-5. グラバのモードを適切に設定 pSGrab->SetBufferSamples(TRUE); // GetCurrentBuffer を用いる際には必ずTRUE (FALSE では失敗する) pSGrab->SetOneShot(FALSE); // ワンショットをOFFにする // 5. キャプチャ開始 IMediaControl *pMediaControl = NULL; pGraph->QueryInterface(IID_IMediaControl, (void **)&pMediaControl); pMediaControl->Run(); long *buffer = NULL; long bufsize = 0; // サイズ取得 do{ pSGrab->GetCurrentBuffer(&bufsize, NULL); }while(bufsize <= 0); cerr << "bufsize : " << bufsize << endl; buffer = (long *)malloc(sizeof(char)*bufsize); if(buffer==NULL){ cerr << "can't allocate memory\n"; return 1; } // 一枚キャプチャ MessageBox(NULL, "Click to capture & stop.", "DirectShow", MB_OK); // バッファ取得 hr = pSGrab->GetCurrentBuffer(&bufsize, buffer); if(hr==S_OK){ cerr << "Capture Succeeded\n"; } // 6. 終了 pF->Release(); // こいつらも pSGrab->Release(); // わすれずに解放 pSrc->Release(); pMediaControl->Release(); pBuilder->Release(); pGraph->Release(); CoUninitialize();
メディアタイプはHelp( DirectShow リファレンス → 定数とGUID → メディアタイプ )参照.
DirectShowでスチルキャプチャ(その2:コールバック編)
基本的流れ
基本的に先のスチルキャプチャと同様であるが,受け取るサンプル毎にコールバッ クを呼び出すことができるので,これを利用してバッファの内容をファイルに落 とすことにする.コールバック関数としては SampleCBかBufferCBを実装せなあ かんらしいが,両者の引数を見てみると,その違いはこんな感じやと思われる.
- SampleCBは,IMediaSampleインタフェースへのポインタを受け取り,サンプ ルのプロパティやデータへのポインタはこのインタフェースを介して取得, 設定する
- BufferCBは,直接的にデータへのポインタや長さを取得できる
ここでは,より単純やと思われる後者を実装する.
0. ISampleGrabberCBインタフェースの実装 | ISampleGrabberCB | メソッドとしてSampleCBかBufferCBのいずれか一つを実装する |
4-5. グラバのモードを適切に設定 | SetBufferSample(FALSE), SetOneShot(FALSE) SetCallback() : コールバックメソッドを指定 第1引数:ISampleGrabberCB実装クラスのインスタンスへのポインタ 第2引数:どちらのメソッドを用いるか.0ならSampleCB, 1ならBufferCB |
Help(
DirectShowチュートリアル → メディアサンプルの獲得 → コールバックメソッ
ドの利用 )参照
(注)streams.hをインクルードし,strmbase.lib(Release)もしくは
strmbasd.lib(Debug)をリンクする必要がある.
コールバックインタフェースの実装
ISampleGrabberCBインタフェースを継承し,メソッドを実装する.ただし,これ はCOMに基づいて実装する必要があるため,簡略化のためCUnknown も継承する. これによって,IUnknown のAddRef, Release, QueryInterfaceといったメソッド を1から実装する手間が省ける.その代わり,NonDelegatingQueryInterfaceを実装す る.(サンプル StillCap では,CUnknownは継承せず1から実装している)
class CGrabCB: public CUnknown, public ISampleGrabberCB { public: DECLARE_IUNKNOWN; STDMETHODIMP NonDelegatingQueryInterface(REFIID riid, void **ppv) { if( riid == IID_ISampleGrabberCB ){ return GetInterface((ISampleGrabberCB*)this, ppv); } return CUnknown::NonDelegatingQueryInterface(riid, ppv); } // ISampleGrabberCB のメソッド STDMETHODIMP SampleCB(double SampleTime, IMediaSample *pSample) { return E_NOTIMPL; } STDMETHODIMP BufferCB(double SampleTime, BYTE *pBuffer, long BufferLen) { cerr << "Sample time: " << SampleTime << "\t"; cerr << "BufferLen: " << BufferLen; cerr << endl; SaveOneImage("C:\\tmp.yuv", pBuffer, BufferLen); // 後に定義する return S_OK; } // コンストラクタ CGrabCB( ) : CUnknown("SGCB", NULL) { } };
インプリメントしないメソッド(この場合はSampleCB)では E_NOTIMPL を返すよ うにする.
ソースリスト ( stillcap_console2 )
まずSaveOneImageを定義しておく.これはsaveflagがTRUEになる度に,一回だ けバッファ内容をファイルに書き出す関数である.
BOOL saveflag=FALSE; inline BOOL SaveOneImage(const char *fname, BYTE *pBuffer, long BufferLen) { if(saveflag){ ofstream fout(fname, ios::out | ios::binary); if(!fout.is_open()) return FALSE; fout.write((char *)pBuffer, BufferLen); fout.close(); saveflag=FALSE; } return TRUE; }
次に,stillcap_cosole1のmain関数内で変更する部分を示す.
// 4-5. グラバのモードを適切に設定 pSGrab->SetBufferSamples(FALSE); pSGrab->SetOneShot(FALSE); CGrabCB *cb = new CGrabCB(); pSGrab->SetCallback(cb, 1); // 第2引数でコールバックを指定 (0:SampleCB, 1:BufferCB) // 5. キャプチャ開始 IMediaControl *pMediaControl = NULL; IMediaEvent *pEvent = NULL; pGraph->QueryInterface(IID_IMediaControl, (void **)&pMediaControl); pGraph->QueryInterface(IID_IMediaEvent, (void **)&pEvent); long evCode; pMediaControl->Run(); MessageBox(NULL, "Click to capture & stop.", "DirectShow", MB_OK); saveflag = TRUE; pEvent->WaitForCompletion(100, &evCode);
CGrabCB のインスタンスを作成し,SetCallbackで設定しているぐらいやね. MessageBoxにOKしたあとにsaveflagをTRUEにする.すると,SaveOneImageはその ときのバッファの内容をファイルを保存する.コールバックはスレッドで呼ばれ るため,WaitForCompletionを用いて保存する時間を確保している(ばかちょん でやけど).
カメラのコントロール
基本的流れ
ビデオデバイスのドライバが, WDM (Windows Deriver Model) に基づいて書か れたビデオキャプチャフィルタであるとする.これが IAMCameraControlをサポー トしていれば,次のような手順でzoom,focus・・・などのパラメタをコントロー ルできるはず.同様に,IAMVideoProcAmpを用いて,輝度,コントラスト,色相, 彩度,ガンマ,鮮明度などを調節できる.
- Help(DirectShow リファレンス → IAMCameraControl インタフェース)参照
- Help(DirectShow リファレンス → IAMVideoProcAmp インタフェース)参照
a. デバイスのインタフェイス取得 | IAMCameraControl, IAMVideoProcAmp | IBaseFilter::QueryInterface(...) |
b. パラメタの範囲取得 | GetRange(...) |
|
c. パラメタコントロール | Set(...) |
|
b. パラメタ取得 | Get(...) |
- WDMドライバとKsProxyフィルタ
- KsProxyフィルタというのがある.Kernel Streaming Proxyの略らしい.細かい
ことは省略するが(ていうか理解できていないが),要するに,カーネルモード
のKSドライバ(WDMドライバの一種)によって動いているデバイスを,ユーザモー
ドのDirectShowフィルタとして見せかけ,アプリケーションレベルでコントロールするための
ラッパフィルタらしい.WDMに準拠したデバイスと,適切に書かれたドライバ
(正確にはmini driver)の組み合わせであれば,ksproxyフィルタによってラップできる.
で,Kernel Streaming は何がいいかというと,「データをユーザモードに渡すことなく,完全にカーネルモードで 直接ストリーミングすることができる」ので,大幅にCPUの負荷を減らす ことができるらしい.
KSドライバとKsProxyフィルタの関係は,おそらくこんな感じやろ(想像図)
ソースリスト (cameracontrol)
これはほとんどvideocapture_consoleその まま.ただし,ファイル出力部分を削除した.
..... #define NSTEP 5 ..... // 3. ICaptureGraphBuilder2 *pBuilder = NULL; CoCreateInstance(CLSID_CaptureGraphBuilder2, NULL, CLSCTX_INPROC, IID_ICaptureGraphBuilder2, (void **)&pBuilder); pBuilder->SetFiltergraph(pGraph); pBuilder->RenderStream( &PIN_CATEGORY_PREVIEW, &MEDIATYPE_Video, pSrc, NULL, NULL ); // カメラコントロール IAMCameraControl *pCameraControl; CameraControlProperty prop; pSrc->QueryInterface(IID_IAMCameraControl, (void **)&pCameraControl); long Min, Max, SteppingDelta, Default, CapsFlags; prop = CameraControl_Zoom; pCameraControl->GetRange(prop, &Min, &Max, &SteppingDelta, &Default, &CapsFlags); cerr << "Min : " << Min << endl; cerr << "Max : " << Max << endl; cerr << "Stepping Delta : " << SteppingDelta << endl; cerr << "Default : " << Default << endl; cerr << "CapsFlags : " << CapsFlags << endl; long zoom; long cflags; pCameraControl->Get(prop, &zoom, &cflags); cerr << "zoom : " << zoom << endl; // 5. キャプチャ IMediaControl *pMediaControl; pGraph->QueryInterface(IID_IMediaControl, (void **)&pMediaControl); pMediaControl->Run(); long setzoom=Min; long step = (Max-Min)/NSTEP; cerr << "step : " << step << endl; for(int i=0; i < NSTEP; i++){ if(i==NSTEP-1) setzoom = Min; // zoom:Minで終える MessageBox(NULL, "Click to zoom up", "DirectShow", MB_OK); pCameraControl->Set(prop, setzoom, cflags); int diff; do{ pCameraControl->Get(prop, &zoom, &cflags); cerr << "zoom : " << zoom << endl; diff = zoom - setzoom; cerr << "diff*diff: " << diff*diff << endl; }while(diff*diff > 1); setzoom += step; } MessageBox(NULL, "Click to stop.", "DirectShow", MB_OK); // 6. pCameraControl->Release(); pSrc->Release(); .....
番外編
自分のフィルタグラフをGraphEditで確認
便利やし,ヘルプそのままやけどメモっとこ.自分の書いたアプリケーションで,
フィルタグラフがどのような構成になっているかを,GraphEditを用いて目で確
認できる.GraphEditからプリントしてpsファイル等に落とせば,ドキュメント
にもしやすいし.
- GraphEditでの表示方法
- 以下の様にAddToRot, RemoveFromRot をソースに追加し,アプリケーション実行 中に,GraphEditの [File] → [Connect] で アプリケーションのpidを選択する.
- 終了方法
- 念のため,GraphEditを終了してから,アプリケーションを終了すること.へル プには,「アプリケーションでは,終了時にさまざまなエラーが発生することが ある.」とある.また,このときのGraphEditでは,フィルタの追加やグラフの停 止など,いらんことはせんほうがええらしい.あくまで見るだけ.
HRESULT AddToRot(IUnknown *pUnkGraph, DWORD *pdwRegister) { IMoniker * pMoniker; IRunningObjectTable *pROT; if (FAILED(GetRunningObjectTable(0, &pROT))) { return E_FAIL; } WCHAR wsz[256]; wsprintfW(wsz, L"FilterGraph %08p pid %08x", (DWORD_PTR)pUnkGraph, GetCurrentProcessId()); HRESULT hr = CreateItemMoniker(L"!", wsz, &pMoniker); if (SUCCEEDED(hr)) { hr = pROT->Register(0, pUnkGraph, pMoniker, pdwRegister); pMoniker->Release(); } pROT->Release(); return hr; } void RemoveFromRot(DWORD pdwRegister) { IRunningObjectTable *pROT; if (SUCCEEDED(GetRunningObjectTable(0, &pROT))) { pROT->Revoke(pdwRegister); pROT->Release(); } }
・・・ // 1. フィルタグラフ作成 IGraphBuilder *pGraph = NULL; CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC, IID_IGraphBuilder, (void **)&pGraph); // FilterGraph作成後にRot(実行時オブジェクトテーブル)に追加 DWORD dwRegister; hr = AddToRot(pGraph, &dwRegister); ・・・ // Graphのリリース前にRotから削除 RemoveFromRot(dwRegister); pGraph->Release(); ・・・
ちなみに videocap_consoleはこんなグラフになった.
コンソールウインドウを消す
これも一応メモっとこっと.
毎回毎回 「Press any key to continue」に答えるのは面倒.もしコンソールが
必要ないなら,[設定] → [リンク] → [プロジェクトオプション] の最後に
/SUBSYSTEM:WINDOWS /ENTRY:mainCRTStartup
を加える.
複数カメラでのキャプチャ
基本的流れ
カメラが複数になっても,一つのグラフに追加できるみたいやな. とりあえずやってみよう. ただし,今はカメラ間の同期などは考えないものとする.
ソースリスト ( two_cameras )
・・・ #define NCAM 2 int main(int argc, char* argv[]) { HRESULT hr; CoInitialize(NULL); // 1. フィルタグラフ作成 IGraphBuilder *pGraph = NULL; CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC, IID_IGraphBuilder, (void **)&pGraph); DWORD dwRegister; hr = AddToRot(pGraph, &dwRegister); // 2. システムデバイス列挙子を作成 ICreateDevEnum *pDevEnum = NULL; CoCreateInstance(CLSID_SystemDeviceEnum, NULL, CLSCTX_INPROC, IID_ICreateDevEnum, (void **)&pDevEnum); IEnumMoniker *pClassEnum = NULL; pDevEnum->CreateClassEnumerator(CLSID_VideoInputDeviceCategory, &pClassEnum, 0); ULONG cFetched; IMoniker *pMoniker = NULL; IBaseFilter *pSrc[NCAM]; int i; for(i=0; i < NCAM; i++){ if (pClassEnum->Next(1, &pMoniker, &cFetched) == S_OK){ // NCAM番目のモニカまでフィルタオブジェクトにバインドする pMoniker->BindToObject(0, 0, IID_IBaseFilter, (void **)&pSrc[i]); pMoniker->Release(); } pGraph->AddFilter(pSrc[i], L"Video Capture"); } pClassEnum->Release(); pDevEnum->Release(); // 3. キャプチャビルダの作成 ICaptureGraphBuilder2 *pBuilder[NCAM]; for(i=0; i < NCAM; i++){ CoCreateInstance(CLSID_CaptureGraphBuilder2, NULL, CLSCTX_INPROC, IID_ICaptureGraphBuilder2, (void **)&pBuilder[i]); pBuilder[i]->SetFiltergraph(pGraph); // ディスプレイへ pBuilder[i]->RenderStream( &PIN_CATEGORY_PREVIEW, &MEDIATYPE_Video, pSrc[i], NULL, NULL ); } // 5. キャプチャ開始 IMediaControl *pMediaControl; pGraph->QueryInterface(IID_IMediaControl, (void **)&pMediaControl); pMediaControl->Run(); MessageBox(NULL, "Click to stop.", "DirectShow", MB_OK); // 6. 終了 for(i=0; i < NCAM; i++){ pSrc[i]->Release(); pBuilder[i]->Release(); } pMediaControl->Release(); RemoveFromRot(dwRegister); pGraph->Release(); CoUninitialize(); return 0; }
フィルタグラフ
GraphEditで表示させると図のようになった.いけとるみたいやん.というわけ で終了.