Python GStreamer チュートリアル

⑦ ケイパビリティ

チュートリアル第7章。Capabilities(キャップ)の巻。


Capabilities、Gst.Caps は、Gst.PadTemplate に渡すことができる情報を格納するコンテナです。パイプラインの状態を再生または一時停止のいずれかに設定すると、エレメント内のパッドはストリームに使用するキャップをネゴシエートします。これで以下のパイプラインは完璧に動作します。

gst-launch-1.0  videotestsrc ! video/x-raw, width=320, height=240 ! xvimagesink

しかし、xvimagesinkximagesink に交換しようとすると、うまくいかないことに気づくでしょう。これは、ximagesink がvideo/x-rawを扱えないため、扱える要素をパイプライン内で ximagesinkの前に置く必要があるからです。

gst-launch-1.0  videotestsrc ! video/x-raw, width=320, height=240 ! videoconvert ! ximagesink

また、ximagesinkはハードウェアのスケーリングをサポートしていないため、ソフトウェアでスケーリングを行いたい場合は、ビデオスケール要素を追加する必要があります。

gst-launch-1.0  videotestsrc ! video/x-raw, width=320, height=240 ! videoscale ! videoconvert ! ximagesink

上記の例をコードに入れるには、capsfilter 要素の中にキャップを入れる必要があります。

# capabilities-example.py

import gi
gi.require_version("Gtk", "3.0")
from gi.repository import Gtk
gi.require_version("Gst", "1.0")
from gi.repository import Gst, GObject

class GTK_Main:

    def __init__(self):
        window = Gtk.Window(Gtk.WindowType.TOPLEVEL)
        window.set_title("Videotestsrc-Player")
        window.set_default_size(300, -1)
        window.connect("destroy", Gtk.main_quit, "WM destroy")
        vbox = Gtk.VBox()
        window.add(vbox)
        self.button = Gtk.Button("Start")
        self.button.connect("clicked", self.start_stop)
        vbox.add(self.button)
        window.show_all()
        self.player = Gst.Pipeline.new("player")
        source = Gst.ElementFactory.make("videotestsrc", "video-source")
        sink = Gst.ElementFactory.make("xvimagesink", "video-output")
        caps = Gst.Caps.from_string("video/x-raw, width=320, height=240")
        filter = Gst.ElementFactory.make("capsfilter", "filter")
        filter.set_property("caps", caps)
        self.player.add(source)
        self.player.add(filter)
        self.player.add(sink)
        source.link(filter)
        filter.link(sink)

    def start_stop(self, w):
        if self.button.get_label() == "Start":
            self.button.set_label("Stop")
            self.player.set_state(Gst.State.PLAYING)
        else:
            self.player.set_state(Gst.State.NULL)
            self.button.set_label("Start")

GObject.threads_init()
Gst.init(None)
GTK_Main()
Gtk.main()

よく聞かれる質問でファイルの解像度を調べる方法が問われることがありますが、その方法の一つに、一時停止状態のデコードビン要素のキャップをチェックする方法があります。

※ このコードを実行してもビデオの解像度情報は得られなかった。caps構造から数字を取り出すところがまずいっぽい。

                    width = caps.get_structure(0).get_int('width')
                    height = caps.get_structure(0).get_int('height')
# capabilities-resolution-example.py

import os
import gi
gi.require_version("Gtk", "3.0")
from gi.repository import Gtk
gi.require_version("Gst", "1.0")
from gi.repository import Gst, GObject

