Subversion Repositories Kolibri OS

Rev

Go to most recent revision | Blame | Last modification | View Log | RSS feed

  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 */
  39. typedef 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.  
  55. static 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.  
  85. static 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.  
  105. static 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 == 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.  
  157. static 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.  
  184. static 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 == 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.  
  248. fail:
  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.  
  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},
  258.     { NULL },
  259. };
  260.  
  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,
  266. };
  267.  
  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,
  277. };
  278.