format_mp3.c 12.7 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; -*- */
Michael Smith's avatar
Michael Smith committed
14
15
/* format_mp3.c
**
Michael Smith's avatar
Michael Smith committed
16
** format plugin for mp3
Michael Smith's avatar
Michael Smith committed
17
18
19
**
*/

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

Michael Smith's avatar
Michael Smith committed
24
25
26
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
Karl Heyes's avatar
Karl Heyes committed
27
28
29
#ifdef HAVE_STRINGS_H
# include <strings.h>
#endif
Michael Smith's avatar
Michael Smith committed
30
31

#include "refbuf.h"
Michael Smith's avatar
Michael Smith committed
32
33
#include "source.h"
#include "client.h"
Michael Smith's avatar
Michael Smith committed
34
35
36

#include "stats.h"
#include "format.h"
Michael Smith's avatar
Michael Smith committed
37
38
39
40
41
42
#include "httpp/httpp.h"

#include "logging.h"

#include "format_mp3.h"

Ed "oddsock" Zaleski's avatar
Ed "oddsock" Zaleski committed
43
44
45
#ifdef WIN32
#define strcasecmp stricmp
#define strncasecmp strnicmp
Ed "oddsock" Zaleski's avatar
Ed "oddsock" Zaleski committed
46
#define snprintf _snprintf
Ed "oddsock" Zaleski's avatar
Ed "oddsock" Zaleski committed
47
48
#endif

Michael Smith's avatar
Michael Smith committed
49
50
#define CATMODULE "format-mp3"

51
52
53
/* 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
54
#define ICY_METADATA_INTERVAL 16000
Michael Smith's avatar
Michael Smith committed
55

56
57
58
59
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
60
61
62
63
64
65
66
67
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
68
   int use_metadata;
Michael Smith's avatar
Michael Smith committed
69
70
   int interval;
   int offset;
Michael Smith's avatar
Michael Smith committed
71
72
   int metadata_age;
   int metadata_offset;
Michael Smith's avatar
Michael Smith committed
73
} mp3_client_data;
Ed "oddsock" Zaleski's avatar
Ed "oddsock" Zaleski committed
74

75
format_plugin_t *format_mp3_get_plugin(http_parser_t *parser)
Michael Smith's avatar
Michael Smith committed
76
{
77
    char *metadata;
78
    format_plugin_t *plugin;
Michael Smith's avatar
Michael Smith committed
79
    mp3_state *state = calloc(1, sizeof(mp3_state));
Michael Smith's avatar
Michael Smith committed
80

81
    plugin = (format_plugin_t *)malloc(sizeof(format_plugin_t));
Michael Smith's avatar
Michael Smith committed
82

83
84
85
86
    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
87
    plugin->write_buf_to_client = format_mp3_write_buf_to_client;
88
    plugin->create_client_data = format_mp3_create_client_data;
Michael Smith's avatar
Michael Smith committed
89
    plugin->client_send_headers = format_mp3_send_headers;
90
    plugin->free_plugin = format_mp3_free_plugin;
91
    plugin->format_description = "MP3 audio";
Michael Smith's avatar
Michael Smith committed
92

93
    plugin->_state = state;
Michael Smith's avatar
Michael Smith committed
94

Michael Smith's avatar
Michael Smith committed
95
96
    state->metadata_age = 0;
    state->metadata = strdup("");
Michael Smith's avatar
Michael Smith committed
97
    thread_mutex_create(&(state->lock));
Michael Smith's avatar
Michael Smith committed
98

99
100
101
102
    metadata = httpp_getvar(parser, "icy-metaint");
    if(metadata)
        state->inline_metadata_interval = atoi(metadata);

103
    return plugin;
Michael Smith's avatar
Michael Smith committed
104
105
}

Michael Smith's avatar
Michael Smith committed
106
107
108
static int send_metadata(client_t *client, mp3_client_data *client_state,
        mp3_state *source_state)
{
109
    int free_meta = 0;
Michael Smith's avatar
Michael Smith committed
110
111
    int len_byte;
    int len;
112
    int ret = -1;
Michael Smith's avatar
Michael Smith committed
113
114
    unsigned char *buf;
    int source_age;
115
116
117
    char *fullmetadata = NULL;
    int  fullmetadata_size = 0;
    const char meta_fmt[] = "StreamTitle='';"; 
Michael Smith's avatar
Michael Smith committed
118

119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
    do 
    {
        thread_mutex_lock (&(source_state->lock));
        if (source_state->metadata == NULL)
            break; /* Shouldn't be possible */

        if (source_state->metadata_raw)
        {
            fullmetadata_size = strlen (source_state->metadata);
            fullmetadata = source_state->metadata;
            if (fullmetadata_size > 4080)
            {
                fullmetadata_size = 4080;
            }
        }
        else
        {
            fullmetadata_size = strlen (source_state->metadata) + 
                sizeof (meta_fmt);

            if (fullmetadata_size > 4080)
            {
                fullmetadata_size = 4080;
            }
            fullmetadata = malloc (fullmetadata_size);
            if (fullmetadata == NULL)
                break;
146

147
148
149
150
            fullmetadata_size = snprintf (fullmetadata, fullmetadata_size,
                    "StreamTitle='%.*s';", fullmetadata_size-(sizeof (meta_fmt)-1), source_state->metadata); 
            free_meta = 1;
        }
151

152
        source_age = source_state->metadata_age;
153

154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
        if (fullmetadata_size > 0 && source_age != client_state->metadata_age)
        {
            len_byte = (fullmetadata_size-1)/16 + 1; /* to give 1-255 */
            client_state->metadata_offset = 0;
        }
        else
            len_byte = 0;
        len = 1 + len_byte*16;
        buf = malloc (len);
        if (buf == NULL)
            break;

        buf[0] = len_byte;

        if (len > 1) {
            strncpy (buf+1, fullmetadata, len-1);
            buf[len-1] = '\0';
        }
