VB2005でDirectShowを扱うときの基本操作をまとめて説明したいと思います。
なお重複しちゃいますので基本的な用語については説明していません。
初めての方はお手数ですがVB6+DirectShowのページを参照してください。
本当に申し訳ないのですが、
フィルタ
フィルタグラフ
フィルタグラフマネージャ
ピン
GRAPHEDIT
ぐらいは何のことかわかっているものとして説明しています。
(VB6+DirectShowのページを参照して頂ければわかると思います)
DirectShowの各インターフェースや定数、
及び、COM関係の関数やインタフェース類は定義は省略してます。(ながくなるんで)
定義は、下記からダウンロードできるDirectShowDefine.vbで行っています。
(私が使っているモジュールです。自由に流用して下さい。間違いがあったら教えてね)
DirectShowDefine.vb(DirectShow関係の定義モジュール)
以降のコードを試すときはこのモジュールをプロジェクトに入れてください。
使い方
①上のDirectShowDefine.vbをダウンロードする。
②新たなプロジェクトを作成する。(Windowsアプリケーションとか)
③メニュー「プロジェクト」-「既存項目の追加」で
ダウンロードしたDirectShowDefine.vbを選択する。
④サンプルコードをコピペ
サンプルファイルのパスとかはこちらの環境になっているので
自分の環境に合わせてください。
⑤実行
なお、以下サンプルコードにはエラー処理つーもんが完璧に欠如しています。
まずインターフェースの定義で<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を用意
②フィルタグラフマネージャの型(タイプ)を定義
③Activatorでインスタンス作成
④CTypeで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」とフィルタのオブジェクトのような名前にしていますが、
ここにはピンのオブジェクトでもフィルタのオブジェクトでもかまいません。