.NETからGStreamerを使ってみる

GStreamerとは

クロスプラットフォームのマルチメディアライブラリ。映像や音声の入力(デバイスでもファイルでもよい)をエンコードしたり、RTMPでストリーミングしたりとか、そういったものを簡単に作ることができる。

なお、.NETからGStreamerをつかう実例としてはBansheeというOSSなiTunesみたいなものが存在している。

.NET/Windows用のGStreamerバイナリの入手

2013年7月の時点では、.NET/Windows向けのバイナリはhttp://code.google.com/p/ossbuild-vs2010/で配布されているいるものを使うのがよさそう。GStreamer本家が配布しているものと比較すると最新ではないものの、比較的新しいものが利用できる。

.NET/WindowsからGStreamerを使うための初期設定

サンプルコード(http://code.google.com/p/ossbuild-vs2010/source/browse/#git%2FExamples%2Fcsharp-helloworld)を参考にしながら。

参照設定

gstreamer-sharp.dllを参照に追加する。gstreamer-sharp-net4.dllも存在するのだが、こちらを使った場合はうまく動かなかった。

ビルド設定

gstreamerが32bitバイナリのため、ビルドターゲットをx86に設定する。

セットアップコード

GStreamerを使う場合、いくつか環境変数が設定されている必要がある。システム設定の方で環境変数を設定してあっても良いのだが、実際にアプリを配布する際に邪魔くさくなるので、アプリの初期化中(WPFの場合はApp.xaml.csのコンストラクタ内など)に環境変数を設定するコードを埋め込む。

var path = @"C:\gstreamer";
Environment.SetEnvironmentVariable("GST_PLUGIN_PATH", "");
Environment.SetEnvironmentVariable("GST_PLUGIN_SYSTEM_PATH", String.Format(@"{0}\bin\plugins", path));
Environment.SetEnvironmentVariable("PATH", String.Format(@"C:\Windows;{0}\lib;{0}\bin", path));

// デバッグログ出力設定
Environment.SetEnvironmentVariable("GST_DEBUG", "*:3");
Environment.SetEnvironmentVariable("GST_DEBUG_FILE", "GstreamerLog.txt");
Environment.SetEnvironmentVariable("GST_DEBUG_DUMP_DOT_DIR", path);

Gst.Application.Init(); //GStreamerの初期化

ひとまずここまでやれば.NETからGStreamerのAPIをたたくことができる。

動作確認

ちゃんとGStreamerが初期化できていれば、以下のようなコードでGStreamerのバージョンが出力できる。

String.Format("{0}.{1}.{2}", Gst.Version.Major, Gst.Version.Minor, Gst.Version.Micro)

GStreamerを使ってマイクからの入力をOgg/Speexで録音してみる

とりあえず音声といえばSpeexだろうということで、マイクからの入力をリアルタイムにOgg/Speexにエンコードしてファイル出力してみる。先ほどと同じくサンプルコード(/ - ossbuild-vs2010 - Visual Studio 2010 fork of ossbuild - Google Project Hosting)を参考にしながらコードを書いてみた。終了処理とかかなり適当なので、本格的にアプリを作る場合は要注意。

public partial class MainWindow : Window
{
    private Gst.GLib.MainLoop glibMainLoop;
    private System.Threading.Thread glibThread;
    private Gst.Pipeline pipeline;
    
    public MainWindow()
    {
        InitializeComponent();

        glibMainLoop = new Gst.GLib.MainLoop();
        glibThread = new System.Threading.Thread(glibMainLoop.Run);
        glibThread.Name = "GLibMainLoop";
        glibThread.Start();
        pipeline = CreatePipeline();
    }

    private Gst.Pipeline CreatePipeline()
    {
        // 録音デバイス(OSの規定のデバイス), 16KHz MONO 16bit
        var src = Gst.ElementFactory.Make("autoaudiosrc");
        var srcFilter = Gst.Caps.FromString("audio/x-raw-int, width=16, depth=16, rate=16000, channels=1");
        
        // ノイズ除去
        var conv = Gst.ElementFactory.Make("audioconvert");

        // Speexエンコーダ ABR, quality=8, complexity=8
        // 帯域設定は未指定だが入力サンプリングレートによって自動設定される(ソースを読んだ)
        // 8KHz -> 狭帯域, 16KHz -> 広帯域, 32KHz -> 超広帯域
        var enc = Gst.ElementFactory.Make("speexenc");
        enc["abr"] = 1;
        enc["quality"] = 8.0f;
        enc["complexity"] = 8;

        // Ogg Multiplexer
        var mux = Gst.ElementFactory.Make("oggmux");

        // Sink(出力先)
        var sink = Gst.ElementFactory.Make("filesink");
        sink["location"] = "test.ogg";

        // パイプラインの構築
        var pipeline = new Gst.Pipeline();
        pipeline.Add(src, conv, enc, mux, sink);
        
        src.LinkFiltered(conv, srcFilter);
        conv.Link(enc);
        enc.Link(mux);
        mux.Link(sink);

        return pipeline;
    }

    private void ToggleButton_Click(object sender, RoutedEventArgs e)
    {
        var button = (ToggleButton)sender;
        if (button.IsChecked == true)
        {
            pipeline.SetState(Gst.State.Playing);
        }
        else
        {
            pipeline.SetState(Gst.State.Ready);
        }
    }
    
    private void Window_Closed(object sender, EventArgs e)
    {
        pipeline.SetState(Gst.State.Ready);
        pipeline.Dispose();
        glibMainLoop.Quit();
    }
}
C#でGStreamerプログインを書いてみる

これもサンプル(http://code.google.com/p/ossbuild-vs2010/source/browse/#git%2FExamples%2Fcsharp-plugin-example)を参考に書いてみる。
自プログラム内のみで使うプラグインは、ベースクラスを継承して、PadTemplate(このプラグインでどんな入出力を受け付けるかを指定)する程度でできてしまう。具体的な実装例はGStreamer自体のコードを参考にする。

public class SampleSink : Gst.Base.BaseSink
{
    public SampleSink() : base() { }
    public SampleSink(IntPtr raw) : base(raw) { }

    static SampleSink()
    {
        var gtype = (Gst.GLib.GType)typeof(SampleSink);
        AddPadTemplate(gtype, new Gst.PadTemplate(
            "sink", Gst.PadDirection.Sink, Gst.PadPresence.Always, Gst.Caps.NewAny()));
    }

    protected override bool OnStart()
    {
        System.Diagnostics.Debug.WriteLine("OnStart");
        return true;
    }

    protected override bool OnStop()
    {
        System.Diagnostics.Debug.WriteLine("OnStop");
        return true;
    }

    protected override Gst.FlowReturn OnRender(Gst.Buffer buffer)
    {
        var bytes = buffer.ToByteArray();
        var time = DateTime.Now.ToString("mm:ss.fff");

        System.Diagnostics.Debug.WriteLine(String.Format("OnRender {0} {1}", time, bytes.Length));
        return base.OnRender(buffer);
    }

    protected override bool OnEvent(Gst.Event evnt)
    {
        System.Diagnostics.Debug.WriteLine("OnEvent");
        return base.OnEvent(evnt);
    }
}

プラグインをインスタンス化する際は、Gst.ElementFactory.Make("filesink")みたいに指定する必要はなく、普通にnewすればよい。

また、外部プログラム(GStreamerのコマンドラインツール等)からC#製のプラグインを認識させたい場合は、サンプル(http://code.google.com/p/ossbuild-vs2010/source/browse/#git%2FExamples%2Fcsharp-plugin-example)に付属しているPluginWrapperを使えばよいらしい。詳細はサンプルコードおよびReadMe.txtを参照。