DirectShow関係


1.動画の再生

具体的なコードの前にDirectShowについて知っておいた方がいいことを先に説明します。


DirectShowでは「フィルタ」と呼ばれるモジュールをつなぎ合わせて「フィルタグラフ」と呼ぶものを作ります。
フィルタ同士の接続は「ピン」と呼ばれるインターフェースで行われます。

下図はMPEG1ファイルを再生するグラフになります。

四角のがフィルタで、フィルタの左右にあるでっぱりがピンになります。
フィルタ同士の接続は方向性があり、出力ピンと入力ピンが接続されます。
(上図の矢印がフィルタの接続になります。)
動画のデータは「ストリーム」と呼ばれ、このストリームがグラフの上流から下流に向かって流れていくと想像してください。

フィルタは大きく分けて下記のような種類があります。

ソースフィルタ HDDなどから動画や音声のファイルを読み込んで、下流のフィルタにデータを流します。
通常出力の口だけがあり、グラフの上流に位置します。
変換フィルタ 入力と出力があり、入力したストリームに何らかの処理を施し出力します。
ソースフィルタが出力したストリームを分解し、映像と音声のストリームに分ける「スプリッタフィルタ」、
圧縮されているストリームを展開する「デコーダ(デコンプレッサ)フィルタ」、
逆に非圧縮なストリームを圧縮する「エンコーダ(コンプレッサ)フィルタ」、
等々、非常に多くの種類があります。
レンダラフィルタ 通常入力だけがあり、画面にストリームを描画(レンダリング)したり
スピーカから音を出したりします。

上の図はDirectX SDKに付属するツール「GRAPHEDIT(graphedt.exe)」で作成したものです。
このGraphEditは非常に便利なツールで、
簡易プレーヤーにもなりますし、MpegからWMVへ等の変換ツールにもなります。テレビのキャプチャなんてのも可能です。
ぜひ使いこなせるようになりましょう。(日本語に対応していないのが難点)

では、実際にVBでDirectShowを使う方法を説明していきます。
基本的に上で説明したフィルタグラフを作成し、それを実行させれば動画の再生ができるわけです。

まず前準備として
VBでDirectShowを使うには下記のDLLを参照設定する必要があります。

ActiveMovie control type library

DirectX SDKをインストールすれば参照設定のダイアログに表示されているはずです。

動画を再生する実際のコードは下記のようになります。
フォームはデフォルトのままでかまいません(コードからサイズ等を変更しています)。
下記のコードをフォームモジュールにコピペして実行すれば、動画ファイルが(いきなり)再生されます。

Option Explicit

'再生する動画ファイル
Private Const VIDEOFILE$ = "C:\DXSDK\samples\Multimedia\Media\butterfly.mpg"

'グラフマネージャ
Private mGrp As QuartzTypeLib.FilgraphManager


Private Sub Form_Load()

  'グラフマネージャの作成
  Set mGrp = New QuartzTypeLib.FilgraphManager

  '再生用のグラフを作成
  mGrp.RenderFile VIDEOFILE$

  'ビデオサイズ(縦横)を取得
  Dim bv As QuartzTypeLib.IBasicVideo
  Dim vx&, vy&
  Set bv = mGrp
  bv.GetVideoSize vx, vy

  'ビデオサイズに合わせてウィンドウを調整
  Dim winx&, winy& 'ウィンドウの縁サイズ
  Me.ScaleMode = vbTwips
  winx = Me.Width - Me.ScaleWidth
  winy = Me.Height - Me.ScaleHeight
  Me.Width = winx + vx * Screen.TwipsPerPixelX
  Me.Height = winy + vy * Screen.TwipsPerPixelY

  'ウィンドウ内で動画を再生させる
  Dim vw As QuartzTypeLib.IVideoWindow
  Set vw = mGrp
  vw.WindowStyle = &H40000000 'WS_CHILD
  vw.SetWindowPosition 0, 0, vx, vy
  vw.Owner = Me.hWnd

  '再生
  mGrp.Run

End Sub

順に説明します。

