Posts Tagged ‘gstreamer’

It’s MythTV, but lighter!

Monday, March 24th, 2008

Over the last two years I’ve been intermittently working on a light-weight frontend application to work with MythTV. Basically I was impatient with the slow loading-time and somewhat resource hungry nature of the proper MythTV frontend application. This is OK when I want to make use of the various PVR features, but sometimes I just want to tune to a digital radio channel and minimise the application or simply watch live TV, so I don’t need the advanced functionality MythTV offers.

And so LightMyth was born. LightMyth is still very much in development, but today I’ve got it working sufficiently to make a release – version 0.5. At the moment I’ve got it playing back audio-only channels perfectly, but TV playback is still somewhat broken (you can get audio or video, but not both at the same time) – this seems to be a threading issue.

LightMyth

LightMyth is written in Python (yes, maybe Python isn’t a great choice for a lightweight application, but writing the same thing in C or C++ would have taken a lot longer) and uses the very good GStreamer framework for the audio/video playback. It has a (currently very unpolished and feature-incomplete) GTK frontend. It is quite happy to work with a remote MythTV backend over the network and should be equally happy with a local backend running on the same machine.

There are three main reasons why such a simple-sounding application has taken me nearly two years to get functional. The first is that I’m lazy. The second is that when I started, GStreamer was somewhat immature and I was frequently coming up against bugs or missing features that were preventing me from doing what I needed – especially when it came to demultiplexing the MPEG2 transport streams from digital TV. These issues now seem to have all been resolved, and GStreamer is doing everything I need.
The third reason is that I was reverse-engineering the Myth protocol. You may well ask why, considering MythTV is Free/Open Source. Well trying to read someone else’s source is hard enough, but trying to decipher it and figure out the inner workings of a network protocol is even harder. I found it much easier to simply run Wireshark to capture the commands being sent and received by the MythTV frontend, then figure out what they’re doing and implement them into my application.

So I’ve made a fairly wild claim that my application is ‘lighter’. In a very, very non-scientific test I started the MythTV frontend, tuned to a radio channel (BBC 6 Music, in case you’re interested) and minimised it (note there’s no MHEG/interactive stuff enabled, so all its doing is receiving the MPEG stream over the network and playing it back). This consumed 33% of my CPU and 12% of my memory.
By comparison, my LightMyth-radio frontend playing back the same radio channel consumed just 1% of my CPU and 2.7% of my memory. I was pretty surprised by these figures – my main desire was to get a frontend that started-up quicker and I hadn’t really been concerned with CPU or memory usage, but the difference between the two is fairly substantial.

It’s fairly basic at the moment, but it has a nice configuration wizard to assist in setting it up. I will add basic support for the digital program guide at some point (maybe just now & next) but don’t expect any of the PVR functionality (scheduling or playing back recordings etc.) as this is not what LightMyth is for (this is what the main MythTV frontend is for).

If you’re feeling brave, you can go and grab a copy. The license is GPLv3. Any feedback or bug reports would be gratefully received, as would simple success/failure reports – there’s only so much I can test, due to lack of hardware. Digital satellite and cable playback is completely untested, for example…

Streaming with flumotion

Thursday, July 12th, 2007

After attending Thomas Vander Stichele’s Flumotion talk at LUGRadio Live 2007 and being suitably impressed, I figured I’d give it a go – partly because it looked cool and partly because I didn’t believe for a second that I could set up a server to compress and stream video and audio data with only a few clicks in the real-world. I am also well aware of how buggy GStreamer (on which it is based) has been when I’ve tried to use it for real-world projects in the past and was not convinced all the features flumotion requires would actually function as expected.

Well, it seems that actually you can get streaming going with a few clicks. After installing the flumotion package in Debian Lenny it was simply a case of running ‘flumotion-admin’ and working through the simple wizard, selecting my super-cheap webcam as the video source and OGG Theora as the codec. After that was done, I simply pointed VLC at the stream and off it went (you can of course use any media player you want, as long as it can handle HTTP streams and the codec you’re streaming in). It literally took about two minutes to get going. I did have to add the ‘flumotion’ user to the ‘video’ group so that it could access the webcam device, but this is a packaging issue rather than a flumotion one.

With my crappy webcam I can get a good enough picture using only 64Kbit/s of bandwidth. Possibly some tweaking could get it even lower, but it depends on the codec. It would be interesting to see how the BBC’s Dirac codec would perform in this environment. Capturing from my webcam (at low resolution) and encoding as Theora uses about 27% of CPU at 1GHz (no, I don’t really have such an old CPU – that’s reduced with frequency scaling in order to save power).

One irritating problem is that you can set up a stream, but as soon as the flumotion server is restarted it completely forgets about it, so all streams are forgotten each time you reboot. Probably I am missing something, but I can’t see an obvious way around this at the moment. Anyway, flumotion is pretty cool and it looks like finally, thanks to GStreamer, Linux’s lack of decent easy-to-use multimedia applications is starting to get fixed. Well done Fluendo for making this happen.

More Video Goodness with Python and GStreamer

Sunday, May 6th, 2007

