netsync.c 8.75 KB
Newer Older
1 2 3
/*****************************************************************************
 * netsync.c: synchronisation between several network clients.
 *****************************************************************************
Jean-Paul Saman's avatar
Jean-Paul Saman committed
4
 * Copyright (C) 2004-2009 the VideoLAN team
5 6 7
 * $Id$
 *
 * Authors: Gildas Bazin <gbazin@videolan.org>
Jean-Paul Saman's avatar
Jean-Paul Saman committed
8
 *          Jean-Paul Saman <jpsaman@videolan.org>
9 10 11 12 13 14 15 16 17 18 19 20 21
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
Antoine Cellerier's avatar
Antoine Cellerier committed
22
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
23 24 25 26 27
 *****************************************************************************/

/*****************************************************************************
 * Preamble
 *****************************************************************************/
28 29 30 31
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif

32
#include <vlc_common.h>
33
#include <vlc_plugin.h>
Clément Stenac's avatar
Clément Stenac committed
34 35
#include <vlc_interface.h>
#include <vlc_input.h>
Jean-Paul Saman's avatar
Jean-Paul Saman committed
36
#include <vlc_playlist.h>
37 38 39 40

#ifdef HAVE_UNISTD_H
#    include <unistd.h>
#endif
41
#include <sys/types.h>
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
42
#ifdef HAVE_POLL
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
43 44
#   include <poll.h>
#endif
45

Clément Stenac's avatar
Clément Stenac committed
46
#include <vlc_network.h>
47

48
#define NETSYNC_PORT 9875
49 50 51 52

/*****************************************************************************
 * Module descriptor
 *****************************************************************************/
53 54
static int  Open (vlc_object_t *);
static void Close(vlc_object_t *);
55

56 57
#define NETSYNC_TEXT N_("Network master clock")
#define NETSYNC_LONGTEXT N_("When set then " \
Jean-Paul Saman's avatar
Jean-Paul Saman committed
58
  "this vlc instance shall dictate its clock for synchronisation" \
59
  "over clients listening on the masters network ip address")
60

61 62 63
#define MIP_TEXT N_("Master server ip address")
#define MIP_LONGTEXT N_("The IP address of " \
  "the network master clock to use for clock synchronisation.")
64

65
#define NETSYNC_TIMEOUT_TEXT N_("UDP timeout (in ms)")
Jean-Paul Saman's avatar
Jean-Paul Saman committed
66
#define NETSYNC_TIMEOUT_LONGTEXT N_("Amount of time (in ms) " \
67
  "to wait before aborting network reception of data.")
68

69 70 71 72 73
vlc_module_begin()
    set_shortname(N_("Network Sync"))
    set_description(N_("Network synchronisation"))
    set_category(CAT_ADVANCED)
    set_subcategory(SUBCAT_ADVANCED_MISC)
74

75 76 77 78 79 80
    add_bool("netsync-master", false, NULL,
              NETSYNC_TEXT, NETSYNC_LONGTEXT, true)
    add_string("netsync-master-ip", NULL, NULL, MIP_TEXT, MIP_LONGTEXT,
                true)
    add_integer("netsync-timeout", 500, NULL,
                 NETSYNC_TIMEOUT_TEXT, NETSYNC_TIMEOUT_LONGTEXT, true)
81

82 83 84
    set_capability("interface", 0)
    set_callbacks(Open, Close)
vlc_module_end()
85 86 87 88

/*****************************************************************************
 * Local prototypes
 *****************************************************************************/
Laurent Aimar's avatar
Laurent Aimar committed
89 90 91 92 93 94 95
struct intf_sys_t {
    int            fd;
    int            timeout;
    bool           is_master;
    playlist_t     *playlist;
    input_thread_t *input;
};
96
static void Run(intf_thread_t *intf);
97 98 99 100

/*****************************************************************************
 * Activate: initialize and create stuff
 *****************************************************************************/
101
static int Open(vlc_object_t *object)
102
{
103
    intf_thread_t *intf = (intf_thread_t*)object;
Laurent Aimar's avatar
Laurent Aimar committed
104
    intf_sys_t    *sys;
105
    int fd;
106

107 108 109 110
    if (!var_InheritBool(intf, "netsync-master")) {
        char *psz_master = var_InheritString(intf, "netsync-master-ip");
        if (psz_master == NULL) {
            msg_Err(intf, "master address not specified");
111 112
            return VLC_EGENERIC;
        }
113 114
        fd = net_ConnectUDP(VLC_OBJECT(intf), psz_master, NETSYNC_PORT, -1);
        free(psz_master);
Laurent Aimar's avatar
Laurent Aimar committed
115
    } else {
116
        fd = net_ListenUDP1(VLC_OBJECT(intf), NULL, NETSYNC_PORT);
Laurent Aimar's avatar
Laurent Aimar committed
117
    }
118

119 120
    if (fd == -1) {
        msg_Err(intf, "Netsync socket failure");
121 122
        return VLC_EGENERIC;
    }
123

Laurent Aimar's avatar
Laurent Aimar committed
124 125 126 127 128
    intf->p_sys = sys = malloc(sizeof(*sys));
    if (!sys) {
        net_Close(fd);
        return VLC_ENOMEM;
    }
129
    intf->pf_run = Run;
Laurent Aimar's avatar
Laurent Aimar committed
130 131 132 133 134 135 136 137 138

    sys->fd = fd;
    sys->is_master = var_InheritBool(intf, "netsync-master");
    sys->timeout = var_InheritInteger(intf, "netsync-timeout");
    if (sys->timeout < 500)
        sys->timeout = 500;
    sys->playlist = pl_Hold(intf);
    sys->input = NULL;

139 140 141 142 143 144
    return VLC_SUCCESS;
}

