/* needed by inet_aton() */
#define _SVID_SOURCE
+#include "libavutil/base64.h"
#include "libavutil/avstring.h"
#include "libavutil/intreadwrite.h"
#include "avformat.h"
#include "rtpdec.h"
#include "rdt.h"
#include "rtp_asf.h"
+#include "rtp_vorbis.h"
//#define DEBUG
//#define DEBUG_RTP_TCP
static int rtsp_read_play(AVFormatContext *s);
-/* XXX: currently, the only way to change the protocols consists in
- changing this variable */
-
#if LIBAVFORMAT_VERSION_INT < (53 << 16)
int rtsp_default_protocols = (1 << RTSP_LOWER_TRANSPORT_UDP);
#endif
}
#define SPACE_CHARS " \t\r\n"
-#define redir_isspace(c) strchr(SPACE_CHARS, c)
+/* 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;
v = 1;
for(;;) {
skip_spaces(&p);
- if (p == '\0')
+ if (*p == '\0')
break;
c = toupper((unsigned char)*p++);
if (c >= '0' && c <= '9')
return len;
}
-static void sdp_parse_fmtp_config(AVCodecContext *codec, char *attr, char *value)
+static void sdp_parse_fmtp_config(AVCodecContext * codec, void *ctx,
+ char *attr, char *value)
{
switch (codec->codec_id) {
case CODEC_ID_MPEG4:
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;
hex_to_data(codec->extradata, value);
}
break;
+ case CODEC_ID_VORBIS:
+ ff_vorbis_parse_fmtp_config(codec, ctx, attr, value);
+ break;
default:
break;
}
int 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)++;
static void sdp_parse_fmtp(AVStream *st, const char *p)
{
char attr[256];
- char value[4096];
+ /* 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;
while(rtsp_next_attr_and_value(&p, attr, sizeof(attr), value, sizeof(value)))
{
/* grab the codec extra_data from the config parameter of the fmtp line */
- sdp_parse_fmtp_config(codec, attr, value);
+ 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)) {
struct in_addr sdp_ip;
int ttl;
-#ifdef DEBUG
- printf("sdp: %c='%s'\n", letter, buf);
-#endif
+ dprintf(s, "sdp: %c='%s'\n", letter, buf);
p = buf;
if (s1->skip_media && letter != 'm')
* contain long SDP lines containing complete ASF Headers (several
* kB) or arrays of MDPR (RM stream descriptor) headers plus
* "rulebooks" describing their properties. Therefore, the SDP line
- * buffer is large. */
- char buf[8192], *q;
+ * buffer is large.
+ *
+ * The Vorbis FMTP line can be up to 16KB - see sdp_parse_fmtp. */
+ char buf[16384], *q;
SDPParseState sdp_parse_state, *s1 = &sdp_parse_state;
memset(s1, 0, sizeof(SDPParseState));
get_word_sep(transport_protocol, sizeof(transport_protocol),
"/", &p);
- if (*p == '/')
- p++;
if (!strcasecmp (transport_protocol, "rtp")) {
get_word_sep(profile, sizeof(profile), "/;,", &p);
lower_transport[0] = '\0';
/* rtp/avp/<protocol> */
if (*p == '/') {
- p++;
get_word_sep(lower_transport, sizeof(lower_transport),
";,", &p);
}
/* NOTE: we do case independent match for broken servers */
p = buf;
if (av_stristart(p, "Session:", &p)) {
+ int t;
get_word_sep(reply->session_id, sizeof(reply->session_id), ";", &p);
+ if (av_stristart(p, ";timeout=", &p) &&
+ (t = strtol(p, NULL, 10)) > 0) {
+ reply->timeout = t;
+ }
} else if (av_stristart(p, "Content-Length:", &p)) {
reply->content_length = strtol(p, NULL, 10);
} else if (av_stristart(p, "Transport:", &p)) {
} else if (av_stristart(p, "Server:", &p)) {
skip_spaces(&p);
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);
}
}
-static int url_readbuf(URLContext *h, unsigned char *buf, int size)
-{
- int ret, len;
-
- len = 0;
- while (len < size) {
- ret = url_read(h, buf+len, size-len);
- if (ret < 1)
- return ret;
- len += ret;
- }
- return len;
-}
-
/* skip a RTP/TCP interleaved packet */
static void rtsp_skip_packet(AVFormatContext *s)
{
int ret, len, len1;
uint8_t buf[1024];
- ret = url_readbuf(rt->rtsp_hd, buf, 3);
+ ret = url_read_complete(rt->rtsp_hd, buf, 3);
if (ret != 3)
return;
len = AV_RB16(buf + 1);
-#ifdef DEBUG
- printf("skipping RTP packet len=%d\n", len);
-#endif
+
+ dprintf(s, "skipping RTP packet len=%d\n", len);
+
/* skip payload */
while (len > 0) {
len1 = len;
if (len1 > sizeof(buf))
len1 = sizeof(buf);
- ret = url_readbuf(rt->rtsp_hd, buf, len1);
+ ret = url_read_complete(rt->rtsp_hd, buf, len1);
if (ret != len1)
return;
len -= len1;
for(;;) {
q = buf;
for(;;) {
- ret = url_readbuf(rt->rtsp_hd, &ch, 1);
+ ret = url_read_complete(rt->rtsp_hd, &ch, 1);
#ifdef DEBUG_RTP_TCP
- printf("ret=%d c=%02x [%c]\n", ret, ch, ch);
+ dprintf(s, "ret=%d c=%02x [%c]\n", ret, ch, ch);
#endif
if (ret != 1)
return -1;
}
}
*q = '\0';
-#ifdef DEBUG
- printf("line='%s'\n", buf);
-#endif
+
+ dprintf(s, "line='%s'\n", buf);
+
/* test if last line */
if (buf[0] == '\0')
break;
if (content_length > 0) {
/* leave some room for a trailing '\0' (useful for simple parsing) */
content = av_malloc(content_length + 1);
- (void)url_readbuf(rt->rtsp_hd, content, content_length);
+ (void)url_read_complete(rt->rtsp_hd, content, content_length);
content[content_length] = '\0';
}
if (content_ptr)
else
av_free(content);
+ /* EOS */
+ if (reply->notice == 2101 /* End-of-Stream Reached */ ||
+ reply->notice == 2104 /* Start-of-Stream Reached */ ||
+ reply->notice == 2306 /* Continuous Feed Terminated */)
+ rt->state = RTSP_STATE_IDLE;
+ else if (reply->notice >= 4400 && reply->notice < 5500)
+ return AVERROR(EIO); /* data or server error */
+ 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(AVFormatContext *s,
+static void rtsp_send_cmd_async (AVFormatContext *s,
const char *cmd, RTSPMessageHeader *reply,
unsigned char **content_ptr)
{
snprintf(buf1, sizeof(buf1), "Session: %s\r\n", rt->session_id);
av_strlcat(buf, buf1, sizeof(buf));
}
+ if (rt->auth_b64)
+ av_strlcatf(buf, sizeof(buf),
+ "Authorization: Basic %s\r\n",
+ rt->auth_b64);
av_strlcat(buf, "\r\n", sizeof(buf));
-#ifdef DEBUG
- printf("Sending:\n%s--\n", buf);
-#endif
+
+ dprintf(s, "Sending:\n%s--\n", buf);
+
url_write(rt->rtsp_hd, buf, strlen(buf));
+ rt->last_cmd_time = av_gettime();
+}
+
+static void rtsp_send_cmd (AVFormatContext *s,
+ const char *cmd, RTSPMessageHeader *reply,
+ unsigned char **content_ptr)
+{
+ rtsp_send_cmd_async(s, cmd, reply, content_ptr);
rtsp_read_reply(s, reply, content_ptr, 0);
}
av_close_input_stream (rt->asf_ctx);
rt->asf_ctx = NULL;
}
+ av_freep(&rt->auth_b64);
}
static int
else
trans_pref = "RTP/AVP";
+ /* default timeout: 1 minute */
+ rt->timeout = 60;
+
/* for each stream, make the setup request */
/* XXX: we assume the same server is used for the control of each
RTSP stream */
goto fail;
}
+ if (reply->timeout > 0)
+ rt->timeout = reply->timeout;
+
if (rt->server_type == RTSP_SERVER_REAL)
rt->need_subscription = 1;
AVFormatParameters *ap)
{
RTSPState *rt = s->priv_data;
- char host[1024], path[1024], tcpname[1024], cmd[2048], *option_list, *option;
+ char host[1024], path[1024], tcpname[1024], cmd[2048], auth[128], *option_list, *option;
URLContext *rtsp_hd;
int port, ret, err;
RTSPMessageHeader reply1, *reply = &reply1;
char real_challenge[64];
/* extract hostname and port */
- url_split(NULL, 0, NULL, 0,
+ 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;
+ }
+ }
if (port < 0)
port = RTSP_DEFAULT_PORT;
/* open the tcp connexion */
snprintf(tcpname, sizeof(tcpname), "tcp://%s:%d", host, port);
- if (url_open(&rtsp_hd, tcpname, URL_RDWR) < 0)
- return AVERROR(EIO);
+ if (url_open(&rtsp_hd, tcpname, URL_RDWR) < 0) {
+ err = AVERROR(EIO);
+ goto fail;
+ }
rt->rtsp_hd = rtsp_hd;
rt->seq = 0;
rtsp_close_streams(rt);
av_freep(&content);
url_close(rt->rtsp_hd);
+ av_freep(&rt->auth_b64);
return err;
}
RTSPStream *rtsp_st;
#ifdef DEBUG_RTP_TCP
- printf("tcp_read_packet:\n");
+ dprintf(s, "tcp_read_packet:\n");
#endif
redo:
for(;;) {
if (ret == 1) /* received '$' */
break;
/* XXX: parse message */
+ if (rt->state != RTSP_STATE_PLAYING)
+ return 0;
}
- ret = url_readbuf(rt->rtsp_hd, buf, 3);
+ ret = url_read_complete(rt->rtsp_hd, buf, 3);
if (ret != 3)
return -1;
id = buf[0];
len = AV_RB16(buf + 1);
#ifdef DEBUG_RTP_TCP
- printf("id=%d len=%d\n", id, len);
+ dprintf(s, "id=%d len=%d\n", id, len);
#endif
if (len > buf_size || len < 12)
goto redo;
/* get the data */
- ret = url_readbuf(rt->rtsp_hd, buf, len);
+ ret = url_read_complete(rt->rtsp_hd, buf, len);
if (ret != len)
return -1;
if (rt->transport == RTSP_TRANSPORT_RDT &&
if (url_interrupt_cb())
return AVERROR(EINTR);
FD_ZERO(&rfds);
- tcp_fd = fd_max = url_get_file_handle(rt->rtsp_hd);
- FD_SET(tcp_fd, &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) {
rtsp_read_reply(s, &reply, NULL, 0);
/* XXX: parse message */
+ if (rt->state != RTSP_STATE_PLAYING)
+ return 0;
}
}
}
RTSPStream *rtsp_st;
int ret, len;
uint8_t buf[10 * RTP_MAX_PACKET_LENGTH];
+ RTSPMessageHeader reply1, *reply = &reply1;
+ char cmd[1024];
if (rt->server_type == RTSP_SERVER_REAL) {
int i;
- RTSPMessageHeader reply1, *reply = &reply1;
enum AVDiscard cache[MAX_STREAMS];
- char cmd[1024];
for (i = 0; i < s->nb_streams; i++)
cache[i] = s->streams[i]->discard;
}
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
/* more packets may follow, so we save the RTP context */
rt->cur_transport_priv = rtsp_st->transport_priv;
}
+
+ /* 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 (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);
+ } else {
+ rtsp_send_cmd_async(s, "OPTIONS * RTSP/1.0\r\n",
+ reply, NULL);
+ }
+ }
+
return 0;
}
case RTSP_STATE_IDLE:
break;
case RTSP_STATE_PLAYING:
+ if (rtsp_read_pause(s) != 0)
+ return -1;
+ rt->state = RTSP_STATE_SEEKING;
if (rtsp_read_play(s) != 0)
return -1;
break;
{
const char *p;
p = pd->buf;
- while (redir_isspace(*p))
- p++;
+ skip_spaces(&p);
if (av_strstart(p, "http://", NULL) ||
av_strstart(p, "rtsp://", NULL))
return AVPROBE_SCORE_MAX;