Browse code

RTMPT protocol support

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>

Samuel Pitoiset authored on 2012/06/18 03:24:43
Showing 8 changed files
... ...
@@ -25,6 +25,7 @@ version <next>:
25 25
   be used with -of old.
26 26
 - Indeo Audio decoder
27 27
 - channelsplit audio filter
28
+- RTMPT protocol support
28 29
 
29 30
 
30 31
 version 0.8:
... ...
@@ -1511,6 +1511,10 @@ mmsh_protocol_select="http_protocol"
1511 1511
 mmst_protocol_deps="network"
1512 1512
 rtmp_protocol_deps="!librtmp_protocol"
1513 1513
 rtmp_protocol_select="tcp_protocol"
1514
+rtmphttp_protocol_deps="!librtmp_protocol"
1515
+rtmphttp_protocol_select="http_protocol"
1516
+rtmpt_protocol_deps="!librtmp_protocol"
1517
+rtmpt_protocol_select="rtmphttp_protocol"
1514 1518
 rtp_protocol_select="udp_protocol"
1515 1519
 sctp_protocol_deps="network netinet_sctp_h"
1516 1520
 tcp_protocol_deps="network"
... ...
@@ -243,6 +243,14 @@ For example to read with @command{avplay} a multimedia resource named
243 243
 avplay rtmp://myserver/vod/sample
244 244
 @end example
245 245
 
246
+@section rtmpt
247
+
248
+Real-Time Messaging Protocol tunneled through HTTP.
249
+
250
+The Real-Time Messaging Protocol tunneled through HTTP (RTMPT) is used
251
+for streaming multimedia content within HTTP requests to traverse
252
+firewalls.
253
+
246 254
 @section rtmp, rtmpe, rtmps, rtmpt, rtmpte
247 255
 
248 256
 Real-Time Messaging Protocol and its variants supported through
... ...
@@ -345,6 +345,8 @@ OBJS-$(CONFIG_MMST_PROTOCOL)             += mmst.o mms.o asf.o
345 345
 OBJS-$(CONFIG_MD5_PROTOCOL)              += md5proto.o
346 346
 OBJS-$(CONFIG_PIPE_PROTOCOL)             += file.o
347 347
 OBJS-$(CONFIG_RTMP_PROTOCOL)             += rtmpproto.o rtmppkt.o
348
+OBJS-$(CONFIG_RTMPHTTP_PROTOCOL)         += rtmphttp.o
349
+OBJS-$(CONFIG_RTMPT_PROTOCOL)            += rtmpproto.o rtmppkt.o
348 350
 OBJS-$(CONFIG_RTP_PROTOCOL)              += rtpproto.o
349 351
 OBJS-$(CONFIG_SCTP_PROTOCOL)             += sctp.o
350 352
 OBJS-$(CONFIG_TCP_PROTOCOL)              += tcp.o
... ...
@@ -256,6 +256,8 @@ void av_register_all(void)
256 256
     REGISTER_PROTOCOL (MD5,  md5);
257 257
     REGISTER_PROTOCOL (PIPE, pipe);
258 258
     REGISTER_PROTOCOL (RTMP, rtmp);
259
+    REGISTER_PROTOCOL (RTMPHTTP, rtmphttp);
260
+    REGISTER_PROTOCOL (RTMPT, rtmpt);
259 261
     REGISTER_PROTOCOL (RTP, rtp);
260 262
     REGISTER_PROTOCOL (SCTP, sctp);
261 263
     REGISTER_PROTOCOL (TCP, tcp);
