DirectShow関係3(VB2005)


2.基本操作

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でトラップしたい場合は外したほうがいいかもしれません。
この辺は好みでお願いします。


2.1 フィルタグラフマネージャの作成

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として定義しています。


2.2 フィルタグラフマネージャの破棄

「1.動画の再生」では明記していませんが、いくつもの動画を再生する場合、
フィルタグラフマネージャのインスタンスを明示的に破棄した方がいいです。

'フィルタグラフマネージャの破棄
        Marshal.ReleaseComObject(mGrp)
        mGrp = nothing

自動的に破棄してくれるのを待つよりも、
もう使わないグラフはMarshal.ReleaseComObjectメソッドを使って明示的に破棄しましょう。
でないと再生ウィンドウがいつまでも残ったりします。


2.3 動画ファイルからグラフを作成

動画ファイルからグラフを作成する、つまり、動画を再生する準備は下記の様に
IGraphBuilderインターフェースのRenderFileメソッドを使います。

'動画ファイルからグラフ作成
        mGrp.RenderFile("E:\media\src320x240.avi", "")

第二引数は空文字列""でOKです。


2.4 グラフの再生/停止

グラフを再生/停止するには、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はこの操作を自動的に(かつ非同期的に)やってくれるメソッドです。


2.5 フィルタの列挙

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に定義してあります。


2.6 グラフにフィルタに追加

グラフにフィルタを追加するには、フィルタの実態(インスタンス)を作成し
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内で定義されています。


2.7 グラフをGRFファイルに保存

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に合わせてエラー処理を変えたにすぎません。


2.8 GRFファイルからグラフの読み込み

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とかぶります。


2.9 プロパティページの表示

フィルタやピンのプロパティページを表示するには下記の様にします。

        '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」とフィルタのオブジェクトのような名前にしていますが、
ここにはピンのオブジェクトでもフィルタのオブジェクトでもかまいません。


上に戻る