初回: https://qiita.com/kyasbal_1994/items/a1a7d1bd5c4832947a8a
前回:https://qiita.com/kyasbal_1994/items/82939a756e0f602bbfa2
コード:https://github.com/kyasbal-1994/qiita-gstreamer-rust-tutorial
ゴール
このチュートリアルでは、GStreamerをGTK+のようなGUIツールキットとどのように連携させるかを学びます。この2つの連携がこのチュートリアルの重要な点です。特に、GStreamerの出力をGTK+に出力すること、また、GTK+上のインターフェースへの入力をGStreamerに流すことです。
- GStreamer専用のウィンドウを作らず、どのようにしてビデオを特定のウィンドウに流すか
- GStreamerから継続的に取得したデータを用いてどのようにしてGUIを更新するか
- ほとんどのGUIツールキットではメインスレッド以外からの操作は禁止されているが、GStreamerの複数のスレッドからどのようにしてGUIを操作するか。
- 興味があるメッセージだけを受信する方法。
今回は今まで習得した知識を用いてビデオプレイヤーを作成します。
事前準備
今回はGTK+を利用するため追加でインストールしておくものがあります。
$ sudo apt-get install libgtk-3-dev
また、今回はgtk,gdkなどとともにgstreamer-videoも用います。また、他のチュートリアルのビルド時にはgtkやgdkは用いません。しかしこれらのライブラリは重いので、optionalにしておきます。
これを、featuresにライブラリ名を書いておくことによって、そのfeaturesフラグがオンであるときのみビルド時にそのライブラリを含めることができます。
[package]
name = "gstreamer-rust"
version = "0.1.0"
authors = ["kyasbal"]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
gstreamer = "0.15.7"
glib = "0.9.3"
gstreamer-video = "0.15.7"
gtk = {version="0.8.1",optional = true}
gdk = {version="0.12.1",optional = true}
[features]
tutorial5 = ["gtk","gdk"]
このプロジェクトをビルドするには以下のようにcargo run
を行います。
$ cargo run --features tutorial5
このフラグをつけたときしか、gtk,gdkは使えません。
Making video player with GStreamer
// This section is only works if --feature tutorial5 was specified on build
#[cfg(feature = "tutorial5")]
mod tutorial5 {
extern crate gstreamer as gst;
extern crate gstreamer_video as gst_video;
// gdk and gtk won't be available if there are no #[cfg(feature = "tutorial5")] on scope they enclosed in.
use gdk::prelude::*;
use gst::prelude::*;
use gst_video::prelude::*;
use gtk::*;
use std::os::raw::c_void;
use std::process;
use glib::object::ObjectType;
pub fn run() {
initialize_gtk_gstreaner(); // Initialize gtk and gstreamer
// Initialize playbin with single file source
let uri = "https://www.freedesktop.org/software/gstreamer-sdk/data/media/sintel_trailer-480p.webm";
let playbin = gst::ElementFactory::make("playbin", None).unwrap();
playbin.set_property("uri", &uri).unwrap();
// Add event handler to be notified when video-tag was changed
playbin
.connect("video-tags-changed", false, |args| {
let pipeline = args[0]
.get::<gst::Element>()
.expect("Failed to get value in video-tags-changed argument")
.unwrap();
// This will send message to application thread
post_app_message(&pipeline);
None
})
.expect("Failed to connect to video-tag-changed");
// Add event handler to be notified when audio-tag was changed
playbin
.connect("audio-tags-changed", false, |args| {
let pipeline = args[0]
.get::<gst::Element>()
.expect("Failed to get value in audio-tags-changed argument")
.unwrap();
// This will send message to application thread
post_app_message(&pipeline);
None
})
.expect("Failed to connect to audio-tags-changed");
// Add event handler to be notified when audio-tag was changed
playbin
.connect("text-tags-changed", false, |args| {
let pipeline = args[0]
.get::<gst::Element>()
.expect("Failed to get value in text-tags-changed argument")
.unwrap();
// This will send message to application thread
post_app_message(&pipeline);
None
})
.expect("Failed to connect to text-tags-changed");
// Construct the ui
create_ui(&playbin);
// Instruct the bus to emit signals for each received message, and connect to the interesting signals
let bus = playbin.get_bus().unwrap();
bus.add_signal_watch();
let pipeline_weak = playbin.downgrade();
bus.connect_message(move |_, msg| {
let pipeline = match pipeline_weak.upgrade() {
Some(pipeline) => pipeline,
None => return,
};
match msg.view() {
gst::MessageView::Eos(..) => {
println!("End of stream reached");
pipeline
.set_state(gst::State::Ready)
.expect("Unable to set pipeline to the ready state");
}
gst::MessageView::Error(err) => {
println!(
"Error from {:?}: {} ({:?})",
err.get_src().map(|s| s.get_path_string()),
err.get_error(),
err.get_debug()
);
}
gst::MessageView::StateChanged(state_changed) => {
if state_changed
.get_src()
.map(|s| s == pipeline)
.unwrap_or(false)
{
println!("State set to {:?}", state_changed.get_current());
}
}
_ => (),
}
});
// start pipeline
playbin
.set_state(gst::State::Playing)
.expect("Unable to set the playbin to the `Playing` state");
// Start the GTK main loop. We will not regain control until gtk::main_quit(); is called.
gtk::main();
// Cleaning up
playbin
.set_state(gst::State::Null)
.expect("Unable to set the playbin to the Null state");
}
fn initialize_gtk_gstreaner() {
gtk::init().unwrap();
gst::init().unwrap();
}
fn create_ui(playbin: &gst::Element) {
// Instanciate window, button, sliders and register their event handlers
let main_window = Window::new(WindowType::Toplevel);
main_window.connect_delete_event(|_, _| {
gtk::main_quit();
Inhibit(false)
});
let pipeline = playbin.clone();
let play_button = gtk::Button::new_from_icon_name(
Some("media-playback-start"),
gtk::IconSize::SmallToolbar,
);
play_button.connect_clicked(move |_| {
// Add event handler to the event when the button was clicked
let pipeline = &pipeline;
pipeline
.set_state(gst::State::Playing)
.expect("Unable to set the pipeline to `Playing` state");
});
let pause_button = gtk::Button::new_from_icon_name(
Some("media-playback-pause"),
gtk::IconSize::SmallToolbar,
);
let pipeline = playbin.clone();
pause_button.connect_clicked(move |_| {
// Add event handler to the event when the button was clicked
let pipeline = &pipeline;
pipeline
.set_state(gst::State::Paused)
.expect("Unable to set the pipeline to the `Paused` state");
});
let stop_button = gtk::Button::new_from_icon_name(
Some("media-playback_stop"),
gtk::IconSize::SmallToolbar,
);
let pipeline = playbin.clone();
stop_button.connect_clicked(move |_| {
// Add event handler to the event when the button was clicked
let pipeline = &pipeline;
pipeline
.seek_simple(
gst::SeekFlags::FLUSH | gst::SeekFlags::KEY_UNIT,
0 * gst::MSECOND,
)
.expect("Failed to seek to start");
pipeline
.set_state(gst::State::Paused)
.expect("Unable to set the pipeline to the `Ready` state");
});
let slider =
gtk::Scale::new_with_range(gtk::Orientation::Horizontal, 0.0 as f64, 100.0 as f64, 1.0);
let pipeline = playbin.clone();
// Add event handler to the event when the slider was moved
let slider_update_signal_id = slider.connect_value_changed(move |slider| {
let pipeline = &pipeline;
let value = slider.get_value() as u64;
if pipeline
.seek_simple(
gst::SeekFlags::FLUSH | gst::SeekFlags::KEY_UNIT,
value * gst::SECOND,
)
.is_err()
{
eprintln!("Seeking to {} failed", value)
}
});
slider.set_draw_value(false);
// Query the position of the stream every 1 sec
let pipeline = playbin.clone();
let lslider = slider.clone();
gtk::timeout_add_seconds(1, move || {
let pipeline = &pipeline;
let lslider = &lslider;
if let Some(dur) = pipeline.query_duration::<gst::ClockTime>() {
let seconds = dur / gst::SECOND;
lslider.set_range(0.0, seconds.map(|v| v as f64).unwrap_or(0.0));
}
if let Some(pos) = pipeline.query_position::<gst::ClockTime>() {
let seconds = pos / gst::SECOND;
lslider.block_signal(&slider_update_signal_id);
lslider.set_value(seconds.map(|v| v as f64).unwrap_or(0.0));
lslider.unblock_signal(&slider_update_signal_id);
}
Continue(true)
});
// Pack UI in tool bar
let controls = Box::new(Orientation::Horizontal, 0);
controls.pack_start(&play_button, false, false, 0);
controls.pack_start(&pause_button, false, false, 0);
controls.pack_start(&stop_button, false, false, 0);
controls.pack_start(&slider, true, true, 2);
// Create video area
let video_window = DrawingArea::new();
let video_overlay = playbin
.clone()
.dynamic_cast::<gst_video::VideoOverlay>()
.unwrap();
video_window.connect_realize(move |video_window| {
let video_overlay = &video_overlay;
let gdk_window = video_window.get_window().unwrap();
if !gdk_window.ensure_native() {
println!("Can't create native window for widget");
process::exit(-1);
}
let display_type_name = gdk_window.get_display().get_type().name();
if display_type_name == "GdkX11Display" {
extern "C" {
pub fn gdk_x11_window_get_xid(
window: *mut glib::object::GObject,
) -> *mut c_void;
}
#[allow(clippy::cast_ptr_alignment)]
unsafe {
// Call native API to obtain the window pointer
let xid = gdk_x11_window_get_xid(gdk_window.as_ptr() as *mut _);
// Set destination with the handler
video_overlay.set_window_handle(xid as usize);
}
} else {
println!("Add support for display type {}", display_type_name);
process::exit(-1);
}
});
// Initialize stream list which shows the stream description available in the media file
let streams_list = gtk::TextView::new();
streams_list.set_editable(false);
let pipeline_weak = playbin.downgrade();
let streams_list_weak = glib::SendWeakRef::from(streams_list.downgrade());
let bus = playbin.get_bus().unwrap();
#[allow(clippy::single_match)]
bus.connect_message(move |_, msg| match msg.view() {
// application message is the message engineer can control
// You can send arbitary message, you can see some messages are sent from post_app_message
gst::MessageView::Application(application) => {
let pipeline = match pipeline_weak.upgrade() {
Some(pipeline) => pipeline,
None => return,
};
let streams_list = match streams_list_weak.upgrade() {
Some(streams_list) => streams_list,
None => return,
};
if application.get_structure().map(|s| s.get_name()) == Some("tags-changed") {
let textbuf = streams_list
.get_buffer()
.expect("Couldn't get buffer from text_view");
analyze_streams(&pipeline, &textbuf);
}
}
_ => (),
});
// Pack video region and stream info side bar
let vbox = Box::new(Orientation::Horizontal, 0);
vbox.pack_start(&video_window, true, true, 0);
vbox.pack_start(&streams_list, false, false, 2);
let main_box = Box::new(Orientation::Vertical, 0);
main_box.pack_start(&controls, false, false, 0);
main_box.pack_start(&vbox, true, true, 0);
main_window.add(&main_box);
main_window.set_default_size(640, 480);
main_window.show_all();
}
fn analyze_streams(playbin: &gst::Element, textbuf: >k::TextBuffer) {
textbuf.set_text("");
add_streams_info(playbin, textbuf, "video");
add_streams_info(playbin, textbuf, "audio");
add_streams_info(playbin, textbuf, "text");
}
fn add_streams_info(playbin: &gst::Element, textbuf: >k::TextBuffer, stype: &str) {
let propname: &str = &format!("n-{}", stype);
let signame: &str = &format!("get-{}-tags", stype);
// Stringify the stream information into gtk::TextBuffer
match playbin.get_property(propname).unwrap().get() {
Ok(Some(x)) => {
for i in 0..x {
let tags = playbin.emit(signame, &[&i]).unwrap().unwrap();
if let Ok(Some(tags)) = tags.get::<gst::TagList>() {
textbuf.insert_at_cursor(&format!("{} stream {}:\n ", stype, i));
if let Some(codec) = tags.get::<gst::tags::VideoCodec>() {
textbuf.insert_at_cursor(&format!(
" codec: {} \n",
codec.get().unwrap()
));
}
if let Some(lang) = tags.get::<gst::tags::LanguageCode>() {
textbuf.insert_at_cursor(&format!(
" language: {} \n",
lang.get().unwrap()
));
}
if let Some(bitrate) = tags.get::<gst::tags::Bitrate>() {
textbuf.insert_at_cursor(&format!(
" bitrate: {} \n",
bitrate.get().unwrap()
));
}
}
}
}
_ => {
eprintln!("Could not get {}!", propname);
}
}
}
fn post_app_message(playbin: &gst::Element) {
/*
* API is under changing in new gstreamer-rs version.
After the new version relased, you may need to code like below.
let _ = playbin.post_message(&gst::message::Application::new(gst::Structure::new_empty(
"tags-changed",
)));
*/
let _ = playbin.post_message(
&gst::message::Message::new_application(gst::Structure::new_empty("tags-changed"))
.build(),
);
}
}
#[cfg(feature = "tutorial5")]
fn main() {
tutorial5::run();
}
#[cfg(not(feature = "tutorial5"))]
fn main() {
println!("Please compile with --features tutorial5");
}
Workthrough
今回は長いです。しかし、大半の部分はUIの作成部分で本チュートリアルの目的と離れるので、本家のチュートリアルでも触れられている点を主に説明します。
今回もplaybinのみからなるパイプラインを扱います。追加でgtkも初期化しているだけで今までのチュートリアルと大きくは異なりません。
initialize_gtk_gstreaner(); // Initialize gtk and gstreamer
// Initialize playbin with single file source
let uri = "https://www.freedesktop.org/software/gstreamer-sdk/data/media/sintel_trailer-480p.webm";
let playbin = gst::ElementFactory::make("playbin", None).unwrap();
playbin.set_property("uri", &uri).unwrap();
次に、playbin上で発生する3つのシグナル(つまりイベント)へのハンドラーを登録します。video-tags-changed
,audio-tags-changed
,text-tags-changed
はそれぞれ、ストリームの中身が解析され、利用可能なストリームの種類が変わった際に通知されます。それぞれの要素にどのようなシグナルがあるか、はそれぞれの要素の説明に掲載されています。
それぞれのシグナルでのイベントハンドラの中で、それが送られたpipeline自体を取得し、post_app_message
関数を呼んでいます。これは、今回はGTK側でパイプラインを流れるストリーム情報を表示してもらいたいため、変更が生じた際に、任意のメッセージを創出できるapplicationメッセージを用いてGTK側に通知しています。
// Add event handler to be notified when video-tag was changed
playbin
.connect("video-tags-changed", false, |args| {
let pipeline = args[0]
.get::<gst::Element>()
.expect("Failed to get value in video-tags-changed argument")
.unwrap();
// This will send message to application thread
post_app_message(&pipeline);
None
})
.expect("Failed to connect to video-tag-changed");
// Add event handler to be notified when audio-tag was changed
playbin
.connect("audio-tags-changed", false, |args| {
let pipeline = args[0]
.get::<gst::Element>()
.expect("Failed to get value in audio-tags-changed argument")
.unwrap();
// This will send message to application thread
post_app_message(&pipeline);
None
})
.expect("Failed to connect to audio-tags-changed");
// Add event handler to be notified when audio-tag was changed
playbin
.connect("text-tags-changed", false, |args| {
let pipeline = args[0]
.get::<gst::Element>()
.expect("Failed to get value in text-tags-changed argument")
.unwrap();
// This will send message to application thread
post_app_message(&pipeline);
None
})
.expect("Failed to connect to text-tags-changed");
実際、post_app_message
関数では、tags-changed
という文字列だけを含んだメッセージを投げています。
fn post_app_message(playbin: &gst::Element) {
/*
* API is under changing in new gstreamer-rs version.
After the new version relased, you may need to code like below.
let _ = playbin.post_message(&gst::message::Application::new(gst::Structure::new_empty(
"tags-changed",
)));
*/
let _ = playbin.post_message(
&gst::message::Message::new_application(gst::Structure::new_empty("tags-changed"))
.build(),
);
}
その後、GTK+に関連するUIの作成処理全般を行う関数を呼んでいます。全体を把握するために、一度スキップしますが、あとで中身について見ていきます。
// Construct the ui
create_ui(&playbin);
今回はメインループはGTK側に任せるため、GStreamer側のバスの監視に以前までのループ形式は用いることができません。かわりに、メッセージが来たときだけコールバックを呼んでもらうことができます。シグナルを監視するのと同じように監視したいバスに対してconnect_message
を呼びます。メッセージが来たときの処理は今までのチュートリアルと同じですのでここでは割愛します。
// Instruct the bus to emit signals for each received message, and connect to the interesting signals
let bus = playbin.get_bus().unwrap();
bus.add_signal_watch();
let pipeline_weak = playbin.downgrade();
bus.connect_message(move |_, msg| {
let pipeline = match pipeline_weak.upgrade() {
Some(pipeline) => pipeline,
None => return,
};
match msg.view() {
gst::MessageView::Eos(..) => {
println!("End of stream reached");
pipeline
.set_state(gst::State::Ready)
.expect("Unable to set pipeline to the ready state");
}
gst::MessageView::Error(err) => {
println!(
"Error from {:?}: {} ({:?})",
err.get_src().map(|s| s.get_path_string()),
err.get_error(),
err.get_debug()
);
}
gst::MessageView::StateChanged(state_changed) => {
if state_changed
.get_src()
.map(|s| s == pipeline)
.unwrap_or(false)
{
println!("State set to {:?}", state_changed.get_current());
}
}
_ => (),
}
});
今までのチュートリアルどおりパイプラインをPlaying状態にします。そのうえでGTK+側のメインループを開始します。ウィンドウを閉じられない限り、以降の処理はGTK+によってループされているためブロックされます。
// start Playing
playbin
.set_state(gst::State::Playing)
.expect("Unable to set the playbin to the `Playing` state");
// Start the GTK main loop. We will not regain control until gtk::main_quit(); is called.
gtk::main();
最後に今までのチュートリアルどおりクリーンアップして終了です。
// Cleaning up
playbin
.set_state(gst::State::Null)
.expect("Unable to set the playbin to the Null state");
Creating UI
Windowを作ってからボタンを作成しています。ここでは、各ハンドラがmoveを使っているので毎回pipelineの所有権がハンドラの中に移ってしまいます。そのため、毎回pipeline.clone()
を呼んで同一の参照を作成しています。
// Instanciate window, button, sliders and register their event handlers
let main_window = Window::new(WindowType::Toplevel);
main_window.connect_delete_event(|_, _| {
gtk::main_quit();
Inhibit(false)
});
let pipeline = playbin.clone();
let play_button = gtk::Button::new_from_icon_name(
Some("media-playback-start"),
gtk::IconSize::SmallToolbar,
);
play_button.connect_clicked(move |_| {
// Add event handler to the event when the button was clicked
let pipeline = &pipeline;
pipeline
.set_state(gst::State::Playing)
.expect("Unable to set the pipeline to `Playing` state");
});
let pause_button = gtk::Button::new_from_icon_name(
Some("media-playback-pause"),
gtk::IconSize::SmallToolbar,
);
let pipeline = playbin.clone();
pause_button.connect_clicked(move |_| {
// Add event handler to the event when the button was clicked
let pipeline = &pipeline;
pipeline
.set_state(gst::State::Paused)
.expect("Unable to set the pipeline to the `Paused` state");
});
let stop_button = gtk::Button::new_from_icon_name(
Some("media-playback_stop"),
gtk::IconSize::SmallToolbar,
);
let pipeline = playbin.clone();
stop_button.connect_clicked(move |_| {
// Add event handler to the event when the button was clicked
let pipeline = &pipeline;
pipeline
.seek_simple(
gst::SeekFlags::FLUSH | gst::SeekFlags::KEY_UNIT,
0 * gst::MSECOND,
)
.expect("Failed to seek to start");
pipeline
.set_state(gst::State::Paused)
.expect("Unable to set the pipeline to the `Ready` state");
});
さらに、スライダーを作成します。スライダーは動かされたときにseek_simple
を呼び出し、また1秒ごとにパイプライン上のpositionを監視し、スライダにセットします。
let slider =
gtk::Scale::new_with_range(gtk::Orientation::Horizontal, 0.0 as f64, 100.0 as f64, 1.0);
let pipeline = playbin.clone();
// Add event handler to the event when the slider was moved
let slider_update_signal_id = slider.connect_value_changed(move |slider| {
let pipeline = &pipeline;
let value = slider.get_value() as u64;
if pipeline
.seek_simple(
gst::SeekFlags::FLUSH | gst::SeekFlags::KEY_UNIT,
value * gst::SECOND,
)
.is_err()
{
eprintln!("Seeking to {} failed", value)
}
});
slider.set_draw_value(false);
// Query the position of the stream every 1 sec
let pipeline = playbin.clone();
let lslider = slider.clone();
gtk::timeout_add_seconds(1, move || {
let pipeline = &pipeline;
let lslider = &lslider;
if let Some(dur) = pipeline.query_duration::<gst::ClockTime>() {
let seconds = dur / gst::SECOND;
lslider.set_range(0.0, seconds.map(|v| v as f64).unwrap_or(0.0));
}
if let Some(pos) = pipeline.query_position::<gst::ClockTime>() {
let seconds = pos / gst::SECOND;
lslider.block_signal(&slider_update_signal_id);
lslider.set_value(seconds.map(|v| v as f64).unwrap_or(0.0));
lslider.unblock_signal(&slider_update_signal_id);
}
Continue(true)
});
ツールバーとして一つのBoxにまとめています。GTK+の処理なので特に深い解説はありません。
// Pack UI in tool bar
let controls = Box::new(Orientation::Horizontal, 0);
controls.pack_start(&play_button, false, false, 0);
controls.pack_start(&pause_button, false, false, 0);
controls.pack_start(&stop_button, false, false, 0);
controls.pack_start(&slider, true, true, 2);
次にビデオ要素を作成してウィンドウが表示されたあとにウィンドウのポインタを取得し、GStreamerに渡して描画させます。
GTK+のDrawingWindowはconnect_realize
で生成されたあとのタイミングでイベントが取得できるのでそのタイミングでウィンドウのポインタを取得します。僕の環境はLinuxでX11上で動かしているのでX11のAPIを直接叩いてウィンドウのポインタを取得します。
他の環境の方はこちらの公式のチュートリアルを参考にMac用などの実装に入れ替えてください。https://gitlab.freedesktop.org/gstreamer/gstreamer-rs/-/blob/master/tutorials/src/bin/basic-tutorial-5.rs#L232
// Create video area
let video_window = DrawingArea::new();
let video_overlay = playbin
.clone()
.dynamic_cast::<gst_video::VideoOverlay>()
.unwrap();
video_window.connect_realize(move |video_window| {
let video_overlay = &video_overlay;
let gdk_window = video_window.get_window().unwrap();
if !gdk_window.ensure_native() {
println!("Can't create native window for widget");
process::exit(-1);
}
let display_type_name = gdk_window.get_display().get_type().name();
if display_type_name == "GdkX11Display" {
extern "C" {
pub fn gdk_x11_window_get_xid(window: *mut glib::object::GObject) -> *mut c_void;
}
#[allow(clippy::cast_ptr_alignment)]
unsafe {
// Call native API to obtain the window pointer
let xid = gdk_x11_window_get_xid(gdk_window.as_ptr() as *mut _);
// Set destination with the handler
video_overlay.set_window_handle(xid as usize);
}
} else {
println!("Add support for display type {}", display_type_name);
process::exit(-1);
}
});
}
さらに、ストリームの詳細を表示するTextViewを用意します。これは、先程post_app_messages
でアプリケーション固有のメッセージが送られたときに、パイプラインの情報を取得してその情報を表示します。
通常通り、パイプラインからbusを取得し、run()内で行ったようにメッセージのハンドラを登録します。この際、送られてきたメッセージ名がtags-changed
であるなら、つまり先程送ったメッセージであるならanalyze_streams
関数を呼び出します。
// Initialize stream list which shows the stream description available in the media file
let streams_list = gtk::TextView::new();
streams_list.set_editable(false);
let pipeline_weak = playbin.downgrade();
let streams_list_weak = glib::SendWeakRef::from(streams_list.downgrade());
let bus = playbin.get_bus().unwrap();
#[allow(clippy::single_match)]
bus.connect_message(move |_, msg| match msg.view() {
// application message is the message engineer can control
// You can send arbitary message, you can see some messages are sent from post_app_message
gst::MessageView::Application(application) => {
let pipeline = match pipeline_weak.upgrade() {
Some(pipeline) => pipeline,
None => return,
};
let streams_list = match streams_list_weak.upgrade() {
Some(streams_list) => streams_list,
None => return,
};
if application.get_structure().map(|s| s.get_name()) == Some("tags-changed") {
let textbuf = streams_list
.get_buffer()
.expect("Couldn't get buffer from text_view");
analyze_streams(&pipeline, &textbuf);
}
}
_ => (),
});
analyze_streams
はadd_streams_info
を、対象をvideo,audio,textごとに呼び出します。pipelineは様々なプロパティをもっていますが、n-video
、n-audio
、n-text
などでそれぞれの型に対応したストリームがいくつながれているか取得することができます。(https://gstreamer.freedesktop.org/documentation/playback/playbin.html?gi-language=c#playbin:n-audio)
その際に、get-audio-tags
などというシグナルを取得したい対象のインデックスとともにエミットした場合に、対応するコーデックや言語、ビットレートなどを取得できます。
(https://gstreamer.freedesktop.org/documentation/playback/playbin.html?gi-language=c#playbin::get-audio-tags)
これを用いて、TextBuffer自身を更新することでストリームの情報を出力します。
fn analyze_streams(playbin: &gst::Element, textbuf: >k::TextBuffer) {
textbuf.set_text("");
add_streams_info(playbin, textbuf, "video");
add_streams_info(playbin, textbuf, "audio");
add_streams_info(playbin, textbuf, "text");
}
fn add_streams_info(playbin: &gst::Element, textbuf: >k::TextBuffer, stype: &str) {
let propname: &str = &format!("n-{}", stype);
let signame: &str = &format!("get-{}-tags", stype);
// Stringify the stream information into gtk::TextBuffer
match playbin.get_property(propname).unwrap().get() {
Ok(Some(x)) => {
for i in 0..x {
let tags = playbin.emit(signame, &[&i]).unwrap().unwrap();
if let Ok(Some(tags)) = tags.get::<gst::TagList>() {
textbuf.insert_at_cursor(&format!("{} stream {}:\n ", stype, i));
if let Some(codec) = tags.get::<gst::tags::VideoCodec>() {
textbuf.insert_at_cursor(&format!(
" codec: {} \n",
codec.get().unwrap()
));
}
if let Some(lang) = tags.get::<gst::tags::LanguageCode>() {
textbuf.insert_at_cursor(&format!(
" language: {} \n",
lang.get().unwrap()
));
}
if let Some(bitrate) = tags.get::<gst::tags::Bitrate>() {
textbuf.insert_at_cursor(&format!(
" bitrate: {} \n",
bitrate.get().unwrap()
));
}
}
}
}
_ => {
eprintln!("Could not get {}!", propname);
}
}
}
Conclusion
このチュートリアルでは以下のことを扱いました。
- アプリケーションメッセージの取扱い。取得方法と送り方。
- ウィンドウハンドラを取得して、そこに直接GStreamerが書く方法。
- さまざまなsignalをrustを介して扱う方法
次回: 6月中?