X-Git-Url: https://git.sesse.net/?a=blobdiff_plain;f=libavformat%2Frtsp.c;h=bdbc7ebb741462a25db5c9e96281b03835ba2b02;hb=5fe8021a6a93c9a0ab46e0e913649aa2b8f32d5d;hp=ba66fa70f04b14c9bddd9a837d392538902946b9;hpb=35cfd6464e60dfe3d65053a102eb44c94ec51fec;p=ffmpeg diff --git a/libavformat/rtsp.c b/libavformat/rtsp.c index ba66fa70f04..bdbc7ebb741 100644 --- a/libavformat/rtsp.c +++ b/libavformat/rtsp.c @@ -19,12 +19,10 @@ * 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" +#include "libavutil/random_seed.h" #include "avformat.h" #include @@ -32,13 +30,15 @@ #include #endif #include +#include "internal.h" #include "network.h" +#include "os_support.h" +#include "http.h" #include "rtsp.h" #include "rtpdec.h" #include "rdt.h" -#include "rtp_asf.h" -#include "rtp_vorbis.h" +#include "rtpdec_formats.h" //#define DEBUG //#define DEBUG_RTP_TCP @@ -47,18 +47,13 @@ int rtsp_default_protocols = (1 << RTSP_LOWER_TRANSPORT_UDP); #endif -#define SPACE_CHARS " \t\r\n" -/* we use memchr() instead of strchr() here because strchr() will return - * the terminating '\0' of SPACE_CHARS instead of NULL if c is '\0'. */ -#define redir_isspace(c) memchr(SPACE_CHARS, c, 4) -static void skip_spaces(const char **pp) -{ - const char *p; - p = *pp; - while (redir_isspace(*p)) - p++; - *pp = p; -} +/* Timeout values for socket select, in ms, + * and read_packet(), in seconds */ +#define SELECT_TIMEOUT_MS 100 +#define READ_PACKET_TIMEOUT_S 10 +#define MAX_TIMEOUTS READ_PACKET_TIMEOUT_S * 1000 / SELECT_TIMEOUT_MS +#define SDP_MAX_SIZE 16384 +#define RECVBUF_SIZE 10 * RTP_MAX_PACKET_LENGTH static void get_word_until_chars(char *buf, int buf_size, const char *sep, const char **pp) @@ -67,7 +62,7 @@ static void get_word_until_chars(char *buf, int buf_size, char *q; p = *pp; - skip_spaces(&p); + p += strspn(p, SPACE_CHARS); q = buf; while (!strchr(sep, *p) && *p != '\0') { if ((q - buf) < buf_size - 1) @@ -120,6 +115,12 @@ static int sdp_parse_rtpmap(AVFormatContext *s, break; } } + /* If no dynamic handler was found, check with the list of standard + * allocated types, if such a stream for some reason happens to + * use a private payload type. This isn't handled in rtpdec.c, since + * the format name from the rtpmap line never is passed into rtpdec. */ + if (!rtsp_st->dynamic_handler) + codec->codec_id = ff_rtp_codec_id(buf, codec->codec_type); } else { /* We are in a standard case * (from http://www.iana.org/assignments/rtp-parameters). */ @@ -136,7 +137,7 @@ static int sdp_parse_rtpmap(AVFormatContext *s, get_word_sep(buf, sizeof(buf), "/", &p); i = atoi(buf); switch (codec->codec_type) { - case CODEC_TYPE_AUDIO: + case AVMEDIA_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; @@ -156,7 +157,7 @@ static int sdp_parse_rtpmap(AVFormatContext *s, av_log(s, AV_LOG_DEBUG, "audio channels set to: %i\n", codec->channels); break; - case CODEC_TYPE_VIDEO: + case AVMEDIA_TYPE_VIDEO: av_log(s, AV_LOG_DEBUG, "video codec set to: %s\n", c_name); break; default: @@ -165,95 +166,13 @@ static int sdp_parse_rtpmap(AVFormatContext *s, return 0; } -/* 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 (;;) { - skip_spaces(&p); - if (*p == '\0') - break; - c = toupper((unsigned char) *p++); - if (c >= '0' && c <= '9') - c = c - '0'; - else if (c >= 'A' && c <= 'F') - c = c - 'A' + 10; - else - break; - v = (v << 4) | c; - if (v & 0x100) { - if (data) - data[len] = v; - len++; - v = 1; - } - } - return len; -} - -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; - } - return; -} - -typedef struct { - const char *str; - uint16_t type; - uint32_t offset; -} AttrNameMap; - -/* All known fmtp parmeters and the corresping RTPAttrTypeEnum */ -#define ATTR_NAME_TYPE_INT 0 -#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 }, -}; - -/* parse the attribute line from the fmtp a line of an sdp resonse. This +/* parse the attribute line from the fmtp a line of an sdp response. 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) +int ff_rtsp_next_attr_and_value(const char **p, char *attr, int attr_size, + char *value, int value_size) { - skip_spaces(p); + *p += strspn(*p, SPACE_CHARS); if (**p) { get_word_sep(attr, attr_size, "=", p); if (**p == '=') @@ -266,39 +185,6 @@ int rtsp_next_attr_and_value(const char **p, char *attr, int attr_size, return 0; } -/* parse a SDP line and save stream attributes */ -static void sdp_parse_fmtp(AVStream *st, const char *p) -{ - char attr[256]; - /* Vorbis setup headers can be up to 12KB and are sent base64 - * 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 */ - 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); - } - } - } -} - /** 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. @@ -307,7 +193,7 @@ static void rtsp_parse_range_npt(const char *p, int64_t *start, int64_t *end) { char buf[256]; - skip_spaces(&p); + p += strspn(p, SPACE_CHARS); if (!av_stristart(p, "npt=", &p)) return; @@ -325,9 +211,21 @@ static void rtsp_parse_range_npt(const char *p, int64_t *start, int64_t *end) // av_log(NULL, AV_LOG_DEBUG, "Range End: %lld\n", *end); } +static int get_sockaddr(const char *buf, struct sockaddr_storage *sock) +{ + struct addrinfo hints, *ai = NULL; + memset(&hints, 0, sizeof(hints)); + hints.ai_flags = AI_NUMERICHOST; + if (getaddrinfo(buf, NULL, &hints, &ai)) + return -1; + memcpy(sock, ai->ai_addr, FFMIN(sizeof(*sock), ai->ai_addrlen)); + freeaddrinfo(ai); + return 0; +} + typedef struct SDPParseState { /* SDP only */ - struct in_addr default_ip; + struct sockaddr_storage default_ip; int default_ttl; int skip_media; ///< set if an unknown m= line occurs } SDPParseState; @@ -338,11 +236,11 @@ static void sdp_parse_line(AVFormatContext *s, SDPParseState *s1, RTSPState *rt = s->priv_data; char buf1[64], st_type[64]; const char *p; - enum CodecType codec_type; + enum AVMediaType codec_type; int payload_type, i; AVStream *st; RTSPStream *rtsp_st; - struct in_addr sdp_ip; + struct sockaddr_storage sdp_ip; int ttl; dprintf(s, "sdp: %c='%s'\n", letter, buf); @@ -356,10 +254,10 @@ static void sdp_parse_line(AVFormatContext *s, SDPParseState *s1, if (strcmp(buf1, "IN") != 0) return; get_word(buf1, sizeof(buf1), &p); - if (strcmp(buf1, "IP4") != 0) + if (strcmp(buf1, "IP4") && strcmp(buf1, "IP6")) return; get_word_sep(buf1, sizeof(buf1), "/", &p); - if (inet_aton(buf1, &sdp_ip) == 0) + if (get_sockaddr(buf1, &sdp_ip)) return; ttl = 16; if (*p == '/') { @@ -378,11 +276,11 @@ static void sdp_parse_line(AVFormatContext *s, SDPParseState *s1, } break; case 's': - av_metadata_set(&s->metadata, "title", p); + av_metadata_set2(&s->metadata, "title", p, 0); break; case 'i': if (s->nb_streams == 0) { - av_metadata_set(&s->metadata, "comment", p); + av_metadata_set2(&s->metadata, "comment", p, 0); break; } break; @@ -391,11 +289,11 @@ static void sdp_parse_line(AVFormatContext *s, SDPParseState *s1, s1->skip_media = 0; get_word(st_type, sizeof(st_type), &p); if (!strcmp(st_type, "audio")) { - codec_type = CODEC_TYPE_AUDIO; + codec_type = AVMEDIA_TYPE_AUDIO; } else if (!strcmp(st_type, "video")) { - codec_type = CODEC_TYPE_VIDEO; + codec_type = AVMEDIA_TYPE_VIDEO; } else if (!strcmp(st_type, "application")) { - codec_type = CODEC_TYPE_DATA; + codec_type = AVMEDIA_TYPE_DATA; } else { s1->skip_media = 1; return; @@ -449,8 +347,8 @@ static void sdp_parse_line(AVFormatContext *s, SDPParseState *s1, 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); + av_url_split(proto, sizeof(proto), NULL, 0, NULL, 0, + NULL, NULL, 0, p); if (proto[0] == '\0') { /* relative control URL */ if (rtsp_st->control_url[strlen(rtsp_st->control_url)-1]!='/') @@ -469,22 +367,9 @@ static void sdp_parse_line(AVFormatContext *s, SDPParseState *s1, st = s->streams[s->nb_streams - 1]; rtsp_st = st->priv_data; sdp_parse_rtpmap(s, st->codec, rtsp_st, payload_type, p); - } else if (av_strstart(p, "fmtp:", &p)) { + } else if (av_strstart(p, "fmtp:", &p) || + av_strstart(p, "framesize:", &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]; - 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))) - 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); @@ -538,14 +423,15 @@ static int sdp_parse(AVFormatContext *s, const char *content) * "rulebooks" describing their properties. Therefore, the SDP line * buffer is large. * - * The Vorbis FMTP line can be up to 16KB - see sdp_parse_fmtp. */ + * The Vorbis FMTP line can be up to 16KB - see xiph_parse_sdp_line + * in rtpdec_xiph.c. */ char buf[16384], *q; SDPParseState sdp_parse_state, *s1 = &sdp_parse_state; memset(s1, 0, sizeof(SDPParseState)); p = content; for (;;) { - skip_spaces(&p); + p += strspn(p, SPACE_CHARS); letter = *p; if (letter == '\0') break; @@ -572,7 +458,7 @@ static int sdp_parse(AVFormatContext *s, const char *content) } /* close and free RTSP streams */ -static void rtsp_close_streams(AVFormatContext *s) +void ff_rtsp_close_streams(AVFormatContext *s) { RTSPState *rt = s->priv_data; int i; @@ -585,7 +471,15 @@ static void rtsp_close_streams(AVFormatContext *s) if (s->oformat) { AVFormatContext *rtpctx = rtsp_st->transport_priv; av_write_trailer(rtpctx); - url_fclose(rtpctx->pb); + if (rt->lower_transport == RTSP_LOWER_TRANSPORT_TCP) { + uint8_t *ptr; + url_close_dyn_buf(rtpctx->pb, &ptr); + av_free(ptr); + } else { + 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) @@ -605,10 +499,13 @@ static void rtsp_close_streams(AVFormatContext *s) av_close_input_stream (rt->asf_ctx); rt->asf_ctx = NULL; } - av_freep(&rt->auth_b64); + av_free(rt->recvbuf); } -static void *rtsp_rtp_mux_open(AVFormatContext *s, AVStream *st, URLContext *handle) { +static void *rtsp_rtp_mux_open(AVFormatContext *s, AVStream *st, + URLContext *handle) +{ + RTSPState *rt = s->priv_data; AVFormatContext *rtpctx; int ret; AVOutputFormat *rtp_format = av_guess_format("rtp", NULL, NULL); @@ -631,17 +528,29 @@ static void *rtsp_rtp_mux_open(AVFormatContext *s, AVStream *st, URLContext *han /* Copy other stream parameters. */ rtpctx->streams[0]->sample_aspect_ratio = st->sample_aspect_ratio; + /* Set the synchronized start time. */ + rtpctx->start_time_realtime = rt->start_time; + /* 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); + if (handle) { + url_fdopen(&rtpctx->pb, handle); + } else + url_open_dyn_packet_buf(&rtpctx->pb, RTSP_TCP_MAX_PACKET_SIZE); ret = av_write_header(rtpctx); if (ret) { - url_fclose(rtpctx->pb); + if (handle) { + url_fclose(rtpctx->pb); + } else { + uint8_t *ptr; + url_close_dyn_buf(rtpctx->pb, &ptr); + av_free(ptr); + } av_free(rtpctx->streams[0]); av_free(rtpctx); return NULL; @@ -665,7 +574,7 @@ static int rtsp_open_transport_ctx(AVFormatContext *s, RTSPStream *rtsp_st) 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 */ + /* Ownership 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, @@ -674,7 +583,8 @@ static int rtsp_open_transport_ctx(AVFormatContext *s, RTSPStream *rtsp_st) else rtsp_st->transport_priv = rtp_parse_open(s, st, rtsp_st->rtp_handle, rtsp_st->sdp_payload_type, - &rtsp_st->rtp_payload_data); + (rt->lower_transport == RTSP_LOWER_TRANSPORT_TCP || !s->max_delay) + ? 0 : RTP_REORDER_QUEUE_DEFAULT_SIZE); if (!rtsp_st->transport_priv) { return AVERROR(ENOMEM); @@ -689,7 +599,7 @@ static int rtsp_open_transport_ctx(AVFormatContext *s, RTSPStream *rtsp_st) return 0; } -#if CONFIG_RTSP_DEMUXER +#if CONFIG_RTSP_DEMUXER || CONFIG_RTSP_MUXER static int rtsp_probe(AVProbeData *p) { if (av_strstart(p->filename, "rtsp:", NULL)) @@ -703,7 +613,7 @@ static void rtsp_parse_range(int *min_ptr, int *max_ptr, const char **pp) int v; p = *pp; - skip_spaces(&p); + p += strspn(p, SPACE_CHARS); v = strtol(p, (char **)&p, 10); if (*p == '-') { p++; @@ -730,7 +640,7 @@ static void rtsp_parse_transport(RTSPMessageHeader *reply, const char *p) reply->nb_transports = 0; for (;;) { - skip_spaces(&p); + p += strspn(p, SPACE_CHARS); if (*p == '\0') break; @@ -796,15 +706,19 @@ static void rtsp_parse_transport(RTSPMessageHeader *reply, const char *p) th->ttl = strtol(p, (char **)&p, 10); } } else if (!strcmp(parameter, "destination")) { - struct in_addr ipaddr; - if (*p == '=') { p++; get_word_sep(buf, sizeof(buf), ";,", &p); - if (inet_aton(buf, &ipaddr)) - th->destination = ntohl(ipaddr.s_addr); + get_sockaddr(buf, &th->destination); + } + } else if (!strcmp(parameter, "source")) { + if (*p == '=') { + p++; + get_word_sep(buf, sizeof(buf), ";,", &p); + av_strlcpy(th->source, buf, sizeof(th->source)); } } + while (*p != ';' && *p != '\0' && *p != ',') p++; if (*p == ';') @@ -817,7 +731,8 @@ 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, + HTTPAuthState *auth_state) { const char *p; @@ -839,22 +754,28 @@ void rtsp_parse_line(RTSPMessageHeader *reply, const char *buf) } else if (av_stristart(p, "Range:", &p)) { rtsp_parse_range_npt(p, &reply->range_start, &reply->range_end); } else if (av_stristart(p, "RealChallenge1:", &p)) { - skip_spaces(&p); + p += strspn(p, SPACE_CHARS); av_strlcpy(reply->real_challenge, p, sizeof(reply->real_challenge)); } else if (av_stristart(p, "Server:", &p)) { - skip_spaces(&p); + p += strspn(p, SPACE_CHARS); av_strlcpy(reply->server, p, sizeof(reply->server)); } else if (av_stristart(p, "Notice:", &p) || av_stristart(p, "X-Notice:", &p)) { reply->notice = strtol(p, NULL, 10); } else if (av_stristart(p, "Location:", &p)) { - skip_spaces(&p); + p += strspn(p, SPACE_CHARS); av_strlcpy(reply->location, p , sizeof(reply->location)); + } else if (av_stristart(p, "WWW-Authenticate:", &p) && auth_state) { + p += strspn(p, SPACE_CHARS); + ff_http_auth_handle_header(auth_state, "WWW-Authenticate", p); + } else if (av_stristart(p, "Authentication-Info:", &p) && auth_state) { + p += strspn(p, SPACE_CHARS); + ff_http_auth_handle_header(auth_state, "Authentication-Info", p); } } /* skip a RTP/TCP interleaved packet */ -static void rtsp_skip_packet(AVFormatContext *s) +void ff_rtsp_skip_packet(AVFormatContext *s) { RTSPState *rt = s->priv_data; int ret, len, len1; @@ -879,30 +800,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 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; @@ -923,7 +823,7 @@ static int rtsp_read_reply(AVFormatContext *s, RTSPMessageHeader *reply, dprintf(s, "ret=%d c=%02x [%c]\n", ret, ch, ch); #endif if (ret != 1) - return -1; + return AVERROR_EOF; if (ch == '\n') break; if (ch == '$') { @@ -931,7 +831,7 @@ static int rtsp_read_reply(AVFormatContext *s, RTSPMessageHeader *reply, if (return_on_interleaved_data) { return 1; } else - rtsp_skip_packet(s); + ff_rtsp_skip_packet(s); } else if (ch != '\r') { if ((q - buf) < sizeof(buf) - 1) *q++ = ch; @@ -950,8 +850,9 @@ static int rtsp_read_reply(AVFormatContext *s, RTSPMessageHeader *reply, get_word(buf1, sizeof(buf1), &p); get_word(buf1, sizeof(buf1), &p); reply->status_code = atoi(buf1); + av_strlcpy(reply->reason, p, sizeof(reply->reason)); } else { - rtsp_parse_line(reply, p); + ff_rtsp_parse_line(reply, p, &rt->auth_state); av_strlcat(rt->last_reply, p, sizeof(rt->last_reply)); av_strlcat(rt->last_reply, "\n", sizeof(rt->last_reply)); } @@ -973,6 +874,11 @@ static int rtsp_read_reply(AVFormatContext *s, RTSPMessageHeader *reply, else av_free(content); + if (rt->seq != reply->seq) { + av_log(s, AV_LOG_WARNING, "CSeq %d expected, %d received.\n", + rt->seq, reply->seq); + } + /* EOS */ if (reply->notice == 2101 /* End-of-Stream Reached */ || reply->notice == 2104 /* Start-of-Stream Reached */ || @@ -987,66 +893,113 @@ static int rtsp_read_reply(AVFormatContext *s, RTSPMessageHeader *reply, return 0; } -static void rtsp_send_cmd_with_content_async(AVFormatContext *s, - const char *cmd, - const unsigned char *send_content, - int send_content_length) +int ff_rtsp_send_cmd_with_content_async(AVFormatContext *s, + const char *method, const char *url, + const char *headers, + const unsigned char *send_content, + int send_content_length) { RTSPState *rt = s->priv_data; - char buf[4096], buf1[1024]; + char buf[4096], *out_buf; + char base64buf[AV_BASE64_SIZE(sizeof(buf))]; + /* Add in RTSP headers */ + out_buf = buf; rt->seq++; - av_strlcpy(buf, cmd, sizeof(buf)); - snprintf(buf1, sizeof(buf1), "CSeq: %d\r\n", rt->seq); - 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); - av_strlcat(buf, buf1, sizeof(buf)); + snprintf(buf, sizeof(buf), "%s %s RTSP/1.0\r\n", method, url); + if (headers) + av_strlcat(buf, headers, sizeof(buf)); + av_strlcatf(buf, sizeof(buf), "CSeq: %d\r\n", rt->seq); + if (rt->session_id[0] != '\0' && (!headers || + !strstr(headers, "\nIf-Match:"))) { + av_strlcatf(buf, sizeof(buf), "Session: %s\r\n", rt->session_id); + } + if (rt->auth[0]) { + char *str = ff_http_auth_create_response(&rt->auth_state, + rt->auth, url, method); + if (str) + av_strlcat(buf, str, sizeof(buf)); + av_free(str); } - if (rt->auth_b64) - 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)); + /* base64 encode rtsp if tunneling */ + if (rt->control_transport == RTSP_MODE_TUNNEL) { + av_base64_encode(base64buf, sizeof(base64buf), buf, strlen(buf)); + out_buf = base64buf; + } + 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); + url_write(rt->rtsp_hd_out, out_buf, strlen(out_buf)); + if (send_content_length > 0 && send_content) { + if (rt->control_transport == RTSP_MODE_TUNNEL) { + av_log(s, AV_LOG_ERROR, "tunneling of RTSP requests " + "with content data not supported\n"); + return AVERROR_PATCHWELCOME; + } + url_write(rt->rtsp_hd_out, send_content, send_content_length); + } rt->last_cmd_time = av_gettime(); + + return 0; } -static void rtsp_send_cmd_async(AVFormatContext *s, const char *cmd) +int ff_rtsp_send_cmd_async(AVFormatContext *s, const char *method, + const char *url, const char *headers) { - rtsp_send_cmd_with_content_async(s, cmd, NULL, 0); + return ff_rtsp_send_cmd_with_content_async(s, method, url, headers, NULL, 0); } -static void rtsp_send_cmd(AVFormatContext *s, - const char *cmd, RTSPMessageHeader *reply, - unsigned char **content_ptr) +int ff_rtsp_send_cmd(AVFormatContext *s, const char *method, const char *url, + const char *headers, RTSPMessageHeader *reply, + unsigned char **content_ptr) { - rtsp_send_cmd_async(s, cmd); - - rtsp_read_reply(s, reply, content_ptr, 0); + return ff_rtsp_send_cmd_with_content(s, method, url, headers, reply, + content_ptr, NULL, 0); } -static void 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) +int ff_rtsp_send_cmd_with_content(AVFormatContext *s, + const char *method, const char *url, + const char *header, + RTSPMessageHeader *reply, + unsigned char **content_ptr, + const unsigned char *send_content, + int send_content_length) { - rtsp_send_cmd_with_content_async(s, cmd, send_content, send_content_length); + RTSPState *rt = s->priv_data; + HTTPAuthType cur_auth_type; + int ret; + +retry: + cur_auth_type = rt->auth_state.auth_type; + if ((ret = ff_rtsp_send_cmd_with_content_async(s, method, url, header, + send_content, + send_content_length))) + return ret; + + if ((ret = ff_rtsp_read_reply(s, reply, content_ptr, 0) ) < 0) + return ret; + + if (reply->status_code == 401 && cur_auth_type == HTTP_AUTH_NONE && + rt->auth_state.auth_type != HTTP_AUTH_NONE) + goto retry; + + if (reply->status_code > 400){ + av_log(s, AV_LOG_ERROR, "method %s failed: %d%s\n", + method, + reply->status_code, + reply->reason); + av_log(s, AV_LOG_DEBUG, "%s\n", rt->last_reply); + } - rtsp_read_reply(s, reply, content_ptr, 0); + return 0; } /** - * @returns 0 on success, <0 on error, 1 if protocol is unavailable. + * @return 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) @@ -1109,8 +1062,8 @@ static int 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); + 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) @@ -1147,7 +1100,7 @@ static int make_setup_request(AVFormatContext *s, const char *host, int port, * 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) + AVMEDIA_TYPE_DATA) continue; snprintf(transport, sizeof(transport) - 1, "%s/TCP;", trans_pref); @@ -1166,12 +1119,11 @@ static int make_setup_request(AVFormatContext *s, const char *host, int port, if (s->oformat) { av_strlcat(transport, ";mode=receive", sizeof(transport)); } else if (rt->server_type == RTSP_SERVER_REAL || - rt->server_type == RTSP_SERVER_WMS) + rt->server_type == RTSP_SERVER_WMS) av_strlcat(transport, ";mode=play", sizeof(transport)); snprintf(cmd, sizeof(cmd), - "SETUP %s RTSP/1.0\r\n" "Transport: %s\r\n", - rtsp_st->control_url, transport); + transport); if (i == 0 && rt->server_type == RTSP_SERVER_REAL) { char real_res[41], real_csum[9]; ff_rdt_calc_response_and_checksum(real_res, real_csum, @@ -1181,7 +1133,7 @@ static int 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, "SETUP", rtsp_st->control_url, cmd, reply, NULL); if (reply->status_code == 461 /* Unsupported protocol */ && i == 0) { err = 1; goto fail; @@ -1203,7 +1155,7 @@ static int make_setup_request(AVFormatContext *s, const char *host, int port, rt->transport = reply->transports[0].transport; } - /* close RTP connection if not choosen */ + /* close RTP connection if not chosen */ if (reply->transports[0].lower_transport != RTSP_LOWER_TRANSPORT_UDP && (lower_transport == RTSP_LOWER_TRANSPORT_UDP)) { url_close(rtsp_st->rtp_handle); @@ -1219,9 +1171,15 @@ static int make_setup_request(AVFormatContext *s, const char *host, int port, 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); + /* Use source address if specified */ + if (reply->transports[0].source[0]) { + ff_url_join(url, sizeof(url), "rtp", NULL, + reply->transports[0].source, + reply->transports[0].server_port_min, NULL); + } else { + 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; @@ -1236,21 +1194,23 @@ static int make_setup_request(AVFormatContext *s, const char *host, int port, break; } case RTSP_LOWER_TRANSPORT_UDP_MULTICAST: { - char url[1024]; - struct in_addr in; + char url[1024], namebuf[50]; + struct sockaddr_storage addr; int port, ttl; - if (reply->transports[0].destination) { - in.s_addr = htonl(reply->transports[0].destination); + if (reply->transports[0].destination.ss_family) { + addr = reply->transports[0].destination; port = reply->transports[0].port_min; ttl = reply->transports[0].ttl; } else { - in = rtsp_st->sdp_ip; + addr = 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); + getnameinfo((struct sockaddr*) &addr, sizeof(addr), + namebuf, sizeof(namebuf), NULL, 0, NI_NUMERICHOST); + ff_url_join(url, sizeof(url), "rtp", NULL, namebuf, + port, "?ttl=%d", ttl); if (url_open(&rtsp_st->rtp_handle, url, URL_RDWR) < 0) { err = AVERROR_INVALIDDATA; goto fail; @@ -1285,44 +1245,59 @@ static int rtsp_read_play(AVFormatContext *s) { RTSPState *rt = s->priv_data; RTSPMessageHeader reply1, *reply = &reply1; + int i; char cmd[1024]; av_log(s, AV_LOG_DEBUG, "hello state=%d\n", rt->state); + rt->nb_byes = 0; if (!(rt->server_type == RTSP_SERVER_REAL && rt->need_subscription)) { if (rt->state == RTSP_STATE_PAUSED) { - snprintf(cmd, sizeof(cmd), - "PLAY %s RTSP/1.0\r\n", - rt->control_uri); + cmd[0] = 0; } else { snprintf(cmd, sizeof(cmd), - "PLAY %s RTSP/1.0\r\n" "Range: npt=%0.3f-\r\n", - rt->control_uri, (double)rt->seek_timestamp / AV_TIME_BASE); } - rtsp_send_cmd(s, cmd, reply, NULL); + ff_rtsp_send_cmd(s, "PLAY", rt->control_uri, cmd, reply, NULL); if (reply->status_code != RTSP_STATUS_OK) { return -1; } + if (rt->transport == RTSP_TRANSPORT_RTP) { + for (i = 0; i < rt->nb_rtsp_streams; i++) { + RTSPStream *rtsp_st = rt->rtsp_streams[i]; + RTPDemuxContext *rtpctx = rtsp_st->transport_priv; + AVStream *st = NULL; + if (!rtpctx) + continue; + if (rtsp_st->stream_index >= 0) + st = s->streams[rtsp_st->stream_index]; + ff_rtp_reset_packet_queue(rtpctx); + if (reply->range_start != AV_NOPTS_VALUE) { + rtpctx->last_rtcp_ntp_time = AV_NOPTS_VALUE; + rtpctx->first_rtcp_ntp_time = AV_NOPTS_VALUE; + if (st) + rtpctx->range_start_offset = + av_rescale_q(reply->range_start, AV_TIME_BASE_Q, + st->time_base); + } + } + } } rt->state = RTSP_STATE_STREAMING; return 0; } -static int rtsp_setup_input_streams(AVFormatContext *s) +static int rtsp_setup_input_streams(AVFormatContext *s, RTSPMessageHeader *reply) { 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); + "Accept: application/sdp\r\n"); if (rt->server_type == RTSP_SERVER_REAL) { /** * The Require: attribute is needed for proper streaming from @@ -1332,7 +1307,7 @@ static int rtsp_setup_input_streams(AVFormatContext *s) "Require: com.real.retain-entity-for-setup\r\n", sizeof(cmd)); } - rtsp_send_cmd(s, cmd, reply, &content); + ff_rtsp_send_cmd(s, "DESCRIBE", rt->control_uri, cmd, reply, &content); if (!content) return AVERROR_INVALIDDATA; if (reply->status_code != RTSP_STATUS_OK) { @@ -1340,6 +1315,7 @@ static int rtsp_setup_input_streams(AVFormatContext *s) return AVERROR_INVALIDDATA; } + av_log(s, AV_LOG_VERBOSE, "SDP:\n%s\n", content); /* now we got the SDP description, we parse it */ ret = sdp_parse(s, (const char *)content); av_freep(&content); @@ -1349,28 +1325,44 @@ static int rtsp_setup_input_streams(AVFormatContext *s) return 0; } -static int rtsp_setup_output_streams(AVFormatContext *s) +static int rtsp_setup_output_streams(AVFormatContext *s, const char *addr) { RTSPState *rt = s->priv_data; RTSPMessageHeader reply1, *reply = &reply1; - char cmd[1024]; int i; char *sdp; + AVFormatContext sdp_ctx, *ctx_array[1]; + + rt->start_time = av_gettime(); /* 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); + sdp = av_mallocz(SDP_MAX_SIZE); if (sdp == NULL) return AVERROR(ENOMEM); - if (avf_sdp_create(&s, 1, sdp, 8192)) { + /* We create the SDP based on the RTSP AVFormatContext where we + * aren't allowed to change the filename field. (We create the SDP + * based on the RTSP context since the contexts for the RTP streams + * don't exist yet.) In order to specify a custom URL with the actual + * peer IP instead of the originally specified hostname, we create + * a temporary copy of the AVFormatContext, where the custom URL is set. + * + * FIXME: Create the SDP without copying the AVFormatContext. + * This either requires setting up the RTP stream AVFormatContexts + * already here (complicating things immensely) or getting a more + * flexible SDP creation interface. + */ + sdp_ctx = *s; + ff_url_join(sdp_ctx.filename, sizeof(sdp_ctx.filename), + "rtsp", NULL, addr, -1, NULL); + ctx_array[0] = &sdp_ctx; + if (avf_sdp_create(ctx_array, 1, sdp, SDP_MAX_SIZE)) { av_free(sdp); return AVERROR_INVALIDDATA; } - av_log(s, AV_LOG_INFO, "SDP:\n%s\n", sdp); - rtsp_send_cmd_with_content(s, cmd, reply, NULL, sdp, strlen(sdp)); + av_log(s, AV_LOG_VERBOSE, "SDP:\n%s\n", sdp); + ff_rtsp_send_cmd_with_content(s, "ANNOUNCE", rt->control_uri, + "Content-Type: application/sdp\r\n", + reply, NULL, sdp, strlen(sdp)); av_free(sdp); if (reply->status_code != RTSP_STATUS_OK) return AVERROR_INVALIDDATA; @@ -1388,7 +1380,7 @@ static int rtsp_setup_output_streams(AVFormatContext *s) st->priv_data = rtsp_st; rtsp_st->stream_index = i; - 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)); /* 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); @@ -1397,37 +1389,45 @@ static int rtsp_setup_output_streams(AVFormatContext *s) return 0; } -static int rtsp_connect(AVFormatContext *s) +void ff_rtsp_close_connections(AVFormatContext *s) +{ + RTSPState *rt = s->priv_data; + if (rt->rtsp_hd_out != rt->rtsp_hd) url_close(rt->rtsp_hd_out); + url_close(rt->rtsp_hd); + rt->rtsp_hd = rt->rtsp_hd_out = NULL; +} + +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, err; - RTSPMessageHeader reply1, *reply = &reply1; + int port, err, tcp_fd; + RTSPMessageHeader reply1 = {0}, *reply = &reply1; int lower_transport_mask = 0; char real_challenge[64]; + struct sockaddr_storage peer; + socklen_t peer_len = sizeof(peer); + + if (!ff_network_init()) + return AVERROR(EIO); redirect: + rt->control_transport = RTSP_MODE_PLAIN; /* extract hostname and port */ - url_split(NULL, 0, auth, sizeof(auth), - host, sizeof(host), &port, path, sizeof(path), s->filename); + av_url_split(NULL, 0, auth, sizeof(auth), + host, sizeof(host), &port, path, sizeof(path), s->filename); if (*auth) { - int auth_len = strlen(auth), b64_len = ((auth_len + 2) / 3) * 4 + 1; - - if (!(rt->auth_b64 = av_malloc(b64_len))) - return AVERROR(ENOMEM); - if (!av_base64_encode(rt->auth_b64, b64_len, auth, auth_len)) { - err = AVERROR(EINVAL); - goto fail; - } + av_strlcpy(rt->auth, auth, sizeof(rt->auth)); } if (port < 0) port = RTSP_DEFAULT_PORT; /* search for options */ - option_list = strchr(path, '?'); + option_list = strrchr(path, '?'); if (option_list) { - filename = strchr(s->filename, '?'); + /* Strip out the RTSP specific options, write out the rest of + * the options back into the same string. */ + filename = option_list; while (option_list) { /* move the option pointer */ option = ++option_list; @@ -1437,14 +1437,20 @@ redirect: /* handle the options */ if (!strcmp(option, "udp")) { - lower_transport_mask = (1<< RTSP_LOWER_TRANSPORT_UDP); + lower_transport_mask |= (1<< RTSP_LOWER_TRANSPORT_UDP); } else if (!strcmp(option, "multicast")) { - lower_transport_mask = (1<< RTSP_LOWER_TRANSPORT_UDP_MULTICAST); + lower_transport_mask |= (1<< RTSP_LOWER_TRANSPORT_UDP_MULTICAST); } else if (!strcmp(option, "tcp")) { - lower_transport_mask = (1<< RTSP_LOWER_TRANSPORT_TCP); + lower_transport_mask |= (1<< RTSP_LOWER_TRANSPORT_TCP); + } else if(!strcmp(option, "http")) { + lower_transport_mask |= (1<< RTSP_LOWER_TRANSPORT_TCP); + rt->control_transport = RTSP_MODE_TUNNEL; } else { - strcpy(++filename, option); - filename += strlen(option); + /* Write options back into the buffer, using memmove instead + * of strcpy since the strings may overlap. */ + int len = strlen(option); + memmove(++filename, option, len); + filename += len; if (option_list) *filename = '&'; } } @@ -1455,32 +1461,116 @@ redirect: 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) { + /* Only UDP or TCP - UDP multicast isn't supported. */ + lower_transport_mask &= (1 << RTSP_LOWER_TRANSPORT_UDP) | + (1 << RTSP_LOWER_TRANSPORT_TCP); + if (!lower_transport_mask || rt->control_transport == RTSP_MODE_TUNNEL) { av_log(s, AV_LOG_ERROR, "Unsupported lower transport method, " - "only UDP is supported for output.\n"); + "only UDP and TCP are supported for output.\n"); err = AVERROR(EINVAL); goto fail; } } - /* open the tcp connexion */ - snprintf(tcpname, sizeof(tcpname), "tcp://%s:%d", host, port); - if (url_open(&rtsp_hd, tcpname, URL_RDWR) < 0) { - err = AVERROR(EIO); - goto fail; + /* Construct the URI used in request; this is similar to s->filename, + * but with authentication credentials removed and RTSP specific options + * stripped out. */ + ff_url_join(rt->control_uri, sizeof(rt->control_uri), "rtsp", NULL, + host, port, "%s", path); + + if (rt->control_transport == RTSP_MODE_TUNNEL) { + /* set up initial handshake for tunneling */ + char httpname[1024]; + char sessioncookie[17]; + char headers[1024]; + + ff_url_join(httpname, sizeof(httpname), "http", auth, host, port, "%s", path); + snprintf(sessioncookie, sizeof(sessioncookie), "%08x%08x", + av_get_random_seed(), av_get_random_seed()); + + /* GET requests */ + if (url_alloc(&rt->rtsp_hd, httpname, URL_RDONLY) < 0) { + err = AVERROR(EIO); + goto fail; + } + + /* generate GET headers */ + snprintf(headers, sizeof(headers), + "x-sessioncookie: %s\r\n" + "Accept: application/x-rtsp-tunnelled\r\n" + "Pragma: no-cache\r\n" + "Cache-Control: no-cache\r\n", + sessioncookie); + ff_http_set_headers(rt->rtsp_hd, headers); + + /* complete the connection */ + if (url_connect(rt->rtsp_hd)) { + err = AVERROR(EIO); + goto fail; + } + + /* POST requests */ + if (url_alloc(&rt->rtsp_hd_out, httpname, URL_WRONLY) < 0 ) { + err = AVERROR(EIO); + goto fail; + } + + /* generate POST headers */ + snprintf(headers, sizeof(headers), + "x-sessioncookie: %s\r\n" + "Content-Type: application/x-rtsp-tunnelled\r\n" + "Pragma: no-cache\r\n" + "Cache-Control: no-cache\r\n" + "Content-Length: 32767\r\n" + "Expires: Sun, 9 Jan 1972 00:00:00 GMT\r\n", + sessioncookie); + ff_http_set_headers(rt->rtsp_hd_out, headers); + ff_http_set_chunked_transfer_encoding(rt->rtsp_hd_out, 0); + + /* Initialize the authentication state for the POST session. The HTTP + * protocol implementation doesn't properly handle multi-pass + * authentication for POST requests, since it would require one of + * the following: + * - implementing Expect: 100-continue, which many HTTP servers + * don't support anyway, even less the RTSP servers that do HTTP + * tunneling + * - sending the whole POST data until getting a 401 reply specifying + * what authentication method to use, then resending all that data + * - waiting for potential 401 replies directly after sending the + * POST header (waiting for some unspecified time) + * Therefore, we copy the full auth state, which works for both basic + * and digest. (For digest, we would have to synchronize the nonce + * count variable between the two sessions, if we'd do more requests + * with the original session, though.) + */ + ff_http_init_auth_state(rt->rtsp_hd_out, rt->rtsp_hd); + + /* complete the connection */ + if (url_connect(rt->rtsp_hd_out)) { + err = AVERROR(EIO); + goto fail; + } + } else { + /* open the tcp connection */ + ff_url_join(tcpname, sizeof(tcpname), "tcp", NULL, host, port, NULL); + if (url_open(&rt->rtsp_hd, tcpname, URL_RDWR) < 0) { + err = AVERROR(EIO); + goto fail; + } + rt->rtsp_hd_out = rt->rtsp_hd; } - rt->rtsp_hd = rtsp_hd; rt->seq = 0; + tcp_fd = url_get_file_handle(rt->rtsp_hd); + if (!getpeername(tcp_fd, (struct sockaddr*) &peer, &peer_len)) { + getnameinfo((struct sockaddr*) &peer, peer_len, host, sizeof(host), + NULL, 0, NI_NUMERICHOST); + } + /* 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); + cmd[0] = 0; if (rt->server_type == RTSP_SERVER_REAL) av_strlcat(cmd, /** @@ -1497,7 +1587,7 @@ redirect: "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, "OPTIONS", rt->control_uri, cmd, reply, NULL); if (reply->status_code != RTSP_STATUS_OK) { err = AVERROR_INVALIDDATA; goto fail; @@ -1515,9 +1605,9 @@ redirect: } if (s->iformat) - err = rtsp_setup_input_streams(s); + err = rtsp_setup_input_streams(s, reply); else - err = rtsp_setup_output_streams(s); + err = rtsp_setup_output_streams(s, host); if (err) goto fail; @@ -1532,7 +1622,7 @@ redirect: goto fail; lower_transport_mask &= ~(1 << lower_transport); if (lower_transport_mask == 0 && err == 1) { - err = AVERROR(FF_NETERROR(EPROTONOSUPPORT)); + err = FF_NETERROR(EPROTONOSUPPORT); goto fail; } } while (err); @@ -1541,8 +1631,8 @@ redirect: rt->seek_timestamp = 0; /* default is to start stream at position zero */ return 0; fail: - rtsp_close_streams(s); - url_close(rt->rtsp_hd); + ff_rtsp_close_streams(s); + ff_rtsp_close_connections(s); 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", @@ -1550,44 +1640,25 @@ redirect: s->filename); goto redirect; } + ff_network_close(); return err; } - -static int rtsp_read_header(AVFormatContext *s, - AVFormatParameters *ap) -{ - RTSPState *rt = s->priv_data; - int ret; - - ret = rtsp_connect(s); - if (ret) - return ret; - - if (ap->initial_pause) { - /* do not start immediately */ - } else { - if (rtsp_read_play(s) < 0) { - rtsp_close_streams(s); - url_close(rt->rtsp_hd); - return AVERROR_INVALIDDATA; - } - } - - return 0; -} +#endif /* CONFIG_RTSP_DEMUXER || CONFIG_RTSP_MUXER */ static int udp_read_packet(AVFormatContext *s, RTSPStream **prtsp_st, - uint8_t *buf, int buf_size) + uint8_t *buf, int buf_size, int64_t wait_end) { RTSPState *rt = s->priv_data; RTSPStream *rtsp_st; fd_set rfds; - int fd, fd_max, n, i, ret, tcp_fd; + int fd, fd_rtcp, fd_max, n, i, ret, tcp_fd, timeout_cnt = 0; struct timeval tv; for (;;) { if (url_interrupt_cb()) return AVERROR(EINTR); + if (wait_end && wait_end - av_gettime() < 0) + return AVERROR(EAGAIN); FD_ZERO(&rfds); if (rt->rtsp_hd) { tcp_fd = fd_max = url_get_file_handle(rt->rtsp_hd); @@ -1599,23 +1670,25 @@ static int udp_read_packet(AVFormatContext *s, RTSPStream **prtsp_st, 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_rtcp = rtp_get_rtcp_file_handle(rtsp_st->rtp_handle); + if (FFMAX(fd, fd_rtcp) > fd_max) + fd_max = FFMAX(fd, fd_rtcp); FD_SET(fd, &rfds); + FD_SET(fd_rtcp, &rfds); } } tv.tv_sec = 0; - tv.tv_usec = 100 * 1000; + tv.tv_usec = SELECT_TIMEOUT_MS * 1000; n = select(fd_max + 1, &rfds, NULL, NULL, &tv); if (n > 0) { + timeout_cnt = 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)) { + fd_rtcp = rtp_get_rtcp_file_handle(rtsp_st->rtp_handle); + if (FD_ISSET(fd_rtcp, &rfds) || FD_ISSET(fd, &rfds)) { ret = url_read(rtsp_st->rtp_handle, buf, buf_size); if (ret > 0) { *prtsp_st = rtsp_st; @@ -1628,14 +1701,166 @@ static int udp_read_packet(AVFormatContext *s, RTSPStream **prtsp_st, if (tcp_fd != -1 && FD_ISSET(tcp_fd, &rfds)) { RTSPMessageHeader reply; - rtsp_read_reply(s, &reply, NULL, 0); + ret = ff_rtsp_read_reply(s, &reply, NULL, 0); + if (ret < 0) + return ret; /* XXX: parse message */ if (rt->state != RTSP_STATE_STREAMING) return 0; } #endif + } else if (n == 0 && ++timeout_cnt >= MAX_TIMEOUTS) { + return FF_NETERROR(ETIMEDOUT); + } else if (n < 0 && errno != EINTR) + return AVERROR(errno); + } +} + +static int tcp_read_packet(AVFormatContext *s, RTSPStream **prtsp_st, + uint8_t *buf, int buf_size); + +static int rtsp_fetch_packet(AVFormatContext *s, AVPacket *pkt) +{ + RTSPState *rt = s->priv_data; + int ret, len; + RTSPStream *rtsp_st, *first_queue_st = NULL; + int64_t wait_end = 0; + + if (rt->nb_byes == rt->nb_rtsp_streams) + return AVERROR_EOF; + + /* 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; + } + + if (rt->transport == RTSP_TRANSPORT_RTP) { + int i; + int64_t first_queue_time = 0; + for (i = 0; i < rt->nb_rtsp_streams; i++) { + RTPDemuxContext *rtpctx = rt->rtsp_streams[i]->transport_priv; + int64_t queue_time = ff_rtp_queued_packet_time(rtpctx); + if (queue_time && (queue_time - first_queue_time < 0 || + !first_queue_time)) { + first_queue_time = queue_time; + first_queue_st = rt->rtsp_streams[i]; + } + } + if (first_queue_time) + wait_end = first_queue_time + s->max_delay; + } + + /* read next RTP packet */ + redo: + if (!rt->recvbuf) { + rt->recvbuf = av_malloc(RECVBUF_SIZE); + if (!rt->recvbuf) + return AVERROR(ENOMEM); + } + + switch(rt->lower_transport) { + default: +#if CONFIG_RTSP_DEMUXER + case RTSP_LOWER_TRANSPORT_TCP: + len = tcp_read_packet(s, &rtsp_st, rt->recvbuf, RECVBUF_SIZE); + break; +#endif + case RTSP_LOWER_TRANSPORT_UDP: + case RTSP_LOWER_TRANSPORT_UDP_MULTICAST: + len = udp_read_packet(s, &rtsp_st, rt->recvbuf, RECVBUF_SIZE, wait_end); + 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 == AVERROR(EAGAIN) && first_queue_st && + rt->transport == RTSP_TRANSPORT_RTP) { + rtsp_st = first_queue_st; + ret = rtp_parse_packet(rtsp_st->transport_priv, pkt, NULL, 0); + goto end; + } + 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, &rt->recvbuf, len); + } else { + ret = rtp_parse_packet(rtsp_st->transport_priv, pkt, &rt->recvbuf, len); + if (ret < 0) { + /* Either bad packet, or a RTCP packet. Check if the + * first_rtcp_ntp_time field was initialized. */ + RTPDemuxContext *rtpctx = rtsp_st->transport_priv; + if (rtpctx->first_rtcp_ntp_time != AV_NOPTS_VALUE) { + /* first_rtcp_ntp_time has been initialized for this stream, + * copy the same value to all other uninitialized streams, + * in order to map their timestamp origin to the same ntp time + * as this one. */ + int i; + for (i = 0; i < rt->nb_rtsp_streams; i++) { + RTPDemuxContext *rtpctx2 = rt->rtsp_streams[i]->transport_priv; + if (rtpctx2 && + rtpctx2->first_rtcp_ntp_time == AV_NOPTS_VALUE) + rtpctx2->first_rtcp_ntp_time = rtpctx->first_rtcp_ntp_time; + } + } + if (ret == -RTCP_BYE) { + rt->nb_byes++; + + av_log(s, AV_LOG_DEBUG, "Received BYE for stream %d (%d/%d)\n", + rtsp_st->stream_index, rt->nb_byes, rt->nb_rtsp_streams); + + if (rt->nb_byes == rt->nb_rtsp_streams) + return AVERROR_EOF; + } + } + } +end: + 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; +} + +#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; + + rt->real_setup_cache = av_mallocz(2 * s->nb_streams * sizeof(*rt->real_setup_cache)); + if (!rt->real_setup_cache) + return AVERROR(ENOMEM); + rt->real_setup = rt->real_setup_cache + s->nb_streams * sizeof(*rt->real_setup); + + if (ap->initial_pause) { + /* do not start immediately */ + } else { + if (rtsp_read_play(s) < 0) { + ff_rtsp_close_streams(s); + ff_rtsp_close_connections(s); + return AVERROR_INVALIDDATA; } } + + return 0; } static int tcp_read_packet(AVFormatContext *s, RTSPStream **prtsp_st, @@ -1652,9 +1877,9 @@ redo: for (;;) { RTSPMessageHeader reply; - ret = rtsp_read_reply(s, &reply, NULL, 1); - if (ret == -1) - return -1; + ret = ff_rtsp_read_reply(s, &reply, NULL, 1); + if (ret < 0) + return ret; if (ret == 1) /* received '$' */ break; /* XXX: parse message */ @@ -1691,62 +1916,6 @@ found: *prtsp_st = rtsp_st; return len; } - -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; @@ -1756,19 +1925,18 @@ static int rtsp_read_packet(AVFormatContext *s, AVPacket *pkt) if (rt->server_type == RTSP_SERVER_REAL) { int i; - enum AVDiscard cache[MAX_STREAMS]; for (i = 0; i < s->nb_streams; i++) - cache[i] = s->streams[i]->discard; + rt->real_setup[i] = s->streams[i]->discard; if (!rt->need_subscription) { - if (memcmp (cache, rt->real_setup_cache, + if (memcmp (rt->real_setup, rt->real_setup_cache, sizeof(enum AVDiscard) * s->nb_streams)) { snprintf(cmd, sizeof(cmd), - "SET_PARAMETER %s RTSP/1.0\r\n" "Unsubscribe: %s\r\n", - rt->control_uri, rt->last_subscription); - rtsp_send_cmd(s, cmd, reply, NULL); + rt->last_subscription); + ff_rtsp_send_cmd(s, "SET_PARAMETER", rt->control_uri, + cmd, reply, NULL); if (reply->status_code != RTSP_STATUS_OK) return AVERROR_INVALIDDATA; rt->need_subscription = 1; @@ -1778,14 +1946,12 @@ static int rtsp_read_packet(AVFormatContext *s, AVPacket *pkt) if (rt->need_subscription) { int r, rule_nr, first = 1; - memcpy(rt->real_setup_cache, cache, + memcpy(rt->real_setup_cache, rt->real_setup, sizeof(enum AVDiscard) * s->nb_streams); rt->last_subscription[0] = 0; snprintf(cmd, sizeof(cmd), - "SET_PARAMETER %s RTSP/1.0\r\n" - "Subscribe: ", - rt->control_uri); + "Subscribe: "); for (i = 0; i < rt->nb_rtsp_streams; i++) { rule_nr = 0; for (r = 0; r < s->nb_streams; r++) { @@ -1804,7 +1970,8 @@ static int rtsp_read_packet(AVFormatContext *s, AVPacket *pkt) } } av_strlcatf(cmd, sizeof(cmd), "%s\r\n", rt->last_subscription); - rtsp_send_cmd(s, cmd, reply, NULL); + ff_rtsp_send_cmd(s, "SET_PARAMETER", rt->control_uri, + cmd, reply, NULL); if (reply->status_code != RTSP_STATUS_OK) return AVERROR_INVALIDDATA; rt->need_subscription = 0; @@ -1819,16 +1986,11 @@ static int rtsp_read_packet(AVFormatContext *s, AVPacket *pkt) return ret; /* send dummy request to keep TCP connection alive */ - if ((rt->server_type == RTSP_SERVER_WMS || - rt->server_type == RTSP_SERVER_REAL) && - (av_gettime() - rt->last_cmd_time) / 1000000 >= rt->timeout / 2) { + if ((av_gettime() - rt->last_cmd_time) / 1000000 >= rt->timeout / 2) { if (rt->server_type == RTSP_SERVER_WMS) { - snprintf(cmd, sizeof(cmd) - 1, - "GET_PARAMETER %s RTSP/1.0\r\n", - rt->control_uri); - rtsp_send_cmd_async(s, cmd); + ff_rtsp_send_cmd_async(s, "GET_PARAMETER", rt->control_uri, NULL); } else { - rtsp_send_cmd_async(s, "OPTIONS * RTSP/1.0\r\n"); + ff_rtsp_send_cmd_async(s, "OPTIONS", "*", NULL); } } @@ -1840,17 +2002,11 @@ static int rtsp_read_pause(AVFormatContext *s) { RTSPState *rt = s->priv_data; RTSPMessageHeader reply1, *reply = &reply1; - char cmd[1024]; - - rt = s->priv_data; 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", - rt->control_uri); - rtsp_send_cmd(s, cmd, reply, NULL); + ff_rtsp_send_cmd(s, "PAUSE", rt->control_uri, NULL, reply, NULL); if (reply->status_code != RTSP_STATUS_OK) { return -1; } @@ -1888,7 +2044,6 @@ static int rtsp_read_seek(AVFormatContext *s, int stream_index, static int rtsp_read_close(AVFormatContext *s) { RTSPState *rt = s->priv_data; - char cmd[1024]; #if 0 /* NOTE: it is valid to flush the buffer here */ @@ -1896,13 +2051,13 @@ static int rtsp_read_close(AVFormatContext *s) url_fclose(&rt->rtsp_gb); } #endif - snprintf(cmd, sizeof(cmd), - "TEARDOWN %s RTSP/1.0\r\n", - s->filename); - rtsp_send_cmd_async(s, cmd); + ff_rtsp_send_cmd_async(s, "TEARDOWN", rt->control_uri, NULL); - rtsp_close_streams(s); - url_close(rt->rtsp_hd); + ff_rtsp_close_streams(s); + ff_rtsp_close_connections(s); + ff_network_close(); + rt->real_setup = NULL; + av_freep(&rt->real_setup_cache); return 0; } @@ -1919,16 +2074,16 @@ AVInputFormat rtsp_demuxer = { .read_play = rtsp_read_play, .read_pause = rtsp_read_pause, }; -#endif +#endif /* CONFIG_RTSP_DEMUXER */ static int sdp_probe(AVProbeData *p1) { const char *p = p1->buf, *p_end = p1->buf + p1->buf_size; - /* we look for a line beginning "c=IN IP4" */ + /* we look for a line beginning "c=IN IP" */ 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 IP") - 1 < p_end && + av_strstart(p, "c=IN IP", NULL)) return AVPROBE_SCORE_MAX / 2; while (p < p_end - 1 && *p != '\n') p++; @@ -1940,8 +2095,6 @@ static int sdp_probe(AVProbeData *p1) return 0; } -#define SDP_MAX_SIZE 8192 - static int sdp_read_header(AVFormatContext *s, AVFormatParameters *ap) { RTSPState *rt = s->priv_data; @@ -1950,6 +2103,9 @@ static int sdp_read_header(AVFormatContext *s, AVFormatParameters *ap) 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); @@ -1965,13 +2121,15 @@ static int sdp_read_header(AVFormatContext *s, AVFormatParameters *ap) /* open each RTP stream */ for (i = 0; i < rt->nb_rtsp_streams; i++) { + char namebuf[50]; 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); + getnameinfo((struct sockaddr*) &rtsp_st->sdp_ip, sizeof(rtsp_st->sdp_ip), + namebuf, sizeof(namebuf), NULL, 0, NI_NUMERICHOST); + ff_url_join(url, sizeof(url), "rtp", NULL, + namebuf, 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; @@ -1981,13 +2139,15 @@ static int sdp_read_header(AVFormatContext *s, AVFormatParameters *ap) } return 0; fail: - rtsp_close_streams(s); + ff_rtsp_close_streams(s); + ff_network_close(); return err; } static int sdp_read_close(AVFormatContext *s) { - rtsp_close_streams(s); + ff_rtsp_close_streams(s); + ff_network_close(); return 0; }