format_mp3.c 11.4 KB
Newer Older
1
/* -*- c-basic-offset: 4; -*- */
Michael Smith's avatar
Michael Smith committed
2 3
/* format_mp3.c
**
Michael Smith's avatar
Michael Smith committed
4
** format plugin for mp3
Michael Smith's avatar
Michael Smith committed
5 6 7
**
*/

8 9 10 11
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

Michael Smith's avatar
Michael Smith committed
12 13 14 15 16
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "refbuf.h"
Michael Smith's avatar
Michael Smith committed
17 18
#include "source.h"
#include "client.h"
Michael Smith's avatar
Michael Smith committed
19 20 21

#include "stats.h"
#include "format.h"
Michael Smith's avatar
Michael Smith committed
22 23 24 25 26 27
#include "httpp/httpp.h"

#include "logging.h"

#include "format_mp3.h"

Ed "oddsock" Zaleski's avatar
Ed "oddsock" Zaleski committed
28 29 30
#ifdef WIN32
#define strcasecmp stricmp
#define strncasecmp strnicmp
Ed "oddsock" Zaleski's avatar
Ed "oddsock" Zaleski committed
31
#define alloca _alloca
Ed "oddsock" Zaleski's avatar
Ed "oddsock" Zaleski committed
32 33
#endif

Michael Smith's avatar
Michael Smith committed
34 35
#define CATMODULE "format-mp3"

36 37 38
/* Note that this seems to be 8192 in shoutcast - perhaps we want to be the
 * same for compability with crappy clients?
 */
Michael Smith's avatar
Michael Smith committed
39
#define ICY_METADATA_INTERVAL 16000
Michael Smith's avatar
Michael Smith committed
40

41 42 43 44
static void format_mp3_free_plugin(format_plugin_t *self);
static int format_mp3_get_buffer(format_plugin_t *self, char *data, 
        unsigned long len, refbuf_t **buffer);
static refbuf_queue_t *format_mp3_get_predata(format_plugin_t *self);
Michael Smith's avatar
Michael Smith committed
45 46 47 48 49 50 51 52
static void *format_mp3_create_client_data(format_plugin_t *self,
        source_t *source, client_t *client);
static int format_mp3_write_buf_to_client(format_plugin_t *self,
        client_t *client, unsigned char *buf, int len);
static void format_mp3_send_headers(format_plugin_t *self, 
        source_t *source, client_t *client);

typedef struct {
Michael Smith's avatar
Michael Smith committed
53
   int use_metadata;
Michael Smith's avatar
Michael Smith committed
54 55
   int interval;
   int offset;
Michael Smith's avatar
Michael Smith committed
56 57
   int metadata_age;
   int metadata_offset;
Michael Smith's avatar
Michael Smith committed
58
} mp3_client_data;
Ed "oddsock" Zaleski's avatar
Ed "oddsock" Zaleski committed
59

60
format_plugin_t *format_mp3_get_plugin(http_parser_t *parser)
Michael Smith's avatar
Michael Smith committed
61
{
62
    char *metadata;
63
    format_plugin_t *plugin;
Michael Smith's avatar
Michael Smith committed
64
    mp3_state *state = calloc(1, sizeof(mp3_state));
Michael Smith's avatar
Michael Smith committed
65

66
    plugin = (format_plugin_t *)malloc(sizeof(format_plugin_t));
Michael Smith's avatar
Michael Smith committed
67

68 69 70 71
    plugin->type = FORMAT_TYPE_MP3;
    plugin->has_predata = 0;
    plugin->get_buffer = format_mp3_get_buffer;
    plugin->get_predata = format_mp3_get_predata;
Michael Smith's avatar
Michael Smith committed
72
    plugin->write_buf_to_client = format_mp3_write_buf_to_client;
73
    plugin->create_client_data = format_mp3_create_client_data;
Michael Smith's avatar
Michael Smith committed
74
    plugin->client_send_headers = format_mp3_send_headers;
75
    plugin->free_plugin = format_mp3_free_plugin;
76
    plugin->format_description = "MP3 audio";
Michael Smith's avatar
Michael Smith committed
77

78
    plugin->_state = state;
Michael Smith's avatar
Michael Smith committed
79

Michael Smith's avatar
Michael Smith committed
80 81
    state->metadata_age = 0;
    state->metadata = strdup("");
Michael Smith's avatar
Michael Smith committed
82
    thread_mutex_create(&(state->lock));
Michael Smith's avatar
Michael Smith committed
83

84 85 86 87
    metadata = httpp_getvar(parser, "icy-metaint");
    if(metadata)
        state->inline_metadata_interval = atoi(metadata);

88
    return plugin;
Michael Smith's avatar
Michael Smith committed
89 90
}

