Commit | Line | Data |
---|---|---|
1617ad97 | 1 | /* |
93ced3e8 | 2 | * RTSP/SDP client |
1617ad97 FB |
3 | * Copyright (c) 2002 Fabrice Bellard. |
4 | * | |
b78e7197 DB |
5 | * This file is part of FFmpeg. |
6 | * | |
7 | * FFmpeg is free software; you can redistribute it and/or | |
1617ad97 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. |
1617ad97 | 11 | * |
b78e7197 | 12 | * FFmpeg is distributed in the hope that it will be useful, |
1617ad97 FB |
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 | |
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 |
1617ad97 | 20 | */ |
245976da | 21 | |
7246177d AJ |
22 | /* needed by inet_aton() */ |
23 | #define _SVID_SOURCE | |
24 | ||
245976da | 25 | #include "libavutil/avstring.h" |
1617ad97 FB |
26 | #include "avformat.h" |
27 | ||
db0ed93e | 28 | #include <sys/time.h> |
6ad1c9c9 | 29 | #ifdef HAVE_SYS_SELECT_H |
933bd8e2 | 30 | #include <sys/select.h> |
6ad1c9c9 | 31 | #endif |
ea452b54 | 32 | #include <strings.h> |
42572ef5 | 33 | #include "network.h" |
c971ff19 | 34 | #include "rtsp.h" |
1617ad97 | 35 | |
4934884a | 36 | #include "rtp_internal.h" |
e9dea59f | 37 | #include "rdt.h" |
4934884a | 38 | |
1617ad97 | 39 | //#define DEBUG |
b6892136 | 40 | //#define DEBUG_RTP_TCP |
1617ad97 | 41 | |
ff762d6e FB |
42 | enum RTSPClientState { |
43 | RTSP_STATE_IDLE, | |
44 | RTSP_STATE_PLAYING, | |
45 | RTSP_STATE_PAUSED, | |
46 | }; | |
47 | ||
e0776043 | 48 | enum RTSPServerType { |
2834c365 | 49 | RTSP_SERVER_RTP, /*< Standard-compliant RTP-server */ |
2e889ae4 | 50 | RTSP_SERVER_REAL, /*< Realmedia-style server */ |
e0776043 RB |
51 | RTSP_SERVER_LAST |
52 | }; | |
53 | ||
119b4668 RB |
54 | enum RTSPTransport { |
55 | RTSP_TRANSPORT_RTP, | |
56 | RTSP_TRANSPORT_RDT, | |
57 | RTSP_TRANSPORT_LAST | |
58 | }; | |
59 | ||
1617ad97 FB |
60 | typedef struct RTSPState { |
61 | URLContext *rtsp_hd; /* RTSP TCP connexion handle */ | |
8b1ab7bf FB |
62 | int nb_rtsp_streams; |
63 | struct RTSPStream **rtsp_streams; | |
115329f1 | 64 | |
ff762d6e FB |
65 | enum RTSPClientState state; |
66 | int64_t seek_timestamp; | |
115329f1 | 67 | |
b6892136 FB |
68 | /* XXX: currently we use unbuffered input */ |
69 | // ByteIOContext rtsp_gb; | |
1617ad97 FB |
70 | int seq; /* RTSP command sequence number */ |
71 | char session_id[512]; | |
119b4668 | 72 | enum RTSPTransport transport; |
90abbdba | 73 | enum RTSPLowerTransport lower_transport; |
e0776043 | 74 | enum RTSPServerType server_type; |
1617ad97 | 75 | char last_reply[2048]; /* XXX: allocate ? */ |
8b1ab7bf | 76 | RTPDemuxContext *cur_rtp; |
1256d16b | 77 | int need_subscription; |
1617ad97 FB |
78 | } RTSPState; |
79 | ||
80 | typedef struct RTSPStream { | |
8b1ab7bf FB |
81 | URLContext *rtp_handle; /* RTP stream handle */ |
82 | RTPDemuxContext *rtp_ctx; /* RTP parse context */ | |
115329f1 | 83 | |
8b1ab7bf | 84 | int stream_index; /* corresponding stream index, if any. -1 if none (MPEG2TS case) */ |
1617ad97 | 85 | int interleaved_min, interleaved_max; /* interleave ids, if TCP transport */ |
93ced3e8 FB |
86 | char control_url[1024]; /* url for this stream (from SDP) */ |
87 | ||
88 | int sdp_port; /* port (from SDP content - not used in RTSP) */ | |
89 | struct in_addr sdp_ip; /* IP address (from SDP content - not used in RTSP) */ | |
90 | int sdp_ttl; /* IP TTL (from SDP content - not used in RTSP) */ | |
91 | int sdp_payload_type; /* payload type - only used in SDP */ | |
d1ccf0e0 | 92 | rtp_payload_data_t rtp_payload_data; /* rtp payload parsing infos from SDP */ |
4934884a RM |
93 | |
94 | RTPDynamicProtocolHandler *dynamic_handler; ///< Only valid if it's a dynamic protocol. (This is the handler structure) | |
95 | void *dynamic_protocol_context; ///< Only valid if it's a dynamic protocol. (This is any private data associated with the dynamic protocol) | |
1617ad97 FB |
96 | } RTSPStream; |
97 | ||
ff762d6e FB |
98 | static int rtsp_read_play(AVFormatContext *s); |
99 | ||
1617ad97 FB |
100 | /* XXX: currently, the only way to change the protocols consists in |
101 | changing this variable */ | |
1617ad97 | 102 | |
c482500f | 103 | #if LIBAVFORMAT_VERSION_INT < (53 << 16) |
90abbdba | 104 | int rtsp_default_protocols = (1 << RTSP_LOWER_TRANSPORT_UDP); |
c482500f | 105 | #endif |
85fb7b34 | 106 | |
1617ad97 FB |
107 | static int rtsp_probe(AVProbeData *p) |
108 | { | |
f7d78f36 | 109 | if (av_strstart(p->filename, "rtsp:", NULL)) |
1617ad97 FB |
110 | return AVPROBE_SCORE_MAX; |
111 | return 0; | |
112 | } | |
113 | ||
114 | static int redir_isspace(int c) | |
115 | { | |
ccd425e7 | 116 | return c == ' ' || c == '\t' || c == '\n' || c == '\r'; |
1617ad97 FB |
117 | } |
118 | ||
119 | static void skip_spaces(const char **pp) | |
120 | { | |
121 | const char *p; | |
122 | p = *pp; | |
123 | while (redir_isspace(*p)) | |
124 | p++; | |
125 | *pp = p; | |
126 | } | |
127 | ||
115329f1 | 128 | static void get_word_sep(char *buf, int buf_size, const char *sep, |
1617ad97 FB |
129 | const char **pp) |
130 | { | |
131 | const char *p; | |
132 | char *q; | |
133 | ||
134 | p = *pp; | |
d1ccf0e0 RD |
135 | if (*p == '/') |
136 | p++; | |
1617ad97 FB |
137 | skip_spaces(&p); |
138 | q = buf; | |
139 | while (!strchr(sep, *p) && *p != '\0') { | |
140 | if ((q - buf) < buf_size - 1) | |
141 | *q++ = *p; | |
142 | p++; | |
143 | } | |
144 | if (buf_size > 0) | |
145 | *q = '\0'; | |
146 | *pp = p; | |
147 | } | |
148 | ||
149 | static void get_word(char *buf, int buf_size, const char **pp) | |
150 | { | |
151 | const char *p; | |
152 | char *q; | |
153 | ||
154 | p = *pp; | |
155 | skip_spaces(&p); | |
156 | q = buf; | |
157 | while (!redir_isspace(*p) && *p != '\0') { | |
158 | if ((q - buf) < buf_size - 1) | |
159 | *q++ = *p; | |
160 | p++; | |
161 | } | |
162 | if (buf_size > 0) | |
163 | *q = '\0'; | |
164 | *pp = p; | |
165 | } | |
166 | ||
93ced3e8 FB |
167 | /* parse the rtpmap description: <codec_name>/<clock_rate>[/<other |
168 | params>] */ | |
4934884a | 169 | static int sdp_parse_rtpmap(AVCodecContext *codec, RTSPStream *rtsp_st, int payload_type, const char *p) |
93ced3e8 FB |
170 | { |
171 | char buf[256]; | |
d1ccf0e0 RD |
172 | int i; |
173 | AVCodec *c; | |
7b49ce2e | 174 | const char *c_name; |
93ced3e8 | 175 | |
d1ccf0e0 RD |
176 | /* Loop into AVRtpDynamicPayloadTypes[] and AVRtpPayloadTypes[] and |
177 | see if we can handle this kind of payload */ | |
93ced3e8 | 178 | get_word_sep(buf, sizeof(buf), "/", &p); |
d1ccf0e0 | 179 | if (payload_type >= RTP_PT_PRIVATE) { |
4934884a RM |
180 | RTPDynamicProtocolHandler *handler= RTPFirstDynamicPayloadHandler; |
181 | while(handler) { | |
182 | if (!strcmp(buf, handler->enc_name) && (codec->codec_type == handler->codec_type)) { | |
183 | codec->codec_id = handler->codec_id; | |
184 | rtsp_st->dynamic_handler= handler; | |
185 | if(handler->open) { | |
186 | rtsp_st->dynamic_protocol_context= handler->open(); | |
187 | } | |
d1ccf0e0 RD |
188 | break; |
189 | } | |
4934884a RM |
190 | handler= handler->next; |
191 | } | |
93ced3e8 | 192 | } else { |
d1ccf0e0 RD |
193 | /* We are in a standard case ( from http://www.iana.org/assignments/rtp-parameters) */ |
194 | /* search into AVRtpPayloadTypes[] */ | |
7ed19d7f | 195 | codec->codec_id = ff_rtp_codec_id(buf, codec->codec_type); |
d1ccf0e0 RD |
196 | } |
197 | ||
198 | c = avcodec_find_decoder(codec->codec_id); | |
199 | if (c && c->name) | |
7b49ce2e | 200 | c_name = c->name; |
d1ccf0e0 RD |
201 | else |
202 | c_name = (char *)NULL; | |
203 | ||
204 | if (c_name) { | |
205 | get_word_sep(buf, sizeof(buf), "/", &p); | |
206 | i = atoi(buf); | |
207 | switch (codec->codec_type) { | |
208 | case CODEC_TYPE_AUDIO: | |
209 | av_log(codec, AV_LOG_DEBUG, " audio codec set to : %s\n", c_name); | |
210 | codec->sample_rate = RTSP_DEFAULT_AUDIO_SAMPLERATE; | |
211 | codec->channels = RTSP_DEFAULT_NB_AUDIO_CHANNELS; | |
212 | if (i > 0) { | |
213 | codec->sample_rate = i; | |
214 | get_word_sep(buf, sizeof(buf), "/", &p); | |
215 | i = atoi(buf); | |
216 | if (i > 0) | |
217 | codec->channels = i; | |
d0deedcb RM |
218 | // TODO: there is a bug here; if it is a mono stream, and less than 22000Hz, faad upconverts to stereo and twice the |
219 | // frequency. No problem, but the sample rate is being set here by the sdp line. Upcoming patch forthcoming. (rdm) | |
d1ccf0e0 RD |
220 | } |
221 | av_log(codec, AV_LOG_DEBUG, " audio samplerate set to : %i\n", codec->sample_rate); | |
222 | av_log(codec, AV_LOG_DEBUG, " audio channels set to : %i\n", codec->channels); | |
223 | break; | |
224 | case CODEC_TYPE_VIDEO: | |
225 | av_log(codec, AV_LOG_DEBUG, " video codec set to : %s\n", c_name); | |
226 | break; | |
227 | default: | |
228 | break; | |
229 | } | |
230 | return 0; | |
93ced3e8 | 231 | } |
d1ccf0e0 RD |
232 | |
233 | return -1; | |
93ced3e8 FB |
234 | } |
235 | ||
236 | /* return the length and optionnaly the data */ | |
237 | static int hex_to_data(uint8_t *data, const char *p) | |
238 | { | |
239 | int c, len, v; | |
240 | ||
241 | len = 0; | |
242 | v = 1; | |
243 | for(;;) { | |
244 | skip_spaces(&p); | |
245 | if (p == '\0') | |
246 | break; | |
247 | c = toupper((unsigned char)*p++); | |
248 | if (c >= '0' && c <= '9') | |
249 | c = c - '0'; | |
250 | else if (c >= 'A' && c <= 'F') | |
251 | c = c - 'A' + 10; | |
252 | else | |
253 | break; | |
254 | v = (v << 4) | c; | |
255 | if (v & 0x100) { | |
256 | if (data) | |
257 | data[len] = v; | |
258 | len++; | |
259 | v = 1; | |
260 | } | |
261 | } | |
262 | return len; | |
263 | } | |
264 | ||
d1ccf0e0 RD |
265 | static void sdp_parse_fmtp_config(AVCodecContext *codec, char *attr, char *value) |
266 | { | |
267 | switch (codec->codec_id) { | |
268 | case CODEC_ID_MPEG4: | |
cbee7a69 | 269 | case CODEC_ID_AAC: |
d1ccf0e0 RD |
270 | if (!strcmp(attr, "config")) { |
271 | /* decode the hexa encoded parameter */ | |
272 | int len = hex_to_data(NULL, value); | |
273 | codec->extradata = av_mallocz(len + FF_INPUT_BUFFER_PADDING_SIZE); | |
274 | if (!codec->extradata) | |
275 | return; | |
276 | codec->extradata_size = len; | |
277 | hex_to_data(codec->extradata, value); | |
278 | } | |
279 | break; | |
280 | default: | |
281 | break; | |
282 | } | |
283 | return; | |
284 | } | |
285 | ||
286 | typedef struct attrname_map | |
287 | { | |
7b49ce2e | 288 | const char *str; |
d1ccf0e0 RD |
289 | uint16_t type; |
290 | uint32_t offset; | |
291 | } attrname_map_t; | |
292 | ||
293 | /* All known fmtp parmeters and the corresping RTPAttrTypeEnum */ | |
294 | #define ATTR_NAME_TYPE_INT 0 | |
295 | #define ATTR_NAME_TYPE_STR 1 | |
296 | static attrname_map_t attr_names[]= | |
297 | { | |
298 | {"SizeLength", ATTR_NAME_TYPE_INT, offsetof(rtp_payload_data_t, sizelength)}, | |
299 | {"IndexLength", ATTR_NAME_TYPE_INT, offsetof(rtp_payload_data_t, indexlength)}, | |
300 | {"IndexDeltaLength", ATTR_NAME_TYPE_INT, offsetof(rtp_payload_data_t, indexdeltalength)}, | |
301 | {"profile-level-id", ATTR_NAME_TYPE_INT, offsetof(rtp_payload_data_t, profile_level_id)}, | |
302 | {"StreamType", ATTR_NAME_TYPE_INT, offsetof(rtp_payload_data_t, streamtype)}, | |
303 | {"mode", ATTR_NAME_TYPE_STR, offsetof(rtp_payload_data_t, mode)}, | |
304 | {NULL, -1, -1}, | |
305 | }; | |
306 | ||
d0deedcb RM |
307 | /** parse the attribute line from the fmtp a line of an sdp resonse. This is broken out as a function |
308 | * because it is used in rtp_h264.c, which is forthcoming. | |
309 | */ | |
310 | int rtsp_next_attr_and_value(const char **p, char *attr, int attr_size, char *value, int value_size) | |
311 | { | |
312 | skip_spaces(p); | |
313 | if(**p) | |
314 | { | |
315 | get_word_sep(attr, attr_size, "=", p); | |
316 | if (**p == '=') | |
317 | (*p)++; | |
318 | get_word_sep(value, value_size, ";", p); | |
319 | if (**p == ';') | |
320 | (*p)++; | |
321 | return 1; | |
322 | } | |
323 | return 0; | |
324 | } | |
325 | ||
d1ccf0e0 RD |
326 | /* parse a SDP line and save stream attributes */ |
327 | static void sdp_parse_fmtp(AVStream *st, const char *p) | |
93ced3e8 FB |
328 | { |
329 | char attr[256]; | |
330 | char value[4096]; | |
d1ccf0e0 RD |
331 | int i; |
332 | ||
333 | RTSPStream *rtsp_st = st->priv_data; | |
01f4895c | 334 | AVCodecContext *codec = st->codec; |
d1ccf0e0 | 335 | rtp_payload_data_t *rtp_payload_data = &rtsp_st->rtp_payload_data; |
93ced3e8 FB |
336 | |
337 | /* loop on each attribute */ | |
1ad20f96 RM |
338 | while(rtsp_next_attr_and_value(&p, attr, sizeof(attr), value, sizeof(value))) |
339 | { | |
bb270c08 | 340 | /* grab the codec extra_data from the config parameter of the fmtp line */ |
d1ccf0e0 RD |
341 | sdp_parse_fmtp_config(codec, attr, value); |
342 | /* Looking for a known attribute */ | |
343 | for (i = 0; attr_names[i].str; ++i) { | |
344 | if (!strcasecmp(attr, attr_names[i].str)) { | |
345 | if (attr_names[i].type == ATTR_NAME_TYPE_INT) | |
346 | *(int *)((char *)rtp_payload_data + attr_names[i].offset) = atoi(value); | |
347 | else if (attr_names[i].type == ATTR_NAME_TYPE_STR) | |
348 | *(char **)((char *)rtp_payload_data + attr_names[i].offset) = av_strdup(value); | |
bb270c08 | 349 | } |
93ced3e8 | 350 | } |
93ced3e8 FB |
351 | } |
352 | } | |
353 | ||
31693e00 RM |
354 | /** Parse a string \p in the form of Range:npt=xx-xx, and determine the start |
355 | * and end time. | |
356 | * Used for seeking in the rtp stream. | |
357 | */ | |
358 | static void rtsp_parse_range_npt(const char *p, int64_t *start, int64_t *end) | |
359 | { | |
360 | char buf[256]; | |
361 | ||
362 | skip_spaces(&p); | |
f7d78f36 | 363 | if (!av_stristart(p, "npt=", &p)) |
31693e00 RM |
364 | return; |
365 | ||
366 | *start = AV_NOPTS_VALUE; | |
367 | *end = AV_NOPTS_VALUE; | |
368 | ||
369 | get_word_sep(buf, sizeof(buf), "-", &p); | |
370 | *start = parse_date(buf, 1); | |
371 | if (*p == '-') { | |
372 | p++; | |
373 | get_word_sep(buf, sizeof(buf), "-", &p); | |
374 | *end = parse_date(buf, 1); | |
375 | } | |
376 | // av_log(NULL, AV_LOG_DEBUG, "Range Start: %lld\n", *start); | |
377 | // av_log(NULL, AV_LOG_DEBUG, "Range End: %lld\n", *end); | |
378 | } | |
379 | ||
93ced3e8 FB |
380 | typedef struct SDPParseState { |
381 | /* SDP only */ | |
382 | struct in_addr default_ip; | |
383 | int default_ttl; | |
384 | } SDPParseState; | |
385 | ||
386 | static void sdp_parse_line(AVFormatContext *s, SDPParseState *s1, | |
1617ad97 FB |
387 | int letter, const char *buf) |
388 | { | |
8b1ab7bf | 389 | RTSPState *rt = s->priv_data; |
1617ad97 FB |
390 | char buf1[64], st_type[64]; |
391 | const char *p; | |
93ced3e8 | 392 | int codec_type, payload_type, i; |
1617ad97 FB |
393 | AVStream *st; |
394 | RTSPStream *rtsp_st; | |
93ced3e8 FB |
395 | struct in_addr sdp_ip; |
396 | int ttl; | |
397 | ||
1617ad97 FB |
398 | #ifdef DEBUG |
399 | printf("sdp: %c='%s'\n", letter, buf); | |
400 | #endif | |
401 | ||
402 | p = buf; | |
403 | switch(letter) { | |
93ced3e8 FB |
404 | case 'c': |
405 | get_word(buf1, sizeof(buf1), &p); | |
406 | if (strcmp(buf1, "IN") != 0) | |
407 | return; | |
408 | get_word(buf1, sizeof(buf1), &p); | |
409 | if (strcmp(buf1, "IP4") != 0) | |
410 | return; | |
411 | get_word_sep(buf1, sizeof(buf1), "/", &p); | |
412 | if (inet_aton(buf1, &sdp_ip) == 0) | |
413 | return; | |
414 | ttl = 16; | |
415 | if (*p == '/') { | |
416 | p++; | |
417 | get_word_sep(buf1, sizeof(buf1), "/", &p); | |
418 | ttl = atoi(buf1); | |
419 | } | |
420 | if (s->nb_streams == 0) { | |
421 | s1->default_ip = sdp_ip; | |
422 | s1->default_ttl = ttl; | |
423 | } else { | |
424 | st = s->streams[s->nb_streams - 1]; | |
425 | rtsp_st = st->priv_data; | |
426 | rtsp_st->sdp_ip = sdp_ip; | |
427 | rtsp_st->sdp_ttl = ttl; | |
428 | } | |
429 | break; | |
1617ad97 | 430 | case 's': |
75e61b0e | 431 | av_strlcpy(s->title, p, sizeof(s->title)); |
1617ad97 FB |
432 | break; |
433 | case 'i': | |
434 | if (s->nb_streams == 0) { | |
75e61b0e | 435 | av_strlcpy(s->comment, p, sizeof(s->comment)); |
1617ad97 FB |
436 | break; |
437 | } | |
438 | break; | |
439 | case 'm': | |
440 | /* new stream */ | |
441 | get_word(st_type, sizeof(st_type), &p); | |
442 | if (!strcmp(st_type, "audio")) { | |
443 | codec_type = CODEC_TYPE_AUDIO; | |
444 | } else if (!strcmp(st_type, "video")) { | |
445 | codec_type = CODEC_TYPE_VIDEO; | |
446 | } else { | |
447 | return; | |
448 | } | |
1617ad97 FB |
449 | rtsp_st = av_mallocz(sizeof(RTSPStream)); |
450 | if (!rtsp_st) | |
451 | return; | |
8b1ab7bf FB |
452 | rtsp_st->stream_index = -1; |
453 | dynarray_add(&rt->rtsp_streams, &rt->nb_rtsp_streams, rtsp_st); | |
93ced3e8 FB |
454 | |
455 | rtsp_st->sdp_ip = s1->default_ip; | |
456 | rtsp_st->sdp_ttl = s1->default_ttl; | |
457 | ||
93ced3e8 FB |
458 | get_word(buf1, sizeof(buf1), &p); /* port */ |
459 | rtsp_st->sdp_port = atoi(buf1); | |
460 | ||
461 | get_word(buf1, sizeof(buf1), &p); /* protocol (ignored) */ | |
115329f1 | 462 | |
93ced3e8 FB |
463 | /* XXX: handle list of formats */ |
464 | get_word(buf1, sizeof(buf1), &p); /* format list */ | |
465 | rtsp_st->sdp_payload_type = atoi(buf1); | |
93ced3e8 | 466 | |
7ed19d7f | 467 | if (!strcmp(ff_rtp_enc_name(rtsp_st->sdp_payload_type), "MP2T")) { |
8b1ab7bf FB |
468 | /* no corresponding stream */ |
469 | } else { | |
470 | st = av_new_stream(s, 0); | |
471 | if (!st) | |
472 | return; | |
473 | st->priv_data = rtsp_st; | |
474 | rtsp_st->stream_index = st->index; | |
01f4895c | 475 | st->codec->codec_type = codec_type; |
d1ccf0e0 | 476 | if (rtsp_st->sdp_payload_type < RTP_PT_PRIVATE) { |
8b1ab7bf | 477 | /* if standard payload type, we can find the codec right now */ |
01f4895c | 478 | rtp_get_codec_info(st->codec, rtsp_st->sdp_payload_type); |
8b1ab7bf FB |
479 | } |
480 | } | |
1617ad97 | 481 | /* put a default control url */ |
75e61b0e | 482 | av_strlcpy(rtsp_st->control_url, s->filename, sizeof(rtsp_st->control_url)); |
1617ad97 FB |
483 | break; |
484 | case 'a': | |
f7d78f36 | 485 | if (av_strstart(p, "control:", &p) && s->nb_streams > 0) { |
1617ad97 FB |
486 | char proto[32]; |
487 | /* get the control url */ | |
488 | st = s->streams[s->nb_streams - 1]; | |
489 | rtsp_st = st->priv_data; | |
115329f1 | 490 | |
1617ad97 | 491 | /* XXX: may need to add full url resolution */ |
6ba5cbc6 | 492 | url_split(proto, sizeof(proto), NULL, 0, NULL, 0, NULL, NULL, 0, p); |
1617ad97 FB |
493 | if (proto[0] == '\0') { |
494 | /* relative control URL */ | |
75e61b0e MR |
495 | av_strlcat(rtsp_st->control_url, "/", sizeof(rtsp_st->control_url)); |
496 | av_strlcat(rtsp_st->control_url, p, sizeof(rtsp_st->control_url)); | |
1617ad97 | 497 | } else { |
75e61b0e | 498 | av_strlcpy(rtsp_st->control_url, p, sizeof(rtsp_st->control_url)); |
1617ad97 | 499 | } |
f7d78f36 | 500 | } else if (av_strstart(p, "rtpmap:", &p)) { |
93ced3e8 | 501 | /* NOTE: rtpmap is only supported AFTER the 'm=' tag */ |
115329f1 | 502 | get_word(buf1, sizeof(buf1), &p); |
93ced3e8 FB |
503 | payload_type = atoi(buf1); |
504 | for(i = 0; i < s->nb_streams;i++) { | |
505 | st = s->streams[i]; | |
506 | rtsp_st = st->priv_data; | |
507 | if (rtsp_st->sdp_payload_type == payload_type) { | |
4934884a | 508 | sdp_parse_rtpmap(st->codec, rtsp_st, payload_type, p); |
93ced3e8 FB |
509 | } |
510 | } | |
f7d78f36 | 511 | } else if (av_strstart(p, "fmtp:", &p)) { |
93ced3e8 | 512 | /* NOTE: fmtp is only supported AFTER the 'a=rtpmap:xxx' tag */ |
115329f1 | 513 | get_word(buf1, sizeof(buf1), &p); |
93ced3e8 FB |
514 | payload_type = atoi(buf1); |
515 | for(i = 0; i < s->nb_streams;i++) { | |
516 | st = s->streams[i]; | |
517 | rtsp_st = st->priv_data; | |
518 | if (rtsp_st->sdp_payload_type == payload_type) { | |
4934884a RM |
519 | if(rtsp_st->dynamic_handler && rtsp_st->dynamic_handler->parse_sdp_a_line) { |
520 | if(!rtsp_st->dynamic_handler->parse_sdp_a_line(st, rtsp_st->dynamic_protocol_context, buf)) { | |
521 | sdp_parse_fmtp(st, p); | |
522 | } | |
523 | } else { | |
ed787542 | 524 | sdp_parse_fmtp(st, p); |
4934884a | 525 | } |
93ced3e8 FB |
526 | } |
527 | } | |
f7d78f36 | 528 | } else if(av_strstart(p, "framesize:", &p)) { |
d0deedcb RM |
529 | // let dynamic protocol handlers have a stab at the line. |
530 | get_word(buf1, sizeof(buf1), &p); | |
531 | payload_type = atoi(buf1); | |
532 | for(i = 0; i < s->nb_streams;i++) { | |
533 | st = s->streams[i]; | |
534 | rtsp_st = st->priv_data; | |
535 | if (rtsp_st->sdp_payload_type == payload_type) { | |
536 | if(rtsp_st->dynamic_handler && rtsp_st->dynamic_handler->parse_sdp_a_line) { | |
537 | rtsp_st->dynamic_handler->parse_sdp_a_line(st, rtsp_st->dynamic_protocol_context, buf); | |
538 | } | |
539 | } | |
540 | } | |
f7d78f36 | 541 | } else if(av_strstart(p, "range:", &p)) { |
31693e00 RM |
542 | int64_t start, end; |
543 | ||
544 | // this is so that seeking on a streamed file can work. | |
545 | rtsp_parse_range_npt(p, &start, &end); | |
546 | s->start_time= start; | |
547 | s->duration= (end==AV_NOPTS_VALUE)?AV_NOPTS_VALUE:end-start; // AV_NOPTS_VALUE means live broadcast (and can't seek) | |
119b4668 RB |
548 | } else if (av_strstart(p, "IsRealDataType:integer;",&p)) { |
549 | if (atoi(p) == 1) | |
550 | rt->transport = RTSP_TRANSPORT_RDT; | |
8646b907 RB |
551 | } else if (s->nb_streams > 0) { |
552 | rtsp_st = s->streams[s->nb_streams - 1]->priv_data; | |
553 | if (rtsp_st->dynamic_handler && | |
554 | rtsp_st->dynamic_handler->parse_sdp_a_line) | |
555 | rtsp_st->dynamic_handler->parse_sdp_a_line(s->streams[s->nb_streams - 1], | |
556 | rtsp_st->dynamic_protocol_context, buf); | |
1617ad97 FB |
557 | } |
558 | break; | |
559 | } | |
560 | } | |
561 | ||
5c91a675 | 562 | static int sdp_parse(AVFormatContext *s, const char *content) |
1617ad97 FB |
563 | { |
564 | const char *p; | |
565 | int letter; | |
d6bb9ebd | 566 | char buf[2048], *q; |
93ced3e8 | 567 | SDPParseState sdp_parse_state, *s1 = &sdp_parse_state; |
115329f1 | 568 | |
93ced3e8 | 569 | memset(s1, 0, sizeof(SDPParseState)); |
1617ad97 FB |
570 | p = content; |
571 | for(;;) { | |
572 | skip_spaces(&p); | |
573 | letter = *p; | |
574 | if (letter == '\0') | |
575 | break; | |
576 | p++; | |
577 | if (*p != '=') | |
578 | goto next_line; | |
579 | p++; | |
580 | /* get the content */ | |
581 | q = buf; | |
b6892136 | 582 | while (*p != '\n' && *p != '\r' && *p != '\0') { |
1617ad97 FB |
583 | if ((q - buf) < sizeof(buf) - 1) |
584 | *q++ = *p; | |
585 | p++; | |
586 | } | |
587 | *q = '\0'; | |
93ced3e8 | 588 | sdp_parse_line(s, s1, letter, buf); |
1617ad97 FB |
589 | next_line: |
590 | while (*p != '\n' && *p != '\0') | |
591 | p++; | |
592 | if (*p == '\n') | |
593 | p++; | |
594 | } | |
595 | return 0; | |
596 | } | |
597 | ||
598 | static void rtsp_parse_range(int *min_ptr, int *max_ptr, const char **pp) | |
599 | { | |
600 | const char *p; | |
601 | int v; | |
602 | ||
603 | p = *pp; | |
604 | skip_spaces(&p); | |
605 | v = strtol(p, (char **)&p, 10); | |
606 | if (*p == '-') { | |
607 | p++; | |
608 | *min_ptr = v; | |
609 | v = strtol(p, (char **)&p, 10); | |
610 | *max_ptr = v; | |
611 | } else { | |
612 | *min_ptr = v; | |
613 | *max_ptr = v; | |
614 | } | |
615 | *pp = p; | |
616 | } | |
617 | ||
618 | /* XXX: only one transport specification is parsed */ | |
619 | static void rtsp_parse_transport(RTSPHeader *reply, const char *p) | |
620 | { | |
621 | char transport_protocol[16]; | |
622 | char profile[16]; | |
623 | char lower_transport[16]; | |
624 | char parameter[16]; | |
625 | RTSPTransportField *th; | |
626 | char buf[256]; | |
115329f1 | 627 | |
1617ad97 | 628 | reply->nb_transports = 0; |
115329f1 | 629 | |
1617ad97 FB |
630 | for(;;) { |
631 | skip_spaces(&p); | |
632 | if (*p == '\0') | |
633 | break; | |
634 | ||
635 | th = &reply->transports[reply->nb_transports]; | |
636 | ||
115329f1 | 637 | get_word_sep(transport_protocol, sizeof(transport_protocol), |
1617ad97 FB |
638 | "/", &p); |
639 | if (*p == '/') | |
640 | p++; | |
e1502118 | 641 | if (!strcasecmp (transport_protocol, "rtp")) { |
7ecc634e LB |
642 | get_word_sep(profile, sizeof(profile), "/;,", &p); |
643 | lower_transport[0] = '\0'; | |
644 | /* rtp/avp/<protocol> */ | |
645 | if (*p == '/') { | |
646 | p++; | |
647 | get_word_sep(lower_transport, sizeof(lower_transport), | |
648 | ";,", &p); | |
119b4668 RB |
649 | } |
650 | th->transport = RTSP_TRANSPORT_RTP; | |
651 | } else if (!strcasecmp (transport_protocol, "x-pn-tng") || | |
652 | !strcasecmp (transport_protocol, "x-real-rdt")) { | |
7ecc634e | 653 | /* x-pn-tng/<protocol> */ |
e1502118 LB |
654 | get_word_sep(lower_transport, sizeof(lower_transport), "/;,", &p); |
655 | profile[0] = '\0'; | |
119b4668 | 656 | th->transport = RTSP_TRANSPORT_RDT; |
1617ad97 | 657 | } |
b6892136 | 658 | if (!strcasecmp(lower_transport, "TCP")) |
90abbdba | 659 | th->lower_transport = RTSP_LOWER_TRANSPORT_TCP; |
1617ad97 | 660 | else |
90abbdba | 661 | th->lower_transport = RTSP_LOWER_TRANSPORT_UDP; |
115329f1 | 662 | |
1617ad97 FB |
663 | if (*p == ';') |
664 | p++; | |
665 | /* get each parameter */ | |
666 | while (*p != '\0' && *p != ',') { | |
667 | get_word_sep(parameter, sizeof(parameter), "=;,", &p); | |
668 | if (!strcmp(parameter, "port")) { | |
669 | if (*p == '=') { | |
670 | p++; | |
671 | rtsp_parse_range(&th->port_min, &th->port_max, &p); | |
672 | } | |
673 | } else if (!strcmp(parameter, "client_port")) { | |
674 | if (*p == '=') { | |
675 | p++; | |
115329f1 | 676 | rtsp_parse_range(&th->client_port_min, |
1617ad97 FB |
677 | &th->client_port_max, &p); |
678 | } | |
679 | } else if (!strcmp(parameter, "server_port")) { | |
680 | if (*p == '=') { | |
681 | p++; | |
115329f1 | 682 | rtsp_parse_range(&th->server_port_min, |
1617ad97 FB |
683 | &th->server_port_max, &p); |
684 | } | |
685 | } else if (!strcmp(parameter, "interleaved")) { | |
686 | if (*p == '=') { | |
687 | p++; | |
115329f1 | 688 | rtsp_parse_range(&th->interleaved_min, |
1617ad97 FB |
689 | &th->interleaved_max, &p); |
690 | } | |
691 | } else if (!strcmp(parameter, "multicast")) { | |
90abbdba RB |
692 | if (th->lower_transport == RTSP_LOWER_TRANSPORT_UDP) |
693 | th->lower_transport = RTSP_LOWER_TRANSPORT_UDP_MULTICAST; | |
1617ad97 FB |
694 | } else if (!strcmp(parameter, "ttl")) { |
695 | if (*p == '=') { | |
696 | p++; | |
697 | th->ttl = strtol(p, (char **)&p, 10); | |
698 | } | |
699 | } else if (!strcmp(parameter, "destination")) { | |
700 | struct in_addr ipaddr; | |
701 | ||
702 | if (*p == '=') { | |
703 | p++; | |
704 | get_word_sep(buf, sizeof(buf), ";,", &p); | |
115329f1 | 705 | if (inet_aton(buf, &ipaddr)) |
1617ad97 FB |
706 | th->destination = ntohl(ipaddr.s_addr); |
707 | } | |
708 | } | |
709 | while (*p != ';' && *p != '\0' && *p != ',') | |
710 | p++; | |
711 | if (*p == ';') | |
712 | p++; | |
713 | } | |
714 | if (*p == ',') | |
715 | p++; | |
716 | ||
717 | reply->nb_transports++; | |
718 | } | |
719 | } | |
720 | ||
721 | void rtsp_parse_line(RTSPHeader *reply, const char *buf) | |
722 | { | |
723 | const char *p; | |
724 | ||
725 | /* NOTE: we do case independent match for broken servers */ | |
726 | p = buf; | |
f7d78f36 | 727 | if (av_stristart(p, "Session:", &p)) { |
1617ad97 | 728 | get_word_sep(reply->session_id, sizeof(reply->session_id), ";", &p); |
f7d78f36 | 729 | } else if (av_stristart(p, "Content-Length:", &p)) { |
1617ad97 | 730 | reply->content_length = strtol(p, NULL, 10); |
f7d78f36 | 731 | } else if (av_stristart(p, "Transport:", &p)) { |
1617ad97 | 732 | rtsp_parse_transport(reply, p); |
f7d78f36 | 733 | } else if (av_stristart(p, "CSeq:", &p)) { |
1617ad97 | 734 | reply->seq = strtol(p, NULL, 10); |
f7d78f36 | 735 | } else if (av_stristart(p, "Range:", &p)) { |
31693e00 | 736 | rtsp_parse_range_npt(p, &reply->range_start, &reply->range_end); |
30aa6aed RB |
737 | } else if (av_stristart(p, "RealChallenge1:", &p)) { |
738 | skip_spaces(&p); | |
739 | av_strlcpy(reply->real_challenge, p, sizeof(reply->real_challenge)); | |
1617ad97 FB |
740 | } |
741 | } | |
742 | ||
2a42b5c3 LS |
743 | static int url_readbuf(URLContext *h, unsigned char *buf, int size) |
744 | { | |
745 | int ret, len; | |
746 | ||
747 | len = 0; | |
748 | while (len < size) { | |
749 | ret = url_read(h, buf+len, size-len); | |
750 | if (ret < 1) | |
751 | return ret; | |
752 | len += ret; | |
753 | } | |
754 | return len; | |
755 | } | |
756 | ||
b7b8fc34 FB |
757 | /* skip a RTP/TCP interleaved packet */ |
758 | static void rtsp_skip_packet(AVFormatContext *s) | |
759 | { | |
760 | RTSPState *rt = s->priv_data; | |
761 | int ret, len, len1; | |
762 | uint8_t buf[1024]; | |
763 | ||
2a42b5c3 | 764 | ret = url_readbuf(rt->rtsp_hd, buf, 3); |
b7b8fc34 FB |
765 | if (ret != 3) |
766 | return; | |
80fb8234 | 767 | len = AV_RB16(buf + 1); |
b7b8fc34 FB |
768 | #ifdef DEBUG |
769 | printf("skipping RTP packet len=%d\n", len); | |
770 | #endif | |
771 | /* skip payload */ | |
772 | while (len > 0) { | |
773 | len1 = len; | |
774 | if (len1 > sizeof(buf)) | |
775 | len1 = sizeof(buf); | |
2a42b5c3 | 776 | ret = url_readbuf(rt->rtsp_hd, buf, len1); |
b7b8fc34 FB |
777 | if (ret != len1) |
778 | return; | |
779 | len -= len1; | |
780 | } | |
781 | } | |
1617ad97 | 782 | |
115329f1 DB |
783 | static void rtsp_send_cmd(AVFormatContext *s, |
784 | const char *cmd, RTSPHeader *reply, | |
1617ad97 FB |
785 | unsigned char **content_ptr) |
786 | { | |
787 | RTSPState *rt = s->priv_data; | |
788 | char buf[4096], buf1[1024], *q; | |
789 | unsigned char ch; | |
790 | const char *p; | |
791 | int content_length, line_count; | |
792 | unsigned char *content = NULL; | |
793 | ||
794 | memset(reply, 0, sizeof(RTSPHeader)); | |
795 | ||
796 | rt->seq++; | |
75e61b0e | 797 | av_strlcpy(buf, cmd, sizeof(buf)); |
b6892136 | 798 | snprintf(buf1, sizeof(buf1), "CSeq: %d\r\n", rt->seq); |
75e61b0e | 799 | av_strlcat(buf, buf1, sizeof(buf)); |
1617ad97 | 800 | if (rt->session_id[0] != '\0' && !strstr(cmd, "\nIf-Match:")) { |
b6892136 | 801 | snprintf(buf1, sizeof(buf1), "Session: %s\r\n", rt->session_id); |
75e61b0e | 802 | av_strlcat(buf, buf1, sizeof(buf)); |
1617ad97 | 803 | } |
75e61b0e | 804 | av_strlcat(buf, "\r\n", sizeof(buf)); |
1617ad97 FB |
805 | #ifdef DEBUG |
806 | printf("Sending:\n%s--\n", buf); | |
807 | #endif | |
808 | url_write(rt->rtsp_hd, buf, strlen(buf)); | |
809 | ||
810 | /* parse reply (XXX: use buffers) */ | |
811 | line_count = 0; | |
812 | rt->last_reply[0] = '\0'; | |
813 | for(;;) { | |
814 | q = buf; | |
815 | for(;;) { | |
2a42b5c3 | 816 | if (url_readbuf(rt->rtsp_hd, &ch, 1) != 1) |
1617ad97 FB |
817 | break; |
818 | if (ch == '\n') | |
819 | break; | |
b7b8fc34 FB |
820 | if (ch == '$') { |
821 | /* XXX: only parse it if first char on line ? */ | |
822 | rtsp_skip_packet(s); | |
823 | } else if (ch != '\r') { | |
1617ad97 FB |
824 | if ((q - buf) < sizeof(buf) - 1) |
825 | *q++ = ch; | |
826 | } | |
827 | } | |
828 | *q = '\0'; | |
829 | #ifdef DEBUG | |
830 | printf("line='%s'\n", buf); | |
831 | #endif | |
832 | /* test if last line */ | |
833 | if (buf[0] == '\0') | |
834 | break; | |
835 | p = buf; | |
836 | if (line_count == 0) { | |
837 | /* get reply code */ | |
838 | get_word(buf1, sizeof(buf1), &p); | |
839 | get_word(buf1, sizeof(buf1), &p); | |
840 | reply->status_code = atoi(buf1); | |
841 | } else { | |
842 | rtsp_parse_line(reply, p); | |
75e61b0e MR |
843 | av_strlcat(rt->last_reply, p, sizeof(rt->last_reply)); |
844 | av_strlcat(rt->last_reply, "\n", sizeof(rt->last_reply)); | |
1617ad97 FB |
845 | } |
846 | line_count++; | |
847 | } | |
115329f1 | 848 | |
1617ad97 | 849 | if (rt->session_id[0] == '\0' && reply->session_id[0] != '\0') |
75e61b0e | 850 | av_strlcpy(rt->session_id, reply->session_id, sizeof(rt->session_id)); |
115329f1 | 851 | |
1617ad97 FB |
852 | content_length = reply->content_length; |
853 | if (content_length > 0) { | |
854 | /* leave some room for a trailing '\0' (useful for simple parsing) */ | |
855 | content = av_malloc(content_length + 1); | |
2a42b5c3 | 856 | (void)url_readbuf(rt->rtsp_hd, content, content_length); |
1617ad97 FB |
857 | content[content_length] = '\0'; |
858 | } | |
859 | if (content_ptr) | |
860 | *content_ptr = content; | |
e2e2e7dd AB |
861 | else |
862 | av_free(content); | |
1617ad97 FB |
863 | } |
864 | ||
1617ad97 | 865 | |
8b1ab7bf FB |
866 | /* close and free RTSP streams */ |
867 | static void rtsp_close_streams(RTSPState *rt) | |
868 | { | |
869 | int i; | |
870 | RTSPStream *rtsp_st; | |
871 | ||
872 | for(i=0;i<rt->nb_rtsp_streams;i++) { | |
873 | rtsp_st = rt->rtsp_streams[i]; | |
874 | if (rtsp_st) { | |
875 | if (rtsp_st->rtp_ctx) | |
876 | rtp_parse_close(rtsp_st->rtp_ctx); | |
877 | if (rtsp_st->rtp_handle) | |
878 | url_close(rtsp_st->rtp_handle); | |
4934884a RM |
879 | if (rtsp_st->dynamic_handler && rtsp_st->dynamic_protocol_context) |
880 | rtsp_st->dynamic_handler->close(rtsp_st->dynamic_protocol_context); | |
8b1ab7bf | 881 | } |
8b1ab7bf FB |
882 | } |
883 | av_free(rt->rtsp_streams); | |
884 | } | |
885 | ||
ee0cb67f RB |
886 | static int |
887 | rtsp_open_transport_ctx(AVFormatContext *s, RTSPStream *rtsp_st) | |
888 | { | |
889 | AVStream *st = NULL; | |
890 | ||
a6789dca RB |
891 | /* open the RTP context */ |
892 | if (rtsp_st->stream_index >= 0) | |
893 | st = s->streams[rtsp_st->stream_index]; | |
894 | if (!st) | |
895 | s->ctx_flags |= AVFMTCTX_NOHEADER; | |
896 | rtsp_st->rtp_ctx = rtp_parse_open(s, st, rtsp_st->rtp_handle, rtsp_st->sdp_payload_type, &rtsp_st->rtp_payload_data); | |
897 | ||
898 | if (!rtsp_st->rtp_ctx) { | |
899 | return AVERROR(ENOMEM); | |
900 | } else { | |
901 | if(rtsp_st->dynamic_handler) { | |
902 | rtsp_st->rtp_ctx->dynamic_protocol_context= rtsp_st->dynamic_protocol_context; | |
903 | rtsp_st->rtp_ctx->parse_packet= rtsp_st->dynamic_handler->parse_packet; | |
ee0cb67f | 904 | } |
a6789dca | 905 | } |
ee0cb67f RB |
906 | |
907 | return 0; | |
908 | } | |
909 | ||
53620bba RB |
910 | /** |
911 | * @returns 0 on success, <0 on error, 1 if protocol is unavailable. | |
912 | */ | |
913 | static int | |
e9dea59f | 914 | make_setup_request (AVFormatContext *s, const char *host, int port, |
90abbdba | 915 | int lower_transport, const char *real_challenge) |
1617ad97 FB |
916 | { |
917 | RTSPState *rt = s->priv_data; | |
53620bba | 918 | int j, i, err; |
1617ad97 | 919 | RTSPStream *rtsp_st; |
53620bba RB |
920 | RTSPHeader reply1, *reply = &reply1; |
921 | char cmd[2048]; | |
e9dea59f RB |
922 | const char *trans_pref; |
923 | ||
119b4668 | 924 | if (rt->transport == RTSP_TRANSPORT_RDT) |
e9dea59f RB |
925 | trans_pref = "x-pn-tng"; |
926 | else | |
927 | trans_pref = "RTP/AVP"; | |
115329f1 | 928 | |
1617ad97 FB |
929 | /* for each stream, make the setup request */ |
930 | /* XXX: we assume the same server is used for the control of each | |
931 | RTSP stream */ | |
d1ccf0e0 RD |
932 | |
933 | for(j = RTSP_RTP_PORT_MIN, i = 0; i < rt->nb_rtsp_streams; ++i) { | |
1617ad97 FB |
934 | char transport[2048]; |
935 | ||
8b1ab7bf | 936 | rtsp_st = rt->rtsp_streams[i]; |
1617ad97 | 937 | |
1617ad97 | 938 | /* RTP/UDP */ |
90abbdba | 939 | if (lower_transport == RTSP_LOWER_TRANSPORT_UDP) { |
85fb7b34 | 940 | char buf[256]; |
85fb7b34 FB |
941 | |
942 | /* first try in specified port range */ | |
d1ccf0e0 RD |
943 | if (RTSP_RTP_PORT_MIN != 0) { |
944 | while(j <= RTSP_RTP_PORT_MAX) { | |
b316aa1a | 945 | snprintf(buf, sizeof(buf), "rtp://%s?localport=%d", host, j); |
ecdcbbf6 | 946 | j += 2; /* we will use two port by rtp stream (rtp and rtcp) */ |
dbf30963 | 947 | if (url_open(&rtsp_st->rtp_handle, buf, URL_RDWR) == 0) { |
85fb7b34 | 948 | goto rtp_opened; |
d1ccf0e0 | 949 | } |
85fb7b34 | 950 | } |
1617ad97 | 951 | } |
85fb7b34 | 952 | |
d1ccf0e0 RD |
953 | /* then try on any port |
954 | ** if (url_open(&rtsp_st->rtp_handle, "rtp://", URL_RDONLY) < 0) { | |
955 | ** err = AVERROR_INVALIDDATA; | |
956 | ** goto fail; | |
957 | ** } | |
958 | */ | |
85fb7b34 FB |
959 | |
960 | rtp_opened: | |
8b1ab7bf | 961 | port = rtp_get_local_port(rtsp_st->rtp_handle); |
0ad306bc | 962 | snprintf(transport, sizeof(transport) - 1, |
eee2cbff RB |
963 | "%s/UDP;", trans_pref); |
964 | if (rt->server_type != RTSP_SERVER_REAL) | |
965 | av_strlcat(transport, "unicast;", sizeof(transport)); | |
966 | av_strlcatf(transport, sizeof(transport), | |
967 | "client_port=%d", port); | |
119b4668 | 968 | if (rt->transport == RTSP_TRANSPORT_RTP) |
e9dea59f | 969 | av_strlcatf(transport, sizeof(transport), "-%d", port + 1); |
1617ad97 FB |
970 | } |
971 | ||
972 | /* RTP/TCP */ | |
90abbdba | 973 | else if (lower_transport == RTSP_LOWER_TRANSPORT_TCP) { |
0ad306bc | 974 | snprintf(transport, sizeof(transport) - 1, |
e9dea59f | 975 | "%s/TCP", trans_pref); |
1617ad97 FB |
976 | } |
977 | ||
90abbdba | 978 | else if (lower_transport == RTSP_LOWER_TRANSPORT_UDP_MULTICAST) { |
0ad306bc | 979 | snprintf(transport, sizeof(transport) - 1, |
e9dea59f | 980 | "%s/UDP;multicast", trans_pref); |
1617ad97 | 981 | } |
2e889ae4 | 982 | if (rt->server_type == RTSP_SERVER_REAL) |
e9dea59f | 983 | av_strlcat(transport, ";mode=play", sizeof(transport)); |
115329f1 | 984 | snprintf(cmd, sizeof(cmd), |
b6892136 FB |
985 | "SETUP %s RTSP/1.0\r\n" |
986 | "Transport: %s\r\n", | |
1617ad97 | 987 | rtsp_st->control_url, transport); |
2e889ae4 | 988 | if (i == 0 && rt->server_type == RTSP_SERVER_REAL) { |
e9dea59f RB |
989 | char real_res[41], real_csum[9]; |
990 | ff_rdt_calc_response_and_checksum(real_res, real_csum, | |
991 | real_challenge); | |
992 | av_strlcatf(cmd, sizeof(cmd), | |
993 | "If-Match: %s\r\n" | |
994 | "RealChallenge2: %s, sd=%s\r\n", | |
995 | rt->session_id, real_res, real_csum); | |
996 | } | |
1617ad97 | 997 | rtsp_send_cmd(s, cmd, reply, NULL); |
8a8754d8 RB |
998 | if (reply->status_code == 461 /* Unsupported protocol */ && i == 0) { |
999 | err = 1; | |
1000 | goto fail; | |
7e6ca34f RB |
1001 | } else if (reply->status_code != RTSP_STATUS_OK || |
1002 | reply->nb_transports != 1) { | |
1617ad97 FB |
1003 | err = AVERROR_INVALIDDATA; |
1004 | goto fail; | |
1005 | } | |
1006 | ||
1007 | /* XXX: same protocol for all streams is required */ | |
1008 | if (i > 0) { | |
119b4668 RB |
1009 | if (reply->transports[0].lower_transport != rt->lower_transport || |
1010 | reply->transports[0].transport != rt->transport) { | |
1617ad97 FB |
1011 | err = AVERROR_INVALIDDATA; |
1012 | goto fail; | |
1013 | } | |
1014 | } else { | |
90abbdba | 1015 | rt->lower_transport = reply->transports[0].lower_transport; |
119b4668 | 1016 | rt->transport = reply->transports[0].transport; |
1617ad97 FB |
1017 | } |
1018 | ||
1019 | /* close RTP connection if not choosen */ | |
90abbdba RB |
1020 | if (reply->transports[0].lower_transport != RTSP_LOWER_TRANSPORT_UDP && |
1021 | (lower_transport == RTSP_LOWER_TRANSPORT_UDP)) { | |
8b1ab7bf FB |
1022 | url_close(rtsp_st->rtp_handle); |
1023 | rtsp_st->rtp_handle = NULL; | |
1617ad97 FB |
1024 | } |
1025 | ||
90abbdba RB |
1026 | switch(reply->transports[0].lower_transport) { |
1027 | case RTSP_LOWER_TRANSPORT_TCP: | |
1617ad97 FB |
1028 | rtsp_st->interleaved_min = reply->transports[0].interleaved_min; |
1029 | rtsp_st->interleaved_max = reply->transports[0].interleaved_max; | |
1030 | break; | |
115329f1 | 1031 | |
90abbdba | 1032 | case RTSP_LOWER_TRANSPORT_UDP: |
1617ad97 FB |
1033 | { |
1034 | char url[1024]; | |
115329f1 | 1035 | |
1617ad97 | 1036 | /* XXX: also use address if specified */ |
115329f1 | 1037 | snprintf(url, sizeof(url), "rtp://%s:%d", |
1617ad97 | 1038 | host, reply->transports[0].server_port_min); |
8b1ab7bf | 1039 | if (rtp_set_remote_url(rtsp_st->rtp_handle, url) < 0) { |
1617ad97 FB |
1040 | err = AVERROR_INVALIDDATA; |
1041 | goto fail; | |
1042 | } | |
1043 | } | |
1044 | break; | |
90abbdba | 1045 | case RTSP_LOWER_TRANSPORT_UDP_MULTICAST: |
1617ad97 FB |
1046 | { |
1047 | char url[1024]; | |
d02678ec | 1048 | struct in_addr in; |
1617ad97 | 1049 | |
d02678ec | 1050 | in.s_addr = htonl(reply->transports[0].destination); |
35b74c3d | 1051 | snprintf(url, sizeof(url), "rtp://%s:%d?ttl=%d", |
d02678ec LB |
1052 | inet_ntoa(in), |
1053 | reply->transports[0].port_min, | |
1054 | reply->transports[0].ttl); | |
dbf30963 | 1055 | if (url_open(&rtsp_st->rtp_handle, url, URL_RDWR) < 0) { |
1617ad97 FB |
1056 | err = AVERROR_INVALIDDATA; |
1057 | goto fail; | |
1058 | } | |
1059 | } | |
1060 | break; | |
1061 | } | |
d1ccf0e0 | 1062 | |
ee0cb67f | 1063 | if ((err = rtsp_open_transport_ctx(s, rtsp_st))) |
8b1ab7bf | 1064 | goto fail; |
1617ad97 FB |
1065 | } |
1066 | ||
2e889ae4 | 1067 | if (rt->server_type == RTSP_SERVER_REAL) |
1256d16b RB |
1068 | rt->need_subscription = 1; |
1069 | ||
53620bba RB |
1070 | return 0; |
1071 | ||
1072 | fail: | |
8a8754d8 RB |
1073 | for (i=0; i<rt->nb_rtsp_streams; i++) { |
1074 | if (rt->rtsp_streams[i]->rtp_handle) { | |
1075 | url_close(rt->rtsp_streams[i]->rtp_handle); | |
1076 | rt->rtsp_streams[i]->rtp_handle = NULL; | |
1077 | } | |
1078 | } | |
53620bba RB |
1079 | return err; |
1080 | } | |
1081 | ||
1082 | static int rtsp_read_header(AVFormatContext *s, | |
1083 | AVFormatParameters *ap) | |
1084 | { | |
1085 | RTSPState *rt = s->priv_data; | |
1086 | char host[1024], path[1024], tcpname[1024], cmd[2048], *option_list, *option; | |
1087 | URLContext *rtsp_hd; | |
1088 | int port, ret, err; | |
1089 | RTSPHeader reply1, *reply = &reply1; | |
1090 | unsigned char *content = NULL; | |
90abbdba | 1091 | int lower_transport_mask = 0; |
1cf151e9 | 1092 | char real_challenge[64]; |
53620bba RB |
1093 | |
1094 | /* extract hostname and port */ | |
1095 | url_split(NULL, 0, NULL, 0, | |
1096 | host, sizeof(host), &port, path, sizeof(path), s->filename); | |
1097 | if (port < 0) | |
1098 | port = RTSP_DEFAULT_PORT; | |
1099 | ||
1100 | /* search for options */ | |
1101 | option_list = strchr(path, '?'); | |
1102 | if (option_list) { | |
1103 | /* remove the options from the path */ | |
1104 | *option_list++ = 0; | |
1105 | while(option_list) { | |
1106 | /* move the option pointer */ | |
1107 | option = option_list; | |
1108 | option_list = strchr(option_list, '&'); | |
1109 | if (option_list) | |
1110 | *(option_list++) = 0; | |
1111 | /* handle the options */ | |
1112 | if (strcmp(option, "udp") == 0) | |
90abbdba | 1113 | lower_transport_mask = (1<< RTSP_LOWER_TRANSPORT_UDP); |
53620bba | 1114 | else if (strcmp(option, "multicast") == 0) |
90abbdba | 1115 | lower_transport_mask = (1<< RTSP_LOWER_TRANSPORT_UDP_MULTICAST); |
53620bba | 1116 | else if (strcmp(option, "tcp") == 0) |
90abbdba | 1117 | lower_transport_mask = (1<< RTSP_LOWER_TRANSPORT_TCP); |
53620bba RB |
1118 | } |
1119 | } | |
1120 | ||
90abbdba RB |
1121 | if (!lower_transport_mask) |
1122 | lower_transport_mask = (1 << RTSP_LOWER_TRANSPORT_LAST) - 1; | |
53620bba RB |
1123 | |
1124 | /* open the tcp connexion */ | |
1125 | snprintf(tcpname, sizeof(tcpname), "tcp://%s:%d", host, port); | |
1126 | if (url_open(&rtsp_hd, tcpname, URL_RDWR) < 0) | |
1127 | return AVERROR(EIO); | |
1128 | rt->rtsp_hd = rtsp_hd; | |
1129 | rt->seq = 0; | |
1130 | ||
1cf151e9 RB |
1131 | /* request options supported by the server; this also detects server type */ |
1132 | for (rt->server_type = RTSP_SERVER_RTP;;) { | |
1133 | snprintf(cmd, sizeof(cmd), | |
1134 | "OPTIONS %s RTSP/1.0\r\n", s->filename); | |
2e889ae4 | 1135 | if (rt->server_type == RTSP_SERVER_REAL) |
1cf151e9 RB |
1136 | av_strlcat(cmd, |
1137 | /** | |
1138 | * The following entries are required for proper | |
1139 | * streaming from a Realmedia server. They are | |
1140 | * interdependent in some way although we currently | |
1141 | * don't quite understand how. Values were copied | |
1142 | * from mplayer SVN r23589. | |
1143 | * @param CompanyID is a 16-byte ID in base64 | |
1144 | * @param ClientChallenge is a 16-byte ID in hex | |
1145 | */ | |
1146 | "ClientChallenge: 9e26d33f2984236010ef6253fb1887f7\r\n" | |
1147 | "PlayerStarttime: [28/03/2003:22:50:23 00:00]\r\n" | |
1148 | "CompanyID: KnKV4M4I/B2FjJ1TToLycw==\r\n" | |
1149 | "GUID: 00000000-0000-0000-0000-000000000000\r\n", | |
1150 | sizeof(cmd)); | |
1151 | rtsp_send_cmd(s, cmd, reply, NULL); | |
1152 | if (reply->status_code != RTSP_STATUS_OK) { | |
1153 | err = AVERROR_INVALIDDATA; | |
1154 | goto fail; | |
1155 | } | |
1156 | ||
1157 | /* detect server type if not standard-compliant RTP */ | |
2e889ae4 RB |
1158 | if (rt->server_type != RTSP_SERVER_REAL && reply->real_challenge[0]) { |
1159 | rt->server_type = RTSP_SERVER_REAL; | |
1cf151e9 | 1160 | continue; |
2e889ae4 | 1161 | } else if (rt->server_type == RTSP_SERVER_REAL) { |
1cf151e9 RB |
1162 | strcpy(real_challenge, reply->real_challenge); |
1163 | } | |
1164 | break; | |
1165 | } | |
1166 | ||
53620bba RB |
1167 | /* describe the stream */ |
1168 | snprintf(cmd, sizeof(cmd), | |
1169 | "DESCRIBE %s RTSP/1.0\r\n" | |
1170 | "Accept: application/sdp\r\n", | |
1171 | s->filename); | |
2e889ae4 | 1172 | if (rt->server_type == RTSP_SERVER_REAL) { |
897ade1b RB |
1173 | /** |
1174 | * The Require: attribute is needed for proper streaming from | |
1175 | * Realmedia servers. | |
1176 | */ | |
1177 | av_strlcat(cmd, | |
1178 | "Require: com.real.retain-entity-for-setup\r\n", | |
1179 | sizeof(cmd)); | |
1180 | } | |
53620bba RB |
1181 | rtsp_send_cmd(s, cmd, reply, &content); |
1182 | if (!content) { | |
1183 | err = AVERROR_INVALIDDATA; | |
1184 | goto fail; | |
1185 | } | |
1186 | if (reply->status_code != RTSP_STATUS_OK) { | |
1187 | err = AVERROR_INVALIDDATA; | |
1188 | goto fail; | |
1189 | } | |
1190 | ||
1191 | /* now we got the SDP description, we parse it */ | |
1192 | ret = sdp_parse(s, (const char *)content); | |
1193 | av_freep(&content); | |
1194 | if (ret < 0) { | |
1195 | err = AVERROR_INVALIDDATA; | |
1196 | goto fail; | |
1197 | } | |
1198 | ||
8a8754d8 | 1199 | do { |
90abbdba | 1200 | int lower_transport = ff_log2_tab[lower_transport_mask & ~(lower_transport_mask - 1)]; |
8a8754d8 | 1201 | |
90abbdba | 1202 | err = make_setup_request(s, host, port, lower_transport, |
2e889ae4 | 1203 | rt->server_type == RTSP_SERVER_REAL ? |
e9dea59f | 1204 | real_challenge : NULL); |
8a8754d8 | 1205 | if (err < 0) |
7e6ca34f | 1206 | goto fail; |
90abbdba RB |
1207 | lower_transport_mask &= ~(1 << lower_transport); |
1208 | if (lower_transport_mask == 0 && err == 1) { | |
5ee0e139 | 1209 | err = AVERROR(FF_NETERROR(EPROTONOSUPPORT)); |
8a8754d8 RB |
1210 | goto fail; |
1211 | } | |
1212 | } while (err); | |
53620bba | 1213 | |
ff762d6e FB |
1214 | rt->state = RTSP_STATE_IDLE; |
1215 | rt->seek_timestamp = 0; /* default is to start stream at position | |
1216 | zero */ | |
c04c3282 | 1217 | if (ap->initial_pause) { |
ff762d6e FB |
1218 | /* do not start immediately */ |
1219 | } else { | |
1220 | if (rtsp_read_play(s) < 0) { | |
1221 | err = AVERROR_INVALIDDATA; | |
1617ad97 FB |
1222 | goto fail; |
1223 | } | |
1224 | } | |
1617ad97 FB |
1225 | return 0; |
1226 | fail: | |
8b1ab7bf | 1227 | rtsp_close_streams(rt); |
1617ad97 FB |
1228 | av_freep(&content); |
1229 | url_close(rt->rtsp_hd); | |
1230 | return err; | |
1231 | } | |
1232 | ||
8b1ab7bf FB |
1233 | static int tcp_read_packet(AVFormatContext *s, RTSPStream **prtsp_st, |
1234 | uint8_t *buf, int buf_size) | |
1617ad97 FB |
1235 | { |
1236 | RTSPState *rt = s->priv_data; | |
b6892136 | 1237 | int id, len, i, ret; |
1617ad97 | 1238 | RTSPStream *rtsp_st; |
1617ad97 | 1239 | |
b6892136 FB |
1240 | #ifdef DEBUG_RTP_TCP |
1241 | printf("tcp_read_packet:\n"); | |
1242 | #endif | |
1617ad97 FB |
1243 | redo: |
1244 | for(;;) { | |
2a42b5c3 | 1245 | ret = url_readbuf(rt->rtsp_hd, buf, 1); |
b6892136 FB |
1246 | #ifdef DEBUG_RTP_TCP |
1247 | printf("ret=%d c=%02x [%c]\n", ret, buf[0], buf[0]); | |
1248 | #endif | |
1249 | if (ret != 1) | |
8b1ab7bf | 1250 | return -1; |
b6892136 | 1251 | if (buf[0] == '$') |
1617ad97 FB |
1252 | break; |
1253 | } | |
2a42b5c3 | 1254 | ret = url_readbuf(rt->rtsp_hd, buf, 3); |
b6892136 | 1255 | if (ret != 3) |
8b1ab7bf | 1256 | return -1; |
b6892136 | 1257 | id = buf[0]; |
80fb8234 | 1258 | len = AV_RB16(buf + 1); |
b6892136 FB |
1259 | #ifdef DEBUG_RTP_TCP |
1260 | printf("id=%d len=%d\n", id, len); | |
1261 | #endif | |
8b1ab7bf | 1262 | if (len > buf_size || len < 12) |
1617ad97 FB |
1263 | goto redo; |
1264 | /* get the data */ | |
2a42b5c3 | 1265 | ret = url_readbuf(rt->rtsp_hd, buf, len); |
b6892136 | 1266 | if (ret != len) |
8b1ab7bf | 1267 | return -1; |
115329f1 | 1268 | |
1617ad97 | 1269 | /* find the matching stream */ |
8b1ab7bf FB |
1270 | for(i = 0; i < rt->nb_rtsp_streams; i++) { |
1271 | rtsp_st = rt->rtsp_streams[i]; | |
115329f1 DB |
1272 | if (id >= rtsp_st->interleaved_min && |
1273 | id <= rtsp_st->interleaved_max) | |
1617ad97 FB |
1274 | goto found; |
1275 | } | |
1276 | goto redo; | |
1277 | found: | |
8b1ab7bf FB |
1278 | *prtsp_st = rtsp_st; |
1279 | return len; | |
1617ad97 FB |
1280 | } |
1281 | ||
115329f1 | 1282 | static int udp_read_packet(AVFormatContext *s, RTSPStream **prtsp_st, |
8b1ab7bf | 1283 | uint8_t *buf, int buf_size) |
1617ad97 | 1284 | { |
8b1ab7bf | 1285 | RTSPState *rt = s->priv_data; |
1617ad97 FB |
1286 | RTSPStream *rtsp_st; |
1287 | fd_set rfds; | |
1288 | int fd1, fd2, fd_max, n, i, ret; | |
1617ad97 FB |
1289 | struct timeval tv; |
1290 | ||
1291 | for(;;) { | |
b7b8fc34 | 1292 | if (url_interrupt_cb()) |
a960a1e0 | 1293 | return AVERROR(EINTR); |
1617ad97 FB |
1294 | FD_ZERO(&rfds); |
1295 | fd_max = -1; | |
8b1ab7bf FB |
1296 | for(i = 0; i < rt->nb_rtsp_streams; i++) { |
1297 | rtsp_st = rt->rtsp_streams[i]; | |
1617ad97 | 1298 | /* currently, we cannot probe RTCP handle because of blocking restrictions */ |
8b1ab7bf | 1299 | rtp_get_file_handles(rtsp_st->rtp_handle, &fd1, &fd2); |
1617ad97 FB |
1300 | if (fd1 > fd_max) |
1301 | fd_max = fd1; | |
1302 | FD_SET(fd1, &rfds); | |
1303 | } | |
1617ad97 | 1304 | tv.tv_sec = 0; |
b7b8fc34 | 1305 | tv.tv_usec = 100 * 1000; |
1617ad97 FB |
1306 | n = select(fd_max + 1, &rfds, NULL, NULL, &tv); |
1307 | if (n > 0) { | |
8b1ab7bf FB |
1308 | for(i = 0; i < rt->nb_rtsp_streams; i++) { |
1309 | rtsp_st = rt->rtsp_streams[i]; | |
1310 | rtp_get_file_handles(rtsp_st->rtp_handle, &fd1, &fd2); | |
1617ad97 | 1311 | if (FD_ISSET(fd1, &rfds)) { |
8b1ab7bf FB |
1312 | ret = url_read(rtsp_st->rtp_handle, buf, buf_size); |
1313 | if (ret > 0) { | |
1314 | *prtsp_st = rtsp_st; | |
1617ad97 FB |
1315 | return ret; |
1316 | } | |
1317 | } | |
1318 | } | |
1319 | } | |
1320 | } | |
1321 | } | |
1322 | ||
1323 | static int rtsp_read_packet(AVFormatContext *s, | |
1324 | AVPacket *pkt) | |
1325 | { | |
1326 | RTSPState *rt = s->priv_data; | |
8b1ab7bf FB |
1327 | RTSPStream *rtsp_st; |
1328 | int ret, len; | |
1329 | uint8_t buf[RTP_MAX_PACKET_LENGTH]; | |
1330 | ||
2e889ae4 | 1331 | if (rt->server_type == RTSP_SERVER_REAL && rt->need_subscription) { |
1256d16b RB |
1332 | int i; |
1333 | RTSPHeader reply1, *reply = &reply1; | |
1334 | char cmd[1024]; | |
1335 | ||
1336 | snprintf(cmd, sizeof(cmd), | |
1337 | "SET_PARAMETER %s RTSP/1.0\r\n" | |
1338 | "Subscribe: ", | |
1339 | s->filename); | |
1340 | for (i = 0; i < rt->nb_rtsp_streams; i++) { | |
1341 | if (i != 0) av_strlcat(cmd, ",", sizeof(cmd)); | |
ab63fb03 | 1342 | ff_rdt_subscribe_rule(cmd, sizeof(cmd), i, 0); |
119b4668 | 1343 | if (rt->transport == RTSP_TRANSPORT_RDT) |
f5f1e97f RB |
1344 | ff_rdt_subscribe_rule2( |
1345 | rt->rtsp_streams[i]->rtp_ctx, | |
1346 | cmd, sizeof(cmd), i, 0); | |
1256d16b RB |
1347 | } |
1348 | av_strlcat(cmd, "\r\n", sizeof(cmd)); | |
1349 | rtsp_send_cmd(s, cmd, reply, NULL); | |
1350 | if (reply->status_code != RTSP_STATUS_OK) | |
1351 | return AVERROR_INVALIDDATA; | |
1352 | rt->need_subscription = 0; | |
1353 | ||
1354 | if (rt->state == RTSP_STATE_PLAYING) | |
1355 | rtsp_read_play (s); | |
1356 | } | |
1357 | ||
8b1ab7bf FB |
1358 | /* get next frames from the same RTP packet */ |
1359 | if (rt->cur_rtp) { | |
119b4668 | 1360 | if (rt->transport == RTSP_TRANSPORT_RDT) |
4fce284c RB |
1361 | ret = ff_rdt_parse_packet(rt->cur_rtp, pkt, NULL, 0); |
1362 | else | |
1363 | ret = rtp_parse_packet(rt->cur_rtp, pkt, NULL, 0); | |
8b1ab7bf FB |
1364 | if (ret == 0) { |
1365 | rt->cur_rtp = NULL; | |
1366 | return 0; | |
1367 | } else if (ret == 1) { | |
1368 | return 0; | |
1369 | } else { | |
1370 | rt->cur_rtp = NULL; | |
1371 | } | |
1372 | } | |
1617ad97 | 1373 | |
8b1ab7bf FB |
1374 | /* read next RTP packet */ |
1375 | redo: | |
90abbdba | 1376 | switch(rt->lower_transport) { |
1617ad97 | 1377 | default: |
90abbdba | 1378 | case RTSP_LOWER_TRANSPORT_TCP: |
8b1ab7bf | 1379 | len = tcp_read_packet(s, &rtsp_st, buf, sizeof(buf)); |
1617ad97 | 1380 | break; |
90abbdba RB |
1381 | case RTSP_LOWER_TRANSPORT_UDP: |
1382 | case RTSP_LOWER_TRANSPORT_UDP_MULTICAST: | |
8b1ab7bf | 1383 | len = udp_read_packet(s, &rtsp_st, buf, sizeof(buf)); |
74ef8b43 | 1384 | if (len >=0 && rtsp_st->rtp_ctx) |
dbf30963 | 1385 | rtp_check_and_send_back_rr(rtsp_st->rtp_ctx, len); |
1617ad97 FB |
1386 | break; |
1387 | } | |
8b1ab7bf | 1388 | if (len < 0) |
489b0d4d | 1389 | return len; |
119b4668 | 1390 | if (rt->transport == RTSP_TRANSPORT_RDT) |
4fce284c RB |
1391 | ret = ff_rdt_parse_packet(rtsp_st->rtp_ctx, pkt, buf, len); |
1392 | else | |
1393 | ret = rtp_parse_packet(rtsp_st->rtp_ctx, pkt, buf, len); | |
8b1ab7bf FB |
1394 | if (ret < 0) |
1395 | goto redo; | |
1396 | if (ret == 1) { | |
1397 | /* more packets may follow, so we save the RTP context */ | |
1398 | rt->cur_rtp = rtsp_st->rtp_ctx; | |
1399 | } | |
1400 | return 0; | |
1617ad97 FB |
1401 | } |
1402 | ||
ff762d6e | 1403 | static int rtsp_read_play(AVFormatContext *s) |
b7b8fc34 | 1404 | { |
ff762d6e | 1405 | RTSPState *rt = s->priv_data; |
b7b8fc34 FB |
1406 | RTSPHeader reply1, *reply = &reply1; |
1407 | char cmd[1024]; | |
1408 | ||
bc874dae | 1409 | av_log(s, AV_LOG_DEBUG, "hello state=%d\n", rt->state); |
ff762d6e | 1410 | |
2e889ae4 | 1411 | if (!(rt->server_type == RTSP_SERVER_REAL && rt->need_subscription)) { |
99b2ac07 RB |
1412 | if (rt->state == RTSP_STATE_PAUSED) { |
1413 | snprintf(cmd, sizeof(cmd), | |
1414 | "PLAY %s RTSP/1.0\r\n", | |
1415 | s->filename); | |
1416 | } else { | |
1417 | snprintf(cmd, sizeof(cmd), | |
1418 | "PLAY %s RTSP/1.0\r\n" | |
1419 | "Range: npt=%0.3f-\r\n", | |
1420 | s->filename, | |
1421 | (double)rt->seek_timestamp / AV_TIME_BASE); | |
1422 | } | |
1423 | rtsp_send_cmd(s, cmd, reply, NULL); | |
1424 | if (reply->status_code != RTSP_STATUS_OK) { | |
1425 | return -1; | |
1426 | } | |
1256d16b | 1427 | } |
5f86057f RB |
1428 | rt->state = RTSP_STATE_PLAYING; |
1429 | return 0; | |
b7b8fc34 FB |
1430 | } |
1431 | ||
ff762d6e FB |
1432 | /* pause the stream */ |
1433 | static int rtsp_read_pause(AVFormatContext *s) | |
b7b8fc34 | 1434 | { |
ff762d6e | 1435 | RTSPState *rt = s->priv_data; |
b7b8fc34 FB |
1436 | RTSPHeader reply1, *reply = &reply1; |
1437 | char cmd[1024]; | |
1438 | ||
b7b8fc34 | 1439 | rt = s->priv_data; |
115329f1 | 1440 | |
ff762d6e FB |
1441 | if (rt->state != RTSP_STATE_PLAYING) |
1442 | return 0; | |
2e889ae4 | 1443 | else if (!(rt->server_type == RTSP_SERVER_REAL && rt->need_subscription)) { |
99b2ac07 RB |
1444 | snprintf(cmd, sizeof(cmd), |
1445 | "PAUSE %s RTSP/1.0\r\n", | |
1446 | s->filename); | |
1447 | rtsp_send_cmd(s, cmd, reply, NULL); | |
1448 | if (reply->status_code != RTSP_STATUS_OK) { | |
1449 | return -1; | |
1450 | } | |
1256d16b | 1451 | } |
5f86057f RB |
1452 | rt->state = RTSP_STATE_PAUSED; |
1453 | return 0; | |
b7b8fc34 FB |
1454 | } |
1455 | ||
115329f1 | 1456 | static int rtsp_read_seek(AVFormatContext *s, int stream_index, |
7b3c1382 | 1457 | int64_t timestamp, int flags) |
ff762d6e FB |
1458 | { |
1459 | RTSPState *rt = s->priv_data; | |
115329f1 | 1460 | |
50755217 | 1461 | rt->seek_timestamp = av_rescale_q(timestamp, s->streams[stream_index]->time_base, AV_TIME_BASE_Q); |
ff762d6e FB |
1462 | switch(rt->state) { |
1463 | default: | |
1464 | case RTSP_STATE_IDLE: | |
1465 | break; | |
1466 | case RTSP_STATE_PLAYING: | |
1467 | if (rtsp_read_play(s) != 0) | |
1468 | return -1; | |
1469 | break; | |
1470 | case RTSP_STATE_PAUSED: | |
1471 | rt->state = RTSP_STATE_IDLE; | |
1472 | break; | |
1473 | } | |
1474 | return 0; | |
1475 | } | |
1476 | ||
1617ad97 FB |
1477 | static int rtsp_read_close(AVFormatContext *s) |
1478 | { | |
1479 | RTSPState *rt = s->priv_data; | |
1617ad97 | 1480 | RTSPHeader reply1, *reply = &reply1; |
1617ad97 FB |
1481 | char cmd[1024]; |
1482 | ||
b6892136 | 1483 | #if 0 |
1617ad97 | 1484 | /* NOTE: it is valid to flush the buffer here */ |
90abbdba | 1485 | if (rt->lower_transport == RTSP_LOWER_TRANSPORT_TCP) { |
1617ad97 FB |
1486 | url_fclose(&rt->rtsp_gb); |
1487 | } | |
b6892136 | 1488 | #endif |
115329f1 | 1489 | snprintf(cmd, sizeof(cmd), |
b6892136 | 1490 | "TEARDOWN %s RTSP/1.0\r\n", |
1617ad97 FB |
1491 | s->filename); |
1492 | rtsp_send_cmd(s, cmd, reply, NULL); | |
1493 | ||
8b1ab7bf | 1494 | rtsp_close_streams(rt); |
1617ad97 FB |
1495 | url_close(rt->rtsp_hd); |
1496 | return 0; | |
1497 | } | |
1498 | ||
96862926 | 1499 | #ifdef CONFIG_RTSP_DEMUXER |
d2a067d1 | 1500 | AVInputFormat rtsp_demuxer = { |
1617ad97 | 1501 | "rtsp", |
bde15e74 | 1502 | NULL_IF_CONFIG_SMALL("RTSP input format"), |
1617ad97 FB |
1503 | sizeof(RTSPState), |
1504 | rtsp_probe, | |
1505 | rtsp_read_header, | |
1506 | rtsp_read_packet, | |
1507 | rtsp_read_close, | |
ff762d6e | 1508 | rtsp_read_seek, |
bb76a117 | 1509 | .flags = AVFMT_NOFILE, |
ff762d6e FB |
1510 | .read_play = rtsp_read_play, |
1511 | .read_pause = rtsp_read_pause, | |
1617ad97 | 1512 | }; |
96862926 | 1513 | #endif |
1617ad97 | 1514 | |
cb1fdc61 | 1515 | static int sdp_probe(AVProbeData *p1) |
93ced3e8 | 1516 | { |
0e1ceacd | 1517 | const char *p = p1->buf, *p_end = p1->buf + p1->buf_size; |
cb1fdc61 FB |
1518 | |
1519 | /* we look for a line beginning "c=IN IP4" */ | |
0e1ceacd | 1520 | while (p < p_end && *p != '\0') { |
f7d78f36 | 1521 | if (p + sizeof("c=IN IP4") - 1 < p_end && av_strstart(p, "c=IN IP4", NULL)) |
cb1fdc61 | 1522 | return AVPROBE_SCORE_MAX / 2; |
0e1ceacd MN |
1523 | |
1524 | while(p < p_end - 1 && *p != '\n') p++; | |
1525 | if (++p >= p_end) | |
cb1fdc61 | 1526 | break; |
cb1fdc61 FB |
1527 | if (*p == '\r') |
1528 | p++; | |
1529 | } | |
93ced3e8 FB |
1530 | return 0; |
1531 | } | |
1532 | ||
1533 | #define SDP_MAX_SIZE 8192 | |
1534 | ||
1535 | static int sdp_read_header(AVFormatContext *s, | |
1536 | AVFormatParameters *ap) | |
1537 | { | |
8b1ab7bf | 1538 | RTSPState *rt = s->priv_data; |
93ced3e8 FB |
1539 | RTSPStream *rtsp_st; |
1540 | int size, i, err; | |
1541 | char *content; | |
1542 | char url[1024]; | |
1543 | ||
1544 | /* read the whole sdp file */ | |
1545 | /* XXX: better loading */ | |
1546 | content = av_malloc(SDP_MAX_SIZE); | |
899681cd | 1547 | size = get_buffer(s->pb, content, SDP_MAX_SIZE - 1); |
93ced3e8 FB |
1548 | if (size <= 0) { |
1549 | av_free(content); | |
1550 | return AVERROR_INVALIDDATA; | |
1551 | } | |
1552 | content[size] ='\0'; | |
1553 | ||
1554 | sdp_parse(s, content); | |
1555 | av_free(content); | |
1556 | ||
1557 | /* open each RTP stream */ | |
8b1ab7bf FB |
1558 | for(i=0;i<rt->nb_rtsp_streams;i++) { |
1559 | rtsp_st = rt->rtsp_streams[i]; | |
115329f1 | 1560 | |
d2bf42be | 1561 | snprintf(url, sizeof(url), "rtp://%s:%d?localport=%d&ttl=%d", |
115329f1 | 1562 | inet_ntoa(rtsp_st->sdp_ip), |
93ced3e8 | 1563 | rtsp_st->sdp_port, |
d2bf42be | 1564 | rtsp_st->sdp_port, |
93ced3e8 | 1565 | rtsp_st->sdp_ttl); |
dbf30963 | 1566 | if (url_open(&rtsp_st->rtp_handle, url, URL_RDWR) < 0) { |
93ced3e8 FB |
1567 | err = AVERROR_INVALIDDATA; |
1568 | goto fail; | |
1569 | } | |
ee0cb67f | 1570 | if ((err = rtsp_open_transport_ctx(s, rtsp_st))) |
8b1ab7bf | 1571 | goto fail; |
93ced3e8 FB |
1572 | } |
1573 | return 0; | |
1574 | fail: | |
8b1ab7bf | 1575 | rtsp_close_streams(rt); |
93ced3e8 FB |
1576 | return err; |
1577 | } | |
1578 | ||
1579 | static int sdp_read_packet(AVFormatContext *s, | |
1580 | AVPacket *pkt) | |
1581 | { | |
8b1ab7bf | 1582 | return rtsp_read_packet(s, pkt); |
93ced3e8 FB |
1583 | } |
1584 | ||
1585 | static int sdp_read_close(AVFormatContext *s) | |
1586 | { | |
8b1ab7bf FB |
1587 | RTSPState *rt = s->priv_data; |
1588 | rtsp_close_streams(rt); | |
93ced3e8 FB |
1589 | return 0; |
1590 | } | |
1591 | ||
ff70e601 MR |
1592 | #ifdef CONFIG_SDP_DEMUXER |
1593 | AVInputFormat sdp_demuxer = { | |
93ced3e8 | 1594 | "sdp", |
bde15e74 | 1595 | NULL_IF_CONFIG_SMALL("SDP"), |
93ced3e8 FB |
1596 | sizeof(RTSPState), |
1597 | sdp_probe, | |
1598 | sdp_read_header, | |
1599 | sdp_read_packet, | |
1600 | sdp_read_close, | |
1601 | }; | |
ff70e601 | 1602 | #endif |
93ced3e8 | 1603 | |
e7047005 | 1604 | #ifdef CONFIG_REDIR_DEMUXER |
1617ad97 FB |
1605 | /* dummy redirector format (used directly in av_open_input_file now) */ |
1606 | static int redir_probe(AVProbeData *pd) | |
1607 | { | |
1608 | const char *p; | |
1609 | p = pd->buf; | |
1610 | while (redir_isspace(*p)) | |
1611 | p++; | |
f7d78f36 MR |
1612 | if (av_strstart(p, "http://", NULL) || |
1613 | av_strstart(p, "rtsp://", NULL)) | |
1617ad97 FB |
1614 | return AVPROBE_SCORE_MAX; |
1615 | return 0; | |
1616 | } | |
1617 | ||
e8acf0ed | 1618 | static int redir_read_header(AVFormatContext *s, AVFormatParameters *ap) |
1617ad97 FB |
1619 | { |
1620 | char buf[4096], *q; | |
1621 | int c; | |
1622 | AVFormatContext *ic = NULL; | |
e8acf0ed | 1623 | ByteIOContext *f = s->pb; |
1617ad97 FB |
1624 | |
1625 | /* parse each URL and try to open it */ | |
1626 | c = url_fgetc(f); | |
1627 | while (c != URL_EOF) { | |
1628 | /* skip spaces */ | |
1629 | for(;;) { | |
1630 | if (!redir_isspace(c)) | |
1631 | break; | |
1632 | c = url_fgetc(f); | |
1633 | } | |
1634 | if (c == URL_EOF) | |
1635 | break; | |
1636 | /* record url */ | |
1637 | q = buf; | |
1638 | for(;;) { | |
1639 | if (c == URL_EOF || redir_isspace(c)) | |
1640 | break; | |
1641 | if ((q - buf) < sizeof(buf) - 1) | |
1642 | *q++ = c; | |
1643 | c = url_fgetc(f); | |
1644 | } | |
1645 | *q = '\0'; | |
1646 | //printf("URL='%s'\n", buf); | |
1647 | /* try to open the media file */ | |
1648 | if (av_open_input_file(&ic, buf, NULL, 0, NULL) == 0) | |
1649 | break; | |
1650 | } | |
1617ad97 | 1651 | if (!ic) |
6f3e0b21 | 1652 | return AVERROR(EIO); |
e8acf0ed LA |
1653 | |
1654 | *s = *ic; | |
1655 | url_fclose(f); | |
1656 | ||
1657 | return 0; | |
1617ad97 FB |
1658 | } |
1659 | ||
d2a067d1 | 1660 | AVInputFormat redir_demuxer = { |
1617ad97 | 1661 | "redir", |
bde15e74 | 1662 | NULL_IF_CONFIG_SMALL("Redirector format"), |
1617ad97 FB |
1663 | 0, |
1664 | redir_probe, | |
e8acf0ed | 1665 | redir_read_header, |
1617ad97 FB |
1666 | NULL, |
1667 | NULL, | |
1668 | }; | |
e7047005 | 1669 | #endif |