yading@11
|
1 /*
|
yading@11
|
2 * RTMP HTTP network protocol
|
yading@11
|
3 * Copyright (c) 2012 Samuel Pitoiset
|
yading@11
|
4 *
|
yading@11
|
5 * This file is part of FFmpeg.
|
yading@11
|
6 *
|
yading@11
|
7 * FFmpeg is free software; you can redistribute it and/or
|
yading@11
|
8 * modify it under the terms of the GNU Lesser General Public
|
yading@11
|
9 * License as published by the Free Software Foundation; either
|
yading@11
|
10 * version 2.1 of the License, or (at your option) any later version.
|
yading@11
|
11 *
|
yading@11
|
12 * FFmpeg is distributed in the hope that it will be useful,
|
yading@11
|
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
yading@11
|
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
yading@11
|
15 * Lesser General Public License for more details.
|
yading@11
|
16 *
|
yading@11
|
17 * You should have received a copy of the GNU Lesser General Public
|
yading@11
|
18 * License along with FFmpeg; if not, write to the Free Software
|
yading@11
|
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
yading@11
|
20 */
|
yading@11
|
21
|
yading@11
|
22 /**
|
yading@11
|
23 * @file
|
yading@11
|
24 * RTMP HTTP protocol
|
yading@11
|
25 */
|
yading@11
|
26
|
yading@11
|
27 #include "libavutil/avstring.h"
|
yading@11
|
28 #include "libavutil/intfloat.h"
|
yading@11
|
29 #include "libavutil/opt.h"
|
yading@11
|
30 #include "libavutil/time.h"
|
yading@11
|
31 #include "internal.h"
|
yading@11
|
32 #include "http.h"
|
yading@11
|
33 #include "rtmp.h"
|
yading@11
|
34
|
yading@11
|
35 #define RTMPT_DEFAULT_PORT 80
|
yading@11
|
36 #define RTMPTS_DEFAULT_PORT RTMPS_DEFAULT_PORT
|
yading@11
|
37
|
yading@11
|
38 /* protocol handler context */
|
yading@11
|
39 typedef struct RTMP_HTTPContext {
|
yading@11
|
40 const AVClass *class;
|
yading@11
|
41 URLContext *stream; ///< HTTP stream
|
yading@11
|
42 char host[256]; ///< hostname of the server
|
yading@11
|
43 int port; ///< port to connect (default is 80)
|
yading@11
|
44 char client_id[64]; ///< client ID used for all requests except the first one
|
yading@11
|
45 int seq; ///< sequence ID used for all requests
|
yading@11
|
46 uint8_t *out_data; ///< output buffer
|
yading@11
|
47 int out_size; ///< current output buffer size
|
yading@11
|
48 int out_capacity; ///< current output buffer capacity
|
yading@11
|
49 int initialized; ///< flag indicating when the http context is initialized
|
yading@11
|
50 int finishing; ///< flag indicating when the client closes the connection
|
yading@11
|
51 int nb_bytes_read; ///< number of bytes read since the last request
|
yading@11
|
52 int tls; ///< use Transport Security Layer (RTMPTS)
|
yading@11
|
53 } RTMP_HTTPContext;
|
yading@11
|
54
|
yading@11
|
55 static int rtmp_http_send_cmd(URLContext *h, const char *cmd)
|
yading@11
|
56 {
|
yading@11
|
57 RTMP_HTTPContext *rt = h->priv_data;
|
yading@11
|
58 char uri[2048];
|
yading@11
|
59 uint8_t c;
|
yading@11
|
60 int ret;
|
yading@11
|
61
|
yading@11
|
62 ff_url_join(uri, sizeof(uri), "http", NULL, rt->host, rt->port,
|
yading@11
|
63 "/%s/%s/%d", cmd, rt->client_id, rt->seq++);
|
yading@11
|
64
|
yading@11
|
65 av_opt_set_bin(rt->stream->priv_data, "post_data", rt->out_data,
|
yading@11
|
66 rt->out_size, 0);
|
yading@11
|
67
|
yading@11
|
68 /* send a new request to the server */
|
yading@11
|
69 if ((ret = ff_http_do_new_request(rt->stream, uri)) < 0)
|
yading@11
|
70 return ret;
|
yading@11
|
71
|
yading@11
|
72 /* re-init output buffer */
|
yading@11
|
73 rt->out_size = 0;
|
yading@11
|
74
|
yading@11
|
75 /* read the first byte which contains the polling interval */
|
yading@11
|
76 if ((ret = ffurl_read(rt->stream, &c, 1)) < 0)
|
yading@11
|
77 return ret;
|
yading@11
|
78
|
yading@11
|
79 /* re-init the number of bytes read */
|
yading@11
|
80 rt->nb_bytes_read = 0;
|
yading@11
|
81
|
yading@11
|
82 return ret;
|
yading@11
|
83 }
|
yading@11
|
84
|
yading@11
|
85 static int rtmp_http_write(URLContext *h, const uint8_t *buf, int size)
|
yading@11
|
86 {
|
yading@11
|
87 RTMP_HTTPContext *rt = h->priv_data;
|
yading@11
|
88 void *ptr;
|
yading@11
|
89
|
yading@11
|
90 if (rt->out_size + size > rt->out_capacity) {
|
yading@11
|
91 rt->out_capacity = (rt->out_size + size) * 2;
|
yading@11
|
92 ptr = av_realloc(rt->out_data, rt->out_capacity);
|
yading@11
|
93 if (!ptr)
|
yading@11
|
94 return AVERROR(ENOMEM);
|
yading@11
|
95 rt->out_data = ptr;
|
yading@11
|
96 }
|
yading@11
|
97
|
yading@11
|
98 memcpy(rt->out_data + rt->out_size, buf, size);
|
yading@11
|
99 rt->out_size += size;
|
yading@11
|
100
|
yading@11
|
101 return size;
|
yading@11
|
102 }
|
yading@11
|
103
|
yading@11
|
104 static int rtmp_http_read(URLContext *h, uint8_t *buf, int size)
|
yading@11
|
105 {
|
yading@11
|
106 RTMP_HTTPContext *rt = h->priv_data;
|
yading@11
|
107 int ret, off = 0;
|
yading@11
|
108
|
yading@11
|
109 /* try to read at least 1 byte of data */
|
yading@11
|
110 do {
|
yading@11
|
111 ret = ffurl_read(rt->stream, buf + off, size);
|
yading@11
|
112 if (ret < 0 && ret != AVERROR_EOF)
|
yading@11
|
113 return ret;
|
yading@11
|
114
|
yading@11
|
115 if (ret == AVERROR_EOF) {
|
yading@11
|
116 if (rt->finishing) {
|
yading@11
|
117 /* Do not send new requests when the client wants to
|
yading@11
|
118 * close the connection. */
|
yading@11
|
119 return AVERROR(EAGAIN);
|
yading@11
|
120 }
|
yading@11
|
121
|
yading@11
|
122 /* When the client has reached end of file for the last request,
|
yading@11
|
123 * we have to send a new request if we have buffered data.
|
yading@11
|
124 * Otherwise, we have to send an idle POST. */
|
yading@11
|
125 if (rt->out_size > 0) {
|
yading@11
|
126 if ((ret = rtmp_http_send_cmd(h, "send")) < 0)
|
yading@11
|
127 return ret;
|
yading@11
|
128 } else {
|
yading@11
|
129 if (rt->nb_bytes_read == 0) {
|
yading@11
|
130 /* Wait 50ms before retrying to read a server reply in
|
yading@11
|
131 * order to reduce the number of idle requets. */
|
yading@11
|
132 av_usleep(50000);
|
yading@11
|
133 }
|
yading@11
|
134
|
yading@11
|
135 if ((ret = rtmp_http_write(h, "", 1)) < 0)
|
yading@11
|
136 return ret;
|
yading@11
|
137
|
yading@11
|
138 if ((ret = rtmp_http_send_cmd(h, "idle")) < 0)
|
yading@11
|
139 return ret;
|
yading@11
|
140 }
|
yading@11
|
141
|
yading@11
|
142 if (h->flags & AVIO_FLAG_NONBLOCK) {
|
yading@11
|
143 /* no incoming data to handle in nonblocking mode */
|
yading@11
|
144 return AVERROR(EAGAIN);
|
yading@11
|
145 }
|
yading@11
|
146 } else {
|
yading@11
|
147 off += ret;
|
yading@11
|
148 size -= ret;
|
yading@11
|
149 rt->nb_bytes_read += ret;
|
yading@11
|
150 }
|
yading@11
|
151 } while (off <= 0);
|
yading@11
|
152
|
yading@11
|
153 return off;
|
yading@11
|
154 }
|
yading@11
|
155
|
yading@11
|
156 static int rtmp_http_close(URLContext *h)
|
yading@11
|
157 {
|
yading@11
|
158 RTMP_HTTPContext *rt = h->priv_data;
|
yading@11
|
159 uint8_t tmp_buf[2048];
|
yading@11
|
160 int ret = 0;
|
yading@11
|
161
|
yading@11
|
162 if (rt->initialized) {
|
yading@11
|
163 /* client wants to close the connection */
|
yading@11
|
164 rt->finishing = 1;
|
yading@11
|
165
|
yading@11
|
166 do {
|
yading@11
|
167 ret = rtmp_http_read(h, tmp_buf, sizeof(tmp_buf));
|
yading@11
|
168 } while (ret > 0);
|
yading@11
|
169
|
yading@11
|
170 /* re-init output buffer before sending the close command */
|
yading@11
|
171 rt->out_size = 0;
|
yading@11
|
172
|
yading@11
|
173 if ((ret = rtmp_http_write(h, "", 1)) == 1)
|
yading@11
|
174 ret = rtmp_http_send_cmd(h, "close");
|
yading@11
|
175 }
|
yading@11
|
176
|
yading@11
|
177 av_freep(&rt->out_data);
|
yading@11
|
178 ffurl_close(rt->stream);
|
yading@11
|
179
|
yading@11
|
180 return ret;
|
yading@11
|
181 }
|
yading@11
|
182
|
yading@11
|
183 static int rtmp_http_open(URLContext *h, const char *uri, int flags)
|
yading@11
|
184 {
|
yading@11
|
185 RTMP_HTTPContext *rt = h->priv_data;
|
yading@11
|
186 char headers[1024], url[1024];
|
yading@11
|
187 int ret, off = 0;
|
yading@11
|
188
|
yading@11
|
189 av_url_split(NULL, 0, NULL, 0, rt->host, sizeof(rt->host), &rt->port,
|
yading@11
|
190 NULL, 0, uri);
|
yading@11
|
191
|
yading@11
|
192 /* This is the first request that is sent to the server in order to
|
yading@11
|
193 * register a client on the server and start a new session. The server
|
yading@11
|
194 * replies with a unique id (usually a number) that is used by the client
|
yading@11
|
195 * for all future requests.
|
yading@11
|
196 * Note: the reply doesn't contain a value for the polling interval.
|
yading@11
|
197 * A successful connect resets the consecutive index that is used
|
yading@11
|
198 * in the URLs. */
|
yading@11
|
199 if (rt->tls) {
|
yading@11
|
200 if (rt->port < 0)
|
yading@11
|
201 rt->port = RTMPTS_DEFAULT_PORT;
|
yading@11
|
202 ff_url_join(url, sizeof(url), "https", NULL, rt->host, rt->port, "/open/1");
|
yading@11
|
203 } else {
|
yading@11
|
204 if (rt->port < 0)
|
yading@11
|
205 rt->port = RTMPT_DEFAULT_PORT;
|
yading@11
|
206 ff_url_join(url, sizeof(url), "http", NULL, rt->host, rt->port, "/open/1");
|
yading@11
|
207 }
|
yading@11
|
208
|
yading@11
|
209 /* alloc the http context */
|
yading@11
|
210 if ((ret = ffurl_alloc(&rt->stream, url, AVIO_FLAG_READ_WRITE, NULL)) < 0)
|
yading@11
|
211 goto fail;
|
yading@11
|
212
|
yading@11
|
213 /* set options */
|
yading@11
|
214 snprintf(headers, sizeof(headers),
|
yading@11
|
215 "Cache-Control: no-cache\r\n"
|
yading@11
|
216 "Content-type: application/x-fcs\r\n"
|
yading@11
|
217 "User-Agent: Shockwave Flash\r\n");
|
yading@11
|
218 av_opt_set(rt->stream->priv_data, "headers", headers, 0);
|
yading@11
|
219 av_opt_set(rt->stream->priv_data, "multiple_requests", "1", 0);
|
yading@11
|
220 av_opt_set_bin(rt->stream->priv_data, "post_data", "", 1, 0);
|
yading@11
|
221
|
yading@11
|
222 /* open the http context */
|
yading@11
|
223 if ((ret = ffurl_connect(rt->stream, NULL)) < 0)
|
yading@11
|
224 goto fail;
|
yading@11
|
225
|
yading@11
|
226 /* read the server reply which contains a unique ID */
|
yading@11
|
227 for (;;) {
|
yading@11
|
228 ret = ffurl_read(rt->stream, rt->client_id + off, sizeof(rt->client_id) - off);
|
yading@11
|
229 if (ret == AVERROR_EOF)
|
yading@11
|
230 break;
|
yading@11
|
231 if (ret < 0)
|
yading@11
|
232 goto fail;
|
yading@11
|
233 off += ret;
|
yading@11
|
234 if (off == sizeof(rt->client_id)) {
|
yading@11
|
235 ret = AVERROR(EIO);
|
yading@11
|
236 goto fail;
|
yading@11
|
237 }
|
yading@11
|
238 }
|
yading@11
|
239 while (off > 0 && av_isspace(rt->client_id[off - 1]))
|
yading@11
|
240 off--;
|
yading@11
|
241 rt->client_id[off] = '\0';
|
yading@11
|
242
|
yading@11
|
243 /* http context is now initialized */
|
yading@11
|
244 rt->initialized = 1;
|
yading@11
|
245 return 0;
|
yading@11
|
246
|
yading@11
|
247 fail:
|
yading@11
|
248 rtmp_http_close(h);
|
yading@11
|
249 return ret;
|
yading@11
|
250 }
|
yading@11
|
251
|
yading@11
|
252 #define OFFSET(x) offsetof(RTMP_HTTPContext, x)
|
yading@11
|
253 #define DEC AV_OPT_FLAG_DECODING_PARAM
|
yading@11
|
254
|
yading@11
|
255 static const AVOption ffrtmphttp_options[] = {
|
yading@11
|
256 {"ffrtmphttp_tls", "Use a HTTPS tunneling connection (RTMPTS).", OFFSET(tls), AV_OPT_TYPE_INT, {.i64 = 0}, 0, 1, DEC},
|
yading@11
|
257 { NULL },
|
yading@11
|
258 };
|
yading@11
|
259
|
yading@11
|
260 static const AVClass ffrtmphttp_class = {
|
yading@11
|
261 .class_name = "ffrtmphttp",
|
yading@11
|
262 .item_name = av_default_item_name,
|
yading@11
|
263 .option = ffrtmphttp_options,
|
yading@11
|
264 .version = LIBAVUTIL_VERSION_INT,
|
yading@11
|
265 };
|
yading@11
|
266
|
yading@11
|
267 URLProtocol ff_ffrtmphttp_protocol = {
|
yading@11
|
268 .name = "ffrtmphttp",
|
yading@11
|
269 .url_open = rtmp_http_open,
|
yading@11
|
270 .url_read = rtmp_http_read,
|
yading@11
|
271 .url_write = rtmp_http_write,
|
yading@11
|
272 .url_close = rtmp_http_close,
|
yading@11
|
273 .priv_data_size = sizeof(RTMP_HTTPContext),
|
yading@11
|
274 .flags = URL_PROTOCOL_FLAG_NETWORK,
|
yading@11
|
275 .priv_data_class= &ffrtmphttp_class,
|
yading@11
|
276 };
|