[gst-devel] An autoplugger in 80 lines of code

Benjamin Otte in7y118 at public.uni-hamburg.de
Wed May 4 06:24:18 CEST 2005


... with 100 lines of comments.

For everyone that wants to know how the magic happens.
I've done it as part of my Python hacking, so it requires gst-python
0.8 CVS.
But it's more interesting to read it then to write it anyway.

Benjamin
-------------- next part --------------
#!/usr/bin/env python
#
# GStreamer python bindings
# Copyright (C) 2005 Benjamin Otte <otte at gnome.org>
#
# audioplug.py: a simple autoplugger
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Library General Public
# License as published by the Free Software Foundation; either
# version 2 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# Library General Public License for more details.
#
# You should have received a copy of the GNU Library General Public
# License along with this library; if not, write to the
# Free Software Foundation, Inc., 59 Temple Place - Suite 330,
# Boston, MA 02111-1307, USA.

# This tutorial shows how to write a simple autoplugger for GStreamer.
# An autoplugger is the element that magically selects the correct elements
# needed to play back a data stream. This autoplugger extracts audio from
# a given data stream. This is why it's called audioplug. So you could use 
# it in applations like music players. Or write your own little shell script
# to play back audio like this:
# #!/bin/sh
# gst-launch filesrc location=$1 ! audioplug ! alsasink
# For this tutroial I assume that you know how to write code in Python and that
# you know about the basics of GStreamer. So I will not explain what an element,
# caps or a reduce() function is.

# We start by importing the required modules
import gobject
import gst

