Network clock examples

Way back in 2006, Andy Wingo wrote some small scripts for GStreamer 0.10 to demonstrate what was (back then) a fairly new feature in GStreamer – the ability to share a clock across the network and use it to synchronise playback of content across different machines.

Since GStreamer 1.x has been out for over 2 years, and we get a lot of questions about how to use the network clock functionality, it’s a good time for an update. I’ve ported the simple examples for API changes and to use the gobject-introspection based Python bindings and put them up on my server.

To give it a try, fetch play-master.py and play-slave.py onto 2 or more computers with GStreamer 1 installed. You need a media file accessible via some URI to all machines, so they have something to play.

Then, on one machine run play-master.py, passing a URI for it to play and a port to publish the clock on:

./play-master.py http://server/path/to/file 8554

The script will print out a command line like so:

Start slave as: python ./play-slave.py http://server/path/to/file [IP] 8554 1071152650838999

On another machine(s), run the printed command, substituting the IP address of the machine running the master script.

After a moment or two, the slaved machine should start playing the file in synch with the master:

Network Synchronised Playback

If they’re not in sync, check that you have the port you chose open for UDP traffic so the clock synchronisation packets can be transferred.

This basic technique is the core of my Aurena home media player system, which builds on top of the network clock mechanism to provide file serving and a simple shuffle playlist.

For anyone still interested in GStreamer 0.10 – Andy’s old scripts can be found on his server: play-master.py and play-slave.py

