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.

#11 sam

Hello ... i am Student from London.. I am developing a media player in my final year project .. I will use gstreamer for library .. can anyone help me .. how shall i start it .. any idea will be helpful ..
thank you

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

I highly recommend you start learning Python if you don't know it already. GStreamer is quite a complicated beast. I can't imagine how hard it must be while trying it in C/C++.

Next, I suggest you start playing with the commandline tools such as gst-launch and build some working pipelines. Once you have something that works, try to implement the same pipeline in Python.

#13 sam

Thankx Sander.. i will start working on that..
do you have any tutorial or any useful documentation on that??..

Thank you

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

Sure.

Python tutorial
Gstreamer tutorials, especially this one

And read the code in my article. It shows the same GStreamer pipeline twice. First using gst-launch from the commandline and then the same pipeline implemented in Python.

#15 celia

Hi! your guide is great! im begining with gstreamer and python ... i like to add a type of pause to your code ... a read something about the bus... sending msgs ... but i dont really get it.. if you can help me, please send me an email!
thanks! and sorry for my english :)

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

Normally, pausing is as simple as setting the entire pipeline to a PAUSED state. But in this case you're dealing with two processes: a sender and a receiver. I wouldn't know how to pause it.

I did something like that for my jukebox server but that was basically a cheat. I had two sound sources: a song and a dummy bin that produces a constant stream of silence. When I wanted to "pause", I only stopped the song. The "silence" still played, so the pipeline kept rolling.

#17 celia

thats a good one!,i keep it in mind! thanks again for de code and the quick response!

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

I'm afraid I accidentally deleted someone's comment. It may have been yours celia. I apologise.

The person in question was asking about a simple GStreamer pipeline tutorial. As it happens, Jono Bacon has written an excellent tutorial on implementing a simple pipeline. He implements it both using gst-launch and using Python.

See here: http://www.jonobacon.org/2006/08/28/getting-started-with-gstreamer-with-python/

#19 celia

it wasn't mine..i implementing a big silence for de pause :):) thanks again!

#20 sam

That was mine sander.. thats fine ...
yeah i have check the Jono Bacon tutorial its very very useful.. As i have a very basic knowledge about this.. it gave me idea to start with.. as i am very basic in this i still couldn't get how can i create a or develop a pipeline to play any mp3/mp4 files.. i might be asking for too much but i will really appreciate your help..
Many thanks

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

@sam: You may want to read his followup tutorial in that case: http://www.jonobacon.org/2006/11/03/gstreamer-dynamic-pads-explained/

That article shows a minimal, basic pipeline that can play pretty much any audiofile (mp3, ogg, etcetera)

#22 Sam

Hello sander, after long time.. still working on the project. I can run those command and stream over tcp but i am unable to do so using python file.. i mean when i run server and client it doesn't seems working so .. could you help me with this??
Thank you

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

@Sam: Is your code available somewhere (Subversion, Githib)? Feel free to contact me through the Contact form. E-mail is much easier than blog comments.

#24 sam

Hello sander, I have used the same code which you have written above.. I have created a server.py and client.py when I try to run the server.py it doesn't run .. it shows the following error..

sam@ubuntu:~/Desktop$ ./server.py
./server.py: line 8: import: command not found
./server.py: line 9: syntax error near unexpected token `"0.10"'
./server.py: line 9: `pygst.require("0.10")'

can you help me with this...

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

@Sam: I have no idea. Are you sure you are executing it with Python and not with bash (or some other shell)? Do you have python-gstreamer installed? Are you *sure* you don't have a typo in there? I mean, it's the first line of code...

#26 Matt Derlay

Hi, thanks for the tutorial, much appreciated. One question: how can you run the client more than one time while the same instance of the server is running? The song plays fine the first time, but when I run the client again (without restarting the server), nothing happens.

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

You can't, not without adding some extra code. Right now, when the client stop sending data, the server pipeline stops. You'll need to add some extra code to set the server pipeline back to the playing state again, waiting for the next client to connect.

#28 sam

Hello.. do you have any tutorial on video streaming??

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

No, unfortunately not.

#30 sam

hello sander.. I am trying stream video using the same concept like yours.. I am a beginner so i am only able to run the server but not the client side.. I have explained the codes in my blog http//samirghimire.wordpress.com

Please have a look, I will be really grateful if you can help me with this...
Thank you

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

Looks like you're missing a codec or plugin bundle on the machine where you are running your client. The dvddemux element comes from the gstreamer-plugins-ugly bundle. Try instralling that (when on Debian or Ubuntu, try to install the non-free versions from e.g. debian-multimedia or Ubuntu Multiverse).

#32 Samir Ghimire (http://samirghimire.wordpress.com)

Hello sander.. bothering you again and again.. but i have a problem and i think you will be able to solve it.. In my previous question i asked about the video streaming which was not running properly.. Some how i can run those but, its not showing any output..

And recently i have been trying for live streaming from my webcam.. here is the link:
http://samirghimire.wordpress.com/2011/03/04/webcam-streaming-using-python-and-gstreamer/

I can stream using just the gstreamer pipeline but when i bind with python its not showing any output although both server and client are running..
Please help me out with this..

Thank you very much

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

I can't really help you there Samir. I have no webcams so I have no way to test your code. The only thing I notice in your receiver is that you have an OnDynamicPad function, but you're not telling GStreamer to use it as a callback.

#34 Samir Ghimire (http://samirghimire.wordpress.com)

Thank you sander...
another question , how to write the call back like in
def new_decode_pad(dbin, pad, islast):
on what basis the dbin, pad, islast is written.. what is islast??
many thanks...

#35 Ionut

Can you please explain what is the purpose of the audioconvert element? I've tried it and it works just fine without it...

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

It may work without it. It may also fail. It depends on what your alsasink (audio card) understands and what file format you are reading. The auioconvert is there to make sure it always works.

#37 Giovanni

I'd like to send real-time audio (coming from the microphone) instead of file-content audio. Can you show how ? Thanks.

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

Hi Giovanni. The microphone is usually handled by Pulse Audio on Linux. So, all you have to do is use `pulsesrc` instead of `filesrc`.

#39 Abhishek (http://thezeroth.net)

In your code the server listens to the client and server is playing the music I just to do exactly opposite where server just streams the audio file and any music player can listen to it. very much like a internet radio but in single command. I tried following :
$ gst-launch-0.10 filesrc location="/path/to/file.mp3" ! tcpserversink host=0.0.0.0 port=3000

but it doesnt work. am I using it correctly ?

Thanks

Comments have been retired for this article.