format.c 12.2 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
/* Icecast
 *
 * This program is distributed under the GNU General Public License, version 2.
 * A copy of this license is included with this source.
 *
 * Copyright 2000-2004, Jack Moffitt <jack@xiph.org, 
 *                      Michael Smith <msmith@xiph.org>,
 *                      oddsock <oddsock@xiph.org>,
 *                      Karl Heyes <karl@xiph.org>
 *                      and others (see AUTHORS for details).
 */

13
/* -*- c-basic-offset: 4; -*- */
Jack Moffitt's avatar
Jack Moffitt committed
14
15
16
17
18
19
/* format.c
**
** format plugin implementation
**
*/

20
21
22
23
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

Jack Moffitt's avatar
Jack Moffitt committed
24
25
#include <stdlib.h>
#include <string.h>
Karl Heyes's avatar
Karl Heyes committed
26
27
28
#ifdef HAVE_STRINGS_H
# include <strings.h>
#endif
29
30
31
#ifdef HAVE_SYS_TYPES_H
# include <sys/types.h>
#endif
Jack Moffitt's avatar
Jack Moffitt committed
32
33
34
35

#include "connection.h"
#include "refbuf.h"

Michael Smith's avatar
Michael Smith committed
36
#include "source.h"
Jack Moffitt's avatar
Jack Moffitt committed
37
#include "format.h"
38
#include "global.h"
Karl Heyes's avatar
Karl Heyes committed
39
#include "httpp/httpp.h"
Jack Moffitt's avatar
Jack Moffitt committed
40

41
#include "format_ogg.h"
Michael Smith's avatar
Michael Smith committed
42
#include "format_mp3.h"
43
#include "format_ebml.h"
Jack Moffitt's avatar
Jack Moffitt committed
44

45
#include "logging.h"
46
#include "stats.h"
47
#define CATMODULE "format"
Ed "oddsock" Zaleski's avatar
Ed "oddsock" Zaleski committed
48
49
50
51

#ifdef WIN32
#define strcasecmp stricmp
#define strncasecmp strnicmp
52
#define snprintf _snprintf
Ed "oddsock" Zaleski's avatar
Ed "oddsock" Zaleski committed
53
#endif
54

55
56
57
static int format_prepare_headers (source_t *source, client_t *client);


58
format_type_t format_get_type (const char *contenttype)
Michael Smith's avatar
Michael Smith committed
59
60
{
    if(strcmp(contenttype, "application/x-ogg") == 0)
61
        return FORMAT_TYPE_OGG; /* Backwards compatibility */
62
    else if(strcmp(contenttype, "application/ogg") == 0)
63
        return FORMAT_TYPE_OGG; /* Now blessed by IANA */
Karl Heyes's avatar
Karl Heyes committed
64
65
66
67
    else if(strcmp(contenttype, "audio/ogg") == 0)
        return FORMAT_TYPE_OGG;
    else if(strcmp(contenttype, "video/ogg") == 0)
        return FORMAT_TYPE_OGG;
68
69
70
71
72
73
74
75
76
77
    else if(strcmp(contenttype, "audio/webm") == 0)
        return FORMAT_TYPE_EBML;
    else if(strcmp(contenttype, "video/webm") == 0)
        return FORMAT_TYPE_EBML;
    else if(strcmp(contenttype, "audio/x-matroska") == 0)
        return FORMAT_TYPE_EBML;
    else if(strcmp(contenttype, "video/x-matroska") == 0)
        return FORMAT_TYPE_EBML;
    else if(strcmp(contenttype, "video/x-matroska-3d") == 0)
        return FORMAT_TYPE_EBML;
78
    else
79
        /* We default to the Generic format handler, which
80
81
           can handle many more formats than just mp3.
	   Let's warn that this is not well supported */
82
	ICECAST_LOG_WARN("Unsupported or legacy stream type: \"%s\". Falling back to generic minimal handler for best effort.", contenttype);
83
        return FORMAT_TYPE_GENERIC;
84
85
}

