Search code examples
gstreamerraspbianpython-gstreamer

Issue defining gstreamer video window on Raspbian


Using Gstreamer 'playbin' in python the 'set_window_handle' code tells Gstreamer the window id in which to render the video:

This works in standard Linux Mate and also on a Raspberry Pi 3 running Ubuntu-Mate. However, running on the same Raspberry but running an up to date Raspbian OS, Gstreamer totally ignores the instruction to run in a specific window, instead it creates its own window, smack in the middle of the screen.
This wouldn't be an issue, if the new window was capable of being manipulated, moved and/or resized but it can't. It cannot be moved, it cannot be closed and the mouse pointer vanishes behind it.
Does anyone know if this is a bug in Raspbian, X windows or Gstreamer or have I not spotted some change that has to be implemented for this to work on the Raspbian OS?
Here is a minimal, working example, which illustrates the behaviour described above.

#!/usr/bin/env python
import os,time
import wx
import gi
gi.require_version('Gst', '1.0')
gi.require_version('GstVideo', '1.0')
from gi.repository import Gst
from gi.repository import GstVideo

class Player(wx.App):

    def OnInit(self):
        window = wx.Frame(None)
        window.SetTitle("Gstreamer Player Test")
        window.SetSize((-1,-1))
        window.Bind(wx.EVT_CLOSE,self.close)
        vbox = wx.BoxSizer(wx.VERTICAL)
        hbox = wx.BoxSizer(wx.HORIZONTAL)
        self.fileid = wx.TextCtrl(window,style=wx.TE_PROCESS_ENTER)
        self.fileid.SetToolTipString("Enter full path to media file")
        hbox.Add(self.fileid, 1)
        self.start = wx.Button(window,label="Start")
        hbox.Add(self.start, 0)
        self.start.Bind(wx.EVT_BUTTON, self.control)
        self.fileid.Bind(wx.EVT_TEXT_ENTER, self.control)
        vbox.Add(hbox, 0, wx.EXPAND, 0)
        self.video_window = wx.Panel(window)
        vbox.Add(self.video_window,1,wx.EXPAND,5)
        window.SetSizer(vbox)
        window.Layout()
        window.Show()
        self.SetTopWindow(window)
        Gst.init(None) #initialise gstreamer
        self.player = Gst.ElementFactory.make("playbin", "player")
        bus = self.player.get_bus()
        bus.add_signal_watch() #hook up bus to signals from gstreamer
        bus.enable_sync_message_emission()
        bus.connect('message', self.on_message)
        bus.connect('sync-message::element', self.on_sync_message)
        return True

    def control(self, event):
        if self.start.GetLabel() == "Start":
            fileid = self.fileid.GetValue()
            if os.path.exists(fileid):
                self.start.SetLabel("Stop")
                fileid = "file://"+unicode(fileid)
                self.player.set_property('uri', fileid)
                self.player.set_state(Gst.State.PLAYING)
            else:
                print "File error - No such file"
        else:
            self.player.set_state(Gst.State.NULL)
            self.start.SetLabel("Start")

    def on_message(self, bus, message):
        t = message.type
        if t == Gst.MessageType.EOS: # media has ended
            self.player.set_state(Gst.State.NULL)
            self.button.SetLabel("Start")
        elif t == Gst.MessageType.ERROR:
            print "Player error"
            self.player.set_state(Gst.State.NULL)
            self.start.SetLabel("Start")

    def on_sync_message(self, bus, message):
        if message.get_structure() is None:
            return True
        message_name = message.get_structure().get_name()
        if message_name == 'prepare-window-handle': #Assign the window id to display in
            imagesink = message.src
            imagesink.set_property('force-aspect-ratio', True) #Force size to fit window
            X_id = self.video_window.GetHandle()
            print ("Window Id:", X_id)
            imagesink.set_window_handle(X_id)
        return True

    def close(self,event):
        self.player.set_state(Gst.State.NULL)
        time.sleep(0.1) #Allow a little time to reach Null state
        event.Skip()

app = Player()
app.MainLoop()