Michael Smith's avatar
Michael Smith committed
91 92 93 94 95 96 97 98 99
static int send_metadata(client_t *client, mp3_client_data *client_state,
        mp3_state *source_state)
{
    int send_metadata;
    int len_byte;
    int len;
    unsigned char *buf;
    int ret;
    int source_age;
100 101
    char    *fullmetadata = NULL;
    int    fullmetadata_size = 0;
Michael Smith's avatar
Michael Smith committed
102 103 104 105 106 107 108 109

    thread_mutex_lock(&(source_state->lock));
    if(source_state->metadata == NULL) {
        /* Shouldn't be possible */
        thread_mutex_unlock(&(source_state->lock));
        return 0;
    }

110 111 112 113 114 115 116
    if(source_state->metadata_raw) {
        fullmetadata_size = strlen(source_state->metadata);
        fullmetadata = source_state->metadata;
    }
    else {
        fullmetadata_size = strlen(source_state->metadata) + 
            strlen("StreamTitle='';StreamUrl=''") + 1;
117

118
        fullmetadata = alloca(fullmetadata_size);
119

120 121 122
        sprintf(fullmetadata, "StreamTitle='%s';StreamUrl=''", 
                source_state->metadata);
    }
123

Michael Smith's avatar
Michael Smith committed
124
    source_age = source_state->metadata_age;
Michael Smith's avatar
Michael Smith committed
125 126
    send_metadata = source_age != client_state->metadata_age;

127 128
    if(send_metadata && strlen(fullmetadata) > 0)
        len_byte = strlen(fullmetadata)/16 + 1 - 
Michael Smith's avatar
Michael Smith committed
129 130 131
            client_state->metadata_offset;
    else
        len_byte = 0;
Michael Smith's avatar
Michael Smith committed
132 133 134 135 136 137 138
    len = 1 + len_byte*16;
    buf = alloca(len);

    memset(buf, 0, len);

    buf[0] = len_byte;

139
    if (len > 1) {
140
        strncpy(buf+1, fullmetadata + client_state->metadata_offset, len-2);
141
    }
Michael Smith's avatar
Michael Smith committed
142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158

    thread_mutex_unlock(&(source_state->lock));

    ret = sock_write_bytes(client->con->sock, buf, len);

    if(ret > 0 && ret < len) {
        client_state->metadata_offset += ret;
    }
    else if(ret == len) {
        client_state->metadata_age = source_age;
        client_state->offset = 0;
        client_state->metadata_offset = 0;
    }

    return ret;
}

Michael Smith's avatar
Michael Smith committed
159 160 161 162
static int format_mp3_write_buf_to_client(format_plugin_t *self, 
    client_t *client, unsigned char *buf, int len) 
{
    int ret;
Michael Smith's avatar
Michael Smith committed
163
    
164 165
    if(((mp3_state *)self->_state)->metadata && 
            ((mp3_client_data *)(client->format_data))->use_metadata)
Michael Smith's avatar
Michael Smith committed
166 167 168
    {
        mp3_client_data *state = client->format_data;
        int max = state->interval - state->offset;
Michael Smith's avatar
Michael Smith committed
169

Michael Smith's avatar
Michael Smith committed
170 171 172 173 174 175 176 177 178 179 180
        if(len == 0) /* Shouldn't happen */
            return 0;

        if(max > len)
            max = len;

        if(max > 0) {
            ret = sock_write_bytes(client->con->sock, buf, max);
            if(ret > 0)
                state->offset += ret;
        }
181
        else {
Michael Smith's avatar
Michael Smith committed
182
            ret = send_metadata(client, state, self->_state);
183 184 185 186
            if(ret > 0)
                client->con->sent_bytes += ret;
            ret = 0;
        }
187

Michael Smith's avatar
Michael Smith committed
188 189 190 191
    }
    else {
        ret = sock_write_bytes(client->con->sock, buf, len);
    }
Michael Smith's avatar
Michael Smith committed
192 193

    if(ret < 0) {
194
        if(sock_recoverable(sock_error())) {
Michael Smith's avatar
Michael Smith committed
195 196 197 198 199 200 201 202 203 204 205
            DEBUG1("Client had recoverable error %ld", ret);
            ret = 0;
        }
    }
    else
        client->con->sent_bytes += ret;

    return ret;
}