Karl Heyes's avatar
Karl Heyes committed
86
int format_get_plugin(format_type_t type, source_t *source)
Jack Moffitt's avatar
Jack Moffitt committed
87
{
Karl Heyes's avatar
Karl Heyes committed
88
    int ret = -1;
Jack Moffitt's avatar
Jack Moffitt committed
89

90
    switch (type) {
91
92
    case FORMAT_TYPE_OGG:
        ret = format_ogg_get_plugin (source);
93
        break;
giles's avatar
giles committed
94
95
96
    case FORMAT_TYPE_EBML:
        ret = format_ebml_get_plugin (source);
        break;
97
    case FORMAT_TYPE_GENERIC:
98
99
        ret = format_mp3_get_plugin (source);
        break;
100
101
102
    default:
        break;
    }
Karl Heyes's avatar
Karl Heyes committed
103
104
105
    if (ret < 0)
        stats_event (source->mount, "content-type", 
                source->format->contenttype);
Jack Moffitt's avatar
Jack Moffitt committed
106

Karl Heyes's avatar
Karl Heyes committed
107
    return ret;
108
109
}

Karl Heyes's avatar
Karl Heyes committed
110
111
112
113
114
115
116
117

/* clients need to be start from somewhere in the queue so we will look for
 * a refbuf which has been previously marked as a sync point. 
 */
static void find_client_start (source_t *source, client_t *client)
{
    refbuf_t *refbuf = source->burst_point;

118
    /* we only want to attempt a burst at connection time, not midstream
119
120
121
122
     * however streams like theora may not have the most recent page marked as
     * a starting point, so look for one from the burst point */
    if (client->intro_offset == -1 && source->stream_data_tail
            && source->stream_data_tail->sync_point)
Karl Heyes's avatar
Karl Heyes committed
123
124
125
        refbuf = source->stream_data_tail;
    else
    {
126
        size_t size = client->intro_offset;
Karl Heyes's avatar
Karl Heyes committed
127
        refbuf = source->burst_point;
128
        while (size > 0 && refbuf && refbuf->next)
Karl Heyes's avatar
Karl Heyes committed
129
130
131
132
133
134
135
136
137
138
139
140
        {
            size -= refbuf->len;
            refbuf = refbuf->next;
        }
    }

    while (refbuf)
    {
        if (refbuf->sync_point)
        {
            client_set_queue (client, refbuf);
            client->check_buffer = format_advance_queue;
141
            client->write_to_client = source->format->write_buf_to_client;
Karl Heyes's avatar
Karl Heyes committed
142
143
144
145
146
147
148
149
150
151
152
            client->intro_offset = -1;
            break;
        }
        refbuf = refbuf->next;
    }
}


static int get_file_data (FILE *intro, client_t *client)
{
    refbuf_t *refbuf = client->refbuf;
153
    size_t bytes;
Karl Heyes's avatar
Karl Heyes committed
154
155
156
157
158
159
160

    if (intro == NULL || fseek (intro, client->intro_offset, SEEK_SET) < 0)
        return 0;
    bytes = fread (refbuf->data, 1, 4096, intro);
    if (bytes == 0)
        return 0;

161
    refbuf->len = (unsigned int)bytes;
Karl Heyes's avatar
Karl Heyes committed
162
163
164
165
166
167
168
169
170
171
172
173
174
175
    return 1;
}


/* call to check the buffer contents for file reading. move the client
 * to right place in the queue at end of file else repeat file if queue
 * is not ready yet.
 */
int format_check_file_buffer (source_t *source, client_t *client)
{
    refbuf_t *refbuf = client->refbuf;

    if (refbuf == NULL)
    {
176
        /* client refers to no data, must be from a move */
177
        if (source->client)
178
179
180
181
182
        {
            find_client_start (source, client);
            return -1;
        }
        /* source -> file fallback, need a refbuf for data */
183
        refbuf = refbuf_new (PER_CLIENT_REFBUF_SIZE);
184
185
186
        client->refbuf = refbuf;
        client->pos = refbuf->len;
        client->intro_offset = 0;
Karl Heyes's avatar
Karl Heyes committed
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
    }
    if (client->pos == refbuf->len)
    {
        if (get_file_data (source->intro_file, client))
        {
            client->pos = 0;
            client->intro_offset += refbuf->len;
        }
        else
        {
            if (source->stream_data_tail)
            {
                /* better find the right place in queue for this client */
                client_set_queue (client, NULL);
                find_client_start (source, client);
            }
            else
                client->intro_offset = 0;  /* replay intro file */
            return -1;
        }
    }
    return 0;
}


