int send_to_runner (struct runner *run, input_buffer *buffer) { if (run) { buffer->next = NULL; #ifdef USE_PIPES if (write (run->fd[1], &buffer, sizeof (buffer)) < (ssize_t)sizeof (buffer)) { LOG_WARN0 ("unable to write to runner"); return -1; } #else *run->pending_tail = buffer; run->pending_tail = &buffer->next; thread_cond_signal (&run->data_available); #endif return 0; } input_free_buffer (buffer); return 0; }
/* Core streaming function for this module * This is what actually produces the data which gets streamed. * * returns: >0 Number of bytes read * 0 Non-fatal error. * <0 Fatal error. */ static int playlist_read(void *self, ref_buffer *rb) { playlist_state_t *pl = (playlist_state_t *)self; int bytes; unsigned char *buf; char *newfn; int result; ogg_page og; if (pl->errors > 5) { LOG_WARN0("Too many consecutive errors - exiting"); return -1; } if (!pl->current_file || pl->nexttrack) { pl->nexttrack = 0; if (pl->current_file && strcmp (pl->filename, "-")) { fclose(pl->current_file); pl->current_file = NULL; } if (pl->file_ended) { pl->file_ended(pl->data, pl->filename); } newfn = pl->get_filename(pl->data); if (!newfn) { LOG_INFO0("No more filenames available, end of playlist"); return -1; /* No more files available */ } if (strcmp (newfn, "-")) { if (!pl->allow_repeat && pl->filename && !strcmp(pl->filename, newfn)) { LOG_ERROR0("Cannot play same file twice in a row, skipping"); pl->errors++; pl->free_filename (pl->data, newfn); return 0; } pl->free_filename(pl->data, pl->filename); pl->filename = newfn; pl->current_file = fopen(pl->filename, "rb"); if (!pl->current_file) { LOG_WARN2("Error opening file \"%s\": %s",pl->filename, strerror(errno)); pl->errors++; return 0; } LOG_INFO1("Currently playing \"%s\"", pl->filename); } else { LOG_INFO0("Currently playing from stdin"); pl->current_file = stdin; pl->free_filename(pl->data, pl->filename); pl->filename = newfn; } /* Reinit sync, so that dead data from previous file is discarded */ ogg_sync_clear(&pl->oy); ogg_sync_init(&pl->oy); } input_sleep (); while(1) { result = ogg_sync_pageout(&pl->oy, &og); if(result < 0) LOG_WARN1("Corrupt or missing data in file (%s)", pl->filename); else if(result > 0) { if (ogg_page_bos (&og)) { if (ogg_page_serialno (&og) == pl->current_serial) LOG_WARN1 ("detected duplicate serial number reading \"%s\"", pl->filename); pl->current_serial = ogg_page_serialno (&og); } if (input_calculate_ogg_sleep (&og) < 0) { pl->nexttrack = 1; return 0; } rb->len = og.header_len + og.body_len; rb->buf = malloc(rb->len); rb->aux_data = og.header_len; memcpy(rb->buf, og.header, og.header_len); memcpy(rb->buf+og.header_len, og.body, og.body_len); if(ogg_page_granulepos(&og)==0) rb->critical = 1; break; } /* If we got to here, we didn't have enough data. */ buf = ogg_sync_buffer(&pl->oy, BUFSIZE); bytes = fread(buf,1, BUFSIZE, pl->current_file); if (bytes <= 0) { if (feof(pl->current_file)) { pl->nexttrack = 1; return playlist_read(pl,rb); } else { LOG_ERROR2("Read error from \"%s\": %s", pl->filename, strerror(errno)); fclose(pl->current_file); pl->current_file=NULL; pl->errors++; return 0; } } else ogg_sync_wrote(&pl->oy, bytes); } pl->errors=0; return rb->len; }
/* The main loop for each instance. Gets data passed to it from the stream * manager (which gets it from the input module), and streams it to the * specified server */ void *ices_instance_stream(void *arg) { int ret, shouterr; ref_buffer *buffer; char *connip; stream_description *sdsc = arg; instance_t *stream = sdsc->stream; input_module_t *inmod = sdsc->input; int reencoding = (inmod->type == ICES_INPUT_VORBIS) && stream->encode; int encoding = (inmod->type == ICES_INPUT_PCM) && stream->encode; char *stream_name = NULL, *stream_genre = NULL, *stream_description = NULL; char *user = NULL; vorbis_comment_init(&sdsc->vc); sdsc->shout = shout_new(); /* we only support the ice protocol and vorbis streams currently */ shout_set_format(sdsc->shout, SHOUT_FORMAT_VORBIS); //shout_set_protocol(sdsc->shout, SHOUT_PROTOCOL_ICE); shout_set_protocol(sdsc->shout, SHOUT_PROTOCOL_HTTP); signal(SIGPIPE, signal_hup_handler); connip = malloc(16); if(!resolver_getip(stream->hostname, connip, 16)) { LOG_ERROR1("Could not resolve hostname \"%s\"", stream->hostname); free(connip); stream->died = 1; return NULL; } if (!(shout_set_host(sdsc->shout, connip)) == SHOUTERR_SUCCESS) { LOG_ERROR1("libshout error: %s\n", shout_get_error(sdsc->shout)); free(connip); stream->died = 1; return NULL; } shout_set_port(sdsc->shout, stream->port); if (!(shout_set_password(sdsc->shout, stream->password)) == SHOUTERR_SUCCESS) { LOG_ERROR1("libshout error: %s\n", shout_get_error(sdsc->shout)); free(connip); stream->died = 1; return NULL; } if (stream->user) user = stream->user; else user = "******"; if(shout_set_user(sdsc->shout, user) != SHOUTERR_SUCCESS) { LOG_ERROR1("libshout error: %s\n", shout_get_error(sdsc->shout)); free(connip); stream->died = 1; return NULL; } if (!(shout_set_agent(sdsc->shout, VERSIONSTRING)) == SHOUTERR_SUCCESS) { LOG_ERROR1("libshout error: %s\n", shout_get_error(sdsc->shout)); free(connip); stream->died = 1; return NULL; } if (!(shout_set_mount(sdsc->shout, stream->mount)) == SHOUTERR_SUCCESS) { LOG_ERROR1("libshout error: %s\n", shout_get_error(sdsc->shout)); free(connip); stream->died = 1; return NULL; } /* set the metadata for the stream */ if(stream->stream_name) stream_name = stream->stream_name; else if (ices_config->stream_name) stream_name = ices_config->stream_name; if(stream->stream_description) stream_description = stream->stream_description; else if (ices_config->stream_description) stream_description = ices_config->stream_description; if(stream->stream_genre) stream_genre = stream->stream_genre; else if (ices_config->stream_genre) stream_genre = ices_config->stream_genre; if(stream_name) if (!(shout_set_name(sdsc->shout, stream_name)) == SHOUTERR_SUCCESS) { LOG_ERROR1("libshout error: %s\n", shout_get_error(sdsc->shout)); free(connip); stream->died = 1; return NULL; } if (stream_genre) if (!(shout_set_genre(sdsc->shout, stream_genre)) == SHOUTERR_SUCCESS) { LOG_ERROR1("libshout error: %s\n", shout_get_error(sdsc->shout)); free(connip); stream->died = 1; return NULL; } if (stream_description) if (!(shout_set_description(sdsc->shout, stream_description)) == SHOUTERR_SUCCESS) { LOG_ERROR1("libshout error: %s\n", shout_get_error(sdsc->shout)); free(connip); stream->died = 1; return NULL; } if(stream->downmix && encoding && stream->channels == 1) { stream->channels = 1; sdsc->downmix = downmix_initialise(); } if(stream->resampleinrate && stream->resampleoutrate && encoding) { stream->samplerate = stream->resampleoutrate; sdsc->resamp = resample_initialise(stream->channels, stream->resampleinrate, stream->resampleoutrate); } if(encoding) { if(inmod->metadata_update) inmod->metadata_update(inmod->internal, &sdsc->vc); sdsc->enc = encode_initialise(stream->channels, stream->samplerate, stream->managed, stream->min_br, stream->nom_br, stream->max_br, stream->quality, stream->serial++, &sdsc->vc); if(!sdsc->enc) { LOG_ERROR0("Failed to configure encoder"); stream->died = 1; return NULL; /* FIXME: probably leaking some memory here */ } } else if(reencoding) sdsc->reenc = reencode_init(stream); if(stream->savefilename != NULL) { stream->savefile = fopen(stream->savefilename, "wb"); if(!stream->savefile) LOG_ERROR2("Failed to open stream save file %s: %s", stream->savefilename, strerror(errno)); else LOG_INFO1("Saving stream to file %s", stream->savefilename); } if((shouterr = shout_open(sdsc->shout)) == SHOUTERR_SUCCESS) { LOG_INFO3("Connected to server: %s:%d%s", shout_get_host(sdsc->shout), shout_get_port(sdsc->shout), shout_get_mount(sdsc->shout)); while(1) { if(stream->buffer_failures > MAX_ERRORS) { LOG_WARN0("Too many errors, shutting down"); break; } buffer = stream_wait_for_data(stream); /* buffer being NULL means that either a fatal error occured, * or we've been told to shut down */ if(!buffer) break; /* If data is NULL or length is 0, we should just skip this one. * Probably, we've been signalled to shut down, and that'll be * caught next iteration. Add to the error count just in case, * so that we eventually break out anyway */ if(!buffer->buf || !buffer->len) { LOG_WARN0("Bad buffer dequeued!"); stream->buffer_failures++; continue; } if(stream->wait_for_critical) { LOG_INFO0("Trying restart on new substream"); stream->wait_for_critical = 0; } ret = process_and_send_buffer(sdsc, buffer); /* No data produced, do nothing */ if(ret == -1) ; /* Fatal error */ else if(ret == -2) { LOG_ERROR0("Serious error, waiting to restart on " "next substream. Stream temporarily suspended."); /* Set to wait until a critical buffer comes through (start of * a new substream, typically), and flush existing queue. */ thread_mutex_lock(&ices_config->flush_lock); stream->wait_for_critical = 1; input_flush_queue(stream->queue, 0); thread_mutex_unlock(&ices_config->flush_lock); } /* Non-fatal shout error */ else if(ret == 0) { LOG_ERROR2("Send error: %s (%s)", shout_get_error(sdsc->shout), strerror(errno)); if(shout_get_errno(sdsc->shout) == SHOUTERR_SOCKET) { int i=0; /* While we're trying to reconnect, don't receive data * to this instance, or we'll overflow once reconnect * succeeds */ thread_mutex_lock(&ices_config->flush_lock); stream->skip = 1; /* Also, flush the current queue */ input_flush_queue(stream->queue, 1); thread_mutex_unlock(&ices_config->flush_lock); while((i < stream->reconnect_attempts || stream->reconnect_attempts==-1) && !ices_config->shutdown) { i++; LOG_WARN0("Trying reconnect after server socket error"); shout_close(sdsc->shout); if((shouterr = shout_open(sdsc->shout)) == SHOUTERR_SUCCESS) { LOG_INFO3("Connected to server: %s:%d%s", shout_get_host(sdsc->shout), shout_get_port(sdsc->shout), shout_get_mount(sdsc->shout)); /* This stream can't restart until the next * logical stream comes along, since the * server won't have any cached headers for * this source/connection. So, don't continue * yet. */ thread_mutex_lock(&ices_config->flush_lock); stream->wait_for_critical = 1; input_flush_queue(stream->queue, 0); thread_mutex_unlock(&ices_config->flush_lock); break; } else { LOG_ERROR3("Failed to reconnect to %s:%d (%s)", shout_get_host(sdsc->shout),shout_get_port(sdsc->shout), shout_get_error(sdsc->shout)); if(i==stream->reconnect_attempts) { LOG_ERROR0("Reconnect failed too many times, " "giving up."); /* We want to die now */ stream->buffer_failures = MAX_ERRORS+1; } else /* Don't try again too soon */ sleep(stream->reconnect_delay); } } stream->skip = 0; } stream->buffer_failures++; } stream_release_buffer(buffer); } } else { LOG_ERROR3("Failed initial connect to %s:%d (%s)", shout_get_host(sdsc->shout),shout_get_port(sdsc->shout), shout_get_error(sdsc->shout)); } shout_close(sdsc->shout); if(stream->savefile != NULL) fclose(stream->savefile); shout_free(sdsc->shout); encode_clear(sdsc->enc); reencode_clear(sdsc->reenc); downmix_clear(sdsc->downmix); resample_clear(sdsc->resamp); vorbis_comment_clear(&sdsc->vc); stream->died = 1; return NULL; }
input_module_t *roar_open_module(module_param_t *params) { input_module_t *mod = calloc(1, sizeof(input_module_t)); im_roar_state *s; module_param_t *current; const char * server = NULL; int dir = ROAR_DIR_MONITOR; enum { MD_NONE = 0, MD_FILE = 1, MD_STREAM = 2 } use_metadata = MD_STREAM; int err; mod->getdata = roar_read; mod->handle_event = event_handler; mod->metadata_update = metadata_update; mod->internal = calloc(1, sizeof(im_roar_state)); s = mod->internal; if(roar_profile2info(&s->info, "default") == -1) { LOG_ERROR1("Failed to get default audio profile: %s", roar_error2str(roar_error)); return NULL; } s->info.bits = 16; s->vss = NULL; s->plugins = roar_plugincontainer_new_simple(IM_ROAR_APPNAME, IM_ROAR_ABIVERSION); if (!s->plugins) { LOG_ERROR1("Failed to create plugin container: %s", roar_error2str(roar_error)); return NULL; } thread_mutex_create(&s->metadatalock); current = params; while(current) { if(!strcmp(current->name, "rate")) s->info.rate = roar_str2rate(current->value); else if(!strcmp(current->name, "channels")) s->info.channels = roar_str2channels(current->value); else if(!strcmp(current->name, "codec")) s->info.codec = roar_str2codec(current->value); else if(!strcmp(current->name, "aiprofile")) { if (roar_profile2info(&s->info, current->value) == -1) { LOG_WARN2("Can not get audio info profile %s: %s", current->value, roar_error2str(roar_error)); } s->info.bits = 16; } else if(!strcmp(current->name, "dir")) { if ( !strcasecmp(current->value, "monitor") ) { dir = ROAR_DIR_MONITOR; } else if ( !strcasecmp(current->value, "record") ) { dir = ROAR_DIR_RECORD; } else { LOG_WARN2("Unknown value %s for parameter %s for roar module", current->value, current->name); } } else if(!strcmp(current->name, "device") || !strcmp(current->name, "server")) server = current->value; else if(!strcmp(current->name, "metadata")) { if ( !strcasecmp(current->value, "none") ) { use_metadata = MD_NONE; } else if ( !strcasecmp(current->value, "file") ) { use_metadata = MD_FILE; } else if ( !strcasecmp(current->value, "stream") ) { use_metadata = MD_STREAM; } else { use_metadata = atoi(current->value); } } else if(!strcmp(current->name, "metadatafilename")) { ices_config->metadata_filename = current->value; use_metadata = MD_FILE; } else if(!strcmp(current->name, "plugin")) { roar_plugin_load(mod, current->value); } else LOG_WARN1("Unknown parameter %s for roar module", current->name); current = current->next; } mod->type = ICES_INPUT_PCM; switch (s->info.codec) { case ROAR_CODEC_PCM_LE: mod->subtype = INPUT_PCM_LE_16; break; case ROAR_CODEC_PCM_BE: mod->subtype = INPUT_PCM_BE_16; break; case ROAR_CODEC_OGG_GENERAL: LOG_WARN0("Codec may not work, specify ogg_vorbis for Vorbis streaming"); case ROAR_CODEC_OGG_VORBIS: mod->type = ICES_INPUT_VORBIS; // we do not set mod->subtype here, strange design ices2 has... break; case -1: LOG_ERROR0("Unknown Codec"); return NULL; default: LOG_ERROR1("Unsupported Codec: %s", roar_codec2str(s->info.codec)); return NULL; } roar_plugincontainer_appsched_trigger(s->plugins, ROAR_DL_APPSCHED_INIT); /* Open the VS connection */ if ( (s->vss = roar_vs_new(server, IM_ROAR_PROGNAME, &err)) == NULL ) { LOG_ERROR2("Failed to open sound server %s: %s", server, roar_vs_strerr(err)); goto fail; } /* Now, set the required parameters on that device */ if ( roar_vs_stream(s->vss, &s->info, dir, &err) == -1 ) { LOG_ERROR2("Failed to create a new stream on sound server %s: %s", server, roar_vs_strerr(err)); goto fail; } if ( _set_flags(roar_vs_connection_obj(s->vss, NULL), roar_vs_stream_obj(s->vss, NULL), ROAR_FLAG_META, ROAR_RESET_FLAG) != 0 ) { LOG_WARN0("Can not reset metadata flag from stream"); } /* We're done, and we didn't fail! */ LOG_INFO3("Opened sound server at %s at %d channel(s), %d Hz", server, s->info.channels, s->info.rate); switch (use_metadata) { case MD_NONE: break; case MD_FILE: LOG_INFO0("Starting metadata update thread"); if(ices_config->metadata_filename) thread_create("im_roar-metadata", metadata_thread_signal, mod, 1); else thread_create("im_roar-metadata", metadata_thread_stdin, mod, 1); break; case MD_STREAM: if ( _set_flags(roar_vs_connection_obj(s->vss, NULL), roar_vs_stream_obj(s->vss, NULL), ROAR_FLAG_META, ROAR_SET_FLAG) != 0 ) { LOG_WARN0("Can not set metadata flag from stream"); } break; } return mod; fail: close_module(mod); /* safe, this checks for valid contents */ return NULL; }