Solution

  • The answer to this is that it is a bug.
    Bugzilla link

    Whether it is in gstreamer or the Rpi stack seems a moot point.
    The resolution is to specify the videosink in the playbin pipeline.
    So for this particular program is should read as follows:

    #!/usr/bin/env python
    import os,time
    import wx
    import gi
    gi.require_version('Gst', '1.0')
    gi.require_version('GstVideo', '1.0')
    from gi.repository import Gst
    from gi.repository import GstVideo
    
    class Player(wx.App):
    
        def OnInit(self):
            window = wx.Frame(None)
            window.SetTitle("Gstreamer Player Test")
            window.SetSize((-1,-1))
            window.Bind(wx.EVT_CLOSE,self.close)
            vbox = wx.BoxSizer(wx.VERTICAL)
            hbox = wx.BoxSizer(wx.HORIZONTAL)
            self.fileid = wx.TextCtrl(window,style=wx.TE_PROCESS_ENTER)
            self.fileid.SetToolTipString("Enter full path to media file")
            hbox.Add(self.fileid, 1)
            self.start = wx.Button(window,label="Start")
            hbox.Add(self.start, 0)
            self.start.Bind(wx.EVT_BUTTON, self.control)
            self.fileid.Bind(wx.EVT_TEXT_ENTER, self.control)
            vbox.Add(hbox, 0, wx.EXPAND, 0)
            video_window = wx.Panel(window)
            vbox.Add(video_window,1,wx.EXPAND,5)
            window.SetSizer(vbox)
            window.Layout()
            window.Show()
            self.SetTopWindow(window)
            Gst.init(None) #initialise gstreamer
            self.X_id = video_window.GetHandle()
            self.player = Gst.ElementFactory.make("playbin", "player")
    #        self.ximagesink = Gst.ElementFactory.make("xvimagesink", None)
            self.ximagesink = Gst.ElementFactory.make("ximagesink", None)
            self.player.set_property('video-sink', self.ximagesink)
            bus = self.player.get_bus()
            bus.add_signal_watch() #hook up bus to signals from gstreamer
            bus.enable_sync_message_emission()
            bus.connect('message', self.on_message)
            bus.connect('sync-message::element', self.on_sync_message)
            return True
    
        def control(self, event):
            if self.start.GetLabel() == "Start":
                fileid = self.fileid.GetValue()
                if os.path.exists(fileid):
                    self.start.SetLabel("Stop")
                    fileid = "file://"+unicode(fileid)
                    self.player.set_property('uri', fileid)
                    self.player.set_state(Gst.State.PLAYING)
                else:
                    print "File error - No such file"
            else:
                self.player.set_state(Gst.State.NULL)
                self.start.SetLabel("Start")
    
        def on_message(self, bus, message):
            t = message.type
            if t == Gst.MessageType.EOS: # media has ended
                self.player.set_state(Gst.State.NULL)
                self.button.SetLabel("Start")
            elif t == Gst.MessageType.ERROR:
                print "Player error"
                self.player.set_state(Gst.State.NULL)
                self.start.SetLabel("Start")
    
        def on_sync_message(self, bus, message):
            if message.get_structure() is None:
                return True
            message_name = message.get_structure().get_name()
            if message_name == 'prepare-window-handle': #Assign the window id to display in
                imagesink = message.src
           #     imagesink.set_property('force-aspect-ratio', True) #Defaults anyway
                imagesink.set_window_handle(self.X_id)
            return True
    
        def close(self,event):
            self.player.set_state(Gst.State.NULL)
            time.sleep(0.1) #Allow a little time to reach Null state
            event.Skip()
    
    app = Player()
    app.MainLoop()
    

    However, if you are getting the same issue using "com.sun.star.comp.avmedia.Manager_GStreamer" in a LibreOffice macro, this does you no good whatsoever, because the Libreoffice developers made the same assumption that I did and there appears to be no way to define the videosink from within Libreoffice. In this case we will simply have to wait until the bug fix makes its way into a general release.

    Notes: use ximagesink
    using xvimagesink gives an error on Raspbian, as the Xv software has an issue where no adaptors are available.
    Using glimagesink gives us the dreaded window stuck in the middle of the screen and it seems that eglesssink has been quietly retired from service, as it is not longer included in the gstreamer plugins on the Raspberry.