mirror of
https://gitlab.xiph.org/xiph/icecast-server.git
synced 2025-02-02 15:07:36 -05:00
Max queue length for clients is now
a) based on total bytes in queue, not total number of buffers in queue b) configurable (defaults to 100 kB) mp3 metadata relaying (inline). Untested. svn path=/trunk/icecast/; revision=4364
This commit is contained in:
parent
8c14c0e0db
commit
5019130d27
11
TODO
11
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 <bind-address>::</bindaddress>, I think.
|
||||
|
||||
- per-mountpoint listener maximums.
|
||||
|
||||
|
||||
|
@ -6,6 +6,7 @@
|
||||
<clients>100</clients>
|
||||
<sources>2</sources>
|
||||
<threadpool>5</threadpool>
|
||||
<queue-size>102400</queue-size>
|
||||
<client-timeout>30</client-timeout>
|
||||
<header-timeout>15</header-timeout>
|
||||
<source-timeout>10</source-timeout>
|
||||
@ -42,6 +43,9 @@
|
||||
<server>127.0.0.1</server>
|
||||
<port>8001</port>
|
||||
<mount>/example.ogg</mount>
|
||||
<local-mount>/different.ogg</local-mount>
|
||||
|
||||
<relay-shoutcast-metadata>0</relay-shoutcast-metadata>
|
||||
</relay>
|
||||
-->
|
||||
|
||||
|
11
src/config.c
11
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));
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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) {
|
||||
|
114
src/format_mp3.c
114
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;
|
||||
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;
|
||||
|
||||
fullmetadata = alloca(fullmetadata_size);
|
||||
fullmetadata = alloca(fullmetadata_size);
|
||||
|
||||
memset(fullmetadata, 0, 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);
|
||||
|
@ -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);
|
||||
|
12
src/refbuf.c
12
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;
|
||||
}
|
||||
|
||||
|
||||
|
@ -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__ */
|
||||
|
||||
|
14
src/slave.c
14
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);
|
||||
|
@ -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;
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user