wxPython and Gstreamer: two Mainloops

Plattformunabhängige GUIs mit wxWidgets.
Antworten
Nebelhom
User
Beiträge: 155
Registriert: Mittwoch 19. Mai 2010, 01:31

Ich benutze derzeit GStreamer in einer wxPython Anwendung und bin gestern auf das Problem gestossen, dass Gstreamer fuer das versenden seiner messages (End of Song etc., siehe auch das PyGst Playbin Tutorial) den MainLoop von Gobject benutzt, ich aber den wx.App.MainLoop brauche, um meine GUI darzustellen.
Da ich das Rad nicht neu erfinden will, wuerde ich gerne die beiden Mainloops gleichzeitig laufen lassen, bin mir aber nicht wirklich sicher, ob meine Loesung wirklich gut ist.

Ich habe das Problem mal mit threading geloest, da ich aber keinerlei Erfahrung mit threading habe und viele Horrorstories gelesen habe, wuerde ich gerne euren Rat fragen, ob ich das denn auch richtig gemacht habe. Es geht mir hauptsaechlich darum, ob es nicht entweder eine leichtere Loesung gibt oder diese Loesung mir potentiell spaeter in den Allerwertesten beisst, weil sich ein neues Problem auftut (Keine Ahnung. ein memory leak oder anderes schlimmes)

Hier ist ein sinngemaesses Minimalbeispiel, dass leider aber immernoch recht lang ist. Ich habe die entsprechenden Abschnitte versucht hervorzuheben (Python 2.6, wxPython 2.8, Gstreamer 0.10, Windows XP)

Vielen Dank schonmal fuer eure Hilfe.

EDIT: Ich sollte vielleicht noch erwaehnen, dass die Audiodatei sofort bei Start losspielen sollte.

Code: Alles auswählen

#!usr/bin/python

import os
import urllib
from threading import Thread

import wx
import pygst
pygst.require("0.10")
import gst
import gobject


class MyFrame(wx.Frame, Thread):
    def __init__(self, *args, **kwds):
        kwds["style"] = wx.DEFAULT_FRAME_STYLE
        wx.Frame.__init__(self, *args, **kwds)
        
        ###############
        ## Threading ##
        ###############
        self.t = GstThread()
        self.t.start()
        
        # Close Event
        self.Bind(wx.EVT_CLOSE, self.on_close)

        self.__set_properties()
        self.__do_layout()

    def on_close(self, event):
        self.t.quit()
        self.Destroy()

    def __set_properties(self):
        self.SetTitle("Test")

    def __do_layout(self):
        self.Layout()


class GstThread(Thread):
    def __init__(self):
        Thread.__init__(self)
        ##############################
        self.loop = gobject.MainLoop() # The mainloop to be separated from wx
        ##############################
        
        # This is almost straight out of the tutorial
        p = os.path.join("your", "path", "to", "your", "audiofile")
        path = urllib.pathname2url(p)
        
        self.player = gst.element_factory_make("playbin2", "player")
        fakesink = gst.element_factory_make("fakesink", "fakesink")
        self.player.set_property("video-sink", fakesink)
        
        self.player.set_property("uri", "file:" + path)
        self.player.set_state(gst.STATE_PLAYING)
        
    def run(self): # Called on Thread.start()
        self.loop.run()
        
    def quit(self):
        # If state not set to NULL, Critical Error occurs on gobject quit
        state = self.player.get_state()
        if state[1] == gst.STATE_PLAYING:
            self.player.set_state(gst.STATE_NULL)
            
        # Stops the gobject Mainloop
        self.loop.quit()

if __name__ == "__main__":
    gobject.threads_init()
    
    # wxGlade's my Friend here
    # Just basics
    app = wx.PySimpleApp(0)
    wx.InitAllImageHandlers()
    frame = MyFrame(None, -1, "")
    app.SetTopWindow(frame)
    frame.Show()
    app.MainLoop()
deets

Grundsaetzlich sieht das soweit ganz gut aus. Ich wuerde allerdings die Initialisierung des GstThread *nicht* in der GUI-Klasse machen. Denn das ist eher ein globales Objekt, da solltest du in der main-Funktion eines anlegen, und dann uU die Objekte jeweils miteinander bekannt machen, wenn das mal erforderlich sein sollte.
Nebelhom
User
Beiträge: 155
Registriert: Mittwoch 19. Mai 2010, 01:31

deets, danke für deinen kommentar.

Den Thread im GUI zu starten hat sich einfach angeboten, weil ich das beenden des Threads dann einfach mit dem Close Event verbinden konnte. Ich werde mir mal anschauen, wie ich das in meinem richtigen Skript realisieren kann und übertrage das dann auf das hier angegebene Minimalbeispiel.

Ich muss jetzt aber mal aus Interesse ganz blöd fragen. Warum ist es schlecht den Thread in der GUI zu starten? Oder ist das nur schlechte Form?
deets

Es ist schlechte Form, sonst nix. Fuer jemanden, der deinen Code verstehen will ist es viel leichter nachzuvollziehen, dass du zwei "Welten" hast, die ueber dezidierte Kanaele kommunizieren, anstatt das eine Welt die andere "gebiert". Natuerlich kannst und sollst du das Close-Event benutzen koennen, aber dann wuerde ich eben den instanziierten GST-Thread explizit an das Frame uebergeben. Der koennte ja auch mit allem moeglichen anderen kommunizieren wollen/muessen.
Nebelhom
User
Beiträge: 155
Registriert: Mittwoch 19. Mai 2010, 01:31