static void format_mp3_free_plugin(format_plugin_t *self)
Michael Smith's avatar
Michael Smith committed
206
{
207
    /* free the plugin instance */
Michael Smith's avatar
Michael Smith committed
208 209
    mp3_state *state = self->_state;
    thread_mutex_destroy(&(state->lock));
Michael Smith's avatar
Michael Smith committed
210 211

    free(state->metadata);
Michael Smith's avatar
Michael Smith committed
212
    free(state);
213
    free(self);
Michael Smith's avatar
Michael Smith committed
214 215
}

Michael Smith's avatar
Michael Smith committed
216 217
static int format_mp3_get_buffer(format_plugin_t *self, char *data, 
    unsigned long len, refbuf_t **buffer)
Michael Smith's avatar
Michael Smith committed
218
{
219
    refbuf_t *refbuf;
220 221
    mp3_state *state = self->_state;

222 223 224 225
    /* Set this to NULL in case it doesn't get set to a valid buffer later */
    *buffer = NULL;

    if(!data)
Michael Smith's avatar
Michael Smith committed
226
        return 0;
227

228
    if(state->inline_metadata_interval) {
229 230 231 232 233 234 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
        /* Source is sending metadata, handle it... */

        while(len > 0) {
            int to_read = state->inline_metadata_interval - state->offset;
            if(to_read > 0) {
                refbuf_t *old_refbuf = *buffer;

                if(to_read > len)
                    to_read = len;

                if(old_refbuf) {
                    refbuf = refbuf_new(to_read + old_refbuf->len);
                    memcpy(refbuf->data, old_refbuf->data, old_refbuf->len);
                    memcpy(refbuf->data+old_refbuf->len, data, to_read);

                    refbuf_release(old_refbuf);
                }
                else {
                    refbuf = refbuf_new(to_read);
                    memcpy(refbuf->data, data, to_read);
                }

                *buffer = refbuf;

                state->offset += to_read;
                data += to_read;
                len -= to_read;
            }
            else if(!state->metadata_length) {
                /* Next up is the metadata byte... */
                unsigned char byte = data[0];
                data++;
                len--;

                /* According to the "spec"... this byte * 16 */
                state->metadata_length = byte * 16;
265

266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290
                if(state->metadata_length) {
                    state->metadata_buffer = 
                        calloc(state->metadata_length + 1, 1);

                    /* Ensure we have a null-terminator even if the source
                     * stream is invalid.
                     */
                    state->metadata_buffer[state->metadata_length] = 0;
                }
                else {
                    state->offset = 0;
                }

                state->metadata_offset = 0;
            }
            else {
                /* Metadata to read! */
                int readable = state->metadata_length - state->metadata_offset;

                if(readable > len)
                    readable = len;

                memcpy(state->metadata_buffer + state->metadata_offset, 
                        data, readable);

291 292
                state->metadata_offset += readable;

293 294 295 296 297 298 299 300 301 302 303 304 305 306 307
                data += readable;
                len -= readable;

                if(state->metadata_offset == state->metadata_length)
                {
                    if(state->metadata_length)
                    {
                        thread_mutex_lock(&(state->lock));
                        free(state->metadata);
                        state->metadata = state->metadata_buffer;
                        state->metadata_buffer = NULL;
                        state->metadata_age++;
                        state->metadata_raw = 1;
                        thread_mutex_unlock(&(state->lock));
                    }
308 309 310

                    state->offset = 0;
                    state->metadata_length = 0;
311 312 313 314 315 316 317 318
                }
            }
        }

        /* Either we got a buffer above (in which case it can be used), or
         * we set *buffer to NULL in the prologue, so the return value is
         * correct anyway...
         */
319 320 321
        return 0;
    }
    else {
322
        /* Simple case - no metadata, just dump data directly to a buffer */
323
        refbuf = refbuf_new(len);
Michael Smith's avatar
Michael Smith committed
324

325
        memcpy(refbuf->data, data, len);
Michael Smith's avatar
Michael Smith committed
326

327
        *buffer = refbuf;
328
        return 0;
329
    }
Michael Smith's avatar
Michael Smith committed
330 331
}

