2 * Session Announcement Protocol (RFC 2974) muxer
3 * Copyright (c) 2010 Martin Storsjo
5 * This file is part of FFmpeg.
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.
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.
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
23 #include "libavutil/mem.h"
24 #include "libavutil/parseutils.h"
25 #include "libavutil/random_seed.h"
26 #include "libavutil/avstring.h"
27 #include "libavutil/dict.h"
28 #include "libavutil/intreadwrite.h"
29 #include "libavutil/time.h"
33 #include "os_support.h"
34 #include "rtpenc_chain.h"
44 static int sap_write_close(AVFormatContext
*s
)
46 struct SAPState
*sap
= s
->priv_data
;
49 for (i
= 0; i
< s
->nb_streams
; i
++) {
50 AVFormatContext
*rtpctx
= s
->streams
[i
]->priv_data
;
53 av_write_trailer(rtpctx
);
54 avio_closep(&rtpctx
->pb
);
55 avformat_free_context(rtpctx
);
56 s
->streams
[i
]->priv_data
= NULL
;
59 if (sap
->last_time
&& sap
->ann
&& sap
->ann_fd
) {
60 sap
->ann
[0] |= 4; /* Session deletion*/
61 ffurl_write(sap
->ann_fd
, sap
->ann
, sap
->ann_size
);
65 ffurl_closep(&sap
->ann_fd
);
70 static int sap_write_header(AVFormatContext
*s
)
72 struct SAPState
*sap
= s
->priv_data
;
73 char host
[1024], path
[1024], url
[1024], announce_addr
[50] = "";
75 int port
= 9875, base_port
= 5004, i
, pos
= 0, same_port
= 0, ttl
= 255;
76 AVFormatContext
**contexts
= NULL
;
78 struct sockaddr_storage localaddr
;
79 socklen_t addrlen
= sizeof(localaddr
);
81 AVDictionaryEntry
* title
= av_dict_get(s
->metadata
, "title", NULL
, 0);
83 if (!ff_network_init())
86 /* extract hostname and port */
87 av_url_split(NULL
, 0, NULL
, 0, host
, sizeof(host
), &base_port
,
88 path
, sizeof(path
), s
->url
);
92 /* search for options */
93 option_list
= strrchr(path
, '?');
96 if (av_find_info_tag(buf
, sizeof(buf
), "announce_port", option_list
)) {
97 port
= strtol(buf
, NULL
, 10);
99 if (av_find_info_tag(buf
, sizeof(buf
), "same_port", option_list
)) {
100 same_port
= strtol(buf
, NULL
, 10);
102 if (av_find_info_tag(buf
, sizeof(buf
), "ttl", option_list
)) {
103 ttl
= strtol(buf
, NULL
, 10);
105 if (av_find_info_tag(buf
, sizeof(buf
), "announce_addr", option_list
)) {
106 av_strlcpy(announce_addr
, buf
, sizeof(announce_addr
));
110 if (!announce_addr
[0]) {
111 struct addrinfo hints
= { 0 }, *ai
= NULL
;
112 hints
.ai_family
= AF_UNSPEC
;
113 if (getaddrinfo(host
, NULL
, &hints
, &ai
)) {
114 av_log(s
, AV_LOG_ERROR
, "Unable to resolve %s\n", host
);
118 if (ai
->ai_family
== AF_INET
) {
119 /* Also known as sap.mcast.net */
120 av_strlcpy(announce_addr
, "224.2.127.254", sizeof(announce_addr
));
121 #if HAVE_STRUCT_SOCKADDR_IN6
122 } else if (ai
->ai_family
== AF_INET6
) {
123 /* With IPv6, you can use the same destination in many different
124 * multicast subnets, to choose how far you want it routed.
125 * This one is intended to be routed globally. */
126 av_strlcpy(announce_addr
, "ff0e::2:7ffe", sizeof(announce_addr
));
130 av_log(s
, AV_LOG_ERROR
, "Host %s resolved to unsupported "
131 "address family\n", host
);
138 contexts
= av_calloc(s
->nb_streams
, sizeof(*contexts
));
140 ret
= AVERROR(ENOMEM
);
144 if (s
->start_time_realtime
== 0 || s
->start_time_realtime
== AV_NOPTS_VALUE
)
145 s
->start_time_realtime
= av_gettime();
146 for (i
= 0; i
< s
->nb_streams
; i
++) {
150 ff_url_join(url
, sizeof(url
), "rtp", NULL
, host
, base_port
,
154 ret
= ffurl_open_whitelist(&fd
, url
, AVIO_FLAG_WRITE
,
155 &s
->interrupt_callback
, NULL
,
156 s
->protocol_whitelist
, s
->protocol_blacklist
, NULL
);
161 ret
= ff_rtp_chain_mux_open(&contexts
[i
], s
, s
->streams
[i
], fd
, 0, i
);
164 s
->streams
[i
]->priv_data
= contexts
[i
];
165 s
->streams
[i
]->time_base
= contexts
[i
]->streams
[0]->time_base
;
166 new_url
= av_strdup(url
);
168 ret
= AVERROR(ENOMEM
);
171 ff_format_set_url(contexts
[i
], new_url
);
174 if (s
->nb_streams
> 0 && title
)
175 av_dict_set(&contexts
[0]->metadata
, "title", title
->value
, 0);
177 ff_url_join(url
, sizeof(url
), "udp", NULL
, announce_addr
, port
,
178 "?ttl=%d&connect=1", ttl
);
179 ret
= ffurl_open_whitelist(&sap
->ann_fd
, url
, AVIO_FLAG_WRITE
,
180 &s
->interrupt_callback
, NULL
,
181 s
->protocol_whitelist
, s
->protocol_blacklist
, NULL
);
187 udp_fd
= ffurl_get_file_handle(sap
->ann_fd
);
188 if (getsockname(udp_fd
, (struct sockaddr
*) &localaddr
, &addrlen
)) {
192 if (localaddr
.ss_family
!= AF_INET
193 #if HAVE_STRUCT_SOCKADDR_IN6
194 && localaddr
.ss_family
!= AF_INET6
197 av_log(s
, AV_LOG_ERROR
, "Unsupported protocol family\n");
201 sap
->ann_size
= 8192;
202 sap
->ann
= av_mallocz(sap
->ann_size
);
207 sap
->ann
[pos
] = (1 << 5);
208 #if HAVE_STRUCT_SOCKADDR_IN6
209 if (localaddr
.ss_family
== AF_INET6
)
210 sap
->ann
[pos
] |= 0x10;
213 sap
->ann
[pos
++] = 0; /* Authentication length */
214 AV_WB16(&sap
->ann
[pos
], av_get_random_seed());
216 if (localaddr
.ss_family
== AF_INET
) {
217 memcpy(&sap
->ann
[pos
], &((struct sockaddr_in
*)&localaddr
)->sin_addr
,
218 sizeof(struct in_addr
));
219 pos
+= sizeof(struct in_addr
);
220 #if HAVE_STRUCT_SOCKADDR_IN6
222 memcpy(&sap
->ann
[pos
], &((struct sockaddr_in6
*)&localaddr
)->sin6_addr
,
223 sizeof(struct in6_addr
));
224 pos
+= sizeof(struct in6_addr
);
228 av_strlcpy(&sap
->ann
[pos
], "application/sdp", sap
->ann_size
- pos
);
229 pos
+= strlen(&sap
->ann
[pos
]) + 1;
231 if (av_sdp_create(contexts
, s
->nb_streams
, &sap
->ann
[pos
],
232 sap
->ann_size
- pos
)) {
233 ret
= AVERROR_INVALIDDATA
;
237 av_log(s
, AV_LOG_VERBOSE
, "SDP:\n%s\n", &sap
->ann
[pos
]);
238 pos
+= strlen(&sap
->ann
[pos
]);
241 if (sap
->ann_size
> sap
->ann_fd
->max_packet_size
) {
242 av_log(s
, AV_LOG_ERROR
, "Announcement too large to send in one "
255 static int sap_write_packet(AVFormatContext
*s
, AVPacket
*pkt
)
257 AVFormatContext
*rtpctx
;
258 struct SAPState
*sap
= s
->priv_data
;
259 int64_t now
= av_gettime_relative();
261 if (!sap
->last_time
|| now
- sap
->last_time
> 5000000) {
262 int ret
= ffurl_write(sap
->ann_fd
, sap
->ann
, sap
->ann_size
);
263 /* Don't abort even if we get "Destination unreachable" */
264 if (ret
< 0 && ret
!= AVERROR(ECONNREFUSED
))
266 sap
->last_time
= now
;
268 rtpctx
= s
->streams
[pkt
->stream_index
]->priv_data
;
269 return ff_write_chained(rtpctx
, 0, pkt
, s
, 0);
272 const FFOutputFormat ff_sap_muxer
= {
274 .p
.long_name
= NULL_IF_CONFIG_SMALL("SAP output"),
275 .priv_data_size
= sizeof(struct SAPState
),
276 .p
.audio_codec
= AV_CODEC_ID_AAC
,
277 .p
.video_codec
= AV_CODEC_ID_MPEG4
,
278 .write_header
= sap_write_header
,
279 .write_packet
= sap_write_packet
,
280 .write_trailer
= sap_write_close
,
281 .p
.flags
= AVFMT_NOFILE
| AVFMT_GLOBALHEADER
,