Streaming audio over TCP with python-gstreamer

by Sander Marechal

I've fallen in love with Python. I'm always on the lookout for a good excuse to learn a new programming language. Since Lua is backwards-incompatible (and most non-Debian based Linux distro's ship Lua in such a way that you cannot run multiple versions at the same time), I wanted to switch the AI script language for Gnome Hearts to Python. I'm still working on that and now I'm hooked. So, when I wanted something that lets me play my music collection from my server to my stereo I took python for a good test drive.

Anoher thing I wanted an excuse for was learning gstreamer, so naturally I picked python-gstreamer. Sadly there's a huge lack of documentation for it. There are only a few good tutorials around, first and foremost Jona Bacon's excellent tutorials [1][2][3]. I wanted my hack to work over TCP as well, because I have multiple music libraries. One on my home server, one on my desktop, a bit more on my laptop, etcetera. I want to be able to stream from other machines to my server which I'll hook up to my stereo. I could not find any tutorials on using tcpserversource and tcpclientsink elements, so honoring Jona's request that everyone “should write an article about something that you have discovered that isn’t particularly well documented”, here's mine about tcpserversource and tcpclientsink.

TCP streaming using gst-launch

Using gst-launch-0.10 is the easiest way to see if a pipeline will actually work or not. It's quicker to change a few commandline variables than it is to change your program every time. It took my a few hours before I found the right one due to two causes:

  • Caps (format descriptors) are not automagically negotiated when you use tcp elements. According to the scarce documentation I could find, using setting the protocol to GST_TCP_PROTOCOL_GDP should enable negotiation, but I could not make this work.
  • Apparently the caps for the audiotestsrc that I was trying to use most of the time could not be negotiated at all.

What I ended up doing was simply streaming the still compressed audiofile before any caps negotiation has taken place. Aside from making my pipeline actually work it has the added benefit for me that it uses less bandwidth. All caps negotiation then happens at the server side. Here are the server and client pipelines that actually worked for me. You can run them both on the same machine simply by setting the host to localhost and using two xterms. Make sure you start the server before starting the client.

  1. you@server$ gst-launch-0.10 tcpserversrc host=localhost port=3000 ! decodebin ! audioconvert ! alsasink
  2. you@client$ gst-launch-0.10 filesrc location=/home/you/song.ogg ! tcpclientsink host=localhost port=3000

Putting it all in Python

With the gst-launch pipelines sorted out it's trivial to implement the same thing in Python. The server code:

  1. #!/usr/bin/env python
  2.  
  3. # This is some example code for python-gstreamer.
  4. # It's a gstreamer TCP/IP server that listens on
  5. # the localhost for a client to send data to it.
  6. # It shows how to use the tcpserversrc and tcpclientsink
  7. # elements.
  8.  
  9. import gobject, pygst
  10. pygst.require("0.10")
  11. import gst
  12.  
  13. # Callback for the decodebin source pad
  14. def new_decode_pad(dbin, pad, islast):
  15.         pad.link(convert.get_pad("sink"))
  16.  
  17. # create a pipeline and add [tcpserversrc ! decodebin ! audioconvert ! alsasink]
  18. pipeline = gst.Pipeline("server")
  19.  
  20. tcpsrc = gst.element_factory_make("tcpserversrc", "source")
  21. pipeline.add(tcpsrc)
  22. tcpsrc.set_property("host", "127.0.0.1")
  23. tcpsrc.set_property("port", 3000)
  24.  
  25. decode = gst.element_factory_make("decodebin", "decode")
  26. decode.connect("new-decoded-pad", new_decode_pad)
  27. pipeline.add(decode)
  28. tcpsrc.link(decode)
  29.  
  30. convert = gst.element_factory_make("audioconvert", "convert")
  31. pipeline.add(convert)
  32.  
  33. sink = gst.element_factory_make("alsasink", "sink")
  34. pipeline.add(sink)
  35. convert.link(sink)
  36.  
  37. pipeline.set_state(gst.STATE_PLAYING)
  38.  
  39. # enter into a mainloop
  40. loop = gobject.MainLoop()
  41. loop.run()

And the client code:

  1. #!/usr/bin/env python
  2.  
  3. # This is some example code for python-gstreamer.
  4. # It's a gstreamer TCP/IP client that sends a file
  5. # through a tcpclientsink to a server on localhost
  6.  
  7. import gobject, pygst
  8. pygst.require("0.10")
  9. import gst
  10.  
  11. # create a pipeline and add [ filesrc ! tcpclientsink ]
  12. pipeline = gst.Pipeline("client")
  13.  
  14. src = gst.element_factory_make("filesrc", "source")
  15. src.set_property("location", "/home/you/song.ogg")
  16. pipeline.add(src)
  17.  
  18. client = gst.element_factory_make("tcpclientsink", "client")
  19. pipeline.add(client)
  20. client.set_property("host", "127.0.0.1")
  21. client.set_property("port", 3000)
  22. src.link(client)
  23.  
  24. pipeline.set_state(gst.STATE_PLAYING)
  25.  
  26. # enter into a mainloop
  27. loop = gobject.MainLoop()
  28. loop.run()

I hope this has been useful to you. Happy coding!

Creative Commons Attribution-ShareAlike

Comments

#1 blaxeep

really helpful post!
I am a student in TUC (Greece) and your code was really helpful for me to understand what's going on!
It's about a project that implements a mail application in which users will send video-mail instead of e-mails :P
thnx!

#2 Diego Tejera

awesome, thnx for the tutorial! (am a student from Panama) also very helpful with a little project am making. Thanks!!

#3 Anonymous

Thanks for this nice tutorial !!!

Note that, if you want to stream data from one computer to another,
you have to fill the host property of the client AND the server with
the server's IP address...

#4 Anonymous Coward

hi,
very simple, easy to understand, appreciate you

a question: is there a similar simple way for live streaming?

#5 Sander Marechal (http://www.jejik.com)

The easiest way for live streaming would simply be to use an IceCast server. It comes with all the bits you need. If you really need programatic control over the stream then you could use a simple gstreamer application that dumps to an IceCast sink and then broadcast with IceCast.

#6 Hassan

Hi,

I am a student from Pakistan. Your tutorial is pretty good, gave me some help for what I was looking for. Its not exactly this but put me on the right path.

Cheers :)

