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