Michael Smith's avatar
Michael Smith committed
172

173
        thread_mutex_unlock (&(source_state->lock));
Michael Smith's avatar
Michael Smith committed
174

175
176
        /* only write what hasn't been written already */
        ret = sock_write_bytes (client->con->sock, buf+client_state->metadata_offset, len-client_state->metadata_offset);
Michael Smith's avatar
Michael Smith committed
177

178
179
180
181
182
183
184
185
186
187
188
189
        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;
        }
        free (buf);
        if (free_meta)
            free (fullmetadata);
        return ret;
Michael Smith's avatar
Michael Smith committed
190

191
    } while (0);
Michael Smith's avatar
Michael Smith committed
192
193

    thread_mutex_unlock(&(source_state->lock));
194
195
196
    if (free_meta)
        free (fullmetadata);
    return -1;
Michael Smith's avatar
Michael Smith committed
197
198
}

Michael Smith's avatar
Michael Smith committed
199
200
201
202
static int format_mp3_write_buf_to_client(format_plugin_t *self, 
    client_t *client, unsigned char *buf, int len) 
{
    int ret;
203
    mp3_client_data *mp3data = client->format_data;
Michael Smith's avatar
Michael Smith committed
204
    
205
    if(((mp3_state *)self->_state)->metadata && mp3data->use_metadata)
Michael Smith's avatar
Michael Smith committed
206
207
208
    {
        mp3_client_data *state = client->format_data;
        int max = state->interval - state->offset;
Michael Smith's avatar
Michael Smith committed
209

Michael Smith's avatar
Michael Smith committed
210
211
212
213
214
215
216
217
218
219
220
        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;
        }
221
        else {
Michael Smith's avatar
Michael Smith committed
222
            ret = send_metadata(client, state, self->_state);
223
224
225
226
            if(ret > 0)
                client->con->sent_bytes += ret;
            ret = 0;
        }
227

Michael Smith's avatar
Michael Smith committed
228
229
230
231
    }
    else {
        ret = sock_write_bytes(client->con->sock, buf, len);
    }
Michael Smith's avatar
Michael Smith committed
232
233

    if(ret < 0) {
234
        if(sock_recoverable(sock_error())) {
Michael Smith's avatar
Michael Smith committed
235
236
237
238
239
240
241
242
243
244
245
            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
246
{
247
    /* free the plugin instance */
Michael Smith's avatar
Michael Smith committed
248
249
    mp3_state *state = self->_state;
    thread_mutex_destroy(&(state->lock));
Michael Smith's avatar
Michael Smith committed
250
251

    free(state->metadata);
Michael Smith's avatar
Michael Smith committed
252
    free(state);
253
    free(self);
Michael Smith's avatar
Michael Smith committed
254
255
}

Michael Smith's avatar
Michael Smith committed
256
257
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
258
{
259
    refbuf_t *refbuf;
260
261
    mp3_state *state = self->_state;

262
263
264
265
    /* 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
266
        return 0;
267

268
    if(state->inline_metadata_interval) {
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
        /* 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;
305

306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
                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);

331
332
                state->metadata_offset += readable;

333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
                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));
                    }
348
349
350

                    state->offset = 0;
                    state->metadata_length = 0;
351
352
353
354
355
356
357
358
                }
            }
        }

        /* 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...
         */
359
360
361
        return 0;
    }
    else {
362
        /* Simple case - no metadata, just dump data directly to a buffer */
363
        refbuf = refbuf_new(len);
Michael Smith's avatar
Michael Smith committed
364

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

367
        *buffer = refbuf;
368
        return 0;
369
    }
Michael Smith's avatar
Michael Smith committed
370
371
}

Michael Smith's avatar
Michael Smith committed
372
static refbuf_queue_t *format_mp3_get_predata(format_plugin_t *self)
Michael Smith's avatar
Michael Smith committed
373
374
375
376
{
    return NULL;
}

Michael Smith's avatar
Michael Smith committed
377
378
379
380
381
382
383
384
385
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;

386
    metadata = httpp_getvar(client->parser, "icy-metadata");
Michael Smith's avatar
Michael Smith committed
387
    if(metadata)
Michael Smith's avatar
Michael Smith committed
388
        data->use_metadata = atoi(metadata)>0?1:0;
Michael Smith's avatar
Michael Smith committed
389
390

    return data;
391
}
Michael Smith's avatar
Michael Smith committed
392

Michael Smith's avatar
Michael Smith committed
393
394
395
static void format_mp3_send_headers(format_plugin_t *self,
        source_t *source, client_t *client)
{
396
397
    http_var_t *var;
    avl_node *node;
Michael Smith's avatar
Michael Smith committed
398
    int bytes;
399
    mp3_client_data *mp3data = client->format_data;
Michael Smith's avatar
Michael Smith committed
400
401
    
    client->respcode = 200;
Michael Smith's avatar
Michael Smith committed
402
    /* TODO: This may need to be ICY/1.0 for shoutcast-compatibility? */
Michael Smith's avatar
Michael Smith committed
403
404
405
406
407
408
409
    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;

410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
    /* 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
438

439
    if (mp3data->use_metadata) {
440
        int bytes = sock_write(client->con->sock, "icy-metaint:%d\r\n", 
Michael Smith's avatar
Michael Smith committed
441
442
443
444
                ICY_METADATA_INTERVAL);
        if(bytes > 0)
            client->con->sent_bytes += bytes;
    }
445
446
447
448

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