'再生する動画ファイル
Private Const VIDEOFILE$ = "C:\DXSDK\samples\Multimedia\Media\butterfly.mpg"

'グラフマネージャ
Private mGrp As QuartzTypeLib.FilgraphManager

モジュールセクションでは定数の定義と「フィルタグラフマネージャ」オブジェクトの定義を行っています。
定数VIDEOFILE$は再生するファイル名を指定しています。試すときは適当に変えてください。
(butterfly.mpgはDirectX SDKにあるサンプルファイルです)

フィルタグラフマネージャはグラフ作成をサポートしてくれるオブジェクトです。(っていうかVBではこれでしか作れない)
FilgraphManagerオブジェクトは動画再生中は存在し続ける必要があるのでモジュールセクションで定義します。

Private Sub Form_Load()

   'グラフマネージャの作成
   Set mGrp = New QuartzTypeLib.FilgraphManager

FilgraphManagerオブジェクトをNew演算子により作成します。

   '再生用のグラフを作成
   mGrp.RenderFile VIDEOFILE$

再生するファイルを指定してグラフを作成します。
RendeFileメソッドはファイルからその形式を自動的に判断し、再生するのに必要なグラフを作成してくれます。
ただ再生させるだけならば非常に楽です。

   'ビデオサイズ(縦横)を取得
   Dim bv As QuartzTypeLib.IBasicVideo
   Dim vx&, vy&
   Set bv = mGrp
   bv.GetVideoSize vx, vy

フィルタグラフマネージャからIBasicVideoインターフェースを取得し、
動画ファイルの縦横サイズを取得しています。
これは下の部分で動画サイズに合わせてウィンドウサイズを変更するために取得しています。

ここでちょっと説明しておくことがあります。
変数mGrpはFilgraphManager型の変数なのですが、
これをIBasicVideo型の変数bvにSet文で参照を代入しています。
これはエラーになりません。
なぜならばFilgraphManagerオブジェクトはIBasicVideoインターフェースを持っているからです。
Impliment文などでクラスを使ったことがある方なら理解できると思いますが、
知らない方には奇妙なコードに見えるかもしれません。
同様にFilgraphManagerオブジェクトは下記のインターフェースを持ちます。

IMediaControl グラフの動作に関するインターフェースです。
実行させたり(再生開始)、止めたりするメソッドがあります。
このインターフェースはデフォルトインターフェースなので、
メソッドはFilgraphManagerオブジェクトから直にメソッドを呼び出せます。
IMediaEvent イベント関係のインターフェースです。
動作が完了するまで待つWaitForCompletionメソッドなどがあります。
IMediaPosition 主にシーク関係のインターフェースです。
再生位置を変更したり、全体の長さなどを取得するメソッドがあります。
IBasicAudio 音声関係のインターフェースです。
IBasicVideo 映像関係のインターフェースです。
これから動画のサイズなどを取得できます。
IVideoWindow ビデオレンダラ関係のインターフェースです。
デフォルトでは別ウィンドウで動画が再生されますが、
このインターフェースのOwnerプロパティで自前のフォーム内で再生が可能です。

 

   'ビデオサイズに合わせてウィンドウを調整
   Dim winx&, winy& 'ウィンドウの縁サイズ
   Me.ScaleMode = vbTwips
   winx = Me.Width - Me.ScaleWidth
   winy = Me.Height - Me.ScaleHeight
   Me.Width = winx + vx * Screen.TwipsPerPixelX
   Me.Height = winy + vy * Screen.TwipsPerPixelY

   'ウィンドウ内で動画を再生させる
   Dim vw As QuartzTypeLib.IVideoWindow
   Set vw = mGrp
   vw.WindowStyle = &H40000000 'WS_CHILD
   vw.SetWindowPosition 0, 0, vx, vy
   vw.Owner = Me.hWnd

ここの前半は、フォームの縁のサイズを計算し、
動画のサイズに合わせてフォームのサイズを変更しています。

