On Wed, 4 Jul 2012, Jordi Ortiz wrote:

---
libavformat/rtsp.c      |   30 ++-
libavformat/rtsp.h      |   12 ++
libavformat/rtspcodes.h |   14 ++
libavformat/rtspdec.c   |  549 ++++++++++++++++++++++++++++++++++++++++++++---
4 files changed, 566 insertions(+), 39 deletions(-)

diff --git a/libavformat/rtsp.c b/libavformat/rtsp.c
index d4206a1..f98dc6b 100644
--- a/libavformat/rtsp.c
+++ b/libavformat/rtsp.c
@@ -63,7 +63,8 @@

#define RTSP_FLAG_OPTS(name, longname) \
    { name, longname, OFFSET(rtsp_flags), AV_OPT_TYPE_FLAGS, {0}, INT_MIN, INT_MAX, DEC, 
"rtsp_flags" }, \
-    { "filter_src", "Only receive packets from the negotiated peer IP", 0, 
AV_OPT_TYPE_CONST, {RTSP_FLAG_FILTER_SRC}, 0, 0, DEC, "rtsp_flags" }
+    { "filter_src", "Only receive packets from the negotiated peer IP", 0, 
AV_OPT_TYPE_CONST, {RTSP_FLAG_FILTER_SRC}, 0, 0, DEC, "rtsp_flags" }, \
+    { "listen", "Wait for incoming connections", 0, AV_OPT_TYPE_CONST, 
{RTSP_FLAG_LISTEN}, 0, 0, DEC, "rtsp_flags" }

