On Sat, 16 Jun 2012, Samuel Pitoiset wrote:
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.
---
- Fix missing proper dependecies in configure
- Fix capacity of the output buffer in rtmp_http_write()
- Fix a case of ==
- Return EIO when off == sizeof(client_id)
Changelog | 1 +
configure | 4 +
doc/general.texi | 1 +
doc/protocols.texi | 8 ++
libavformat/Makefile | 1 +
libavformat/allformats.c | 2 +
libavformat/rtmphttp.c | 220 ++++++++++++++++++++++++++++++++++++++++++++++
libavformat/rtmpproto.c | 31 ++++++-
libavformat/version.h | 4 +-
9 files changed, 267 insertions(+), 5 deletions(-)
create mode 100644 libavformat/rtmphttp.c
diff --git a/Changelog b/Changelog
index b80ff88..4288aa3 100644
--- 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:
diff --git a/configure b/configure
index 4bb2030..d614366 100755
--- 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"
Strictly speaking, this isn't needed, you can build this protocol just
fine even if you enable librtmp. It's useless in that case though, so
perhaps it's better this way anyway.
+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"
diff --git a/doc/general.texi b/doc/general.texi
index 354067d..1c81774 100644
--- a/doc/general.texi
+++ b/doc/general.texi
@@ -811,6 +811,7 @@ performance on systems without hardware floating point
support).
@item MMS @tab X
@item pipe @tab X
@item RTMP @tab X
+@item RTMPT @tab X
@item RTP @tab X
@item TCP @tab X
@item UDP @tab X
diff --git a/doc/protocols.texi b/doc/protocols.texi
index 8492033..0b4f1b1 100644
--- a/doc/protocols.texi
+++ b/doc/protocols.texi
@@ -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
diff --git a/libavformat/Makefile b/libavformat/Makefile
index ca4f7a0..b8d93cd 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 rtmpproto.o rtmppkt.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..d9bf028
--- /dev/null
+++ b/libavformat/rtmphttp.c
@@ -0,0 +1,220 @@
+/*
+ * 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 state */
+typedef enum {
+ STATE_START, ///< client has not done anything yet
+ STATE_OPENED, ///< client has opened the connection
+ STATE_WAITING, ///< client is waiting for a server reply
+ STATE_READY, ///< client is ready for sending requests
+} ClientState;
+
+/* 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
+ ClientState state; ///< current state
+ 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 += (rt->out_size + size) * 2;
Using += here will make it grow a bit faster than what is necessary, a
plain = is enough.
+ 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;
+
+ 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;
+ int ret = 0;
+
+ if (rt->state > STATE_OPENED) {
+ // TODO: Add support for the nonblocking variant.
+ if ((ret = rtmp_http_write(h, "", 1)) > 0)
+ 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);
+
+ rt->state = STATE_START;
+ 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"
+ "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)
+ return ret;
This doesn't free stuff allocated so far in the function.
+
+ /* 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)
+ return ret;
+ if (off == sizeof(rt->client_id))
+ return AVERROR(EIO);
+ off += ret;
Please swap these lines, first off += ret, then check against the end of
the buffer. Otherwise, at the end of the buffer, you'll try to do a read
of 0 bytes before you reach the "buffer full" check, and I'm not sure
that's well defined currently.
+ }
+ while (off > 0 && isspace(rt->client_id[off - 1]))
+ rt->client_id[--off] = '\0';
Almost. If there is no whitespace at all at the end, this won't add any
null termination. You could either add a rt->client_id[off] = '\0'; above
it - this assumes that off < sizeof(rt->client_id), but you have a check
for that in the loop so it should be ok. Another version would be this:
while (off > 0 && isspace(rt->client_id[off - 1])
off--;
rt->client_id[off] = '\0';
I think that's the cleanest solution.
+ /* client has now opened the http tunneling connection */
+ rt->state = STATE_OPENED;
+
+ return 0;
+}
I think the state variable can be simplified. For example, STATE_START is
set at the start of this function, and set to STATE_OPENED at the end if
it succeeded. I think the main thing you need to keep track of is whether
you have an unfinished request or not.
The things you still need is:
- Nonblocking read. If you don't have any unfinished request, return
EAGAIN. If you have an unfinished request, try to read data from the
server. If the server returns EOF, it means there's no more data you can
immediately return to the caller, thus return EAGAIN. (In blocking read
mode, you should instead send queued data or send an idle request here,
but for nonblocking reads you just indicate that there's no more data to
read.)
- You need to readd a shutdown() function for flushing data to write. You
only need to call it in rtmp_write - for reading, it's totally enough when
we send queued data in rtmp_http_read. This shutdown/flush function will
send the queued data if there is no request ongoing, otherwise just
return without doing anything.
The point here is how all these will work together when publishing data.
Normally you just do a lot of writes - you queue data to send, then you'll
send it via the flush/shutdown call. Now you have an ongoing request, and
will not be able to send another request until you have finished this
request. Nothing will finish this request since you (previously) don't
read anything from the input.
Now the nonblocking read will save you - in most cases, the reply from the
server is empty. The nonblocking read will try to read data, will return
EOF, return EAGAIN to the caller. Now you don't have any more unfinished
request, and you will be able to send more data in a new request the next
time.
If the reply from the server isn't empty (once in a while, when the server
sends a ping), the nonblocking read will return the first byte of this to
the caller, and the normal blocking read will read the rest of the request
(possibly sending idle packets if needed and so on).
Also a final missing thing which I've told you before and you've changed a
few times but not got really right yet. At the end, you can't send the
'close' request until you have finished any previous unfinished request.
Therefore you'll need to read data in nonblocking mode, until there's no
more data to return (returning EAGAIN). Only then, you've finished the
previous request and can start sending the final 'close' request.
// Martin
_______________________________________________
libav-devel mailing list
libav-devel@libav.org
https://lists.libav.org/mailman/listinfo/libav-devel