From 8e50c57dcb481479f2fd46f9bdb6a9776b0d9fa6 Mon Sep 17 00:00:00 2001 From: Samuel Pitoiset Date: Sun, 17 Jun 2012 20:24:43 +0200 Subject: [PATCH] RTMPT protocol support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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ö --- Changelog | 1 + configure | 4 + doc/protocols.texi | 8 ++ libavformat/Makefile | 2 + libavformat/allformats.c | 2 + libavformat/rtmphttp.c | 239 +++++++++++++++++++++++++++++++++++++++ libavformat/rtmpproto.c | 30 ++++- libavformat/version.h | 4 +- 8 files changed, 285 insertions(+), 5 deletions(-) create mode 100644 libavformat/rtmphttp.c diff --git a/Changelog b/Changelog index b80ff8885e..4288aa3cc5 100644 --- a/Changelog +++ b/Changelog @@ -25,6 +25,7 @@ version : be used with -of old. - Indeo Audio decoder - channelsplit audio filter +- RTMPT protocol support version 0.8: diff --git a/configure b/configure index 4bb20301e7..d614366b2d 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" +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/protocols.texi b/doc/protocols.texi index 84920332f6..0b4f1b1772 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 ca4f7a02e1..6262324830 100644 --- a/libavformat/Makefile +++ b/libavformat/Makefile @@ -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 diff --git a/libavformat/allformats.c b/libavformat/allformats.c index 1320a28ac6..42c588f294 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 (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 index 0000000000..fdcff50bed --- /dev/null +++ b/libavformat/rtmphttp.c @@ -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, +}; diff --git a/libavformat/rtmpproto.c b/libavformat/rtmpproto.c index b3ae5a21e6..b3e2a30f48 100644 --- a/libavformat/rtmpproto.c +++ b/libavformat/rtmpproto.c @@ -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, +}; diff --git a/libavformat/version.h b/libavformat/version.h index b00701eefa..aae0eb1d70 100644 --- a/libavformat/version.h +++ b/libavformat/version.h @@ -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, \