# An autoplugger needs to be a bin, as it contains other elements. So we inherit
# from gst.Bin
class AudioPlug(gst.Bin):
    # This is the format we want to achieve. Those two media types are used for 
    # raw audio.
    targetcaps = gst.Caps ('audio/x-raw-int;audio/x-raw-float')
    # This declares the templates for our caps. We accept any input (we are the
    # ones doing the magic after all) and produce output in our target format.
    __gsttemplates__ = (
	gst.PadTemplate ('sink', gst.PAD_SINK, gst.PAD_ALWAYS, 'ANY'),
	gst.PadTemplate ('src', gst.PAD_SRC, gst.PAD_SOMETIMES, targetcaps)
    )
    # These are the element details, that provide some meta information about
    # this element. They are listed by gst-inspect for example. Try running:
    # gst-inspect audioplug
    __gstdetails__ = (
	'Python Autoplugger', 
	'Generic/Bin/Decoder', 
	'decode a file to raw audio', 
	'Benjamin Otte <otte at gnome.org>'
    )
    # This is the list of element factories that we use for autoplugging. We 
    # initialize it to the empty list here and fill it in later.
    factories = []
    # This is the initialization function. Here we setup our element.
    def __init__(self):
	# Do the standard GObject initialization stuff for our new element.
        self.__gobject_init__()
	# next we create a typefind element. The typefind element can detect
	# the media type of an unknown stream. It is part of libgstreamer, so
	# everyone has it installed.
	typefind = gst.element_factory_make ('typefind')
	self.add(typefind)
	# When the typefind element has found out what type the data is in, it 
	# emits a 'have-type' signal. When that happens, we want to connect the
	# next element.
        typefind.connect('have-type', self.have_type_cb)
	# Now we add a ghostpad to the autopluggger. This makes the typefind 
	# sinkpad appear to be a pad from the autoplugger which you may connect
	# elements to.
	pad = typefind.get_pad ('sink')
        pad = gst.GhostPad('sink', pad)
	self.add_pad (pad)
	# Now we're done. We'll now wait for the have_type_cb callback to fire.

    # This function returns True if a given element factory is suitable for 
    # autoplugging. There is lots of elements that make no sense for this. We
    # wouldn't want to plug another instance of this element for example, since
    # then we would end up in a loop of audioplug elements.
    # This filter function is modeled after the function used in the decodebin
    # autoplugger which is used by a lot of GStreamer applications (for example
    # Totem).
    def factory_filter(self, fac):
	'''filter to use when deciding if an elementfactory is suitable for autoplugging'''
	# Elements must have a rank. It is a common rule for autopluggers that 
	# elements with a rank of 0 do not get used by autopluggers.
	if fac.get_rank() <= 0:
	    return False
	# Next we remove all factories that do not have at least a source and a 
	# sink template. Such elements are endpoints (like filesrc or alsasink).
	def reduce_func (val, templ):
	    if templ.direction == gst.PAD_SRC:
		return [val[0], 1]
	    else:
		return [1, val[1]]
	if reduce (reduce_func, fac.get_pad_templates(), [0, 0]) != [1, 1]:
	    return False
	klass = fac.get_klass();
	# Finally we do a somewhat arbitrary decision to only use elements that 
	# contain 'Demux', 'Decoder' or 'Parse' in their element details' klass
	# field. Since we're a decoding autoplugger, we don't want elements like
	# encoders.
	if klass.find('Demux') >= 0 or klass.find('Decoder') >= 0 or klass.find('Parse') >= 0:
	    return True
	return False
    
    # This function gets all the element factories we use for autoplugging. It
    # basically just initializes the factories list with the above filter.
    def get_factories(self):
	if not self.factories:
	    factories = filter (self.factory_filter, gst.registry_pool_feature_list (gst.ElementFactory))
	return factories
      
    # This function finds the next element and inserts it into the autoplugger.
    # It uses the given caps argument to determine this. It then connects the
    # newly found element to the given one.
    def plug_next_element(self, element, pad, caps):
	# Check if we've already found our target caps. In that case we don't
	# need to find a new element to insert.
	if caps & self.targetcaps:
	    # Create another ghostpad on the src side of our autoplugger using
	    # the pad that has the desired caps. After this, the sink side of
	    # our autoplugger can be connected.
	    if not pad:
		pads = [pad for pad in element.get_pad_list() if pad.get_direction() == gst.PAD_SRC and
								 pad.get_caps() & caps]
		pad = pads[0]
	    pad = gst.GhostPad('src', pad)
	    self.add_pad (pad)
	    # We're done autoplugging right here.
	    return
	# Ok, we need to plug a new element. So we take all the factories we
	# can plug and select those that can connect with the given format.
	possibilities = [fac for fac in self.get_factories() if fac.can_sink_caps (caps)]
	# now loop through all factories until one is suitable
	next = None
	while not next:
	    # if there's no more possibilities and we haven't yet found a 
	    # suitable type, we cannot autoplug this format. In that case we
	    # error out.
	    # FIXME: Someone find a way to error out correctly please, like
	    #        binding GST_ELEMENT_ERROR
	    if not possibilities: 
		raise RuntimeError, 'we should properly fail here'
	    # Take the first possible factory and create an element from it.
	    next = possibilities[0].create()
	    # remove that factory from the list of possibilities. We don't want
	    # to try it again.
	    del possibilities[0]
	    # When the element could be created...
	    if next:
		# ... we add it to ourselves, synchronize its state with the
		# autoplugger's and try to link it to the element it should be
		# linked to. After that, we're done.
		self.add (next)
		next.sync_state_with_parent()
		try:
		    element.link_pads (pad, next, None)
		except gst.LinkError:
		    # If the linking fails, remove the element and try the next 
		    # one.
		    self.remove (next)
		    next = None
	# Now that we've pluged a new element, we try plugging from that 
	# element.
	self.try_plug_next_element(next)

    # This function tries to plug another element next to a newly inserted one.
    # Or if this doesn't work (yet), it prepares it for later.
    def try_plug_next_element(self, element):
	# Get all source pads of the element.
	pads = [pad for pad in element.get_pad_list() if pad.get_direction() == gst.PAD_SRC]
	# If source pads exits yet ...
	if pads:
	    # ... find all possible caps and go from there. using the function
	    # described above
	    caps = reduce (lambda caps, pad: caps | pad.get_caps(), pads, gst.Caps())
	    self.plug_next_element(element, None, caps)
	else:
	    # If there's no pads yet, they will be dynamically created. In that 
	    # case add a callback to the element, so we gt notified when that 
	    # has happened.
	    element.connect('new-pad', self.new_pad_cb)

    # This callback gets called when the typefind element found its type. It
    # then plugs the next element. See the __init__ function for details.
    def have_type_cb(self,typefind,prio,caps):
	self.plug_next_element(typefind, typefind.get_pad ('src'), caps)

    # This callback gets called when an element we couldn't plug earlier creates
    # a new pad. In that case we try to plug this pad. See try_plug_next_element
    # for details.
    def new_pad_cb(self,element,pad):
	self.plug_next_element(element, pad, pad.get_caps())

# Finally the autoplugger is done. Now it just has to be registered with the 
# glib type system and the GStreamer element list. After this, it can be used
# by all GStreamer applications.
gobject.type_register(AudioPlug)
gst.element_register(AudioPlug, "audioplug")


More information about the gstreamer-devel mailing list