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