On Wed, 4 Jul 2012, Jordi Ortiz wrote:

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);

This still isn't a good error code, strerror() will print it as "Bad address", which will be more misleading than helpful. If you don't find one that fits well, EIO is better than something misleading (while the error code name might sound almost right). Please change all EFAULT into EIO.

+}
+
+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);

I'd rather use LIBAVFORMAT_IDENT here

+    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",

Change "SessionId" into "session id" or similar

+               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);

You probably want a \n at the end of the debug line - especially since the newline isn't part of the returned buffer.

+            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)) {

I prefer if you leave the operator on the former line when splitting lines like this

+            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");

"using the first one" sounds better to me

+    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) {

Same thing here, keep the == on the first line

+        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);

I don't think you need to include source=%s here - when using TCP interleaving, the packets are sent within the TCP control connection, so they can't come from any other source than that.


+    } 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;
+        }
+    }

What happens if you're in any other state?

Yes, you might currently check that it isn't called in any other state. But that might change at some point, accidentally or deliberately. So adding a catch-all else clause here where you either reject all methods, or accept them (by having a comment saying you do that), this is much clearer.

+    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);

This line is split more than you need, I think.

+        if (*methodcode == ANNOUNCE) {
+            av_log(s, AV_LOG_INFO,
+                   "WARNING: Updating control URI to %s\n", uri);
+            strcpy(rt->control_uri, uri);
+        }
+    }
+
+    linept = searchlinept + 1;
+    if (!av_strstart(linept, "RTSP/1.0", NULL)) {
+        av_log(s, AV_LOG_ERROR, "Error parsing protocol or version\n");
+        return AVERROR_PROTOCOL_NOT_FOUND;
+    }
+    return 0;
+}
+
+int ff_rtsp_parse_streaming_commands(AVFormatContext *s)
+{
+    RTSPState *rt = s->priv_data;
+    unsigned char rbuf[4096];
+    unsigned char method[10];
+    char uri[500];
+    int ret;
+    int rbuflen               = 0;
+    RTSPMessageHeader request = { 0 };
+    enum RTSPMethod methodcode;
+
+    ret = read_line(s, rbuf, sizeof(rbuf), &rbuflen);
+    if (ret < 0)
+        return ret;
+    ret = parse_command_line(s, rbuf, rbuflen, uri, sizeof(uri), method,
+                             sizeof(method), &methodcode);
+    if (ret) {
+        av_log(s, AV_LOG_ERROR, "RTSP: Unexpected Command\n");
+        return ret;
+    }
+
+    ret = rtsp_read_request(s, &request, method);
+    if (ret)
+        return ret;
+    rt->seq++;
+    if (methodcode == PAUSE) {
+        rt->state = RTSP_STATE_PAUSED;
+        ret       = rtsp_send_reply(s, RTSP_STATUS_OK, NULL , request.seq);
+        // TODO: Missing date header in response
+    } else if (methodcode == OPTIONS)
+        ret = rtsp_send_reply(s, RTSP_STATUS_OK,
+                              "Public: ANNOUNCE, PAUSE, SETUP, TEARDOWN, "
+                              "RECORD\r\n", request.seq);
+    else if (methodcode == TEARDOWN) {

Please add braces here as well. It doesn't cost you any extra lines, and makes it easier to work with the code later.

+        rt->state = RTSP_STATE_IDLE;
+        ret       = rtsp_send_reply(s, RTSP_STATUS_OK, NULL , request.seq);
+        return 0;
+    }
+    return ret;
+}
+
static int rtsp_read_play(AVFormatContext *s)
{
    RTSPState *rt = s->priv_data;
@@ -58,7 +475,7 @@ static int rtsp_read_play(AVFormatContext *s)
    if (!(rt->server_type == RTSP_SERVER_REAL && rt->need_subscription)) {
        if (rt->transport == RTSP_TRANSPORT_RTP) {
            for (i = 0; i < rt->nb_rtsp_streams; i++) {
-                RTSPStream *rtsp_st = rt->rtsp_streams[i];
+                RTSPStream *rtsp_st     = rt->rtsp_streams[i];
                RTPDemuxContext *rtpctx = rtsp_st->transport_priv;
                if (!rtpctx)
                    continue;
@@ -157,6 +574,67 @@ int ff_rtsp_setup_input_streams(AVFormatContext *s, 
RTSPMessageHeader *reply)
    return 0;
}

+static int rtsp_listen(AVFormatContext *s)
+{
+    RTSPState *rt = s->priv_data;
+    char host[500], path[500], auth[128];
+    char uri[500];
+    int port;
+    char tcpname[500];
+    unsigned char rbuf[4096];
+    unsigned char method[10];
+    int rbuflen = 0;
+    int ret;
+    enum RTSPMethod methodcode;
+
+    /* extract hostname and port */
+    av_url_split(NULL, 0, auth, sizeof(auth),
+                 host, sizeof(host), &port, path, sizeof(path), s->filename);
+
+    /* ff_url_join. No authorization by now (NULL) */
+    ff_url_join(rt->control_uri, sizeof(rt->control_uri), "rtsp", NULL, host,
+                port, "%s", path);
+    /* Create TCP connection */
+    ff_url_join(tcpname, sizeof(tcpname), "tcp", NULL, host, port,
+                "?listen&listen_timeout=%d", rt->initial_timeout * 1000);

The timeout I was asking for at some point would be more than just this. What if I connect to this port, but don't send a single byte over the socket? Then you'll sit waiting for data forever, even if you had a timeout set for actually receiving a connection.

Implementing this can be a bit tricky though so I'd suggest we leave it out for now.


+
+    if (ret = ffurl_open(&rt->rtsp_hd, tcpname, AVIO_FLAG_READ_WRITE,
+                         &s->interrupt_callback, NULL)) {
+        av_log(s, AV_LOG_ERROR, "Unable to open RTSP for listening\n");
+        return ret;
+    }
+    rt->state       = RTSP_STATE_IDLE;
+    rt->rtsp_hd_out = rt->rtsp_hd;
+    for (;;) { /* Wait for incoming RTSP messages */
+        ret = read_line(s, rbuf, sizeof(rbuf), &rbuflen);
+        if (ret < 0)
+            return ret;
+        ret = parse_command_line(s, rbuf, rbuflen, uri, sizeof(uri), method,
+                                 sizeof(method), &methodcode);
+        if (ret) {
+            av_log(s, AV_LOG_ERROR, "RTSP: Unexpected Command\n");
+            return ret;
+        }
+
+        if (methodcode == ANNOUNCE) {
+            ret       = rtsp_read_announce(s);
+            rt->state = RTSP_STATE_PAUSED;
+        } else if (methodcode == OPTIONS)
+            ret = rtsp_read_options(s);
+        else if (methodcode == RECORD) {

Same here, add braces


Other than this, this looks quite promising. Please add a minor bump as part of this patch as well, since it's a new user-visible feature.

// Martin
_______________________________________________
libav-devel mailing list
libav-devel@libav.org
https://lists.libav.org/mailman/listinfo/libav-devel

Reply via email to