212
213
214
215
216
217
218
219
220
221
222
223
/* call this to verify that the HTTP data has been sent and if so setup
 * callbacks to the appropriate format functions
 */
int format_check_http_buffer (source_t *source, client_t *client)
{
    refbuf_t *refbuf = client->refbuf;

    if (refbuf == NULL)
        return -1;

    if (client->respcode == 0)
    {
224
        ICECAST_LOG_DEBUG("processing pending client headers");
225
226
227

        if (format_prepare_headers (source, client) < 0)
        {
228
            ICECAST_LOG_ERROR("internal problem, dropping client");
229
230
231
            client->con->error = 1;
            return -1;
        }
Karl Heyes's avatar
Karl Heyes committed
232
        client->respcode = 200;
Karl Heyes's avatar
Karl Heyes committed
233
        stats_event_inc (NULL, "listeners");
234
235
        stats_event_inc (NULL, "listener_connections");
        stats_event_inc (source->mount, "listener_connections");
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
    }

    if (client->pos == refbuf->len)
    {
        client->write_to_client = source->format->write_buf_to_client;
        client->check_buffer = format_check_file_buffer;
        client->intro_offset = 0;
        client->pos = refbuf->len = 4096;
        return -1;
    }
    return 0;
}


int format_generic_write_to_client (client_t *client)
{
    refbuf_t *refbuf = client->refbuf;
    int ret;
    const char *buf = refbuf->data + client->pos;
    unsigned int len = refbuf->len - client->pos;

    ret = client_send_bytes (client, buf, len);

    if (ret > 0)
        client->pos += ret;

    return ret;
}


Karl Heyes's avatar
Karl Heyes committed
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
/* This is the commonly used for source streams, here we just progress to
 * the next buffer in the queue if there is no more left to be written from 
 * the existing buffer.
 */
int format_advance_queue (source_t *source, client_t *client)
{
    refbuf_t *refbuf = client->refbuf;

    if (refbuf == NULL)
        return -1;

    if (refbuf->next == NULL && client->pos == refbuf->len)
        return -1;

    /* move to the next buffer if we have finished with the current one */
    if (refbuf->next && client->pos == refbuf->len)
    {
        client_set_queue (client, refbuf->next);
        refbuf = client->refbuf;
    }
    return 0;
}


