[ストリーミング技術]RustでGStreamerチュートリアル 1: Hello World

# はじめに

この記事はGStreamerの[公式チュートリアル](https://gstreamer.freedesktop.org/documentation/tutorials/basic/hello-world.html?gi-language=c)をRustで行い、さらに日本語に雑に翻訳したものです。

特に今回は[GStreamer-1:Hello World](https://gstreamer.freedesktop.org/documentation/tutorials/basic/hello-world.html?gi-language=c)で扱われているものを対象とします。

また、Rustに書き直されたサンプルとして[gstreamer-rsのtutorialsフォルダ以下のソース](https://gitlab.freedesktop.org/gstreamer/gstreamer-rs/-/tree/master/tutorials)を大きく参考にしますが、必ずしも一致しません。特にMac上でのメインループの記述のために本家のgstreamer-rsではOSごとの分岐を行う処理を含めていますが、自分の実行環境上で必要がなかったため含めていません。

このチュートリアル用に解説したコードはすべて以下に含まれています。
https://github.com/kyasbal-1994/qiita-gstreamer-rust-tutorial

単に自分の学習目的と日々の継続的なアウトプットの目標として記事を書くだけですが、昨今の動画配信技術の需要の高まりの一方で直接動画技術を扱うための入門として参考にしていただければ幸いです。

# Rust環境の構築

本家のチュートリアルではCの環境ですので特殊な環境ではありませんが、チュートリアルのrustのプロジェクトは相対パスで各ライブラリをリンクしているため現実的なユースケースにおけるプロジェクトの構造と異なります。そのため、チュートリアルをこなすために自分でプロジェクトを作成します。

```bash
$ cargo init
```

生成された`Cargo.toml`に対して、チュートリアルをこなすのに最低限の依存ライブラリを追加します。

```toml
[dependencies]
gstreamer = "0.15.7"
glib = "0.9.3"
```

さらに、gstreamer自身をインストールしておきます。

```shell
$ apt-get install libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev \
      gstreamer1.0-plugins-base gstreamer1.0-plugins-good \
      gstreamer1.0-plugins-bad gstreamer1.0-plugins-ugly \
      gstreamer1.0-libav libgstrtspserver-1.0-dev libges-1.0-dev
```

Tutorial 1の目標は単純な動画プレイヤーのエレメントであるplaybinを用いて動画を再生することにあります。Rustによる構築の前に、gstreamerに付随する簡易的なパイプラインのテストに利用するコマンドである`gst-launch-1.0`を用いて実験をしてみましょう。

```shell
gst-launch-1.0 playbin uri=https://www.freedesktop.org/software/gstreamer-sdk/data/media/sintel_trailer-480p.webm
```

![gst-launch.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/41402/a828ee69-125f-9c62-1a92-b73cf11e7cd9.gif)

このように普通の動画が再生されれば成功です。(VMWare上の仮想マシンで上手く動かなかったので(webm形式だけ)、そもそも動かすのに手こずりました)

# Tutorial 1 : Hello World

(この辺から[本家チュートリアル](https://gstreamer.freedesktop.org/documentation/tutorials/basic/hello-world.html?gi-language=c)の翻訳のRust版への改変になります)

このチュートリアルでは、単に動画を表示して"Hello World"を行います。

実際に用いるコードは以下のようなものです。

```rust
extern crate gstreamer as gst;
use gst::prelude::*;


fn main(){
        // Initialize GStreamer
        gst::init().unwrap();

        // Build the pipeline
        let uri =
            "https://www.freedesktop.org/software/gstreamer-sdk/data/media/sintel_trailer-480p.webm";
        let pipeline = gst::parse_launch(&format!("playbin uri={}", uri)).unwrap();
    
        // Start playing
        pipeline
            .set_state(gst::State::Playing)
            .expect("Unable to set the pipeline to the `Playing` state");
    
        // Wait until error or EOS
        let bus = pipeline.get_bus().unwrap();
        let msg_types = [gst::MessageType::Error,gst::MessageType::Eos];
        bus.timed_pop_filtered(gst::CLOCK_TIME_NONE, &msg_types);
    
        // Shutdown pipeline
        pipeline
            .set_state(gst::State::Null)
            .expect("Unable to set the pipeline to the `Null` state");
    
}
```

## Walkthrough

### gstreamer::init()

```rust
gst::init().unwrap();
```

どうやら、GStreamerを用いるためには最初に`gstreamer::init()`を呼んでおかなければならないようです。

### パイプラインの作成

```rust
let uri = "https://www.freedesktop.org/software/gstreamer-sdk/data/media/sintel_trailer-480p.webm";
let pipeline = gst::parse_launch(&format!("playbin uri={}", uri)).unwrap();
```

GStreamerではマルチメディアをパイプラインを構築することによって扱います。
このパイプライン上で`source`と呼ばれる要素から、`sink`と呼ばれる要素へいくつかのタスクを行う要素を通っていくことによってメディアデータを操作します。
こうしてつなぎ合わされた要素たちをパイプラインと呼んでいます。

プログラムで一つ一つの要素を作成しつなぎ合わせることによってパイプラインを構築することも可能ですが、特に生成するパイプラインが簡単な場合には、手軽な方法として文字列から作成することができます。

この例では、`playbin`という要素しかないパイプラインを生成しています。`playbin`はパラメータとして`uri`を取り、ここに文字列として再生したい動画ファイルのパスを配置しました。

### playbin

先程のパイプラインの作成で我々は[playbin](https://gstreamer.freedesktop.org/documentation/playback/playbin.html#playbin)という要素ひとつだけからなるパイプラインを構築しました。

`playbin`は`source`としても`sink`としても振る舞うことのでき、それ自身がパイプライン全体になりうることができる特殊な要素です。内部的に、渡されたメディアデータを再生するのに必要な要素を作成し、互いに接続してくれるため内部について気にする必要がありません。

自分でパイプラインを構築する場合と異なり、微細なパイプラインの調整を行うことはできませんが、十分に幅広いアプリケーションで利用できるだけのカスタマイズ可能な項目が存在します。

このチュートリアルでは、再生したい動画ファイルのURIを指定しました。ぜひ違うファイルで試してみてください。それが`http://`でも、`file://`でもplaybinは対応した適切な`source`要素を作成しつなげてくれます。

### State

```rust
pipeline.set_state(gst::State::Playing).expect("Unable to set the pipeline to the `Playing` state");
```

この行では、GStreamerの興味深いコンセプトの一つであるステートを操作しています。すべてのGStreamerの要素は対応したステートを常に持っています。
これは、通常のビデオプレイヤーから想像するような、ただ単に再生/停止だけを意味するもの以上のものがありますが、ここではpipelineのステートが`Playing`にならない限り再生されないという程度の説明にとどめます。

また、プログラムの最後ではパイプラインのステートをNoneにしています。

```rust
pipeline.set_state(gst::State::Null).expect("Unable to set the pipeline to the `Null` state");
```
終了時にはこのようにしてパイプラインの後処理をしてあげる必要があります。パイプラインにNullステートをセットすることによって、確保された様々なリソースが開放されていることを保証します。

### Bus

```rust
let bus = pipeline.get_bus().unwrap();
let msg_types = [gst::MessageType::Error,gst::MessageType::Eos];
bus.timed_pop_filtered(gst::CLOCK_TIME_NONE, &msg_types);
```

これらの行ではパイプラインからバスを作成しています。バスについてはチュートリアルその2で深く取り扱うので簡単な説明にとどめますが、ここではErrorもしくはEos(End Of Stream)の状態になるまで、タイムアウトなく(gst::CLOCK_TIME_NONE)ループを行うことを示しています。

実際、このループにタイムアウトをもたせたい場合には以下のようにすると1秒間再生して終了するようなメッセージループを記述することができます。

```rust
let bus = pipeline.get_bus().unwrap();
let msg_types = [gst::MessageType::Error,gst::MessageType::Eos];
bus.timed_pop_filtered(gst::ClockTime::from_mseconds(1000), &msg_types);
```

## Conclusion

* `gst::init()`を用いて初期化する
* 文字列のパイプラインを作成するのに`gst::parse_launch`を用いる。
* 簡単に再生するパイプラインを作成するにはplaybin要素を用いる
* パイプラインを動き始めるようにするには`.set_state(gst::State::Playing)`を用いる。
* `timed_pop_filtered`を行っている間はGStreamerがパイプラインの中身を処理している

次回: https://qiita.com/kyasbal_1994/items/a7f2ab0b05f47d96ded3