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/intfloat.h"
29 #include "libavutil/opt.h"
30 #include "libavutil/time.h"
35 #define RTMPT_DEFAULT_PORT 80
36 #define RTMPTS_DEFAULT_PORT RTMPS_DEFAULT_PORT
38 /* protocol handler context */
39 typedef struct RTMP_HTTPContext
{
41 URLContext
*stream
; ///< HTTP stream
42 char host
[256]; ///< hostname of the server
43 int port
; ///< port to connect (default is 80)
44 char client_id
[64]; ///< client ID used for all requests except the first one
45 int seq
; ///< sequence ID used for all requests
46 uint8_t *out_data
; ///< output buffer
47 int out_size
; ///< current output buffer size
48 int out_capacity
; ///< current output buffer capacity
49 int initialized
; ///< flag indicating when the http context is initialized
50 int finishing
; ///< flag indicating when the client closes the connection
51 int nb_bytes_read
; ///< number of bytes read since the last request
52 int tls
; ///< use Transport Security Layer (RTMPTS)
55 static int rtmp_http_send_cmd(URLContext
*h
, const char *cmd
)
57 RTMP_HTTPContext
*rt
= h
->priv_data
;
62 ff_url_join(uri
, sizeof(uri
), "http", NULL
, rt
->host
, rt
->port
,
63 "/%s/%s/%d", cmd
, rt
->client_id
, rt
->seq
++);
65 av_opt_set_bin(rt
->stream
->priv_data
, "post_data", rt
->out_data
,
68 /* send a new request to the server */
69 if ((ret
= ff_http_do_new_request(rt
->stream
, uri
)) < 0)
72 /* re-init output buffer */
75 /* read the first byte which contains the polling interval */
76 if ((ret
= ffurl_read(rt
->stream
, &c
, 1)) < 0)
79 /* re-init the number of bytes read */
80 rt
->nb_bytes_read
= 0;
85 static int rtmp_http_write(URLContext
*h
, const uint8_t *buf
, int size
)
87 RTMP_HTTPContext
*rt
= h
->priv_data
;
89 if (rt
->out_size
+ size
> rt
->out_capacity
) {
91 rt
->out_capacity
= (rt
->out_size
+ size
) * 2;
92 if ((err
= av_reallocp(&rt
->out_data
, rt
->out_capacity
)) < 0) {
99 memcpy(rt
->out_data
+ rt
->out_size
, buf
, size
);
100 rt
->out_size
+= size
;
105 static int rtmp_http_read(URLContext
*h
, uint8_t *buf
, int size
)
107 RTMP_HTTPContext
*rt
= h
->priv_data
;
110 /* try to read at least 1 byte of data */
112 ret
= ffurl_read(rt
->stream
, buf
+ off
, size
);
113 if (ret
< 0 && ret
!= AVERROR_EOF
)
116 if (!ret
|| ret
== AVERROR_EOF
) {
118 /* Do not send new requests when the client wants to
119 * close the connection. */
120 return AVERROR(EAGAIN
);
123 /* When the client has reached end of file for the last request,
124 * we have to send a new request if we have buffered data.
125 * Otherwise, we have to send an idle POST. */
126 if (rt
->out_size
> 0) {
127 if ((ret
= rtmp_http_send_cmd(h
, "send")) < 0)
130 if (rt
->nb_bytes_read
== 0) {
131 /* Wait 50ms before retrying to read a server reply in
132 * order to reduce the number of idle requets. */
136 if ((ret
= rtmp_http_write(h
, "", 1)) < 0)
139 if ((ret
= rtmp_http_send_cmd(h
, "idle")) < 0)
143 if (h
->flags
& AVIO_FLAG_NONBLOCK
) {
144 /* no incoming data to handle in nonblocking mode */
145 return AVERROR(EAGAIN
);
150 rt
->nb_bytes_read
+= ret
;
157 static int rtmp_http_close(URLContext
*h
)
159 RTMP_HTTPContext
*rt
= h
->priv_data
;
160 uint8_t tmp_buf
[2048];
163 if (rt
->initialized
) {
164 /* client wants to close the connection */
168 ret
= rtmp_http_read(h
, tmp_buf
, sizeof(tmp_buf
));
171 /* re-init output buffer before sending the close command */
174 if ((ret
= rtmp_http_write(h
, "", 1)) == 1)
175 ret
= rtmp_http_send_cmd(h
, "close");
178 av_freep(&rt
->out_data
);
179 ffurl_close(rt
->stream
);
184 static int rtmp_http_open(URLContext
*h
, const char *uri
, int flags
)
186 RTMP_HTTPContext
*rt
= h
->priv_data
;
187 char headers
[1024], url
[1024];
190 av_url_split(NULL
, 0, NULL
, 0, rt
->host
, sizeof(rt
->host
), &rt
->port
,
193 /* This is the first request that is sent to the server in order to
194 * register a client on the server and start a new session. The server
195 * replies with a unique id (usually a number) that is used by the client
196 * for all future requests.
197 * Note: the reply doesn't contain a value for the polling interval.
198 * A successful connect resets the consecutive index that is used
202 rt
->port
= RTMPTS_DEFAULT_PORT
;
203 ff_url_join(url
, sizeof(url
), "https", NULL
, rt
->host
, rt
->port
, "/open/1");
206 rt
->port
= RTMPT_DEFAULT_PORT
;
207 ff_url_join(url
, sizeof(url
), "http", NULL
, rt
->host
, rt
->port
, "/open/1");
210 /* alloc the http context */
211 if ((ret
= ffurl_alloc(&rt
->stream
, url
, AVIO_FLAG_READ_WRITE
, NULL
)) < 0)
215 snprintf(headers
, sizeof(headers
),
216 "Cache-Control: no-cache\r\n"
217 "Content-type: application/x-fcs\r\n"
218 "User-Agent: Shockwave Flash\r\n");
219 av_opt_set(rt
->stream
->priv_data
, "headers", headers
, 0);
220 av_opt_set(rt
->stream
->priv_data
, "multiple_requests", "1", 0);
221 av_opt_set_bin(rt
->stream
->priv_data
, "post_data", "", 1, 0);
223 /* open the http context */
224 if ((ret
= ffurl_connect(rt
->stream
, NULL
)) < 0)
227 /* read the server reply which contains a unique ID */
229 ret
= ffurl_read(rt
->stream
, rt
->client_id
+ off
, sizeof(rt
->client_id
) - off
);
230 if (!ret
|| ret
== AVERROR_EOF
)
235 if (off
== sizeof(rt
->client_id
)) {
240 while (off
> 0 && av_isspace(rt
->client_id
[off
- 1]))
242 rt
->client_id
[off
] = '\0';
244 /* http context is now initialized */
253 #define OFFSET(x) offsetof(RTMP_HTTPContext, x)
254 #define DEC AV_OPT_FLAG_DECODING_PARAM
256 static const AVOption ffrtmphttp_options
[] = {
257 {"ffrtmphttp_tls", "Use a HTTPS tunneling connection (RTMPTS).", OFFSET(tls
), AV_OPT_TYPE_INT
, {.i64
= 0}, 0, 1, DEC
},
261 static const AVClass ffrtmphttp_class
= {
262 .class_name
= "ffrtmphttp",
263 .item_name
= av_default_item_name
,
264 .option
= ffrtmphttp_options
,
265 .version
= LIBAVUTIL_VERSION_INT
,
268 URLProtocol ff_ffrtmphttp_protocol
= {
269 .name
= "ffrtmphttp",
270 .url_open
= rtmp_http_open
,
271 .url_read
= rtmp_http_read
,
272 .url_write
= rtmp_http_write
,
273 .url_close
= rtmp_http_close
,
274 .priv_data_size
= sizeof(RTMP_HTTPContext
),
275 .flags
= URL_PROTOCOL_FLAG_NETWORK
,
276 .priv_data_class
= &ffrtmphttp_class
,