X-Git-Url: https://git.sesse.net/?a=blobdiff_plain;f=libavformat%2Frtsp.c;h=8819d02b2527e9351e793ebeb543d15995633944;hb=e3227b3f25d8dacba271dd60c07e7302a8272f5d;hp=802591dd965d45d51a37cbd523e4d3359b112525;hpb=4358d5def36e01994d2c953ddde00c1675fa2368;p=ffmpeg diff --git a/libavformat/rtsp.c b/libavformat/rtsp.c index 802591dd965..8819d02b252 100644 --- a/libavformat/rtsp.c +++ b/libavformat/rtsp.c @@ -2,47 +2,64 @@ * RTSP/SDP client * Copyright (c) 2002 Fabrice Bellard. * - * This library is free software; you can redistribute it and/or + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. + * version 2.1 of the License, or (at your option) any later version. * - * This library is distributed in the hope that it will be useful, + * FFmpeg is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ + +#include "libavutil/avstring.h" #include "avformat.h" -#include /* for select() prototype */ #include -#include -#include -#ifndef __BEOS__ -# include -#else -# include "barpainet.h" -#endif +#include /* for select() prototype */ +#include "network.h" +#include "rtsp.h" + +#include "rtp_internal.h" //#define DEBUG //#define DEBUG_RTP_TCP +enum RTSPClientState { + RTSP_STATE_IDLE, + RTSP_STATE_PLAYING, + RTSP_STATE_PAUSED, +}; + typedef struct RTSPState { URLContext *rtsp_hd; /* RTSP TCP connexion handle */ + int nb_rtsp_streams; + struct RTSPStream **rtsp_streams; + + enum RTSPClientState state; + int64_t seek_timestamp; + /* XXX: currently we use unbuffered input */ // ByteIOContext rtsp_gb; int seq; /* RTSP command sequence number */ char session_id[512]; enum RTSPProtocol protocol; char last_reply[2048]; /* XXX: allocate ? */ + RTPDemuxContext *cur_rtp; } RTSPState; typedef struct RTSPStream { - AVFormatContext *ic; + URLContext *rtp_handle; /* RTP stream handle */ + RTPDemuxContext *rtp_ctx; /* RTP parse context */ + + int stream_index; /* corresponding stream index, if any. -1 if none (MPEG2TS case) */ int interleaved_min, interleaved_max; /* interleave ids, if TCP transport */ char control_url[1024]; /* url for this stream (from SDP) */ @@ -50,33 +67,31 @@ typedef struct RTSPStream { struct in_addr sdp_ip; /* IP address (from SDP content - not used in RTSP) */ int sdp_ttl; /* IP TTL (from SDP content - not used in RTSP) */ int sdp_payload_type; /* payload type - only used in SDP */ + rtp_payload_data_t rtp_payload_data; /* rtp payload parsing infos from SDP */ + + RTPDynamicProtocolHandler *dynamic_handler; ///< Only valid if it's a dynamic protocol. (This is the handler structure) + void *dynamic_protocol_context; ///< Only valid if it's a dynamic protocol. (This is any private data associated with the dynamic protocol) } RTSPStream; +static int rtsp_read_play(AVFormatContext *s); + /* XXX: currently, the only way to change the protocols consists in changing this variable */ -#if 1 -int rtsp_default_protocols = (1 << RTSP_PROTOCOL_RTP_TCP) | (1 << RTSP_PROTOCOL_RTP_UDP) | (1 << RTSP_PROTOCOL_RTP_UDP_MULTICAST); -#else -/* try it if a proxy is used */ -int rtsp_default_protocols = (1 << RTSP_PROTOCOL_RTP_TCP); -#endif -/* if non zero, then set a range for RTP ports */ -int rtsp_rtp_port_min = 0; -int rtsp_rtp_port_max = 0; - -FFRTSPCallback *ff_rtsp_callback = NULL; +#if LIBAVFORMAT_VERSION_INT < (53 << 16) +int rtsp_default_protocols = (1 << RTSP_PROTOCOL_RTP_UDP); +#endif static int rtsp_probe(AVProbeData *p) { - if (strstart(p->filename, "rtsp:", NULL)) + if (av_strstart(p->filename, "rtsp:", NULL)) return AVPROBE_SCORE_MAX; return 0; } static int redir_isspace(int c) { - return (c == ' ' || c == '\t' || c == '\n' || c == '\r'); + return c == ' ' || c == '\t' || c == '\n' || c == '\r'; } static void skip_spaces(const char **pp) @@ -88,13 +103,15 @@ static void skip_spaces(const char **pp) *pp = p; } -static void get_word_sep(char *buf, int buf_size, const char *sep, +static void get_word_sep(char *buf, int buf_size, const char *sep, const char **pp) { const char *p; char *q; p = *pp; + if (*p == '/') + p++; skip_spaces(&p); q = buf; while (!strchr(sep, *p) && *p != '\0') { @@ -127,18 +144,71 @@ static void get_word(char *buf, int buf_size, const char **pp) /* parse the rtpmap description: /[/] */ -static int sdp_parse_rtpmap(AVCodecContext *codec, const char *p) +static int sdp_parse_rtpmap(AVCodecContext *codec, RTSPStream *rtsp_st, int payload_type, const char *p) { char buf[256]; + int i; + AVCodec *c; + const char *c_name; - /* codec name */ + /* Loop into AVRtpDynamicPayloadTypes[] and AVRtpPayloadTypes[] and + see if we can handle this kind of payload */ get_word_sep(buf, sizeof(buf), "/", &p); - if (!strcmp(buf, "MP4V-ES")) { - codec->codec_id = CODEC_ID_MPEG4; - return 0; + if (payload_type >= RTP_PT_PRIVATE) { + RTPDynamicProtocolHandler *handler= RTPFirstDynamicPayloadHandler; + while(handler) { + if (!strcmp(buf, handler->enc_name) && (codec->codec_type == handler->codec_type)) { + codec->codec_id = handler->codec_id; + rtsp_st->dynamic_handler= handler; + if(handler->open) { + rtsp_st->dynamic_protocol_context= handler->open(); + } + break; + } + handler= handler->next; + } } else { - return -1; + /* We are in a standard case ( from http://www.iana.org/assignments/rtp-parameters) */ + /* search into AVRtpPayloadTypes[] */ + codec->codec_id = ff_rtp_codec_id(buf, codec->codec_type); + } + + c = avcodec_find_decoder(codec->codec_id); + if (c && c->name) + c_name = c->name; + else + c_name = (char *)NULL; + + if (c_name) { + get_word_sep(buf, sizeof(buf), "/", &p); + i = atoi(buf); + switch (codec->codec_type) { + case CODEC_TYPE_AUDIO: + av_log(codec, AV_LOG_DEBUG, " audio codec set to : %s\n", c_name); + codec->sample_rate = RTSP_DEFAULT_AUDIO_SAMPLERATE; + codec->channels = RTSP_DEFAULT_NB_AUDIO_CHANNELS; + if (i > 0) { + codec->sample_rate = i; + get_word_sep(buf, sizeof(buf), "/", &p); + i = atoi(buf); + if (i > 0) + codec->channels = i; + // TODO: there is a bug here; if it is a mono stream, and less than 22000Hz, faad upconverts to stereo and twice the + // frequency. No problem, but the sample rate is being set here by the sdp line. Upcoming patch forthcoming. (rdm) + } + av_log(codec, AV_LOG_DEBUG, " audio samplerate set to : %i\n", codec->sample_rate); + av_log(codec, AV_LOG_DEBUG, " audio channels set to : %i\n", codec->channels); + break; + case CODEC_TYPE_VIDEO: + av_log(codec, AV_LOG_DEBUG, " video codec set to : %s\n", c_name); + break; + default: + break; + } + return 0; } + + return -1; } /* return the length and optionnaly the data */ @@ -170,45 +240,121 @@ static int hex_to_data(uint8_t *data, const char *p) return len; } -static void sdp_parse_fmtp(AVCodecContext *codec, const char *p) +static void sdp_parse_fmtp_config(AVCodecContext *codec, char *attr, char *value) { - char attr[256]; - char value[4096]; - int len; - - /* loop on each attribute */ - for(;;) { - skip_spaces(&p); - if (*p == '\0') - break; - get_word_sep(attr, sizeof(attr), "=", &p); - if (*p == '=') - p++; - get_word_sep(value, sizeof(value), ";", &p); - if (*p == ';') - p++; - /* handle MPEG4 video */ - switch(codec->codec_id) { + switch (codec->codec_id) { case CODEC_ID_MPEG4: + case CODEC_ID_AAC: if (!strcmp(attr, "config")) { /* decode the hexa encoded parameter */ - len = hex_to_data(NULL, value); - codec->extradata = av_mallocz(len); + int len = hex_to_data(NULL, value); + codec->extradata = av_mallocz(len + FF_INPUT_BUFFER_PADDING_SIZE); if (!codec->extradata) - goto fail; + return; codec->extradata_size = len; hex_to_data(codec->extradata, value); } break; default: - /* ignore data for other codecs */ break; + } + return; +} + +typedef struct attrname_map +{ + const char *str; + uint16_t type; + uint32_t offset; +} attrname_map_t; + +/* All known fmtp parmeters and the corresping RTPAttrTypeEnum */ +#define ATTR_NAME_TYPE_INT 0 +#define ATTR_NAME_TYPE_STR 1 +static attrname_map_t attr_names[]= +{ + {"SizeLength", ATTR_NAME_TYPE_INT, offsetof(rtp_payload_data_t, sizelength)}, + {"IndexLength", ATTR_NAME_TYPE_INT, offsetof(rtp_payload_data_t, indexlength)}, + {"IndexDeltaLength", ATTR_NAME_TYPE_INT, offsetof(rtp_payload_data_t, indexdeltalength)}, + {"profile-level-id", ATTR_NAME_TYPE_INT, offsetof(rtp_payload_data_t, profile_level_id)}, + {"StreamType", ATTR_NAME_TYPE_INT, offsetof(rtp_payload_data_t, streamtype)}, + {"mode", ATTR_NAME_TYPE_STR, offsetof(rtp_payload_data_t, mode)}, + {NULL, -1, -1}, +}; + +/** parse the attribute line from the fmtp a line of an sdp resonse. This is broken out as a function +* because it is used in rtp_h264.c, which is forthcoming. +*/ +int rtsp_next_attr_and_value(const char **p, char *attr, int attr_size, char *value, int value_size) +{ + skip_spaces(p); + if(**p) + { + get_word_sep(attr, attr_size, "=", p); + if (**p == '=') + (*p)++; + get_word_sep(value, value_size, ";", p); + if (**p == ';') + (*p)++; + return 1; + } + return 0; +} + +/* parse a SDP line and save stream attributes */ +static void sdp_parse_fmtp(AVStream *st, const char *p) +{ + char attr[256]; + char value[4096]; + int i; + + RTSPStream *rtsp_st = st->priv_data; + AVCodecContext *codec = st->codec; + rtp_payload_data_t *rtp_payload_data = &rtsp_st->rtp_payload_data; + + /* loop on each attribute */ + while(rtsp_next_attr_and_value(&p, attr, sizeof(attr), value, sizeof(value))) + { + /* grab the codec extra_data from the config parameter of the fmtp line */ + sdp_parse_fmtp_config(codec, attr, value); + /* Looking for a known attribute */ + for (i = 0; attr_names[i].str; ++i) { + if (!strcasecmp(attr, attr_names[i].str)) { + if (attr_names[i].type == ATTR_NAME_TYPE_INT) + *(int *)((char *)rtp_payload_data + attr_names[i].offset) = atoi(value); + else if (attr_names[i].type == ATTR_NAME_TYPE_STR) + *(char **)((char *)rtp_payload_data + attr_names[i].offset) = av_strdup(value); + } } - fail: ; - // printf("'%s' = '%s'\n", attr, value); } } +/** Parse a string \p in the form of Range:npt=xx-xx, and determine the start + * and end time. + * Used for seeking in the rtp stream. + */ +static void rtsp_parse_range_npt(const char *p, int64_t *start, int64_t *end) +{ + char buf[256]; + + skip_spaces(&p); + if (!av_stristart(p, "npt=", &p)) + return; + + *start = AV_NOPTS_VALUE; + *end = AV_NOPTS_VALUE; + + get_word_sep(buf, sizeof(buf), "-", &p); + *start = parse_date(buf, 1); + if (*p == '-') { + p++; + get_word_sep(buf, sizeof(buf), "-", &p); + *end = parse_date(buf, 1); + } +// av_log(NULL, AV_LOG_DEBUG, "Range Start: %lld\n", *start); +// av_log(NULL, AV_LOG_DEBUG, "Range End: %lld\n", *end); +} + typedef struct SDPParseState { /* SDP only */ struct in_addr default_ip; @@ -218,6 +364,7 @@ typedef struct SDPParseState { static void sdp_parse_line(AVFormatContext *s, SDPParseState *s1, int letter, const char *buf) { + RTSPState *rt = s->priv_data; char buf1[64], st_type[64]; const char *p; int codec_type, payload_type, i; @@ -259,11 +406,11 @@ static void sdp_parse_line(AVFormatContext *s, SDPParseState *s1, } break; case 's': - pstrcpy(s->title, sizeof(s->title), p); + av_strlcpy(s->title, p, sizeof(s->title)); break; case 'i': if (s->nb_streams == 0) { - pstrcpy(s->comment, sizeof(s->comment), p); + av_strlcpy(s->comment, p, sizeof(s->comment)); break; } break; @@ -280,70 +427,102 @@ static void sdp_parse_line(AVFormatContext *s, SDPParseState *s1, rtsp_st = av_mallocz(sizeof(RTSPStream)); if (!rtsp_st) return; - st = av_new_stream(s, s->nb_streams); - if (!st) - return; - st->priv_data = rtsp_st; + rtsp_st->stream_index = -1; + dynarray_add(&rt->rtsp_streams, &rt->nb_rtsp_streams, rtsp_st); rtsp_st->sdp_ip = s1->default_ip; rtsp_st->sdp_ttl = s1->default_ttl; - st->codec.codec_type = codec_type; - get_word(buf1, sizeof(buf1), &p); /* port */ rtsp_st->sdp_port = atoi(buf1); get_word(buf1, sizeof(buf1), &p); /* protocol (ignored) */ - + /* XXX: handle list of formats */ get_word(buf1, sizeof(buf1), &p); /* format list */ rtsp_st->sdp_payload_type = atoi(buf1); - if (rtsp_st->sdp_payload_type < 96) { - /* if standard payload type, we can find the codec right now */ - rtp_get_codec_info(&st->codec, rtsp_st->sdp_payload_type); - } + if (!strcmp(ff_rtp_enc_name(rtsp_st->sdp_payload_type), "MP2T")) { + /* no corresponding stream */ + } else { + st = av_new_stream(s, 0); + if (!st) + return; + st->priv_data = rtsp_st; + rtsp_st->stream_index = st->index; + st->codec->codec_type = codec_type; + if (rtsp_st->sdp_payload_type < RTP_PT_PRIVATE) { + /* if standard payload type, we can find the codec right now */ + rtp_get_codec_info(st->codec, rtsp_st->sdp_payload_type); + } + } /* put a default control url */ - pstrcpy(rtsp_st->control_url, sizeof(rtsp_st->control_url), s->filename); + av_strlcpy(rtsp_st->control_url, s->filename, sizeof(rtsp_st->control_url)); break; case 'a': - if (strstart(p, "control:", &p) && s->nb_streams > 0) { + if (av_strstart(p, "control:", &p) && s->nb_streams > 0) { char proto[32]; /* get the control url */ st = s->streams[s->nb_streams - 1]; rtsp_st = st->priv_data; - + /* XXX: may need to add full url resolution */ - url_split(proto, sizeof(proto), NULL, 0, NULL, NULL, 0, p); + url_split(proto, sizeof(proto), NULL, 0, NULL, 0, NULL, NULL, 0, p); if (proto[0] == '\0') { /* relative control URL */ - pstrcat(rtsp_st->control_url, sizeof(rtsp_st->control_url), "/"); - pstrcat(rtsp_st->control_url, sizeof(rtsp_st->control_url), p); + av_strlcat(rtsp_st->control_url, "/", sizeof(rtsp_st->control_url)); + av_strlcat(rtsp_st->control_url, p, sizeof(rtsp_st->control_url)); } else { - pstrcpy(rtsp_st->control_url, sizeof(rtsp_st->control_url), p); + av_strlcpy(rtsp_st->control_url, p, sizeof(rtsp_st->control_url)); } - } else if (strstart(p, "rtpmap:", &p)) { + } else if (av_strstart(p, "rtpmap:", &p)) { /* NOTE: rtpmap is only supported AFTER the 'm=' tag */ - get_word(buf1, sizeof(buf1), &p); + get_word(buf1, sizeof(buf1), &p); payload_type = atoi(buf1); for(i = 0; i < s->nb_streams;i++) { st = s->streams[i]; rtsp_st = st->priv_data; if (rtsp_st->sdp_payload_type == payload_type) { - sdp_parse_rtpmap(&st->codec, p); + sdp_parse_rtpmap(st->codec, rtsp_st, payload_type, p); } } - } else if (strstart(p, "fmtp:", &p)) { + } else if (av_strstart(p, "fmtp:", &p)) { /* NOTE: fmtp is only supported AFTER the 'a=rtpmap:xxx' tag */ - get_word(buf1, sizeof(buf1), &p); + get_word(buf1, sizeof(buf1), &p); + payload_type = atoi(buf1); + for(i = 0; i < s->nb_streams;i++) { + st = s->streams[i]; + rtsp_st = st->priv_data; + if (rtsp_st->sdp_payload_type == payload_type) { + if(rtsp_st->dynamic_handler && rtsp_st->dynamic_handler->parse_sdp_a_line) { + if(!rtsp_st->dynamic_handler->parse_sdp_a_line(st, rtsp_st->dynamic_protocol_context, buf)) { + sdp_parse_fmtp(st, p); + } + } else { + sdp_parse_fmtp(st, p); + } + } + } + } else if(av_strstart(p, "framesize:", &p)) { + // let dynamic protocol handlers have a stab at the line. + get_word(buf1, sizeof(buf1), &p); payload_type = atoi(buf1); for(i = 0; i < s->nb_streams;i++) { st = s->streams[i]; rtsp_st = st->priv_data; if (rtsp_st->sdp_payload_type == payload_type) { - sdp_parse_fmtp(&st->codec, p); + if(rtsp_st->dynamic_handler && rtsp_st->dynamic_handler->parse_sdp_a_line) { + rtsp_st->dynamic_handler->parse_sdp_a_line(st, rtsp_st->dynamic_protocol_context, buf); + } } } + } else if(av_strstart(p, "range:", &p)) { + int64_t start, end; + + // this is so that seeking on a streamed file can work. + rtsp_parse_range_npt(p, &start, &end); + s->start_time= start; + s->duration= (end==AV_NOPTS_VALUE)?AV_NOPTS_VALUE:end-start; // AV_NOPTS_VALUE means live broadcast (and can't seek) } break; } @@ -355,7 +534,7 @@ static int sdp_parse(AVFormatContext *s, const char *content) int letter; char buf[1024], *q; SDPParseState sdp_parse_state, *s1 = &sdp_parse_state; - + memset(s1, 0, sizeof(SDPParseState)); p = content; for(;;) { @@ -414,9 +593,9 @@ static void rtsp_parse_transport(RTSPHeader *reply, const char *p) char parameter[16]; RTSPTransportField *th; char buf[256]; - + reply->nb_transports = 0; - + for(;;) { skip_spaces(&p); if (*p == '\0') @@ -424,22 +603,29 @@ static void rtsp_parse_transport(RTSPHeader *reply, const char *p) th = &reply->transports[reply->nb_transports]; - get_word_sep(transport_protocol, sizeof(transport_protocol), + get_word_sep(transport_protocol, sizeof(transport_protocol), "/", &p); if (*p == '/') p++; - get_word_sep(profile, sizeof(profile), "/;,", &p); - lower_transport[0] = '\0'; - if (*p == '/') { - p++; - get_word_sep(lower_transport, sizeof(lower_transport), - ";,", &p); + if (!strcasecmp (transport_protocol, "rtp")) { + get_word_sep(profile, sizeof(profile), "/;,", &p); + lower_transport[0] = '\0'; + /* rtp/avp/ */ + if (*p == '/') { + p++; + get_word_sep(lower_transport, sizeof(lower_transport), + ";,", &p); + } + } else if (!strcasecmp (transport_protocol, "x-pn-tng")) { + /* x-pn-tng/ */ + get_word_sep(lower_transport, sizeof(lower_transport), "/;,", &p); + profile[0] = '\0'; } if (!strcasecmp(lower_transport, "TCP")) th->protocol = RTSP_PROTOCOL_RTP_TCP; else th->protocol = RTSP_PROTOCOL_RTP_UDP; - + if (*p == ';') p++; /* get each parameter */ @@ -453,19 +639,19 @@ static void rtsp_parse_transport(RTSPHeader *reply, const char *p) } else if (!strcmp(parameter, "client_port")) { if (*p == '=') { p++; - rtsp_parse_range(&th->client_port_min, + rtsp_parse_range(&th->client_port_min, &th->client_port_max, &p); } } else if (!strcmp(parameter, "server_port")) { if (*p == '=') { p++; - rtsp_parse_range(&th->server_port_min, + rtsp_parse_range(&th->server_port_min, &th->server_port_max, &p); } } else if (!strcmp(parameter, "interleaved")) { if (*p == '=') { p++; - rtsp_parse_range(&th->interleaved_min, + rtsp_parse_range(&th->interleaved_min, &th->interleaved_max, &p); } } else if (!strcmp(parameter, "multicast")) { @@ -482,7 +668,7 @@ static void rtsp_parse_transport(RTSPHeader *reply, const char *p) if (*p == '=') { p++; get_word_sep(buf, sizeof(buf), ";,", &p); - if (inet_aton(buf, &ipaddr)) + if (inet_aton(buf, &ipaddr)) th->destination = ntohl(ipaddr.s_addr); } } @@ -504,17 +690,33 @@ void rtsp_parse_line(RTSPHeader *reply, const char *buf) /* NOTE: we do case independent match for broken servers */ p = buf; - if (stristart(p, "Session:", &p)) { + if (av_stristart(p, "Session:", &p)) { get_word_sep(reply->session_id, sizeof(reply->session_id), ";", &p); - } else if (stristart(p, "Content-Length:", &p)) { + } else if (av_stristart(p, "Content-Length:", &p)) { reply->content_length = strtol(p, NULL, 10); - } else if (stristart(p, "Transport:", &p)) { + } else if (av_stristart(p, "Transport:", &p)) { rtsp_parse_transport(reply, p); - } else if (stristart(p, "CSeq:", &p)) { + } else if (av_stristart(p, "CSeq:", &p)) { reply->seq = strtol(p, NULL, 10); + } else if (av_stristart(p, "Range:", &p)) { + rtsp_parse_range_npt(p, &reply->range_start, &reply->range_end); } } +static int url_readbuf(URLContext *h, unsigned char *buf, int size) +{ + int ret, len; + + len = 0; + while (len < size) { + ret = url_read(h, buf+len, size-len); + if (ret < 1) + return ret; + len += ret; + } + return len; +} + /* skip a RTP/TCP interleaved packet */ static void rtsp_skip_packet(AVFormatContext *s) { @@ -522,10 +724,10 @@ static void rtsp_skip_packet(AVFormatContext *s) int ret, len, len1; uint8_t buf[1024]; - ret = url_read(rt->rtsp_hd, buf, 3); + ret = url_readbuf(rt->rtsp_hd, buf, 3); if (ret != 3) return; - len = (buf[1] << 8) | buf[2]; + len = AV_RB16(buf + 1); #ifdef DEBUG printf("skipping RTP packet len=%d\n", len); #endif @@ -534,15 +736,15 @@ static void rtsp_skip_packet(AVFormatContext *s) len1 = len; if (len1 > sizeof(buf)) len1 = sizeof(buf); - ret = url_read(rt->rtsp_hd, buf, len1); + ret = url_readbuf(rt->rtsp_hd, buf, len1); if (ret != len1) return; len -= len1; } } -static void rtsp_send_cmd(AVFormatContext *s, - const char *cmd, RTSPHeader *reply, +static void rtsp_send_cmd(AVFormatContext *s, + const char *cmd, RTSPHeader *reply, unsigned char **content_ptr) { RTSPState *rt = s->priv_data; @@ -555,14 +757,14 @@ static void rtsp_send_cmd(AVFormatContext *s, memset(reply, 0, sizeof(RTSPHeader)); rt->seq++; - pstrcpy(buf, sizeof(buf), cmd); + av_strlcpy(buf, cmd, sizeof(buf)); snprintf(buf1, sizeof(buf1), "CSeq: %d\r\n", rt->seq); - pstrcat(buf, sizeof(buf), buf1); + av_strlcat(buf, buf1, sizeof(buf)); if (rt->session_id[0] != '\0' && !strstr(cmd, "\nIf-Match:")) { snprintf(buf1, sizeof(buf1), "Session: %s\r\n", rt->session_id); - pstrcat(buf, sizeof(buf), buf1); + av_strlcat(buf, buf1, sizeof(buf)); } - pstrcat(buf, sizeof(buf), "\r\n"); + av_strlcat(buf, "\r\n", sizeof(buf)); #ifdef DEBUG printf("Sending:\n%s--\n", buf); #endif @@ -574,7 +776,7 @@ static void rtsp_send_cmd(AVFormatContext *s, for(;;) { q = buf; for(;;) { - if (url_read(rt->rtsp_hd, &ch, 1) != 1) + if (url_readbuf(rt->rtsp_hd, &ch, 1) != 1) break; if (ch == '\n') break; @@ -601,151 +803,131 @@ static void rtsp_send_cmd(AVFormatContext *s, reply->status_code = atoi(buf1); } else { rtsp_parse_line(reply, p); - pstrcat(rt->last_reply, sizeof(rt->last_reply), p); - pstrcat(rt->last_reply, sizeof(rt->last_reply), "\n"); + av_strlcat(rt->last_reply, p, sizeof(rt->last_reply)); + av_strlcat(rt->last_reply, "\n", sizeof(rt->last_reply)); } line_count++; } - + if (rt->session_id[0] == '\0' && reply->session_id[0] != '\0') - pstrcpy(rt->session_id, sizeof(rt->session_id), reply->session_id); - + av_strlcpy(rt->session_id, reply->session_id, sizeof(rt->session_id)); + content_length = reply->content_length; if (content_length > 0) { /* leave some room for a trailing '\0' (useful for simple parsing) */ content = av_malloc(content_length + 1); - url_read(rt->rtsp_hd, content, content_length); + (void)url_readbuf(rt->rtsp_hd, content, content_length); content[content_length] = '\0'; } if (content_ptr) *content_ptr = content; + else + av_free(content); } -/* useful for modules: set RTSP callback function */ -void rtsp_set_callback(FFRTSPCallback *rtsp_cb) +/* close and free RTSP streams */ +static void rtsp_close_streams(RTSPState *rt) { - ff_rtsp_callback = rtsp_cb; -} + int i; + RTSPStream *rtsp_st; + for(i=0;inb_rtsp_streams;i++) { + rtsp_st = rt->rtsp_streams[i]; + if (rtsp_st) { + if (rtsp_st->rtp_ctx) + rtp_parse_close(rtsp_st->rtp_ctx); + if (rtsp_st->rtp_handle) + url_close(rtsp_st->rtp_handle); + if (rtsp_st->dynamic_handler && rtsp_st->dynamic_protocol_context) + rtsp_st->dynamic_handler->close(rtsp_st->dynamic_protocol_context); + } + av_free(rtsp_st); + } + av_free(rt->rtsp_streams); +} -static int rtsp_read_header(AVFormatContext *s, - AVFormatParameters *ap) +/** + * @returns 0 on success, <0 on error, 1 if protocol is unavailable. + */ +static int +make_setup_request (AVFormatContext *s, const char *host, int port, int protocol) { RTSPState *rt = s->priv_data; - char host[1024], path[1024], tcpname[1024], cmd[2048]; - URLContext *rtsp_hd; - int port, i, ret, err; - RTSPHeader reply1, *reply = &reply1; - unsigned char *content = NULL; - AVStream *st; + int j, i, err; RTSPStream *rtsp_st; - int protocol_mask; - - /* extract hostname and port */ - url_split(NULL, 0, - host, sizeof(host), &port, path, sizeof(path), s->filename); - if (port < 0) - port = RTSP_DEFAULT_PORT; - - /* open the tcp connexion */ - snprintf(tcpname, sizeof(tcpname), "tcp://%s:%d", host, port); - if (url_open(&rtsp_hd, tcpname, URL_RDWR) < 0) - return AVERROR_IO; - rt->rtsp_hd = rtsp_hd; - rt->seq = 0; - - /* describe the stream */ - snprintf(cmd, sizeof(cmd), - "DESCRIBE %s RTSP/1.0\r\n" - "Accept: application/sdp\r\n", - s->filename); - rtsp_send_cmd(s, cmd, reply, &content); - if (!content) { - err = AVERROR_INVALIDDATA; - goto fail; - } - if (reply->status_code != RTSP_STATUS_OK) { - err = AVERROR_INVALIDDATA; - goto fail; - } - - /* now we got the SDP description, we parse it */ - ret = sdp_parse(s, (const char *)content); - av_freep(&content); - if (ret < 0) { - err = AVERROR_INVALIDDATA; - goto fail; - } - - protocol_mask = rtsp_default_protocols; + AVStream *st; + RTSPHeader reply1, *reply = &reply1; + char cmd[2048]; /* for each stream, make the setup request */ /* XXX: we assume the same server is used for the control of each RTSP stream */ - for(i=0;inb_streams;i++) { + + for(j = RTSP_RTP_PORT_MIN, i = 0; i < rt->nb_rtsp_streams; ++i) { char transport[2048]; - AVInputFormat *fmt; - st = s->streams[i]; - rtsp_st = st->priv_data; + rtsp_st = rt->rtsp_streams[i]; /* compute available transports */ transport[0] = '\0'; /* RTP/UDP */ - if (protocol_mask & (1 << RTSP_PROTOCOL_RTP_UDP)) { + if (protocol == RTSP_PROTOCOL_RTP_UDP) { char buf[256]; - int j; /* first try in specified port range */ - if (rtsp_rtp_port_min != 0) { - for(j=rtsp_rtp_port_min;j<=rtsp_rtp_port_max;j++) { - snprintf(buf, sizeof(buf), "rtp://?localport=%d", j); - if (!av_open_input_file(&rtsp_st->ic, buf, - &rtp_demux, 0, NULL)) + if (RTSP_RTP_PORT_MIN != 0) { + while(j <= RTSP_RTP_PORT_MAX) { + snprintf(buf, sizeof(buf), "rtp://%s?localport=%d", host, j); + j += 2; /* we will use two port by rtp stream (rtp and rtcp) */ + if (url_open(&rtsp_st->rtp_handle, buf, URL_RDWR) == 0) { goto rtp_opened; + } } } - /* then try on any port */ - if (av_open_input_file(&rtsp_st->ic, "rtp://", - &rtp_demux, 0, NULL) < 0) { - err = AVERROR_INVALIDDATA; - goto fail; - } +/* then try on any port +** if (url_open(&rtsp_st->rtp_handle, "rtp://", URL_RDONLY) < 0) { +** err = AVERROR_INVALIDDATA; +** goto fail; +** } +*/ rtp_opened: - port = rtp_get_local_port(url_fileno(&rtsp_st->ic->pb)); + port = rtp_get_local_port(rtsp_st->rtp_handle); if (transport[0] != '\0') - pstrcat(transport, sizeof(transport), ","); + av_strlcat(transport, ",", sizeof(transport)); snprintf(transport + strlen(transport), sizeof(transport) - strlen(transport) - 1, "RTP/AVP/UDP;unicast;client_port=%d-%d", port, port + 1); } /* RTP/TCP */ - if (protocol_mask & (1 << RTSP_PROTOCOL_RTP_TCP)) { + else if (protocol == RTSP_PROTOCOL_RTP_TCP) { if (transport[0] != '\0') - pstrcat(transport, sizeof(transport), ","); + av_strlcat(transport, ",", sizeof(transport)); snprintf(transport + strlen(transport), sizeof(transport) - strlen(transport) - 1, "RTP/AVP/TCP"); } - if (protocol_mask & (1 << RTSP_PROTOCOL_RTP_UDP_MULTICAST)) { + else if (protocol == RTSP_PROTOCOL_RTP_UDP_MULTICAST) { if (transport[0] != '\0') - pstrcat(transport, sizeof(transport), ","); - snprintf(transport + strlen(transport), + av_strlcat(transport, ",", sizeof(transport)); + snprintf(transport + strlen(transport), sizeof(transport) - strlen(transport) - 1, "RTP/AVP/UDP;multicast"); } - snprintf(cmd, sizeof(cmd), + snprintf(cmd, sizeof(cmd), "SETUP %s RTSP/1.0\r\n" "Transport: %s\r\n", rtsp_st->control_url, transport); rtsp_send_cmd(s, cmd, reply, NULL); - if (reply->status_code != RTSP_STATUS_OK || - reply->nb_transports != 1) { + if (reply->status_code == 461 /* Unsupported protocol */ && i == 0) { + err = 1; + goto fail; + } else if (reply->status_code != RTSP_STATUS_OK || + reply->nb_transports != 1) { err = AVERROR_INVALIDDATA; goto fail; } @@ -762,30 +944,25 @@ static int rtsp_read_header(AVFormatContext *s, /* close RTP connection if not choosen */ if (reply->transports[0].protocol != RTSP_PROTOCOL_RTP_UDP && - (protocol_mask & (1 << RTSP_PROTOCOL_RTP_UDP))) { - av_close_input_file(rtsp_st->ic); - rtsp_st->ic = NULL; + (protocol == RTSP_PROTOCOL_RTP_UDP)) { + url_close(rtsp_st->rtp_handle); + rtsp_st->rtp_handle = NULL; } switch(reply->transports[0].protocol) { case RTSP_PROTOCOL_RTP_TCP: - fmt = &rtp_demux; - if (av_open_input_file(&rtsp_st->ic, "null", fmt, 0, NULL) < 0) { - err = AVERROR_INVALIDDATA; - goto fail; - } rtsp_st->interleaved_min = reply->transports[0].interleaved_min; rtsp_st->interleaved_max = reply->transports[0].interleaved_max; break; - + case RTSP_PROTOCOL_RTP_UDP: { char url[1024]; - + /* XXX: also use address if specified */ - snprintf(url, sizeof(url), "rtp://%s:%d", + snprintf(url, sizeof(url), "rtp://%s:%d", host, reply->transports[0].server_port_min); - if (rtp_set_remote_url(url_fileno(&rtsp_st->ic->pb), url) < 0) { + if (rtp_set_remote_url(rtsp_st->rtp_handle, url) < 0) { err = AVERROR_INVALIDDATA; goto fail; } @@ -794,168 +971,236 @@ static int rtsp_read_header(AVFormatContext *s, case RTSP_PROTOCOL_RTP_UDP_MULTICAST: { char url[1024]; - int ttl; - - fmt = &rtp_demux; - ttl = reply->transports[0].ttl; - if (!ttl) - ttl = 16; - snprintf(url, sizeof(url), "rtp://%s:%d?multicast=1&ttl=%d", - host, - reply->transports[0].server_port_min, - ttl); - if (av_open_input_file(&rtsp_st->ic, url, fmt, 0, NULL) < 0) { + struct in_addr in; + + in.s_addr = htonl(reply->transports[0].destination); + snprintf(url, sizeof(url), "rtp://%s:%d?ttl=%d", + inet_ntoa(in), + reply->transports[0].port_min, + reply->transports[0].ttl); + if (url_open(&rtsp_st->rtp_handle, url, URL_RDWR) < 0) { err = AVERROR_INVALIDDATA; goto fail; } } break; } + /* open the RTP context */ + st = NULL; + if (rtsp_st->stream_index >= 0) + st = s->streams[rtsp_st->stream_index]; + if (!st) + s->ctx_flags |= AVFMTCTX_NOHEADER; + rtsp_st->rtp_ctx = rtp_parse_open(s, st, rtsp_st->rtp_handle, rtsp_st->sdp_payload_type, &rtsp_st->rtp_payload_data); + + if (!rtsp_st->rtp_ctx) { + err = AVERROR(ENOMEM); + goto fail; + } else { + if(rtsp_st->dynamic_handler) { + rtsp_st->rtp_ctx->dynamic_protocol_context= rtsp_st->dynamic_protocol_context; + rtsp_st->rtp_ctx->parse_packet= rtsp_st->dynamic_handler->parse_packet; + } + } } - /* use callback if available to extend setup */ - if (ff_rtsp_callback) { - if (ff_rtsp_callback(RTSP_ACTION_CLIENT_SETUP, rt->session_id, - NULL, 0, rt->last_reply) < 0) { - err = AVERROR_INVALIDDATA; - goto fail; + return 0; + +fail: + for (i=0; inb_rtsp_streams; i++) { + if (rt->rtsp_streams[i]->rtp_handle) { + url_close(rt->rtsp_streams[i]->rtp_handle); + rt->rtsp_streams[i]->rtp_handle = NULL; } } - - /* start playing */ - snprintf(cmd, sizeof(cmd), - "PLAY %s RTSP/1.0\r\n" - "Range: npt=0-\r\n", + return err; +} + +static int rtsp_read_header(AVFormatContext *s, + AVFormatParameters *ap) +{ + RTSPState *rt = s->priv_data; + char host[1024], path[1024], tcpname[1024], cmd[2048], *option_list, *option; + URLContext *rtsp_hd; + int port, ret, err; + RTSPHeader reply1, *reply = &reply1; + unsigned char *content = NULL; + int protocol_mask = 0; + + /* extract hostname and port */ + url_split(NULL, 0, NULL, 0, + host, sizeof(host), &port, path, sizeof(path), s->filename); + if (port < 0) + port = RTSP_DEFAULT_PORT; + + /* search for options */ + option_list = strchr(path, '?'); + if (option_list) { + /* remove the options from the path */ + *option_list++ = 0; + while(option_list) { + /* move the option pointer */ + option = option_list; + option_list = strchr(option_list, '&'); + if (option_list) + *(option_list++) = 0; + /* handle the options */ + if (strcmp(option, "udp") == 0) + protocol_mask = (1<< RTSP_PROTOCOL_RTP_UDP); + else if (strcmp(option, "multicast") == 0) + protocol_mask = (1<< RTSP_PROTOCOL_RTP_UDP_MULTICAST); + else if (strcmp(option, "tcp") == 0) + protocol_mask = (1<< RTSP_PROTOCOL_RTP_TCP); + } + } + + if (!protocol_mask) + protocol_mask = (1 << RTSP_PROTOCOL_RTP_LAST) - 1; + + /* open the tcp connexion */ + snprintf(tcpname, sizeof(tcpname), "tcp://%s:%d", host, port); + if (url_open(&rtsp_hd, tcpname, URL_RDWR) < 0) + return AVERROR(EIO); + rt->rtsp_hd = rtsp_hd; + rt->seq = 0; + + /* describe the stream */ + snprintf(cmd, sizeof(cmd), + "DESCRIBE %s RTSP/1.0\r\n" + "Accept: application/sdp\r\n", s->filename); - rtsp_send_cmd(s, cmd, reply, NULL); + rtsp_send_cmd(s, cmd, reply, &content); + if (!content) { + err = AVERROR_INVALIDDATA; + goto fail; + } if (reply->status_code != RTSP_STATUS_OK) { err = AVERROR_INVALIDDATA; goto fail; } -#if 0 - /* open TCP with bufferized input */ - if (rt->protocol == RTSP_PROTOCOL_RTP_TCP) { - if (url_fdopen(&rt->rtsp_gb, rt->rtsp_hd) < 0) { - err = AVERROR_NOMEM; + /* now we got the SDP description, we parse it */ + ret = sdp_parse(s, (const char *)content); + av_freep(&content); + if (ret < 0) { + err = AVERROR_INVALIDDATA; + goto fail; + } + + do { + int protocol = ff_log2_tab[protocol_mask & ~(protocol_mask - 1)]; + + err = make_setup_request(s, host, port, protocol); + if (err < 0) + goto fail; + protocol_mask &= ~(1 << protocol); + if (protocol_mask == 0 && err == 1) { + err = AVERROR(FF_NETERROR(EPROTONOSUPPORT)); goto fail; } - } -#endif + } while (err); - return 0; - fail: - for(i=0;inb_streams;i++) { - st = s->streams[i]; - rtsp_st = st->priv_data; - if (rtsp_st) { - if (rtsp_st->ic) - av_close_input_file(rtsp_st->ic); + rt->state = RTSP_STATE_IDLE; + rt->seek_timestamp = 0; /* default is to start stream at position + zero */ + if (ap->initial_pause) { + /* do not start immediately */ + } else { + if (rtsp_read_play(s) < 0) { + err = AVERROR_INVALIDDATA; + goto fail; } - av_free(rtsp_st); } + return 0; + fail: + rtsp_close_streams(rt); av_freep(&content); url_close(rt->rtsp_hd); return err; } -static int tcp_read_packet(AVFormatContext *s, - AVPacket *pkt) +static int tcp_read_packet(AVFormatContext *s, RTSPStream **prtsp_st, + uint8_t *buf, int buf_size) { RTSPState *rt = s->priv_data; int id, len, i, ret; - AVStream *st; RTSPStream *rtsp_st; - uint8_t buf[RTP_MAX_PACKET_LENGTH]; #ifdef DEBUG_RTP_TCP printf("tcp_read_packet:\n"); #endif redo: for(;;) { - ret = url_read(rt->rtsp_hd, buf, 1); + ret = url_readbuf(rt->rtsp_hd, buf, 1); #ifdef DEBUG_RTP_TCP printf("ret=%d c=%02x [%c]\n", ret, buf[0], buf[0]); #endif if (ret != 1) - return AVERROR_IO; + return -1; if (buf[0] == '$') break; } - ret = url_read(rt->rtsp_hd, buf, 3); + ret = url_readbuf(rt->rtsp_hd, buf, 3); if (ret != 3) - return AVERROR_IO; + return -1; id = buf[0]; - len = (buf[1] << 8) | buf[2]; + len = AV_RB16(buf + 1); #ifdef DEBUG_RTP_TCP printf("id=%d len=%d\n", id, len); #endif - if (len > RTP_MAX_PACKET_LENGTH || len < 12) + if (len > buf_size || len < 12) goto redo; /* get the data */ - ret = url_read(rt->rtsp_hd, buf, len); + ret = url_readbuf(rt->rtsp_hd, buf, len); if (ret != len) - return AVERROR_IO; - + return -1; + /* find the matching stream */ - for(i = 0; i < s->nb_streams; i++) { - st = s->streams[i]; - rtsp_st = st->priv_data; - if (id >= rtsp_st->interleaved_min && - id <= rtsp_st->interleaved_max) + for(i = 0; i < rt->nb_rtsp_streams; i++) { + rtsp_st = rt->rtsp_streams[i]; + if (id >= rtsp_st->interleaved_min && + id <= rtsp_st->interleaved_max) goto found; } goto redo; found: - ret = rtp_parse_packet(rtsp_st->ic, pkt, buf, len); - if (ret < 0) - goto redo; - pkt->stream_index = i; - return ret; + *prtsp_st = rtsp_st; + return len; } -/* NOTE: output one packet at a time. May need to add a small fifo */ -static int udp_read_packet(AVFormatContext *s, - AVPacket *pkt) +static int udp_read_packet(AVFormatContext *s, RTSPStream **prtsp_st, + uint8_t *buf, int buf_size) { - AVFormatContext *ic; - AVStream *st; + RTSPState *rt = s->priv_data; RTSPStream *rtsp_st; fd_set rfds; int fd1, fd2, fd_max, n, i, ret; - char buf[RTP_MAX_PACKET_LENGTH]; struct timeval tv; for(;;) { if (url_interrupt_cb()) - return -EIO; + return AVERROR(EINTR); FD_ZERO(&rfds); fd_max = -1; - for(i = 0; i < s->nb_streams; i++) { - st = s->streams[i]; - rtsp_st = st->priv_data; - ic = rtsp_st->ic; + for(i = 0; i < rt->nb_rtsp_streams; i++) { + rtsp_st = rt->rtsp_streams[i]; /* currently, we cannot probe RTCP handle because of blocking restrictions */ - rtp_get_file_handles(url_fileno(&ic->pb), &fd1, &fd2); + rtp_get_file_handles(rtsp_st->rtp_handle, &fd1, &fd2); if (fd1 > fd_max) fd_max = fd1; FD_SET(fd1, &rfds); } - /* XXX: also add proper API to abort */ tv.tv_sec = 0; tv.tv_usec = 100 * 1000; n = select(fd_max + 1, &rfds, NULL, NULL, &tv); if (n > 0) { - for(i = 0; i < s->nb_streams; i++) { - st = s->streams[i]; - rtsp_st = st->priv_data; - ic = rtsp_st->ic; - rtp_get_file_handles(url_fileno(&ic->pb), &fd1, &fd2); + for(i = 0; i < rt->nb_rtsp_streams; i++) { + rtsp_st = rt->rtsp_streams[i]; + rtp_get_file_handles(rtsp_st->rtp_handle, &fd1, &fd2); if (FD_ISSET(fd1, &rfds)) { - ret = url_read(url_fileno(&ic->pb), buf, sizeof(buf)); - if (ret >= 0 && - rtp_parse_packet(ic, pkt, buf, ret) == 0) { - pkt->stream_index = i; + ret = url_read(rtsp_st->rtp_handle, buf, buf_size); + if (ret > 0) { + *prtsp_st = rtsp_st; return ret; } } @@ -968,73 +1213,126 @@ static int rtsp_read_packet(AVFormatContext *s, AVPacket *pkt) { RTSPState *rt = s->priv_data; - int ret; + RTSPStream *rtsp_st; + int ret, len; + uint8_t buf[RTP_MAX_PACKET_LENGTH]; + + /* get next frames from the same RTP packet */ + if (rt->cur_rtp) { + ret = rtp_parse_packet(rt->cur_rtp, pkt, NULL, 0); + if (ret == 0) { + rt->cur_rtp = NULL; + return 0; + } else if (ret == 1) { + return 0; + } else { + rt->cur_rtp = NULL; + } + } + /* read next RTP packet */ + redo: switch(rt->protocol) { default: case RTSP_PROTOCOL_RTP_TCP: - ret = tcp_read_packet(s, pkt); + len = tcp_read_packet(s, &rtsp_st, buf, sizeof(buf)); break; case RTSP_PROTOCOL_RTP_UDP: - ret = udp_read_packet(s, pkt); + case RTSP_PROTOCOL_RTP_UDP_MULTICAST: + len = udp_read_packet(s, &rtsp_st, buf, sizeof(buf)); + if (len >=0 && rtsp_st->rtp_ctx) + rtp_check_and_send_back_rr(rtsp_st->rtp_ctx, len); break; } - return ret; + if (len < 0) + return len; + ret = rtp_parse_packet(rtsp_st->rtp_ctx, pkt, buf, len); + if (ret < 0) + goto redo; + if (ret == 1) { + /* more packets may follow, so we save the RTP context */ + rt->cur_rtp = rtsp_st->rtp_ctx; + } + return 0; } -/* pause the stream */ -int rtsp_pause(AVFormatContext *s) +static int rtsp_read_play(AVFormatContext *s) { - RTSPState *rt; + RTSPState *rt = s->priv_data; RTSPHeader reply1, *reply = &reply1; char cmd[1024]; - if (s->iformat != &rtsp_demux) - return -1; - - rt = s->priv_data; - - snprintf(cmd, sizeof(cmd), - "PAUSE %s RTSP/1.0\r\n", - s->filename); + av_log(s, AV_LOG_DEBUG, "hello state=%d\n", rt->state); + + if (rt->state == RTSP_STATE_PAUSED) { + snprintf(cmd, sizeof(cmd), + "PLAY %s RTSP/1.0\r\n", + s->filename); + } else { + snprintf(cmd, sizeof(cmd), + "PLAY %s RTSP/1.0\r\n" + "Range: npt=%0.3f-\r\n", + s->filename, + (double)rt->seek_timestamp / AV_TIME_BASE); + } rtsp_send_cmd(s, cmd, reply, NULL); if (reply->status_code != RTSP_STATUS_OK) { return -1; } else { + rt->state = RTSP_STATE_PLAYING; return 0; } } -/* resume the stream */ -int rtsp_resume(AVFormatContext *s) +/* pause the stream */ +static int rtsp_read_pause(AVFormatContext *s) { - RTSPState *rt; + RTSPState *rt = s->priv_data; RTSPHeader reply1, *reply = &reply1; char cmd[1024]; - if (s->iformat != &rtsp_demux) - return -1; - rt = s->priv_data; - - snprintf(cmd, sizeof(cmd), - "PLAY %s RTSP/1.0\r\n", + + if (rt->state != RTSP_STATE_PLAYING) + return 0; + + snprintf(cmd, sizeof(cmd), + "PAUSE %s RTSP/1.0\r\n", s->filename); rtsp_send_cmd(s, cmd, reply, NULL); if (reply->status_code != RTSP_STATUS_OK) { return -1; } else { + rt->state = RTSP_STATE_PAUSED; return 0; } } +static int rtsp_read_seek(AVFormatContext *s, int stream_index, + int64_t timestamp, int flags) +{ + RTSPState *rt = s->priv_data; + + rt->seek_timestamp = av_rescale_q(timestamp, s->streams[stream_index]->time_base, AV_TIME_BASE_Q); + switch(rt->state) { + default: + case RTSP_STATE_IDLE: + break; + case RTSP_STATE_PLAYING: + if (rtsp_read_play(s) != 0) + return -1; + break; + case RTSP_STATE_PAUSED: + rt->state = RTSP_STATE_IDLE; + break; + } + return 0; +} + static int rtsp_read_close(AVFormatContext *s) { RTSPState *rt = s->priv_data; - AVStream *st; - RTSPStream *rtsp_st; RTSPHeader reply1, *reply = &reply1; - int i; char cmd[1024]; #if 0 @@ -1043,30 +1341,18 @@ static int rtsp_read_close(AVFormatContext *s) url_fclose(&rt->rtsp_gb); } #endif - snprintf(cmd, sizeof(cmd), + snprintf(cmd, sizeof(cmd), "TEARDOWN %s RTSP/1.0\r\n", s->filename); rtsp_send_cmd(s, cmd, reply, NULL); - if (ff_rtsp_callback) { - ff_rtsp_callback(RTSP_ACTION_CLIENT_TEARDOWN, rt->session_id, - NULL, 0, NULL); - } - - for(i=0;inb_streams;i++) { - st = s->streams[i]; - rtsp_st = st->priv_data; - if (rtsp_st) { - if (rtsp_st->ic) - av_close_input_file(rtsp_st->ic); - } - av_free(rtsp_st); - } + rtsp_close_streams(rt); url_close(rt->rtsp_hd); return 0; } -AVInputFormat rtsp_demux = { +#ifdef CONFIG_RTSP_DEMUXER +AVInputFormat rtsp_demuxer = { "rtsp", "RTSP input format", sizeof(RTSPState), @@ -1074,22 +1360,25 @@ AVInputFormat rtsp_demux = { rtsp_read_header, rtsp_read_packet, rtsp_read_close, + rtsp_read_seek, .flags = AVFMT_NOFILE, + .read_play = rtsp_read_play, + .read_pause = rtsp_read_pause, }; +#endif static int sdp_probe(AVProbeData *p1) { - const char *p; + const char *p = p1->buf, *p_end = p1->buf + p1->buf_size; /* we look for a line beginning "c=IN IP4" */ - p = p1->buf; - while (*p != '\0') { - if (strstart(p, "c=IN IP4", NULL)) + while (p < p_end && *p != '\0') { + if (p + sizeof("c=IN IP4") - 1 < p_end && av_strstart(p, "c=IN IP4", NULL)) return AVPROBE_SCORE_MAX / 2; - p = strchr(p, '\n'); - if (!p) + + while(p < p_end - 1 && *p != '\n') p++; + if (++p >= p_end) break; - p++; if (*p == '\r') p++; } @@ -1101,16 +1390,17 @@ static int sdp_probe(AVProbeData *p1) static int sdp_read_header(AVFormatContext *s, AVFormatParameters *ap) { - AVStream *st; + RTSPState *rt = s->priv_data; RTSPStream *rtsp_st; int size, i, err; char *content; char url[1024]; + AVStream *st; /* read the whole sdp file */ /* XXX: better loading */ content = av_malloc(SDP_MAX_SIZE); - size = get_buffer(&s->pb, content, SDP_MAX_SIZE - 1); + size = get_buffer(s->pb, content, SDP_MAX_SIZE - 1); if (size <= 0) { av_free(content); return AVERROR_INVALIDDATA; @@ -1121,59 +1411,56 @@ static int sdp_read_header(AVFormatContext *s, av_free(content); /* open each RTP stream */ - for(i=0;inb_streams;i++) { - st = s->streams[i]; - rtsp_st = st->priv_data; - - snprintf(url, sizeof(url), "rtp://%s:%d?multicast=1&ttl=%d", - inet_ntoa(rtsp_st->sdp_ip), + for(i=0;inb_rtsp_streams;i++) { + rtsp_st = rt->rtsp_streams[i]; + + snprintf(url, sizeof(url), "rtp://%s:%d?localport=%d&ttl=%d", + inet_ntoa(rtsp_st->sdp_ip), + rtsp_st->sdp_port, rtsp_st->sdp_port, rtsp_st->sdp_ttl); - if (av_open_input_file(&rtsp_st->ic, url, &rtp_demux, 0, NULL) < 0) { + if (url_open(&rtsp_st->rtp_handle, url, URL_RDWR) < 0) { err = AVERROR_INVALIDDATA; goto fail; } + /* open the RTP context */ + st = NULL; + if (rtsp_st->stream_index >= 0) + st = s->streams[rtsp_st->stream_index]; + if (!st) + s->ctx_flags |= AVFMTCTX_NOHEADER; + rtsp_st->rtp_ctx = rtp_parse_open(s, st, rtsp_st->rtp_handle, rtsp_st->sdp_payload_type, &rtsp_st->rtp_payload_data); + if (!rtsp_st->rtp_ctx) { + err = AVERROR(ENOMEM); + goto fail; + } else { + if(rtsp_st->dynamic_handler) { + rtsp_st->rtp_ctx->dynamic_protocol_context= rtsp_st->dynamic_protocol_context; + rtsp_st->rtp_ctx->parse_packet= rtsp_st->dynamic_handler->parse_packet; + } + } } return 0; fail: - for(i=0;inb_streams;i++) { - st = s->streams[i]; - rtsp_st = st->priv_data; - if (rtsp_st) { - if (rtsp_st->ic) - av_close_input_file(rtsp_st->ic); - } - av_free(rtsp_st); - } + rtsp_close_streams(rt); return err; } static int sdp_read_packet(AVFormatContext *s, AVPacket *pkt) { - return udp_read_packet(s, pkt); + return rtsp_read_packet(s, pkt); } static int sdp_read_close(AVFormatContext *s) { - AVStream *st; - RTSPStream *rtsp_st; - int i; - - for(i=0;inb_streams;i++) { - st = s->streams[i]; - rtsp_st = st->priv_data; - if (rtsp_st) { - if (rtsp_st->ic) - av_close_input_file(rtsp_st->ic); - } - av_free(rtsp_st); - } + RTSPState *rt = s->priv_data; + rtsp_close_streams(rt); return 0; } - -static AVInputFormat sdp_demux = { +#ifdef CONFIG_SDP_DEMUXER +AVInputFormat sdp_demuxer = { "sdp", "SDP", sizeof(RTSPState), @@ -1182,8 +1469,9 @@ static AVInputFormat sdp_demux = { sdp_read_packet, sdp_read_close, }; +#endif - +#ifdef CONFIG_REDIR_DEMUXER /* dummy redirector format (used directly in av_open_input_file now) */ static int redir_probe(AVProbeData *pd) { @@ -1191,18 +1479,18 @@ static int redir_probe(AVProbeData *pd) p = pd->buf; while (redir_isspace(*p)) p++; - if (strstart(p, "http://", NULL) || - strstart(p, "rtsp://", NULL)) + if (av_strstart(p, "http://", NULL) || + av_strstart(p, "rtsp://", NULL)) return AVPROBE_SCORE_MAX; return 0; } -/* called from utils.c */ -int redir_open(AVFormatContext **ic_ptr, ByteIOContext *f) +static int redir_read_header(AVFormatContext *s, AVFormatParameters *ap) { char buf[4096], *q; int c; AVFormatContext *ic = NULL; + ByteIOContext *f = s->pb; /* parse each URL and try to open it */ c = url_fgetc(f); @@ -1230,27 +1518,22 @@ int redir_open(AVFormatContext **ic_ptr, ByteIOContext *f) if (av_open_input_file(&ic, buf, NULL, 0, NULL) == 0) break; } - *ic_ptr = ic; if (!ic) - return AVERROR_IO; - else - return 0; + return AVERROR(EIO); + + *s = *ic; + url_fclose(f); + + return 0; } -AVInputFormat redir_demux = { +AVInputFormat redir_demuxer = { "redir", "Redirector format", 0, redir_probe, - NULL, + redir_read_header, NULL, NULL, }; - -int rtsp_init(void) -{ - av_register_input_format(&rtsp_demux); - av_register_input_format(&redir_demux); - av_register_input_format(&sdp_demux); - return 0; -} +#endif