changeset 31982:184969a3a437

Add synchronization of multiple MPlayer instances over UDP. Patch by Jason Holt [jholt google com]
author reimar
date Wed, 08 Sep 2010 05:29:05 +0000
parents ae5a36acc995
children 33ff3fdad741
files AUTHORS DOCS/man/en/mplayer.1 DOCS/xml/en/usage.xml Makefile cfg-mplayer.h help/help_mp-en.h mplayer.c udp_sync.c udp_sync.h
diffstat 9 files changed, 391 insertions(+), 1 deletions(-) [+]
line wrap: on
line diff
--- a/AUTHORS	Tue Sep 07 23:46:14 2010 +0000
+++ b/AUTHORS	Wed Sep 08 05:29:05 2010 +0000
@@ -368,6 +368,9 @@
 Holm, David (dholm, mswitch) <dholm@telia.com>
     * DXR3 support
 
+Holt, Jason <jholt [at] google.com>
+    * UDP network synchronization
+
 Horst, Bohdan (Nexus) <nexus@irc.pl>
     * FreeBSD support
 
--- a/DOCS/man/en/mplayer.1	Tue Sep 07 23:46:14 2010 +0000
+++ b/DOCS/man/en/mplayer.1	Wed Sep 08 05:29:05 2010 +0000
@@ -1195,6 +1195,34 @@
 The normal framerate of the movie is kept, so playback is accelerated.
 Since MPlayer can only seek to the next keyframe this may be inexact.
 .
+.TP
+.B \-udp\-ip <ip>
+Sets the destination address for datagrams sent by the \-udp\-master.
+Setting it to a broadcast address allows multiple slaves having the same
+broadcast address to sync to the master (default: 127.0.0.1).
+.
+.TP
+.B \-udp\-master
+Send a datagram to \-udp\-ip on \-udp\-port just before playing each frame.
+The datagram indicates the master's position in the file.
+.
+.TP
+.B \-udp\-port <port>
+Sets the destination port for datagrams sent by the \-udp\-master, and the
+port a \-udp\-slave listens on (default: 23867).
+.
+.TP
+.B \-udp\-seek\-threshold <sec>
+When the master seeks, the slave has to decide whether to seek as
+well, or to catch up by decoding frames without pausing between frames.
+If the master is more than <sec> seconds away from the
+slave, the slave seeks.
+Otherwise, it "runs" to catch up or waits for the master.
+This should almost always be left at its default setting of 1 second.
+.
+.TP
+.B \-udp\-slave
+Listen on \-udp\-port and match the master's position.
 .
 .
 .SH "DEMUXER/STREAM OPTIONS"
--- a/DOCS/xml/en/usage.xml	Tue Sep 07 23:46:14 2010 +0000
+++ b/DOCS/xml/en/usage.xml	Wed Sep 08 05:29:05 2010 +0000
@@ -686,10 +686,70 @@
 </sect2>
 </sect1>
 
+<!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
+
+<sect1 id="networksync" xreflabel="Network Synchronized Playback">
+<title>Synchronized playback over a network</title>
+
+<para>
+Multiple instances of <application>MPlayer</application> can synchronize
+playback over a network. This is useful for creating "video walls" with
+multiple screens controlled by different computers. Each
+<application>MPlayer</application> instance can
+play a different video, but they all will try to stay at the same time offset
+in the file. It is recommended but not necessary to encode the video files
+using the same codec and parameters.
+</para>
+
+<para>The relevant options are <option>-udp-master</option>,
+  <option>-udp-slave</option>, <option>-udp-ip</option>,
+  <option>-udp-port</option>, and <option>-udp-seek-threshold</option>.
+</para>
+
+<para>
+If <option>-udp-master</option> is given, <application>MPlayer</application>
+sends a datagram to <option>-udp-ip</option> (default: 127.0.0.1)
+on <option>-udp-port</option> (default: 23867) just before playing each frame.
+The datagram indicates the master's position in the file. If
+<option>-udp-slave</option> is given, <application>MPlayer</application> listens on
+<option>-udp-ip</option>/<option>-udp-port</option>
+and matches the master's position. Setting <option>-udp-ip</option> to the
+master's broadcast address allows multiple slaves having the same broadcast
+address to sync to the master.  Note that this feature assumes an
+ethernet-like low-latency network connection.  Your mileage may vary on high
+latency networks.
+</para>
+
+<para>
+For example, assume 8 computers are on a network, with IP addresses 192.168.0.1
+through 192.168.0.8. Assume the first computer is to be the master. Running
+ifconfig on all the machines lists "Bcast:192.168.0.255". On the master, run:
+</para>
+
+<screen>
+mplayer -udp-master -udp-ip 192.168.0.255 video1.mpg
+</screen>
+
+<para>
+On each slave, run:
+</para>
+
+<screen>
+mplayer -udp-slave videoN.mpg
+</screen>
+
+<para>
+Seeking, pausing and even playback speed adjustment (see the
+<option>-input</option> option) can be done on the master, and all the slaves
+will follow. When the master exits, it sends out a "bye" message which causes
+the slaves to exit as well.
+</para>
+
+</sect1>
+</chapter>
 
 <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
 
