Commit | Line | Data |
---|---|---|
de6d9b64 FB |
1 | /* |
2 | * UDP prototype streaming system | |
406792e7 | 3 | * Copyright (c) 2000, 2001, 2002 Fabrice Bellard |
de6d9b64 | 4 | * |
b78e7197 DB |
5 | * This file is part of FFmpeg. |
6 | * | |
7 | * FFmpeg is free software; you can redistribute it and/or | |
19720f15 FB |
8 | * modify it under the terms of the GNU Lesser General Public |
9 | * License as published by the Free Software Foundation; either | |
b78e7197 | 10 | * version 2.1 of the License, or (at your option) any later version. |
de6d9b64 | 11 | * |
b78e7197 | 12 | * FFmpeg is distributed in the hope that it will be useful, |
de6d9b64 | 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
19720f15 FB |
14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
15 | * Lesser General Public License for more details. | |
de6d9b64 | 16 | * |
19720f15 | 17 | * You should have received a copy of the GNU Lesser General Public |
b78e7197 | 18 | * License along with FFmpeg; if not, write to the Free Software |
5509bffa | 19 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
de6d9b64 | 20 | */ |
3adfb377 LB |
21 | |
22 | /** | |
bad5537e | 23 | * @file libavformat/udp.c |
3adfb377 LB |
24 | * UDP protocol |
25 | */ | |
26 | ||
a9e02947 | 27 | #define _BSD_SOURCE /* Needed for using struct ip_mreq with recent glibc */ |
8be1c656 | 28 | #include "avformat.h" |
de6d9b64 | 29 | #include <unistd.h> |
e4a9e3cc | 30 | #include "internal.h" |
42572ef5 | 31 | #include "network.h" |
087b3272 | 32 | #include "os_support.h" |
b250f9c6 | 33 | #if HAVE_SYS_SELECT_H |
7d084299 JM |
34 | #include <sys/select.h> |
35 | #endif | |
c0a8f8d4 | 36 | #include <sys/time.h> |
de6d9b64 | 37 | |
9c633e9a AB |
38 | #ifndef IPV6_ADD_MEMBERSHIP |
39 | #define IPV6_ADD_MEMBERSHIP IPV6_JOIN_GROUP | |
40 | #define IPV6_DROP_MEMBERSHIP IPV6_LEAVE_GROUP | |
41 | #endif | |
35b74c3d LA |
42 | #ifndef IN_MULTICAST |
43 | #define IN_MULTICAST(a) ((((uint32_t)(a)) & 0xf0000000) == 0xe0000000) | |
44 | #endif | |
45 | #ifndef IN6_IS_ADDR_MULTICAST | |
46 | #define IN6_IS_ADDR_MULTICAST(a) (((uint8_t *) (a))[0] == 0xff) | |
47 | #endif | |
9c633e9a | 48 | |
de6d9b64 | 49 | typedef struct { |
2d225591 FB |
50 | int udp_fd; |
51 | int ttl; | |
236bb1ab | 52 | int buffer_size; |
2d225591 FB |
53 | int is_multicast; |
54 | int local_port; | |
34ecc397 | 55 | int reuse_socket; |
7a91333f | 56 | struct sockaddr_storage dest_addr; |
397db8ac | 57 | int dest_addr_len; |
de6d9b64 FB |
58 | } UDPContext; |
59 | ||
60 | #define UDP_TX_BUF_SIZE 32768 | |
f18cae4d | 61 | #define UDP_MAX_PKT_SIZE 65536 |
de6d9b64 | 62 | |
64810754 RB |
63 | static int udp_set_multicast_ttl(int sockfd, int mcastTTL, |
64 | struct sockaddr *addr) | |
65 | { | |
a8bde059 | 66 | #ifdef IP_MULTICAST_TTL |
7a91333f HZ |
67 | if (addr->sa_family == AF_INET) { |
68 | if (setsockopt(sockfd, IPPROTO_IP, IP_MULTICAST_TTL, &mcastTTL, sizeof(mcastTTL)) < 0) { | |
086119b3 | 69 | av_log(NULL, AV_LOG_ERROR, "setsockopt(IP_MULTICAST_TTL): %s\n", strerror(errno)); |
7a91333f HZ |
70 | return -1; |
71 | } | |
72 | } | |
a8bde059 | 73 | #endif |
b4d68544 | 74 | #if defined(IPPROTO_IPV6) && defined(IPV6_MULTICAST_HOPS) |
7a91333f HZ |
75 | if (addr->sa_family == AF_INET6) { |
76 | if (setsockopt(sockfd, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, &mcastTTL, sizeof(mcastTTL)) < 0) { | |
086119b3 | 77 | av_log(NULL, AV_LOG_ERROR, "setsockopt(IPV6_MULTICAST_HOPS): %s\n", strerror(errno)); |
7a91333f HZ |
78 | return -1; |
79 | } | |
80 | } | |
a8bde059 | 81 | #endif |
7a91333f HZ |
82 | return 0; |
83 | } | |
84 | ||
64810754 RB |
85 | static int udp_join_multicast_group(int sockfd, struct sockaddr *addr) |
86 | { | |
a8bde059 | 87 | #ifdef IP_ADD_MEMBERSHIP |
7a91333f | 88 | if (addr->sa_family == AF_INET) { |
a8bde059 LA |
89 | struct ip_mreq mreq; |
90 | ||
7a91333f HZ |
91 | mreq.imr_multiaddr.s_addr = ((struct sockaddr_in *)addr)->sin_addr.s_addr; |
92 | mreq.imr_interface.s_addr= INADDR_ANY; | |
93 | if (setsockopt(sockfd, IPPROTO_IP, IP_ADD_MEMBERSHIP, (const void *)&mreq, sizeof(mreq)) < 0) { | |
086119b3 | 94 | av_log(NULL, AV_LOG_ERROR, "setsockopt(IP_ADD_MEMBERSHIP): %s\n", strerror(errno)); |
7a91333f HZ |
95 | return -1; |
96 | } | |
97 | } | |
a8bde059 | 98 | #endif |
b4d68544 | 99 | #if HAVE_STRUCT_IPV6_MREQ |
7a91333f | 100 | if (addr->sa_family == AF_INET6) { |
a8bde059 LA |
101 | struct ipv6_mreq mreq6; |
102 | ||
7a91333f HZ |
103 | memcpy(&mreq6.ipv6mr_multiaddr, &(((struct sockaddr_in6 *)addr)->sin6_addr), sizeof(struct in6_addr)); |
104 | mreq6.ipv6mr_interface= 0; | |
105 | if (setsockopt(sockfd, IPPROTO_IPV6, IPV6_ADD_MEMBERSHIP, &mreq6, sizeof(mreq6)) < 0) { | |
086119b3 | 106 | av_log(NULL, AV_LOG_ERROR, "setsockopt(IPV6_ADD_MEMBERSHIP): %s\n", strerror(errno)); |
7a91333f HZ |
107 | return -1; |
108 | } | |
109 | } | |
a8bde059 | 110 | #endif |
7a91333f HZ |
111 | return 0; |
112 | } | |
113 | ||
64810754 RB |
114 | static int udp_leave_multicast_group(int sockfd, struct sockaddr *addr) |
115 | { | |
a8bde059 | 116 | #ifdef IP_DROP_MEMBERSHIP |
7a91333f | 117 | if (addr->sa_family == AF_INET) { |
a8bde059 LA |
118 | struct ip_mreq mreq; |
119 | ||
7a91333f HZ |
120 | mreq.imr_multiaddr.s_addr = ((struct sockaddr_in *)addr)->sin_addr.s_addr; |
121 | mreq.imr_interface.s_addr= INADDR_ANY; | |
122 | if (setsockopt(sockfd, IPPROTO_IP, IP_DROP_MEMBERSHIP, (const void *)&mreq, sizeof(mreq)) < 0) { | |
086119b3 | 123 | av_log(NULL, AV_LOG_ERROR, "setsockopt(IP_DROP_MEMBERSHIP): %s\n", strerror(errno)); |
7a91333f HZ |
124 | return -1; |
125 | } | |
126 | } | |
a8bde059 | 127 | #endif |
b4d68544 | 128 | #if HAVE_STRUCT_IPV6_MREQ |
7a91333f | 129 | if (addr->sa_family == AF_INET6) { |
a8bde059 LA |
130 | struct ipv6_mreq mreq6; |
131 | ||
7a91333f HZ |
132 | memcpy(&mreq6.ipv6mr_multiaddr, &(((struct sockaddr_in6 *)addr)->sin6_addr), sizeof(struct in6_addr)); |
133 | mreq6.ipv6mr_interface= 0; | |
134 | if (setsockopt(sockfd, IPPROTO_IPV6, IPV6_DROP_MEMBERSHIP, &mreq6, sizeof(mreq6)) < 0) { | |
086119b3 | 135 | av_log(NULL, AV_LOG_ERROR, "setsockopt(IPV6_DROP_MEMBERSHIP): %s\n", strerror(errno)); |
7a91333f HZ |
136 | return -1; |
137 | } | |
138 | } | |
a8bde059 | 139 | #endif |
7a91333f HZ |
140 | return 0; |
141 | } | |
142 | ||
64810754 RB |
143 | static struct addrinfo* udp_resolve_host(const char *hostname, int port, |
144 | int type, int family, int flags) | |
145 | { | |
7a91333f HZ |
146 | struct addrinfo hints, *res = 0; |
147 | int error; | |
148 | char sport[16]; | |
d607861c | 149 | const char *node = 0, *service = "0"; |
7a91333f HZ |
150 | |
151 | if (port > 0) { | |
2fc8ea24 | 152 | snprintf(sport, sizeof(sport), "%d", port); |
7a91333f HZ |
153 | service = sport; |
154 | } | |
155 | if ((hostname) && (hostname[0] != '\0') && (hostname[0] != '?')) { | |
156 | node = hostname; | |
157 | } | |
7d8576c2 LA |
158 | memset(&hints, 0, sizeof(hints)); |
159 | hints.ai_socktype = type; | |
160 | hints.ai_family = family; | |
161 | hints.ai_flags = flags; | |
162 | if ((error = getaddrinfo(node, service, &hints, &res))) { | |
11a74f36 | 163 | res = NULL; |
7c823d8b | 164 | av_log(NULL, AV_LOG_ERROR, "udp_resolve_host: %s\n", gai_strerror(error)); |
7d8576c2 LA |
165 | } |
166 | ||
7a91333f HZ |
167 | return res; |
168 | } | |
169 | ||
64810754 RB |
170 | static int udp_set_url(struct sockaddr_storage *addr, |
171 | const char *hostname, int port) | |
172 | { | |
7a91333f | 173 | struct addrinfo *res0; |
d05cb726 LA |
174 | int addr_len; |
175 | ||
7c823d8b | 176 | res0 = udp_resolve_host(hostname, port, SOCK_DGRAM, AF_UNSPEC, 0); |
6f3e0b21 | 177 | if (res0 == 0) return AVERROR(EIO); |
d05cb726 LA |
178 | memcpy(addr, res0->ai_addr, res0->ai_addrlen); |
179 | addr_len = res0->ai_addrlen; | |
7a91333f | 180 | freeaddrinfo(res0); |
d05cb726 LA |
181 | |
182 | return addr_len; | |
7a91333f HZ |
183 | } |
184 | ||
35b74c3d LA |
185 | static int is_multicast_address(struct sockaddr_storage *addr) |
186 | { | |
187 | if (addr->ss_family == AF_INET) { | |
188 | return IN_MULTICAST(ntohl(((struct sockaddr_in *)addr)->sin_addr.s_addr)); | |
189 | } | |
b4d68544 | 190 | #if HAVE_STRUCT_SOCKADDR_IN6 |
35b74c3d LA |
191 | if (addr->ss_family == AF_INET6) { |
192 | return IN6_IS_ADDR_MULTICAST(&((struct sockaddr_in6 *)addr)->sin6_addr); | |
193 | } | |
b4d68544 | 194 | #endif |
35b74c3d LA |
195 | |
196 | return 0; | |
197 | } | |
198 | ||
64810754 RB |
199 | static int udp_socket_create(UDPContext *s, |
200 | struct sockaddr_storage *addr, int *addr_len) | |
aa519c47 | 201 | { |
7a91333f | 202 | int udp_fd = -1; |
276358c1 | 203 | struct addrinfo *res0 = NULL, *res = NULL; |
51844e6c | 204 | int family = AF_UNSPEC; |
115329f1 | 205 | |
51844e6c RB |
206 | if (((struct sockaddr *) &s->dest_addr)->sa_family) |
207 | family = ((struct sockaddr *) &s->dest_addr)->sa_family; | |
7c823d8b | 208 | res0 = udp_resolve_host(0, s->local_port, SOCK_DGRAM, family, AI_PASSIVE); |
7d8576c2 LA |
209 | if (res0 == 0) |
210 | goto fail; | |
211 | for (res = res0; res; res=res->ai_next) { | |
212 | udp_fd = socket(res->ai_family, SOCK_DGRAM, 0); | |
213 | if (udp_fd > 0) break; | |
086119b3 | 214 | av_log(NULL, AV_LOG_ERROR, "socket: %s\n", strerror(errno)); |
7d8576c2 | 215 | } |
276358c1 HZ |
216 | |
217 | if (udp_fd < 0) | |
7a91333f | 218 | goto fail; |
115329f1 | 219 | |
aa519c47 LA |
220 | memcpy(addr, res->ai_addr, res->ai_addrlen); |
221 | *addr_len = res->ai_addrlen; | |
7a91333f | 222 | |
aa519c47 | 223 | freeaddrinfo(res0); |
115329f1 | 224 | |
7a91333f | 225 | return udp_fd; |
115329f1 | 226 | |
7a91333f HZ |
227 | fail: |
228 | if (udp_fd >= 0) | |
7a91333f | 229 | closesocket(udp_fd); |
88730be6 MR |
230 | if(res0) |
231 | freeaddrinfo(res0); | |
7a91333f HZ |
232 | return -1; |
233 | } | |
234 | ||
aa519c47 LA |
235 | static int udp_port(struct sockaddr_storage *addr, int addr_len) |
236 | { | |
03c09e43 | 237 | char sbuf[sizeof(int)*3+1]; |
aa519c47 | 238 | |
03c09e43 | 239 | if (getnameinfo((struct sockaddr *)addr, addr_len, NULL, 0, sbuf, sizeof(sbuf), NI_NUMERICSERV) != 0) { |
086119b3 | 240 | av_log(NULL, AV_LOG_ERROR, "getnameinfo: %s\n", strerror(errno)); |
aa519c47 LA |
241 | return -1; |
242 | } | |
243 | ||
244 | return strtol(sbuf, NULL, 10); | |
245 | } | |
246 | ||
7a91333f | 247 | |
2d225591 FB |
248 | /** |
249 | * If no filename is given to av_open_input_file because you want to | |
250 | * get the local port first, then you must call this function to set | |
251 | * the remote server address. | |
252 | * | |
253 | * url syntax: udp://host:port[?option=val...] | |
35b74c3d | 254 | * option: 'ttl=n' : set the ttl value (for multicast only) |
2d225591 | 255 | * 'localport=n' : set the local port |
9899efb4 | 256 | * 'pkt_size=n' : set max packet size |
34ecc397 | 257 | * 'reuse=1' : enable reusing the socket |
2d225591 FB |
258 | * |
259 | * @param s1 media file context | |
260 | * @param uri of the remote server | |
261 | * @return zero if no error. | |
262 | */ | |
263 | int udp_set_remote_url(URLContext *h, const char *uri) | |
264 | { | |
265 | UDPContext *s = h->priv_data; | |
266 | char hostname[256]; | |
267 | int port; | |
115329f1 | 268 | |
c5c6e67c | 269 | ff_url_split(NULL, 0, NULL, 0, hostname, sizeof(hostname), &port, NULL, 0, uri); |
de6d9b64 | 270 | |
2d225591 | 271 | /* set the destination address */ |
d05cb726 LA |
272 | s->dest_addr_len = udp_set_url(&s->dest_addr, hostname, port); |
273 | if (s->dest_addr_len < 0) { | |
6f3e0b21 | 274 | return AVERROR(EIO); |
d05cb726 | 275 | } |
35b74c3d | 276 | s->is_multicast = is_multicast_address(&s->dest_addr); |
d05cb726 | 277 | |
2d225591 FB |
278 | return 0; |
279 | } | |
280 | ||
281 | /** | |
282 | * Return the local port used by the UDP connexion | |
283 | * @param s1 media file context | |
284 | * @return the local port number | |
285 | */ | |
286 | int udp_get_local_port(URLContext *h) | |
287 | { | |
288 | UDPContext *s = h->priv_data; | |
289 | return s->local_port; | |
290 | } | |
291 | ||
292 | /** | |
293 | * Return the udp file handle for select() usage to wait for several RTP | |
294 | * streams at the same time. | |
295 | * @param h media file context | |
296 | */ | |
f0a80394 RB |
297 | #if (LIBAVFORMAT_VERSION_MAJOR >= 53) |
298 | static | |
299 | #endif | |
2d225591 FB |
300 | int udp_get_file_handle(URLContext *h) |
301 | { | |
302 | UDPContext *s = h->priv_data; | |
303 | return s->udp_fd; | |
304 | } | |
305 | ||
306 | /* put it in UDP context */ | |
de6d9b64 FB |
307 | /* return non zero if error */ |
308 | static int udp_open(URLContext *h, const char *uri, int flags) | |
309 | { | |
de6d9b64 | 310 | char hostname[1024]; |
36b53281 | 311 | int port, udp_fd = -1, tmp, bind_ret = -1; |
2d225591 | 312 | UDPContext *s = NULL; |
88730be6 | 313 | int is_output; |
2d225591 FB |
314 | const char *p; |
315 | char buf[256]; | |
aa519c47 | 316 | struct sockaddr_storage my_addr; |
aa519c47 | 317 | int len; |
de6d9b64 FB |
318 | |
319 | h->is_streamed = 1; | |
9899efb4 | 320 | h->max_packet_size = 1472; |
de6d9b64 | 321 | |
2d225591 | 322 | is_output = (flags & URL_WRONLY); |
115329f1 | 323 | |
51844e6c | 324 | s = av_mallocz(sizeof(UDPContext)); |
2d225591 | 325 | if (!s) |
8fa36ae0 | 326 | return AVERROR(ENOMEM); |
2d225591 FB |
327 | |
328 | h->priv_data = s; | |
329 | s->ttl = 16; | |
236bb1ab JM |
330 | s->buffer_size = is_output ? UDP_TX_BUF_SIZE : UDP_MAX_PKT_SIZE; |
331 | ||
2d225591 FB |
332 | p = strchr(uri, '?'); |
333 | if (p) { | |
34ecc397 | 334 | s->reuse_socket = find_info_tag(buf, sizeof(buf), "reuse", p); |
2d225591 FB |
335 | if (find_info_tag(buf, sizeof(buf), "ttl", p)) { |
336 | s->ttl = strtol(buf, NULL, 10); | |
337 | } | |
338 | if (find_info_tag(buf, sizeof(buf), "localport", p)) { | |
339 | s->local_port = strtol(buf, NULL, 10); | |
340 | } | |
9899efb4 MK |
341 | if (find_info_tag(buf, sizeof(buf), "pkt_size", p)) { |
342 | h->max_packet_size = strtol(buf, NULL, 10); | |
343 | } | |
236bb1ab JM |
344 | if (find_info_tag(buf, sizeof(buf), "buffer_size", p)) { |
345 | s->buffer_size = strtol(buf, NULL, 10); | |
346 | } | |
de6d9b64 | 347 | } |
2d225591 FB |
348 | |
349 | /* fill the dest addr */ | |
c5c6e67c | 350 | ff_url_split(NULL, 0, NULL, 0, hostname, sizeof(hostname), &port, NULL, 0, uri); |
115329f1 | 351 | |
c5c6e67c | 352 | /* XXX: fix ff_url_split */ |
2d225591 FB |
353 | if (hostname[0] == '\0' || hostname[0] == '?') { |
354 | /* only accepts null hostname if input */ | |
35b74c3d | 355 | if (flags & URL_WRONLY) |
2d225591 FB |
356 | goto fail; |
357 | } else { | |
358 | udp_set_remote_url(h, uri); | |
359 | } | |
de6d9b64 | 360 | |
882d00f2 LA |
361 | if (s->is_multicast && !(h->flags & URL_WRONLY)) |
362 | s->local_port = port; | |
aa519c47 | 363 | udp_fd = udp_socket_create(s, &my_addr, &len); |
2d225591 FB |
364 | if (udp_fd < 0) |
365 | goto fail; | |
366 | ||
34ecc397 T |
367 | if (s->reuse_socket) |
368 | if (setsockopt (udp_fd, SOL_SOCKET, SO_REUSEADDR, &(s->reuse_socket), sizeof(s->reuse_socket)) != 0) | |
369 | goto fail; | |
370 | ||
de6d9b64 | 371 | /* the bind is needed to give a port to the socket now */ |
36b53281 JM |
372 | /* if multicast, try the multicast address bind first */ |
373 | if (s->is_multicast && !(h->flags & URL_WRONLY)) { | |
374 | bind_ret = bind(udp_fd,(struct sockaddr *)&s->dest_addr, len); | |
375 | } | |
376 | /* bind to the local address if not multicast or if the multicast | |
377 | * bind failed */ | |
378 | if (bind_ret < 0 && bind(udp_fd,(struct sockaddr *)&my_addr, len) < 0) | |
de6d9b64 FB |
379 | goto fail; |
380 | ||
d4936869 LA |
381 | len = sizeof(my_addr); |
382 | getsockname(udp_fd, (struct sockaddr *)&my_addr, &len); | |
aa519c47 LA |
383 | s->local_port = udp_port(&my_addr, len); |
384 | ||
7a91333f HZ |
385 | if (s->is_multicast) { |
386 | if (h->flags & URL_WRONLY) { | |
a8bde059 | 387 | /* output */ |
9cdcb04f | 388 | if (udp_set_multicast_ttl(udp_fd, s->ttl, (struct sockaddr *)&s->dest_addr) < 0) |
7a91333f HZ |
389 | goto fail; |
390 | } else { | |
a8bde059 | 391 | /* input */ |
9cdcb04f | 392 | if (udp_join_multicast_group(udp_fd, (struct sockaddr *)&s->dest_addr) < 0) |
7a91333f HZ |
393 | goto fail; |
394 | } | |
395 | } | |
de6d9b64 | 396 | |
2d225591 FB |
397 | if (is_output) { |
398 | /* limit the tx buf size to limit latency */ | |
236bb1ab | 399 | tmp = s->buffer_size; |
2d225591 | 400 | if (setsockopt(udp_fd, SOL_SOCKET, SO_SNDBUF, &tmp, sizeof(tmp)) < 0) { |
086119b3 | 401 | av_log(NULL, AV_LOG_ERROR, "setsockopt(SO_SNDBUF): %s\n", strerror(errno)); |
2d225591 FB |
402 | goto fail; |
403 | } | |
f18cae4d RP |
404 | } else { |
405 | /* set udp recv buffer size to the largest possible udp packet size to | |
406 | * avoid losing data on OSes that set this too low by default. */ | |
236bb1ab JM |
407 | tmp = s->buffer_size; |
408 | if (setsockopt(udp_fd, SOL_SOCKET, SO_RCVBUF, &tmp, sizeof(tmp)) < 0) { | |
409 | av_log(NULL, AV_LOG_WARNING, "setsockopt(SO_RECVBUF): %s\n", strerror(errno)); | |
410 | } | |
ce09d560 JM |
411 | /* make the socket non-blocking */ |
412 | ff_socket_nonblock(udp_fd, 1); | |
2d225591 FB |
413 | } |
414 | ||
415 | s->udp_fd = udp_fd; | |
de6d9b64 FB |
416 | return 0; |
417 | fail: | |
2d225591 | 418 | if (udp_fd >= 0) |
9ddd71fc | 419 | closesocket(udp_fd); |
2d225591 | 420 | av_free(s); |
6f3e0b21 | 421 | return AVERROR(EIO); |
de6d9b64 FB |
422 | } |
423 | ||
0c1a9eda | 424 | static int udp_read(URLContext *h, uint8_t *buf, int size) |
de6d9b64 FB |
425 | { |
426 | UDPContext *s = h->priv_data; | |
191e8ca7 | 427 | int len; |
7d084299 JM |
428 | fd_set rfds; |
429 | int ret; | |
430 | struct timeval tv; | |
2d225591 FB |
431 | |
432 | for(;;) { | |
7d084299 JM |
433 | if (url_interrupt_cb()) |
434 | return AVERROR(EINTR); | |
435 | FD_ZERO(&rfds); | |
436 | FD_SET(s->udp_fd, &rfds); | |
437 | tv.tv_sec = 0; | |
438 | tv.tv_usec = 100 * 1000; | |
439 | ret = select(s->udp_fd + 1, &rfds, NULL, NULL, &tv); | |
cae9a15c MS |
440 | if (ret < 0) { |
441 | if (ff_neterrno() == FF_NETERROR(EINTR)) | |
442 | continue; | |
7d084299 | 443 | return AVERROR(EIO); |
cae9a15c | 444 | } |
7d084299 JM |
445 | if (!(ret > 0 && FD_ISSET(s->udp_fd, &rfds))) |
446 | continue; | |
ce09d560 | 447 | len = recv(s->udp_fd, buf, size, 0); |
2d225591 | 448 | if (len < 0) { |
8da4034f AB |
449 | if (ff_neterrno() != FF_NETERROR(EAGAIN) && |
450 | ff_neterrno() != FF_NETERROR(EINTR)) | |
6f3e0b21 | 451 | return AVERROR(EIO); |
2d225591 FB |
452 | } else { |
453 | break; | |
454 | } | |
455 | } | |
456 | return len; | |
de6d9b64 FB |
457 | } |
458 | ||
0c1a9eda | 459 | static int udp_write(URLContext *h, uint8_t *buf, int size) |
de6d9b64 FB |
460 | { |
461 | UDPContext *s = h->priv_data; | |
2d225591 FB |
462 | int ret; |
463 | ||
464 | for(;;) { | |
115329f1 | 465 | ret = sendto (s->udp_fd, buf, size, 0, |
2d225591 | 466 | (struct sockaddr *) &s->dest_addr, |
7a91333f | 467 | s->dest_addr_len); |
2d225591 | 468 | if (ret < 0) { |
8da4034f AB |
469 | if (ff_neterrno() != FF_NETERROR(EINTR) && |
470 | ff_neterrno() != FF_NETERROR(EAGAIN)) | |
6f3e0b21 | 471 | return AVERROR(EIO); |
2d225591 FB |
472 | } else { |
473 | break; | |
474 | } | |
de6d9b64 | 475 | } |
2d225591 FB |
476 | return size; |
477 | } | |
478 | ||
479 | static int udp_close(URLContext *h) | |
480 | { | |
481 | UDPContext *s = h->priv_data; | |
482 | ||
7a91333f | 483 | if (s->is_multicast && !(h->flags & URL_WRONLY)) |
9cdcb04f | 484 | udp_leave_multicast_group(s->udp_fd, (struct sockaddr *)&s->dest_addr); |
9ddd71fc | 485 | closesocket(s->udp_fd); |
2d225591 FB |
486 | av_free(s); |
487 | return 0; | |
de6d9b64 FB |
488 | } |
489 | ||
490 | URLProtocol udp_protocol = { | |
491 | "udp", | |
492 | udp_open, | |
2d225591 | 493 | udp_read, |
de6d9b64 FB |
494 | udp_write, |
495 | NULL, /* seek */ | |
496 | udp_close, | |
f0a80394 | 497 | .url_get_file_handle = udp_get_file_handle, |
de6d9b64 | 498 | }; |