On Mon, 11 Jun 2012, Samuel Pitoiset wrote:

diff --git a/Changelog b/Changelog
index 898a247..d901923 100644
--- a/Changelog
+++ b/Changelog
@@ -24,6 +24,7 @@ version <next>:
- avprobe output is now standard INI or JSON. The old format can still
  be used with -of old.
- Indeo Audio decoder
+- RTMPT support


version 0.8:
diff --git a/doc/general.texi b/doc/general.texi
index dee8f9e..29ef864 100644
--- a/doc/general.texi
+++ b/doc/general.texi
@@ -810,6 +810,7 @@ performance on systems without hardware floating point 
support).
@item HTTP         @tab X
@item MMS          @tab X
@item pipe         @tab X
+@item RTMPT        @tab x
@item RTP          @tab X

Since the normal RTMP isn't mentioned here, this looks a bit strange - please add it first (in a separate patch).

@item TCP          @tab X
@item UDP          @tab X
diff --git a/doc/protocols.texi b/doc/protocols.texi
index 172184e..eabb26d 100644
--- a/doc/protocols.texi
+++ b/doc/protocols.texi
@@ -193,6 +193,14 @@ For example to read with @command{avplay} a multimedia 
resource named
avplay rtmp://myserver/vod/sample
@end example

+@section rtmpt
+
+Real-Time Messaging Protocol tunneled in HTTP.
+
+The Real-Time Messaging Protocol tunneled in HTTP (RTMPT) is used
+for streaming multimedia content within HTTP requests to traverse
+firewalls.
+
@section rtmp, rtmpe, rtmps, rtmpt, rtmpte

Real-Time Messaging Protocol and its variants supported through
diff --git a/libavformat/Makefile b/libavformat/Makefile
index ca4f7a0..5f1cafb 100644
--- a/libavformat/Makefile
+++ b/libavformat/Makefile
@@ -345,6 +345,7 @@ OBJS-$(CONFIG_MMST_PROTOCOL)             += mmst.o mms.o 
asf.o
OBJS-$(CONFIG_MD5_PROTOCOL)              += md5proto.o
OBJS-$(CONFIG_PIPE_PROTOCOL)             += file.o
OBJS-$(CONFIG_RTMP_PROTOCOL)             += rtmpproto.o rtmppkt.o
+OBJS-$(CONFIG_RTMPHTTP_PROTOCOL)         += rtmphttp.o
OBJS-$(CONFIG_RTP_PROTOCOL)              += rtpproto.o
OBJS-$(CONFIG_SCTP_PROTOCOL)             += sctp.o
OBJS-$(CONFIG_TCP_PROTOCOL)              += tcp.o
diff --git a/libavformat/allformats.c b/libavformat/allformats.c
index 1320a28..69f27ab 100644
--- a/libavformat/allformats.c
+++ b/libavformat/allformats.c
@@ -256,6 +256,8 @@ void av_register_all(void)
    REGISTER_PROTOCOL (MD5,  md5);
    REGISTER_PROTOCOL (PIPE, pipe);
    REGISTER_PROTOCOL (RTMP, rtmp);
+    REGISTER_PROTOCOL (RTMPT, rtmpt);
+    REGISTER_PROTOCOL (RTMPHTTP, rtmphttp);
    REGISTER_PROTOCOL (RTP, rtp);
    REGISTER_PROTOCOL (SCTP, sctp);
    REGISTER_PROTOCOL (TCP, tcp);
