このDirectShowを使用したプログラミングに関して、わたしが得た情報等を、 書き留めてみました。結構不定期ですが、チョットずつでも更新していきたいです。
* DirectShowを使用して作成したアプリケーションは、このサイトにあるランタイム (DirectX Media 6.0 Run Time)をインストールする必要があります。
無くなっていますね(^_^;)
ここに飛ぶよう になっています。DirectMediaはDirectXに統合される運命の様ですね。
win98のSecond Editionでは、Runtimeをインストールしなくても動作するようですね。 w2000はどうなのだろう?
* ここにランタイムがありました。多分これでOKでしょう 2000/05/08確認
* さらにさらにDirectShow (DirectX Media)はPlatform SDKに存在している事が 判明しました。うーん、これでこのサイトも無駄にならないですむカモ(^^ゞ
ダウンロードはここから出来ます。 しかし、巨大だ。一応選択ダウンロードできますが、みなさん気をつけて下さいね(^^ゞ
DirectDrawに関してわたしは何も知らないので、大きいことは言えないが、 これらDirectDraw、DirectX関連は生き残りそうには思える。今ならDirectDrawを 習得した方がいいのだろうが、DirectShowもなかなか面白い技術で、COMがベース にある為、COMを知らないとかなり苦労するが、フィルター・ピンの構成は他の アプリケーションにも応用できそうな考え方で、参考になる
ここで必要なことは、サンプルをコンパイルする時に必要なbox定義およびリンク時に
必要なライブラリです。
特にフィルタを作成する場合にはベースアドレスとエントリポイントが重要です。
また、デバッグ時には上記のファイルには記述されていませんが、define定義に
DEBUG=1を追加することと、ライブラリをSTRMBASE.libではなく、
STRMBASd.libとすることです。
基本的に以上のことをすることで、VC-IDEにてサンプルの動作確認をすることができるでしょう。
(もしできなければお知らせください、BBSにて対応します。
(わたしがわかればですが))
追加 1999/10/15
timeGetTimeというAPIを使用するクラスがあるので、winmm.libもリンクする必要が
ある場合がある。
2000/05/22
VC6に対応したウィザードを作成しました。上記の設定を簡単に行うことができます。
DirectShow開発用ウィザードをご利用ください
このツールで具体的に何ができるかというと、実際にフィルタをつなぎ合わせてどのようなことができるか、ビジュアル的に確認できるということです。
起動してまず何をするかというと、GraphメニューのInsertFilterを選択することです。するとダイアログが現れ、カテゴリ毎のフィルタ一覧が表示されます。
もちろんキャプチャ時に必ず必要なVideoCaptureSourceなどもあります。オーディオをキャプチャする場合にはもちろんAudioCaptureSourceを使用するのだが、今回はVideoCaptureSourceを使用します。VFW対応のキャプチャ機器が、接続されていれば、そのドライバ名が表示されるはずです。
まあ、この辺りは適当にいじってもらうとして、特にVideoCompressorというカテゴリに注目してほしい。カテゴリの+印をクリックすると、そのカテゴリ内のフィルタが表示されるが、やはりみなれたcodecであるMS-RLEやIndeo Videoなどの名前もある。
これらのいずれかを選択して、うまくAVIファイルを作成できれば感じがつかめるだろうと考え、試行錯誤の上、下記のような構成でなんとか期待どおりのAVIファイルを作成することができた。
キャプチャソース -> Indeo Video 5.04 Compression Filter -> AVI Mux -> File writerやはりねらいどおりVideoCompressorというカテゴリのフィルタが必要で、Render時にこのフィルタを追加しておく必要がありそうだということが認識できました。
それではどのような手順で進めたか、どうすれば実現できたかを、説明してみよう。
とにかく、Compressorと関係があるものを見つけることが必要だと思い、それにはまずGUIDを検索するのが一番だと考えました。
X:/DXMedia/include/uuids.hというファイルにはDirectShowで使用するGUIDが記述されているので、まずはこのファイルに対して、Compというキーワードで検索するとCLSID_VideoCompressorCategoryというGUIDが見つかった。いきなりビンゴです。これを使用すればCompressorの一覧が取得できそうな予感。しかも名前からすると、デバイス一覧を取得した方法と同じ流れで取得できるかもしれないと考えられます。
もちろん今度はDirectShowに付属のヘルプに対して上記のGUIDを検索します。しかし、これでは使い方は出ていません。
次はサンプルです。X:/DXMedia/samples/multimedia/dshow/src以下を検索してみると一つだけヒットしました。
X:/DXMedia/samples/multimedia/dshow/src/vidclip/setting.cppです。やはり考えていたとおり、デバイス名一覧を取得した方法とまったく同じように、Compressor一覧も取得できることがわかりました。なるほど、GraphEditのフィルタ挿入ダイアログに出ている一覧は、全てこの方法を使用して取得しているということがわかった(^o^)。上記のuuids.hの中でCategoryを含むGUIDは以下の10個。
CLSID_VideoInputDeviceCategory, CLSID_LegacyAmFilterCategory, CLSID_VideoCompressorCategory, CLSID_AudioCompressorCategory, CLSID_AudioInputDeviceCategory, CLSID_AudioRendererCategory, CLSID_MidiRendererCategory, CLSID_TransmitCategory, CLSID_DeviceControlCategory, CLSID_DVDHWDecodersCategory,GraphEditのダイアログのカテゴリも10個である。
また、VidClipサンプルソースを見ると、IMonikerを保存して、利用しているようなので、デバイス名やCompressor名だけでなく、IMonikerも含めて保存するようにした。
struct DevInfo
{
std::string name; // Device name
CComPtr <IMoniker> pM; // IMoniker pointer
};
typedef std::vector<DevInfo> DevInfos;
ただ、わたしが引っかかってしまった罠があった。
そのため、下準備としてキャプチャ時に使用しているフィルタや、どのピンがどのフィルタに接続されているかを確認しようと考えた。
そこで使用したのが、DisConnectAllメソッドで、これはAmCapサンプルのTearDownGraph関数に相当するものである。この機能は、接続されているピンを切り離すということを行っており、このときにフィルタ情報や、ピン情報を取得すれば、簡単に求める情報を得られると考えた。
以下のロジックにて行う。
FILTER_INFO finfo;
hr=pininfo.pFilter->QueryFilterInfo(&finfo);
if(SUCCEEDED(hr)) {
finfo.pGraph->Release(); // <= これを忘れるとひどい目にあう
TRACE("filter:%s %s",
Helper::wstr2mstr(finfo.achName),
( (pininfo.dir==PINDIR_INPUT)?"<-":"->" ) );
}
TRACE(" pin:%s\n",Helper::wstr2mstr(pininfo.achName));
それにしても、この4行目のロジックが必要だとはどこにも書いていない。
まさかAddRefされているとは思わずに、使用していたため、本当に困った。
まずRenderStreamをする。これに関してはAmCapでも同じように行っているので説明は不要かと思うが簡単に言うと、必要なフィルタを接続するというものである。
その後に、以前調査したときに圧縮フィルタがどのように接続されるかは、わかっており、キャプチャソースとAVI Muxフィルタの間に接続してやれば良い。
キャプチャソース -> Indeo Video 5.04 Compression Filter -> AVI Mux -> File writerまずAVI MuxフィルタをIGraphBuilder(IFilterGraph)から取り出し、そのフィルタの入力ピンを取得する。
ICaptureGraphBuilder->GetFiltergraph ==> IGraphBuilder IGraphBuilder->FindFirstByName ==> IBaseFilter(Mux filter) IBaseFilter->FindPin ==> IPin -- (A) さらにそのピンに接続されているピンを取得 IPin->ConnectedTo ==> IPin -- (B)上記のピンを切断し、圧縮フィルタの入出力ピンに(A),(B)のピンをそれぞれ接続してあげれば良い。
IGraphBuilder->Disconnect IGraphBuilder->Connect
まず静止画を取りこむ方法を考えてみます。
考え方としては、プレビュー時に取りこみボタンを押し、そのタイミングでプレビュー
を一時停止させておき、画像データを取得、その後プレビューを再開するという感じ
でしょうか。
この流れの中で一番の問題は、画像データを取得すると言うところですね。
どの様に画像を取り込めば良いのでしょうか。
今回はわたしが散々苦労して見つけ出した方法を使用します。と言っても、
別の方法があるのかはわたしにはわかりませんが(^_^;)。
まず、動画というのは、パタパタアニメのように一枚ずつその瞬間の画面が
集まったもので、一枚に対応するものを一フレームと言います。
(一秒あたりのフレーム数のことをフレームレートと言います)
さて、静止画を取り込むと言うのはその瞬間のフレームを取り出すと言うことですが、
DirectShowではフレームデータをIMediaSampleというインターフェイスで管理
しているようです。
このことは、DirectShowのサンプル(特にフィルタサンプル)を見ていて大体見当を
付けました。
さて、ここで話は変わりまして、キャプチャするときにレンダーされるフィルタの中で、
実際にフレームを取り出すインターフェイスなりが必ず取得できるかどうかは、
わかるのでしょうか?
後ほどフレームを取得する方法は述べますが、私が考えついた方法では必ず
レンダーされるフィルタが取得できる条件を満たしているとは思えませんでした。
ということで、こちらの都合のよい条件を満足するフィルタを作成する事にしました。
フィルタを作成する方法が大きく2通りあります。それは、COMとして作成するか、
アプリケーション内部に作成するかです。
ちょっとわかりにくい表現で申し訳ありませんが、どちらの場合もわかってしまえば、
それほど難しいことはありませんし、作成するのも容易です。
DirectShowのサンプルはほとんどが COM として作成する場合に関してです。
ですから、恐らく最初に作成する場合には COM として作成したほうが
わかりやすいかもしれません。
少なくとも私の場合はそうだったのですが、実はアプリケーション内部に
組み込んでしまったほうが、自由度が高く、作成も容易であることが
今回わかりました。(^^ゞ
このあたりはトレードオフの関係があるのですが、COMにしたほうがモジュール化 されている分汎用的に使える反面、拡張するのに面倒(COMインターフェイスを 定義したり)である。アプリ内部の場合には、通常のクラスのように使用でき、 容易に使用できる反面、COMのように汎用的に使用することはできない。 といったところでしょうか。
今回は、作成が容易であることと、サンプル自体が少ないことを考えて、 アプリ内部に作成する方法を取ってみました。
アプリ内部に自由度の高いフィルタを作成した場合には、苦労してDirectShowの
枠組みであるインターフェイスを使用する必要はなく、自由にメソッドを作成して
必要なデータを取得することができるように作成することができます。極端な話、
上記で見つけ出した
IMediaSampleを使用する必要もないのですが、そうすると、COM版にしたときに
その方法を使用できなくなります。まあ、それようにインターフェイスを作成して
あげればよいのですが、今回は止めます。
既存の枠組みであるDirectShowのインターフェイスを利用してフレームデータを
取得できるようにしてみます。
それでは、上記のIMediaSampleを取得するにはどのようにすれば良いのでしょうか。
ドキュメントからIMediaSampleを取得できるメソッドを片っ端から探してみました。
これは結構大変な作業でした。
その成果として、IMemAllocator::GetBufferメソッドはIMediaSampleを取得できる
ことがわかった。(次はIMemAllocatorか(-_-;) )
さてIMemAllocatorを検索すると、CBaseInputPinおよびCBaseOutputPinが最初の
ほうで見つかった。
ヘルプを見ると、とりあえずどちらもIMemAllocatorを取得できそうです。
それぞれよく見ると大きな違いがあります。それは、上記2つのクラスの派生元
クラスが違うのです。CBaseInputPinはIMemInputPin,CBaseOutputPinはIPinから
CBasePinを介して派生されています。
IPinからはIMemAllocatorを取得できるインターフェイスはありません。
IMemInputPinからならば取得できるようです。IMemInputPin::GetAllocator
今回作成するフィルタは入力ピンとしてIMemInputPinを持っていなくてはならない。
それ以外には必要最小限の機能を持ったフィルタを作成する。
上記の条件を満たすようにフィルタを作成すればよいのだが、DirectShowには便利な
クラスが提供されている。CTransformFilterである。これは入力ピンと出力ピンを
一つずつ持ち、CBaseInputPinとCBaseOutputPinである。ただし、5つのメソッドを
オーバーライドする必要があると、ヘルプには記載されている。
実装に関しては、とりあえずサンプルを探してみることにした。CTransformFilterを キーにサンプルを探してみると、ありました。contrastやezrgb24などなど。
とりあえず、このフィルタをCStillFilterというクラス名にして、下記のように定義する。
class CStillFilter : public CTransformFilter
{
..
};
ezrgb24を参考にして、下記の5つのメソッドをインプリメントしてみましょう。
さて、ここまでくれば準備はOKです。後は、作成したフィルタのインスタンスを作成して、RenderStreamをする前に、AddFilterで登録してあげます。
インスタンスを作成するときには、ちょっとわからない部分もあった。
クラスのコンストラクタは、COM化するのに比べ、簡単にすることができたのだ。
CTransformFilterのコンストラクタは、以下のようである。
CTransformFilter(TCHAR *, LPUNKNOWN, REFCLSID clsid);
一番目と三番目のパラメータは、タイプを見れば大体想像がつきますね。でも、二番目のパラメータはいったい何を渡せば言いのでしょう?
そもそもLPUNKNOWNとはなあに? そう、IUnknownのポインタだって。
実際上記で参照したezrgb24というサンプルを見るとCreateInstanceで指定されたIUnknownポインタを渡していた。
次にIClassFactory::CreateInstanceをヘルプで見ると答えが見つかった。
[in] If the object is being created as part of an aggregate, pointer to the controlling IUnknown interface of the aggregate. Otherwise, pUnkOuter must be NULL.簡単に言うと、今回の場合NULLでいいってこと。
プレビュー中のみ静止画をキャプチャできるように、プレビューフィルタをレンダーするときに、以下のような感じで、自作フィルタの登録完成です。
このときに、IMemAllocatorインターフェイスを取得しておく。
さて、MFCcap(AMcap MFC化計画)も初志をあっさり捻じ曲げ、自分のやりたい部分だけをやってみて終わりとなりました。アプリ自体はまったく安定性がなく、こちらが想定していない動作をさせるとあっさり落ちるという面がありますが、DirectShowを使用したアプリケーションの作成という面で、何らかのヒントとなれば幸いですm(_"_)m
最初に書いたとおり、DirectShowに関してはまだまだ情報が少ないと感じているが、サンプルを含め、combase.h等のヘッダファイルに簡単な解説があり、これらを眺めるだけでも有用な情報が得られるし、自分の理解も深まると思う。
修正の肝は、現在のデバイス状態をチェックし、ビデオフォーマットの圧縮されるのかを確認する。 VIDEOINFOHEADER:biCompression
非圧縮と判断すると接続し、一方フィルタの方は、RGB24だけではなく、メディアタイプがビデオならば接続できる様にした。
これにより、キャプチャできるかどうかを判断し、出来なければそれなりに、出来る場合は今まで通りビットマップに取り込む事が可能となった。
ソースと、バイナリ(76,927バイト)
質問はここのBBSへお願いします。
最近はMLの方がよろしいかも。MLの情報はTOPからどうぞ