From 2d225591e97f407ce9ae794d698fcb5d15d009a2 Mon Sep 17 00:00:00 2001 From: Fabrice Bellard Date: Wed, 24 Jul 2002 17:45:41 +0000 Subject: [PATCH] better UDP support - added preliminary multicast support (untested) Originally committed as revision 793 to svn://svn.ffmpeg.org/ffmpeg/trunk --- libav/udp.c | 271 +++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 193 insertions(+), 78 deletions(-) diff --git a/libav/udp.c b/libav/udp.c index 06ad34d582..ad514cb2fb 100644 --- a/libav/udp.c +++ b/libav/udp.c @@ -1,6 +1,6 @@ /* * UDP prototype streaming system - * Copyright (c) 2000 Fabrice Bellard. + * Copyright (c) 2000, 2001, 2002 Fabrice Bellard. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -25,118 +25,233 @@ #include typedef struct { - int udp_socket; - int max_payload_size; /* in bytes */ + int udp_fd; + int ttl; + int is_multicast; + int local_port; + struct ip_mreq mreq; + struct sockaddr_in dest_addr; } UDPContext; #define UDP_TX_BUF_SIZE 32768 -/* put it in UDP context */ -static struct sockaddr_in dest_addr; +/** + * If no filename is given to av_open_input_file because you want to + * get the local port first, then you must call this function to set + * the remote server address. + * + * url syntax: udp://host:port[?option=val...] + * option: 'multicast=1' : enable multicast + * 'ttl=n' : set the ttl value (for multicast only) + * 'localport=n' : set the local port + * + * @param s1 media file context + * @param uri of the remote server + * @return zero if no error. + */ +int udp_set_remote_url(URLContext *h, const char *uri) +{ + UDPContext *s = h->priv_data; + char hostname[256]; + int port; + + url_split(NULL, 0, hostname, sizeof(hostname), &port, NULL, 0, uri); + /* set the destination address */ + if (resolve_host(&s->dest_addr.sin_addr, hostname) < 0) + return -EIO; + s->dest_addr.sin_family = AF_INET; + s->dest_addr.sin_port = htons(port); + return 0; +} + +/** + * Return the local port used by the UDP connexion + * @param s1 media file context + * @return the local port number + */ +int udp_get_local_port(URLContext *h) +{ + UDPContext *s = h->priv_data; + return s->local_port; +} + +/** + * Return the udp file handle for select() usage to wait for several RTP + * streams at the same time. + * @param h media file context + */ +int udp_get_file_handle(URLContext *h) +{ + UDPContext *s = h->priv_data; + return s->udp_fd; +} + +/* put it in UDP context */ /* return non zero if error */ static int udp_open(URLContext *h, const char *uri, int flags) { - int local_port = 0; - struct sockaddr_in my_addr; - const char *p, *q; + struct sockaddr_in my_addr, my_addr1; char hostname[1024]; - int port, udp_socket, tmp; - struct hostent *hp; - UDPContext *s; + int port, udp_fd = -1, tmp; + UDPContext *s = NULL; + int is_output, len; + const char *p; + char buf[256]; h->is_streamed = 1; - if (!(flags & URL_WRONLY)) - return -EIO; + is_output = (flags & URL_WRONLY); - /* fill the dest addr */ - p = uri; - if (!strstart(p, "udp:", &p)) - return -1; - q = strchr(p, ':'); - if (!q) - return -1; - memcpy(hostname, p, q - p); - hostname[q - p] = '\0'; - port = strtol(q+1, NULL, 10); - if (port <= 0) - return -1; - - dest_addr.sin_family = AF_INET; - dest_addr.sin_port = htons(port); - if ((inet_aton(hostname, &dest_addr.sin_addr)) == 0) { - hp = gethostbyname(hostname); - if (!hp) - return -1; - memcpy ((char *) &dest_addr.sin_addr, hp->h_addr, hp->h_length); - } - - udp_socket = socket(PF_INET, SOCK_DGRAM, 0); - if (udp_socket < 0) - return -1; - - my_addr.sin_family = AF_INET; - my_addr.sin_port = htons(local_port); - my_addr.sin_addr.s_addr = htonl (INADDR_ANY); - - /* the bind is needed to give a port to the socket now */ - if (bind(udp_socket,(struct sockaddr *)&my_addr, sizeof(my_addr)) < 0) - goto fail; - - /* limit the tx buf size to limit latency */ - - tmp = UDP_TX_BUF_SIZE; - if (setsockopt(udp_socket, SOL_SOCKET, SO_SNDBUF, &tmp, sizeof(tmp)) < 0) { - perror("setsockopt sndbuf"); - goto fail; - } - s = av_malloc(sizeof(UDPContext)); if (!s) return -ENOMEM; + h->priv_data = s; - s->udp_socket = udp_socket; - h->packet_size = 1500; + s->ttl = 16; + s->is_multicast = 0; + p = strchr(uri, '?'); + if (p) { + s->is_multicast = find_info_tag(buf, sizeof(buf), "multicast", p); + if (find_info_tag(buf, sizeof(buf), "ttl", p)) { + s->ttl = strtol(buf, NULL, 10); + } + if (find_info_tag(buf, sizeof(buf), "localport", p)) { + s->local_port = strtol(buf, NULL, 10); + } + } + + /* fill the dest addr */ + url_split(NULL, 0, hostname, sizeof(hostname), &port, NULL, 0, uri); + + /* XXX: fix url_split */ + if (hostname[0] == '\0' || hostname[0] == '?') { + /* only accepts null hostname if input */ + if (s->is_multicast || (flags & URL_WRONLY)) + goto fail; + } else { + udp_set_remote_url(h, uri); + } + + udp_fd = socket(PF_INET, SOCK_DGRAM, 0); + if (udp_fd < 0) + goto fail; + + if (s->is_multicast && !(h->flags & URL_WRONLY)) { + /* special case: the bind must be done on the multicast address */ + my_addr = s->dest_addr; + } else { + my_addr.sin_family = AF_INET; + my_addr.sin_port = htons(s->local_port); + my_addr.sin_addr.s_addr = htonl (INADDR_ANY); + } + + /* the bind is needed to give a port to the socket now */ + if (bind(udp_fd,(struct sockaddr *)&my_addr, sizeof(my_addr)) < 0) + goto fail; + + len = sizeof(my_addr1); + getsockname(udp_fd, (struct sockaddr *)&my_addr1, &len); + s->local_port = ntohs(my_addr1.sin_port); + + if (s->is_multicast) { + if (h->flags & URL_WRONLY) { + /* output */ + if (setsockopt(udp_fd, IPPROTO_IP, IP_MULTICAST_TTL, + &s->ttl, sizeof(s->ttl)) < 0) { + perror("IP_MULTICAST_TTL"); + goto fail; + } + } else { + /* input */ + memset(&s->mreq, 0, sizeof(s->mreq)); + s->mreq.imr_multiaddr = s->dest_addr.sin_addr; + s->mreq.imr_interface.s_addr = htonl (INADDR_ANY); + if (setsockopt(udp_fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, + &s->mreq, sizeof(s->mreq)) < 0) { + perror("rtp: IP_ADD_MEMBERSHIP"); + goto fail; + } + } + } + + if (is_output) { + /* limit the tx buf size to limit latency */ + tmp = UDP_TX_BUF_SIZE; + if (setsockopt(udp_fd, SOL_SOCKET, SO_SNDBUF, &tmp, sizeof(tmp)) < 0) { + perror("setsockopt sndbuf"); + goto fail; + } + } + + s->udp_fd = udp_fd; + h->max_packet_size = 1472; /* XXX: probe it ? */ return 0; fail: + if (udp_fd >= 0) + close(udp_fd); + av_free(s); return -EIO; } -int udp_close(URLContext *h) +static int udp_read(URLContext *h, UINT8 *buf, int size) { UDPContext *s = h->priv_data; - close(s->udp_socket); - return 0; + struct sockaddr_in from; + int from_len, len; + + for(;;) { + from_len = sizeof(from); + len = recvfrom (s->udp_fd, buf, size, 0, + (struct sockaddr *)&from, &from_len); + if (len < 0) { + if (errno != EAGAIN && errno != EINTR) + return -EIO; + } else { + break; + } + } + return len; } -int udp_write(URLContext *h, UINT8 *buf, int size) +static int udp_write(URLContext *h, UINT8 *buf, int size) { UDPContext *s = h->priv_data; - int ret, len, size1; + int ret; - /* primitive way to avoid big packets */ - size1 = size; - while (size > 0) { - len = size; - if (len > h->packet_size) - len = h->packet_size; - - ret = sendto (s->udp_socket, buf, len, 0, - (struct sockaddr *) &dest_addr, - sizeof (dest_addr)); - if (ret < 0) - perror("sendto"); - buf += len; - size -= len; + for(;;) { + ret = sendto (s->udp_fd, buf, size, 0, + (struct sockaddr *) &s->dest_addr, + sizeof (s->dest_addr)); + if (ret < 0) { + if (errno != EINTR && errno != EAGAIN) + return -EIO; + } else { + break; + } } - return size1 - size; + return size; +} + +static int udp_close(URLContext *h) +{ + UDPContext *s = h->priv_data; + + if (s->is_multicast && !(h->flags & URL_WRONLY)) { + if (setsockopt(s->udp_fd, IPPROTO_IP, IP_DROP_MEMBERSHIP, + &s->mreq, sizeof(s->mreq)) < 0) { + perror("IP_DROP_MEMBERSHIP"); + } + } + close(s->udp_fd); + av_free(s); + return 0; } URLProtocol udp_protocol = { "udp", udp_open, - NULL, /* read */ + udp_read, udp_write, NULL, /* seek */ udp_close,