Further to my previous post, Video goodness with Python and GStreamer, I’ve now mastered playback of video along with audio using Python and GStreamer. This is my code to play back an MPEG2 transport stream:

def gstInit(self):
  # declare our pipeline and GST elements
  self.pipeline = gst.Pipeline(“mypipeline”)
  self.src = gst.element_factory_make(“filesrc”, “src”);
  self.src.set_property(“location”, “file.mpeg”)
  self.demux = gst.element_factory_make(“ffdemux_mpegts”, “demux”)
  self.queue1 = gst.element_factory_make(“queue”, “queue1″)
  self.queue2 = gst.element_factory_make(“queue”, “queue2″)
  self.deinterlace = gst.element_factory_make(“ffdeinterlace”, “deinterlace”)
  self.vdecode = gst.element_factory_make(“mpeg2dec”, “vdecode”)
  self.adecode = gst.element_factory_make(“mad”, “adecode”)
  self.vsink = gst.element_factory_make(“xvimagesink”, “vsink”)
  self.asink = gst.element_factory_make(“alsasink”, “asink”)
  
  # add elements to the pipeline
  self.pipeline.add(self.src)
  self.pipeline.add(self.demux)
  self.pipeline.add(self.queue1)
  self.pipeline.add(self.queue2)
  self.pipeline.add(self.vdecode)
  self.pipeline.add(self.deinterlace)
  self.pipeline.add(self.adecode)
  self.pipeline.add(self.vsink)
  self.pipeline.add(self.asink)

  # we can’t link demux until the audio and video pads are added
  # we need to listen for ‘pad-added’ signals
  self.demux.connect(‘pad-added’, self.on_pad_added)

  # link all elements apart from demux
  gst.element_link_many(self.src, self.demux)
  gst.element_link_many(self.queue1, self.vdecode, self.deinterlace, self.vsink)
  gst.element_link_many(self.queue2, self.adecode, self.asink)

def on_pad_added(self, element, src_pad):
  caps = src_pad.get_caps()
  name = caps[0].get_name()
  # link demux to vdecode when video/mpeg pad added to demux
  if name == ‘video/mpeg’:
    sink_pad = self.queue1.get_pad(’sink’)
  elif name == ‘audio/mpeg’:
    sink_pad = self.queue2.get_pad(’sink’)
  else:
    return
  if not sink_pad.is_linked():
    src_pad.link(sink_pad)

def startPlayback(self):
  # start playback
  self.pipeline.set_state(gst.STATE_PLAYING)

Naturally you begin by declaring your pipeline and elements (for an introduction to GStreamer, see my previous post linked above and the link to Jono Bacon’s post included in that post).
Then you link your ‘filesrc’ to your demux element (variable depending on the type of content you’re playing).

Now, you cannot link anything to your demux element until you’ve started playback – this is because your demux element hasn’t yet looked at the data its being given, so it doesn’t know whether it should have audio and/or video pads. So you need a local function to handle the ‘pad-added’ signal which is sent when, you guessed it, a pad is added to an element – we only care about this signal when it relates to the demux element, so we connect that signal from that element to a local function. In this function we first need to determine what type of pad has been added – audio, video, or something else. In my case, because I’m playing MPEG, I know that I’m waiting for audio/mpeg and video/mpeg pads. The precise pads you need to wait for will depend on the content you’re playing.

When each pad is added, you link both the video and audio pads to separate ‘queue’ elements. Queue elements are essentially buffers and you need these for video and audio playback to work at the same time. You can link the audio queue element to your audio decoder and the decoder to your audio sink during initialisation – you don’t need to wait until you can link in the demux element. Likewise for the video queue element.

The equivalent if you want to launch playback from the command-line would be:

gst-launch filesrc location=file.mpeg ! ffdemux_mpegts name=demux ! { queue ! mpeg2dec ! xvimagesink } { demux. ! queue ! mad ! alsasink }

Things become simpler if you’re using a ‘decodebin’ or ‘playbin’ element rather than specifying your elements individually (which is what you’ll do if you want to play more than one type of content). In this case with a playbin you can simply add that one element to your pipeline, after giving it a URI to play, without worrying about what the content is.