/*****************************************************************************
 * Close: destroy interface
 *****************************************************************************/
145
void Close(vlc_object_t *object)
146
{
147
    intf_thread_t *intf = (intf_thread_t*)object;
Laurent Aimar's avatar
Laurent Aimar committed
148
    intf_sys_t *sys = intf->p_sys;
149

Laurent Aimar's avatar
Laurent Aimar committed
150 151 152
    pl_Release(intf);
    net_Close(sys->fd);
    free(sys);
153 154
}

Laurent Aimar's avatar
Laurent Aimar committed
155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246
static void Master(intf_thread_t *intf)
{
    intf_sys_t *sys = intf->p_sys;
    struct pollfd ufd = { .fd = sys->fd, .events = POLLIN, };
    uint64_t data[2];

    /* Don't block */
    if (poll(&ufd, 1, sys->timeout) <= 0)
        return;

    /* We received something */
    struct sockaddr_storage from;
    unsigned struct_size = sizeof(from);
    recvfrom(sys->fd, data, sizeof(data), 0,
             (struct sockaddr*)&from, &struct_size);

    mtime_t master_system;
    if (input_GetPcrSystem(sys->input, &master_system))
        return;

    data[0] = hton64(mdate());
    data[1] = hton64(master_system);

    /* Reply to the sender */
    sendto(sys->fd, data, sizeof(data), 0,
           (struct sockaddr *)&from, struct_size);

#if 0
    /* not sure we need the client information to sync,
       since we are the master anyway */
    mtime_t client_system = ntoh64(data[0]);
    msg_Dbg(intf, "Master clockref: %"PRId64" -> %"PRId64", from %s "
             "(date: %"PRId64")", client_system, master_system,
             (from.ss_family == AF_INET) ? inet_ntoa(((struct sockaddr_in *)&from)->sin_addr)
             : "non-IPv4", date);
#endif
}

static void Slave(intf_thread_t *intf)
{
    intf_sys_t *sys = intf->p_sys;
    struct pollfd ufd = { .fd = sys->fd, .events = POLLIN, };
    uint64_t data[2];

    mtime_t system;
    if (input_GetPcrSystem(sys->input, &system))
        goto wait;

    /* Send clock request to the master */
    data[0] = hton64(system);

    const mtime_t send_date = mdate();
    if (send(sys->fd, data, sizeof(data[0]), 0) <= 0)
        goto wait;

    /* Don't block */
    int ret = poll(&ufd, 1, sys->timeout);
    if (ret == 0)
        return;
    if (ret < 0)
        goto wait;

    const mtime_t receive_date = mdate();
    if (recv(sys->fd, data, sizeof(data), 0) <= 0)
        goto wait;

    const mtime_t master_date   = ntoh64(data[0]);
    const mtime_t master_system = ntoh64(data[1]);
    const mtime_t diff_date = receive_date -
                              ((receive_date - send_date) / 2 + master_date);

    if (master_system > 0) {
        mtime_t client_system;
        if (input_GetPcrSystem(sys->input, &client_system))
            goto wait;

        const mtime_t diff_system = client_system - master_system - diff_date;
        if (diff_system != 0) {
            input_ModifyPcrSystem(sys->input, true, master_system - diff_date);
#if 0
            msg_Dbg(intf, "Slave clockref: %"PRId64" -> %"PRId64" -> %"PRId64","
                     " clock diff: %"PRId64", diff: %"PRId64"",
                     system, master_system, client_system,
                     diff_system, diff_date);
#endif
        }
    }
wait:
    msleep(INTF_IDLE_SLEEP);
}


247 248 249
/*****************************************************************************
 * Run: interface thread
 *****************************************************************************/
250
static void Run(intf_thread_t *intf)
251
{
Laurent Aimar's avatar
Laurent Aimar committed
252
    intf_sys_t *sys = intf->p_sys;
Jean-Paul Saman's avatar
Jean-Paul Saman committed
253

Laurent Aimar's avatar
Laurent Aimar committed
254
    int canc = vlc_savecancel();
255 256

    /* High priority thread */
257
    vlc_thread_set_priority(intf, VLC_THREAD_PRIORITY_INPUT);
258

259
    while (vlc_object_alive(intf)) {
260
        /* Update the input */
Laurent Aimar's avatar
Laurent Aimar committed
261 262 263 264 265
        if (sys->input == NULL) {
            sys->input = playlist_CurrentInput(sys->playlist);
        } else if (sys->input->b_dead || !vlc_object_alive(sys->input)) {
            vlc_object_release(sys->input);
            sys->input = NULL;
266 267
        }

Laurent Aimar's avatar
Laurent Aimar committed
268
        if (sys->input == NULL) {
269
            /* Wait a bit */
270
            msleep(INTF_IDLE_SLEEP);
271 272 273 274 275 276
            continue;
        }

        /*
         * We now have an input
         */
Laurent Aimar's avatar
Laurent Aimar committed
277 278
        if (sys->is_master)
            Master(intf);
279
        else
Laurent Aimar's avatar
Laurent Aimar committed
280
            Slave(intf);
281 282
    }

Laurent Aimar's avatar
Laurent Aimar committed
283 284
    if (sys->input)
        vlc_object_release(sys->input);
285
    vlc_restorecancel(canc);
286 287
}