Posts Tagged ‘mythtv’

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…

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.

Video editing impossible on Linux?

Saturday, December 3rd, 2005

I’ve recently realised that MythTV is getting very close to filling my 200GB drive, so I’ve decided that I need to burn some stuff to DVD. This seems straightforward, the problem is that I need to edit each file to chop the ads out (MythTV has ad-detection and the ability to transcode and cut them out, but it only ever detects ads where there aren’t any). I’ve looked at every video editing tool I can find and none of them seem to be capable of doing the very simple task of letting me select and delete a section of video from an MPEG file. Here’s what I’ve tried:

  • Cinelerra: over-complicated interface, but most importantly it crashed when I tried to select a section of video I wanted to delete. Useless.
  • Kino: can only open DV files which FFmpeg seemingly can’t convert to without screwing with the video size and aspect ratio.
  • LiVES: 64-bit compilation broken.
  • kfilm: 64-bit binary has broken dependencies (requires libraries that don’t exist) and running make causes it to unexpectedly download a copy of FFmpeg (!) which doesn’t compile in any case.
  • PiTiVi: latest stable release has a bug which stops it running and the CVS has a dependancy which requires a newer version of GStreamer than my distro has. I’m not upgrading my whole distribution to an unstable development version just so I can run a video editor – the stable version is unstable enough as it is. Compiling GStreamer from source requires manually compiling about 10 different packages, which is not reasonable.
  • OpenVIP: uses a non-standard compilation process which gives screen-fulls of unhelpful output seemingly without doing anything useful. Couldn’t make sense of it.
  • kdenlive: again, only seems to support DV which is no use to me. Compile was broken too – the bug concerned seems to be fixed in CVS (which doesn’t compile due to other problems) but I created a patch to fix it in case other people encounter it before there’s a new release (current is 0.2.4).
  • LVE: lots of silly compile-time requirements – must be in a particular directory, must have a copy of FFmpeg source in a specific place, etc. FFmpeg doesn’t compile in any case because of the options passed to it by LVE’s make file – gave up.

So that’s it – every video editing tool I could find, none of which could meet my very basic requirements.
Situations such as this are very depressing to say the least.