class GTK_Main:
    def __init__(self):
        window = Gtk.Window(Gtk.WindowType.TOPLEVEL)
        window.set_title("Resolutionchecker")
        window.set_default_size(300, -1)
        window.connect("destroy", Gtk.main_quit, "WM destroy")
        vbox = Gtk.VBox()
        window.add(vbox)
        self.entry = Gtk.Entry()
        self.entry.set_text("Sintel - Wikipedia.webm")
        vbox.pack_start(self.entry, False, True, 0)
        self.button = Gtk.Button("Check")
        self.button.connect("clicked", self.start_stop)
        vbox.add(self.button)
        window.show_all()

        self.player = Gst.Pipeline.new("player")
        source = Gst.ElementFactory.make("filesrc", "file-source")
        decoder = Gst.ElementFactory.make("decodebin", "decoder")
        decoder.connect("pad-added", self.decoder_callback)
        self.fakea = Gst.ElementFactory.make("fakesink", "fakea")
        self.fakev = Gst.ElementFactory.make("fakesink", "fakev")
        self.player.add(source)
        self.player.add(decoder)
        self.player.add(self.fakea)
        self.player.add(self.fakev)
        source.link(decoder)

        bus = self.player.get_bus()
        bus.add_signal_watch()
        bus.connect("message", self.on_message)

    def start_stop(self, w):
        filepath = self.entry.get_text().strip()
        if os.path.isfile(filepath):
            filepath = os.path.realpath(filepath)
            self.player.set_state(Gst.State.NULL)
            self.player.get_by_name("file-source").set_property("location", filepath)
            self.player.set_state(Gst.State.PAUSED)

    def on_message(self, bus, message):
        typ = message.type
        if typ == Gst.MessageType.STATE_CHANGED:
            if message.parse_state_changed()[1] == Gst.State.PAUSED:
                decoder = self.player.get_by_name("decoder")
                for pad in decoder.srcpads:
                    caps = pad.query_caps(None)
                    structure_name = caps.to_string()


                    width = caps.get_structure(0).get_int('width')
                    height = caps.get_structure(0).get_int('height')

                    print(structure_name[0:5], width, height )
                    print("..................................")

                    # width, heightの取得がうまくいかないので、以下のif文もTrueにならない。
                    if structure_name.startswith("video") and len(str(width)) < 6:
                        print("Width:%d, Height:%d" %(width, height))
                        self.player.set_state(Gst.State.NULL)
                        break

        elif typ == Gst.MessageType.ERROR:
            err, debug = message.parse_error()
            print("エラーです!: %s" % err, debug)
            self.player.set_state(Gst.State.NULL)

    def decoder_callback(self, decoder, pad):
        caps = pad.query_caps(None)
        structure_name = caps.to_string()
        if structure_name.startswith("video"):
            fv_pad = self.fakev.get_static_pad("sink")
            pad.link(fv_pad)
        elif structure_name.startswith("audio"):
            fa_pad = self.fakea.get_static_pad("sink")
            pad.link(fa_pad)

GObject.threads_init()
Gst.init(None)
GTK_Main()
Gtk.main()

次の例では、3章2節のプレイビンを使用して、そのビデオシンクを、いくつかの要素で満たされた自作のビンに切り替えます。さて、あなたがテレビ局を運営していて、画面の右上にロゴを入れたいとしましょう。そのためには textoverlay (テキストオーバーレイ)を使用できますが、ソースの解像度がどのようなものであってもフォントが画面上でまったく同じサイズになるようにするには、すべてのフォントが画面に応じてスケーリングされるように幅を指定しなければなりません。

※ このコードを実行してもビデオは再生されなかった。message.typeの型が違うような?

        typ = message.type
        if typ == Gst.MessageType.EOS:
# capabilities-playbin-example.py

import os
import gi
gi.require_version("Gtk", "3.0")
from gi.repository import Gtk
gi.require_version("Gst", "1.0")
from gi.repository import Gst, GObject

