yading@11: /* yading@11: * RTMP HTTP network protocol yading@11: * Copyright (c) 2012 Samuel Pitoiset yading@11: * yading@11: * This file is part of FFmpeg. yading@11: * yading@11: * FFmpeg is free software; you can redistribute it and/or yading@11: * modify it under the terms of the GNU Lesser General Public yading@11: * License as published by the Free Software Foundation; either yading@11: * version 2.1 of the License, or (at your option) any later version. yading@11: * yading@11: * FFmpeg is distributed in the hope that it will be useful, yading@11: * but WITHOUT ANY WARRANTY; without even the implied warranty of yading@11: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU yading@11: * Lesser General Public License for more details. yading@11: * yading@11: * You should have received a copy of the GNU Lesser General Public yading@11: * License along with FFmpeg; if not, write to the Free Software yading@11: * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA yading@11: */ yading@11: yading@11: /** yading@11: * @file yading@11: * RTMP HTTP protocol yading@11: */ yading@11: yading@11: #include "libavutil/avstring.h" yading@11: #include "libavutil/intfloat.h" yading@11: #include "libavutil/opt.h" yading@11: #include "libavutil/time.h" yading@11: #include "internal.h" yading@11: #include "http.h" yading@11: #include "rtmp.h" yading@11: yading@11: #define RTMPT_DEFAULT_PORT 80 yading@11: #define RTMPTS_DEFAULT_PORT RTMPS_DEFAULT_PORT yading@11: yading@11: /* protocol handler context */ yading@11: typedef struct RTMP_HTTPContext { yading@11: const AVClass *class; yading@11: URLContext *stream; ///< HTTP stream yading@11: char host[256]; ///< hostname of the server yading@11: int port; ///< port to connect (default is 80) yading@11: char client_id[64]; ///< client ID used for all requests except the first one yading@11: int seq; ///< sequence ID used for all requests yading@11: uint8_t *out_data; ///< output buffer yading@11: int out_size; ///< current output buffer size yading@11: int out_capacity; ///< current output buffer capacity yading@11: int initialized; ///< flag indicating when the http context is initialized yading@11: int finishing; ///< flag indicating when the client closes the connection yading@11: int nb_bytes_read; ///< number of bytes read since the last request yading@11: int tls; ///< use Transport Security Layer (RTMPTS) yading@11: } RTMP_HTTPContext; yading@11: yading@11: static int rtmp_http_send_cmd(URLContext *h, const char *cmd) yading@11: { yading@11: RTMP_HTTPContext *rt = h->priv_data; yading@11: char uri[2048]; yading@11: uint8_t c; yading@11: int ret; yading@11: yading@11: ff_url_join(uri, sizeof(uri), "http", NULL, rt->host, rt->port, yading@11: "/%s/%s/%d", cmd, rt->client_id, rt->seq++); yading@11: yading@11: av_opt_set_bin(rt->stream->priv_data, "post_data", rt->out_data, yading@11: rt->out_size, 0); yading@11: yading@11: /* send a new request to the server */ yading@11: if ((ret = ff_http_do_new_request(rt->stream, uri)) < 0) yading@11: return ret; yading@11: yading@11: /* re-init output buffer */ yading@11: rt->out_size = 0; yading@11: yading@11: /* read the first byte which contains the polling interval */ yading@11: if ((ret = ffurl_read(rt->stream, &c, 1)) < 0) yading@11: return ret; yading@11: yading@11: /* re-init the number of bytes read */ yading@11: rt->nb_bytes_read = 0; yading@11: yading@11: return ret; yading@11: } yading@11: yading@11: static int rtmp_http_write(URLContext *h, const uint8_t *buf, int size) yading@11: { yading@11: RTMP_HTTPContext *rt = h->priv_data; yading@11: void *ptr; yading@11: yading@11: if (rt->out_size + size > rt->out_capacity) { yading@11: rt->out_capacity = (rt->out_size + size) * 2; yading@11: ptr = av_realloc(rt->out_data, rt->out_capacity); yading@11: if (!ptr) yading@11: return AVERROR(ENOMEM); yading@11: rt->out_data = ptr; yading@11: } yading@11: yading@11: memcpy(rt->out_data + rt->out_size, buf, size); yading@11: rt->out_size += size; yading@11: yading@11: return size; yading@11: } yading@11: yading@11: static int rtmp_http_read(URLContext *h, uint8_t *buf, int size) yading@11: { yading@11: RTMP_HTTPContext *rt = h->priv_data; yading@11: int ret, off = 0; yading@11: yading@11: /* try to read at least 1 byte of data */ yading@11: do { yading@11: ret = ffurl_read(rt->stream, buf + off, size); yading@11: if (ret < 0 && ret != AVERROR_EOF) yading@11: return ret; yading@11: yading@11: if (ret == AVERROR_EOF) { yading@11: if (rt->finishing) { yading@11: /* Do not send new requests when the client wants to yading@11: * close the connection. */ yading@11: return AVERROR(EAGAIN); yading@11: } yading@11: yading@11: /* When the client has reached end of file for the last request, yading@11: * we have to send a new request if we have buffered data. yading@11: * Otherwise, we have to send an idle POST. */ yading@11: if (rt->out_size > 0) { yading@11: if ((ret = rtmp_http_send_cmd(h, "send")) < 0) yading@11: return ret; yading@11: } else { yading@11: if (rt->nb_bytes_read == 0) { yading@11: /* Wait 50ms before retrying to read a server reply in yading@11: * order to reduce the number of idle requets. */ yading@11: av_usleep(50000); yading@11: } yading@11: yading@11: if ((ret = rtmp_http_write(h, "", 1)) < 0) yading@11: return ret; yading@11: yading@11: if ((ret = rtmp_http_send_cmd(h, "idle")) < 0) yading@11: return ret; yading@11: } yading@11: yading@11: if (h->flags & AVIO_FLAG_NONBLOCK) { yading@11: /* no incoming data to handle in nonblocking mode */ yading@11: return AVERROR(EAGAIN); yading@11: } yading@11: } else { yading@11: off += ret; yading@11: size -= ret; yading@11: rt->nb_bytes_read += ret; yading@11: } yading@11: } while (off <= 0); yading@11: yading@11: return off; yading@11: } yading@11: yading@11: static int rtmp_http_close(URLContext *h) yading@11: { yading@11: RTMP_HTTPContext *rt = h->priv_data; yading@11: uint8_t tmp_buf[2048]; yading@11: int ret = 0; yading@11: yading@11: if (rt->initialized) { yading@11: /* client wants to close the connection */ yading@11: rt->finishing = 1; yading@11: yading@11: do { yading@11: ret = rtmp_http_read(h, tmp_buf, sizeof(tmp_buf)); yading@11: } while (ret > 0); yading@11: yading@11: /* re-init output buffer before sending the close command */ yading@11: rt->out_size = 0; yading@11: yading@11: if ((ret = rtmp_http_write(h, "", 1)) == 1) yading@11: ret = rtmp_http_send_cmd(h, "close"); yading@11: } yading@11: yading@11: av_freep(&rt->out_data); yading@11: ffurl_close(rt->stream); yading@11: yading@11: return ret; yading@11: } yading@11: yading@11: static int rtmp_http_open(URLContext *h, const char *uri, int flags) yading@11: { yading@11: RTMP_HTTPContext *rt = h->priv_data; yading@11: char headers[1024], url[1024]; yading@11: int ret, off = 0; yading@11: yading@11: av_url_split(NULL, 0, NULL, 0, rt->host, sizeof(rt->host), &rt->port, yading@11: NULL, 0, uri); yading@11: yading@11: /* This is the first request that is sent to the server in order to yading@11: * register a client on the server and start a new session. The server yading@11: * replies with a unique id (usually a number) that is used by the client yading@11: * for all future requests. yading@11: * Note: the reply doesn't contain a value for the polling interval. yading@11: * A successful connect resets the consecutive index that is used yading@11: * in the URLs. */ yading@11: if (rt->tls) { yading@11: if (rt->port < 0) yading@11: rt->port = RTMPTS_DEFAULT_PORT; yading@11: ff_url_join(url, sizeof(url), "https", NULL, rt->host, rt->port, "/open/1"); yading@11: } else { yading@11: if (rt->port < 0) yading@11: rt->port = RTMPT_DEFAULT_PORT; yading@11: ff_url_join(url, sizeof(url), "http", NULL, rt->host, rt->port, "/open/1"); yading@11: } yading@11: yading@11: /* alloc the http context */ yading@11: if ((ret = ffurl_alloc(&rt->stream, url, AVIO_FLAG_READ_WRITE, NULL)) < 0) yading@11: goto fail; yading@11: yading@11: /* set options */ yading@11: snprintf(headers, sizeof(headers), yading@11: "Cache-Control: no-cache\r\n" yading@11: "Content-type: application/x-fcs\r\n" yading@11: "User-Agent: Shockwave Flash\r\n"); yading@11: av_opt_set(rt->stream->priv_data, "headers", headers, 0); yading@11: av_opt_set(rt->stream->priv_data, "multiple_requests", "1", 0); yading@11: av_opt_set_bin(rt->stream->priv_data, "post_data", "", 1, 0); yading@11: yading@11: /* open the http context */ yading@11: if ((ret = ffurl_connect(rt->stream, NULL)) < 0) yading@11: goto fail; yading@11: yading@11: /* read the server reply which contains a unique ID */ yading@11: for (;;) { yading@11: ret = ffurl_read(rt->stream, rt->client_id + off, sizeof(rt->client_id) - off); yading@11: if (ret == AVERROR_EOF) yading@11: break; yading@11: if (ret < 0) yading@11: goto fail; yading@11: off += ret; yading@11: if (off == sizeof(rt->client_id)) { yading@11: ret = AVERROR(EIO); yading@11: goto fail; yading@11: } yading@11: } yading@11: while (off > 0 && av_isspace(rt->client_id[off - 1])) yading@11: off--; yading@11: rt->client_id[off] = '\0'; yading@11: yading@11: /* http context is now initialized */ yading@11: rt->initialized = 1; yading@11: return 0; yading@11: yading@11: fail: yading@11: rtmp_http_close(h); yading@11: return ret; yading@11: } yading@11: yading@11: #define OFFSET(x) offsetof(RTMP_HTTPContext, x) yading@11: #define DEC AV_OPT_FLAG_DECODING_PARAM yading@11: yading@11: static const AVOption ffrtmphttp_options[] = { yading@11: {"ffrtmphttp_tls", "Use a HTTPS tunneling connection (RTMPTS).", OFFSET(tls), AV_OPT_TYPE_INT, {.i64 = 0}, 0, 1, DEC}, yading@11: { NULL }, yading@11: }; yading@11: yading@11: static const AVClass ffrtmphttp_class = { yading@11: .class_name = "ffrtmphttp", yading@11: .item_name = av_default_item_name, yading@11: .option = ffrtmphttp_options, yading@11: .version = LIBAVUTIL_VERSION_INT, yading@11: }; yading@11: yading@11: URLProtocol ff_ffrtmphttp_protocol = { yading@11: .name = "ffrtmphttp", yading@11: .url_open = rtmp_http_open, yading@11: .url_read = rtmp_http_read, yading@11: .url_write = rtmp_http_write, yading@11: .url_close = rtmp_http_close, yading@11: .priv_data_size = sizeof(RTMP_HTTPContext), yading@11: .flags = URL_PROTOCOL_FLAG_NETWORK, yading@11: .priv_data_class= &ffrtmphttp_class, yading@11: };