■DirectShowによる各種メディア形式の再生


DirectShowでは、様々なメディア形式を再生できます。
現在はDirectXに統合されていますが、以前はActiveMovieとかそんな呼び名だったようです。

ちなみに、Windows 3.0で実装され、現在のWindowsでもサポートされているVideo for Windowsは、 Windows XP以降のWindowsでは、サポートしないと2001年くらいに発表されていました。
おそらくAVI〜()系 APIが実装されなくなる(互換性のために定義だけはされている)日も来るのだと思いますが、 今後は API直接呼び出しといった古いプログラミング スタイルは消えていく運命にあるのかも知れません。

ここは、私がDirectShowについて学んでいったことを少しずつ書き留めておくページです。
ページ立ち上げは2001年の9月ですが、その後配属により仕事が忙しくなってしまったこともあり、 更新されていませんでしたが、意外にアクセス数があるので(といっても月に数十件程度ですが。。。) ページを書き直しました。

DirectShowは COM (Component Object Model)を学ぶにも非常に役立つ技術です。
今更 COM?って声も聞こえてきそうですが、COMの技術は今後も暫くは継承されていくと思います。
また、.NETが主流になっても、.NETからCOMを呼び出す方法はありますし、逆も可能です。



1.ファイルの再生 (1) Sep. 10, 2001
#include <windows.h>
#include <DShow.h>
#pragma comment( lib, "strmBasd.lib" )

VOID sample( HWND hWnd )
{
	HRESULT		hRslt;
	IGraphBuilder	*pGrph;
	IMediaControl	*pMdaCtrl;

	// COMライブラリの初期化
	hRslt = CoInitialize( NULL );
	if( FAILED(hRslt) ){
	    MessageBox( hWnd, "COMライブラリ初期化失敗", "Error", MB_OK | MB_ICONSTOP );
	    return;
	}

	// フィルタ グラフ マネージャの生成
	CoCreateInstance( CLSID_FilterGraph, NULL, CLSCTX_INPROC, IID_IGraphBuilder, (LPVOID *)&pGrph );

	// フィルタ グラフ マネージャに公開されている IMediaControlインターフェースを取得する
	hRslt = pGrph->QueryInterface( IID_IMediaControl, (void **)&pMdaCtrl );

	// 再生するファイルを指定して、フィルタ グラフを構築する
	pGrph->RenderFile( L"test.mpg", NULL );

	// ストリームの再生(グラフの実行)
	pMdaCtrl->Run();

	MessageBox( hWnd, "OK", "Status", MB_OK | MB_ICONINFORMATION );

	//解放
	pMdaCtrl->Stop();
	pMdaCtrl->Release();
	pGrph->Release();
	CoUninitialize();
}
解説ってほどのものでもないのですが、これの大本のソースコードは マイクロソフト社 が提供するサンプルのものです。
そこに私なりにコメントを入れました。

この関数を呼び出すと、test.mpgを別ウィンドウで勝手に再生します。とても簡単ですね。
ちなみに再生できるファイル形式は、Windows Media Playerが再生できるものと全く一緒です。 mp3とか、当然 aviも再生できます。
一つ注意ですが、COMの初期化以外の部分でエラー処理を省いていますので、実際にはエラー処理を実装して下さい。



2.マルチバイト文字 Sep. 26, 2001 / Nov. 2, 2002
前回の更新から結構時間が経ってしまいました。とりあえず、記憶喪失になってしまった自分が後で見ても 理解できるように書いていくので、今回のテーマは表題の通りです。

ファイル名を指定してレンダリングを行うためのフィルタ グラフを構築するメソッドであるIGraphBuilder::RenderFile()ですが、 この関数の最初の引数はLPCWSTR lpwstrFileと定義されており、つまり、定数ワイド文字列へのポインタなのです。

char型へのポインタである通常の文字列をこの引数に渡してもこの関数は理解できません。 ワイド文字列へマッピングしてやる必要があります。

そのためのAPIとして、MultiByteToWideChar()というものが 用意されています。そのまんまですね^^;)。 この関数は引数がいっぱいあって分かりにくいのですが、とりあえず、

MultiByteToWideChar( CP_ACP, 0, szFileName, -1, szFileNameWide, 260 );


とすれば、OKです。szFileName(char型)が通常のバイト文字。 szFileNameWide(WCHAR型)がワイド文字列です。 最後の260は、szFileNameWideのバッファサイズです。

今回は対しておもしろくないテーマでした(笑)。



3.DirectShow について Nov. 3, 2002
さて、前項までの内容に DirectShowというものについては何も解説がありませんでした。
技術的な資料は、マイクロソフト社のページ を見てもらうとして、ここでは手っ取り早くDirectShowを使いたい人を対象に しています。 というか、元々このページは私が勉強した成果を記録していくものなので、その辺はご了承願います。

