前回: 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
このチュートリアルではただ単にテストパターンを生成する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");
パイプラインの例
このコード中に見られる通り、新しい要素はgstreamer::ElementFactory::makeによって作成することができます。第一引数は作成する要素の型で、第二引数は作成した要素に割り当てる名前です。名前をつけることによって後で要素を取ってくるのが楽になります。
このチュートリアルではvideotestsrc
とautovideosink
を作成しました。videotestsrc
はsource
要素つまり、データを生成する要素であり、これがテストパターンを生成しています。この要素はデバッグ目的などで便利です。
autovideosink
はsink
要素、つまりデータを消費する要素になります。これによって受け取った画像や動画を画面に表示します。動画用の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