RTMPT protocol support
authorSamuel Pitoiset <samuel.pitoiset@gmail.com>
Sun, 17 Jun 2012 18:24:43 +0000 (20:24 +0200)
committerMartin Storsjö <martin@martin.st>
Sun, 17 Jun 2012 19:56:56 +0000 (22:56 +0300)
This adds two protocols, but one of them is an internal implementation
detail just used as an abstraction layer/generalization in the code. The
RTMPT protocol implementation uses rtmphttp:// as an alternative to the
tcp:// protocol. This allows moving most of the lower level logic out
from the higher level generic rtmp code.

Signed-off-by: Martin Storsjö <martin@martin.st>
Changelog
configure
doc/protocols.texi
libavformat/Makefile
libavformat/allformats.c
libavformat/rtmphttp.c [new file with mode: 0644]
libavformat/rtmpproto.c
libavformat/version.h

index b80ff88..4288aa3 100644 (file)
--- a/Changelog
+++ b/Changelog
@@ -25,6 +25,7 @@ version <next>:
   be used with -of old.
 - Indeo Audio decoder
 - channelsplit audio filter
+- RTMPT protocol support
 
 
 version 0.8:
index 4bb2030..d614366 100755 (executable)
--- a/configure
+++ b/configure
@@ -1511,6 +1511,10 @@ mmsh_protocol_select="http_protocol"
 mmst_protocol_deps="network"
 rtmp_protocol_deps="!librtmp_protocol"
 rtmp_protocol_select="tcp_protocol"
+rtmphttp_protocol_deps="!librtmp_protocol"
+rtmphttp_protocol_select="http_protocol"
+rtmpt_protocol_deps="!librtmp_protocol"
+rtmpt_protocol_select="rtmphttp_protocol"
 rtp_protocol_select="udp_protocol"
 sctp_protocol_deps="network netinet_sctp_h"
 tcp_protocol_deps="network"
index 8492033..0b4f1b1 100644 (file)
@@ -243,6 +243,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 through HTTP.
+
+The Real-Time Messaging Protocol tunneled through 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
index ca4f7a0..6262324 100644 (file)
@@ -345,6 +345,8 @@ 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_RTMPT_PROTOCOL)            += rtmpproto.o rtmppkt.o
 OBJS-$(CONFIG_RTP_PROTOCOL)              += rtpproto.o
 OBJS-$(CONFIG_SCTP_PROTOCOL)             += sctp.o
 OBJS-$(CONFIG_TCP_PROTOCOL)              += tcp.o
index 1320a28..42c588f 100644 (file)
@@ -256,6 +256,8 @@ void av_register_all(void)
     REGISTER_PROTOCOL (MD5,  md5);
     REGISTER_PROTOCOL (PIPE, pipe);
     REGISTER_PROTOCOL (RTMP, rtmp);
+    REGISTER_PROTOCOL (RTMPHTTP, rtmphttp);
+    REGISTER_PROTOCOL (RTMPT, rtmpt);
     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 (file)
