DirectShow视频捕获

本文档比较适合初学者,写的比较详细,水平有限,翻译得不好还请见谅。

目标是只要不会误导大家就可以了,我可不想误人子弟,哈哈!

  1. DirectShow捕捉程序的准备
    1. Graph Edit 概述
    2. COM 入门知识
      • http://www.microsoft.com/japan/com/compapers.asp
      • C Magazine 2000年11月号
      • Help(DirectShow的使用方法 → DirectShowe 文件夹开发)
    3. DirectShowのHellow World
  2. DirectShow 样本程序
    1. StillCap (Still, Video的捕捉例程)
      C:/mssdk/samples/Multimedia/DirectShow/Editing/StillCap
    2. AMCap (Video的捕捉,Camera的参数控制)
      C:/mssdk/samples/Multimedia/DirectShow/Capture/AMCap
  3. DirectShow视频捕捉
    ICaptureGraphBuilder2とIFileSinkFilter
    1. 基本时序
    2. 设备枚举  
    3. 源代码(videocap_console)
  4. Filter属性取得
    1. Filter枚举  
    2. Filter属性页表示 ISpecifyPropertyPages
  5. 通过DirectShow方法进行媒体流捕捉(1: GetCurrentBuffer編)  
    视频流中取得一张图片 ISampleGrabber + GetCurrentBuffer
    1. 基本时序
    2. PIN枚举  
    3. 源代码(stillcap_console1)
  6. 通过DirectShow方法进行媒体流捕捉(2:ISampleGrabber 編)
    视频流中取得一张图片 ISampleGrabber + ISampleGrabberCB
    1. 基本时序
    2. ISampleGrabber接口实现
    3. 源代码(stillcap_console2)
  7. 视频设备控制  
    1. 基本流程  
    2. 源代码(cameracontrol)
  8. 其它 
    1. 通过GraphEdit 实现自己的视频Filter
    2. 关闭控制窗口
  9. 多个视频设备环境下的捕捉
    1. 基本流程  
    2. 源代码(two_cameras)
    3. 图像Filter  
  10. MDI的GUI(省略)

(注)ドキュメントの Help(...)は,DirectX 8.0 日本語へルプ をデフォルトのパス(C:/mssdk/doc/DX8JHelp/directx8_c.chm)にインストールしたものとして,ローカルディスクにリンクを張っている.

ページの先頭へ

DirectShow捕捉程序的准备

Graph Edit概述

  1. 启动 →程序 → Microsoft DirectX 8 SDK → DirectX Utilities → Graph Edit运行.
  2. 通过Graph → Insert Filters进行Filter选择
    例如 
    • Video Capture Sources
    • DirectShow Filter → Video Renderer
  3. Filter的PIN连接(マウスで入力ピンから出力ピンへドラッグ)
    必要なフィルタはある程度自動的に挿入される(Graph → Connect Intelligent のチェックを入れておく).
  4. 再生ボタンを押してグラフをプレイしてみる
詳しい遊び方
Help( DirectShow の使い方 → GraphEditによるグラフ構築のシミュレーション )

COMの入门知识

まぁ,要はインタフェースが重要らしい
dll入れ替えられるんやね
CoCreateInstanceでインスタンスつくって,インタフェースのポインタは QueryInterfaceで取得する
GUID(CLSID, IID)でインタフェースや実装クラスを識別する
こんなんで,ええか?
CComPtr ? ATL ? はあとまわし


DirectShowのHellow World

DirectXのへルプにあるDirectShowのファーストステップを参考に,まずは DirectShow のHellow World と呼ばれる サンプルコードを書いてみる.

  1. Win32コンソールアプリケーションとしてプロジェクトを作成
  2. プロジェクト→設定→リンクに,strmiids.libを追加する.
  3. dshow.hをインクルードする(あらかじめ設定→C/C++→プロジェクト オプションに,インクルードファイルのパス(/I"C:/mssdk/include")が通っているのを確認)
  4. COMの初期化と終了処理のために, CoInitialize(NULL), CoUninitialize()を始めと終わりに書く.
  5. 最後に参照カウンタをデクリメントするために (ポインタ)->Release を忘れずに.
#include 
#include

using namespace std;

void 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 0;
}

あと,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 
#include

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つの方法があるらしい.

  1. ISampleGrabber::SetBufferSample(TRUE)にし,ISampleGrabber::GetCurrentBuffer()を明示的に呼ぶ
  2. 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(...)

サンプルグラバの接続は・・・次のようなやり方があるらしいが.

  1. IGraphBuilder::RenderFile (メディアタイプにしたがってグラフマネー ジャがサンプルグラバを接続する)
  2. IGraphBuilder::Connect (ピンで指定)
  3. ICaptureGraphBuilder2

1と3は,ある程度自動で構築するみたいやけど,今はどこに接続するかを理解するのが大切なので,ここでは,2のピンで指定してサンプルグラバフィルタを挿入するやり方をとる.フィルタを追加した際のピンの接続に関しては,Help ( DirectShow について → フィルタグラフの構築 → 一般的なグラフの構築 ) 参照.

(注) ISampleGrabberを用いるには, #includeをインクルードする必要がある.

 

ピンの列挙

まず,指定した方向のピンを得るための関数をつくる. 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 を加える.

  // 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フィルタの関係は,おそらく こんな感じやろ(想像図)
  • Help(DirectShow について → ハードウェア デバイスがフィルタグラフに参加する方法 )参照
  • Help(DirectShow の使い方 → ビデオキャプチャ → WDMビデオキャプチャについて )参照

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

ページの先頭へ

你可能感兴趣的:(Technique)