static void format_mp3_apply_settings (client_t *client, format_plugin_t *format, mount_proxy *mount) { mp3_state *source_mp3 = format->_state; source_mp3->interval = -1; free (format->charset); format->charset = NULL; if (mount) { if (mount->mp3_meta_interval >= 0) source_mp3->interval = mount->mp3_meta_interval; if (mount->charset) format->charset = strdup (mount->charset); } if (source_mp3->interval < 0) { const char *metadata = httpp_getvar (client->parser, "icy-metaint"); source_mp3->interval = ICY_METADATA_INTERVAL; if (metadata) { int interval = atoi (metadata); if (interval > 0) source_mp3->interval = interval; } } if (format->charset == NULL) format->charset = strdup ("ISO8859-1"); ICECAST_LOG_DEBUG("sending metadata interval %d", source_mp3->interval); ICECAST_LOG_DEBUG("charset %s", format->charset); }
/* if 0 is returned then the client should not be touched, however if -1 * is returned then the caller is responsible for handling the client */ static int add_listener_to_source (source_t *source, client_t *client) { int loop = 10; do { ICECAST_LOG_DEBUG("max on %s is %ld (cur %lu)", source->mount, source->max_listeners, source->listeners); if (source->max_listeners == -1) break; if (source->listeners < (unsigned long)source->max_listeners) break; if (loop && source->fallback_when_full && source->fallback_mount) { source_t *next = source_find_mount (source->fallback_mount); if (!next) { ICECAST_LOG_ERROR("Fallback '%s' for full source '%s' not found", source->mount, source->fallback_mount); return -1; } ICECAST_LOG_INFO("stream full trying %s", next->mount); source = next; loop--; continue; } /* now we fail the client */ return -1; } while (1); client->write_to_client = format_generic_write_to_client; client->check_buffer = format_check_http_buffer; client->refbuf->len = PER_CLIENT_REFBUF_SIZE; memset (client->refbuf->data, 0, PER_CLIENT_REFBUF_SIZE); /* lets add the client to the active list */ avl_tree_wlock (source->pending_tree); avl_insert (source->pending_tree, client); avl_tree_unlock (source->pending_tree); if (source->running == 0 && source->on_demand) { /* enable on-demand relay to start, wake up the slave thread */ ICECAST_LOG_DEBUG("kicking off on-demand relay"); source->on_demand_req = 1; } ICECAST_LOG_DEBUG("Added client to %s", source->mount); return 0; }
/* helper to apply specialised changes to a stats node */ static void modify_node_event(stats_node_t *node, stats_event_t *event) { char *str; if (event->action == STATS_EVENT_HIDDEN) { if (event->value) node->hidden = 1; else node->hidden = 0; return; } if (event->action != STATS_EVENT_SET) { int64_t value = 0; switch (event->action) { case STATS_EVENT_INC: value = atoi (node->value)+1; break; case STATS_EVENT_DEC: value = atoi (node->value)-1; break; case STATS_EVENT_ADD: value = atoi (node->value)+atoi (event->value); break; case STATS_EVENT_SUB: value = atoll (node->value) - atoll (event->value); break; default: ICECAST_LOG_WARN("unhandled event (%d) for %s", event->action, event->source); break; } str = malloc (16); snprintf (str, 16, "%" PRId64, value); if (event->value == NULL) event->value = strdup (str); } else str = (char *)strdup (event->value); free (node->value); node->value = str; if (event->source) ICECAST_LOG_DEBUG("update \"%s\" %s (%s)", event->source, node->name, node->value); else ICECAST_LOG_DEBUG("update global %s (%s)", node->name, node->value); }
/* Add listener to the pending lists of either the source or fserve thread. * This can be run from the connection or auth thread context */ static int add_authenticated_listener (const char *mount, mount_proxy *mountinfo, client_t *client) { int ret = 0; source_t *source = NULL; client->authenticated = 1; /* Here we are parsing the URI request to see if the extension is .xsl, if * so, then process this request as an XSLT request */ if (util_check_valid_extension (mount) == XSLT_CONTENT) { /* If the file exists, then transform it, otherwise, write a 404 */ ICECAST_LOG_DEBUG("Stats request, sending XSL transformed stats"); stats_transform_xslt (client, mount); return 0; } avl_tree_rlock (global.source_tree); source = source_find_mount (mount); if (source) { if (mountinfo) { if (check_duplicate_logins (source, client, mountinfo->auth) == 0) { avl_tree_unlock (global.source_tree); return -1; } /* set a per-mount disconnect time if auth hasn't set one already */ if (mountinfo->max_listener_duration && client->con->discon_time == 0) client->con->discon_time = time(NULL) + mountinfo->max_listener_duration; } ret = add_listener_to_source (source, client); avl_tree_unlock (global.source_tree); if (ret == 0) ICECAST_LOG_DEBUG("client authenticated, passed to source"); } else { avl_tree_unlock (global.source_tree); fserve_client_create (client, mount); } return ret; }
static void flac_codec_free (ogg_state_t *ogg_info, ogg_codec_t *codec) { ICECAST_LOG_DEBUG("freeing FLAC codec"); stats_event (ogg_info->mount, "FLAC_version", NULL); ogg_stream_clear (&codec->os); free (codec); }
/* release the memory used for the codec and header pages from the module */ static void free_ogg_codecs (ogg_state_t *ogg_info) { ogg_codec_t *codec; if (ogg_info == NULL) return; format_ogg_free_headers (ogg_info); /* now free the codecs */ codec = ogg_info->codecs; ICECAST_LOG_DEBUG("freeing codecs"); while (codec) { ogg_codec_t *next = codec->next; if (codec->possible_start) refbuf_release (codec->possible_start); codec->codec_free (ogg_info, codec); codec = next; } ogg_info->codecs = NULL; ogg_info->current = NULL; ogg_info->bos_completed = 0; ogg_info->codec_count = 0; }
/* call this to verify that the HTTP data has been sent and if so setup * callbacks to the appropriate format functions */ int format_check_http_buffer(source_t *source, client_t *client) { refbuf_t *refbuf = client->refbuf; if (refbuf == NULL) return -1; if (client->respcode == 0) { ICECAST_LOG_DEBUG("processing pending client headers"); if (format_prepare_headers (source, client) < 0) { ICECAST_LOG_ERROR("internal problem, dropping client"); client->con->error = 1; return -1; } client->respcode = 200; stats_event_inc(NULL, "listeners"); stats_event_inc(NULL, "listener_connections"); stats_event_inc(source->mount, "listener_connections"); } if (client->pos == refbuf->len) { client->write_to_client = source->format->write_buf_to_client; client->check_buffer = format_check_file_buffer; client->intro_offset = 0; client->pos = refbuf->len = 4096; return -1; } return 0; }
ogg_codec_t *initial_opus_page (format_plugin_t *plugin, ogg_page *page) { ogg_state_t *ogg_info = plugin->_state; ogg_codec_t *codec = calloc (1, sizeof (ogg_codec_t)); ogg_packet packet; ogg_stream_init (&codec->os, ogg_page_serialno (page)); ogg_stream_pagein (&codec->os, page); ogg_stream_packetout (&codec->os, &packet); ICECAST_LOG_DEBUG("checking for opus codec"); if (strncmp((char *)packet.packet, "OpusHead", 8) != 0) { ogg_stream_clear (&codec->os); free (codec); return NULL; } ICECAST_LOG_INFO("seen initial opus header"); codec->process_page = process_opus_page; codec->codec_free = opus_codec_free; codec->headers = 1; format_ogg_attach_header (ogg_info, page); return codec; }
static void queue_auth_client (auth_client *auth_user, mount_proxy *mountinfo) { auth_t *auth; if (auth_user == NULL) return; auth_user->next = NULL; if (mountinfo) { auth = mountinfo->auth; thread_mutex_lock (&auth->lock); if (auth_user->client) auth_user->client->auth = auth; auth->refcount++; } else { if (auth_user->client == NULL || auth_user->client->auth == NULL) { ICECAST_LOG_WARN("internal state is incorrect for %p", auth_user->client); return; } auth = auth_user->client->auth; thread_mutex_lock (&auth->lock); } ICECAST_LOG_DEBUG("...refcount on auth_t %s is now %d", auth->mount, auth->refcount); *auth->tailp = auth_user; auth->tailp = &auth_user->next; auth->pending_count++; ICECAST_LOG_INFO("auth on %s has %d pending", auth->mount, auth->pending_count); thread_mutex_unlock (&auth->lock); }
/* helper function for reading data from a client */ int client_read_bytes (client_t *client, void *buf, unsigned len) { int bytes; if (client->refbuf && client->refbuf->len) { /* we have data to read from a refbuf first */ if (client->refbuf->len < len) len = client->refbuf->len; memcpy (buf, client->refbuf->data, len); if (len < client->refbuf->len) { char *ptr = client->refbuf->data; memmove (ptr, ptr+len, client->refbuf->len - len); } client->refbuf->len -= len; return len; } bytes = client->con->read (client->con, buf, len); if (bytes == -1 && client->con->error) ICECAST_LOG_DEBUG("reading from connection has failed"); return bytes; }
/* wrapper function for auth thread to authenticate new listener * connection details */ static void auth_new_listener (auth_t *auth, auth_client *auth_user) { client_t *client = auth_user->client; /* make sure there is still a client at this point, a slow backend request * can be avoided if client has disconnected */ if (is_listener_connected (client) == 0) { ICECAST_LOG_DEBUG("listener is no longer connected"); client->respcode = 400; auth_release (client->auth); client->auth = NULL; return; } if (auth->authenticate) { if (auth->authenticate (auth_user) != AUTH_OK) { auth_release (client->auth); client->auth = NULL; return; } } if (auth_postprocess_listener (auth_user) < 0) { auth_release (client->auth); client->auth = NULL; ICECAST_LOG_INFO("client %lu failed", client->con->id); } }
/* release the auth. It is referred to by multiple structures so this is * refcounted and only actual freed after the last use */ void auth_release (auth_t *authenticator) { if (authenticator == NULL) return; thread_mutex_lock (&authenticator->lock); authenticator->refcount--; ICECAST_LOG_DEBUG("...refcount on auth_t %s is now %d", authenticator->mount, authenticator->refcount); if (authenticator->refcount) { thread_mutex_unlock (&authenticator->lock); return; } /* cleanup auth thread attached to this auth */ authenticator->running = 0; thread_join (authenticator->thread); if (authenticator->free) authenticator->free (authenticator); xmlFree (authenticator->type); thread_mutex_unlock (&authenticator->lock); thread_mutex_destroy (&authenticator->lock); if (authenticator->mount) free (authenticator->mount); free (authenticator); }
ogg_codec_t *initial_midi_page (format_plugin_t *plugin, ogg_page *page) { ogg_state_t *ogg_info = plugin->_state; ogg_codec_t *codec = calloc (1, sizeof (ogg_codec_t)); ogg_packet packet; ogg_stream_init (&codec->os, ogg_page_serialno (page)); ogg_stream_pagein (&codec->os, page); ogg_stream_packetout (&codec->os, &packet); ICECAST_LOG_DEBUG("checking for MIDI codec"); do { if (packet.bytes < 9) break; if (memcmp (packet.packet, "OggMIDI\000", 8) != 0) break; if (packet.bytes != 12) break; ICECAST_LOG_INFO("seen initial MIDI header"); codec->process_page = process_midi_page; codec->codec_free = midi_codec_free; codec->headers = 1; codec->name = "MIDI"; format_ogg_attach_header(ogg_info, page); return codec; } while (0); ogg_stream_clear(&codec->os); free(codec); return NULL; }
void slave_shutdown(void) { if (!slave_running) return; slave_running = 0; ICECAST_LOG_DEBUG("waiting for slave thread"); thread_join (_slave_thread_id); }
/* helper function for sending the data to a client */ int client_send_bytes (client_t *client, const void *buf, unsigned len) { int ret = client->con->send (client->con, buf, len); if (client->con->error) ICECAST_LOG_DEBUG("Client connection died"); return ret; }
/* Decide whether we need to start a source or just process a source * admin request. */ void auth_postprocess_source (auth_client *auth_user) { client_t *client = auth_user->client; const char *mount = auth_user->mount; const char *req = httpp_getvar (client->parser, HTTPP_VAR_URI); auth_user->client = NULL; client->authenticated = 1; if (strcmp (req, "/admin.cgi") == 0 || strncmp ("/admin/metadata", req, 15) == 0) { ICECAST_LOG_DEBUG("metadata request (%s, %s)", req, mount); admin_handle_request (client, "/admin/metadata"); } else { ICECAST_LOG_DEBUG("on mountpoint %s", mount); source_startup (client, mount, 0); } }
/* Routine to actually add pre-configured client structure to pending list and * then to start off the file serving thread if it is not already running */ static void fserve_add_pending (fserve_t *fclient) { thread_spin_lock (&pending_lock); fclient->next = (fserve_t *)pending_list; pending_list = fclient; if (run_fserv == 0) { run_fserv = 1; ICECAST_LOG_DEBUG("fserve handler waking up"); thread_create("File Serving Thread", fserv_thread_function, NULL, THREAD_DETACHED); } thread_spin_unlock (&pending_lock); }
/* routine for taking the provided page (should be a header page) and * placing it on the collection of header pages */ void format_ogg_attach_header (ogg_state_t *ogg_info, ogg_page *page) { refbuf_t *refbuf = make_refbuf_with_page (page); if (ogg_page_bos (page)) { ICECAST_LOG_DEBUG("attaching BOS page"); if (*ogg_info->bos_end == NULL) ogg_info->header_pages_tail = refbuf; refbuf->next = *ogg_info->bos_end; *ogg_info->bos_end = refbuf; ogg_info->bos_end = &refbuf->next; return; } ICECAST_LOG_DEBUG("attaching header page"); if (ogg_info->header_pages_tail) ogg_info->header_pages_tail->next = refbuf; ogg_info->header_pages_tail = refbuf; if (ogg_info->header_pages == NULL) ogg_info->header_pages = refbuf; }
relay_server *relay_free (relay_server *relay) { relay_server *next = relay->next; ICECAST_LOG_DEBUG("freeing relay %s", relay->localmount); if (relay->source) source_free_source (relay->source); xmlFree (relay->server); xmlFree (relay->mount); xmlFree (relay->localmount); if (relay->username) xmlFree (relay->username); if (relay->password) xmlFree (relay->password); free (relay); return next; }
static xsltStylesheetPtr xslt_get_stylesheet(const char *fn) { int i; int empty = -1; struct stat file; if(stat(fn, &file)) { ICECAST_LOG_WARN("Error checking for stylesheet file \"%s\": %s", fn, strerror(errno)); return NULL; } for(i=0; i < CACHESIZE; i++) { if(cache[i].filename) { #ifdef _WIN32 if(!stricmp(fn, cache[i].filename)) #else if(!strcmp(fn, cache[i].filename)) #endif { if(file.st_mtime > cache[i].last_modified) { xsltFreeStylesheet(cache[i].stylesheet); cache[i].last_modified = file.st_mtime; cache[i].stylesheet = xsltParseStylesheetFile (XMLSTR(fn)); cache[i].cache_age = time(NULL); } ICECAST_LOG_DEBUG("Using cached sheet %i", i); return cache[i].stylesheet; } } else empty = i; } if(empty>=0) i = empty; else i = evict_cache_entry(); cache[i].last_modified = file.st_mtime; cache[i].filename = strdup(fn); cache[i].stylesheet = xsltParseStylesheetFile (XMLSTR(fn)); cache[i].cache_age = time(NULL); return cache[i].stylesheet; }
void format_ogg_free_headers (ogg_state_t *ogg_info) { refbuf_t *header; /* release the header pages first */ ICECAST_LOG_DEBUG("releasing header pages"); header = ogg_info->header_pages; while (header) { refbuf_t *to_release = header; header = header->next; refbuf_release (to_release); } ogg_info->header_pages = NULL; ogg_info->header_pages_tail = NULL; ogg_info->bos_end = &ogg_info->header_pages; }
/* Add client to fserve thread, client needs to have refbuf set and filled * but may provide a NULL file if no data needs to be read */ int fserve_add_client (client_t *client, FILE *file) { fserve_t *fclient = calloc (1, sizeof(fserve_t)); ICECAST_LOG_DEBUG("Adding client %p to file serving engine", client); if (fclient == NULL) { client_send_error_by_id(client, ICECAST_ERROR_GEN_MEMORY_EXHAUSTED); return -1; } fclient->file = file; fclient->client = client; fclient->ready = 0; fserve_add_pending (fclient); return 0; }
/* add client to file serving engine, but just write out the buffer contents, * then pass the client to the callback with the provided arg */ void fserve_add_client_callback (client_t *client, fserve_callback_t callback, void *arg) { fserve_t *fclient = calloc (1, sizeof(fserve_t)); ICECAST_LOG_DEBUG("Adding client to file serving engine"); if (fclient == NULL) { client_send_error_by_id(client, ICECAST_ERROR_GEN_MEMORY_EXHAUSTED); return; } fclient->file = NULL; fclient->client = client; fclient->ready = 0; fclient->callback = callback; fclient->arg = arg; fserve_add_pending(fclient); }
ogg_codec_t *initial_speex_page (format_plugin_t *plugin, ogg_page *page) { ogg_state_t *ogg_info = plugin->_state; ogg_codec_t *codec = calloc (1, sizeof (ogg_codec_t)); ogg_packet packet; SpeexHeader *header; ogg_stream_init (&codec->os, ogg_page_serialno (page)); ogg_stream_pagein (&codec->os, page); ogg_stream_packetout (&codec->os, &packet); /* Check for te first packet to be at least of the minimal size for a Speex header. * The header size is 80 bytes as per specs. You can find the specs here: * https://speex.org/docs/manual/speex-manual/node8.html#SECTION00830000000000000000 * * speex_packet_to_header() will also check the header size for us. However * that function generates noise on stderr in case the header is too short. * This is dangerous as we may have closed stderr already and the handle may be use * again for something else. */ if (packet.bytes < 80) { return NULL; } ICECAST_LOG_DEBUG("checking for speex codec"); header = speex_packet_to_header ((char*)packet.packet, packet.bytes); if (header == NULL) { ogg_stream_clear (&codec->os); free (header); free (codec); return NULL; } ICECAST_LOG_INFO("seen initial speex header"); codec->process_page = process_speex_page; codec->codec_free = speex_codec_free; codec->headers = 1; format_ogg_attach_header (ogg_info, page); free (header); return codec; }
/* The auth thread main loop. */ static void *auth_run_thread (void *arg) { auth_t *auth = arg; ICECAST_LOG_INFO("Authentication thread started"); while (auth->running) { /* usually no clients are waiting, so don't bother taking locks */ if (auth->head) { auth_client *auth_user; /* may become NULL before lock taken */ thread_mutex_lock (&auth->lock); auth_user = (auth_client*)auth->head; if (auth_user == NULL) { thread_mutex_unlock (&auth->lock); continue; } ICECAST_LOG_DEBUG("%d client(s) pending on %s", auth->pending_count, auth->mount); auth->head = auth_user->next; if (auth->head == NULL) auth->tailp = &auth->head; auth->pending_count--; thread_mutex_unlock (&auth->lock); auth_user->next = NULL; if (auth_user->process) auth_user->process (auth, auth_user); else ICECAST_LOG_ERROR("client auth process not set"); auth_client_free (auth_user); continue; } thread_sleep (150000); } ICECAST_LOG_INFO("Authenication thread shutting down"); return NULL; }
static int get_authenticator (auth_t *auth, config_options_t *options) { if (auth->type == NULL) { ICECAST_LOG_WARN("no authentication type defined"); return -1; } do { ICECAST_LOG_DEBUG("type is %s", auth->type); if (strcmp (auth->type, "url") == 0) { #ifdef HAVE_AUTH_URL if (auth_get_url_auth (auth, options) < 0) return -1; break; #else ICECAST_LOG_ERROR("Auth URL disabled"); return -1; #endif } if (strcmp (auth->type, "htpasswd") == 0) { if (auth_get_htpasswd_auth (auth, options) < 0) return -1; break; } ICECAST_LOG_ERROR("Unrecognised authenticator type: \"%s\"", auth->type); return -1; } while (0); while (options) { if (strcmp (options->name, "allow_duplicate_users") == 0) auth->allow_duplicate_users = atoi ((char*)options->value); options = options->next; } return 0; }
ogg_codec_t *initial_flac_page (format_plugin_t *plugin, ogg_page *page) { ogg_state_t *ogg_info = plugin->_state; ogg_codec_t *codec = calloc (1, sizeof (ogg_codec_t)); ogg_packet packet; ogg_stream_init (&codec->os, ogg_page_serialno (page)); ogg_stream_pagein (&codec->os, page); ogg_stream_packetout (&codec->os, &packet); ICECAST_LOG_DEBUG("checking for FLAC codec"); do { unsigned char *parse = packet.packet; if (page->header_len + page->body_len != 79) break; if (*parse != 0x7F) break; parse++; if (memcmp (parse, "FLAC", 4) != 0) break; ICECAST_LOG_INFO("seen initial FLAC header"); parse += 4; stats_event_args (ogg_info->mount, "FLAC_version", "%d.%d", parse[0], parse[1]); codec->process_page = process_flac_page; codec->codec_free = flac_codec_free; codec->headers = 1; codec->name = "FLAC"; format_ogg_attach_header (ogg_info, page); return codec; } while (0); ogg_stream_clear (&codec->os); free (codec); return NULL; }
/* This removes any source stats from virtual mountpoints, ie mountpoints * where no source_t exists. This function requires the global sources lock * to be held before calling. */ void stats_clear_virtual_mounts (void) { avl_node *snode; thread_mutex_lock (&_stats_mutex); snode = avl_get_first(_stats.source_tree); while (snode) { stats_source_t *src = (stats_source_t *)snode->key; source_t *source = source_find_mount_raw (src->source); if (source == NULL) { /* no source_t is reserved so remove them now */ snode = avl_get_next (snode); ICECAST_LOG_DEBUG("releasing %s stats", src->source); avl_delete (_stats.source_tree, src, _free_source_stats); continue; } snode = avl_get_next (snode); } thread_mutex_unlock (&_stats_mutex); }
/* called from the source thread when the metadata has been updated. * The artist title are checked and made ready for clients to send */ static void mp3_set_title (source_t *source) { const char streamtitle[] = "StreamTitle='"; const char streamurl[] = "StreamUrl='"; size_t size; unsigned char len_byte; refbuf_t *p; unsigned int len = sizeof(streamtitle) + 2; /* the StreamTitle, quotes, ; and null */ mp3_state *source_mp3 = source->format->_state; /* make sure the url data does not disappear from under us */ thread_mutex_lock (&source_mp3->url_lock); /* work out message length */ if (source_mp3->url_artist) len += strlen (source_mp3->url_artist); if (source_mp3->url_title) len += strlen (source_mp3->url_title); if (source_mp3->url_artist && source_mp3->url_title) len += 3; if (source_mp3->inline_url) { char *end = strstr (source_mp3->inline_url, "';"); if (end) len += end - source_mp3->inline_url+2; } else if (source_mp3->url) len += strlen (source_mp3->url) + strlen (streamurl) + 2; #define MAX_META_LEN 255*16 if (len > MAX_META_LEN) { thread_mutex_unlock (&source_mp3->url_lock); ICECAST_LOG_WARN("Metadata too long at %d chars", len); return; } /* work out the metadata len byte */ len_byte = (len-1) / 16 + 1; /* now we know how much space to allocate, +1 for the len byte */ size = len_byte * 16 + 1; p = refbuf_new (size); if (p) { mp3_state *source_mp3 = source->format->_state; int r; memset (p->data, '\0', size); if (source_mp3->url_artist && source_mp3->url_title) r = snprintf (p->data, size, "%c%s%s - %s';", len_byte, streamtitle, source_mp3->url_artist, source_mp3->url_title); else r = snprintf (p->data, size, "%c%s%s';", len_byte, streamtitle, source_mp3->url_title); if (r > 0) { if (source_mp3->inline_url) { char *end = strstr (source_mp3->inline_url, "';"); ssize_t urllen = size; if (end) urllen = end - source_mp3->inline_url + 2; if ((ssize_t)(size-r) > urllen) snprintf (p->data+r, size-r, "StreamUrl='%s';", source_mp3->inline_url+11); } else if (source_mp3->url) snprintf (p->data+r, size-r, "StreamUrl='%s';", source_mp3->url); } ICECAST_LOG_DEBUG("shoutcast metadata block setup with %s", p->data+1); filter_shoutcast_metadata (source, p->data, size); refbuf_release (source_mp3->metadata); source_mp3->metadata = p; } thread_mutex_unlock (&source_mp3->url_lock); }
/* read mp3 data with inlined metadata from the source. Filter out the * metadata so that the mp3 data itself is store on the queue and the * metadata is is associated with it */ static refbuf_t *mp3_get_filter_meta (source_t *source) { refbuf_t *refbuf; format_plugin_t *plugin = source->format; mp3_state *source_mp3 = plugin->_state; unsigned char *src; unsigned int bytes, mp3_block; if (complete_read (source) == 0) return NULL; refbuf = source_mp3->read_data; source_mp3->read_data = NULL; src = (unsigned char *)refbuf->data; if (source_mp3->update_metadata) { mp3_set_title (source); source_mp3->update_metadata = 0; } /* fill the buffer with the read data */ bytes = source_mp3->read_count; refbuf->len = 0; while (bytes > 0) { unsigned int metadata_remaining; mp3_block = source_mp3->inline_metadata_interval - source_mp3->offset; /* is there only enough to account for mp3 data */ if (bytes <= mp3_block) { refbuf->len += bytes; source_mp3->offset += bytes; break; } /* we have enough data to get to the metadata * block, but only transfer upto it */ if (mp3_block) { src += mp3_block; bytes -= mp3_block; refbuf->len += mp3_block; source_mp3->offset += mp3_block; continue; } /* process the inline metadata, len == 0 indicates not seen any yet */ if (source_mp3->build_metadata_len == 0) { memset (source_mp3->build_metadata, 0, sizeof (source_mp3->build_metadata)); source_mp3->build_metadata_offset = 0; source_mp3->build_metadata_len = 1 + (*src * 16); } /* do we have all of the metatdata block */ metadata_remaining = source_mp3->build_metadata_len - source_mp3->build_metadata_offset; if (bytes < metadata_remaining) { memcpy (source_mp3->build_metadata + source_mp3->build_metadata_offset, src, bytes); source_mp3->build_metadata_offset += bytes; break; } /* copy all bytes except the last one, that way we * know a null byte terminates the message */ memcpy (source_mp3->build_metadata + source_mp3->build_metadata_offset, src, metadata_remaining-1); /* overwrite metadata in the buffer */ bytes -= metadata_remaining; memmove (src, src+metadata_remaining, bytes); /* assign metadata if it's greater than 1 byte, and the text has changed */ if (source_mp3->build_metadata_len > 1 && strcmp (source_mp3->build_metadata+1, source_mp3->metadata->data+1) != 0) { refbuf_t *meta = refbuf_new (source_mp3->build_metadata_len); memcpy (meta->data, source_mp3->build_metadata, source_mp3->build_metadata_len); ICECAST_LOG_DEBUG("shoutcast metadata %.*s", 4080, meta->data+1); if (strncmp (meta->data+1, "StreamTitle=", 12) == 0) { filter_shoutcast_metadata (source, source_mp3->build_metadata, source_mp3->build_metadata_len); refbuf_release (source_mp3->metadata); source_mp3->metadata = meta; source_mp3->inline_url = strstr (meta->data+1, "StreamUrl='"); } else { ICECAST_LOG_ERROR("Incorrect metadata format, ending stream"); source->running = 0; refbuf_release (refbuf); refbuf_release (meta); return NULL; } } source_mp3->offset = 0; source_mp3->build_metadata_len = 0; } /* the data we have just read may of just been metadata */ if (refbuf->len == 0) { refbuf_release (refbuf); return NULL; } refbuf->associated = source_mp3->metadata; refbuf_addref (source_mp3->metadata); refbuf->sync_point = 1; return refbuf; }