Search code examples
pythonaudiogstreamermixing

Adding and removing audio sources to/from GStreamer pipeline on-the-go


I wrote a little Python script which uses an Adder plugin to mix two source streams together.

After starting the program, you hear a 1kHz tone generated by the audiotestsrc plugin. When you press Enter, an another 500Hz test tone is connected to the Adder so you hear them together. (By the way, i don't really get why should i set the pipeline again to playing state here to hear the mix. Is there any way i can plug in new sources without having to restart the pipeline?)

When you press Enter once again, the 1kHz tone should be removed from the mix and the 500Hz tone should keep playing, but instead i hear nothing anymore. I get a pulse pulsesink.c:528:gst_pulsering_stream_underflow_cb:<pulseaudio_output> Got underflow in the debug output as the last line. I don't really know what to try next.

Here is the full source code:

#!/usr/bin/python
# On-the-go source removal doesn't work this way with GStreamer. Why?

import gobject;
gobject.threads_init()
import gst;

if __name__ == "__main__":
    pipe = gst.Pipeline("mypipe")

    adder = gst.element_factory_make("adder","audiomixer")
    pipe.add(adder)

    buzzer = gst.element_factory_make("audiotestsrc","buzzer")
    buzzer.set_property("freq",1000)
    pipe.add(buzzer)

    pulse = gst.element_factory_make("pulsesink", "pulseaudio_output")
    pipe.add(pulse)

    buzzer.link(adder)
    adder.link(pulse)
    pipe.set_state(gst.STATE_PLAYING)

    raw_input("1kHz test sound. Press <ENTER> to continue.")

    buzzer2=gst.element_factory_make("audiotestsrc","buzzer2")
    buzzer2.set_property("freq",500)

    pipe.add(buzzer2)
    buzzer2.link(adder)
    pipe.set_state(gst.STATE_PLAYING)

    raw_input("1kHz + 500Hz test sound playing simoultenously. Press <ENTER> to continue.")

    buzzer.unlink(adder)
    pipe.set_state(gst.STATE_PLAYING)

    raw_input("Only 500Hz test sound. Press <ENTER> to stop.")

Solution

  • I've found the solution on my own. I had to use request pads with Adder and use the pad blocking capability of GStreamer.

    Here's the working source code with some descriptions:

    #!/usr/bin/python
    
    import gobject;
    gobject.threads_init()
    import gst;
    
    if __name__ == "__main__":
        # First create our pipeline
        pipe = gst.Pipeline("mypipe")
    
        # Create a software mixer with "Adder"
        adder = gst.element_factory_make("adder","audiomixer")
        pipe.add(adder)
    
        # Gather a request sink pad on the mixer
        sinkpad1=adder.get_request_pad("sink%d")
    
        # Create the first buzzer..
        buzzer1 = gst.element_factory_make("audiotestsrc","buzzer1")
        buzzer1.set_property("freq",1000)
        pipe.add(buzzer1)
        # .. and connect it's source pad to the previously gathered request pad
        buzzersrc1=buzzer1.get_pad("src")
        buzzersrc1.link(sinkpad1)
    
        # Add some output
        output = gst.element_factory_make("autoaudiosink", "audio_out")
        pipe.add(output)
        adder.link(output)
    
        # Start the playback
        pipe.set_state(gst.STATE_PLAYING)
    
        raw_input("1kHz test sound. Press <ENTER> to continue.")
    
        # Get an another request sink pad on the mixer
        sinkpad2=adder.get_request_pad("sink%d")
    
        # Create an another buzzer and connect it the same way
        buzzer2 = gst.element_factory_make("audiotestsrc","buzzer2")
        buzzer2.set_property("freq",500)
        pipe.add(buzzer2)
    
        buzzersrc2=buzzer2.get_pad("src")
        buzzersrc2.link(sinkpad2)
    
        # Start the second buzzer (other ways streaming stops because of starvation)
        buzzer2.set_state(gst.STATE_PLAYING)
    
        raw_input("1kHz + 500Hz test sound playing simoultenously. Press <ENTER> to continue.")
    
        # Before removing a source, we must use pad blocking to prevent state changes
        buzzersrc1.set_blocked(True)
        # Stop the first buzzer
        buzzer1.set_state(gst.STATE_NULL)
        # Unlink from the mixer
        buzzersrc1.unlink(sinkpad1)
        # Release the mixers first sink pad
        adder.release_request_pad(sinkpad1)
        # Because here none of the Adder's sink pads block, streaming continues
    
        raw_input("Only 500Hz test sound. Press <ENTER> to stop.")