DirectShowは COMベースなため、オブジェクト指向の概念が理解できないと非常に取っつきにくいものであることは確かです。 最近の言語は皆、オブジェクト指向であり、JAVAやC#を学ぼうとしている人やそもそもC++でプログラミング出来る人には 覚えなければいけないことは少ないと思われます。
(DirectShowはCOMコンポーネントとして実装されているため、C++などのオブジェクト指向言語からしか呼び出せないように思えますが、 実は、C言語からメソッドをコールすることもできます。私も昔 C言語でオブジェクト指向を と題してプログラミングの研究課題を提出したことがありますが(笑) それと同じような方法です まぁ、要するに構造体で無理矢理オブジェクト指向をやるんですが。。。。)

まずは、COMですが、これもマイクロソフト社のページ で少し勉強しましょう。 の3つがありますが、 かなり分かりやすく解説されていて、勉強になると思います。 別に自分でCOMコンポーネントを作れるようになる必要はないのですが、知識は役に立つので見ておくといいかも、ですね。
(全部読むのに、1、2時間もあれば十分です。)

いよいよ、DirectShowですが、まず最初に躓くのは「言葉」だと思います。私はそうでした。
フィルタとか、ピンとか、フィルタ グラフだとか、わけのわからない言葉が沢山出てきます。
この言葉の解説も全部、マイクロソフト社にあります。 (マイクロソフト社は技術資料は大量に公開しているんです。 ソースコードは公開していないので、隠しメソッドや隠しWindows APIなんかもいっぱいありますが、 それでも最低限公開すべきものは公開しています。)

ここでは、簡単にこのセクションで使う言葉をまとめます。

フィルタ (Filter)
DirectShowを構成する基本要素。単純に入力があったら出力をするブラックボックス。COMコンポーネントとして実装される。

フィルタ グラフ (Filter Graph)
複数のフィルタの出力と入力を繋いだフィルタの集合のこと。

フィルタ グラフ マネージャ (Filter Graph Manager)
個々のフィルタを接続してできたフィルタ グラフ全体を管理するCOMコンポーネントのこと。フィルタを通過していくデータを制御する。

はい、ここまでで少しは DirectShowがどのように構成されているか見えてきましたでしょうか?
DirectShow自身は個々のフィルタから構成されたシステムなのです。そのコンポーネントを管理する上位のコンポーネントに フィルタ グラフ マネージャなどがあるというわけです。 取っ付きにくい感じがしますが、一つずつ見ていくとそんなに難しくはないですね。


ではここで、「1.ファイルの再生(1)」のソースコードをもう一度見直して、言葉とコードを一致させましょう。
CoInitialize()は、COMを初期化する APIです。これはDirectShowに限らず、COMを使う上で欠かせないコードです。
次のCoCreateInstance() は何でしょうか。実はこの APIを使って、フィルタ グラフ マネージャのインスタンスを生成するのです。

おっと、意味が分からないと言う声が聞こえてきそうなので簡単に説明しますと、フィルタ グラフ マネージャというのは、フィルタ グラフを管理するCOMコンポーネントでしたよね。このコンポーネントはいくつかのオブジェクトで構成されています。
IGraphBuilderというインターフェースもその1つで、このインターフェース(のインスタンス)によって、フィルタ グラフを 構築するメソッドが提供されるのです。
(インターフェース自身は何も実行コードを持ちません。そのインスタンス(プログラムでは pGrph)はただのポインタであり実態はないということです。 COMはインターフェースとその実装(オブジェクト)が分離されていることに注意して下さい。)
もっと、簡単にいえば、IGraphBuilderのインスタンスに実装コードのポインタを与えてやるということです。

次のIGraphBulder::QueryInterface()メソッドですが、これは、COMのインターフェースなら必ず持つメソッドです。
この場合、フィルタ グラフ マネージャが実装している IGraphBuilderインターフェースに公開された別のインターフェースの インスタンスを取得するために使用しています。こうして、フィルタ グラフを構築していくのです。
では、何のインスタンスかというと、フィルタ グラフを流れるデータを制御するメソッド群 IMediaControlインターフェースの インスタンスです。
IMediaControlインターフェースのオブジェクトは Run()やStop()と言った、ある意味で抽象的であり、ある意味で具体的なメソッドを提供します。

では、次のメソッドに行きましょう。次は、IGraphBulder::RenderFile()です。
これは、再生したいファイル名を指定する具体的なメソッドです。
このメソッドは、指定されたファイルを再生できるビデオ レンダリング フィルタ(通称、レンダラ)と言われるフィルタをフィルタ グラフに追加し、 必要ならそのファイルをデコードできるフィルタ等を自動的に見つけ出し、同じようにグラフに追加してくれます。
第1引数はワイド文字列で指定します。(「2.マルチバイト文字」参照)
第2引数は将来のために予約されているので 常にNULLにします。