-
 <sect1 id="advaudio-surround">
 <title>Surround/Multichannel playback</title>
 
--- a/Makefile	Tue Sep 07 23:46:14 2010 +0000
+++ b/Makefile	Wed Sep 08 05:29:05 2010 +0000
@@ -614,6 +614,7 @@
 SRCS_MPLAYER-$(MD5SUM)        += libvo/vo_md5sum.c
 SRCS_MPLAYER-$(MGA)           += libvo/vo_mga.c
 SRCS_MPLAYER-$(NAS)           += libao2/ao_nas.c
+SRCS_MPLAYER-$(NETWORKING)    += udp_sync.c
 SRCS_MPLAYER-$(OPENAL)        += libao2/ao_openal.c
 SRCS_MPLAYER-$(OSS)           += libao2/ao_oss.c
 SRCS_MPLAYER-$(PNM)           += libvo/vo_pnm.c
--- a/cfg-mplayer.h	Tue Sep 07 23:46:14 2010 +0000
+++ b/cfg-mplayer.h	Wed Sep 08 05:29:05 2010 +0000
@@ -275,6 +275,14 @@
 
     {"benchmark", &benchmark, CONF_TYPE_FLAG, 0, 0, 1, NULL},
 
+#ifdef CONFIG_NETWORKING
+    {"udp-slave", &udp_slave, CONF_TYPE_FLAG, 0, 0, 1, NULL},
+    {"udp-master", &udp_master, CONF_TYPE_FLAG, 0, 0, 1, NULL},
+    {"udp-ip", &udp_ip, CONF_TYPE_STRING, 0, 0, 1, NULL},
+    {"udp-port", &udp_port, CONF_TYPE_INT, 0, 1, 65535, NULL},
+    {"udp-seek-threshold", &udp_seek_threshold, CONF_TYPE_FLOAT, CONF_RANGE, 0.1, 100, NULL},
+#endif /* CONFIG_NETWORKING */
+
     // dump some stream out instead of playing the file
     // this really should be in MEncoder instead of MPlayer... -> TODO
     {"dumpfile", &stream_dump_name, CONF_TYPE_STRING, 0, 0, 0, NULL},
--- a/help/help_mp-en.h	Tue Sep 07 23:46:14 2010 +0000
+++ b/help/help_mp-en.h	Wed Sep 08 05:29:05 2010 +0000
@@ -180,6 +180,8 @@
 #define MSGTR_DvdnavNavSpuClutChange "DVDNAV Event: Nav SPU CLUT Change\n"
 #define MSGTR_DvdnavNavSeekDone "DVDNAV Event: Nav Seek Done\n"
 #define MSGTR_MenuCall "Menu call\n"
+#define MSGTR_MasterQuit "Option -udp_slave: exiting because master exited\n"
+#define MSGTR_InvalidIP "Option -udp-ip: invalid IP address\n"
 
 // --- edit decision lists
 #define MSGTR_EdlOutOfMem "Can't allocate enough memory to hold EDL data.\n"
--- a/mplayer.c	Tue Sep 07 23:46:14 2010 +0000
+++ b/mplayer.c	Wed Sep 08 05:29:05 2010 +0000
@@ -125,6 +125,12 @@
 #include "subreader.h"
 #include "vobsub.h"
 #include "eosd.h"
+#include "osdep/getch2.h"
+#include "osdep/timer.h"
+
+#ifdef CONFIG_NETWORKING
+#include "udp_sync.h"
+#endif /* CONFIG_NETWORKING */
 
 #ifdef CONFIG_X11
 #include "libvo/x11_common.h"
