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"
Jack Moffitt's avatar
Jack Moffitt committed
39

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

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

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

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


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

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

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

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

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

/* 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;

117
    /* we only want to attempt a burst at connection time, not midstream
118 119 120 121
     * 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
122 123 124
        refbuf = source->stream_data_tail;
    else
    {
125
        size_t size = client->intro_offset;
Karl Heyes's avatar
Karl Heyes committed
126
        refbuf = source->burst_point;
127
        while (size > 0 && refbuf && refbuf->next)
Karl Heyes's avatar
Karl Heyes committed
128 129 130 131 132 133 134 135 136 137 138 139
        {
            size -= refbuf->len;
            refbuf = refbuf->next;
        }
    }

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


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

    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;

160
    refbuf->len = (unsigned int)bytes;
Karl Heyes's avatar
Karl Heyes committed
161 162 163 164 165 166 167 168 169 170 171 172 173 174
    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)
    {
175
        /* client refers to no data, must be from a move */
176
        if (source->client)
177 178 179 180 181
        {
            find_client_start (source, client);
            return -1;
        }
        /* source -> file fallback, need a refbuf for data */
182
        refbuf = refbuf_new (PER_CLIENT_REFBUF_SIZE);
183 184 185
        client->refbuf = refbuf;
        client->pos = refbuf->len;
        client->intro_offset = 0;
Karl Heyes's avatar
Karl Heyes committed
186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210
    }
    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;
}


211 212 213 214 215 216 217 218 219 220 221 222
/* 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)
    {
223
        ICECAST_LOG_DEBUG("processing pending client headers");
224 225 226

        if (format_prepare_headers (source, client) < 0)
        {
227
            ICECAST_LOG_ERROR("internal problem, dropping client");
228 229 230
            client->con->error = 1;
            return -1;
        }
Karl Heyes's avatar
Karl Heyes committed
231
        client->respcode = 200;
Karl Heyes's avatar
Karl Heyes committed
232
        stats_event_inc (NULL, "listeners");
233 234
        stats_event_inc (NULL, "listener_connections");
        stats_event_inc (source->mount, "listener_connections");
235 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
    }

    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
265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288
/* 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;
}


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

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

301 302 303
    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.");
304
        client_send_error(client, 500, 0, "Header generation failed.");
305 306 307 308 309 310 311 312 313 314
        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.");
315
                client_send_error(client, 500, 0, "Header generation failed.");
316 317 318 319
                return -1;
            }
        } else {
            ICECAST_LOG_ERROR("Client buffer reallocation failed. Dropping client.");
320
            client_send_error(client, 500, 0, "Buffer reallocation failed.");
321 322 323
            return -1;
        }
    }
324 325 326

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

328 329 330
    /* 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
331 332
    while (node)
    {
333 334 335 336 337
        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
338
            /* convert ice-audio-info to icy-br */
339
            char *brfield = NULL;
Karl Heyes's avatar
Karl Heyes committed
340 341
            unsigned int bitrate;

342 343 344 345 346 347 348
            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
349
            }
350 351 352
            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
353 354 355 356 357 358
        }
        else
        {
            if (strcasecmp(var->name, "ice-password") &&
                strcasecmp(var->name, "icy-metaint"))
            {
359 360 361 362 363 364
		if (!strcasecmp(var->name, "ice-name"))
		{
		    ice_config_t *config;
		    mount_proxy *mountinfo;

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

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

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

    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
410 411
}

412