次の IMediaControl::Run()が、フィルタ グラフを実際に実行するメソッドです。
このメソッドを実行するまでに、フィルタ グラフはファイル ソース (Async) フィルタAVI スプリッタ フィルタ など、沢山のフィルタで構成されていますが、実際にそのフィルタにファイルからデータを読み取り、画面等に映像をレンダリング するためにデータを流す合図がこのメソッドなのです。
IMediaControl::Stop()で、データの流れを止めることも出来ます。
サンプルコードでは、ここでプログラムを一旦停止するためにMessageBox()を入れていますが、 IMediaEventインターフェースのインスタンスを使えば、再生が終わるまで待つなどの処理ができますが、それらは今後のサンプルコードで 実験していきましょう。

最後に、使ったものは片づけなくてはいけません。Release()メソッドはQueryInterface()と同じで全てのCOMコンポーネントが 持つメソッド(基底クラス IUnknownで定義)です。内部的に所持している参照カウンタを減らし、0になった時点でこのオブジェクトは メモリから消えます。

さて、かなり長文となってしまいましたが、DirectShowの基本は理解してもらえたでしょうか。
次回以降もMicrosoft社のサンプルコードに倣って詳しい解説を交えながら掲載していく予定です。



4.ファイルの再生(2) Nov. 4, 2002
さて今回は、「1.ファイルの再生(1)」のソースコードを見直し 自分のウィンドウの中でムービーを再生する方法を検討します。
第3回の最後にも書いたとおり、Microsoft社の DirectShowファーストステップガイド と同じ構成ですので、そちらの方が確実な情報です。
ここでは、実際に動くバイナリとソースを掲載し、実験を行うためにあります。

第1回のファイルの再生方法では、作成したウィンドウとは無関係に新しいウィンドウが作成され そのウィンドウの中でムービーが再生されていました。 これを自分作ったウィンドウで描画させるにはどうすればいいのでしょうか。
これには、レンダラがサポートしているIVideoWindowインターフェースを使います。
レンダラ(ビデオ レンダリング フィルタ)は前回の解説に出てきた用語で、フィルタの1つです。 レンダラは既に圧縮解除されたデータを受け取り、どこかのウィンドウに描画するフィルタですが、この どこか は指定しなければ新しいウィンドウを作って、そこにレンダリングします。

このIMediaVideoインターフェースは、フィルタ グラフ マネージャが公開します。
(サポートしているのは レンダラなのですが、このフィルタから直接ポインタを取得してはいけないと、DirectX のドキュメントに記載されています。理由は、必ずしも全てのレンダラがこのインターフェースを公開しているわけではないから のようです。)
この、公開するとは、QueryInterface()メソッドでオブジェクトの実行コードへのポインタを取得できるという 意味です。
hResult = pGrph->QueryInterface( IID_IVideoWindow, (LPVOID *)&pVdoWin );
で、IVideoWindowインターフェースのオブジェクトを取得できますね。(pVdoWinはIVideoWindowへのポインタ)

プログラマが指定するウィンドウ上でムービーを再生させたい場合には、IVideoWinodow::put_Owner() メソッドで、オーナーとなるウィンドウハンドルを指定し、IVideoWinodow::put_WindowStyle() で、再生されるウィンドウを子ウィンドウにして親ウィンドウに貼り付けてやる必要があります。 またその貼り付ける位置は、IVideoWinodow::SetWindowPosition() メソッドで指定します。
実際のコードと、引数については、先程述べたファーストステップガイドを見て下さい。

では、ここまでのサンプルを示します。以降のセクションでもこのアプリに肉付けしていく形で作っていき、 当面の目標として、再生、停止、ポーズ、巻き戻し、早送り、コマ送りなどのビューアとしての機能を盛り込んで行きたいと考えています。 ここまででの知識でも、再生と停止(たとえ、再生が終わっても終わったことを知らされないから、自分で停止しないといけないのですが(笑)) はできます。意外にあっさりとできてしまう DirectShowはなかなのものです。








[HOME] [INDEX]

since Nov. 5, 2002




このページで記載されている社名/商品名などは一般に各社の商標または登録商標であり、
このページではTMや®マークは省略しています。

このページは情報の提供を目的としたページであり、その情報の正確性等を保証するものではありません。
従いまして、このページ内の情報を元に行った行為については当方は一切の責任を負いませんのでご承知下さい。

Copyright ©2002 by Tomonori Kobayashi
All Rights Reserved.

tomo@peace.ne.jp / tomo@limber.jp