後半はフォーム内で動画を再生させるための処理です。
WindowStyleプロパティで子ウィンドウの形式にし、
SetWIndowPositionメソッドで位置とサイズを指定、
Ownerプロパティで親ウィンドウをフォームにしています。
これでフォーム内で動画が再生されます。

SetWindowPositionの引数の意味はMSDNを参照してください。
(指定しない場合どうなるかを見てみればわかります。これはこれで面白いけど)

   '再生
   mGrp.Run

End Sub

最後にRunメソッドでグラフを開始させます。
これで動画再生が始まります。

 

このサンプルではフォーム内で再生させるためのコードの方が多いです。
それを抜いてDIrectShowで動画を再生させる方法をまとめるとこんな感じです。

1.FilgraphManagerオブジェクトを作成

2.RenderFileメソッドでグラフを作成

3.Runメソッドで再生開始

実は定義を入れても4行ですんだりします(笑

DirectShowではAVIやMPEG、WMV形式の他に、JPEGやGIFファイル等の静止画にも対応しています。
GIFの場合アニメーションGIFならばちゃんと動きます。
アニメーションGIFをVBで再生させるときはWebBrowserコントロールを使うのが常ですが、
DirectShowを使っても可能なんですよ。


追加情報

上記コードでは最後まで再生されると、最後の画像が表示されたままになります。
再生が終わったことを知りたい場合や、連続再生(ループ再生)する場合は
主に2つ方法があります。(他の方法もありますが)

 (1)現在の再生位置を調べて終端に達したか判断する(ポーリング)
 (2)再生が完了したときに通知をもらう(K.J.K様よりゲストブックに投稿頂きました)

再生位置の表示も行うような場合は(1)の方が楽かもしれません。
単純に再生が終了したことを知りたい場合は(2)の方がスマートだと思います。

---------------------------------------------

(1)現在の再生位置を調べて終端に達したか判断する(ポーリング)
具体的なコードは下記になります。
上で提示しているサンプルコードに下記を追加し、
フォームにタイマーコントロール(Name=Timer1、Interval=100程度)を追加してください。

'タイマー処理
Private Sub Timer1_Timer()

  'IMediaPositionインターフェース取得
  Dim ms As IMediaPosition
  Set ms = mGrp
  If ms Is Nothing Then Exit Sub

    '長さ取得
    Dim maxpos As Double
    maxpos = ms.Duration

    '現在位置取得
    Dim pos As Double
    pos = ms.CurrentPosition

    '判定
    If maxpos <= pos Then
    '最後に達している

    '巻き戻し
    ms.CurrentPosition = 0 '現在位置を先頭にする

  End If

End Sub

タイマーイベントにて現在の再生位置と終了位置(全体の長さ)を取得し、
終了位置に達していた場合は現在の再生位置を先頭にすることでループ再生を実現しています。
(IMediaPositionインターフェースのCurrentPositionプロパティが現在の再生位置で
 取得も設定もできます。)


---------------------------------------------

(2)再生が完了したときに通知をもらう
IMediaEventExインターフェースのSetNotifyWindowを使用すると
再生が終了したときに指定したウィンドウに対してメッセージを送ってもらえます。
この通知メッセージを受け取った時にグラフのイベントを取得することにより
再生の終了タイミングを得ることができます。

具体的なコードは下記になります。
上で提示しているサンプルコードを下記の様に変更・追加し、
フォーム上に非表示にしたPictureBox(Name=Pic_Dummy、Visible=False)を配置して下さい。


  略

  'ウィンドウ内で動画を再生させる
  Dim vw As QuartzTypeLib.IVideoWindow
  Set vw = mGrp
  vw.WindowStyle = &H40000000 'WS_CHILD
  vw.SetWindowPosition 0, 0, vx, vy
  vw.Owner = Me.hWnd

  'イベントを通知するウィンドウの指定
  Dim ex As IMediaEventEx
  Set ex = mGrp
  ex.SetNotifyWindow Pic_Dummy.hWnd, &H200, 0 '&H200 = WM_MOUSEMOVE

  '再生
  mGrp.Run

End Sub


'イベント通知を受けたときの処理
Private Sub Pic_Dummy_MouseMove(Button As Integer, Shift As Integer, X As Single, Y As Single)

  'IMediaEventインターフェースの取得
  Dim ev As IMediaEvent
  Set ev = mGrp

  'すべてのイベントを取得する
  Dim ecode As Long, para1 As Long, para2 As Long
  Dim ms As IMediaPosition
  Do
    'イベントコードの取得
    On Error Resume Next
    ev.GetEvent ecode, para1, para2, 100
    If Err Then Exit Do
    On Error GoTo 0
    Debug.Print "Event : " + Format$(ecode)

    '各イベントコードの処理
    Select Case ecode
      Case 1 'EC_COMPLETE
      '再生終了

      '巻き戻し
      Set ms = mGrp
      ms.CurrentPosition = 0 '現在位置を先頭にする

    End Select

    'リソース解放
    ev.FreeEventParams ecode, para1, para2

  Loop

End Sub



若干補足します。


  'イベントを通知するウィンドウの指定
  Dim ex As IMediaEventEx
  Set ex = mGrp
  ex.SetNotifyWindow Pic_Dummy.hWnd, &H200, 0 '&H200 = WM_MOUSEMOVE


Runメソッドで再生する前に、
上記のようにSetNotifyWindowで通知先のウィンドウハンドルと通知するメッセージを指定しておきます。
これによりグラフでなんらかのイベント(再生が終わったとか、状態が変化したとか、等など)が
発生した場合、指定されたウィンドウに対して指定したメッセージが送られるようになります。

サンプルではウィンドウハンドルにはPic_DummyのhWndプロパティを指定し
通知メッセージとしてWM_MOUSEMOVEを指定しています。
このようにすることでイベントが発生した場合Pic_dummyのMouseMoveイベントが発生します。
(この辺はWindowsの仕組みをある程度知ってないと理解できないかもしれません。)


'イベント通知を受けたときの処理
Private Sub Pic_Dummy_MouseMove(Button As Integer, Shift As Integer, X As Single, Y As Single)

   'IMediaEventインターフェースの取得
   Dim ev As IMediaEvent
   Set ev = mGrp

   'すべてのイベントを取得する
   Dim ecode As Long, para1 As Long, para2 As Long
   Dim ms As IMediaPosition
   Do
     'イベントコードの取得
     On Error Resume Next
     ev.GetEvent ecode, para1, para2, 100
     If Err Then Exit Do
     On Error GoTo 0
     Debug.Print "Event : " + Format$(ecode)

     '各イベントコードの処理
     Select Case ecode
       Case 1 'EC_COMPLETE
       '再生終了

       '巻き戻し
       Set ms = mGrp
       ms.CurrentPosition = 0 '現在位置を先頭にする

     End Select

     'リソース解放
     ev.FreeEventParams ecode, para1, para2

   Loop

End Sub


ピクチャーボックスPic_DummyのMouseMoveイベントプロシジャでは
IMediaEventインタフェースを使いイベントコードを取得し、
再生終了のイベントコード(EC_COMPLETE = 1)を得た時に
(1)と同様に再生位置を先頭に戻しています。

ここで注意して欲しい点が2点あります。
一つは通知メッセージが送られる(=MouseMoveイベントが発生する)のは
再生終了時のみではないということです。
メッセージが送られるのはなんらかのグラフイベントが発生したからで、
再生が終わってない状態でもMouseMoveイベントが発生します。

もう一つはグラフのイベント一件毎にMouseMoveイベントが発生する訳ではないということです。
MouseMoveイベントが発生した要因のグラフイベントのコードは
IMediaEventインターフェースのGetEventメソッドで取得しますが、
サンプルコードのように失敗(=エラー)となるまで
Do〜Loopにて繰り返し取得する必要があります。


グラフのイベントコード(EC_COMPLETEなど)の意味については
リファレンスマニュアルに記載がありますのでそちらを参照してください。
また実際の値についてはC/C++用のヘッダファイルをみることで確認できます。
(私はWindowsSDKなどにあるevcode.hなどを見て確認しています)


上に戻る