2

I am attempting to write a Gstreamer Python program that will control the Whole Home Audio system in my house. The basic premise is that I will have multiple different options for sources (Pandora, MP3, Google Music, etc.) and will be able to play them in different "Zones" throughout my house. I have gotten it to work for dynamically adding a zone to a currently playing pipeline, but when I attempt to remove a zone the audio stops playing in all zones. Here is the relevant code, I can post more if necessary:

Basic Setup:

def __init__(self, username, password, zones=[]):
    # initialize the player
    self.player = gst.element_factory_make('playbin2', 'pandora_player')
    fakesink = gst.element_factory_make('fakesink', 'fakesink')
    self.player.set_property('video-sink', fakesink)

    # enable progressive download (GST_PLAY_FLAG_DOWNLOAD)
    self.player.props.flags |= (1 << 7)

    # create bin
    teebin = gst.element_factory_make('bin', 'master')
    tee = gst.element_factory_make('tee', 'tee')
    teebin.add(tee)
    ghost_pad = gst.GhostPad('sink', tee.get_pad('sink'))
    teebin.add_pad(ghost_pad)

    # set bin as audio sink
    self.player.set_property('audio-sink', teebin)

    # set volume
    self.player.set_property('volume', 0.01)

    bus = self.player.get_bus()
    bus.add_signal_watch()
    bus.connect('message', self.on_message)

    # make everything accessible
    self.tee = tee
    self.teebin = teebin


def add_zone(self, zone_id):
    # create out first audio output device
    zone_name = 'zone_{0}'.format(zone_id)
    device_name = 'mono{0}'.format(zone_id)
    bin_name = 'bin_{0}'.format(zone_id)
    queue_name = 'q_{0}'.format(zone_id)

    # wrap everything in a convenient zone object
    zone = gst.element_factory_make('bin', bin_name)

    # handle sending to the proper sound device
    zone_device = gst.element_factory_make('alsasink', zone_name)
    zone_device.set_property('device', device_name)

    # create a queue to handle asynchronous playback
    zone_queue = gst.element_factory_make('queue', queue_name)
    zone.add(zone_queue, zone_device)
    zone_queue.link(zone_device)

    # add sink into element
    zone_ghost = gst.GhostPad('sink', zone_queue.get_pad('sink'))
    zone.add_pad(zone_ghost)

    self.zones[zone_id] = zone

    self.teebin.add(zone)
    zone.sync_state_with_parent()
    self.tee.link(zone)

def remove_zone(self, zone_id):
    # get zone
    zone = self.zones[zone_id]

    # get src pad that is sending audio
    pad = zone.get_pad('sink').get_peer()

    # block src pad
    pad.set_blocked(True)

    # set zone state null
    zone.set_state(gst.STATE_NULL)

    # unlink and remove zone
    self.tee.unlink(zone)
    self.teebin.remove(zone)

    # remove zone reference
    del self.zones[zone_id]
Amazinzay
  • 85
  • 1
  • 7

1 Answers1

0

Probably your entire pipeline goes into PAUSE.

Try to follow the steps of the documentation

http://gstreamer.freedesktop.org/data/doc/gstreamer/head/manual/html/section-dynamic-pipelines.html

We can't just unlink element2's sinkpad from element1's source pad because that would leave element1's source pad unlinked and would cause a streaming error in the pipeline when data is pushed on the source pad. The technique is to block the dataflow from element1's source pad before we change element2 by element4 and then resume dataflow as shown in the following steps:

Block element1's source pad with a blocking pad probe. When the pad is blocked, the probe callback will be called.

Inside the block callback nothing is flowing between element1 and element2 and nothing will flow until unblocked.

Unlink element1 and element2.

Make sure data is flushed out of element2. Some elements might internally keep some data, you need to make sure not to lose data by forcing it out of element2. You can do this by pushing EOS into element2, like this:

Put an event probe on element2's source pad.

Send EOS to element2's sinkpad. This makes sure the all the data inside element2 is forced out.

Wait for the EOS event to appear on element2's source pad. When the EOS is received, drop it and remove the event probe.

Unlink element2 and element3. You can now also remove element2 from the pipeline and set the state to NULL.

Add element4 to the pipeline, if not already added. Link element4 and element3. Link element1 and element4.

Make sure element4 is in the same state as the rest of the elements in the pipeline. It should be at least in the PAUSED state before it can receive buffers and events.

Unblock element1's source pad probe. This will let new data into element4 and continue streaming.

The above algorithm works when the source pad is blocked, i.e. when there is dataflow in the pipeline. If there is no dataflow, there is also no point in changing the element (just yet) so this algorithm can be used in the PAUSED state as well.

P.R.
  • 3,061
  • 21
  • 40
  • I found that explanation. Unfortunately, they are talking about replacing one element with another. I am simply attempting to remove the element altogether. So I block the src pad as is recommended in the article you posted, but then I remove it completely because I don't need in anymore. – Amazinzay Jun 11 '13 at 23:38
  • Reconnecting element1 and element3 does not work? Another "trick" might be to insert a queue as a replacement – P.R. Jun 12 '13 at 18:24