Subversion Repositories Kolibri OS

Rev

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
4349 Serge 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
};