index 0000000..fdcff50
--- /dev/null
@@ -0,0 +1,239 @@
+/*
+ * 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
+    int          initialized;       ///< flag indicating when the http context is initialized
+    int          finishing;         ///< flag indicating when the client closes the connection
+} 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 = (rt->out_size + size) * 2;
+        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) {
+            if (rt->finishing) {
+                /* Do not send new requests when the client wants to
+                 * close the connection. */
+                return AVERROR(EAGAIN);
+            }
+
+            /* 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;
+
+                if ((ret = rtmp_http_send_cmd(h, "idle")) < 0)
+                    return ret;
+            }
+
+            if (h->flags & AVIO_FLAG_NONBLOCK) {
+                /* no incoming data to handle in nonblocking mode */
+                return AVERROR(EAGAIN);
+            }
+        } 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 (rt->initialized) {
+        /* client wants to close the connection */
+        rt->finishing = 1;
+
+        do {
+            ret = rtmp_http_read(h, tmp_buf, sizeof(tmp_buf));
+        } while (ret > 0);
+
+        /* re-init output buffer before sending the close command */
+        rt->out_size = 0;
+
+        if ((ret = rtmp_http_write(h, "", 1)) == 1)
+            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)
+        goto fail;
+
+    /* set options */
+    snprintf(headers, sizeof(headers),
+             "Cache-Control: no-cache\r\n"
+             "Content-type: application/x-fcs\r\n"
+             "User-Agent: Shockwave Flash\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)
+        goto fail;
+
+    /* 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)
+            goto fail;
+        off += ret;
+        if (off == sizeof(rt->client_id)) {
+            ret = AVERROR(EIO);
+            goto fail;
+        }
+    }
+    while (off > 0 && isspace(rt->client_id[off - 1]))
+        off--;
+    rt->client_id[off] = '\0';
+
+    /* http context is now initialized */
+    rt->initialized = 1;
+    return 0;
+
+fail:
+    rtmp_http_close(h);
+    return ret;
+}
+
+URLProtocol ff_rtmphttp_protocol = {
+    .name           = "rtmphttp",
+    .url_open       = rtmp_http_open,
+    .url_read       = rtmp_http_read,
+    .url_write      = rtmp_http_write,
+    .url_close      = rtmp_http_close,
+    .priv_data_size = sizeof(RTMP_HTTPContext),
+    .flags          = URL_PROTOCOL_FLAG_NETWORK,
+};
index b3ae5a2..b3e2a30 100644 (file)
@@ -1112,9 +1112,15 @@ static int rtmp_open(URLContext *s, const char *uri, int flags)
     av_url_split(proto, sizeof(proto), NULL, 0, hostname, sizeof(hostname), &port,
                  path, sizeof(path), s->filename);
 
-    if (port < 0)
-        port = RTMP_DEFAULT_PORT;
-    ff_url_join(buf, sizeof(buf), "tcp", NULL, hostname, port, NULL);
+    if (!strcmp(proto, "rtmpt")) {
+        /* open the http tunneling connection */
+        ff_url_join(buf, sizeof(buf), "rtmphttp", NULL, hostname, port, NULL);
+    } else {
+        /* open the tcp connection */
+        if (port < 0)
+            port = RTMP_DEFAULT_PORT;
+        ff_url_join(buf, sizeof(buf), "tcp", NULL, hostname, port, NULL);
+    }
 
     if ((ret = ffurl_open(&rt->stream, buf, AVIO_FLAG_READ_WRITE,
                           &s->interrupt_callback, NULL)) < 0) {
@@ -1425,3 +1431,21 @@ URLProtocol ff_rtmp_protocol = {
     .flags          = URL_PROTOCOL_FLAG_NETWORK,
     .priv_data_class= &rtmp_class,
 };
+
+static const AVClass rtmpt_class = {
+    .class_name = "rtmpt",
+    .item_name  = av_default_item_name,
+    .option     = rtmp_options,
+    .version    = LIBAVUTIL_VERSION_INT,
+};
+
+URLProtocol ff_rtmpt_protocol = {
+    .name            = "rtmpt",
+    .url_open        = rtmp_open,
+    .url_read        = rtmp_read,
+    .url_write       = rtmp_write,
+    .url_close       = rtmp_close,
+    .priv_data_size  = sizeof(RTMPContext),
+    .flags           = URL_PROTOCOL_FLAG_NETWORK,
+    .priv_data_class = &rtmpt_class,
+};
index b00701e..aae0eb1 100644 (file)
@@ -30,8 +30,8 @@
 #include "libavutil/avutil.h"
 
 #define LIBAVFORMAT_VERSION_MAJOR 54
-#define LIBAVFORMAT_VERSION_MINOR  3
-#define LIBAVFORMAT_VERSION_MICRO  1
+#define LIBAVFORMAT_VERSION_MINOR  4
+#define LIBAVFORMAT_VERSION_MICRO  0
 
 #define LIBAVFORMAT_VERSION_INT AV_VERSION_INT(LIBAVFORMAT_VERSION_MAJOR, \
                                                LIBAVFORMAT_VERSION_MINOR, \