1/* 2 * Session Announcement Protocol (RFC 2974) muxer 3 * Copyright (c) 2010 Martin Storsjo 4 * 5 * This file is part of FFmpeg. 6 * 7 * FFmpeg is free software; you can redistribute it and/or 8 * modify it under the terms of the GNU Lesser General Public 9 * License as published by the Free Software Foundation; either 10 * version 2.1 of the License, or (at your option) any later version. 11 * 12 * FFmpeg 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 GNU 15 * Lesser General Public License for more details. 16 * 17 * You should have received a copy of the GNU Lesser General Public 18 * License along with FFmpeg; if not, write to the Free Software 19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 20 */ 21 22#include "avformat.h" 23#include "libavutil/parseutils.h" 24#include "libavutil/random_seed.h" 25#include "libavutil/avstring.h" 26#include "libavutil/dict.h" 27#include "libavutil/intreadwrite.h" 28#include "libavutil/time.h" 29#include "internal.h" 30#include "mux.h" 31#include "network.h" 32#include "os_support.h" 33#include "rtpenc_chain.h" 34#include "url.h" 35 36struct SAPState { 37 uint8_t *ann; 38 int ann_size; 39 URLContext *ann_fd; 40 int64_t last_time; 41}; 42 43static int sap_write_close(AVFormatContext *s) 44{ 45 struct SAPState *sap = s->priv_data; 46 int i; 47 48 for (i = 0; i < s->nb_streams; i++) { 49 AVFormatContext *rtpctx = s->streams[i]->priv_data; 50 if (!rtpctx) 51 continue; 52 av_write_trailer(rtpctx); 53 avio_closep(&rtpctx->pb); 54 avformat_free_context(rtpctx); 55 s->streams[i]->priv_data = NULL; 56 } 57 58 if (sap->last_time && sap->ann && sap->ann_fd) { 59 sap->ann[0] |= 4; /* Session deletion*/ 60 ffurl_write(sap->ann_fd, sap->ann, sap->ann_size); 61 } 62 63 av_freep(&sap->ann); 64 ffurl_closep(&sap->ann_fd); 65 ff_network_close(); 66 return 0; 67} 68 69static int sap_write_header(AVFormatContext *s) 70{ 71 struct SAPState *sap = s->priv_data; 72 char host[1024], path[1024], url[1024], announce_addr[50] = ""; 73 char *option_list; 74 int port = 9875, base_port = 5004, i, pos = 0, same_port = 0, ttl = 255; 75 AVFormatContext **contexts = NULL; 76 int ret = 0; 77 struct sockaddr_storage localaddr; 78 socklen_t addrlen = sizeof(localaddr); 79 int udp_fd; 80 AVDictionaryEntry* title = av_dict_get(s->metadata, "title", NULL, 0); 81 82 if (!ff_network_init()) 83 return AVERROR(EIO); 84 85 /* extract hostname and port */ 86 av_url_split(NULL, 0, NULL, 0, host, sizeof(host), &base_port, 87 path, sizeof(path), s->url); 88 if (base_port < 0) 89 base_port = 5004; 90 91 /* search for options */ 92 option_list = strrchr(path, '?'); 93 if (option_list) { 94 char buf[50]; 95 if (av_find_info_tag(buf, sizeof(buf), "announce_port", option_list)) { 96 port = strtol(buf, NULL, 10); 97 } 98 if (av_find_info_tag(buf, sizeof(buf), "same_port", option_list)) { 99 same_port = strtol(buf, NULL, 10); 100 } 101 if (av_find_info_tag(buf, sizeof(buf), "ttl", option_list)) { 102 ttl = strtol(buf, NULL, 10); 103 } 104 if (av_find_info_tag(buf, sizeof(buf), "announce_addr", option_list)) { 105 av_strlcpy(announce_addr, buf, sizeof(announce_addr)); 106 } 107 } 108 109 if (!announce_addr[0]) { 110 struct addrinfo hints = { 0 }, *ai = NULL; 111 hints.ai_family = AF_UNSPEC; 112 if (getaddrinfo(host, NULL, &hints, &ai)) { 113 av_log(s, AV_LOG_ERROR, "Unable to resolve %s\n", host); 114 ret = AVERROR(EIO); 115 goto fail; 116 } 117 if (ai->ai_family == AF_INET) { 118 /* Also known as sap.mcast.net */ 119 av_strlcpy(announce_addr, "224.2.127.254", sizeof(announce_addr)); 120#if HAVE_STRUCT_SOCKADDR_IN6 121 } else if (ai->ai_family == AF_INET6) { 122 /* With IPv6, you can use the same destination in many different 123 * multicast subnets, to choose how far you want it routed. 124 * This one is intended to be routed globally. */ 125 av_strlcpy(announce_addr, "ff0e::2:7ffe", sizeof(announce_addr)); 126#endif 127 } else { 128 freeaddrinfo(ai); 129 av_log(s, AV_LOG_ERROR, "Host %s resolved to unsupported " 130 "address family\n", host); 131 ret = AVERROR(EIO); 132 goto fail; 133 } 134 freeaddrinfo(ai); 135 } 136 137 contexts = av_calloc(s->nb_streams, sizeof(*contexts)); 138 if (!contexts) { 139 ret = AVERROR(ENOMEM); 140 goto fail; 141 } 142 143 if (s->start_time_realtime == 0 || s->start_time_realtime == AV_NOPTS_VALUE) 144 s->start_time_realtime = av_gettime(); 145 for (i = 0; i < s->nb_streams; i++) { 146 URLContext *fd; 147 char *new_url; 148 149 ff_url_join(url, sizeof(url), "rtp", NULL, host, base_port, 150 "?ttl=%d", ttl); 151 if (!same_port) 152 base_port += 2; 153 ret = ffurl_open_whitelist(&fd, url, AVIO_FLAG_WRITE, 154 &s->interrupt_callback, NULL, 155 s->protocol_whitelist, s->protocol_blacklist, NULL); 156 if (ret) { 157 ret = AVERROR(EIO); 158 goto fail; 159 } 160 ret = ff_rtp_chain_mux_open(&contexts[i], s, s->streams[i], fd, 0, i); 161 if (ret < 0) 162 goto fail; 163 s->streams[i]->priv_data = contexts[i]; 164 s->streams[i]->time_base = contexts[i]->streams[0]->time_base; 165 new_url = av_strdup(url); 166 if (!new_url) { 167 ret = AVERROR(ENOMEM); 168 goto fail; 169 } 170 ff_format_set_url(contexts[i], new_url); 171 } 172 173 if (s->nb_streams > 0 && title) 174 av_dict_set(&contexts[0]->metadata, "title", title->value, 0); 175 176 ff_url_join(url, sizeof(url), "udp", NULL, announce_addr, port, 177 "?ttl=%d&connect=1", ttl); 178 ret = ffurl_open_whitelist(&sap->ann_fd, url, AVIO_FLAG_WRITE, 179 &s->interrupt_callback, NULL, 180 s->protocol_whitelist, s->protocol_blacklist, NULL); 181 if (ret) { 182 ret = AVERROR(EIO); 183 goto fail; 184 } 185 186 udp_fd = ffurl_get_file_handle(sap->ann_fd); 187 if (getsockname(udp_fd, (struct sockaddr*) &localaddr, &addrlen)) { 188 ret = AVERROR(EIO); 189 goto fail; 190 } 191 if (localaddr.ss_family != AF_INET 192#if HAVE_STRUCT_SOCKADDR_IN6 193 && localaddr.ss_family != AF_INET6 194#endif 195 ) { 196 av_log(s, AV_LOG_ERROR, "Unsupported protocol family\n"); 197 ret = AVERROR(EIO); 198 goto fail; 199 } 200 sap->ann_size = 8192; 201 sap->ann = av_mallocz(sap->ann_size); 202 if (!sap->ann) { 203 ret = AVERROR(EIO); 204 goto fail; 205 } 206 sap->ann[pos] = (1 << 5); 207#if HAVE_STRUCT_SOCKADDR_IN6 208 if (localaddr.ss_family == AF_INET6) 209 sap->ann[pos] |= 0x10; 210#endif 211 pos++; 212 sap->ann[pos++] = 0; /* Authentication length */ 213 AV_WB16(&sap->ann[pos], av_get_random_seed()); 214 pos += 2; 215 if (localaddr.ss_family == AF_INET) { 216 memcpy(&sap->ann[pos], &((struct sockaddr_in*)&localaddr)->sin_addr, 217 sizeof(struct in_addr)); 218 pos += sizeof(struct in_addr); 219#if HAVE_STRUCT_SOCKADDR_IN6 220 } else { 221 memcpy(&sap->ann[pos], &((struct sockaddr_in6*)&localaddr)->sin6_addr, 222 sizeof(struct in6_addr)); 223 pos += sizeof(struct in6_addr); 224#endif 225 } 226 227 av_strlcpy(&sap->ann[pos], "application/sdp", sap->ann_size - pos); 228 pos += strlen(&sap->ann[pos]) + 1; 229 230 if (av_sdp_create(contexts, s->nb_streams, &sap->ann[pos], 231 sap->ann_size - pos)) { 232 ret = AVERROR_INVALIDDATA; 233 goto fail; 234 } 235 av_freep(&contexts); 236 av_log(s, AV_LOG_VERBOSE, "SDP:\n%s\n", &sap->ann[pos]); 237 pos += strlen(&sap->ann[pos]); 238 sap->ann_size = pos; 239 240 if (sap->ann_size > sap->ann_fd->max_packet_size) { 241 av_log(s, AV_LOG_ERROR, "Announcement too large to send in one " 242 "packet\n"); 243 goto fail; 244 } 245 246 return 0; 247 248fail: 249 av_free(contexts); 250 sap_write_close(s); 251 return ret; 252} 253 254static int sap_write_packet(AVFormatContext *s, AVPacket *pkt) 255{ 256 AVFormatContext *rtpctx; 257 struct SAPState *sap = s->priv_data; 258 int64_t now = av_gettime_relative(); 259 260 if (!sap->last_time || now - sap->last_time > 5000000) { 261 int ret = ffurl_write(sap->ann_fd, sap->ann, sap->ann_size); 262 /* Don't abort even if we get "Destination unreachable" */ 263 if (ret < 0 && ret != AVERROR(ECONNREFUSED)) 264 return ret; 265 sap->last_time = now; 266 } 267 rtpctx = s->streams[pkt->stream_index]->priv_data; 268 return ff_write_chained(rtpctx, 0, pkt, s, 0); 269} 270 271const AVOutputFormat ff_sap_muxer = { 272 .name = "sap", 273 .long_name = NULL_IF_CONFIG_SMALL("SAP output"), 274 .priv_data_size = sizeof(struct SAPState), 275 .audio_codec = AV_CODEC_ID_AAC, 276 .video_codec = AV_CODEC_ID_MPEG4, 277 .write_header = sap_write_header, 278 .write_packet = sap_write_packet, 279 .write_trailer = sap_write_close, 280 .flags = AVFMT_NOFILE | AVFMT_GLOBALHEADER, 281}; 282