class GTK_Main:
    def __init__(self):
        window = Gtk.Window(Gtk.WindowType.TOPLEVEL)
        window.set_title("Video-Player")
        window.set_default_size(500, 400)
        window.connect("destroy", Gtk.main_quit, "WM destroy")
        vbox = Gtk.VBox()
        window.add(vbox)
        hbox = Gtk.HBox()
        vbox.pack_start(hbox, False, False, 0)
        self.entry = Gtk.Entry()
        self.entry.set_text("Sintel - Wikipedia.webm")
        hbox.add(self.entry)
        self.button = Gtk.Button("Start")
        hbox.pack_start(self.button, False, False, 0)
        self.button.connect("clicked", self.start_stop)
        self.movie_window = Gtk.DrawingArea()
        vbox.add(self.movie_window)
        window.show_all()


        # PlayBinの設定
        self.player = Gst.ElementFactory.make("playbin", "player")
        self.bin = Gst.Bin.new("my-bin")
        videoscale = Gst.ElementFactory.make("videoscale")
        videoscale.set_property("method", 1)
        pad = videoscale.get_static_pad("sink")
        ghostpad = Gst.GhostPad.new("sink", pad)
        self.bin.add_pad(ghostpad)
        caps = Gst.Caps.from_string("video/x-raw, width=720")
        myfilter = Gst.ElementFactory.make("capsfilter", "filter")
        myfilter.set_property("caps", caps)
        textoverlay = Gst.ElementFactory.make('textoverlay')
        textoverlay.set_property("text", "GNUTV")
        textoverlay.set_property("font-desc", "normal 14")
        conv = Gst.ElementFactory.make ("videoconvert", "conv")
        videosink = Gst.ElementFactory.make("autovideosink")

        self.bin.add(videoscale)
        self.bin.add(myfilter)
        self.bin.add(textoverlay)
        self.bin.add(conv)
        self.bin.add(videosink)

        videoscale.link(myfilter)
        myfilter.link(textoverlay)
        textoverlay.link(conv)
        conv.link(videosink)

        self.player.set_property("video-sink", self.bin)

        bus = self.player.get_bus()
        bus.add_signal_watch()
        bus.enable_sync_message_emission()
        bus.connect("message", self.on_message)
        bus.connect("sync-message::element", self.on_sync_message)

    def start_stop(self, w):
        if self.button.get_label() == "Start":
            filepath = self.entry.get_text().strip()
            if os.path.exists(filepath):
                filepath = os.path.realpath(filepath)
                print(filepath)
                self.button.set_label("Stop")
                self.player.set_property("uri", "file:///" + filepath)
                self.player.set_state(Gst.State.PLAYING)
        else:
            self.player.set_state(Gst.State.NULL)
            self.button.set_label("Start")

    def on_message(self, bus, message):

        print("on_messageのメッセージだよ", message.type)

        # message.typeをプリントすると<flags GST_MESSAGE_STATE...>
        # if文で期待しているものとは型が違うっぽい

        typ = message.type
        if typ == Gst.MessageType.EOS:
            self.player.set_state(Gst.State.NULL)
            self.button.set_label("Start")
        elif typ == Gst.MessageType.ERROR:
            self.player.set_state(Gst.State.NULL)
            self.button.set_label("Start")
            err, debug = message.parse_error()
            print("Error: %s" % err, debug)

    def on_sync_message(self, bus, message):
        if message.structure is None:
            return
        message_name = message.structure.get_name()
        print(message_name)

        if message_name == "prepare-xwindow-id":
            imagesink = message.src
            imagesink.set_property("force-aspect-ratio", True)
            imagesink.set_xwindow_id(self.movie_window.window.xid)

GObject.threads_init()
Gst.init(None)
GTK_Main()
Gtk.main()

capsを利用して解像度情報を得るコード、画面にテキストをかぶせるコード、どちらも動かなかった。怪しいところを抜き出したけれども、どう解決していいかわからない。

それよりも、細かいところにこだわるのを止めて、早くこのチュートリアルを終わらせたいです。

で、残りの8章・9章を見たところ、

  • 8章:ビデオミキサー サンプルコードは動かないらしい
  • 9章:ウェブカメラ・ビューワ

ということで、文字起こしツールにはあまり関係のない機能なので、GStreamer の勉強はここで終わりにします。


関連記事