diff --git a/TODO b/TODO index 93cbc5bdb88c2c7d9136d20ef3da455f7e5e16f3..6f4e9f18b68d286c55cf8470191c01d53ccf43a2 100644 --- a/TODO +++ b/TODO @@ -2,12 +2,12 @@ BUGS ---- - stats get off? this needs testing more testing. -- autoconf doesn't set HAVE_POLL (still true?) - - some stuff (like 'genre') isn't making it into the stats dump - make install - doesn't install configs? +- logging - bytes send and time listening may both be broken? + FEATURES -------- @@ -46,7 +46,10 @@ FEATURES - httpp - split out query string for further processing -- binding to multiple ports (possibly including full ipv6 support) +- binding to multiple ports + +- option to use ipv6 (equiv to using ::, I think. +- per-mountpoint listener maximums. diff --git a/conf/icecast.xml b/conf/icecast.xml index 36c00dff667f41314cd39b2ef124d891b95bf5c0..8bdde06946ea556b88dea39aec1fbd87267e58b5 100644 --- a/conf/icecast.xml +++ b/conf/icecast.xml @@ -6,6 +6,7 @@ 100 2 5 + 102400 30 15 10 @@ -42,6 +43,9 @@ 127.0.0.1 8001 /example.ogg + /different.ogg + + 0 --> diff --git a/src/config.c b/src/config.c index 6bf5498cdc80244bae38b32c48b4b0b16d22c164..52c9f00600be88dd1546ef3861242e87fcef1146 100644 --- a/src/config.c +++ b/src/config.c @@ -13,6 +13,7 @@ #define CONFIG_DEFAULT_ADMIN "icemaster@localhost" #define CONFIG_DEFAULT_CLIENT_LIMIT 256 #define CONFIG_DEFAULT_SOURCE_LIMIT 16 +#define CONFIG_DEFAULT_QUEUE_SIZE_LIMIT (100*1024) #define CONFIG_DEFAULT_THREADPOOL_SIZE 4 #define CONFIG_DEFAULT_CLIENT_TIMEOUT 30 #define CONFIG_DEFAULT_HEADER_TIMEOUT 15 @@ -193,6 +194,7 @@ static void _set_defaults(void) _configuration.admin = CONFIG_DEFAULT_ADMIN; _configuration.client_limit = CONFIG_DEFAULT_CLIENT_LIMIT; _configuration.source_limit = CONFIG_DEFAULT_SOURCE_LIMIT; + _configuration.queue_size_limit = CONFIG_DEFAULT_QUEUE_SIZE_LIMIT; _configuration.threadpool_size = CONFIG_DEFAULT_THREADPOOL_SIZE; _configuration.client_timeout = CONFIG_DEFAULT_CLIENT_TIMEOUT; _configuration.header_timeout = CONFIG_DEFAULT_HEADER_TIMEOUT; @@ -318,6 +320,10 @@ static void _parse_limits(xmlDocPtr doc, xmlNodePtr node) tmp = (char *)xmlNodeListGetString(doc, node->xmlChildrenNode, 1); _configuration.source_limit = atoi(tmp); if (tmp) xmlFree(tmp); + } else if (strcmp(node->name, "queue-size") == 0) { + tmp = (char *)xmlNodeListGetString(doc, node->xmlChildrenNode, 1); + _configuration.queue_size_limit = atoi(tmp); + if (tmp) xmlFree(tmp); } else if (strcmp(node->name, "threadpool") == 0) { tmp = (char *)xmlNodeListGetString(doc, node->xmlChildrenNode, 1); _configuration.threadpool_size = atoi(tmp); @@ -425,6 +431,11 @@ static void _parse_relay(xmlDocPtr doc, xmlNodePtr node) relay->localmount = (char *)xmlNodeListGetString( doc, node->xmlChildrenNode, 1); } + else if (strcmp(node->name, "relay-shoutcast-metadata") == 0) { + tmp = (char *)xmlNodeListGetString(doc, node->xmlChildrenNode, 1); + relay->mp3metadata = atoi(tmp); + if(tmp) xmlFree(tmp); + } } while ((node = node->next)); } diff --git a/src/config.h b/src/config.h index 2b425f1568bdbd3d3719878346777823136917d5..212856dd4f5ae137243e90f5a59667fd1661aa79 100644 --- a/src/config.h +++ b/src/config.h @@ -20,6 +20,7 @@ typedef struct _relay_server { int port; char *mount; char *localmount; + int mp3metadata; struct _relay_server *next; } relay_server; @@ -44,6 +45,7 @@ typedef struct ice_config_tag int client_limit; int source_limit; + long queue_size_limit; int threadpool_size; int client_timeout; int header_timeout; diff --git a/src/connection.c b/src/connection.c index 1f9a1d356a6b56a0dc6bda440d19b3a9e3a2eaf0..ef73fb5fcd40820438e6a08b534f1688f8fd0bbd 100644 --- a/src/connection.c +++ b/src/connection.c @@ -535,6 +535,7 @@ static void handle_metadata_request(client_t *client) free(state->metadata); state->metadata = strdup(value); state->metadata_age++; + state->metadata_raw = 0; thread_mutex_unlock(&(state->lock)); DEBUG2("Metadata on mountpoint %s changed to \"%s\"", mount, value); diff --git a/src/format.c b/src/format.c index d995c901394ffbd082912e1113539095e5a550bf..6cbaddde728ec1192130dbaeadc6837cca6c4ab4 100644 --- a/src/format.c +++ b/src/format.c @@ -80,7 +80,7 @@ int format_generic_write_buf_to_client(format_plugin_t *format, client_t *client, unsigned char *buf, int len) { int ret; - + ret = sock_write_bytes(client->con->sock, buf, len); if(ret < 0) { diff --git a/src/format_mp3.c b/src/format_mp3.c index b1c575c4a34a48073747486e98d4dd5819744515..5de600f76cb4fec9968c88dc239f96db55e38643 100644 --- a/src/format_mp3.c +++ b/src/format_mp3.c @@ -101,15 +101,19 @@ static int send_metadata(client_t *client, mp3_client_data *client_state, return 0; } - fullmetadata_size = strlen(source_state->metadata) + - strlen("StreamTitle='';StreamUrl=''") + 1; - - fullmetadata = alloca(fullmetadata_size); + 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; - memset(fullmetadata, 0, fullmetadata_size); + fullmetadata = alloca(fullmetadata_size); - sprintf(fullmetadata, "StreamTitle='%s';StreamUrl=''", - source_state->metadata); + sprintf(fullmetadata, "StreamTitle='%s';StreamUrl=''", + source_state->metadata); + } source_age = source_state->metadata_age; send_metadata = source_age != client_state->metadata_age; @@ -210,14 +214,104 @@ static int format_mp3_get_buffer(format_plugin_t *self, char *data, refbuf_t *refbuf; mp3_state *state = self->_state; - if(!data) { - *buffer = NULL; + /* Set this to NULL in case it doesn't get set to a valid buffer later */ + *buffer = NULL; + + if(!data) return 0; - } + if(state->inline_metadata_interval) { + /* 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; + 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); + + data += readable; + len -= readable; + + if(state->metadata_offset == state->metadata_length) + { + state->offset = 0; + state->metadata_length = 0; + + 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)); + } + } + } + } + + /* 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... + */ return 0; } else { + /* Simple case - no metadata, just dump data directly to a buffer */ refbuf = refbuf_new(len); memcpy(refbuf->data, data, len); diff --git a/src/format_mp3.h b/src/format_mp3.h index 64fc55176a116a4764c50b8c25273b05af1c640d..c24ae91f6519026a9dce5f0fc8d6bdcf6b1ddbbc 100644 --- a/src/format_mp3.h +++ b/src/format_mp3.h @@ -9,9 +9,15 @@ typedef struct { char *metadata; int metadata_age; - int metadata_formatted; - int inline_metadata_interval; + int metadata_raw; mutex_t lock; + + /* These are for inline metadata */ + int inline_metadata_interval; + int offset; + int metadata_length; + char *metadata_buffer; + int metadata_offset; } mp3_state; format_plugin_t *format_mp3_get_plugin(http_parser_t *parser); diff --git a/src/refbuf.c b/src/refbuf.c index 39d9c2d0b12d2668d35eae506e99e2578f4227b3..9aaa051d86a85962ccf1b062a906ecb427428575 100644 --- a/src/refbuf.c +++ b/src/refbuf.c @@ -83,8 +83,12 @@ refbuf_t *refbuf_queue_remove(refbuf_queue_t **queue) refbuf = item->refbuf; item->refbuf = NULL; + + if(*queue) + (*queue)->total_length = item->total_length - refbuf->len; free(item); + return refbuf; } @@ -95,6 +99,7 @@ void refbuf_queue_insert(refbuf_queue_t **queue, refbuf_t *refbuf) item->refbuf = refbuf; item->next = *queue; + item->total_length = item->next->total_length + item->refbuf->len; *queue = item; } @@ -111,5 +116,12 @@ int refbuf_queue_size(refbuf_queue_t **queue) return size; } +int refbuf_queue_length(refbuf_queue_t **queue) +{ + if(*queue) + return (*queue)->total_length; + else + return 0; +} diff --git a/src/refbuf.h b/src/refbuf.h index c1d1c1f4394f75232192cae166beff5fd29aa95a..3ab89772fc19796163bd1a4c5a9df28bced9a415 100644 --- a/src/refbuf.h +++ b/src/refbuf.h @@ -17,6 +17,7 @@ typedef struct _refbuf_tag typedef struct _refbuf_queue_tag { refbuf_t *refbuf; + long total_length; struct _refbuf_queue_tag *next; } refbuf_queue_t; @@ -31,7 +32,11 @@ void refbuf_release(refbuf_t *self); void refbuf_queue_add(refbuf_queue_t **queue, refbuf_t *refbuf); refbuf_t *refbuf_queue_remove(refbuf_queue_t **queue); void refbuf_queue_insert(refbuf_queue_t **queue, refbuf_t *refbuf); + +/* Size in buffers */ int refbuf_queue_size(refbuf_queue_t **queue); +/* Size in bytes */ +int refbuf_queue_length(refbuf_queue_t **queue); #endif /* __REFBUF_H__ */ diff --git a/src/slave.c b/src/slave.c index 2756e5ec347872c2fd3d34c2d0ee4f2cb413e27b..533f75eba6e16905009d34487a78aff806539983 100644 --- a/src/slave.c +++ b/src/slave.c @@ -66,7 +66,7 @@ void slave_shutdown(void) { } static void create_relay_stream(char *server, int port, - char *remotemount, char *localmount) + char *remotemount, char *localmount, int mp3) { sock_t streamsock; char header[4096]; @@ -85,7 +85,13 @@ static void create_relay_stream(char *server, int port, return; } con = create_connection(streamsock, NULL); - sock_write(streamsock, "GET %s HTTP/1.0\r\n\r\n", remotemount); + if(mp3) { + sock_write(streamsock, "GET %s HTTP/1.0\r\nIcy-MetaData: 1\r\n", + remotemount); + } + else { + sock_write(streamsock, "GET %s HTTP/1.0\r\n\r\n", remotemount); + } memset(header, 0, sizeof(header)); if (util_read_header(con->sock, header, 4096) == 0) { connection_close(con); @@ -168,7 +174,7 @@ static void *_slave_thread(void *arg) { create_relay_stream( config_get_config()->master_server, config_get_config()->master_server_port, - buf, NULL); + buf, NULL, 0); } else avl_tree_unlock(global.source_tree); @@ -184,7 +190,7 @@ static void *_slave_thread(void *arg) { avl_tree_unlock(global.source_tree); create_relay_stream(relay->server, relay->port, relay->mount, - relay->localmount); + relay->localmount, relay->mp3metadata); } else avl_tree_unlock(global.source_tree); diff --git a/src/source.c b/src/source.c index 2e0aa260eb8a55be99a72e6fe7156d47a6000514..2572ddbefdc5c17b71b363b2a36a8597e327463b 100644 --- a/src/source.c +++ b/src/source.c @@ -153,6 +153,8 @@ void *source_main(void *arg) int i=0; int suppress_yp = 0; + long queue_limit = config_get_config()->queue_size_limit; + timeout = config_get_config()->source_timeout; /* grab a read lock, to make sure we get a chance to cleanup */ @@ -447,10 +449,8 @@ void *source_main(void *arg) /* if the client is too slow, its queue will slowly build up. ** we need to make sure the client is keeping up with the ** data, so we'll kick any client who's queue gets to large. - ** the queue_limit might need to be tuned, but should work fine. - ** TODO: put queue_limit in a config file */ - if (refbuf_queue_size(&client->queue) > 25) { + if (refbuf_queue_length(&client->queue) > queue_limit) { DEBUG0("Client has fallen too far behind, removing"); client->con->error = 1; }