290
static int format_prepare_headers (source_t *source, client_t *client)
Michael Smith's avatar
Michael Smith committed
291
{
292
293
    unsigned remaining;
    char *ptr;
Michael Smith's avatar
Michael Smith committed
294
    int bytes;
295
296
297
298
299
300
301
    int bitrate_filtered = 0;
    avl_node *node;

    remaining = client->refbuf->len;
    ptr = client->refbuf->data;
    client->respcode = 200;

302
303
304
    bytes = util_http_build_header(ptr, remaining, 0, 0, 200, NULL, source->format->contenttype, NULL, NULL, source);
    if (bytes == -1) {
        ICECAST_LOG_ERROR("Dropping client as we can not build response headers.");
305
        client_send_error(client, 500, 0, "Header generation failed.");
306
307
308
309
310
311
312
313
314
315
        return -1;
    } else if ((bytes + 1024) >= remaining) { /* we don't know yet how much to follow but want at least 1kB free space */
        void *new_ptr = realloc(ptr, bytes + 1024);
        if (new_ptr) {
            ICECAST_LOG_DEBUG("Client buffer reallocation succeeded.");
            client->refbuf->data = ptr = new_ptr;
            client->refbuf->len = remaining = bytes + 1024;
            bytes = util_http_build_header(ptr, remaining, 0, 0, 200, NULL, source->format->contenttype, NULL, NULL, source);
            if (bytes == -1 ) {
                ICECAST_LOG_ERROR("Dropping client as we can not build response headers.");
316
                client_send_error(client, 500, 0, "Header generation failed.");
317
318
319
320
                return -1;
            }
        } else {
            ICECAST_LOG_ERROR("Client buffer reallocation failed. Dropping client.");
321
            client_send_error(client, 500, 0, "Buffer reallocation failed.");
322
323
324
            return -1;
        }
    }
325
326
327

    remaining -= bytes;
    ptr += bytes;
Michael Smith's avatar
Michael Smith committed
328

329
330
331
    /* iterate through source http headers and send to client */
    avl_tree_rlock(source->parser->vars);
    node = avl_get_first(source->parser->vars);
Karl Heyes's avatar
Karl Heyes committed
332
333
    while (node)
    {
334
335
336
337
338
        int next = 1;
        http_var_t *var = (http_var_t *)node->key;
        bytes = 0;
        if (!strcasecmp(var->name, "ice-audio-info"))
        {
Karl Heyes's avatar
Karl Heyes committed
339
            /* convert ice-audio-info to icy-br */
340
            char *brfield = NULL;
Karl Heyes's avatar
Karl Heyes committed
341
342
            unsigned int bitrate;

343
344
345
346
347
348
349
            if (bitrate_filtered == 0)
                brfield = strstr(var->value, "bitrate=");
            if (brfield && sscanf (brfield, "bitrate=%u", &bitrate))
            {           
                bytes = snprintf (ptr, remaining, "icy-br:%u\r\n", bitrate);
                next = 0;
                bitrate_filtered = 1;
Karl Heyes's avatar
Karl Heyes committed
350
            }
351
352
353
            else
                /* show ice-audio_info header as well because of relays */
                bytes = snprintf (ptr, remaining, "%s: %s\r\n", var->name, var->value);
Karl Heyes's avatar
Karl Heyes committed
354
355
356
357
358
359
        }
        else
        {
            if (strcasecmp(var->name, "ice-password") &&
                strcasecmp(var->name, "icy-metaint"))
            {
360
361
362
363
364
365
		if (!strcasecmp(var->name, "ice-name"))
		{
		    ice_config_t *config;
		    mount_proxy *mountinfo;

		    config = config_get_config();
366
		    mountinfo = config_find_mount (config, source->mount, MOUNT_TYPE_NORMAL);
367
368
369
370
371
372
373
374
375

		    if (mountinfo && mountinfo->stream_name)
		        bytes = snprintf (ptr, remaining, "icy-name:%s\r\n", mountinfo->stream_name);
                    else
		        bytes = snprintf (ptr, remaining, "icy-name:%s\r\n", var->value);

                    config_release_config();
		}
                else if (!strncasecmp("ice-", var->name, 4))
Karl Heyes's avatar
Karl Heyes committed
376
                {
377
378
                    if (!strcasecmp("ice-public", var->name))
                        bytes = snprintf (ptr, remaining, "icy-pub:%s\r\n", var->value);
Karl Heyes's avatar
Karl Heyes committed
379
                    else
380
381
                        if (!strcasecmp ("ice-bitrate", var->name))
                            bytes = snprintf (ptr, remaining, "icy-br:%s\r\n", var->value);
382
                        else
383
384
                            bytes = snprintf (ptr, remaining, "icy%s:%s\r\n",
                                    var->name + 3, var->value);
Karl Heyes's avatar
Karl Heyes committed
385
                }
386
387
388
389
390
391
                else
                    if (!strncasecmp("icy-", var->name, 4))
                    {
                        bytes = snprintf (ptr, remaining, "icy%s:%s\r\n",
                                var->name + 3, var->value);
                    }
Karl Heyes's avatar
Karl Heyes committed
392
            }
393
        }
394
395
396
397
398

        remaining -= bytes;
        ptr += bytes;
        if (next)
            node = avl_get_next(node);
399
400
    }
    avl_tree_unlock(source->parser->vars);
401
402
403
404
405
406
407
408
409
410

    bytes = snprintf (ptr, remaining, "\r\n");
    remaining -= bytes;
    ptr += bytes;

    client->refbuf->len -= remaining;
    if (source->format->create_client_data)
        if (source->format->create_client_data (source, client) < 0)
            return -1;
    return 0;
Michael Smith's avatar
Michael Smith committed
411
412
}

413