@@ -721,6 +727,11 @@
 void exit_player_with_rc(enum exit_reason how, int rc)
 {
 
+#ifdef CONFIG_NETWORKING
+  if (udp_master)
+    send_udp(udp_ip, udp_port, "bye");
+#endif /* CONFIG_NETWORKING */
+
   if (mpctx->user_muted && !mpctx->edl_muted) mixer_mute(&mpctx->mixer);
   uninit_player(INITIALIZED_ALL);
 #if defined(__MINGW32__) || defined(__CYGWIN__)
@@ -2213,6 +2224,17 @@
     int frame_time_remaining = 0;
     current_module="calc_sleep_time";
 
+#ifdef CONFIG_NETWORKING
+    if (udp_slave) {
+        int udp_master_exited = udp_slave_sync(mpctx);
+        if (udp_master_exited) {
+            mp_msg(MSGT_CPLAYER, MSGL_INFO, MSGTR_MasterQuit);
+            exit_player(EXIT_QUIT);
+        }
+        return 0;
+    }
+#endif /* CONFIG_NETWORKING */
+
     *time_frame -= GetRelativeTime(); // reset timer
 
     if (mpctx->sh_audio && !mpctx->d_audio->eof) {
@@ -2261,6 +2283,15 @@
     // flag 256 means: libvo driver does its timing (dvb card)
     if (*time_frame > 0.001 && !(vo_flags&256))
 	*time_frame = timing_sleep(*time_frame);
+
+#ifdef CONFIG_NETWORKING
+    if (udp_master) {
+      char current_time[256];
+      sprintf(current_time, "%f", mpctx->sh_video->pts);
+      send_udp(udp_ip, udp_port, current_time);
+    }
+#endif /* CONFIG_NETWORKING */
+
     return frame_time_remaining;
 }
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/udp_sync.c	Wed Sep 08 05:29:05 2010 +0000
@@ -0,0 +1,218 @@
+/*
+ * Network playback synchronization
+ * Copyright (C) 2009 Google Inc.
+ *
+ * This file is part of MPlayer.
+ *
+ * MPlayer 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.
+ *
+ * MPlayer 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 MPlayer; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+
+#if !HAVE_WINSOCK2_H
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <stdlib.h>
+#include <sys/ioctl.h>
+#include <fcntl.h>
+#include <string.h>
+#include <strings.h>
+#include <netdb.h>
+#include <signal.h>
+#else
+#include <winsock2.h>
+#include <ws2tcpip.h>
+#endif /* HAVE_WINSOCK2_H */
+
+#include "mplayer.h"
+#include "mp_core.h"
+#include "udp_sync.h"
+#include "mp_msg.h"
+#include "help_mp.h"
+
+
+// config options for UDP sync
+int udp_master = 0;
+int udp_slave = 0;
+int udp_port = 23867;
+const char *udp_ip = "127.0.0.1"; // where the master sends datagrams
+                                  // (can be a broadcast address)
+float udp_seek_threshold = 1.0; // how far off before we seek
+
+// remember where the master is in the file
+static float udp_master_position = -1.0;
+
+// how far off is still considered equal
+#define UDP_TIMING_TOLERANCE 0.02
+
+// gets a datagram from the master with or without blocking.  updates
+// master_position if successful.  if the master has exited, returns 1.
+// otherwise, returns 0.
+int get_udp(int blocking, float *master_position)
+{
+    long sock_flags;
+    struct sockaddr_in cliaddr;
+    char mesg[100];
+    socklen_t len;
+
+    int chars_received;
+    int n;
+
+    static int done_init_yet = 0;
+    static int sockfd;
+    if (!done_init_yet) {
+        struct timeval tv;
+        struct sockaddr_in servaddr;
+
+        done_init_yet = 1;
+
+        sockfd = socket(AF_INET, SOCK_DGRAM, 0);
+
+        memset(&servaddr, sizeof(servaddr), 0);
+        servaddr.sin_family =      AF_INET;
+        servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
+        servaddr.sin_port =        htons(udp_port);
+        bind(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
+
+        tv.tv_sec = 30;
+        setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
+
+    }
+
+#if HAVE_WINSOCK2_H
+    sock_flags = blocking;
+    ioctlsocket(sockfd, FIONBIO, &sock_flags);
+#else
+    sock_flags = fcntl(sockfd, F_GETFL, 0);
+    sock_flags = blocking ? sock_flags & ~O_NONBLOCK : sock_flags | O_NONBLOCK;
+    fcntl(sockfd, F_SETFL, sock_flags);
+#endif /* HAVE_WINSOCK2_H */
+
+    len = sizeof(cliaddr);
+
+    chars_received = recvfrom(sockfd, mesg, sizeof(mesg)-1, 0, (struct sockaddr *)&cliaddr, &len);
+
+    if (chars_received == -1) {
+      return 0;
+    }
+
+#if HAVE_WINSOCK2_H
+    sock_flags = 0;
+    ioctlsocket(sockfd, FIONBIO, &sock_flags);
+#else
+    fcntl(sockfd, F_SETFL, sock_flags | O_NONBLOCK);
+#endif
+
+    // flush out any further messages so we don't get behind
+    while (-1 != (n = recvfrom(sockfd, mesg, sizeof(mesg)-1, 0, (struct sockaddr *)&cliaddr, &len))) {
+        chars_received = n;
+        mesg[chars_received] = 0;
+        if (strcmp(mesg, "bye") == 0) {
+          return 1;
+        }
+    }
+
+    if (chars_received > -1) {
+        mesg[chars_received] = 0;
+
+        if (strcmp(mesg, "bye") == 0) {
+            return 1;
+        } else {
+            sscanf(mesg, "%f", master_position);
+            return 0;
+        }
+    } else {
+        // UDP wait error, probably a timeout.  Safe to ignore.
+    }
+
+    return 0;
+}
+
+void send_udp(const char *send_to_ip, int port, char *mesg)
+{
+    static int done_init_yet = 0;
+    static int sockfd;
+    static struct sockaddr_in socketinfo;
+
+    int one = 1;
+
+    if (!done_init_yet) {
+        int ip_valid = 0;
+
+        done_init_yet = 1;
+
+        sockfd=socket(AF_INET, SOCK_DGRAM, 0);
+
+        // Enable broadcast
+        setsockopt(sockfd, SOL_SOCKET, SO_BROADCAST, &one, sizeof(one));
+
+#if HAVE_WINSOCK2_H
+        ip_valid = (inet_addr(send_to_ip) != INADDR_NONE);
+#else
+        ip_valid = inet_aton(send_to_ip, &socketinfo.sin_addr);
+#endif
+
+        if (!ip_valid) {
+            mp_msg(MSGT_CPLAYER, MSGL_FATAL, MSGTR_InvalidIP);
+            exit_player(EXIT_ERROR);
+        }
+
+        socketinfo.sin_family = AF_INET;
+        socketinfo.sin_port = htons(port);
+    }
+
+    sendto(sockfd, mesg, strlen(mesg), 0, (struct sockaddr *) &socketinfo, sizeof(socketinfo));
+}
+
+// this function makes sure we stay as close as possible to the master's
+// position.  returns 1 if the master tells us to exit, 0 otherwise.
+int udp_slave_sync(MPContext *mpctx)
+{
+    // grab any waiting datagrams without blocking
+    int master_exited = get_udp(0, &udp_master_position);
+
+    while (!master_exited) {
+        float my_position = mpctx->sh_video->pts;
+
+        // if we're way off, seek to catch up
+        if (FFABS(my_position - udp_master_position) > udp_seek_threshold) {
+            abs_seek_pos = SEEK_ABSOLUTE;
+            rel_seek_secs = udp_master_position;
+            break;
+        }
+
+        // normally we expect that the master will have just played the
+        // frame we're ready to play.  break out and play it, and we'll be
+        // right in sync.
+        // or, the master might be up to a few seconds ahead of us, in
+        // which case we also want to play the current frame immediately,
+        // without waiting.
+        // UDP_TIMING_TOLERANCE is a small value that lets us consider
+        // the master equal to us even if it's very slightly ahead.
+        if (udp_master_position + UDP_TIMING_TOLERANCE > my_position) {
+          break;
+        }
+
+        // the remaining case is that we're slightly ahead of the master.
+        // usually, it just means we called get_udp() before the datagram
+        // arrived.  call get_udp again, but this time block until we receive
+        // a datagram.
+        master_exited = get_udp(1, &udp_master_position);
+    }
+
+    return master_exited;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/udp_sync.h	Wed Sep 08 05:29:05 2010 +0000
@@ -0,0 +1,39 @@
+/*
+ * Network playback synchronization
+ * Copyright (C) 2009 Google Inc.
+ *
+ * This file is part of MPlayer.
+ *
+ * MPlayer 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.
+ *
+ * MPlayer 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 MPlayer; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPLAYER_UDP_SYNC_H
+#define MPLAYER_UDP_SYNC_H
+
+#include "mp_core.h"
+
+// config options for UDP sync
+extern int udp_master;
+extern int udp_slave;
+extern int udp_port;
+extern const char *udp_ip; // where the master sends datagrams
+                           // (can be a broadcast address)
+extern float udp_seek_threshold; // how far off before we seek
+
+void send_udp(const char *send_to_ip, int port, char *mesg);
+int get_udp(int blocking, float *master_position);
+int udp_slave_sync(MPContext *mpctx);
+
+#endif /* MPLAYER_UDP_SYNC_H */