diff --git a/libavformat/rtmphttp.c b/libavformat/rtmphttp.c
new file mode 100644
index 0000000..1e474a8
--- /dev/null
+++ b/libavformat/rtmphttp.c
@@ -0,0 +1,210 @@
+/*
+ * RTMP HTTP network protocol
+ * Copyright (c) 2012 Samuel Pitoiset
+ *
+ * This file is part of Libav.
+ *
+ * Libav is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * Libav is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with Libav; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+/**
+ * @file
+ * RTMP HTTP protocol
+ */
+
+#include "libavutil/avstring.h"
+#include "libavutil/intfloat.h"
+#include "libavutil/opt.h"
+#include "internal.h"
+#include "http.h"
+
+#define RTMPT_DEFAULT_PORT 80
+
+/* protocol handler context */
+typedef struct RTMP_HTTPContext {
+    URLContext   *stream;           ///< HTTP stream
+    char         host[256];         ///< hostname of the server
+    int          port;              ///< port to connect (default is 80)
+    char         client_id[64];     ///< client ID used for all requests 
except the first one
+    int          seq;               ///< sequence ID used for all requests
+    uint8_t      *out_data;         ///< output buffer
+    int          out_size;          ///< current output buffer size
+    int          out_capacity;      ///< current output buffer capacity
+} RTMP_HTTPContext;
+
+static int rtmp_http_send_cmd(URLContext *h, const char *cmd)
+{
+    RTMP_HTTPContext *rt = h->priv_data;
+    char uri[2048];
+    uint8_t c;
+    int ret;
+
+    ff_url_join(uri, sizeof(uri), "http", NULL, rt->host, rt->port,
+                "/%s/%s/%d", cmd, rt->client_id, rt->seq++);
+
+    av_opt_set_bin(rt->stream->priv_data, "post_data", rt->out_data,
+                   rt->out_size, 0);
+
+    /* send a new request to the server */
+    if ((ret = ff_http_do_new_request(rt->stream, uri)) < 0)
+        return ret;
+
+    /* re-init output buffer */
+    rt->out_size = 0;
+
+    /* read the first byte which contains the polling interval */
+    if ((ret = ffurl_read(rt->stream, &c, 1)) < 0)
+        return ret;
+
+    return ret;
+}
+
+static int rtmp_http_write(URLContext *h, const uint8_t *buf, int size)
+{
+    RTMP_HTTPContext *rt = h->priv_data;
+    void *ptr;
+
+    if (rt->out_size + size > rt->out_capacity) {
+        rt->out_capacity += size;

In code like this, one might want to increase the capacity with a bit more than the size (like += size*3/2, or by doubling capacity), to reduce the number of realloc calls.

+        ptr = av_realloc(rt->out_data, rt->out_capacity);
+        if (!ptr)
+            return AVERROR(ENOMEM);
+        rt->out_data = ptr;
+    }
+
+    memcpy(rt->out_data + rt->out_size, buf, size);
+    rt->out_size += size;
+
+    return size;
+}
+
+static int rtmp_http_read(URLContext *h, uint8_t *buf, int size)
+{
+    RTMP_HTTPContext *rt = h->priv_data;
+    int ret, off = 0;
+
+    /* try to read at least 1 byte of data */
+    do {
+        ret = ffurl_read(rt->stream, buf + off, size);
+        if (ret < 0 && ret != AVERROR_EOF)
+            return ret;
+
+        if (ret == AVERROR_EOF) {
+            /* When the client has reached end of file for the last request,
+             * we have to send a new request if we have buffered data.
+             * Otherwise, we have to send an idle POST. */
+            if (rt->out_size > 0) {
+                if ((ret = rtmp_http_send_cmd(h, "send")) < 0)
+                    return ret;
+            } else {
+                if ((ret == rtmp_http_write(h, "", 1)) < 0)
+                    return ret;

Note that you've used == instead of = here, this won't do the right thing. (Some compiler perhaps might even warn that the comparison won't ever be true.) The same mistake seems to be repeated a few times - make sure you find and fix all of them.

+
+                if ((ret == rtmp_http_send_cmd(h, "idle")) < 0)
+                    return ret;
+            }
+        } else {
+            off  += ret;
+            size -= ret;
+        }
+    } while (off <= 0);
+
+    return off;
+}
+
+static int rtmp_http_close(URLContext *h)
+{
+    RTMP_HTTPContext *rt = h->priv_data;
+    uint8_t tmp_buf[2048];
+    int ret = 0;
+
+    if ((ret == rtmp_http_write(h, "", 1)) > 0) {
+        for (;;) {
+            /* consume data from the current request */
+            ret = rtmp_http_read(h, tmp_buf, sizeof(tmp_buf));
+            if (ret < 0)
+                break;
+        }

Does this work correctly? Won't rtmp_http_read just keep sending new idle posts, to which the server will respond with empty replies, looping infinitely here? To do this properly I think you'll have to reintroduce the state variable saying whether you still have an ongoing request or not, and you might need to add support for the nonblocking variant of reading. Otherwise, rtmp_http_read will only return once there's at least 1 byte to return, and the server won't break the connection.

+        ret = rtmp_http_send_cmd(h, "close");
+    }
+
+    av_freep(&rt->out_data);
+    ffurl_close(rt->stream);
+
+    return ret;
+}
+
+static int rtmp_http_open(URLContext *h, const char *uri, int flags)
+{
+    RTMP_HTTPContext *rt = h->priv_data;
+    char headers[1024], url[1024];
+    int ret, off = 0;
+
+    av_url_split(NULL, 0, NULL, 0, rt->host, sizeof(rt->host), &rt->port,
+                 NULL, 0, uri);
+
+    if (rt->port < 0)
+        rt->port = RTMPT_DEFAULT_PORT;
+
+    /* This is the first request that is sent to the server in order to
+     * register a client on the server and start a new session. The server
+     * replies with a unique id (usually a number) that is used by the client
+     * for all future requests.
+     * Note: the reply doesn't contain a value for the polling interval.
+     * A successful connect resets the consecutive index that is used
+     * in the URLs. */
+    ff_url_join(url, sizeof(url), "http", NULL, rt->host, rt->port, "/open/1");
+
+    /* alloc the http context */
+    if ((ret = ffurl_alloc(&rt->stream, url, AVIO_FLAG_READ_WRITE, NULL)) < 0)
+        return ret;
+
+    /* set options */
+    snprintf(headers, sizeof(headers),
+             "Cache-Control: no-cache\r\n"
+             "Content-type: application/x-fcs\r\n");
+    av_opt_set(rt->stream->priv_data, "headers", headers, 0);
+    av_opt_set(rt->stream->priv_data, "multiple_requests", "1", 0);
+    av_opt_set_bin(rt->stream->priv_data, "post_data", "", 1, 0);
+
+    /* open the http context */
+    if ((ret = ffurl_connect(rt->stream, NULL)) < 0)
+        return ret;
+
+    /* read the server reply which contains a unique ID */
+    for (;;) {
+        ret = ffurl_read(rt->stream, rt->client_id + off, 
sizeof(rt->client_id) - off);
+        if (ret == AVERROR_EOF)
+            break;
+        if (ret < 0 || off == sizeof(rt->client_id)) {
+            rtmp_http_close(h);
+            return ret;
+        }
+        off += ret;
+    }
+    rt->client_id[off - 1] = '\0';

This will write out of bounds if off == 0. Also, I've twice requested you to do this newline trimming more generally, by trimming all whitespace chars at the end of the string. Please either do that or respond saying why you didn't do it.


The patch also still will break if publishing data, since the general rtmp proto lacks readyness for that, and you need to implement the "nonblocking" read mode in this proto, which I described earlier.

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

Reply via email to