def gstInit(self):
  # declare our pipeline and GST elements
  self.pipeline = gst.Pipeline(“mypipeline”)
  self.pbin = gst.element_factory_make(“playbin”, “pbin”);
  self.pbin.set_property(“uri”, “file:///home/user/file.mpeg”)
  
  # add elements to the pipeline
  self.pipeline.add(self.pbin)

def startPlayback(self):
  # start playback
  self.pipeline.set_state(gst.STATE_PLAYING)

And on the command line:

gst-launch playbin uri=file:///home/user/file.mpeg

You’re probably wondering why I went through all the complicated code at the beginning of this post, when you can just use a playbin as above? Well, if you are only going to be playing one type of content you can make initialisation of GStreamer quicker and more efficient if you specify your elements manually rather than asking GStreamer to detect which elements it needs and then set them up. If you are in the position of being able to specify your elements manually, you should do so. If not, then you should aim to use a decodebin element to automatically detect the decoders needed for the content and specify the src and sink element(s) manually.

I haven’t given an example of using a decodebin, because I couldn’t make it work (GStreamer continues to be pretty buggy – you may well find that you need to use development versions of plugins or even core libraries in order to make things work as they should).

Edit: I’ve put together a quick, fairly simplified PlayBin example for those wanting something downloadable they can play with. playbin-example.py.

GStreamer rocks!

Wednesday, October 4th, 2006

It’s dull and takes a long time to do anything? No, quite the opposite actually. I was rather dismayed to recently find that GStreamer was unable to play an MPEG2 transport stream, as neither of the two plugins that are supposed to be able to do this (ffdemux_mpegts which is part of GStreamer and flutsdemux which is produced by Fluendo) were working. So this morning I reported a bug against ffdemux_mpegts, mentioning in passing that the Fluendo plugin didn’t work either. Three hours later, both plugins have been fixed.

Rock on GStreamer devs!

Video goodness with Python and GStreamer

Thursday, September 14th, 2006

I’ve been working on a project for some time which involves using Python and GStreamer to display some video. The lack of documentation has been a PITA and I’ve been trying for some time to figure out how you get GStreamer to display its video output within a GTK widget. Well today, I figured it out. Thanks to this blog post by Jono Bacon I learned a lot more about GStreamer which motivated me to start looking at the problem again. His post urges everyone who can to document what they’re doing in Python and GStreamer, so here’s some example code to display some video within a GTK widget. The code is based on Jono’s example for an audio player.

#!/usr/bin/python
import pygst
pygst.require(“0.10″)
import gst
import pygtk
import gtk

class Main:
 def __init__(self):

  # Create GUI objects
  self.window = gtk.Window()
  self.vbox = gtk.VBox()
  self.da = gtk.DrawingArea()
  self.bb = gtk.HButtonBox()
  self.da.set_size_request(300,150)
  self.playButton = gtk.Button(stock=’gtk-media-play’)
  self.playButton.connect(“clicked”, self.OnPlay)
  self.stopButton = gtk.Button(stock=’gtk-media-stop’)
  self.stopButton.connect(“clicked”, self.OnStop)
  self.quitButton = gtk.Button(stock=’gtk-quit’)
  self.quitButton.connect(“clicked”, self.OnQuit)
  self.vbox.pack_start(self.da)
  self.bb.add(self.playButton)
  self.bb.add(self.stopButton)
  self.bb.add(self.quitButton)
  self.vbox.pack_start(self.bb)
  self.window.add(self.vbox)

  # Create GStreamer pipeline
  self.pipeline = gst.Pipeline(“mypipeline”)
  # Set up our video test source
  self.videotestsrc = gst.element_factory_make(“videotestsrc”, “video”)
  # Add it to the pipeline
  self.pipeline.add(self.videotestsrc)
  # Now we need somewhere to send the video
  self.sink = gst.element_factory_make(“xvimagesink”, “sink”)
  # Add it to the pipeline
  self.pipeline.add(self.sink)
  # Link the video source to the sink – xv
  self.videotestsrc.link(self.sink)
  self.window.show_all()

  def OnPlay(self, widget):
   print “play”
  # Tell the video sink to display the output in our DrawingArea
   self.sink.set_xwindow_id(self.da.window.xid)
   self.pipeline.set_state(gst.STATE_PLAYING)

  def OnStop(self, widget):
   print “stop”
   self.pipeline.set_state(gst.STATE_READY)

  def OnQuit(self, widget):
   gtk.main_quit()

start=Main()
gtk.main()

Download this code

We start off by creating our objects. Unlike Jono I’m not using glade to create the GUI because, for some reason, it won’t allow me to access the full range of functions/properties associated with the objects – perhaps my own fault. This part should be pretty self-explanatory for anyone who’s done GUI programming in other languages such as Java.
To understand the basics of GStreamer and pipelines, sources, sinks etc. refer to Jono’s post.

Here I’m using the videotestsrc which produces a testcard. To decode an actual file, you’d instead use a filesrc and decodebin and link them together. I then create an xvimagesink which will display the video using the xv functionality of X. There is also a sdlvideosink and a glvideosink – which you use will depend on what’s installed on the system of the user, but generally xvimagesink is the best choice. By default, any of the video/image sinks will simply create a new window in which to display their output, but we want to display it in the DrawingArea we created earlier.

This is achieved in the OnPlay function, where we get the DrawingArea’s ID using the window.xid property and pass it to the video sink we created earlier using the set_xwindow_id function. This tells the video sink to not create a new window, but use the DrawingArea instead. Note that we can’t get the window.xid property until after the window has been created, which is why we do it in the OnPlay function rather than before when we’re setting up the pipeline. Once that’s done, we can start the playback and enjoy the GStreamer video goodness. Note that this only displays video though – any audio content will be ignored. Getting audio and video playback working together is my next task and I shall endeavour to document that here once I figure it out.

Unfortunately I can’t show you a screenshot of the working script due to the way xv works using overlay – if you take a screen shot, you just get nothingness where the video is.