VB2005でDirectShowを扱うときの基本操作をまとめて説明したいと思います。
なお重複しちゃいますので基本的な用語については説明していません。
初めての方はお手数ですがVB6+DirectShowのページを参照してください。
本当に申し訳ないのですが、
フィルタ
フィルタグラフ
フィルタグラフマネージャ
ピン
GRAPHEDIT
ぐらいは何のことかわかっているものとして説明しています。
(VB6+DirectShowのページを参照して頂ければわかると思います)
DirectShowの各インターフェースや定数、
及び、COM関係の関数やインタフェース類は定義は省略してます。(ながくなるんで)
定義は、下記からダウンロードできるDirectShowDefine.vbで行っています。
(私が使っているモジュールです。自由に流用して下さい。間違いがあったら教えてね)
DirectShowDefine.vb(DirectShow関係の定義モジュール)
以降のコードを試すときはこのモジュールをプロジェクトに入れてください。
使い方
@上のDirectShowDefine.vbをダウンロードする。
A新たなプロジェクトを作成する。(Windowsアプリケーションとか)
Bメニュー「プロジェクト」−「既存項目の追加」で
ダウンロードしたDirectShowDefine.vbを選択する。
Cサンプルコードをコピペ
サンプルファイルのパスとかはこちらの環境になっているので
自分の環境に合わせてください。
D実行
なお、以下サンプルコードにはエラー処理つーもんが完璧に欠如しています。
まずインターフェースの定義で<PreserveSig()>をつけてますのでメソッドでエラーが発生しても停止しません。
エラーをTry〜Catchでトラップしたい場合は外したほうがいいかもしれません。
この辺は好みでお願いします。
「1.動画の再生」ですでにやってますが、
もう一度フィルタグラフマネージャの作成とIGraphBuilderインターフェースの取得方法について説明します。
Imports System Imports System.Runtime.InteropServices Public Class Form1 Private mGrp As IGraphBuilder Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click 'クラスID準備 Dim FilterGraphManagerClassID As Guid FilterGraphManagerClassID = New Guid("{E436EBB3-524F-11CE-9F53-0020AF0BA770}") 'タイプの取得 Dim FilterGraphManagerType As Type FilterGraphManagerType = Type.GetTypeFromCLSID(FilterGraphManagerClassID) 'フィルタグラフマネージャのインスタンス作成 Dim FilterGraphManagerObject As Object FilterGraphManagerObject = Activator.CreateInstance(FilterGraphManagerType) 'IGraphBuilderインターフェースの取得 mGrp = CType(FilterGraphManagerObject, IGraphBuilder) End Sub End Class |
この方法はActivatorクラスのCreateInstanceメソッドを使用してインスタンスを作成しています。
手順としては、
@フィルタグラフマネージャのクラスIDを用意
Aフィルタグラフマネージャの型(タイプ)を定義
BActivatorでインスタンス作成
CCTypeでIGraphuBuilderインターフェースを取得
となります。
また、フィルタグラフマネージャのクラスIDの文字列は
DirectShowDefine.vb内でGUIDString.CLSID_FilterGraphとして定義しています。
「1.動画の再生」では明記していませんが、いくつもの動画を再生する場合、
フィルタグラフマネージャのインスタンスを明示的に破棄した方がいいです。
'フィルタグラフマネージャの破棄 Marshal.ReleaseComObject(mGrp) mGrp = nothing |
自動的に破棄してくれるのを待つよりも、
もう使わないグラフはMarshal.ReleaseComObjectメソッドを使って明示的に破棄しましょう。
でないと再生ウィンドウがいつまでも残ったりします。
動画ファイルからグラフを作成する、つまり、動画を再生する準備は下記の様に
IGraphBuilderインターフェースのRenderFileメソッドを使います。
'動画ファイルからグラフ作成 mGrp.RenderFile("E:\media\src320x240.avi", "") |
第二引数は空文字列""でOKです。
グラフを再生/停止するには、IMediaControlインターフェースを使用します。
'IMediaControlインターフェースの取得 Dim ctl As IMediaControl ctl = CType(mGrp, IMediaControl) '再生 ctl.Run() '停止 ctl.Run() '一時停止 ctl.Pause() 'リフレッシュ後停止 ctl.StopWhenReady() |
Run/Stop/Pauseは動作がわかりやすいと思いますが、
StopWhenReadyは若干わかりにくいかもしれません。
これはPause後にStop状態にする操作と考えればいいかもしれません。
これが必要になるのは停止中に再生位置を変更したときです。
グラフが停止している状態ではストリームが流れませんので
現在時刻(再生カレント位置)を変更しても表示はかわりません。
このため、一旦Pause状態にしてストリームにデータを流し
その後に停止状態にする必要があります。
StopWhenReadyはこの操作を自動的に(かつ非同期的に)やってくれるメソッドです。
VB6の時はDLLを作ってやる方法を紹介してましたが、
VB2005では便利なクラスがあるのでDLLなどを使わないでも
カテゴリ毎のフィルタを列挙することができます。
Private Sub Button6_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button6.Click 'システムデバイス列挙子のタイプ準備 Dim devenumtype As Type devenumtype = Type.GetTypeFromCLSID(New Guid(GUIDString.CLSID_SystemDeviceEnum)) 'システムデバイス列挙子のインスタンス作成 Dim devenumobj As Object devenumobj = Activator.CreateInstance(devenumtype) 'ICreateDevEnumインターフェース取得 Dim devenum As ICreateDevEnum devenum = CType(devenumobj, ICreateDevEnum) devenumobj = Nothing 'EnumMonikerの作成 Dim emon As ComTypes.IEnumMoniker = Nothing devenum.CreateClassEnumerator(New Guid(GUIDString.FilterCategory.CLSID_VideoInputDeviceCategory), emon, 0) '列挙 Dim mon(0) As ComTypes.IMoniker Dim fc As IntPtr Do While emon.Next(1, mon, fc) = 0 '列挙が失敗するまでループ 'プロパティバッグへのバインド Dim propbagobj As Object = Nothing mon(0).BindToStorage(Nothing, Nothing, New Guid(GUIDString.Interface.IID_IPropertyBag), propbagobj) Dim propbag As IPropertyBag propbag = CType(propbagobj, IPropertyBag) propbagobj = Nothing '名称取得 Dim fnameobj As Object = Nothing, fname As String propbag.Read("FriendlyName", fnameobj, 0) fname = CStr(fnameobj) 'クラスID取得 Dim cidobj As Object = Nothing, cid As String propbag.Read("CLSID", cidobj, 0) cid = CStr(cidobj) '表示 System.Console.WriteLine(cid.ToString + ":" + fname) '後片付け Marshal.ReleaseComObject(mon(0)) mon(0) = Nothing Marshal.ReleaseComObject(propbag) propbag = Nothing Loop '列挙終了 Marshal.ReleaseComObject(emon) emon = Nothing Marshal.ReleaseComObject(devenum) devenum = Nothing End Sub |
ちょっと長くて面倒かもしれませんが、一応これでカテゴリ内のフィルタ名とクラスIDを取得できます。
(このサンプルプログラムではコンソールにフィルタ名称とクラスIDを出力しています。)
「EnumMonikerの作成」のところで、CreateClassEnumeratorメソッドに与えているクラスIDがカテゴリのクラスIDになります。
これは「DirectX C++ヘルプ」の「フィルタ カテゴリ」のところに一覧があります。
(実際の値が定義されているヘッダファイルも記載されてます)
一応DirectShowDefine.vb内でGUIDString.FilterCategoryに定義してあります。
グラフにフィルタを追加するには、フィルタの実態(インスタンス)を作成し
IGraphBuilderインターフェースのAddFilterメソッドでグラフに追加します。
'クラスID準備 Dim FilterClassID As Guid FilterClassID = New Guid(GUIDString.Filter.CLSID_Filewriter) 'タイプの取得 Dim FilterType As Type FilterType = Type.GetTypeFromCLSID(FilterClassID) 'フィルタグラフマネージャのインスタンス作成 Dim FilterObject As Object FilterObject = Activator.CreateInstance(FilterType) 'グラフにフィルタを追加 mGrp.AddFilter(CType(FilterObject, IBaseFilter), "FilteWriter") |
上の例では、フィルタの設定をなにもしていませんが、
目的のフィルタのインターフェースを定義すればそのフィルタ特有のメソッドにアクセスできます。
'IFileSinkFilterインターフェース取得 Dim SinkFilter As IFileSinkFilter SinkFilter = CType(FilterObject, IFileSinkFilter) '出力ファイル名の指定 SinkFilter.SetFileName("c:\iran.avi", Nothing) |
IFileSinkFilterは動画を保存するときのファイル名を指定するインタフェースです。
DirectShowDefine.vb内で定義されています。
GRAPHEDITで開けるGRFファイルとしてグラフを保存するには下記の様にします。
'呼び出し元 Try '保存 SaveGraphFile( mGrp, "d:\iran.grf") Catch ex As Exception '保存失敗 End Try |
Private sub SaveGraphFile(ByRef Grp As IGraphBuilder, ByVal Filename As String) Dim ps As IPersistStream = Nothing Dim grpstr As IStorage = Nothing Dim strm As System.Runtime.InteropServices.ComTypes.IStream = Nothing Try 'IPresistStreamインターフェースの取得 ps = CType(Grp, IPersistStream) 'IStorageオブジェクト作成 Win32API.StgCreateDocfile(Filename, STGM.STGM_CREATE Or STGM.STGM_TRANSACTED Or STGM.STGM_READWRITE Or STGM.STGM_SHARE_EXCLUSIVE, 0, grpstr) 'ストリーム作成 grpstr.CreateStream("ActiveMovieGraph", STGM.STGM_WRITE Or STGM.STGM_CREATE Or STGM.STGM_SHARE_EXCLUSIVE, 0, 0, strm) 'グラフ保存 ps.Save(strm, True) 'ファイルに出力 grpstr.Commit(0) Catch ex As Exception '失敗 Throw Finally '不要となったオブジェクトの開放 If Not ps Is Nothing Then Marshal.ReleaseComObject(ps) ps = Nothing End If If Not grpstr Is Nothing Then Marshal.ReleaseComObject(grpstr) grpstr = Nothing End If If Not strm Is Nothing Then Marshal.ReleaseComObject(strm) strm = Nothing End If End Try End Sub |
そのまんまコピペして使えるように、関数の形にしています。
基本的には「DirectX 9.0 プログラマーズ リファレンス」に記載されているまんまです。
VB2005に合わせてエラー処理を変えたにすぎません。
GRAPHEDITで開けるGRFファイルからグラフを読み込むには下記の様にします。
'呼び出し元 Try '保存 SaveGraphFile( mGrp, "d:\iran.grf") Catch ex As Exception '保存失敗 End Try |
Private Function LoadGraphFilte(ByVal Filename As String) As IGraphBuilder 'クラスID準備 Dim FilterGraphManagerClassID As Guid FilterGraphManagerClassID = New Guid(GUIDString.CLSID_FilterGraph) 'タイプの取得 Dim FilterGraphManagerType As Type FilterGraphManagerType = Type.GetTypeFromCLSID(FilterGraphManagerClassID) 'フィルタグラフマネージャのインスタンス作成 Dim FilterGraphManagerObject As Object FilterGraphManagerObject = Activator.CreateInstance(FilterGraphManagerType) 'IGraphBuilderインターフェースの取得 Dim newgrp As IGraphBuilder newgrp = CType(FilterGraphManagerObject, IGraphBuilder) Dim grpstr As IStorage = Nothing Dim ps As IPersistStream = Nothing Dim strm As System.Runtime.InteropServices.ComTypes.IStream = Nothing Try '(普通の)ファイルであるかチェック If Win32API.StgIsStorageFile(Filename) <> 0 Then Return Nothing 'IStorageオブジェクト作成 Win32API.StgOpenStorage(Filename, Nothing, STGM.STGM_TRANSACTED Or STGM.STGM_READ Or STGM.STGM_SHARE_DENY_WRITE, Nothing, 0, grpstr) 'IPresistStreamインターフェースの取得 ps = CType(newgrp, IPersistStream) 'ストリーム作成 grpstr.OpenStream("ActiveMovieGraph", Nothing, STGM.STGM_READ Or STGM.STGM_SHARE_EXCLUSIVE, 0, strm) '読込 ps.Load(strm) Catch ex As Exception '失敗 If Not newgrp Is Nothing Then Marshal.ReleaseComObject(newgrp) newgrp = Nothing End If Throw Finally '不要となったオブジェクトの開放 If Not grpstr Is Nothing Then Marshal.ReleaseComObject(grpstr) grpstr = Nothing End If If Not ps Is Nothing Then Marshal.ReleaseComObject(ps) ps = Nothing End If If Not strm Is Nothing Then Marshal.ReleaseComObject(strm) strm = Nothing End If End Try Return newgrp End Function |
これも関数の形にしています。
前半はグラフの作成なので2.1とかぶります。
フィルタやピンのプロパティページを表示するには下記の様にします。
'ISpecifyPropertyPagesインターフェース取得 Dim sp As ISpecifyPropertyPages sp = CType(FilterObject, ISpecifyPropertyPages) 'プロパティページ情報取得 Dim ca As CAUUID sp.GetPages(ca) 'プロパティページ表示 Win32API.OleCreatePropertyFrame( _ Me.Handle, 0, 0, _ "プロパティページのキャプション", 1, _ FilterObject, ca.cElems, ca.pElems, 0, 0, Nothing) '後始末 If ca.pElems <> IntPtr.Zero Then Marshal.FreeCoTaskMem(ca.pElems) End If |
上のサンプルコード中では「FilterObject」とフィルタのオブジェクトのような名前にしていますが、
ここにはピンのオブジェクトでもフィルタのオブジェクトでもかまいません。