4
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

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

Last updated at Posted at 2020-06-23

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

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

今日のゴール

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

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

実際にコードを書く前に

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

gst-launch-1.0 videotestsrc pattern=smpte ! autovideosink

smpte.gif

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

Manual Hello World

実際に先程コマンドで行ったパイプラインを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

    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

パイプラインの例

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

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

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

Pipeline creation

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

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

    // 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

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

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

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

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


    // 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時にはエラーであるかどうかをチェックしてきました。

    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

4
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?