8 thoughts on “Network clock examples”

  1. Does it work well over a wireless network? I tried to set up audio via Pulseaudio streaming over my home wireless, but it seemed to take up too much bandwidth, freezing other use of the Internet and even with some noise from out-of-synch playback. But maybe the problem was with PA.

    1. There are a couple of benefits over PA streaming, and one drawback. On the benefits side, I can stream encoded content, where PA streams raw audio – so the bandwidth use is lower. Also, because there’s explicit synchronisation of the playback, audio and video play in synch – IME there’s an annoyingly large delay when streaming with PA.

      On the drawbacks side, setting up network streaming with PA is really easy – this takes a bit more work.

  2. I am trying to run these python scripts on Ubuntu 12.04 with GStreamer 1.0 installed. Running the master, it complained about Gst.init requiring a parameter. I changed line 12 to Gst.init(args), and now it has an error on line 34, value not in range:
    pipeline.set_start_time(Gst.CLOCK_TIME_NONE)

  3. Hi I have been playing around with these scripts but can’t get the master and slave to sync and mp3 that well. Its about the same on Wifi and Ethernet.
    If I play on two devices in one room its sounds echoey. I believe one is around 200 ms ahead. Have you had this syncing so that two audio streams are in perfect sync or is that beyond the current implementation ?

    Thanks

    1. What devices are you using on either side? In general, even over very bad wifi it’s possible to get the slaves synced to about 100ms. On good wifi or ethernet, the sync can easily be sub-millisecond. There are some utilities in gstreamer/tests/examples/netclock/ that display statistics about the clock sync accuracy.

      1. One device is a laptop and one a rapsberry pi A+.
        Neither seems to be stressed. They are on Ethernet on a Gigabit switch.
        ping times seem ok with no packet loss

        64 bytes from partyzone-slave1.home (192.168.1.128): icmp_seq=1 ttl=64 time=0.535 ms
        64 bytes from partyzone-slave1.home (192.168.1.128): icmp_seq=2 ttl=64 time=0.530 ms
        64 bytes from partyzone-slave1.home (192.168.1.128): icmp_seq=3 ttl=64 time=0.579 m

        Neither device seems stressed in anyway. I will check the test examples

        Thanks

      2. Running the test and leaving for 10 seconds gives results like

        gst-netclock-statistics, synchronised=(boolean)true, rtt=(guint64)807959, rtt-average=(guint64)842948, local=(guint64)117911994823257, remote=(guint64)114114833002158, discontinuity=(gint64)3595, remote-min-estimate=(guint64)114114832581899, remote-max-estimate=(guint64)114114833389855, remote-min-error=(gint64)-420259, remote-max-error=(gint64)387697, request-send=(guint64)117911994419278, request-receive=(guint64)117911995227237, r-squared=(double)0.9999999999050091, timeout=(guint64)1000000000, internal-time=(guint64)117911994823257, external-time=(guint64)114114832989472, rate-num=(guint64)6399542065174194594, rate-den=(guint64)6399562284369299987, rate=(double)0.99999684053467908, local-clock-offset=(gint64)-3797161833785;
        gst-netclock-statistics, synchronised=(boolean)true, rtt=(guint64)930953, rtt-average=(guint64)848448, local=(guint64)117912998852883, remote=(guint64)114115837079987, discontinuity=(gint64)13343, remote-min-estimate=(guint64)114115836550451, remote-max-estimate=(guint64)114115837481401, remote-min-error=(gint64)-529536, remote-max-error=(gint64)401414, request-send=(guint64)117912998387407, request-receive=(guint64)117912999318360, r-squared=(double)0.99999999991399069, timeout=(guint64)1000000000, internal-time=(guint64)117912998852883, external-time=(guint64)114115837029268, rate-num=(guint64)7658404085804090154, rate-den=(guint64)7658419542196756008, rate=(double)0.99999798177775701, local-clock-offset=(gint64)-3797161823615;
        gst-netclock-statistics, synchronised=(boolean)true, rtt=(guint64)910955, rtt-average=(guint64)852354, local=(guint64)117914003225992, remote=(guint64)114116841417566, discontinuity=(gint64)3397, remote-min-estimate=(guint64)114116840944873, remote-max-estimate=(guint64)114116841855827, remote-min-error=(gint64)-472693, remote-max-error=(gint64)438261, request-send=(guint64)117914002770515, request-receive=(guint64)117914003681470, r-squared=(double)0.99999999992693656, timeout=(guint64)1000000000, internal-time=(guint64)117914003225992, external-time=(guint64)114116841403747, rate-num=(guint64)9066848322575823194, rate-den=(guint64)9066864136127895403, rate=(double)0.99999825589621338, local-clock-offset=(gint64)-3797161822245;
        gst-netclock-statistics, synchronised=(boolean)true, rtt=(guint64)1081946, rtt-average=(guint64)866703, local=(guint64)117915007369613, remote=(guint64)114117845651984, discontinuity=(gint64)20000, remote-min-estimate=(guint64)114117845004644, remote-max-estimate=(guint64)114117846086588, remote-min-error=(gint64)-647340, remote-max-error=(gint64)434604, request-send=(guint64)117915006828640, request-receive=(guint64)117915007910586, r-squared=(double)0.99999999992421185, timeout=(guint64)1000000000, internal-time=(guint64)117915007369613, external-time=(guint64)114117845565616, rate-num=(guint64)2658181561756514874, rate-den=(guint64)2658182134462765963, rate=(double)0.99999978454965754, local-clock-offset=(gint64)-3797161803997;
        gst-netclock-statistics, synchronised=(boolean)true, rtt=(guint64)1111945, rtt-average=(guint64)882030, local=(guint64)117916011843716, remote=(guint64)114118850132880, discontinuity=(gint64)16716, remote-min-estimate=(guint64)114118849483530, remote-max-estimate=(guint64)114118850595475, remote-min-error=(gint64)-649350, remote-max-error=(gint64)462595, request-send=(guint64)117916011287744, request-receive=(guint64)117916012399689, r-squared=(double)0.99999999992576571, timeout=(guint64)1000000000, internal-time=(guint64)117916011843716, external-time=(guint64)114118850056218, rate-num=(guint64)3091006546024267513, rate-den=(guint64)3091003464229293483, rate=(double)1.0000009970208734, local-clock-offset=(gint64)-3797161787498;

        Not sure what important things to look for here are.

        thanks

Leave a Reply

Your email address will not be published. Required fields are marked *