X-Git-Url: https://git.sesse.net/?a=blobdiff_plain;f=libavformat%2Frtsp.c;h=60f13275e5e7e9dcdc69460061dddf6ea2daf184;hb=ac11d562e51a7764abc20447303ce95225d07a0b;hp=1946d5e458f53105d037d8bdb5ec491a43eea268;hpb=987131828cf7457fe995d1469a3cae9b55b2d70b;p=ffmpeg diff --git a/libavformat/rtsp.c b/libavformat/rtsp.c index 1946d5e458f..60f13275e5e 100644 --- a/libavformat/rtsp.c +++ b/libavformat/rtsp.c @@ -19,9 +19,6 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ -/* needed by inet_aton() */ -#define _SVID_SOURCE - #include "libavutil/base64.h" #include "libavutil/avstring.h" #include "libavutil/intreadwrite.h" @@ -37,18 +34,12 @@ #include "rtpdec.h" #include "rdt.h" -#include "rtp_asf.h" -#include "rtp_vorbis.h" +#include "rtpdec_asf.h" +#include "rtpdec_vorbis.h" //#define DEBUG //#define DEBUG_RTP_TCP -static int tcp_read_packet(AVFormatContext *s, RTSPStream **prtsp_st, - uint8_t *buf, int buf_size); -static int rtsp_read_reply(AVFormatContext *s, RTSPMessageHeader *reply, - unsigned char **content_ptr, - int return_on_interleaved_data); - #if LIBAVFORMAT_VERSION_INT < (53 << 16) int rtsp_default_protocols = (1 << RTSP_LOWER_TRANSPORT_UDP); #endif @@ -97,9 +88,10 @@ static void get_word(char *buf, int buf_size, const char **pp) get_word_until_chars(buf, buf_size, SPACE_CHARS, pp); } -/* parse the rtpmap description: /[/] */ -static int sdp_parse_rtpmap(AVCodecContext *codec, RTSPStream *rtsp_st, int payload_type, const char *p) +/* parse the rtpmap description: /[/] */ +static int sdp_parse_rtpmap(AVFormatContext *s, + AVCodecContext *codec, RTSPStream *rtsp_st, + int payload_type, const char *p) { char buf[256]; int i; @@ -107,23 +99,27 @@ static int sdp_parse_rtpmap(AVCodecContext *codec, RTSPStream *rtsp_st, int payl const char *c_name; /* Loop into AVRtpDynamicPayloadTypes[] and AVRtpPayloadTypes[] and - see if we can handle this kind of payload */ - get_word_sep(buf, sizeof(buf), "/", &p); + * see if we can handle this kind of payload. + * The space should normally not be there but some Real streams or + * particular servers ("RealServer Version 6.1.3.970", see issue 1658) + * have a trailing space. */ + get_word_sep(buf, sizeof(buf), "/ ", &p); if (payload_type >= RTP_PT_PRIVATE) { - RTPDynamicProtocolHandler *handler= RTPFirstDynamicPayloadHandler; - while(handler) { - if (!strcasecmp(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(); - } + RTPDynamicProtocolHandler *handler; + for (handler = RTPFirstDynamicPayloadHandler; + handler; handler = handler->next) { + if (!strcasecmp(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 { - /* We are in a standard case ( from http://www.iana.org/assignments/rtp-parameters) */ + /* 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); } @@ -132,52 +128,52 @@ static int sdp_parse_rtpmap(AVCodecContext *codec, RTSPStream *rtsp_st, int payl 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; + c_name = "(null)"; + + get_word_sep(buf, sizeof(buf), "/", &p); + i = atoi(buf); + switch (codec->codec_type) { + case CODEC_TYPE_AUDIO: + av_log(s, 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. Patch on its way. (rdm) } - return 0; + av_log(s, AV_LOG_DEBUG, "audio samplerate set to: %i\n", + codec->sample_rate); + av_log(s, AV_LOG_DEBUG, "audio channels set to: %i\n", + codec->channels); + break; + case CODEC_TYPE_VIDEO: + av_log(s, AV_LOG_DEBUG, "video codec set to: %s\n", c_name); + break; + default: + break; } - - return -1; + return 0; } -/* return the length and optionnaly the data */ +/* return the length and optionally the data */ static int hex_to_data(uint8_t *data, const char *p) { int c, len, v; len = 0; v = 1; - for(;;) { + for (;;) { skip_spaces(&p); if (*p == '\0') break; - c = toupper((unsigned char)*p++); + c = toupper((unsigned char) *p++); if (c >= '0' && c <= '9') c = c - '0'; else if (c >= 'A' && c <= 'F') @@ -199,33 +195,33 @@ static void sdp_parse_fmtp_config(AVCodecContext * codec, void *ctx, char *attr, char *value) { switch (codec->codec_id) { - case CODEC_ID_MPEG4: - case CODEC_ID_AAC: - if (!strcmp(attr, "config")) { - /* decode the hexa encoded parameter */ - int len = hex_to_data(NULL, value); - if (codec->extradata) - av_free(codec->extradata); - codec->extradata = av_mallocz(len + FF_INPUT_BUFFER_PADDING_SIZE); - if (!codec->extradata) - return; - codec->extradata_size = len; - hex_to_data(codec->extradata, value); - } - break; - case CODEC_ID_VORBIS: - ff_vorbis_parse_fmtp_config(codec, ctx, attr, value); - break; - default: - break; + case CODEC_ID_MPEG4: + case CODEC_ID_AAC: + if (!strcmp(attr, "config")) { + /* decode the hexa encoded parameter */ + int len = hex_to_data(NULL, value); + if (codec->extradata) + av_free(codec->extradata); + codec->extradata = av_mallocz(len + FF_INPUT_BUFFER_PADDING_SIZE); + if (!codec->extradata) + return; + codec->extradata_size = len; + hex_to_data(codec->extradata, value); + } + break; + case CODEC_ID_VORBIS: + ff_vorbis_parse_fmtp_config(codec, ctx, attr, value); + break; + default: + break; } return; } typedef struct { const char *str; - uint16_t type; - uint32_t offset; + uint16_t type; + uint32_t offset; } AttrNameMap; /* All known fmtp parmeters and the corresping RTPAttrTypeEnum */ @@ -233,22 +229,29 @@ typedef struct { #define ATTR_NAME_TYPE_STR 1 static const AttrNameMap attr_names[]= { - {"SizeLength", ATTR_NAME_TYPE_INT, offsetof(RTPPayloadData, sizelength)}, - {"IndexLength", ATTR_NAME_TYPE_INT, offsetof(RTPPayloadData, indexlength)}, - {"IndexDeltaLength", ATTR_NAME_TYPE_INT, offsetof(RTPPayloadData, indexdeltalength)}, - {"profile-level-id", ATTR_NAME_TYPE_INT, offsetof(RTPPayloadData, profile_level_id)}, - {"StreamType", ATTR_NAME_TYPE_INT, offsetof(RTPPayloadData, streamtype)}, - {"mode", ATTR_NAME_TYPE_STR, offsetof(RTPPayloadData, mode)}, - {NULL, -1, -1}, + { "SizeLength", ATTR_NAME_TYPE_INT, + offsetof(RTPPayloadData, sizelength) }, + { "IndexLength", ATTR_NAME_TYPE_INT, + offsetof(RTPPayloadData, indexlength) }, + { "IndexDeltaLength", ATTR_NAME_TYPE_INT, + offsetof(RTPPayloadData, indexdeltalength) }, + { "profile-level-id", ATTR_NAME_TYPE_INT, + offsetof(RTPPayloadData, profile_level_id) }, + { "StreamType", ATTR_NAME_TYPE_INT, + offsetof(RTPPayloadData, streamtype) }, + { "mode", ATTR_NAME_TYPE_STR, + offsetof(RTPPayloadData, 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) +/* 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 ff_rtsp_next_attr_and_value(const char **p, char *attr, int attr_size, + char *value, int value_size) { skip_spaces(p); - if(**p) { + if (**p) { get_word_sep(attr, attr_size, "=", p); if (**p == '=') (*p)++; @@ -268,30 +271,32 @@ static void sdp_parse_fmtp(AVStream *st, const char *p) * encoded, giving a 12KB * (4/3) = 16KB FMTP line. */ char value[16384]; int i; - RTSPStream *rtsp_st = st->priv_data; AVCodecContext *codec = st->codec; RTPPayloadData *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 */ + while (ff_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, rtsp_st->dynamic_protocol_context, 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); + 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); } } } } -/** Parse a string \p in the form of Range:npt=xx-xx, and determine the start +/** 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. */ @@ -320,8 +325,8 @@ static void rtsp_parse_range_npt(const char *p, int64_t *start, int64_t *end) typedef struct SDPParseState { /* SDP only */ struct in_addr default_ip; - int default_ttl; - int skip_media; ///< set if an unknown m= line occurs + int default_ttl; + int skip_media; ///< set if an unknown m= line occurs } SDPParseState; static void sdp_parse_line(AVFormatContext *s, SDPParseState *s1, @@ -342,7 +347,7 @@ static void sdp_parse_line(AVFormatContext *s, SDPParseState *s1, p = buf; if (s1->skip_media && letter != 'm') return; - switch(letter) { + switch (letter) { case 'c': get_word(buf1, sizeof(buf1), &p); if (strcmp(buf1, "IN") != 0) @@ -351,7 +356,7 @@ static void sdp_parse_line(AVFormatContext *s, SDPParseState *s1, if (strcmp(buf1, "IP4") != 0) return; get_word_sep(buf1, sizeof(buf1), "/", &p); - if (inet_aton(buf1, &sdp_ip) == 0) + if (ff_inet_aton(buf1, &sdp_ip) == 0) return; ttl = 16; if (*p == '/') { @@ -425,23 +430,34 @@ static void sdp_parse_line(AVFormatContext *s, SDPParseState *s1, } } /* put a default control url */ - av_strlcpy(rtsp_st->control_url, s->filename, sizeof(rtsp_st->control_url)); + av_strlcpy(rtsp_st->control_url, rt->control_uri, + sizeof(rtsp_st->control_url)); break; case 'a': - if (av_strstart(p, "control:", &p) && s->nb_streams > 0) { + if (av_strstart(p, "control:", &p)) { + if (s->nb_streams == 0) { + if (!strncmp(p, "rtsp://", 7)) + av_strlcpy(rt->control_uri, p, + sizeof(rt->control_uri)); + } else { 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, 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 */ - av_strlcat(rtsp_st->control_url, "/", sizeof(rtsp_st->control_url)); - av_strlcat(rtsp_st->control_url, p, sizeof(rtsp_st->control_url)); - } else { - av_strlcpy(rtsp_st->control_url, p, sizeof(rtsp_st->control_url)); + if (rtsp_st->control_url[strlen(rtsp_st->control_url)-1]!='/') + av_strlcat(rtsp_st->control_url, "/", + sizeof(rtsp_st->control_url)); + av_strlcat(rtsp_st->control_url, p, + sizeof(rtsp_st->control_url)); + } else + av_strlcpy(rtsp_st->control_url, p, + sizeof(rtsp_st->control_url)); } } else if (av_strstart(p, "rtpmap:", &p) && s->nb_streams > 0) { /* NOTE: rtpmap is only supported AFTER the 'm=' tag */ @@ -449,44 +465,44 @@ static void sdp_parse_line(AVFormatContext *s, SDPParseState *s1, payload_type = atoi(buf1); st = s->streams[s->nb_streams - 1]; rtsp_st = st->priv_data; - sdp_parse_rtpmap(st->codec, rtsp_st, payload_type, p); + sdp_parse_rtpmap(s, st->codec, rtsp_st, payload_type, 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); payload_type = atoi(buf1); - for(i = 0; i < s->nb_streams;i++) { - st = s->streams[i]; + 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(s, i, rtsp_st->dynamic_protocol_context, buf)) { - sdp_parse_fmtp(st, p); - } - } else { + if (!(rtsp_st->dynamic_handler && + rtsp_st->dynamic_handler->parse_sdp_a_line && + rtsp_st->dynamic_handler->parse_sdp_a_line(s, + i, rtsp_st->dynamic_protocol_context, buf))) sdp_parse_fmtp(st, p); - } } } - } else if(av_strstart(p, "framesize:", &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]; + 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) { - rtsp_st->dynamic_handler->parse_sdp_a_line(s, i, rtsp_st->dynamic_protocol_context, buf); - } - } + if (rtsp_st->sdp_payload_type == payload_type && + rtsp_st->dynamic_handler && + rtsp_st->dynamic_handler->parse_sdp_a_line) + rtsp_st->dynamic_handler->parse_sdp_a_line(s, i, + rtsp_st->dynamic_protocol_context, buf); } - } else if(av_strstart(p, "range:", &p)) { + } 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) + s->start_time = start; + /* AV_NOPTS_VALUE means live broadcast (and can't seek) */ + s->duration = (end == AV_NOPTS_VALUE) ? + AV_NOPTS_VALUE : end - start; } else if (av_strstart(p, "IsRealDataType:integer;",&p)) { if (atoi(p) == 1) rt->transport = RTSP_TRANSPORT_RDT; @@ -500,7 +516,8 @@ static void sdp_parse_line(AVFormatContext *s, SDPParseState *s1, rtsp_st = s->streams[s->nb_streams - 1]->priv_data; if (rtsp_st->dynamic_handler && rtsp_st->dynamic_handler->parse_sdp_a_line) - rtsp_st->dynamic_handler->parse_sdp_a_line(s, s->nb_streams - 1, + rtsp_st->dynamic_handler->parse_sdp_a_line(s, + s->nb_streams - 1, rtsp_st->dynamic_protocol_context, buf); } } @@ -524,7 +541,7 @@ static int sdp_parse(AVFormatContext *s, const char *content) memset(s1, 0, sizeof(SDPParseState)); p = content; - for(;;) { + for (;;) { skip_spaces(&p); letter = *p; if (letter == '\0') @@ -551,134 +568,26 @@ static int sdp_parse(AVFormatContext *s, const char *content) return 0; } -static int udp_read_packet(AVFormatContext *s, RTSPStream **prtsp_st, - uint8_t *buf, int buf_size) -{ - RTSPState *rt = s->priv_data; - RTSPStream *rtsp_st; - fd_set rfds; - int fd, fd_max, n, i, ret, tcp_fd; - struct timeval tv; - - for(;;) { - if (url_interrupt_cb()) - return AVERROR(EINTR); - FD_ZERO(&rfds); - if (rt->rtsp_hd) { - tcp_fd = fd_max = url_get_file_handle(rt->rtsp_hd); - FD_SET(tcp_fd, &rfds); - } else { - fd_max = 0; - tcp_fd = -1; - } - for(i = 0; i < rt->nb_rtsp_streams; i++) { - rtsp_st = rt->rtsp_streams[i]; - if (rtsp_st->rtp_handle) { - /* currently, we cannot probe RTCP handle because of - * blocking restrictions */ - fd = url_get_file_handle(rtsp_st->rtp_handle); - if (fd > fd_max) - fd_max = fd; - FD_SET(fd, &rfds); - } - } - 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 < rt->nb_rtsp_streams; i++) { - rtsp_st = rt->rtsp_streams[i]; - if (rtsp_st->rtp_handle) { - fd = url_get_file_handle(rtsp_st->rtp_handle); - if (FD_ISSET(fd, &rfds)) { - ret = url_read(rtsp_st->rtp_handle, buf, buf_size); - if (ret > 0) { - *prtsp_st = rtsp_st; - return ret; - } - } - } - } - if (FD_ISSET(tcp_fd, &rfds)) { - RTSPMessageHeader reply; - - rtsp_read_reply(s, &reply, NULL, 0); - /* XXX: parse message */ - if (rt->state != RTSP_STATE_PLAYING) - return 0; - } - } - } -} - -static int sdp_read_packet(AVFormatContext *s, AVPacket *pkt) -{ - RTSPState *rt = s->priv_data; - int ret, len; - uint8_t buf[10 * RTP_MAX_PACKET_LENGTH]; - RTSPStream *rtsp_st; - - /* get next frames from the same RTP packet */ - if (rt->cur_transport_priv) { - if (rt->transport == RTSP_TRANSPORT_RDT) - ret = ff_rdt_parse_packet(rt->cur_transport_priv, pkt, NULL, 0); - else - ret = rtp_parse_packet(rt->cur_transport_priv, pkt, NULL, 0); - if (ret == 0) { - rt->cur_transport_priv = NULL; - return 0; - } else if (ret == 1) { - return 0; - } else { - rt->cur_transport_priv = NULL; - } - } - - /* read next RTP packet */ - redo: - switch(rt->lower_transport) { - default: -#if CONFIG_RTSP_DEMUXER - case RTSP_LOWER_TRANSPORT_TCP: - len = tcp_read_packet(s, &rtsp_st, buf, sizeof(buf)); - break; -#endif - case RTSP_LOWER_TRANSPORT_UDP: - case RTSP_LOWER_TRANSPORT_UDP_MULTICAST: - len = udp_read_packet(s, &rtsp_st, buf, sizeof(buf)); - if (len >=0 && rtsp_st->transport_priv && rt->transport == RTSP_TRANSPORT_RTP) - rtp_check_and_send_back_rr(rtsp_st->transport_priv, len); - break; - } - if (len < 0) - return len; - if (len == 0) - return AVERROR_EOF; - if (rt->transport == RTSP_TRANSPORT_RDT) - ret = ff_rdt_parse_packet(rtsp_st->transport_priv, pkt, buf, len); - else - ret = rtp_parse_packet(rtsp_st->transport_priv, pkt, buf, len); - if (ret < 0) - goto redo; - if (ret == 1) { - /* more packets may follow, so we save the RTP context */ - rt->cur_transport_priv = rtsp_st->transport_priv; - } - - return ret; -} - /* close and free RTSP streams */ -static void rtsp_close_streams(RTSPState *rt) +void ff_rtsp_close_streams(AVFormatContext *s) { + RTSPState *rt = s->priv_data; int i; RTSPStream *rtsp_st; - for(i=0;inb_rtsp_streams;i++) { + for (i = 0; i < rt->nb_rtsp_streams; i++) { rtsp_st = rt->rtsp_streams[i]; if (rtsp_st) { if (rtsp_st->transport_priv) { - if (rt->transport == RTSP_TRANSPORT_RDT) + if (s->oformat) { + AVFormatContext *rtpctx = rtsp_st->transport_priv; + av_write_trailer(rtpctx); + url_fclose(rtpctx->pb); + av_metadata_free(&rtpctx->streams[0]->metadata); + av_metadata_free(&rtpctx->metadata); + av_free(rtpctx->streams[0]); + av_free(rtpctx); + } else if (rt->transport == RTSP_TRANSPORT_RDT) ff_rdt_parse_close(rtsp_st->transport_priv); else rtp_parse_close(rtsp_st->transport_priv); @@ -686,7 +595,8 @@ static void rtsp_close_streams(RTSPState *rt) 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); + rtsp_st->dynamic_handler->close( + rtsp_st->dynamic_protocol_context); } } av_free(rt->rtsp_streams); @@ -697,8 +607,51 @@ static void rtsp_close_streams(RTSPState *rt) av_freep(&rt->auth_b64); } -static int -rtsp_open_transport_ctx(AVFormatContext *s, RTSPStream *rtsp_st) +static void *rtsp_rtp_mux_open(AVFormatContext *s, AVStream *st, URLContext *handle) { + AVFormatContext *rtpctx; + int ret; + AVOutputFormat *rtp_format = av_guess_format("rtp", NULL, NULL); + + if (!rtp_format) + return NULL; + + /* Allocate an AVFormatContext for each output stream */ + rtpctx = avformat_alloc_context(); + if (!rtpctx) + return NULL; + + rtpctx->oformat = rtp_format; + if (!av_new_stream(rtpctx, 0)) { + av_free(rtpctx); + return NULL; + } + /* Copy the max delay setting; the rtp muxer reads this. */ + rtpctx->max_delay = s->max_delay; + /* Copy other stream parameters. */ + rtpctx->streams[0]->sample_aspect_ratio = st->sample_aspect_ratio; + + /* Remove the local codec, link to the original codec + * context instead, to give the rtp muxer access to + * codec parameters. */ + av_free(rtpctx->streams[0]->codec); + rtpctx->streams[0]->codec = st->codec; + + url_fdopen(&rtpctx->pb, handle); + ret = av_write_header(rtpctx); + + if (ret) { + url_fclose(rtpctx->pb); + av_free(rtpctx->streams[0]); + av_free(rtpctx); + return NULL; + } + + /* Copy the RTP AVStream timebase back to the original AVStream */ + st->time_base = rtpctx->streams[0]->time_base; + return rtpctx; +} + +static int rtsp_open_transport_ctx(AVFormatContext *s, RTSPStream *rtsp_st) { RTSPState *rt = s->priv_data; AVStream *st = NULL; @@ -709,7 +662,11 @@ rtsp_open_transport_ctx(AVFormatContext *s, RTSPStream *rtsp_st) if (!st) s->ctx_flags |= AVFMTCTX_NOHEADER; - if (rt->transport == RTSP_TRANSPORT_RDT) + if (s->oformat) { + rtsp_st->transport_priv = rtsp_rtp_mux_open(s, st, rtsp_st->rtp_handle); + /* Ownage of rtp_handle is passed to the rtp mux context */ + rtsp_st->rtp_handle = NULL; + } else if (rt->transport == RTSP_TRANSPORT_RDT) rtsp_st->transport_priv = ff_rdt_parse_open(s, st->index, rtsp_st->dynamic_protocol_context, rtsp_st->dynamic_handler); @@ -721,7 +678,7 @@ rtsp_open_transport_ctx(AVFormatContext *s, RTSPStream *rtsp_st) if (!rtsp_st->transport_priv) { return AVERROR(ENOMEM); } else if (rt->transport != RTSP_TRANSPORT_RDT) { - if(rtsp_st->dynamic_handler) { + if (rtsp_st->dynamic_handler) { rtp_parse_set_dynamic_protocol(rtsp_st->transport_priv, rtsp_st->dynamic_protocol_context, rtsp_st->dynamic_handler); @@ -731,6 +688,7 @@ rtsp_open_transport_ctx(AVFormatContext *s, RTSPStream *rtsp_st) return 0; } +#if CONFIG_RTSP_DEMUXER || CONFIG_RTSP_MUXER static int rtsp_probe(AVProbeData *p) { if (av_strstart(p->filename, "rtsp:", NULL)) @@ -770,7 +728,7 @@ static void rtsp_parse_transport(RTSPMessageHeader *reply, const char *p) reply->nb_transports = 0; - for(;;) { + for (;;) { skip_spaces(&p); if (*p == '\0') break; @@ -842,7 +800,7 @@ static void rtsp_parse_transport(RTSPMessageHeader *reply, const char *p) if (*p == '=') { p++; get_word_sep(buf, sizeof(buf), ";,", &p); - if (inet_aton(buf, &ipaddr)) + if (ff_inet_aton(buf, &ipaddr)) th->destination = ntohl(ipaddr.s_addr); } } @@ -858,7 +816,7 @@ static void rtsp_parse_transport(RTSPMessageHeader *reply, const char *p) } } -void rtsp_parse_line(RTSPMessageHeader *reply, const char *buf) +void ff_rtsp_parse_line(RTSPMessageHeader *reply, const char *buf) { const char *p; @@ -920,30 +878,9 @@ static void rtsp_skip_packet(AVFormatContext *s) } } -/** - * Read a RTSP message from the server, or prepare to read data - * packets if we're reading data interleaved over the TCP/RTSP - * connection as well. - * - * @param s RTSP demuxer context - * @param reply pointer where the RTSP message header will be stored - * @param content_ptr pointer where the RTSP message body, if any, will - * be stored (length is in \p reply) - * @param return_on_interleaved_data whether the function may return if we - * encounter a data marker ('$'), which precedes data - * packets over interleaved TCP/RTSP connections. If this - * is set, this function will return 1 after encountering - * a '$'. If it is not set, the function will skip any - * data packets (if they are encountered), until a reply - * has been fully parsed. If no more data is available - * without parsing a reply, it will return an error. - * - * @returns 1 if a data packets is ready to be received, -1 on error, - * and 0 on success. - */ -static int -rtsp_read_reply (AVFormatContext *s, RTSPMessageHeader *reply, - unsigned char **content_ptr, int return_on_interleaved_data) +int ff_rtsp_read_reply(AVFormatContext *s, RTSPMessageHeader *reply, + unsigned char **content_ptr, + int return_on_interleaved_data) { RTSPState *rt = s->priv_data; char buf[4096], buf1[1024], *q; @@ -956,9 +893,9 @@ rtsp_read_reply (AVFormatContext *s, RTSPMessageHeader *reply, /* parse reply (XXX: use buffers) */ rt->last_reply[0] = '\0'; - for(;;) { + for (;;) { q = buf; - for(;;) { + for (;;) { ret = url_read_complete(rt->rtsp_hd, &ch, 1); #ifdef DEBUG_RTP_TCP dprintf(s, "ret=%d c=%02x [%c]\n", ret, ch, ch); @@ -992,7 +929,7 @@ rtsp_read_reply (AVFormatContext *s, RTSPMessageHeader *reply, get_word(buf1, sizeof(buf1), &p); reply->status_code = atoi(buf1); } else { - rtsp_parse_line(reply, p); + ff_rtsp_parse_line(reply, p); av_strlcat(rt->last_reply, p, sizeof(rt->last_reply)); av_strlcat(rt->last_reply, "\n", sizeof(rt->last_reply)); } @@ -1017,20 +954,21 @@ rtsp_read_reply (AVFormatContext *s, RTSPMessageHeader *reply, /* EOS */ if (reply->notice == 2101 /* End-of-Stream Reached */ || reply->notice == 2104 /* Start-of-Stream Reached */ || - reply->notice == 2306 /* Continuous Feed Terminated */) + reply->notice == 2306 /* Continuous Feed Terminated */) { rt->state = RTSP_STATE_IDLE; - else if (reply->notice >= 4400 && reply->notice < 5500) + } else if (reply->notice >= 4400 && reply->notice < 5500) { return AVERROR(EIO); /* data or server error */ - else if (reply->notice == 2401 /* Ticket Expired */ || + } else if (reply->notice == 2401 /* Ticket Expired */ || (reply->notice >= 5500 && reply->notice < 5600) /* end of term */ ) return AVERROR(EPERM); return 0; } -static void rtsp_send_cmd_async (AVFormatContext *s, - const char *cmd, RTSPMessageHeader *reply, - unsigned char **content_ptr) +void ff_rtsp_send_cmd_with_content_async(AVFormatContext *s, + const char *cmd, + const unsigned char *send_content, + int send_content_length) { RTSPState *rt = s->priv_data; char buf[4096], buf1[1024]; @@ -1047,29 +985,49 @@ static void rtsp_send_cmd_async (AVFormatContext *s, av_strlcatf(buf, sizeof(buf), "Authorization: Basic %s\r\n", rt->auth_b64); + if (send_content_length > 0 && send_content) + av_strlcatf(buf, sizeof(buf), "Content-Length: %d\r\n", send_content_length); av_strlcat(buf, "\r\n", sizeof(buf)); dprintf(s, "Sending:\n%s--\n", buf); url_write(rt->rtsp_hd, buf, strlen(buf)); + if (send_content_length > 0 && send_content) + url_write(rt->rtsp_hd, send_content, send_content_length); rt->last_cmd_time = av_gettime(); } -static void rtsp_send_cmd (AVFormatContext *s, - const char *cmd, RTSPMessageHeader *reply, - unsigned char **content_ptr) +void ff_rtsp_send_cmd_async(AVFormatContext *s, const char *cmd) +{ + ff_rtsp_send_cmd_with_content_async(s, cmd, NULL, 0); +} + +void ff_rtsp_send_cmd(AVFormatContext *s, + const char *cmd, RTSPMessageHeader *reply, + unsigned char **content_ptr) +{ + ff_rtsp_send_cmd_async(s, cmd); + + ff_rtsp_read_reply(s, reply, content_ptr, 0); +} + +void ff_rtsp_send_cmd_with_content(AVFormatContext *s, + const char *cmd, + RTSPMessageHeader *reply, + unsigned char **content_ptr, + const unsigned char *send_content, + int send_content_length) { - rtsp_send_cmd_async(s, cmd, reply, content_ptr); + ff_rtsp_send_cmd_with_content_async(s, cmd, send_content, send_content_length); - rtsp_read_reply(s, reply, content_ptr, 0); + ff_rtsp_read_reply(s, reply, content_ptr, 0); } /** * @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 lower_transport, const char *real_challenge) +static int make_setup_request(AVFormatContext *s, const char *host, int port, + int lower_transport, const char *real_challenge) { RTSPState *rt = s->priv_data; int rtx, j, i, err, interleave = 0; @@ -1088,9 +1046,9 @@ make_setup_request (AVFormatContext *s, const char *host, int port, /* for each stream, make the setup request */ /* XXX: we assume the same server is used for the control of each - RTSP stream */ + * RTSP stream */ - for(j = RTSP_RTP_PORT_MIN, i = 0; i < rt->nb_rtsp_streams; ++i) { + for (j = RTSP_RTP_PORT_MIN, i = 0; i < rt->nb_rtsp_streams; ++i) { char transport[2048]; /** @@ -1105,7 +1063,8 @@ make_setup_request (AVFormatContext *s, const char *host, int port, for (rtx = 0; rtx < rt->nb_rtsp_streams; rtx++) { int len = strlen(rt->rtsp_streams[rtx]->control_url); if (len >= 4 && - !strcmp(rt->rtsp_streams[rtx]->control_url + len - 4, "/rtx")) + !strcmp(rt->rtsp_streams[rtx]->control_url + len - 4, + "/rtx")) break; } if (rtx == rt->nb_rtsp_streams) @@ -1127,21 +1086,23 @@ make_setup_request (AVFormatContext *s, const char *host, int port, /* first try in specified port range */ 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) { + while (j <= RTSP_RTP_PORT_MAX) { + ff_url_join(buf, sizeof(buf), "rtp", NULL, host, -1, + "?localport=%d", j); + /* we will use two ports per rtp stream (rtp and rtcp) */ + j += 2; + if (url_open(&rtsp_st->rtp_handle, buf, URL_RDWR) == 0) goto rtp_opened; - } } } -/* then try on any port -** if (url_open(&rtsp_st->rtp_handle, "rtp://", URL_RDONLY) < 0) { -** err = AVERROR_INVALIDDATA; -** goto fail; -** } -*/ +#if 0 + /* then try on any port */ + if (url_open(&rtsp_st->rtp_handle, "rtp://", URL_RDONLY) < 0) { + err = AVERROR_INVALIDDATA; + goto fail; + } +#endif rtp_opened: port = rtp_get_local_port(rtsp_st->rtp_handle); @@ -1163,7 +1124,8 @@ make_setup_request (AVFormatContext *s, const char *host, int port, * UDP. When trying to set it up for TCP streams, the server * will return an error. Therefore, we skip those streams. */ if (rt->server_type == RTSP_SERVER_WMS && - s->streams[rtsp_st->stream_index]->codec->codec_type == CODEC_TYPE_DATA) + s->streams[rtsp_st->stream_index]->codec->codec_type == + CODEC_TYPE_DATA) continue; snprintf(transport, sizeof(transport) - 1, "%s/TCP;", trans_pref); @@ -1179,8 +1141,10 @@ make_setup_request (AVFormatContext *s, const char *host, int port, snprintf(transport, sizeof(transport) - 1, "%s/UDP;multicast", trans_pref); } - if (rt->server_type == RTSP_SERVER_REAL || - rt->server_type == RTSP_SERVER_WMS) + if (s->oformat) { + av_strlcat(transport, ";mode=receive", sizeof(transport)); + } else if (rt->server_type == RTSP_SERVER_REAL || + rt->server_type == RTSP_SERVER_WMS) av_strlcat(transport, ";mode=play", sizeof(transport)); snprintf(cmd, sizeof(cmd), "SETUP %s RTSP/1.0\r\n" @@ -1195,7 +1159,7 @@ make_setup_request (AVFormatContext *s, const char *host, int port, "RealChallenge2: %s, sd=%s\r\n", rt->session_id, real_res, real_csum); } - rtsp_send_cmd(s, cmd, reply, NULL); + ff_rtsp_send_cmd(s, cmd, reply, NULL); if (reply->status_code == 461 /* Unsupported protocol */ && i == 0) { err = 1; goto fail; @@ -1230,44 +1194,48 @@ make_setup_request (AVFormatContext *s, const char *host, int port, rtsp_st->interleaved_max = reply->transports[0].interleaved_max; break; - case RTSP_LOWER_TRANSPORT_UDP: - { - char url[1024]; - - /* XXX: also use address if specified */ - snprintf(url, sizeof(url), "rtp://%s:%d", - host, reply->transports[0].server_port_min); - if (!(rt->server_type == RTSP_SERVER_WMS && i > 1) && - rtp_set_remote_url(rtsp_st->rtp_handle, url) < 0) { - err = AVERROR_INVALIDDATA; - goto fail; - } + case RTSP_LOWER_TRANSPORT_UDP: { + char url[1024]; + + /* XXX: also use address if specified */ + ff_url_join(url, sizeof(url), "rtp", NULL, host, + reply->transports[0].server_port_min, NULL); + if (!(rt->server_type == RTSP_SERVER_WMS && i > 1) && + rtp_set_remote_url(rtsp_st->rtp_handle, url) < 0) { + err = AVERROR_INVALIDDATA; + goto fail; } + /* Try to initialize the connection state in a + * potential NAT router by sending dummy packets. + * RTP/RTCP dummy packets are used for RDT, too. + */ + if (!(rt->server_type == RTSP_SERVER_WMS && i > 1) && s->iformat) + rtp_send_punch_packets(rtsp_st->rtp_handle); break; - case RTSP_LOWER_TRANSPORT_UDP_MULTICAST: - { - char url[1024]; - struct in_addr in; - int port, ttl; - - if (reply->transports[0].destination) { - in.s_addr = htonl(reply->transports[0].destination); - port = reply->transports[0].port_min; - ttl = reply->transports[0].ttl; - } else { - in = rtsp_st->sdp_ip; - port = rtsp_st->sdp_port; - ttl = rtsp_st->sdp_ttl; - } - snprintf(url, sizeof(url), "rtp://%s:%d?ttl=%d", - inet_ntoa(in), port, ttl); - if (url_open(&rtsp_st->rtp_handle, url, URL_RDWR) < 0) { - err = AVERROR_INVALIDDATA; - goto fail; - } + } + case RTSP_LOWER_TRANSPORT_UDP_MULTICAST: { + char url[1024]; + struct in_addr in; + int port, ttl; + + if (reply->transports[0].destination) { + in.s_addr = htonl(reply->transports[0].destination); + port = reply->transports[0].port_min; + ttl = reply->transports[0].ttl; + } else { + in = rtsp_st->sdp_ip; + port = rtsp_st->sdp_port; + ttl = rtsp_st->sdp_ttl; + } + ff_url_join(url, sizeof(url), "rtp", NULL, inet_ntoa(in), + port, "?ttl=%d", ttl); + if (url_open(&rtsp_st->rtp_handle, url, URL_RDWR) < 0) { + err = AVERROR_INVALIDDATA; + goto fail; } break; } + } if ((err = rtsp_open_transport_ctx(s, rtsp_st))) goto fail; @@ -1282,7 +1250,7 @@ make_setup_request (AVFormatContext *s, const char *host, int port, return 0; fail: - for (i=0; inb_rtsp_streams; i++) { + for (i = 0; i < rt->nb_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; @@ -1303,36 +1271,124 @@ static int rtsp_read_play(AVFormatContext *s) if (rt->state == RTSP_STATE_PAUSED) { snprintf(cmd, sizeof(cmd), "PLAY %s RTSP/1.0\r\n", - s->filename); + rt->control_uri); } else { snprintf(cmd, sizeof(cmd), "PLAY %s RTSP/1.0\r\n" "Range: npt=%0.3f-\r\n", - s->filename, + rt->control_uri, (double)rt->seek_timestamp / AV_TIME_BASE); } - rtsp_send_cmd(s, cmd, reply, NULL); + ff_rtsp_send_cmd(s, cmd, reply, NULL); if (reply->status_code != RTSP_STATUS_OK) { return -1; } } - rt->state = RTSP_STATE_PLAYING; + rt->state = RTSP_STATE_STREAMING; return 0; } -static int rtsp_read_header(AVFormatContext *s, - AVFormatParameters *ap) +static int rtsp_setup_input_streams(AVFormatContext *s) +{ + RTSPState *rt = s->priv_data; + RTSPMessageHeader reply1, *reply = &reply1; + char cmd[1024]; + unsigned char *content = NULL; + int ret; + + /* describe the stream */ + snprintf(cmd, sizeof(cmd), + "DESCRIBE %s RTSP/1.0\r\n" + "Accept: application/sdp\r\n", + s->filename); + if (rt->server_type == RTSP_SERVER_REAL) { + /** + * The Require: attribute is needed for proper streaming from + * Realmedia servers. + */ + av_strlcat(cmd, + "Require: com.real.retain-entity-for-setup\r\n", + sizeof(cmd)); + } + ff_rtsp_send_cmd(s, cmd, reply, &content); + if (!content) + return AVERROR_INVALIDDATA; + if (reply->status_code != RTSP_STATUS_OK) { + av_freep(&content); + return AVERROR_INVALIDDATA; + } + + /* now we got the SDP description, we parse it */ + ret = sdp_parse(s, (const char *)content); + av_freep(&content); + if (ret < 0) + return AVERROR_INVALIDDATA; + + return 0; +} + +static int rtsp_setup_output_streams(AVFormatContext *s) +{ + RTSPState *rt = s->priv_data; + RTSPMessageHeader reply1, *reply = &reply1; + char cmd[1024]; + int i; + char *sdp; + + /* Announce the stream */ + snprintf(cmd, sizeof(cmd), + "ANNOUNCE %s RTSP/1.0\r\n" + "Content-Type: application/sdp\r\n", + s->filename); + sdp = av_mallocz(8192); + if (sdp == NULL) + return AVERROR(ENOMEM); + if (avf_sdp_create(&s, 1, sdp, 8192)) { + av_free(sdp); + return AVERROR_INVALIDDATA; + } + av_log(s, AV_LOG_INFO, "SDP:\n%s\n", sdp); + ff_rtsp_send_cmd_with_content(s, cmd, reply, NULL, sdp, strlen(sdp)); + av_free(sdp); + if (reply->status_code != RTSP_STATUS_OK) + return AVERROR_INVALIDDATA; + + /* Set up the RTSPStreams for each AVStream */ + for (i = 0; i < s->nb_streams; i++) { + RTSPStream *rtsp_st; + AVStream *st = s->streams[i]; + + rtsp_st = av_mallocz(sizeof(RTSPStream)); + if (!rtsp_st) + return AVERROR(ENOMEM); + dynarray_add(&rt->rtsp_streams, &rt->nb_rtsp_streams, rtsp_st); + + st->priv_data = rtsp_st; + rtsp_st->stream_index = i; + + av_strlcpy(rtsp_st->control_url, s->filename, sizeof(rtsp_st->control_url)); + /* Note, this must match the relative uri set in the sdp content */ + av_strlcatf(rtsp_st->control_url, sizeof(rtsp_st->control_url), + "/streamid=%d", i); + } + + return 0; +} + +int ff_rtsp_connect(AVFormatContext *s) { RTSPState *rt = s->priv_data; char host[1024], path[1024], tcpname[1024], cmd[2048], auth[128]; char *option_list, *option, *filename; URLContext *rtsp_hd; - int port, ret, err; + int port, err; RTSPMessageHeader reply1, *reply = &reply1; - unsigned char *content = NULL; int lower_transport_mask = 0; char real_challenge[64]; - redirect: + + if (!ff_network_init()) + return AVERROR(EIO); +redirect: /* extract hostname and port */ url_split(NULL, 0, auth, sizeof(auth), host, sizeof(host), &port, path, sizeof(path), s->filename); @@ -1353,7 +1409,7 @@ static int rtsp_read_header(AVFormatContext *s, option_list = strchr(path, '?'); if (option_list) { filename = strchr(s->filename, '?'); - while(option_list) { + while (option_list) { /* move the option pointer */ option = ++option_list; option_list = strchr(option_list, '&'); @@ -1361,13 +1417,13 @@ static int rtsp_read_header(AVFormatContext *s, *option_list = 0; /* handle the options */ - if (strcmp(option, "udp") == 0) + if (!strcmp(option, "udp")) { lower_transport_mask = (1<< RTSP_LOWER_TRANSPORT_UDP); - else if (strcmp(option, "multicast") == 0) + } else if (!strcmp(option, "multicast")) { lower_transport_mask = (1<< RTSP_LOWER_TRANSPORT_UDP_MULTICAST); - else if (strcmp(option, "tcp") == 0) + } else if (!strcmp(option, "tcp")) { lower_transport_mask = (1<< RTSP_LOWER_TRANSPORT_TCP); - else { + } else { strcpy(++filename, option); filename += strlen(option); if (option_list) *filename = '&'; @@ -1379,8 +1435,19 @@ static int rtsp_read_header(AVFormatContext *s, if (!lower_transport_mask) lower_transport_mask = (1 << RTSP_LOWER_TRANSPORT_NB) - 1; + if (s->oformat) { + /* Only UDP output is supported at the moment. */ + lower_transport_mask &= 1 << RTSP_LOWER_TRANSPORT_UDP; + if (!lower_transport_mask) { + av_log(s, AV_LOG_ERROR, "Unsupported lower transport method, " + "only UDP is supported for output.\n"); + err = AVERROR(EINVAL); + goto fail; + } + } + /* open the tcp connexion */ - snprintf(tcpname, sizeof(tcpname), "tcp://%s:%d", host, port); + ff_url_join(tcpname, sizeof(tcpname), "tcp", NULL, host, port, NULL); if (url_open(&rtsp_hd, tcpname, URL_RDWR) < 0) { err = AVERROR(EIO); goto fail; @@ -1388,7 +1455,10 @@ static int rtsp_read_header(AVFormatContext *s, rt->rtsp_hd = rtsp_hd; rt->seq = 0; - /* request options supported by the server; this also detects server type */ + /* request options supported by the server; this also detects server + * type */ + av_strlcpy(rt->control_uri, s->filename, + sizeof(rt->control_uri)); for (rt->server_type = RTSP_SERVER_RTP;;) { snprintf(cmd, sizeof(cmd), "OPTIONS %s RTSP/1.0\r\n", s->filename); @@ -1408,7 +1478,7 @@ static int rtsp_read_header(AVFormatContext *s, "CompanyID: KnKV4M4I/B2FjJ1TToLycw==\r\n" "GUID: 00000000-0000-0000-0000-000000000000\r\n", sizeof(cmd)); - rtsp_send_cmd(s, cmd, reply, NULL); + ff_rtsp_send_cmd(s, cmd, reply, NULL); if (reply->status_code != RTSP_STATUS_OK) { err = AVERROR_INVALIDDATA; goto fail; @@ -1420,46 +1490,21 @@ static int rtsp_read_header(AVFormatContext *s, continue; } else if (!strncasecmp(reply->server, "WMServer/", 9)) { rt->server_type = RTSP_SERVER_WMS; - } else if (rt->server_type == RTSP_SERVER_REAL) { + } else if (rt->server_type == RTSP_SERVER_REAL) strcpy(real_challenge, reply->real_challenge); - } break; } - /* describe the stream */ - snprintf(cmd, sizeof(cmd), - "DESCRIBE %s RTSP/1.0\r\n" - "Accept: application/sdp\r\n", - s->filename); - if (rt->server_type == RTSP_SERVER_REAL) { - /** - * The Require: attribute is needed for proper streaming from - * Realmedia servers. - */ - av_strlcat(cmd, - "Require: com.real.retain-entity-for-setup\r\n", - sizeof(cmd)); - } - 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; + if (s->iformat) + err = rtsp_setup_input_streams(s); + else + err = rtsp_setup_output_streams(s); + if (err) goto fail; - } do { - int lower_transport = ff_log2_tab[lower_transport_mask & ~(lower_transport_mask - 1)]; + int lower_transport = ff_log2_tab[lower_transport_mask & + ~(lower_transport_mask - 1)]; err = make_setup_request(s, host, port, lower_transport, rt->server_type == RTSP_SERVER_REAL ? @@ -1474,30 +1519,108 @@ static int rtsp_read_header(AVFormatContext *s, } while (err); 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; - } - } + rt->seek_timestamp = 0; /* default is to start stream at position zero */ return 0; fail: - rtsp_close_streams(rt); - av_freep(&content); + ff_rtsp_close_streams(s); url_close(rt->rtsp_hd); - if (reply->status_code >=300 && reply->status_code < 400) { + if (reply->status_code >=300 && reply->status_code < 400 && s->iformat) { av_strlcpy(s->filename, reply->location, sizeof(s->filename)); av_log(s, AV_LOG_INFO, "Status %d: Redirecting to %s\n", reply->status_code, s->filename); goto redirect; } + ff_network_close(); return err; } +#endif + +#if CONFIG_RTSP_DEMUXER +static int rtsp_read_header(AVFormatContext *s, + AVFormatParameters *ap) +{ + RTSPState *rt = s->priv_data; + int ret; + + ret = ff_rtsp_connect(s); + if (ret) + return ret; + + if (ap->initial_pause) { + /* do not start immediately */ + } else { + if (rtsp_read_play(s) < 0) { + ff_rtsp_close_streams(s); + url_close(rt->rtsp_hd); + return AVERROR_INVALIDDATA; + } + } + + return 0; +} + +static int udp_read_packet(AVFormatContext *s, RTSPStream **prtsp_st, + uint8_t *buf, int buf_size) +{ + RTSPState *rt = s->priv_data; + RTSPStream *rtsp_st; + fd_set rfds; + int fd, fd_max, n, i, ret, tcp_fd; + struct timeval tv; + + for (;;) { + if (url_interrupt_cb()) + return AVERROR(EINTR); + FD_ZERO(&rfds); + if (rt->rtsp_hd) { + tcp_fd = fd_max = url_get_file_handle(rt->rtsp_hd); + FD_SET(tcp_fd, &rfds); + } else { + fd_max = 0; + tcp_fd = -1; + } + for (i = 0; i < rt->nb_rtsp_streams; i++) { + rtsp_st = rt->rtsp_streams[i]; + if (rtsp_st->rtp_handle) { + /* currently, we cannot probe RTCP handle because of + * blocking restrictions */ + fd = url_get_file_handle(rtsp_st->rtp_handle); + if (fd > fd_max) + fd_max = fd; + FD_SET(fd, &rfds); + } + } + 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 < rt->nb_rtsp_streams; i++) { + rtsp_st = rt->rtsp_streams[i]; + if (rtsp_st->rtp_handle) { + fd = url_get_file_handle(rtsp_st->rtp_handle); + if (FD_ISSET(fd, &rfds)) { + ret = url_read(rtsp_st->rtp_handle, buf, buf_size); + if (ret > 0) { + *prtsp_st = rtsp_st; + return ret; + } + } + } + } +#if CONFIG_RTSP_DEMUXER + if (tcp_fd != -1 && FD_ISSET(tcp_fd, &rfds)) { + RTSPMessageHeader reply; + + ff_rtsp_read_reply(s, &reply, NULL, 0); + /* XXX: parse message */ + if (rt->state != RTSP_STATE_STREAMING) + return 0; + } +#endif + } + } +} static int tcp_read_packet(AVFormatContext *s, RTSPStream **prtsp_st, uint8_t *buf, int buf_size) @@ -1509,23 +1632,23 @@ static int tcp_read_packet(AVFormatContext *s, RTSPStream **prtsp_st, #ifdef DEBUG_RTP_TCP dprintf(s, "tcp_read_packet:\n"); #endif - redo: - for(;;) { +redo: + for (;;) { RTSPMessageHeader reply; - ret = rtsp_read_reply(s, &reply, NULL, 1); + ret = ff_rtsp_read_reply(s, &reply, NULL, 1); if (ret == -1) return -1; if (ret == 1) /* received '$' */ break; /* XXX: parse message */ - if (rt->state != RTSP_STATE_PLAYING) + if (rt->state != RTSP_STATE_STREAMING) return 0; } ret = url_read_complete(rt->rtsp_hd, buf, 3); if (ret != 3) return -1; - id = buf[0]; + id = buf[0]; len = AV_RB16(buf + 1); #ifdef DEBUG_RTP_TCP dprintf(s, "id=%d len=%d\n", id, len); @@ -1541,20 +1664,74 @@ static int tcp_read_packet(AVFormatContext *s, RTSPStream **prtsp_st, return -1; /* find the matching stream */ - for(i = 0; i < rt->nb_rtsp_streams; i++) { + 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: +found: *prtsp_st = rtsp_st; return len; } -static int rtsp_read_packet(AVFormatContext *s, - AVPacket *pkt) +static int rtsp_fetch_packet(AVFormatContext *s, AVPacket *pkt) +{ + RTSPState *rt = s->priv_data; + int ret, len; + uint8_t buf[10 * RTP_MAX_PACKET_LENGTH]; + RTSPStream *rtsp_st; + + /* get next frames from the same RTP packet */ + if (rt->cur_transport_priv) { + if (rt->transport == RTSP_TRANSPORT_RDT) { + ret = ff_rdt_parse_packet(rt->cur_transport_priv, pkt, NULL, 0); + } else + ret = rtp_parse_packet(rt->cur_transport_priv, pkt, NULL, 0); + if (ret == 0) { + rt->cur_transport_priv = NULL; + return 0; + } else if (ret == 1) { + return 0; + } else + rt->cur_transport_priv = NULL; + } + + /* read next RTP packet */ + redo: + switch(rt->lower_transport) { + default: +#if CONFIG_RTSP_DEMUXER + case RTSP_LOWER_TRANSPORT_TCP: + len = tcp_read_packet(s, &rtsp_st, buf, sizeof(buf)); + break; +#endif + case RTSP_LOWER_TRANSPORT_UDP: + case RTSP_LOWER_TRANSPORT_UDP_MULTICAST: + len = udp_read_packet(s, &rtsp_st, buf, sizeof(buf)); + if (len >=0 && rtsp_st->transport_priv && rt->transport == RTSP_TRANSPORT_RTP) + rtp_check_and_send_back_rr(rtsp_st->transport_priv, len); + break; + } + if (len < 0) + return len; + if (len == 0) + return AVERROR_EOF; + if (rt->transport == RTSP_TRANSPORT_RDT) { + ret = ff_rdt_parse_packet(rtsp_st->transport_priv, pkt, buf, len); + } else + ret = rtp_parse_packet(rtsp_st->transport_priv, pkt, buf, len); + if (ret < 0) + goto redo; + if (ret == 1) + /* more packets may follow, so we save the RTP context */ + rt->cur_transport_priv = rtsp_st->transport_priv; + + return ret; +} + +static int rtsp_read_packet(AVFormatContext *s, AVPacket *pkt) { RTSPState *rt = s->priv_data; int ret; @@ -1571,11 +1748,11 @@ static int rtsp_read_packet(AVFormatContext *s, if (!rt->need_subscription) { if (memcmp (cache, rt->real_setup_cache, sizeof(enum AVDiscard) * s->nb_streams)) { - av_strlcatf(cmd, sizeof(cmd), - "SET_PARAMETER %s RTSP/1.0\r\n" - "Unsubscribe: %s\r\n", - s->filename, rt->last_subscription); - rtsp_send_cmd(s, cmd, reply, NULL); + snprintf(cmd, sizeof(cmd), + "SET_PARAMETER %s RTSP/1.0\r\n" + "Unsubscribe: %s\r\n", + rt->control_uri, rt->last_subscription); + ff_rtsp_send_cmd(s, cmd, reply, NULL); if (reply->status_code != RTSP_STATUS_OK) return AVERROR_INVALIDDATA; rt->need_subscription = 1; @@ -1592,7 +1769,7 @@ static int rtsp_read_packet(AVFormatContext *s, snprintf(cmd, sizeof(cmd), "SET_PARAMETER %s RTSP/1.0\r\n" "Subscribe: ", - s->filename); + rt->control_uri); for (i = 0; i < rt->nb_rtsp_streams; i++) { rule_nr = 0; for (r = 0; r < s->nb_streams; r++) { @@ -1611,20 +1788,19 @@ static int rtsp_read_packet(AVFormatContext *s, } } av_strlcatf(cmd, sizeof(cmd), "%s\r\n", rt->last_subscription); - rtsp_send_cmd(s, cmd, reply, NULL); + ff_rtsp_send_cmd(s, cmd, reply, NULL); if (reply->status_code != RTSP_STATUS_OK) return AVERROR_INVALIDDATA; rt->need_subscription = 0; - if (rt->state == RTSP_STATE_PLAYING) + if (rt->state == RTSP_STATE_STREAMING) rtsp_read_play (s); } } - ret = sdp_read_packet(s, pkt); - if (ret < 0) { + ret = rtsp_fetch_packet(s, pkt); + if (ret < 0) return ret; - } /* send dummy request to keep TCP connection alive */ if ((rt->server_type == RTSP_SERVER_WMS || @@ -1633,11 +1809,10 @@ static int rtsp_read_packet(AVFormatContext *s, if (rt->server_type == RTSP_SERVER_WMS) { snprintf(cmd, sizeof(cmd) - 1, "GET_PARAMETER %s RTSP/1.0\r\n", - s->filename); - rtsp_send_cmd_async(s, cmd, reply, NULL); + rt->control_uri); + ff_rtsp_send_cmd_async(s, cmd); } else { - rtsp_send_cmd_async(s, "OPTIONS * RTSP/1.0\r\n", - reply, NULL); + ff_rtsp_send_cmd_async(s, "OPTIONS * RTSP/1.0\r\n"); } } @@ -1653,13 +1828,13 @@ static int rtsp_read_pause(AVFormatContext *s) rt = s->priv_data; - if (rt->state != RTSP_STATE_PLAYING) + if (rt->state != RTSP_STATE_STREAMING) return 0; else if (!(rt->server_type == RTSP_SERVER_REAL && rt->need_subscription)) { snprintf(cmd, sizeof(cmd), "PAUSE %s RTSP/1.0\r\n", - s->filename); - rtsp_send_cmd(s, cmd, reply, NULL); + rt->control_uri); + ff_rtsp_send_cmd(s, cmd, reply, NULL); if (reply->status_code != RTSP_STATUS_OK) { return -1; } @@ -1673,12 +1848,14 @@ static int rtsp_read_seek(AVFormatContext *s, int stream_index, { RTSPState *rt = s->priv_data; - rt->seek_timestamp = av_rescale_q(timestamp, s->streams[stream_index]->time_base, AV_TIME_BASE_Q); + 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: + case RTSP_STATE_STREAMING: if (rtsp_read_pause(s) != 0) return -1; rt->state = RTSP_STATE_SEEKING; @@ -1695,7 +1872,6 @@ static int rtsp_read_seek(AVFormatContext *s, int stream_index, static int rtsp_read_close(AVFormatContext *s) { RTSPState *rt = s->priv_data; - RTSPMessageHeader reply1, *reply = &reply1; char cmd[1024]; #if 0 @@ -1707,14 +1883,14 @@ static int rtsp_read_close(AVFormatContext *s) snprintf(cmd, sizeof(cmd), "TEARDOWN %s RTSP/1.0\r\n", s->filename); - rtsp_send_cmd(s, cmd, reply, NULL); + ff_rtsp_send_cmd_async(s, cmd); - rtsp_close_streams(rt); + ff_rtsp_close_streams(s); url_close(rt->rtsp_hd); + ff_network_close(); return 0; } -#if CONFIG_RTSP_DEMUXER AVInputFormat rtsp_demuxer = { "rtsp", NULL_IF_CONFIG_SMALL("RTSP input format"), @@ -1736,10 +1912,11 @@ static int sdp_probe(AVProbeData *p1) /* we look for a line beginning "c=IN IP4" */ while (p < p_end && *p != '\0') { - if (p + sizeof("c=IN IP4") - 1 < p_end && av_strstart(p, "c=IN IP4", NULL)) + if (p + sizeof("c=IN IP4") - 1 < p_end && + av_strstart(p, "c=IN IP4", NULL)) return AVPROBE_SCORE_MAX / 2; - while(p < p_end - 1 && *p != '\n') p++; + while (p < p_end - 1 && *p != '\n') p++; if (++p >= p_end) break; if (*p == '\r') @@ -1750,8 +1927,7 @@ static int sdp_probe(AVProbeData *p1) #define SDP_MAX_SIZE 8192 -static int sdp_read_header(AVFormatContext *s, - AVFormatParameters *ap) +static int sdp_read_header(AVFormatContext *s, AVFormatParameters *ap) { RTSPState *rt = s->priv_data; RTSPStream *rtsp_st; @@ -1759,6 +1935,9 @@ static int sdp_read_header(AVFormatContext *s, char *content; char url[1024]; + if (!ff_network_init()) + return AVERROR(EIO); + /* read the whole sdp file */ /* XXX: better loading */ content = av_malloc(SDP_MAX_SIZE); @@ -1773,14 +1952,13 @@ static int sdp_read_header(AVFormatContext *s, av_free(content); /* open each RTP stream */ - for(i=0;inb_rtsp_streams;i++) { + for (i = 0; i < rt->nb_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); + ff_url_join(url, sizeof(url), "rtp", NULL, + inet_ntoa(rtsp_st->sdp_ip), rtsp_st->sdp_port, + "?localport=%d&ttl=%d", rtsp_st->sdp_port, + rtsp_st->sdp_ttl); if (url_open(&rtsp_st->rtp_handle, url, URL_RDWR) < 0) { err = AVERROR_INVALIDDATA; goto fail; @@ -1789,26 +1967,25 @@ static int sdp_read_header(AVFormatContext *s, goto fail; } return 0; - fail: - rtsp_close_streams(rt); +fail: + ff_rtsp_close_streams(s); + ff_network_close(); return err; } static int sdp_read_close(AVFormatContext *s) { - RTSPState *rt = s->priv_data; - rtsp_close_streams(rt); + ff_rtsp_close_streams(s); + ff_network_close(); return 0; } -#if CONFIG_SDP_DEMUXER AVInputFormat sdp_demuxer = { "sdp", NULL_IF_CONFIG_SMALL("SDP"), sizeof(RTSPState), sdp_probe, sdp_read_header, - sdp_read_packet, + rtsp_fetch_packet, sdp_read_close, }; -#endif