[ストリーミング技術]RustでGStreamerチュートリアル 2:  GStreamerのコンセプト

前回: https://qiita.com/kyasbal_1994/items/a1a7d1bd5c4832947a8a

コード: https://github.com/kyasbal-1994/qiita-gstreamer-rust-tutorial

# 今日のゴール

前のチュートリアルではパイプラインを自動でテキストから作成することを示した。
このチュートリアルではそれぞれの要素を作成し、それぞれをつなげることによってパイプラインを作成する。
このチュートリアルによって以下のことを学べる。

* GStreamerの要素とはなにか。どうやってそれぞれ作成するか
* それぞれの要素をどうやってつなげるのか
* それぞれの要素の振る舞いをどうやってカスタマイズするか
* GStreamerのメッセージからエラーや必要な情報を取るためにどうやってバスを監視するか

# 実際にコードを書く前に

実際にコードを書く前に`gst-launch-1.0`を用いて今回実験するパイプラインを作成してみます。

```bash
gst-launch-1.0 videotestsrc pattern=smpte ! autovideosink
```

![smpte.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/41402/b3a542b1-5385-6b0a-66d1-49a783c4b041.gif)

このチュートリアルではただ単にテストパターンを生成するsourceである`videotestsrc`を単に受け取った動画を再生する`autovideosink`につなげています。


# Manual Hello World

実際に先程コマンドで行ったパイプラインをrustで手動で作成すると以下のような形になります。

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

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

    // Instanciating source and sink elements
    let source = gst::ElementFactory::make("videotestsrc", Some("source")).expect("Could not create a source element");
    let sink = gst::ElementFactory::make("autovideosink",Some("sink")).expect("Could not create a sink element");

    // Instanciate pipeline
    let pipeline = gst::Pipeline::new(Some("test-manual-pipeline"));

    // Connecting each elements
    pipeline.add_many(&[&source,&sink]).unwrap();
    source.link(&sink).expect("Elements could not be linked");

    // Set a property
    source.set_property_from_str("pattern", "smpte");

    // Start playing pipeline
    pipeline.set_state(gst::State::Playing).expect("Unable to set the pipeline to Playing state");

    // Watch pipeline until Eos or getting an error
    let bus = pipeline.get_bus().unwrap();
    for msg in bus.iter_timed(gst::CLOCK_TIME_NONE){
        match msg.view() {
            gst::MessageView::Error(err)=>{
                eprintln!(
                    "Error received from element {:?}: {}",
                    err.get_src().map(|s| s.get_path_string()),
                    err.get_error()
                );
                eprintln!("Debugging information: {:?}", err.get_debug());
                break;
            }
            gst::MessageView::Eos(..)=>break,
            _=>(),
        }
    }

    // Cleaning up
    pipeline.set_state(gst::State::Null).expect("Unable to set the pipeline to `Null` state");
}
```

### Element creation

```rust
    let source = gst::ElementFactory::make("videotestsrc", Some("source")).expect("Could not create a source element");
    let sink = gst::ElementFactory::make("autovideosink",Some("sink")).expect("Could not create a sink element");
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/41402/863193c8-2158-e4fa-9382-dbff1746ec9c.png)
> パイプラインの例

このコード中に見られる通り、新しい要素はgstreamer::ElementFactory::makeによって作成することができます。第一引数は作成する要素の型で、第二引数は作成した要素に割り当てる名前です。名前をつけることによって後で要素を取ってくるのが楽になります。

このチュートリアルでは`videotestsrc`と`autovideosink`を作成しました。`videotestsrc`は`source`要素つまり、データを生成する要素であり、これがテストパターンを生成しています。この要素はデバッグ目的などで便利です。

`autovideosink`は`sink`要素、つまりデータを消費する要素になります。これによって受け取った画像や動画を画面に表示します。動画用のsinkにはOSなどによっていくつかの種類があり、`autovideosink`は使っている環境に合わせて適切なものを選んでくれます。
これにより、コードはプラットホームを選ばずに実行できるようになります。

## Pipeline creation

```rust
    // Instanciate pipeline
    let pipeline = gst::Pipeline::new(Some("test-manual-pipeline"));
```

パイプラインはPipeline::newで作成することができ、要素と同様に名前をつけることができます。