So, jetzt habe ich mir das mal angesehen und das Starten des gobject Mainloops komplett abgekapselt. Ich habe gemerkt, dass es egal ist, wo der in meinem Fall laeuft, so lange er "laeuft".

Den modifizierten Code habe ich mal unten angehaengt. Aus der GstThread Klasse wurde GstPlayer und GobjectThread. GobjectThread wurde dann vor dem Starten des wx.App.MainLoop gestartet und nach dem Mainloop beendet.

deets, ich nehme an, dass ist es, was du gemeint hast, oder?

Code: Alles auswählen

#!usr/bin/python

import os
import urllib
from threading import Thread

import wx
import pygst
pygst.require("0.10")
import gst
import gobject


class MyFrame(wx.Frame):
    def __init__(self, *args, **kwds):
        kwds["style"] = wx.DEFAULT_FRAME_STYLE
        wx.Frame.__init__(self, *args, **kwds)

        self.player = GstPlayer()
        
        # Close Event
        self.Bind(wx.EVT_CLOSE, self.on_close)

        self.__set_properties()
        self.__do_layout()

    def on_close(self, event):
        # If state not set to NULL, Critical Error occurs on gobject quit
        state = self.player.player.get_state()
        if state[1] == gst.STATE_PLAYING:
            self.player.player.set_state(gst.STATE_NULL)
        self.Destroy()

    def __set_properties(self):
        self.SetTitle("Test")

    def __do_layout(self):
        self.Layout()


class GstPlayer():
    def __init__(self):
        # This is almost straight out of the tutorial
        p = os.path.join("path", "to", "your", "audiofile")
        path = urllib.pathname2url(p)
        
        self.player = gst.element_factory_make("playbin2", "player")
        fakesink = gst.element_factory_make("fakesink", "fakesink")
        self.player.set_property("video-sink", fakesink)
        
        self.player.set_property("uri", "file:" + path)
        self.player.set_state(gst.STATE_PLAYING)
        
class GobjectThread(Thread):
    def __init__(self):
        Thread.__init__(self)
        ##############################
        self.loop = gobject.MainLoop() # The mainloop to be separated from wx
        ##############################
    
    def run(self): # Called on Thread.start()
        self.loop.run()
        
    def quit(self):
        # Stops the gobject Mainloop
        self.loop.quit()

if __name__ == "__main__":
    gobject.threads_init()
    
    t = GobjectThread()
    t.start()
    
    # wxGlade's my Friend here
    # Just basics
    app = wx.PySimpleApp(0)
    wx.InitAllImageHandlers()
    frame = MyFrame(None, -1, "")
    app.SetTopWindow(frame)
    frame.Show()
    app.MainLoop()
    
    t.quit() #End the gobject.MainLoop()
deets

Schon besser. Wobei ich mich frage, ob GstPlayer nicht ebenfalls ein globales Singleton sein sollte, das seinerseits den Thread verwaltet. Denn aus Programm-Sicht ist der Thread doch eher ein Implementationsdetail des Players, oder nicht?

Was in jedem Fall aber nicht gut ist, sind Aufrufe wie

Code: Alles auswählen

self.player.player....
Das verletzt "Demeters Law": wenn du einem Hund sagen willst "sitz" - dann sagst du das dem Hund, nicht jedem seiner Beine, was es tun soll. Und wenn dann statt Dackel irgendwann 6beinige Eidechsen der neueste Trend beim Schosstier sind, dann wuerde dein alter "Code" sitz nicht funktionieren. So ist's auch mit Software. Spendier GstPlayer start und stop-Methoden, und gut ist. Der GUI-Kram braucht ueber die Details darin nichts zu wissen.
Nebelhom
User
Beiträge: 155
Registriert: Mittwoch 19. Mai 2010, 01:31

Wobei ich mich frage, ob GstPlayer nicht ebenfalls ein globales Singleton sein sollte, das seinerseits den Thread verwaltet.
Ich habe das Beispiel hier nach dem Original gemodelt und da geht sowas leider nicht mehr.

Ich habe eine AudioFile Klasse, eine AudioPlayer Klasse, die auf AudioFile zugreift und wieder eine AudioPlayerUI Klasse, die auf AudioPlayer zugreift. Das schien am Anfang Sinn zu machen, falls ich eine externe Library (Gstreamer, wxpython) mal austauschen muss (Habe lange nach einer verlaesslichen Audiolibrary gesucht, die in Linux und Windows funktioniert). Ich bin mir jedoch nicht mehr so sicher, ob das eine tolle Idee war.

Egal, auf jeden Fall, da AudioPlayer auf AudioFile zugreift und AudioPlayerUI eben auf den AudioPlayer, kann ich das mit AudioFile nicht mehr bewerkstelligen ohne sehr viel Ummodellieren (gibt es dieses Wort ueberhaupt?) zu betreiben. Ich moechte das Programm, aber erstmal praesentabel fertigschreiben, bevor ich eine weitere Ueberarbeitung beginne.
Was in jedem Fall aber nicht gut ist, sind Aufrufe wie
Wieder was gelernt. Das werde ich in meinem Code auf jeden Fall abaendern, denn ab einer bestimmten Komplexitaet tue ich das aus der Not heraus doch eher haeufig. Dem Wiki Eintrag zu folgen bedeutet das, dass ich Methoden einsetze, wo direkt auf solche Attribute zugegriffen wird. Sehe ich das richtig?

deets, vielen dank fuer deine Hilfe. Da ich nie Informatik oder Aehnliches studiert habe, sind solche Kommentare immer willkommen. Also bitte nicht knauserig sein, falls dir mal wieder so etwas bei einem meiner Posts auffaellt.
Antworten