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; -*- */
16 ** format plugin for mp3
37 #include "httpp/httpp.h"
41 #include "format_mp3.h"
44 #define strcasecmp stricmp
45 #define strncasecmp strnicmp
46 #define snprintf _snprintf
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
);
75 format_plugin_t
*format_mp3_get_plugin(http_parser_t
*parser
)
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");
101 state
->inline_metadata_interval
= atoi(metadata
);
106 static int send_metadata(client_t
*client
, mp3_client_data
*client_state
,
107 mp3_state
*source_state
)
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
)
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;
146 len
= 1 + len_byte
*16;
154 strncpy (buf
+1, fullmetadata
, len
-1);
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;
177 thread_mutex_unlock(&(source_state
->lock
));
182 static int format_mp3_write_buf_to_client(format_plugin_t
*self
,
183 client_t
*client
, unsigned char *buf
, int len
)
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 */
200 ret
= sock_write_bytes(client
->con
->sock
, buf
, max
);
202 state
->offset
+= ret
;
205 ret
= send_metadata(client
, state
, self
->_state
);
207 client
->con
->sent_bytes
+= ret
;
213 ret
= sock_write_bytes(client
->con
->sock
, buf
, len
);
217 if(sock_recoverable(sock_error())) {
218 DEBUG1("Client had recoverable error %ld", ret
);
223 client
->con
->sent_bytes
+= 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
);
239 static int format_mp3_get_buffer(format_plugin_t
*self
, char *data
,
240 unsigned long len
, refbuf_t
**buffer
)
243 mp3_state
*state
= self
->_state
;
245 /* Set this to NULL in case it doesn't get set to a valid buffer later */
251 if(state
->inline_metadata_interval
) {
252 /* Source is sending metadata, handle it... */
255 int to_read
= state
->inline_metadata_interval
- state
->offset
;
257 refbuf_t
*old_refbuf
= *buffer
;
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
);
270 refbuf
= refbuf_new(to_read
);
271 memcpy(refbuf
->data
, data
, to_read
);
276 state
->offset
+= to_read
;
280 else if(!state
->metadata_length
) {
281 /* Next up is the metadata byte... */
282 unsigned char byte
= data
[0];
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
296 state
->metadata_buffer
[state
->metadata_length
] = 0;
302 state
->metadata_offset
= 0;
305 /* Metadata to read! */
306 int readable
= state
->metadata_length
- state
->metadata_offset
;
311 memcpy(state
->metadata_buffer
+ state
->metadata_offset
,
314 state
->metadata_offset
+= 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
-
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
);
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
));
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
359 /* Simple case - no metadata, just dump data directly to a buffer */
360 refbuf
= refbuf_new(len
);
362 memcpy(refbuf
->data
, data
, len
);
369 static refbuf_queue_t
*format_mp3_get_predata(format_plugin_t
*self
)
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
));
380 data
->interval
= ICY_METADATA_INTERVAL
;
383 metadata
= httpp_getvar(client
->parser
, "icy-metadata");
385 data
->use_metadata
= atoi(metadata
)>0?1:0;
390 static void format_mp3_send_headers(format_plugin_t
*self
,
391 source_t
*source
, client_t
*client
)
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
));
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
);
411 client
->con
->sent_bytes
+= bytes
;
413 format_send_general_headers(self
, source
, client
);