Mercurial > mplayer.hg
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 */