1 /*
2  * Network playback synchronization
3  * Copyright (C) 2009 Google Inc.
4  *
5  * This file is part of MPlayer.
6  *
7  * MPlayer is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation; either version 2 of the License, or
10  * (at your option) any later version.
11  *
12  * MPlayer is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.    See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License along
18  * with MPlayer; if not, write to the Free Software Foundation, Inc.,
19  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20  */
21 
22 #define _BSD_SOURCE
23 
24 #include "config.h"
25 
26 #if !HAVE_WINSOCK2_H
27 #include <errno.h>
28 #include <unistd.h>
29 #include <sys/types.h>
30 #include <sys/socket.h>
31 #include <netinet/in.h>
32 #include <stdlib.h>
33 #include <sys/ioctl.h>
34 #include <fcntl.h>
35 #include <string.h>
36 #include <netdb.h>
37 #include <signal.h>
38 #else
39 #include <winsock2.h>
40 #include <ws2tcpip.h>
41 #endif /* HAVE_WINSOCK2_H */
42 
43 #include "mplayer.h"
44 #include "mp_core.h"
45 #include "mp_msg.h"
46 #include "help_mp.h"
47 #include "udp_sync.h"
48 #include "osdep/timer.h"
49 
50 
51 // config options for UDP sync
52 int udp_master = 0;
53 int udp_slave  = 0;
54 int udp_port   = 23867;
55 const char *udp_ip = "127.0.0.1"; // where the master sends datagrams
56                                   // (can be a broadcast address)
57 float udp_seek_threshold = 1.0;   // how far off before we seek
58 
59 // how far off is still considered equal
60 #define UDP_TIMING_TOLERANCE 0.02
61 
startup(void)62 static void startup(void)
63 {
64 #if HAVE_WINSOCK2_H
65     static int wsa_started;
66     if (!wsa_started) {
67         WSADATA wd;
68         WSAStartup(0x0202, &wd);
69         wsa_started = 1;
70     }
71 #endif
72 }
73 
set_blocking(int fd,int blocking)74 static void set_blocking(int fd, int blocking)
75 {
76     long sock_flags;
77 #if HAVE_WINSOCK2_H
78     sock_flags = !blocking;
79     ioctlsocket(fd, FIONBIO, &sock_flags);
80 #else
81     sock_flags = fcntl(fd, F_GETFL, 0);
82     sock_flags = blocking ? sock_flags & ~O_NONBLOCK : sock_flags | O_NONBLOCK;
83     fcntl(fd, F_SETFL, sock_flags);
84 #endif /* HAVE_WINSOCK2_H */
85 }
86 
87 // gets a datagram from the master with or without blocking.  updates
88 // master_position if successful.  if the master has exited, returns 1.
89 // returns -1 on error or if no message received.
90 // otherwise, returns 0.
get_udp(int blocking,double * master_position)91 static int get_udp(int blocking, double *master_position)
92 {
93     char mesg[100];
94 
95     int chars_received = -1;
96     int n;
97 
98     static int sockfd = -1;
99     if (sockfd == -1) {
100 #if HAVE_WINSOCK2_H
101         DWORD tv = 30000;
102 #else
103         struct timeval tv = { .tv_sec = 30 };
104 #endif
105         struct sockaddr_in servaddr = { 0 };
106 
107         startup();
108         sockfd = socket(AF_INET, SOCK_DGRAM, 0);
109         if (sockfd == -1)
110             return -1;
111 
112         servaddr.sin_family      = AF_INET;
113         servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
114         servaddr.sin_port        = htons(udp_port);
115         if (bind(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) == -1) {
116             closesocket(sockfd);
117             sockfd = -1;
118             return -1;
119         }
120 
121         setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
122 
123     }
124 
125     set_blocking(sockfd, blocking);
126 
127     while (-1 != (n = recvfrom(sockfd, mesg, sizeof(mesg)-1, 0,
128                                NULL, NULL))) {
129         char *end;
130         // flush out any further messages so we don't get behind
131         if (chars_received == -1)
132             set_blocking(sockfd, 0);
133 
134         chars_received = n;
135         mesg[chars_received] = 0;
136         if (strcmp(mesg, "bye") == 0)
137             return 1;
138         *master_position = strtod(mesg, &end);
139         if (*end) {
140             mp_msg(MSGT_CPLAYER, MSGL_WARN, "Could not parse udp string!\n");
141             return -1;
142         }
143     }
144     if (chars_received == -1)
145         return -1;
146 
147     return 0;
148 }
149 
send_udp(const char * send_to_ip,int port,char * mesg)150 void send_udp(const char *send_to_ip, int port, char *mesg)
151 {
152     static int sockfd = -1;
153     static struct sockaddr_in socketinfo;
154 
155     if (sockfd == -1) {
156         static const int one = 1;
157         int ip_valid = 0;
158 
159         startup();
160         sockfd = socket(AF_INET, SOCK_DGRAM, 0);
161         if (sockfd == -1)
162             exit_player(EXIT_ERROR);
163 
164         // Enable broadcast
165         setsockopt(sockfd, SOL_SOCKET, SO_BROADCAST, &one, sizeof(one));
166 
167 #if HAVE_WINSOCK2_H
168         socketinfo.sin_addr.s_addr = inet_addr(send_to_ip);
169         ip_valid = socketinfo.sin_addr.s_addr != INADDR_NONE;
170 #else
171         ip_valid = inet_aton(send_to_ip, &socketinfo.sin_addr);
172 #endif
173 
174         if (!ip_valid) {
175             mp_msg(MSGT_CPLAYER, MSGL_FATAL, MSGTR_InvalidIP);
176             exit_player(EXIT_ERROR);
177         }
178 
179         socketinfo.sin_family = AF_INET;
180         socketinfo.sin_port   = htons(port);
181     }
182 
183     sendto(sockfd, mesg, strlen(mesg), 0, (struct sockaddr *) &socketinfo,
184            sizeof(socketinfo));
185 }
186 
187 // this function makes sure we stay as close as possible to the master's
188 // position.  returns 1 if the master tells us to exit,
189 // -1 on error and normal timing should be used again, 0 otherwise.
udp_slave_sync(MPContext * mpctx)190 int udp_slave_sync(MPContext *mpctx)
191 {
192     // remember where the master is in the file
193     static double udp_master_position;
194     // whether we timed out before waiting for a master message
195     static int timed_out = -1;
196     // last time we received a valid master message
197     static unsigned last_success;
198     int master_exited;
199 
200     if (timed_out < 0) {
201         // initialize
202         udp_master_position = mpctx->sh_video->pts - udp_seek_threshold / 2;
203         timed_out = 0;
204         last_success = GetTimerMS();
205     }
206 
207     // grab any waiting datagrams without blocking
208     master_exited = get_udp(0, &udp_master_position);
209 
210     while (!master_exited || (!timed_out && master_exited < 0)) {
211         double my_position = mpctx->sh_video->pts;
212 
213         // if we're way off, seek to catch up
214         if (FFABS(my_position - udp_master_position) > udp_seek_threshold) {
215             abs_seek_pos  = SEEK_ABSOLUTE;
216             rel_seek_secs = udp_master_position;
217             break;
218         }
219 
220         // normally we expect that the master will have just played the
221         // frame we're ready to play.  break out and play it, and we'll be
222         // right in sync.
223         // or, the master might be up to a few seconds ahead of us, in
224         // which case we also want to play the current frame immediately,
225         // without waiting.
226         // UDP_TIMING_TOLERANCE is a small value that lets us consider
227         // the master equal to us even if it's very slightly ahead.
228         if (udp_master_position + UDP_TIMING_TOLERANCE > my_position)
229             break;
230 
231         // the remaining case is that we're slightly ahead of the master.
232         // usually, it just means we called get_udp() before the datagram
233         // arrived.  call get_udp again, but this time block until we receive
234         // a datagram.
235         master_exited = get_udp(1, &udp_master_position);
236         if (master_exited < 0)
237             timed_out = 1;
238     }
239 
240     if (master_exited >= 0) {
241         last_success = GetTimerMS();
242         timed_out = 0;
243     } else {
244         master_exited = 0;
245         timed_out |= GetTimerMS() - last_success > 30000;
246     }
247 
248     return timed_out ? -1 : master_exited;
249 }
250