1/* 2 * RTMP HTTP network protocol 3 * Copyright (c) 2012 Samuel Pitoiset 4 * 5 * This file is part of FFmpeg. 6 * 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. 11 * 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. 16 * 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 20 */ 21 22/** 23 * @file 24 * RTMP HTTP protocol 25 */ 26 27#include "libavutil/avstring.h" 28#include "libavutil/intfloat.h" 29#include "libavutil/opt.h" 30#include "libavutil/time.h" 31#include "internal.h" 32#include "http.h" 33#include "rtmp.h" 34 35#define RTMPT_DEFAULT_PORT 80 36#define RTMPTS_DEFAULT_PORT RTMPS_DEFAULT_PORT 37 38/* protocol handler context */ 39typedef struct RTMP_HTTPContext { 40 const AVClass *class; 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) 53} RTMP_HTTPContext; 54 55static int rtmp_http_send_cmd(URLContext *h, const char *cmd) 56{ 57 RTMP_HTTPContext *rt = h->priv_data; 58 char uri[2048]; 59 uint8_t c; 60 int ret; 61 62 ff_url_join(uri, sizeof(uri), "http", NULL, rt->host, rt->port, 63 "/%s/%s/%d", cmd, rt->client_id, rt->seq++); 64 65 av_opt_set_bin(rt->stream->priv_data, "post_data", rt->out_data, 66 rt->out_size, 0); 67 68 /* send a new request to the server */ 69 if ((ret = ff_http_do_new_request(rt->stream, uri)) < 0) 70 return ret; 71 72 /* re-init output buffer */ 73 rt->out_size = 0; 74 75 /* read the first byte which contains the polling interval */ 76 if ((ret = ffurl_read(rt->stream, &c, 1)) < 0) 77 return ret; 78 79 /* re-init the number of bytes read */ 80 rt->nb_bytes_read = 0; 81 82 return ret; 83} 84 85static int rtmp_http_write(URLContext *h, const uint8_t *buf, int size) 86{ 87 RTMP_HTTPContext *rt = h->priv_data; 88 89 if (rt->out_size + size > rt->out_capacity) { 90 int err; 91 rt->out_capacity = (rt->out_size + size) * 2; 92 if ((err = av_reallocp(&rt->out_data, rt->out_capacity)) < 0) { 93 rt->out_size = 0; 94 rt->out_capacity = 0; 95 return err; 96 } 97 } 98 99 memcpy(rt->out_data + rt->out_size, buf, size); 100 rt->out_size += size; 101 102 return size; 103} 104 105static int rtmp_http_read(URLContext *h, uint8_t *buf, int size) 106{ 107 RTMP_HTTPContext *rt = h->priv_data; 108 int ret, off = 0; 109 110 /* try to read at least 1 byte of data */ 111 do { 112 ret = ffurl_read(rt->stream, buf + off, size); 113 if (ret < 0 && ret != AVERROR_EOF) 114 return ret; 115 116 if (!ret || ret == AVERROR_EOF) { 117 if (rt->finishing) { 118 /* Do not send new requests when the client wants to 119 * close the connection. */ 120 return AVERROR(EAGAIN); 121 } 122 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) 128 return ret; 129 } else { 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. */ 133 av_usleep(50000); 134 } 135 136 if ((ret = rtmp_http_write(h, "", 1)) < 0) 137 return ret; 138 139 if ((ret = rtmp_http_send_cmd(h, "idle")) < 0) 140 return ret; 141 } 142 143 if (h->flags & AVIO_FLAG_NONBLOCK) { 144 /* no incoming data to handle in nonblocking mode */ 145 return AVERROR(EAGAIN); 146 } 147 } else { 148 off += ret; 149 size -= ret; 150 rt->nb_bytes_read += ret; 151 } 152 } while (off <= 0); 153 154 return off; 155} 156 157static int rtmp_http_close(URLContext *h) 158{ 159 RTMP_HTTPContext *rt = h->priv_data; 160 uint8_t tmp_buf[2048]; 161 int ret = 0; 162 163 if (rt->initialized) { 164 /* client wants to close the connection */ 165 rt->finishing = 1; 166 167 do { 168 ret = rtmp_http_read(h, tmp_buf, sizeof(tmp_buf)); 169 } while (ret > 0); 170 171 /* re-init output buffer before sending the close command */ 172 rt->out_size = 0; 173 174 if ((ret = rtmp_http_write(h, "", 1)) == 1) 175 ret = rtmp_http_send_cmd(h, "close"); 176 } 177 178 av_freep(&rt->out_data); 179 ffurl_close(rt->stream); 180 181 return ret; 182} 183 184static int rtmp_http_open(URLContext *h, const char *uri, int flags) 185{ 186 RTMP_HTTPContext *rt = h->priv_data; 187 char headers[1024], url[1024]; 188 int ret, off = 0; 189 190 av_url_split(NULL, 0, NULL, 0, rt->host, sizeof(rt->host), &rt->port, 191 NULL, 0, uri); 192 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 199 * in the URLs. */ 200 if (rt->tls) { 201 if (rt->port < 0) 202 rt->port = RTMPTS_DEFAULT_PORT; 203 ff_url_join(url, sizeof(url), "https", NULL, rt->host, rt->port, "/open/1"); 204 } else { 205 if (rt->port < 0) 206 rt->port = RTMPT_DEFAULT_PORT; 207 ff_url_join(url, sizeof(url), "http", NULL, rt->host, rt->port, "/open/1"); 208 } 209 210 /* alloc the http context */ 211 if ((ret = ffurl_alloc(&rt->stream, url, AVIO_FLAG_READ_WRITE, NULL)) < 0) 212 goto fail; 213 214 /* set options */ 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); 222 223 /* open the http context */ 224 if ((ret = ffurl_connect(rt->stream, NULL)) < 0) 225 goto fail; 226 227 /* read the server reply which contains a unique ID */ 228 for (;;) { 229 ret = ffurl_read(rt->stream, rt->client_id + off, sizeof(rt->client_id) - off); 230 if (!ret || ret == AVERROR_EOF) 231 break; 232 if (ret < 0) 233 goto fail; 234 off += ret; 235 if (off == sizeof(rt->client_id)) { 236 ret = AVERROR(EIO); 237 goto fail; 238 } 239 } 240 while (off > 0 && av_isspace(rt->client_id[off - 1])) 241 off--; 242 rt->client_id[off] = '\0'; 243 244 /* http context is now initialized */ 245 rt->initialized = 1; 246 return 0; 247 248fail: 249 rtmp_http_close(h); 250 return ret; 251} 252 253#define OFFSET(x) offsetof(RTMP_HTTPContext, x) 254#define DEC AV_OPT_FLAG_DECODING_PARAM 255 256static const AVOption ffrtmphttp_options[] = { 257 {"ffrtmphttp_tls", "Use a HTTPS tunneling connection (RTMPTS).", OFFSET(tls), AV_OPT_TYPE_INT, {.i64 = 0}, 0, 1, DEC}, 258 { NULL }, 259}; 260 261static const AVClass ffrtmphttp_class = { 262 .class_name = "ffrtmphttp", 263 .item_name = av_default_item_name, 264 .option = ffrtmphttp_options, 265 .version = LIBAVUTIL_VERSION_INT, 266}; 267 268URLProtocol 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, 277}; 278