0

I have a simple Python program creates an RTSP stream using gst-rtsp-server. It works, but as-is there's no error handling. If the pipeline has a typo or there's some issue connecting to the video source, I don't see a stack trace or any logging. Where would I hook in code to handle problems like this?

I should mention that I'm a complete beginner to the GObject world. I suspect there is a standard way for these libraries to report errors but I haven't been able to find anything in the documentation I've read about how that's done.

In case it's helpful, here is my code as I have it right now:

from threading import Thread
from time import sleep
import signal

import gi
gi.require_version("Gst", "1.0")
gi.require_version("GstRtsp", "1.0")
gi.require_version("GstRtspServer", "1.0")
from gi.repository import GLib, GObject, Gst, GstRtsp, GstRtspServer

PIPELINE = (
    "( videotestsrc ! vp8enc ! rtpvp8pay name=pay0 pt=96 )")


def main():
    GObject.threads_init()
    Gst.init(None)

    server = GstRtspServer.RTSPServer.new()
    server.props.service = "3000"

    server.attach(None)

    loop = GLib.MainLoop.new(None, False)

    def on_sigint(_sig, _frame):
        print("Got a SIGINT, closing...")
        loop.quit()
    signal.signal(signal.SIGINT, on_sigint)

    def run_main_loop():
        loop.run()

    main_loop_thread = Thread(target=run_main_loop)

    main_loop_thread.start()

    media_factory = GstRtspServer.RTSPMediaFactory.new()
    media_factory.set_launch(PIPELINE)
    media_factory.set_shared(True)
    server.get_mount_points().add_factory("/test", media_factory)
    print("Stream ready at rtsp://127.0.0.1:3000/test")


    while loop.is_running():
        sleep(0.1)


if __name__ == "__main__":
    main()
Velovix
  • 527
  • 1
  • 6
  • 19
  • 1
    I don't currently have access to an environment where I can test this myself, but I expect you should be able to get access to the `gst_parse_launchv ()` function, which should allow you to validate the `PIPELINE` before you actually go and use it. https://gstreamer.freedesktop.org/data/doc/gstreamer/head/gstreamer/html/gstreamer-GstParse.html – Grismar Jan 17 '19 at 01:00
  • You're right, I'm able to call Gst.parse_launch and it throws an exception if the pipeline is invalid. That's a step in the right direction! – Velovix Jan 17 '19 at 01:17
  • 1
    It seems like the expectation is to increase the log verbosity to see errors, which helps somewhat for development but doesn't allow me to handle them in code. To increase the log level, change the GST_DEBUG environment variable. The values are described here: https://gstreamer.freedesktop.org/data/doc/gstreamer/head/gstreamer/html/gst-running.html – Velovix Jan 18 '19 at 01:17

2 Answers2

2

So you can override do_handle_message in Gst.Bin in the following way:

import gi
gi.require_version("Gst", "1.0")
from gi.repository import Gst, GLib
Gst.init(None)

class SubclassBin(Gst.Bin):
    def do_handle_message(self, message):
        if message.type == Gst.MessageType.ERROR:
            error, message = message.parse_error()
            # TODO: Do something with the error
        # Call the base Gst.Bin do_handle_message
        super().do_handle_message(self, message)

subclass_bin = SubclassBin("mybin")

That said, I'm not sure how to tell GstRtspServer.RTSPMediaFactory to use SubclassBin instead of Gst.Bin because as far as I can tell, the only way to connect any Bin to RTSPMediaFactory is through the set_launch method which wants a pipeline string instead of a prebuilt bin. If there was a way of adding a prebuilt bin to RTSPMediaFactory, this would be a complete answer...but unfortunately this is as far as I can get.

bkanuka
  • 907
  • 7
  • 18
  • You could build the bin by "manually" adding elements to an instance of `SubclassBin` and then override `RTSPMediaFactory.do_create_element` to return this SubclassBin instance. However, I have not yet found a solution that allows you to build the pipeline using a `parse_launch` string. – bkanuka Sep 06 '19 at 21:58
  • For anyone who wants to keep using `parse_launch`, potential solution could be [this](https://stackoverflow.com/questions/61604103/where-are-gstreamer-bus-log-messages/61819517#61819517) one. – Dusan Kovacevic May 15 '20 at 13:01
1

Edit: I would recommend using bkanuka's solution (Subclassing Gst.Bin) instead of the below one, which has many drawbacks.

After some more experimentation with the GStreamer and the RTSP server library, the error handling situation is complicated.

The Normal Way

The canonical way to see errors on a GStreamer pipeline is to add a watcher to the pipeline's bus and listen for error messages.

def watcher(bus, message, *user_data);
    if message.type == Gst.MessageType.ERROR:
        error, message = message.parse_error()
        # TODO: Do something with the error

my_pipeline.get_bus().add_watch(
    GLib.PRIORITY_DEFAULT,
    watcher,
    None)

However, you cannot do this with pipelines you supply to a GstRtspServer. This is because GstRtspServer expects to be able to install its own watcher on the pipeline's bus and only one watcher can be attached to a bus at once. This is especially unfortunate because this prevents us from listening to any events on the pipeline, not just errors.

My Workaround

We can split the pipeline into two pieces: one that's in charge of the error-prone process of connecting to the source and decoding frames, and another that is in charge of encoding the resulting frames and payloading them for the GstRtspServer. We can then use the intervideo plugin to communicate between the two.

For example, let's say you're trying to stream from a file in the VP8 format. Our first pipeline that's in charge of reading and decoding frames would look like this:

filesrc location="{filepath}" ! decodebin ! intervideosink channel="file-channel"

... and our second pipeline that's in charge of encoding and payloading the frame would look like this:

intervideosrc channel="file-channel" ! videoconvert ! vp8enc deadline=1 ! rtpvp8pay name=pay0 pt=96

The key here is that only the second pipeline has to be managed by GstRtspServer, since it's the pipeline that provides the payloaded data. The first one is managed by us and we can attach our own watcher to it that responds intelligently to errors and does whatever else we need. This of course is not a perfect solution because we aren't able to respond to errors related to encoding and payloading, but we've gained the ability to receive errors related to reading the file and decoding it. We're now also able to do other message-related tasks, like intercepting an end-of-stream message to loop the video file.

Velovix
  • 527
  • 1
  • 6
  • 19
  • I've created an issue on the gst-rtsp-server repository here: https://gitlab.freedesktop.org/gstreamer/gst-rtsp-server/issues/67 – Velovix May 31 '19 at 00:22
  • do you have an example of subclassing GstBin and overriding handle_message() ? intervideo won't work for me because I'm not doing any decoding. – bkanuka Sep 05 '19 at 11:43
  • No, and I haven't been able to get the suggestion on that issue to work after trying it for the first time today. I opened an issue on PyGObject: https://gitlab.gnome.org/GNOME/pygobject/issues/360 . I'll edit this answer when I get to the bottom of this. – Velovix Sep 05 '19 at 19:42
  • I should be leaving this comment on the pygobject bug you opened but you shouldn't have to call `new()` at all. Just use the class like a standard python class. – bkanuka Sep 06 '19 at 20:54