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で表示させると図のようになった.いけとるみたいやん.というわけ で終了.