Time synchronization between android devices

05 December 2014

As part of a larger android project, I need to be able to synchronize the clocks between android devices connected to the same wifi network, with an accuracy of a few milliseconds. After doing some reading on NTP (network time protocols), I decided to use a simple round-trip udp exchange of time stamps, illustrated below.

The calculation of the offset (variable "o") assumes that the delay (variable "d") is the same in either direction. The message from the server to the client contains the time stamps "t2" and "t3" so that the client can do the calculation.

I want both devices to be able to either request a time offset or provide one, so I run a server thread that listens on a predefined port for incoming udp packets.

public class UdpServerThread extends Thread {

    DatagramSocket socket;

    @Override
    public void run() {
        try {
            socket = new DatagramSocket(SERVERPORT);

            // keep listening
            while (true) {
                // receive client packet
                byte[] buf = new byte[16];
                DatagramPacket packet = new DatagramPacket(buf, buf.length);
                socket.receive(packet);
                long serverreceive = System.currentTimeMillis();

                // get client address:port
                InetAddress clientAddress = packet.getAddress();
                int port = packet.getPort();

                // send server time stamps
                long serversend = System.currentTimeMillis();
                byte[] sendbuf = ByteBuffer.allocate(24)
                        .putLong(serverreceive).putLong(serversend).array();
                packet = new DatagramPacket(sendbuf, sendbuf.length,
                        clientAddress, port);
                socket.send(packet);
            }

        } catch (Exception e) {
            e.printStackTrace();
            Log.d("server", "Error!");
        }
    }
}

The client initializes the exchange with the server and waits for the server response which contains the server receiving and sending time stamps. There is noise in the measurement depending on the network and how busy the devices are, so I get 10 measurements of the offset and take a median value. The noise on the filtered values is about one millisecond, so I get very consistent values. The question that remains is: is this offset accurate.

public class UdpClient implements Runnable {
    private InetAddress serverAddr;
    private int port;

    @Override
    public void run() {
        try {
            DatagramSocket socket = new DatagramSocket();

            int numsamples = 10;
            long[] delay = new long[numsamples];
            long[] offset = new long[numsamples];

            long clientsend = 0;
            long clientreceive = 0;
            long serverreceive = 0;
            long serversend = 0;
            // obtain 10 measurements for accuracy
            for (int i = 0; i < numsamples; i++) {
                // send first packet to server (its content is unimportant
                // but its length is kept the same as the received packet
                // for symmetry)
                clientsend = System.currentTimeMillis();
                byte[] sendbuf = ByteBuffer.allocate(16)
                        .putLong(clientsend).array();
                DatagramPacket packet = new DatagramPacket(sendbuf,
                        sendbuf.length, serverAddr, port);
                socket.send(packet);

                // wait for server response
                byte[] receivebuf = new byte[16];
                packet = new DatagramPacket(receivebuf, receivebuf.length);
                socket.receive(packet);
                // mark the packet reception time stamp
                clientreceive = System.currentTimeMillis();

                ByteBuffer.allocate(16);
                ByteBuffer serverResponseBuffer = ByteBuffer
                        .wrap(receivebuf);
                serverreceive = serverResponseBuffer.getLong();
                serversend = serverResponseBuffer.getLong();

                delay[i] = clientreceive - clientsend - serversend
                        + serverreceive;
                offset[i] = (serverreceive - clientsend + serversend - clientreceive) / 2;
            }

            // use the median value as true offset
            Arrays.sort(offset);
            long medianoffset = offset[offset.length / 2];
            Log.d("client", "median offset " + medianoffset);

        } catch (Exception e) {
            sendUI("Client: Error!");
            e.printStackTrace();
        }
    }

    public InetAddress getServerAddr() {
        return serverAddr;
    }

    public void setServerAddr(InetAddress serverAddr) {
        this.serverAddr = serverAddr;
    }

    public int getPort() {
        return port;
    }

    public void setPort(int port) {
        this.port = port;
    }
}

To try to answer the question of the accuracy, I use this process to synchronize the blinking of some text on three devices and record it at 240fps to estimate the error. There is some error in the synchronization by up to 12ms, however the blinking order among the devices is not consistent (the device that blinks first isn't always the same), so it is hard to know what is the actual accuracy of the offset measurement. Because the blinking order is quite random, I actually think that the offset calculation accuracy is much better than those 12ms. The video is played at 1/8x real speed.