2 * RTMP HTTP network protocol
3 * Copyright (c) 2012 Samuel Pitoiset
5 * This file is part of FFmpeg.
7 * FFmpeg is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Lesser General Public
9 * License as published by the Free Software Foundation; either
10 * version 2.1 of the License, or (at your option) any later version.
12 * FFmpeg is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Lesser General Public License for more details.
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with FFmpeg; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
27 #include "libavutil/avstring.h"
28 #include "libavutil/mem.h"
29 #include "libavutil/opt.h"
30 #include "libavutil/time.h"
34 #define RTMPT_DEFAULT_PORT 80
35 #define RTMPTS_DEFAULT_PORT RTMPS_DEFAULT_PORT
37 /* protocol handler context */
38 typedef struct RTMP_HTTPContext
{
40 URLContext
*stream
; ///< HTTP stream
41 char host
[256]; ///< hostname of the server
42 int port
; ///< port to connect (default is 80)
43 char client_id
[64]; ///< client ID used for all requests except the first one
44 int seq
; ///< sequence ID used for all requests
45 uint8_t *out_data
; ///< output buffer
46 int out_size
; ///< current output buffer size
47 int out_capacity
; ///< current output buffer capacity
48 int initialized
; ///< flag indicating when the http context is initialized
49 int finishing
; ///< flag indicating when the client closes the connection
50 int nb_bytes_read
; ///< number of bytes read since the last request
51 int tls
; ///< use Transport Security Layer (RTMPTS)
54 static int rtmp_http_send_cmd(URLContext
*h
, const char *cmd
)
56 RTMP_HTTPContext
*rt
= h
->priv_data
;
61 ff_url_join(uri
, sizeof(uri
), "http", NULL
, rt
->host
, rt
->port
,
62 "/%s/%s/%d", cmd
, rt
->client_id
, rt
->seq
++);
64 av_opt_set_bin(rt
->stream
->priv_data
, "post_data", rt
->out_data
,
67 /* send a new request to the server */
68 if ((ret
= ff_http_do_new_request(rt
->stream
, uri
)) < 0)
71 /* re-init output buffer */
74 /* read the first byte which contains the polling interval */
75 if ((ret
= ffurl_read(rt
->stream
, &c
, 1)) < 0)
78 /* re-init the number of bytes read */
79 rt
->nb_bytes_read
= 0;
84 static int rtmp_http_write(URLContext
*h
, const uint8_t *buf
, int size
)
86 RTMP_HTTPContext
*rt
= h
->priv_data
;
88 if (rt
->out_size
+ size
> rt
->out_capacity
) {
90 rt
->out_capacity
= (rt
->out_size
+ size
) * 2;
91 if ((err
= av_reallocp(&rt
->out_data
, rt
->out_capacity
)) < 0) {
98 memcpy(rt
->out_data
+ rt
->out_size
, buf
, size
);
104 static int rtmp_http_read(URLContext
*h
, uint8_t *buf
, int size
)
106 RTMP_HTTPContext
*rt
= h
->priv_data
;
109 /* try to read at least 1 byte of data */
111 ret
= ffurl_read(rt
->stream
, buf
+ off
, size
);
112 if (ret
< 0 && ret
!= AVERROR_EOF
)
115 if (!ret
|| ret
== AVERROR_EOF
) {
117 /* Do not send new requests when the client wants to
118 * close the connection. */
119 return AVERROR(EAGAIN
);
122 /* When the client has reached end of file for the last request,
123 * we have to send a new request if we have buffered data.
124 * Otherwise, we have to send an idle POST. */
125 if (rt
->out_size
> 0) {
126 if ((ret
= rtmp_http_send_cmd(h
, "send")) < 0)
129 if (rt
->nb_bytes_read
== 0) {
130 /* Wait 50ms before retrying to read a server reply in
131 * order to reduce the number of idle requests. */
135 if ((ret
= rtmp_http_write(h
, "", 1)) < 0)
138 if ((ret
= rtmp_http_send_cmd(h
, "idle")) < 0)
142 if (h
->flags
& AVIO_FLAG_NONBLOCK
) {
143 /* no incoming data to handle in nonblocking mode */
144 return AVERROR(EAGAIN
);
149 rt
->nb_bytes_read
+= ret
;
156 static int rtmp_http_close(URLContext
*h
)
158 RTMP_HTTPContext
*rt
= h
->priv_data
;
159 uint8_t tmp_buf
[2048];
162 if (rt
->initialized
) {
163 /* client wants to close the connection */
167 ret
= rtmp_http_read(h
, tmp_buf
, sizeof(tmp_buf
));
170 /* re-init output buffer before sending the close command */
173 if ((ret
= rtmp_http_write(h
, "", 1)) == 1)
174 ret
= rtmp_http_send_cmd(h
, "close");
177 av_freep(&rt
->out_data
);
178 ffurl_closep(&rt
->stream
);
183 static int rtmp_http_open(URLContext
*h
, const char *uri
, int flags
)
185 RTMP_HTTPContext
*rt
= h
->priv_data
;
186 char headers
[1024], url
[1024];
189 av_url_split(NULL
, 0, NULL
, 0, rt
->host
, sizeof(rt
->host
), &rt
->port
,
192 /* This is the first request that is sent to the server in order to
193 * register a client on the server and start a new session. The server
194 * replies with a unique id (usually a number) that is used by the client
195 * for all future requests.
196 * Note: the reply doesn't contain a value for the polling interval.
197 * A successful connect resets the consecutive index that is used
201 rt
->port
= RTMPTS_DEFAULT_PORT
;
202 ff_url_join(url
, sizeof(url
), "https", NULL
, rt
->host
, rt
->port
, "/open/1");
205 rt
->port
= RTMPT_DEFAULT_PORT
;
206 ff_url_join(url
, sizeof(url
), "http", NULL
, rt
->host
, rt
->port
, "/open/1");
209 /* alloc the http context */
210 if ((ret
= ffurl_alloc(&rt
->stream
, url
, AVIO_FLAG_READ_WRITE
, &h
->interrupt_callback
)) < 0)
214 snprintf(headers
, sizeof(headers
),
215 "Cache-Control: no-cache\r\n"
216 "Content-type: application/x-fcs\r\n"
217 "User-Agent: Shockwave Flash\r\n");
218 av_opt_set(rt
->stream
->priv_data
, "headers", headers
, 0);
219 av_opt_set(rt
->stream
->priv_data
, "multiple_requests", "1", 0);
220 av_opt_set_bin(rt
->stream
->priv_data
, "post_data", "", 1, 0);
222 if (!rt
->stream
->protocol_whitelist
&& h
->protocol_whitelist
) {
223 rt
->stream
->protocol_whitelist
= av_strdup(h
->protocol_whitelist
);
224 if (!rt
->stream
->protocol_whitelist
) {
225 ret
= AVERROR(ENOMEM
);
230 /* open the http context */
231 if ((ret
= ffurl_connect(rt
->stream
, NULL
)) < 0)
234 /* read the server reply which contains a unique ID */
236 ret
= ffurl_read(rt
->stream
, rt
->client_id
+ off
, sizeof(rt
->client_id
) - off
);
237 if (!ret
|| ret
== AVERROR_EOF
)
242 if (off
== sizeof(rt
->client_id
)) {
247 while (off
> 0 && av_isspace(rt
->client_id
[off
- 1]))
249 rt
->client_id
[off
] = '\0';
251 /* http context is now initialized */
260 #define OFFSET(x) offsetof(RTMP_HTTPContext, x)
261 #define DEC AV_OPT_FLAG_DECODING_PARAM
263 static const AVOption ffrtmphttp_options
[] = {
264 {"ffrtmphttp_tls", "Use a HTTPS tunneling connection (RTMPTS).", OFFSET(tls
), AV_OPT_TYPE_BOOL
, {.i64
= 0}, 0, 1, DEC
},
268 static const AVClass ffrtmphttp_class
= {
269 .class_name
= "ffrtmphttp",
270 .item_name
= av_default_item_name
,
271 .option
= ffrtmphttp_options
,
272 .version
= LIBAVUTIL_VERSION_INT
,
275 const URLProtocol ff_ffrtmphttp_protocol
= {
276 .name
= "ffrtmphttp",
277 .url_open
= rtmp_http_open
,
278 .url_read
= rtmp_http_read
,
279 .url_write
= rtmp_http_write
,
280 .url_close
= rtmp_http_close
,
281 .priv_data_size
= sizeof(RTMP_HTTPContext
),
282 .flags
= URL_PROTOCOL_FLAG_NETWORK
,
283 .priv_data_class
= &ffrtmphttp_class
,
284 .default_whitelist
= "https,http,tcp,tls",