```rust
    // Connecting each elements
    pipeline.add_many(&[&source,&sink]).unwrap();
    source.link(&sink).expect("Elements could not be linked");
```

パイプラインは`bin`と呼ばれる要素の特殊形です。`bin`は複数の要素を含むために使われます。
rustでは、pipelineに要素を追加するには`add_many`を用いれます。場合によっては、`add`でひとつだけ追加することも可能です。

しかし、これだけではこれらの要素はつながっていません。そのため、`link`をつなげたいもとの要素から対象の要素に呼んであげる必要があります。ここでは、`source`から`sink`にデータが流れていくため`source.link(&sink)`と記述します。
この際、予めエレメントが属する`bin`に接続されるすべての要素が、すでに追加されている必要があります。

## Properties

```rust
    source.set_property_from_str("pattern", "smpte");
```

ほとんどのGStreamerの要素はカスタマイズ可能なプロパティを持っています。これは名前のつけられた属性で、書き込み可能である場合には変更することにより要素の振る舞いを変更することができます。
あるいは読み込み可能である場合、要素の中の内部的なステートを知るためにも利用されます。

この例では文字列からプロパティを変更しています。set_propertyを用いることによって特定のプロパティを特定の値を用いて変更することができます。

この値を変更することができる様子を見てみます。

```rust

    // Watch pipeline until Eos or getting an error
    let bus = pipeline.get_bus().unwrap();
    for msg in bus.iter_timed(gst::ClockTime::from_mseconds(3000)){
        match msg.view() {
            gst::MessageView::Error(err)=>{
                eprintln!(
                    "Error received from element {:?}: {}",
                    err.get_src().map(|s| s.get_path_string()),
                    err.get_error()
                );
                eprintln!("Debugging information: {:?}", err.get_debug());
                break;
            }
            gst::MessageView::Eos(..)=>break,
            _=>(),
        }
    }
    source.set_property_from_str("pattern", "snow");
    for msg in bus.iter_timed(gst::ClockTime::from_mseconds(3000)){
        match msg.view() {
            gst::MessageView::Error(err)=>{
                eprintln!(
                    "Error received from element {:?}: {}",
                    err.get_src().map(|s| s.get_path_string()),
                    err.get_error()
                );
                eprintln!("Debugging information: {:?}", err.get_debug());
                break;
            }
            gst::MessageView::Eos(..)=>break,
            _=>(),
        }
    }

```

のようなコードにバスの監視部分を変更すると、3秒経過後に、sourceのパターンを`smpte`から`snow`に変更します。
結果として、表示している動画が切り替わることが観測できると思います。

## Error checking

今まで、set_state時にはエラーであるかどうかをチェックしてきました。

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

詳しくは第3章で扱いますが、ステートの変更はとてもデリケートな処理であるため、ステートの変更時にはエラーをチェックしたほうが良いでしょう。

第1章で登場した`timed_pop_filtered()`では指定したメッセージがくるまでGStreamer側でメッセージループを回すことができました。

しかし、今回はメッセージの処理を自前で行うことを考えます。こうすることによって特定の要素が発行する様々なイベントやエラーに対して適切に応答するような改変を施すことが可能になります。

## The Gstreamer bus

ここでは、少しだけGStreamerのバスについて触れておいたほうがいいでしょう。Gstreamerのバスは各要素で生成されたメッセージを順番通りにアプリケーションに、アプリケーションスレッドまで伝える役割があります。
最後の点が重要で、実際のメディアのストリーミングはアプリケーションスレッドではないところで実行されるべきだからです。

これらのメッセージは同期的にtimed_pop_filteredを用いて取得するか、非同期的にシグナルを用いて取得することができます。(次章で解説)
アプリケーションでは常にエラーや他の再生にかかわる問題をモニタリングし適切にハンドリングしなければなりません。

# Conclusion

* どうやってエレメントを`ElementFactory`を用いて作成するか
* どうやってパイプラインを`Pipeline::new`を用いて作成するか
* 複数の要素を`add_many`を用いて追加する方法
* それらの要素同士を`link`を用いて接続し合う方法

次回: https://qiita.com/kyasbal_1994/items/88a5d0dfa2abdc44296c