[ストリーミング技術]RustでGStreamerチュートリアル 5 : GUIとGStreamerの連携

初回: 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ツールキットとどのように連携させるかを学びます。この２つの連携がこのチュートリアルの重要な点です。特に、GStreamerの出力をGTK+に出力すること、また、GTK+上のインターフェースへの入力をGStreamerに流すことです。

* GStreamer専用のウィンドウを作らず、どのようにしてビデオを特定のウィンドウに流すか
* GStreamerから継続的に取得したデータを用いてどのようにしてGUIを更新するか
* ほとんどのGUIツールキットではメインスレッド以外からの操作は禁止されているが、GStreamerの複数のスレッドからどのようにしてGUIを操作するか。
* 興味があるメッセージだけを受信する方法。

今回は今まで習得した知識を用いてビデオプレイヤーを作成します。
![video-player-min.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/41402/84a9f24c-f670-4c01-a162-e30722a061e4.gif)


## 事前準備

今回はGTK+を利用するため追加でインストールしておくものがあります。

```shell
$ sudo apt-get install libgtk-3-dev
```

また、今回はgtk,gdkなどとともにgstreamer-videoも用います。また、他のチュートリアルのビルド時にはgtkやgdkは用いません。しかしこれらのライブラリは重いので、optionalにしておきます。
これを、featuresにライブラリ名を書いておくことによって、そのfeaturesフラグがオンであるときのみビルド時にそのライブラリを含めることができます。

```toml:Cargo.toml
[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`を行います。

```shell
$ cargo run --features tutorial5
```

このフラグをつけたときしか、gtk,gdkは使えません。

# Making video player with GStreamer

```rust:main.rs
// 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: &gtk::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: &gtk::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も初期化しているだけで今までのチュートリアルと大きくは異なりません。

```rust
    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`はそれぞれ、ストリームの中身が解析され、利用可能なストリームの種類が変わった際に通知されます。それぞれの要素にどのようなシグナルがあるか、はそれぞれの要素の説明に掲載されています。

* [audio-tags-changed](https://gstreamer.freedesktop.org/documentation/playback/playbin.html?gi-language=c#playbin::audio-tags-changed)
* [video-tags-changed](https://gstreamer.freedesktop.org/documentation/playback/playbin.html?gi-language=c#playbin::video-tags-changed)
* [text-tags-changed](https://gstreamer.freedesktop.org/documentation/playback/playbin.html?gi-language=c#playbin::text-tags-changed)

それぞれのシグナルでのイベントハンドラの中で、それが送られたpipeline自体を取得し、`post_app_message`関数を呼んでいます。これは、今回はGTK側でパイプラインを流れるストリーム情報を表示してもらいたいため、変更が生じた際に、任意のメッセージを創出できるapplicationメッセージを用いてGTK側に通知しています。

```rust
    // 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`という文字列だけを含んだメッセージを投げています。

```rust
    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の作成処理全般を行う関数を呼んでいます。全体を把握するために、一度スキップしますが、あとで中身について見ていきます。

```rust
        // Construct the ui
        create_ui(&playbin);
```

今回はメインループはGTK側に任せるため、GStreamer側のバスの監視に以前までのループ形式は用いることができません。かわりに、メッセージが来たときだけコールバックを呼んでもらうことができます。シグナルを監視するのと同じように監視したいバスに対して`connect_message`を呼びます。メッセージが来たときの処理は今までのチュートリアルと同じですのでここでは割愛します。

```rust
       // 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+によってループされているためブロックされます。

```rust
        // 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();
```

最後に今までのチュートリアルどおりクリーンアップして終了です。

```rust
        // 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()`を呼んで同一の参照を作成しています。

```rust
// 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を監視し、スライダにセットします。

```rust
        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+の処理なので特に深い解説はありません。

```rust
        // 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**

```rust
    // 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`関数を呼び出します。

```rust
        // 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自身を更新することでストリームの情報を出力します。

```rust

    fn analyze_streams(playbin: &gst::Element, textbuf: &gtk::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: &gtk::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月中?