C#でDirectShowを使ってUSBカメラの映像をキャプチャ

chakemiです。先日、Androidタブレットを購入しました。使いこなせるか自信がありません。。。 本日は、C#からDirectShowを使って映像をキャプチャしてみようと思います。

開発環境

  • WindowsXP SP3
  • VisualC#2010Express

前回までで、キャプチャデバイスの映像を表示するとこまでは出来るようになりました。

今回は、その映像をファイルに記録していくわけですが、DirectShowでは、出力ファイル名を受け取る必要があるフィルタでは、アプリケーションからファイル名を設定するために、IFileSinkFilterインターフェースを使わなければならないようです。

残念なことに、「Active Movie control type library」では、このCOMインターフェースの定義がされていないため、今までのようにはいきません。。。

DirectX8の頃には、dshowvblib.tlbって名のタイプライブラリが、SDKとともに配布されていて、そこで定義されていたようなので、簡単にマネージラッパーdllを生成できたようですが、2000年の頃の話で、現在DirectX8もサポートされていませんし、入手出来ません。。。。(T T) 仕方が無いので、今回は、C#でCOMを直接定義して、マーシャリングしてみたいと思います。

まず、IFileSinkFilterインターフェースのGuidやメソッドを確認するために、「OLE/COM Object Viewer」を使います。OLE/COM Object Viewerについてはこちら

適当にクラスファイルを追加して、以下のようにCOMインターフェースを宣言します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;

namespace ActiveMovieControl3
{
    [ComVisible(true), ComImport, Guid("A2104830-7C70-11CF-8BCE-00AA00A3F1A6"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    public interface IFileSinkFilter
    {
        void SetFileName([In, MarshalAs(UnmanagedType.LPWStr)] string pszFileName,
                         [In, MarshalAs(UnmanagedType.IUnknown)] object pmt);

        void GetCurFile([Out, MarshalAs(UnmanagedType.LPWStr)] out string ppszFileName,
                        [Out, MarshalAs(UnmanagedType.IUnknown) ] out object pmt);
    }
}

COMのマーシャリングについては、こちらを参考にさせて頂きました。

これで準備が整ったので、キャプチャのためのコードを書いてきます。 キャプチャデバイスの表示は、ほぼ前回と一緒なので省略します。

今回は、キャプチャ開始と停止のボタンを追加し CaptureGraph関数のなかで、キャプチャ処理を実行するようにしました。

まずは、フィルタグラフにVideo Capture Sourceフィルタを追加して、Captureピンを取得します。

1
2
3
4
5
6
7
8
private void CaptureGraph()
{
    filgraphManager = new FilgraphManager();

    IFilterInfo cameraFilter;
    cameraFilter = AddFilter(filgraphManager, "SCFH DSF");

    IPinInfo cameraPin = AddPin(cameraFilter, "Capture");

次に、AVI Muxフィルタを追加します。 圧縮のためのTransform(Codec)フィルタを追加してないので、今回は非圧縮でキャプチャとなります。

1
AddFilter(filgraphManager, "AVI Mux");

ファイル書き込みのためのFile writerフィルタを追加し、フィルタ情報を取得します。

1
2
IFilterInfo writerFilter;
writerFilter = AddFilter(filgraphManager, "File writer");

ここで、File writerフィルタからIFileSinkFilterインターフェースを取得し、SetFileNameメソッドで出力ファイル名を設定します。

1
2
3
IFileSinkFilter sink;
sink = writerFilter.Filter;
sink.SetFileName(@"C:\Active.avi", null);

あとは、Renderメソッドでピンを繋げて、フィルタグラフを実行すればキャプチャが開始されます。 以下、全体です。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using QuartzTypeLib;

namespace ActiveMovieControl3
{

    public partial class Form1 : Form
    {
        private FilgraphManager filgraphManager;

        public Form1()
        {
            InitializeComponent();

            filgraphManager = new FilgraphManager();

            IFilterInfo cameraFilter;
            cameraFilter = AddFilter(filgraphManager, "SCFH DSF");


            IPinInfo cameraPin = AddPin(cameraFilter, "Capture");

            cameraPin.Render();

            IBasicVideo bv = (IBasicVideo)filgraphManager;
            IVideoWindow vw = (IVideoWindow)filgraphManager;
            IMediaEventEx mEvent = (IMediaEventEx)filgraphManager;

            int vx, vy;
            bv.GetVideoSize(out vx, out vy);

            vw.Owner = (int)this.Handle;
            vw.WindowStyle = 0x40000000 | 0x4000000;

            vw.SetWindowPosition(0, 0, vx, vy);
            this.Width = vx + 2;
            this.Height = vy + 55;

            mEvent.SetNotifyWindow((int)this.Handle, 0x8000, 0);

            filgraphManager.Run();

        }

        private void CaptureGraph()
        {
            filgraphManager = new FilgraphManager();

            IFilterInfo cameraFilter;
            cameraFilter = AddFilter(filgraphManager, "SCFH DSF");

            IPinInfo cameraPin = AddPin(cameraFilter, "Capture");


            //AVI Muxフィルタ追加
            AddFilter(filgraphManager, "AVI Mux");

            IFilterInfo writerFilter;
            writerFilter = AddFilter(filgraphManager, "File writer");

            //IFileSinkFilterインターフェース取得
            IFileSinkFilter sink;
            sink = writerFilter.Filter;
            sink.SetFileName(@"C:\Active.avi", null);

            cameraPin.Render();

        }

        //AddFilter関数
        public IFilterInfo AddFilter(FilgraphManager graph, string filterName)
        {
            IAMCollection collection;
            IRegFilterInfo regFilterInfo;
            Object obj;
            IFilterInfo filter;

            collection = (IAMCollection)graph.RegFilterCollection;
            filter = null;

            for (int i = 0; i < collection.Count; i++)
            {
                collection.Item(i, out obj);
                regFilterInfo = (IRegFilterInfo)obj;
                if (regFilterInfo.Name == filterName)
                {
                    regFilterInfo.Filter(out obj);
                    filter = (IFilterInfo)obj;
                }
            }
            return filter;
        }

        //AddPin関数
        public IPinInfo AddPin(IFilterInfo filInfo, string pinName)
        {
            IAMCollection collection;
            Object obj;
            IPinInfo pinInfo, pin;

            collection = (IAMCollection)filInfo.Pins;
            pinInfo = null;
            pin = null;
            for (int i = 0; i < collection.Count; i++)
            {
                collection.Item(i, out obj);
                pinInfo = (IPinInfo)obj;
                if (pinInfo.Name == pinName)
                {
                    pin = (IPinInfo)obj;
                }
            }
            return pin;
        }

        private void button1_Click(object sender, EventArgs e)
        {
            CaptureGraph();
            filgraphManager.Run();
        }

        private void button2_Click(object sender, EventArgs e)
        {
            if (filgraphManager != null)
                filgraphManager.Stop();
        }
    }
}

※今回、Video Capture Sourceフィルタに「SCFH DSF」を使わせて頂きましたが、デバイスによってはうまくキャプチャ出来ないかもしれません。
GraphEditorなどで、キャプチャまでのストリームの流れを確認して必要なフィルタを追加する必要があります。