#define RTSP_MEDIATYPE_OPTS(name, longname) \
    { name, longname, OFFSET(media_type_mask), AV_OPT_TYPE_FLAGS, { (1 << 
(AVMEDIA_TYPE_DATA+1)) - 1 }, INT_MIN, INT_MAX, DEC, "allowed_media_types" }, \
@@ -83,6 +84,7 @@ const AVOption ff_rtsp_options[] = {
    RTSP_MEDIATYPE_OPTS("allowed_media_types", "Media types to accept from the 
server"),
    { "min_port", "Minimum local UDP port", OFFSET(rtp_port_min), 
AV_OPT_TYPE_INT, {RTSP_RTP_PORT_MIN}, 0, 65535, DEC|ENC },
    { "max_port", "Maximum local UDP port", OFFSET(rtp_port_max), 
AV_OPT_TYPE_INT, {RTSP_RTP_PORT_MAX}, 0, 65535, DEC|ENC },
+    { "timeout", "Maximum timeout (in seconds) to wait for incoming connections. -1 
is infinite. Implies flag listen", OFFSET(initial_timeout), AV_OPT_TYPE_INT, {-1}, INT_MIN, 
INT_MAX, DEC },
    { NULL },
};

@@ -1714,14 +1716,24 @@ static int udp_read_packet(AVFormatContext *s, 
RTSPStream **prtsp_st,
            }
#if CONFIG_RTSP_DEMUXER
            if (tcp_fd != -1 && p[0].revents & POLLIN) {
-                RTSPMessageHeader reply;
-
-                ret = ff_rtsp_read_reply(s, &reply, NULL, 0, NULL);
-                if (ret < 0)
-                    return ret;
-                /* XXX: parse message */
-                if (rt->state != RTSP_STATE_STREAMING)
-                    return 0;
+                if (rt->rtsp_flags & RTSP_FLAG_LISTEN) {
+                    if (rt->state == RTSP_STATE_STREAMING) {
+                        if (!ff_rtsp_parse_streaming_commands(s))
+                            return AVERROR_EOF;
+                        else
+                            av_log(s, AV_LOG_WARNING,
+                                   "Unable to answer to TEARDOWN\n");
+                    } else
+                        return 0;
+                } else {
+                    RTSPMessageHeader reply;
+                    ret = ff_rtsp_read_reply(s, &reply, NULL, 0, NULL);
+                    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) {
diff --git a/libavformat/rtsp.h b/libavformat/rtsp.h
index 41bf8bb..a1dbf33 100644
--- a/libavformat/rtsp.h
+++ b/libavformat/rtsp.h
@@ -372,11 +372,17 @@ typedef struct RTSPState {
     * Minimum and maximum local UDP ports.
     */
    int rtp_port_min, rtp_port_max;
+
+    /**
+     * Timeout to wait for incoming connections.
+     */
+    int initial_timeout;
} RTSPState;

#define RTSP_FLAG_FILTER_SRC  0x1    /**< Filter incoming UDP packets -
                                          receive packets only from the right
                                          source address and port. */
+#define RTSP_FLAG_LISTEN 0x2         /**< Wait for incoming connections. */

/**
 * Describe a single stream, as identified by a single m= line block in the
@@ -529,6 +535,12 @@ int ff_rtsp_setup_input_streams(AVFormatContext *s, 
RTSPMessageHeader *reply);
int ff_rtsp_setup_output_streams(AVFormatContext *s, const char *addr);

/**
+ * Parses RTSP commands (OPTIONS, PAUSE and TEARDOWN) on listen mode.
+ * @return 0 if SUCCESS and 1 if TEARDOWN Received
+ */
+int ff_rtsp_parse_streaming_commands(AVFormatContext *s);
+
+/**
 * Parse an SDP description of streams by populating an RTSPState struct
 * within the AVFormatContext; also allocate the RTP streams and the
 * pollfd array used for UDP streams.
diff --git a/libavformat/rtspcodes.h b/libavformat/rtspcodes.h
index 63ceb66..31ab336 100644
--- a/libavformat/rtspcodes.h
+++ b/libavformat/rtspcodes.h
@@ -37,4 +37,18 @@ RTSP_STATUS_SERVICE         =503, /**< Service Unavailable */
RTSP_STATUS_VERSION         =505, /**< RTSP Version not supported */
};

+enum RTSPMethod {
+    DESCRIBE,
+    ANNOUNCE,
+    OPTIONS,
+    SETUP,
+    PLAY,
+    PAUSE,
+    TEARDOWN,
+    GET_PARAMETER,
+    SET_PARAMETER,
+    REDIRECT,
+    RECORD,
+    UNKNOWN = -1,
+};
#endif /* AVFORMAT_RTSPCODES_H */
diff --git a/libavformat/rtspdec.c b/libavformat/rtspdec.c
index 6226f41..b603792 100644
--- a/libavformat/rtspdec.c
+++ b/libavformat/rtspdec.c
@@ -22,6 +22,7 @@
#include "libavutil/avstring.h"
#include "libavutil/intreadwrite.h"
#include "libavutil/mathematics.h"
+#include "libavutil/random_seed.h"
#include "avformat.h"

#include "internal.h"
@@ -31,11 +32,30 @@
#include "rdt.h"
#include "url.h"

+static const struct RTSPStatusMessage {
+    enum RTSPStatusCode code;
+    const char *message;
+} status_messages[] = {
+    { RTSP_STATUS_OK,             "OK"                               },
+    { RTSP_STATUS_METHOD,         "Method Not Allowed"               },
+    { RTSP_STATUS_BANDWIDTH,      "Not Enough Bandwidth"             },
+    { RTSP_STATUS_SESSION,        "Session Not Found"                },
+    { RTSP_STATUS_STATE,          "Method Not Valid in This State"   },
+    { RTSP_STATUS_AGGREGATE,      "Aggregate operation not allowed"  },
+    { RTSP_STATUS_ONLY_AGGREGATE, "Only aggregate operation allowed" },
+    { RTSP_STATUS_TRANSPORT,      "Unsupported transport"            },
+    { RTSP_STATUS_INTERNAL,       "Internal Server Error"            },
+    { RTSP_STATUS_SERVICE,        "Service Unavailable"              },
+    { RTSP_STATUS_VERSION,        "RTSP Version not supported"       },
+    { 0,                          "NULL"                             }
+};
+
static int rtsp_read_close(AVFormatContext *s)
{
    RTSPState *rt = s->priv_data;

-    ff_rtsp_send_cmd_async(s, "TEARDOWN", rt->control_uri, NULL);
+    if (!(rt->rtsp_flags & RTSP_FLAG_LISTEN))
+        ff_rtsp_send_cmd_async(s, "TEARDOWN", rt->control_uri, NULL);

    ff_rtsp_close_streams(s);
    ff_rtsp_close_connections(s);
@@ -45,6 +65,403 @@ static int rtsp_read_close(AVFormatContext *s)
    return 0;
}

+static inline int read_line(AVFormatContext *s, char *rbuf, const int rbufsize,
+                            int *rbuflen)
+{
+    RTSPState *rt = s->priv_data;
+    int idx       = 0;
+    int ret       = 0;
+    *rbuflen      = 0;
+
+    do {
+        ret = ffurl_read_complete(rt->rtsp_hd, rbuf + idx, 1);
+        if (ret < 0)
+            return ret;
+        if (rbuf[idx] == '\r') {
+            /* Ignore */
+        } else if (rbuf[idx] == '\n') {
+            rbuf[idx] = '\0';
+            *rbuflen  = idx;
+            return 0;
+        } else
+            idx++;
+    } while (idx < rbufsize);
+    av_log(s, AV_LOG_ERROR, "Message too long\n");
+    return AVERROR(EFAULT);
+}
+
+static int rtsp_send_reply(AVFormatContext *s, enum RTSPStatusCode code,
+                           const char *extracontent, uint16_t seq)
+{
+    RTSPState *rt = s->priv_data;
+    char message[4096];
+    int index = 0;
+    while (status_messages[index].code) {
+        if (status_messages[index].code == code) {
+            snprintf(message, sizeof(message), "RTSP/1.0 %d %s\r\n",
+                     code, status_messages[index].message);
+            break;
+        }
+        index++;
+    }
+    if (!status_messages[index].code)
+        return AVERROR(EINVAL);
+    av_strlcatf(message, sizeof(message), "CSeq: %d\r\n", seq);
+    av_strlcatf(message, sizeof(message), "Server: LibAVFormat %d\r\n",
+                LIBAVFORMAT_VERSION_INT);
+    if (extracontent)
+        av_strlcat(message, extracontent, sizeof(message));
+    av_strlcat(message, "\r\n", sizeof(message));
+    av_dlog(s, "Sending response:\n%s", message);
+    ffurl_write(rt->rtsp_hd, message, strlen(message));
+
+    return 0;
+}
+
+static inline int check_sessionid(AVFormatContext *s,
+                                  unsigned char *session_id,
+                                  RTSPMessageHeader *request)
+{
+    if (session_id[0] && strcmp(session_id, request->session_id)) {
+        av_log(s, AV_LOG_ERROR, "Unexpected SessionId %s\n",
+               request->session_id);
+        rtsp_send_reply(s, RTSP_STATUS_SESSION, NULL, request->seq);
+        return AVERROR_STREAM_NOT_FOUND;
+    }
+    return 0;
+}
+
+static inline int rtsp_read_request(AVFormatContext *s,
+                                    RTSPMessageHeader *request,
+                                    const char *method)
+{
+    RTSPState *rt = s->priv_data;
+    char rbuf[1024];
+    int rbuflen, ret;
+    do {
+        ret = read_line(s, rbuf, sizeof(rbuf), &rbuflen);
+        if (ret)
+            return ret;
+        if (rbuflen > 1) {
+            av_dlog(s, "Parsing[%d]: %s", rbuflen, rbuf);
+            ff_rtsp_parse_line(request, rbuf, rt, method);
+        }
+    } while (rbuflen > 0);
+    if (request->seq != rt->seq + 1) {
+        av_log(s, AV_LOG_ERROR, "Unexpected Sequence number %d\n",
+               request->seq);
+        return AVERROR(EINVAL);
+    }
+    if (rt->session_id[0] && strcmp(method, "OPTIONS")) {
+        ret = check_sessionid(s, rt->session_id, request);
+        if (ret)
+            return ret;
+    }
+
+    return 0;
+}
+
+static int rtsp_read_announce(AVFormatContext *s)
+{
+    RTSPState *rt             = s->priv_data;
+    RTSPMessageHeader request = { 0 };
+    char sdp[4096];
+    int  ret;
+
+    ret = rtsp_read_request(s, &request, "ANNOUNCE");
+    if (ret)
+        return ret;
+    rt->seq++;
+    if (strcmp(request.content_type, "application/sdp")) {
+        av_log(s, AV_LOG_ERROR, "Unexpected content type %s\n",
+               request.content_type);
+        rtsp_send_reply(s, RTSP_STATUS_SERVICE, NULL, request.seq);
+        return AVERROR_OPTION_NOT_FOUND;
+    }
+    if (request.content_length && request.content_length < sizeof(sdp)) {
+        /* Read SDP */
+        if (ffurl_read_complete(rt->rtsp_hd, sdp, request.content_length)
+            < request.content_length) {
+            av_log(s, AV_LOG_ERROR,
+                   "Unable to get complete SDP Description in ANNOUNCE\n");
+            rtsp_send_reply(s, RTSP_STATUS_INTERNAL, NULL, request.seq);
+            return AVERROR(EIO);
+        }
+        sdp[request.content_length] = '\0';
+        av_log(s, AV_LOG_VERBOSE, "SDP: %s\n", sdp);
+        ret = ff_sdp_parse(s, sdp);
+        if (ret)
+            return ret;
+        rtsp_send_reply(s, RTSP_STATUS_OK, NULL, request.seq);
+        return 0;
+    }
+    av_log(s, AV_LOG_ERROR,
+           "Content-Length header value exceeds sdp allocated buffer (4KB)\n");
+    rtsp_send_reply(s, RTSP_STATUS_INTERNAL,
+                    "Content-Length exceeds buffer size", request.seq);
+    return AVERROR(E2BIG);
+}
+
+static int rtsp_read_options(AVFormatContext *s)
+{
+    RTSPState *rt             = s->priv_data;
+    RTSPMessageHeader request = { 0 };
+    int ret                   = 0;
+
+    /* Parsing headers */
+    ret = rtsp_read_request(s, &request, "OPTIONS");
+    if (ret)
+        return ret;
+    rt->seq++;
+    /* Send Reply */
+    rtsp_send_reply(s, RTSP_STATUS_OK,
+                    "Public: ANNOUNCE, PAUSE, SETUP, TEARDOWN, RECORD\r\n",
+                    request.seq);
+    return 0;
+}
+
+static int rtsp_read_setup(AVFormatContext *s, char* host, char *controlurl)
+{
+    RTSPState *rt             = s->priv_data;
+    RTSPMessageHeader request = { 0 };
+    int ret                   = 0;
+    char url[1024];
+    RTSPStream *rtsp_st;
+    char responseheaders[1024];
+    int localport    = -1;
+    int transportidx = 0;
+
+    ret = rtsp_read_request(s, &request, "SETUP");
+    if (ret)
+        return ret;
+    rt->seq++;
+    if (!request.nb_transports) {
+        av_log(s, AV_LOG_ERROR, "No transport defined in SETUP\n");
+        return AVERROR_INVALIDDATA;
+    }
+    for (transportidx = 0; transportidx < request.nb_transports;
+         transportidx++) {
+        if (!request.transports[transportidx].mode_record
+            || (request.transports[transportidx].lower_transport
+                != RTSP_LOWER_TRANSPORT_UDP &&
+                request.transports[transportidx].lower_transport
+                != RTSP_LOWER_TRANSPORT_TCP)) {
+            av_log(s, AV_LOG_ERROR, "mode=record/receive not set or transport"
+                   " protocol not supported (yet)\n");
+            return AVERROR_INVALIDDATA;
+        }
+    }
+    if (request.nb_transports > 1)
+        av_log(s, AV_LOG_WARNING, "More than one transport not supported, "
+               "using first of all\n");
+    if (strcmp(rt->rtsp_streams[0]->control_url,
+               controlurl)) {
+        av_log(s, AV_LOG_ERROR, "Unable to find requested track\n");
+        return AVERROR_STREAM_NOT_FOUND;
+    }
+    rtsp_st   = rt->rtsp_streams[0];
+    localport = rt->rtp_port_min;
+
+    if (request.transports[0].lower_transport
+        == RTSP_LOWER_TRANSPORT_TCP) {
+        rt->lower_transport = RTSP_LOWER_TRANSPORT_TCP;
+        if ((ret = ff_rtsp_open_transport_ctx(s, rtsp_st))) {
+            rtsp_send_reply(s, RTSP_STATUS_TRANSPORT, NULL, request.seq);
+            return ret;
+        }
+        snprintf(responseheaders, sizeof(responseheaders), "Transport: "
+                 "RTP/AVP/TCP;unicast;mode=receive;source=%s;interleaved=%d-%d"
+                 "\r\n", host, request.transports[0].interleaved_min,
+                 request.transports[0].interleaved_max);
+    } else {
+        do {
+            ff_url_join(url, sizeof(url), "rtp", NULL, host, localport, NULL);
+            av_dlog(s, "Opening: %s", url);
+            ret = ffurl_open(&rtsp_st->rtp_handle, url, AVIO_FLAG_READ_WRITE,
+                             &s->interrupt_callback, NULL);
+            if (ret)
+                localport += 2;
+        } while (ret || localport > rt->rtp_port_max);
+        if (localport > rt->rtp_port_max) {
+            rtsp_send_reply(s, RTSP_STATUS_TRANSPORT, NULL, request.seq);
+            return ret;
+        }
+
+        av_dlog(s, "Listening on: %d",
+                ff_rtp_get_local_rtp_port(rtsp_st->rtp_handle));
+        if ((ret = ff_rtsp_open_transport_ctx(s, rtsp_st))) {
+            rtsp_send_reply(s, RTSP_STATUS_TRANSPORT, NULL, request.seq);
+            return ret;
+        }
+
+        localport = ff_rtp_get_local_rtp_port(rtsp_st->rtp_handle);
+        snprintf(responseheaders, sizeof(responseheaders), "Transport: "
+                 "RTP/AVP/UDP;unicast;mode=receive;source=%s;"
+                 "client_port=%d-%d;server_port=%d-%d\r\n",
+                 host, request.transports[0].client_port_min,
+                 request.transports[0].client_port_max, localport,
+                 localport + 1);
+    }
+
+    /* Establish sessionid if not previously set */
+    /* Put this in a function? */
+    /* RFC 2326: Session Id must be at least 8 digits */
+    while (strlen(rt->session_id) < 8)
+        av_strlcatf(rt->session_id, 512, "%u", av_get_random_seed());
+
+    av_strlcatf(responseheaders, sizeof(responseheaders), "Session: %s\r\n",
+                rt->session_id);
+    /* Send Reply */
+    rtsp_send_reply(s, RTSP_STATUS_OK, responseheaders, request.seq);
+
+    rt->state = RTSP_STATE_PAUSED;
+    return 0;
+}
+
+static int rtsp_read_record(AVFormatContext *s)
+{
+    RTSPState *rt             = s->priv_data;
+    RTSPMessageHeader request = { 0 };
+    int ret                   = 0;
+    char responseheaders[1024];
+
+    ret = rtsp_read_request(s, &request, "RECORD");
+    if (ret)
+        return ret;
+    ret = check_sessionid(s, rt->session_id, &request);
+    if (ret)
+        return ret;
+    rt->seq++;
+    snprintf(responseheaders, sizeof(responseheaders), "Session: %s\r\n",
+             rt->session_id);
+    rtsp_send_reply(s, RTSP_STATUS_OK, responseheaders, request.seq);
+
+    rt->state = RTSP_STATE_STREAMING;
+    return 0;
+}
+
+static inline int parse_command_line(AVFormatContext *s, const char *line,
+                                     int linelen, char *uri, int urisize,
+                                     char *method, int methodsize,
+                                     enum RTSPMethod *methodcode)
+{
+    RTSPState *rt = s->priv_data;
+    const char *linept, *searchlinept;
+    linept = strchr(line, ' ');
+    if (linept - line > methodsize - 1) {
+        av_log(s, AV_LOG_ERROR, "Method string too long\n");
+        return AVERROR(EFAULT);
+    }
+    memcpy(method, line, linept - line);
+    method[linept - line] = '\0';
+    linept++;
+    if (!strcmp(method, "ANNOUNCE"))
+        *methodcode = ANNOUNCE;
+    else if (!strcmp(method, "OPTIONS"))
+        *methodcode = OPTIONS;
+    else if (!strcmp(method, "RECORD"))
+        *methodcode = RECORD;
+    else if (!strcmp(method, "SETUP"))
+        *methodcode = SETUP;
+    else if (!strcmp(method, "PAUSE"))
+        *methodcode = PAUSE;
+    else if (!strcmp(method, "TEARDOWN"))
+        *methodcode = TEARDOWN;
+    else
+        *methodcode = UNKNOWN;
+    /* Check method with the state  */
+    if (rt->state == RTSP_STATE_IDLE) {
+        if ((*methodcode != ANNOUNCE) && (*methodcode != OPTIONS)) {
+            av_log(s, AV_LOG_ERROR, "Unexpected command in Idle State %s\n",
+                   line);
+            return AVERROR_PROTOCOL_NOT_FOUND;
+        }
+    } else if (rt->state == RTSP_STATE_PAUSED) {
+        if ((*methodcode != OPTIONS) && (*methodcode != RECORD)
+            && (*methodcode != SETUP)) {
+            av_log(s, AV_LOG_ERROR, "Unexpected command in Paused State %s\n",
+                   line);
+            return AVERROR_PROTOCOL_NOT_FOUND;
+        }
+    } else if (rt->state == RTSP_STATE_STREAMING) {
+        if ((*methodcode != PAUSE) && (*methodcode != OPTIONS)
+            && (*methodcode != TEARDOWN)) {
+            av_log(s, AV_LOG_ERROR, "Unexpected command in Streaming State"
+                   " %s\n", line);
+            return AVERROR_PROTOCOL_NOT_FOUND;
+        }
+    }
+    searchlinept = strchr(linept, ' ');
+    if (searchlinept == NULL) {
+        av_log(s, AV_LOG_ERROR, "Error parsing message URI\n");
+        return AVERROR_INVALIDDATA;
+    }
+    if (searchlinept - linept > urisize - 1) {
+        av_log(s, AV_LOG_ERROR, "uri string length exceeded buffer size\n");
+        return AVERROR(EFAULT);
+    }
+    memcpy(uri, linept, searchlinept - linept);
+    uri[searchlinept - linept] = '\0';
+    if (!strcmp(rt->control_uri, uri)) {
+        av_dlog(s, "Warning received \"%s\" doesn't"
+                " look like \"%s\".\n",
+                uri, rt->control_uri);
+        if (*methodcode == ANNOUNCE) {
+            av_log(s, AV_LOG_INFO,
+                   "WARNING: Updating control URI to %s\n", uri);
+            strcpy(rt->control_uri, uri);
+        }

Here, the condition is inverted, now you warn (and update) only if it matches...

You could also choose to make the warning message for ANNOUNCE less dramatic.

If the server e.g. listens for rtsp://127.0.0.1:8554/foo1.sdp and I connect to it as rtsp://localhost:8554/foo1.sdp, then I more or less don't want any message at all, especially not anything with a big fat WARNING in it.

However, if the server listens for rtsp://127.0.0.1:8554/foo1.sdp and I connect to it as rtsp://127.0.0.1:8554/something_else.sdp, I more would appreciate a louder message. Perhaps not aborting the connection totally though, or that's totally dependent on the use case.

// Martin
_______________________________________________
libav-devel mailing list
[email protected]
https://lists.libav.org/mailman/listinfo/libav-devel

Reply via email to