3

I have a code that currently takes one video and show it in screen using the gstreamer bindings for Python. I can seek the video when a "Forward" button is clicked at the player opened, and that is an important feature for me, hence the reason I don't want to use a parse to write a pipeline and send it to gst-launch.

What I want to do now is stream this video not only to a new opened window, but also (or only if I can't have both) via RTSP to open it at VLC or even another client over LAN. Is there any way to do that?

I am sorry, for the long code, but here it is:

import sys, os, time
import gi
gi.require_version('Gst', '1.0')
from gi.repository import Gst, GObject, Gtk
from gi.repository import GdkX11, GstVideo

class GTK_Main(object):

    def __init__(self):
        window = Gtk.Window(Gtk.WindowType.TOPLEVEL)
        window.set_title("Vorbis-Player")
        window.set_default_size(500, -1)
        window.connect("destroy", Gtk.main_quit, "WM destroy")
        vbox = Gtk.VBox()
        window.add(vbox)
        self.entry = Gtk.Entry()
        vbox.pack_start(self.entry, False, False, 0)
        hbox = Gtk.HBox()
        vbox.add(hbox)
        buttonbox = Gtk.HButtonBox()
        hbox.pack_start(buttonbox, False, False, 0)
        rewind_button = Gtk.Button("Rewind")
        rewind_button.connect("clicked", self.rewind_callback)
        buttonbox.add(rewind_button)
        self.button = Gtk.Button("Start")
        self.button.connect("clicked", self.start_stop)
        buttonbox.add(self.button)
        forward_button = Gtk.Button("Forward")
        forward_button.connect("clicked", self.forward_callback)
        buttonbox.add(forward_button)
        self.time_label = Gtk.Label()
        self.time_label.set_text("00:00 / 00:00")
        hbox.add(self.time_label)
        window.show_all()

        self.player = Gst.ElementFactory.make("playbin", "player")
        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.isfile(filepath):
                filepath = os.path.realpath(filepath)
                self.butto

    n.set_label("Stop")
                    self.player.set_property("uri", "file://" + filepath)
                    self.player.set_state(Gst.State.PLAYING)
                    time.sleep(1)
                    self.forward_callback(60)
                else:
                    self.player.set_state(Gst.State.NULL)
                    self.button.set_label("Start")

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

        def on_sync_message(self, bus, message):
            if message.get_structure().get_name() == 'prepare-window-handle':
                imagesink = message.src
                imagesink.set_property("force-aspect-ratio", True)
                imagesink.set_window_handle(self.movie_window.get_property('window').get_xid())

        def rewind_callback(self, w):
            rc, pos_int = self.player.query_position(Gst.Format.TIME)
            seek_ns = pos_int - 10 * 1000000000
            if seek_ns < 0:
                seek_ns = 0
            print ("Backward: %d ns -> %d ns" % (pos_int, seek_ns))
            self.player.seek_simple(Gst.Format.TIME, Gst.SeekFlags.FLUSH, seek_ns)

        def forward_callback(self, w):
            rc, pos_int = self.player.query_position(Gst.Format.TIME)
            if type(w) == int:
                seek_ns = w * 1000000000
            else:
                seek_ns = pos_int + 10 * 1000000000
            print ("Forward: %d ns -> %d ns" % (pos_int, seek_ns))
            self.player.seek_simple(Gst.Format.TIME, Gst.SeekFlags.FLUSH, seek_ns)

        def convert_ns(self, t):
            # This method was submitted by Sam Mason.
            # It's much shorter than the original one.
            s,ns = divmod(t, 1000000000)
            m,s = divmod(s, 60)

            if m < 60:
                return "%02i:%02i" %(m,s)
            else:
                h,m = divmod(m, 60)
                return "%i:%02i:%02i" %(h,m,s)


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

I found this code at this tutorial.

RMPR
  • 3,368
  • 4
  • 19
  • 31
João Gondim
  • 101
  • 1
  • 1
  • 9
  • 1
    I think this link has the solution you want: https://stackoverflow.com/questions/47396372/write-opencv-frames-into-gstreamer-rtsp-server-pipeline – Vítor Cézar Mar 07 '20 at 14:02
  • I've managed to run the solution for this post, the problem is within opencv frames reading. Streamed video has overlapped frames, but the appsrc part with the gstrtsp server might actually be the way here...I'll give it another try – João Gondim Mar 09 '20 at 13:15

1 Answers1

3

So, I ended up managing to make the link provided work. My problem was with the video's width and height, which must have the exact same values as the video you want to play, maybe something to do with opencv frames passing...also, the "is-live" property, which was set to true due to the use of webcam camera from the link's solution must be set to false (or not used at all since that is the default value) or else the video will begin with a certain delay in black screen. The code ended up being:

import cv2
import gi

gi.require_version('Gst', '1.0')
gi.require_version('GstRtspServer', '1.0')
from gi.repository import Gst, GstRtspServer, GObject


class SensorFactory(GstRtspServer.RTSPMediaFactory):
    def __init__(self, **properties):
        super(SensorFactory, self).__init__(**properties)
        self.cap = cv2.VideoCapture("path/to/video")
        self.number_frames = 0
        self.fps = 8
        self.duration = 1 / self.fps * Gst.SECOND  # duration of a frame in nanoseconds
        self.launch_string = 'appsrc name=source block=true format=GST_FORMAT_TIME ' \
                             'caps=video/x-raw,format=BGR,width=1280,height=720,framerate={}/1 ' \
                             '! videoconvert ! video/x-raw,format=I420 ' \
                             '! x264enc speed-preset=ultrafast tune=zerolatency ! queue ' \
                             '! rtph264pay config-interval=1 name=pay0 pt=96 '.format(self.fps)
        # streams to gst-launch-1.0 rtspsrc location=rtsp://localhost:8554/test latency=50 ! decodebin ! autovideosink

    def on_need_data(self, src, lenght):
        if self.cap.isOpened():
            ret, frame = self.cap.read()
            if ret:
                data = frame.tostring()
                #print(data)
                buf = Gst.Buffer.new_allocate(None, len(data), None)
                buf.fill(0, data)
                buf.duration = self.duration
                timestamp = self.number_frames * self.duration
                buf.pts = buf.dts = int(timestamp)
                buf.offset = timestamp
                self.number_frames += 1
                retval = src.emit('push-buffer', buf)
                #print('pushed buffer, frame {}, duration {} ns, durations {} s'.format(self.number_frames,
                #                                                                       self.duration,
                #                                                                       self.duration / Gst.SECOND))
                if retval != Gst.FlowReturn.OK:
                    print(retval)

    def do_create_element(self, url):
        return Gst.parse_launch(self.launch_string)

    def do_configure(self, rtsp_media):
        self.number_frames = 0
        appsrc = rtsp_media.get_element().get_child_by_name('source')
        appsrc.connect('need-data', self.on_need_data)


class GstServer(GstRtspServer.RTSPServer):
    def __init__(self, **properties):
        super(GstServer, self).__init__(**properties)
        self.factory = SensorFactory()
        self.factory.set_shared(True)
        self.get_mount_points().add_factory("/test", self.factory)
        self.attach(None)


GObject.threads_init()
Gst.init(None)

server = GstServer()

loop = GObject.MainLoop()
loop.run()
João Gondim
  • 101
  • 1
  • 1
  • 9
  • It seems you're overriding a method `RTSPMediaFactory.do_configure()` but it doesn't seem to be documented anywhere. Do you know where I can find further information on this? – balu Nov 20 '20 at 15:55
  • @balu the override was done by the person from this link https://stackoverflow.com/questions/47396372/write-opencv-frames-into-gstreamer-rtsp-server-pipeline maybe you can find some infos on the gstreamer docs – João Gondim Jan 19 '21 at 19:11
  • Thanks, I'll ask there! – balu Jan 20 '21 at 08:57