262 264
new file mode 100644
... ...
@@ -0,0 +1,239 @@
0
+/*
1
+ * RTMP HTTP network protocol
2
+ * Copyright (c) 2012 Samuel Pitoiset
3
+ *
4
+ * This file is part of Libav.
5
+ *
6
+ * Libav is free software; you can redistribute it and/or
7
+ * modify it under the terms of the GNU Lesser General Public
8
+ * License as published by the Free Software Foundation; either
9
+ * version 2.1 of the License, or (at your option) any later version.
10
+ *
11
+ * Libav is distributed in the hope that it will be useful,
12
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14
+ * Lesser General Public License for more details.
15
+ *
16
+ * You should have received a copy of the GNU Lesser General Public
17
+ * License along with Libav; if not, write to the Free Software
18
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
19
+ */
20
+
21
+/**
22
+ * @file
23
+ * RTMP HTTP protocol
24
+ */
25
+
26
+#include "libavutil/avstring.h"
27
+#include "libavutil/intfloat.h"
28
+#include "libavutil/opt.h"
29
+#include "internal.h"
30
+#include "http.h"
31
+
32
+#define RTMPT_DEFAULT_PORT 80
33
+
34
+/* protocol handler context */
35
+typedef struct RTMP_HTTPContext {
36
+    URLContext   *stream;           ///< HTTP stream
37
+    char         host[256];         ///< hostname of the server
38
+    int          port;              ///< port to connect (default is 80)
39
+    char         client_id[64];     ///< client ID used for all requests except the first one
40
+    int          seq;               ///< sequence ID used for all requests
41
+    uint8_t      *out_data;         ///< output buffer
42
+    int          out_size;          ///< current output buffer size
43
+    int          out_capacity;      ///< current output buffer capacity
44
+    int          initialized;       ///< flag indicating when the http context is initialized
45
+    int          finishing;         ///< flag indicating when the client closes the connection
46
+} RTMP_HTTPContext;
47
+
48
+static int rtmp_http_send_cmd(URLContext *h, const char *cmd)
49
+{
50
+    RTMP_HTTPContext *rt = h->priv_data;
51
+    char uri[2048];
52
+    uint8_t c;
53
+    int ret;
54
+
55
+    ff_url_join(uri, sizeof(uri), "http", NULL, rt->host, rt->port,
56
+                "/%s/%s/%d", cmd, rt->client_id, rt->seq++);
57
+
58
+    av_opt_set_bin(rt->stream->priv_data, "post_data", rt->out_data,
59
+                   rt->out_size, 0);
60
+
61
+    /* send a new request to the server */
62
+    if ((ret = ff_http_do_new_request(rt->stream, uri)) < 0)
63
+        return ret;
64
+
65
+    /* re-init output buffer */
66
+    rt->out_size = 0;
67
+
68
+    /* read the first byte which contains the polling interval */
69
+    if ((ret = ffurl_read(rt->stream, &c, 1)) < 0)
70
+        return ret;
71
+
72
+    return ret;
73
+}
74
+
75
+static int rtmp_http_write(URLContext *h, const uint8_t *buf, int size)
76
+{
77
+    RTMP_HTTPContext *rt = h->priv_data;
78
+    void *ptr;
79
+
80
+    if (rt->out_size + size > rt->out_capacity) {
81
+        rt->out_capacity = (rt->out_size + size) * 2;
82
+        ptr = av_realloc(rt->out_data, rt->out_capacity);
83
+        if (!ptr)
84
+            return AVERROR(ENOMEM);
85
+        rt->out_data = ptr;
86
+    }
87
+
88
+    memcpy(rt->out_data + rt->out_size, buf, size);
89
+    rt->out_size += size;
90
+
91
+    return size;
92
+}
93
+
94
+static int rtmp_http_read(URLContext *h, uint8_t *buf, int size)
95
+{
96
+    RTMP_HTTPContext *rt = h->priv_data;
97
+    int ret, off = 0;
98
+
99
+    /* try to read at least 1 byte of data */
100
+    do {
101
+        ret = ffurl_read(rt->stream, buf + off, size);
102
+        if (ret < 0 && ret != AVERROR_EOF)
103
+            return ret;
104
+
105
+        if (ret == AVERROR_EOF) {
106
+            if (rt->finishing) {
107
+                /* Do not send new requests when the client wants to
108
+                 * close the connection. */
109
+                return AVERROR(EAGAIN);
110
+            }
111
+
112
+            /* When the client has reached end of file for the last request,
113
+             * we have to send a new request if we have buffered data.
114
+             * Otherwise, we have to send an idle POST. */
115
+            if (rt->out_size > 0) {
116
+                if ((ret = rtmp_http_send_cmd(h, "send")) < 0)
117
+                    return ret;
118
+            } else {
119
+                if ((ret = rtmp_http_write(h, "", 1)) < 0)
120
+                    return ret;
121
+
122
+                if ((ret = rtmp_http_send_cmd(h, "idle")) < 0)
123
+                    return ret;
124
+            }
125
+
126
+            if (h->flags & AVIO_FLAG_NONBLOCK) {
127
+                /* no incoming data to handle in nonblocking mode */
128
+                return AVERROR(EAGAIN);
129
+            }
130
+        } else {
131
+            off  += ret;
132
+            size -= ret;
133
+        }
134
+    } while (off <= 0);
135
+
136
+    return off;
137
+}
138
+
139
+static int rtmp_http_close(URLContext *h)
140
+{
141
+    RTMP_HTTPContext *rt = h->priv_data;
142
+    uint8_t tmp_buf[2048];
143
+    int ret = 0;
144
+
145
+    if (rt->initialized) {
146
+        /* client wants to close the connection */
147
+        rt->finishing = 1;
148
+
149
+        do {
150
+            ret = rtmp_http_read(h, tmp_buf, sizeof(tmp_buf));
151
+        } while (ret > 0);
152
+
153
+        /* re-init output buffer before sending the close command */
154
+        rt->out_size = 0;
155
+
156
+        if ((ret = rtmp_http_write(h, "", 1)) == 1)
157
+            ret = rtmp_http_send_cmd(h, "close");
158
+    }
159
+
160
+    av_freep(&rt->out_data);
161
+    ffurl_close(rt->stream);
162
+
163
+    return ret;
164
+}
165
+
166
+static int rtmp_http_open(URLContext *h, const char *uri, int flags)
167
+{
168
+    RTMP_HTTPContext *rt = h->priv_data;
169
+    char headers[1024], url[1024];
170
+    int ret, off = 0;
171
+
172
+    av_url_split(NULL, 0, NULL, 0, rt->host, sizeof(rt->host), &rt->port,
173
+                 NULL, 0, uri);
174
+
175
+    if (rt->port < 0)
176
+        rt->port = RTMPT_DEFAULT_PORT;
177
+
178
+    /* This is the first request that is sent to the server in order to
179
+     * register a client on the server and start a new session. The server
180
+     * replies with a unique id (usually a number) that is used by the client
181
+     * for all future requests.
182
+     * Note: the reply doesn't contain a value for the polling interval.
183
+     * A successful connect resets the consecutive index that is used
184
+     * in the URLs. */
185
+    ff_url_join(url, sizeof(url), "http", NULL, rt->host, rt->port, "/open/1");
186
+
187
+    /* alloc the http context */
188
+    if ((ret = ffurl_alloc(&rt->stream, url, AVIO_FLAG_READ_WRITE, NULL)) < 0)
189
+        goto fail;
190
+
191
+    /* set options */
192
+    snprintf(headers, sizeof(headers),
193
+             "Cache-Control: no-cache\r\n"
194
+             "Content-type: application/x-fcs\r\n"
195
+             "User-Agent: Shockwave Flash\r\n");
196
+    av_opt_set(rt->stream->priv_data, "headers", headers, 0);
197
+    av_opt_set(rt->stream->priv_data, "multiple_requests", "1", 0);
198
+    av_opt_set_bin(rt->stream->priv_data, "post_data", "", 1, 0);
199
+
200
+    /* open the http context */
201
+    if ((ret = ffurl_connect(rt->stream, NULL)) < 0)
202
+        goto fail;
203
+
204
+    /* read the server reply which contains a unique ID */
205
+    for (;;) {
206
+        ret = ffurl_read(rt->stream, rt->client_id + off, sizeof(rt->client_id) - off);
207
+        if (ret == AVERROR_EOF)
208
+            break;
209
+        if (ret < 0)
210
+            goto fail;
211
+        off += ret;
212
+        if (off == sizeof(rt->client_id)) {
213
+            ret = AVERROR(EIO);
214
+            goto fail;
215
+        }
216
+    }
217
+    while (off > 0 && isspace(rt->client_id[off - 1]))
218
+        off--;
219
+    rt->client_id[off] = '\0';
220
+
221
+    /* http context is now initialized */
222
+    rt->initialized = 1;
223
+    return 0;
224
+
225
+fail:
226
+    rtmp_http_close(h);
227
+    return ret;
228
+}
229
+
230
+URLProtocol ff_rtmphttp_protocol = {
231
+    .name           = "rtmphttp",
232
+    .url_open       = rtmp_http_open,
233
+    .url_read       = rtmp_http_read,
234
+    .url_write      = rtmp_http_write,
235
+    .url_close      = rtmp_http_close,
236
+    .priv_data_size = sizeof(RTMP_HTTPContext),
237
+    .flags          = URL_PROTOCOL_FLAG_NETWORK,
238
+};
... ...
@@ -1112,9 +1112,15 @@ static int rtmp_open(URLContext *s, const char *uri, int flags)
1112 1112
     av_url_split(proto, sizeof(proto), NULL, 0, hostname, sizeof(hostname), &port,
1113 1113
                  path, sizeof(path), s->filename);
