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