Oops. Fix null-terminator.
[xiph/unicode.git] / icecast / src / format_mp3.c
blobcced126781f93fdfdf7f5b8c73d39cc8e6682104
1 /* Icecast
3 * This program is distributed under the GNU General Public License, version 2.
4 * A copy of this license is included with this source.
6 * Copyright 2000-2004, Jack Moffitt <jack@xiph.org,
7 * Michael Smith <msmith@xiph.org>,
8 * oddsock <oddsock@xiph.org>,
9 * Karl Heyes <karl@xiph.org>
10 * and others (see AUTHORS for details).
13 /* -*- c-basic-offset: 4; -*- */
14 /* format_mp3.c
16 ** format plugin for mp3
20 #ifdef HAVE_CONFIG_H
21 #include <config.h>
22 #endif
24 #include <stdio.h>
25 #include <stdlib.h>
26 #include <string.h>
27 #ifdef HAVE_STRINGS_H
28 # include <strings.h>
29 #endif
31 #include "refbuf.h"
32 #include "source.h"
33 #include "client.h"
35 #include "stats.h"
36 #include "format.h"
37 #include "httpp/httpp.h"
39 #include "logging.h"
41 #include "format_mp3.h"
43 #ifdef WIN32
44 #define strcasecmp stricmp
45 #define strncasecmp strnicmp
46 #define snprintf _snprintf
47 #endif
49 #define CATMODULE "format-mp3"
51 /* Note that this seems to be 8192 in shoutcast - perhaps we want to be the
52 * same for compability with crappy clients?
54 #define ICY_METADATA_INTERVAL 16000
56 static void format_mp3_free_plugin(format_plugin_t *self);
57 static int format_mp3_get_buffer(format_plugin_t *self, char *data,
58 unsigned long len, refbuf_t **buffer);
59 static refbuf_queue_t *format_mp3_get_predata(format_plugin_t *self);
60 static void *format_mp3_create_client_data(format_plugin_t *self,
61 source_t *source, client_t *client);
62 static int format_mp3_write_buf_to_client(format_plugin_t *self,
63 client_t *client, unsigned char *buf, int len);
64 static void format_mp3_send_headers(format_plugin_t *self,
65 source_t *source, client_t *client);
67 typedef struct {
68 int use_metadata;
69 int interval;
70 int offset;
71 int metadata_age;
72 int metadata_offset;
73 } mp3_client_data;
75 format_plugin_t *format_mp3_get_plugin(http_parser_t *parser)
77 char *metadata;
78 format_plugin_t *plugin;
79 mp3_state *state = calloc(1, sizeof(mp3_state));
81 plugin = (format_plugin_t *)malloc(sizeof(format_plugin_t));
83 plugin->type = FORMAT_TYPE_MP3;
84 plugin->has_predata = 0;
85 plugin->get_buffer = format_mp3_get_buffer;
86 plugin->get_predata = format_mp3_get_predata;
87 plugin->write_buf_to_client = format_mp3_write_buf_to_client;
88 plugin->create_client_data = format_mp3_create_client_data;
89 plugin->client_send_headers = format_mp3_send_headers;
90 plugin->free_plugin = format_mp3_free_plugin;
91 plugin->format_description = "MP3 audio";
93 plugin->_state = state;
95 state->metadata_age = 0;
96 state->metadata = strdup("");
97 thread_mutex_create(&(state->lock));
99 metadata = httpp_getvar(parser, "icy-metaint");
100 if(metadata)
101 state->inline_metadata_interval = atoi(metadata);
103 return plugin;
106 static int send_metadata(client_t *client, mp3_client_data *client_state,
107 mp3_state *source_state)
109 int len_byte;
110 int len;
111 int ret = -1;
112 unsigned char *buf;
113 int source_age;
114 char *fullmetadata = NULL;
115 int fullmetadata_size = 0;
116 const char meta_fmt[] = "StreamTitle='';";
120 thread_mutex_lock (&(source_state->lock));
121 if (source_state->metadata == NULL)
122 break; /* Shouldn't be possible */
124 fullmetadata_size = strlen (source_state->metadata) + sizeof (meta_fmt);
126 if (fullmetadata_size > 4080)
128 fullmetadata_size = 4080;
130 fullmetadata = malloc (fullmetadata_size);
131 if (fullmetadata == NULL)
132 break;
134 fullmetadata_size = snprintf (fullmetadata, fullmetadata_size,
135 "StreamTitle='%.*s';", fullmetadata_size-(sizeof (meta_fmt)-1), source_state->metadata);
137 source_age = source_state->metadata_age;
139 if (fullmetadata_size > 0 && source_age != client_state->metadata_age)
141 len_byte = (fullmetadata_size-1)/16 + 1; /* to give 1-255 */
142 client_state->metadata_offset = 0;
144 else
145 len_byte = 0;
146 len = 1 + len_byte*16;
147 buf = malloc (len);
148 if (buf == NULL)
149 break;
151 buf[0] = len_byte;
153 if (len > 1) {
154 strncpy (buf+1, fullmetadata, len-1);
155 buf[len-1] = '\0';
158 thread_mutex_unlock (&(source_state->lock));
160 /* only write what hasn't been written already */
161 ret = sock_write_bytes (client->con->sock, buf+client_state->metadata_offset, len-client_state->metadata_offset);
163 if (ret > 0 && ret < len) {
164 client_state->metadata_offset += ret;
166 else if (ret == len) {
167 client_state->metadata_age = source_age;
168 client_state->offset = 0;
169 client_state->metadata_offset = 0;
171 free (buf);
172 free (fullmetadata);
173 return ret;
175 } while (0);
177 thread_mutex_unlock(&(source_state->lock));
178 free (fullmetadata);
179 return -1;
182 static int format_mp3_write_buf_to_client(format_plugin_t *self,
183 client_t *client, unsigned char *buf, int len)
185 int ret;
186 mp3_client_data *mp3data = client->format_data;
188 if(((mp3_state *)self->_state)->metadata && mp3data->use_metadata)
190 mp3_client_data *state = client->format_data;
191 int max = state->interval - state->offset;
193 if(len == 0) /* Shouldn't happen */
194 return 0;
196 if(max > len)
197 max = len;
199 if(max > 0) {
200 ret = sock_write_bytes(client->con->sock, buf, max);
201 if(ret > 0)
202 state->offset += ret;
204 else {
205 ret = send_metadata(client, state, self->_state);
206 if(ret > 0)
207 client->con->sent_bytes += ret;
208 ret = 0;
212 else {
213 ret = sock_write_bytes(client->con->sock, buf, len);
216 if(ret < 0) {
217 if(sock_recoverable(sock_error())) {
218 DEBUG1("Client had recoverable error %ld", ret);
219 ret = 0;
222 else
223 client->con->sent_bytes += ret;
225 return ret;
228 static void format_mp3_free_plugin(format_plugin_t *self)
230 /* free the plugin instance */
231 mp3_state *state = self->_state;
232 thread_mutex_destroy(&(state->lock));
234 free(state->metadata);
235 free(state);
236 free(self);
239 static int format_mp3_get_buffer(format_plugin_t *self, char *data,
240 unsigned long len, refbuf_t **buffer)
242 refbuf_t *refbuf;
243 mp3_state *state = self->_state;
245 /* Set this to NULL in case it doesn't get set to a valid buffer later */
246 *buffer = NULL;
248 if(!data)
249 return 0;
251 if(state->inline_metadata_interval) {
252 /* Source is sending metadata, handle it... */
254 while(len > 0) {
255 int to_read = state->inline_metadata_interval - state->offset;
256 if(to_read > 0) {
257 refbuf_t *old_refbuf = *buffer;
259 if(to_read > len)
260 to_read = len;
262 if(old_refbuf) {
263 refbuf = refbuf_new(to_read + old_refbuf->len);
264 memcpy(refbuf->data, old_refbuf->data, old_refbuf->len);
265 memcpy(refbuf->data+old_refbuf->len, data, to_read);
267 refbuf_release(old_refbuf);
269 else {
270 refbuf = refbuf_new(to_read);
271 memcpy(refbuf->data, data, to_read);
274 *buffer = refbuf;
276 state->offset += to_read;
277 data += to_read;
278 len -= to_read;
280 else if(!state->metadata_length) {
281 /* Next up is the metadata byte... */
282 unsigned char byte = data[0];
283 data++;
284 len--;
286 /* According to the "spec"... this byte * 16 */
287 state->metadata_length = byte * 16;
289 if(state->metadata_length) {
290 state->metadata_buffer =
291 calloc(state->metadata_length + 1, 1);
293 /* Ensure we have a null-terminator even if the source
294 * stream is invalid.
296 state->metadata_buffer[state->metadata_length] = 0;
298 else {
299 state->offset = 0;
302 state->metadata_offset = 0;
304 else {
305 /* Metadata to read! */
306 int readable = state->metadata_length - state->metadata_offset;
308 if(readable > len)
309 readable = len;
311 memcpy(state->metadata_buffer + state->metadata_offset,
312 data, readable);
314 state->metadata_offset += readable;
316 data += readable;
317 len -= readable;
319 if(state->metadata_offset == state->metadata_length)
321 if(state->metadata_length)
323 thread_mutex_lock(&(state->lock));
324 free(state->metadata);
325 /* Now, reformat state->metadata_buffer to strip off
326 StreamTitle=' and the closing '; (but only if there's
327 enough data for it to be correctly formatted) */
328 if(state->metadata_length >= 15) {
329 state->metadata = malloc(state->metadata_length -
330 15 + 1);
331 memcpy(state->metadata,
332 state->metadata_buffer + 13,
333 state->metadata_length - 15);
334 state->metadata[state->metadata_length - 15] = 0;
335 free(state->metadata_buffer);
337 else
338 state->metadata = state->metadata_buffer;
340 stats_event(self->mount, "title", state->metadata);
341 state->metadata_buffer = NULL;
342 state->metadata_age++;
343 thread_mutex_unlock(&(state->lock));
346 state->offset = 0;
347 state->metadata_length = 0;
352 /* Either we got a buffer above (in which case it can be used), or
353 * we set *buffer to NULL in the prologue, so the return value is
354 * correct anyway...
356 return 0;
358 else {
359 /* Simple case - no metadata, just dump data directly to a buffer */
360 refbuf = refbuf_new(len);
362 memcpy(refbuf->data, data, len);
364 *buffer = refbuf;
365 return 0;
369 static refbuf_queue_t *format_mp3_get_predata(format_plugin_t *self)
371 return NULL;
374 static void *format_mp3_create_client_data(format_plugin_t *self,
375 source_t *source, client_t *client)
377 mp3_client_data *data = calloc(1,sizeof(mp3_client_data));
378 char *metadata;
380 data->interval = ICY_METADATA_INTERVAL;
381 data->offset = 0;
383 metadata = httpp_getvar(client->parser, "icy-metadata");
384 if(metadata)
385 data->use_metadata = atoi(metadata)>0?1:0;
387 return data;
390 static void format_mp3_send_headers(format_plugin_t *self,
391 source_t *source, client_t *client)
393 int bytes;
394 mp3_client_data *mp3data = client->format_data;
396 client->respcode = 200;
397 /* TODO: This may need to be ICY/1.0 for shoutcast-compatibility? */
398 bytes = sock_write(client->con->sock,
399 "HTTP/1.0 200 OK\r\n"
400 "Content-Type: %s\r\n",
401 format_get_mimetype(source->format->type));
403 if (bytes > 0)
404 client->con->sent_bytes += bytes;
406 if (mp3data->use_metadata)
408 int bytes = sock_write(client->con->sock, "icy-metaint:%d\r\n",
409 ICY_METADATA_INTERVAL);
410 if(bytes > 0)
411 client->con->sent_bytes += bytes;
413 format_send_general_headers(self, source, client);