#7 Anonymous Coward (http://153.19.231.17)

thx a lot, can You (or somebody) put a similary code but where transmission is realized over RTP

#8 Tulga (http://melug.blogspot.com)

your tutorial is very helpful, i'll use your idea to my project. few weeks ago i was looking for way to using ffmpeg and python, gtk together. but ended up using gstream. yep, there's not enough documentation. i'll write the documentation in own language. again, thank you very much. from Mongolia.

#9 Antoine Martin (http://devloop.org.uk)

Great example. Nice and simple.

One question though, how do I make it re-start after the first client disconnects?
It just seems to get stuck and you have to kill the tcpserversrc script and start it again.
I tried catching the EOS and unlink()ing the elements after the tcpserversrc (then re-adding new ones), but the exact incantions required are beyond me at this point.

How about allowing multiple concurrent connections? Is that even possible using the same port?

Also, when I run it across the network it seems to stutter a bit (despite the fact there is lots of bandwidth to spare), even more so when tunneled over ssh, how can I get rid of that problem? (increase buffering somehow?)

Many thanks

#10 Sander Marechal (http://www.jejik.com)

Hi Antoine. I don't know about restarting the server or allowing multiple connections. I played with this code in order to make a jukebox server, but after this experiment I simplified my design. Instead of doing the streaming myself over TCP, I simply use shout2send and drop to an IceCast server. IceCast is designed to stream to multiple concurrent connections. If you want to take a look at my jukebox code, you can find it http://svn.jejik.com/viewvc.cgi/jukebox/.

As for the stuttering, try introducing some queue elements to act as buffers.
Post a new comment

Registration is not required to post comments, but cookies must be enabled. One of the advantages of registration is that you can edit your comments later on (editing not yet implemented). You can register or login here.




Your e-mail address will not be published, but your website URL will. All links that you post will tagged rel="nofollow" to throw off spammers. You are allowed to use the following XHTML tags in your comment: <em> <strong> <u> <b> <i> <strike> <blockquote> <big> <small> <ul> <ol> <li> <a href=""> <pre> <code>. Please allow up to 60 second processing time after you post a comment. Our spam filters may take some time.