1114 1114
 
1115
-    if (port < 0)
1116
-        port = RTMP_DEFAULT_PORT;
1117
-    ff_url_join(buf, sizeof(buf), "tcp", NULL, hostname, port, NULL);
1115
+    if (!strcmp(proto, "rtmpt")) {
1116
+        /* open the http tunneling connection */
1117
+        ff_url_join(buf, sizeof(buf), "rtmphttp", NULL, hostname, port, NULL);
1118
+    } else {
1119
+        /* open the tcp connection */
1120
+        if (port < 0)
1121
+            port = RTMP_DEFAULT_PORT;
1122
+        ff_url_join(buf, sizeof(buf), "tcp", NULL, hostname, port, NULL);
1123
+    }
1118 1124
 
1119 1125
     if ((ret = ffurl_open(&rt->stream, buf, AVIO_FLAG_READ_WRITE,
1120 1126
                           &s->interrupt_callback, NULL)) < 0) {
... ...
@@ -1425,3 +1431,21 @@ URLProtocol ff_rtmp_protocol = {
1425 1425
     .flags          = URL_PROTOCOL_FLAG_NETWORK,
1426 1426
     .priv_data_class= &rtmp_class,
1427 1427
 };
1428
+
1429
+static const AVClass rtmpt_class = {
1430
+    .class_name = "rtmpt",
1431
+    .item_name  = av_default_item_name,
1432
+    .option     = rtmp_options,
1433
+    .version    = LIBAVUTIL_VERSION_INT,
1434
+};
1435
+
1436
+URLProtocol ff_rtmpt_protocol = {
1437
+    .name            = "rtmpt",
1438
+    .url_open        = rtmp_open,
1439
+    .url_read        = rtmp_read,
1440
+    .url_write       = rtmp_write,
1441
+    .url_close       = rtmp_close,
1442
+    .priv_data_size  = sizeof(RTMPContext),
1443
+    .flags           = URL_PROTOCOL_FLAG_NETWORK,
1444
+    .priv_data_class = &rtmpt_class,
1445
+};
... ...
@@ -30,8 +30,8 @@
30 30
 #include "libavutil/avutil.h"
31 31
 
32 32
 #define LIBAVFORMAT_VERSION_MAJOR 54
33
-#define LIBAVFORMAT_VERSION_MINOR  3
34
-#define LIBAVFORMAT_VERSION_MICRO  1
33
+#define LIBAVFORMAT_VERSION_MINOR  4
34
+#define LIBAVFORMAT_VERSION_MICRO  0
35 35
 
36 36
 #define LIBAVFORMAT_VERSION_INT AV_VERSION_INT(LIBAVFORMAT_VERSION_MAJOR, \
37 37
                                                LIBAVFORMAT_VERSION_MINOR, \