Michael Smith's avatar
Michael Smith committed
332
static refbuf_queue_t *format_mp3_get_predata(format_plugin_t *self)
Michael Smith's avatar
Michael Smith committed
333 334 335 336
{
    return NULL;
}

Michael Smith's avatar
Michael Smith committed
337 338 339 340 341 342 343 344 345
static void *format_mp3_create_client_data(format_plugin_t *self, 
        source_t *source, client_t *client) 
{
    mp3_client_data *data = calloc(1,sizeof(mp3_client_data));
    char *metadata;

    data->interval = ICY_METADATA_INTERVAL;
    data->offset = 0;

346
    metadata = httpp_getvar(client->parser, "icy-metadata");
Michael Smith's avatar
Michael Smith committed
347
    if(metadata)
Michael Smith's avatar
Michael Smith committed
348
        data->use_metadata = atoi(metadata)>0?1:0;
Michael Smith's avatar
Michael Smith committed
349 350

    return data;
351
}
Michael Smith's avatar
Michael Smith committed
352

Michael Smith's avatar
Michael Smith committed
353 354 355
static void format_mp3_send_headers(format_plugin_t *self,
        source_t *source, client_t *client)
{
356 357
    http_var_t *var;
    avl_node *node;
Michael Smith's avatar
Michael Smith committed
358 359 360
    int bytes;
    
    client->respcode = 200;
Michael Smith's avatar
Michael Smith committed
361
    /* TODO: This may need to be ICY/1.0 for shoutcast-compatibility? */
Michael Smith's avatar
Michael Smith committed
362 363 364 365 366 367 368
    bytes = sock_write(client->con->sock, 
            "HTTP/1.0 200 OK\r\n" 
            "Content-Type: %s\r\n", 
            format_get_mimetype(source->format->type));

    if(bytes > 0) client->con->sent_bytes += bytes;

369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396
    /* iterate through source http headers and send to client */
    avl_tree_rlock(source->parser->vars);
    node = avl_get_first(source->parser->vars);
    while (node) {
        var = (http_var_t *)node->key;
	if (!strcasecmp(var->name, "ice-audio-info")) {
	    /* convert ice-audio-info to icy-br */
	    char *brfield;
	    unsigned int bitrate;

	    brfield = strstr(var->value, "bitrate=");
	    if (brfield && sscanf(var->value, "bitrate=%u", &bitrate)) {
		bytes = sock_write(client->con->sock, "icy-br:%u\r\n", bitrate);
		if (bytes > 0)
		    client->con->sent_bytes += bytes;
	    }
	} else if (strcasecmp(var->name, "ice-password") &&
	    strcasecmp(var->name, "icy-metaint") &&
	    (!strncasecmp("ice-", var->name, 4) ||
	     !strncasecmp("icy-", var->name, 4))) {
	    bytes = sock_write(client->con->sock, "icy%s:%s\r\n",
			       var->name + 3, var->value);
            if (bytes > 0)
		client->con->sent_bytes += bytes;
        }
        node = avl_get_next(node);
    }
    avl_tree_unlock(source->parser->vars);
Michael Smith's avatar
Michael Smith committed
397

Michael Smith's avatar
Michael Smith committed
398
    if(((mp3_client_data *)(client->format_data))->use_metadata) {
399
        int bytes = sock_write(client->con->sock, "icy-metaint:%d\r\n", 
Michael Smith's avatar
Michael Smith committed
400 401 402 403
                ICY_METADATA_INTERVAL);
        if(bytes > 0)
            client->con->sent_bytes += bytes;
    }
404 405 406 407 408

    bytes = sock_write(client->con->sock,
		       "Server: %s\r\n", ICECAST_VERSION_STRING);
    if (bytes > 0)
	client->con->sent_bytes += bytes;
Michael Smith's avatar
Michael Smith committed
409 410
}