/* 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 stdin_read(void *self, ref_buffer *rb) { int result; stdinpcm_state *s = self; rb->buf = malloc(BUFSIZE); if(!rb->buf) return -1; result = fread(rb->buf, 1,BUFSIZE, stdin); rb->len = result; rb->aux_data = s->rate*s->channels*2; if(s->newtrack) { rb->critical = 1; s->newtrack = 0; } if(rb->len <= 0) { LOG_INFO0("Reached EOF, no more data available\n"); free(rb->buf); return -1; } input_calculate_pcm_sleep (rb->len, rb->aux_data); input_sleep (); return rb->len; }
void signal_usr1_handler(int signum) { LOG_INFO0("Metadata update requested"); metadata_update_signalled = 1; thread_cond_broadcast(&ices_config->event_pending_cond); signal(SIGUSR1, signal_usr1_handler); }
void *ices_runner (void *arg) { struct runner *run = arg; struct instance *current; #ifdef HAVE_SCHED_GET_PRIORITY_MAX int policy; struct sched_param param; pthread_getschedparam (pthread_self(), &policy, ¶m); param . sched_priority = sched_get_priority_min (SCHED_OTHER); if (pthread_setschedparam (pthread_self(), SCHED_OTHER, ¶m)) { LOG_ERROR1 ("failed to set priority: %s", strerror (errno)); } else LOG_INFO0 ("set priority on runner"); #endif LOG_INFO1 ("Runner %d ready", run->id); while (1) { input_buffer *buffer; buffer = runner_wait_for_data (run); if (buffer == NULL) break; current = run->instances; while (current != NULL) { add_to_stream (current, buffer); current = current->next; } send_to_runner (run->next, buffer); } runner_close (run->next); LOG_DEBUG1 ("Runner thread %d cleaning up streams", run->id); current = run->instances; while (current) { struct instance *next; next = current->next; stream_cleanup (current); current = next; } close (run->fd[0]); run->fd[0] = -1; run->not_running = 1; LOG_DEBUG1 ("Runner thread %d finshed", run->id); return NULL; }
void signal_hup_handler(int signum) { LOG_INFO0("Flushing logs"); log_flush(ices_config->log_id); /* Now, let's tell it to move to the next track */ ices_config->inmod->handle_event(ices_config->inmod,EVENT_NEXTTRACK,NULL); signal(SIGHUP, signal_hup_handler); }
void signal_int_handler(int signum) { /* Is a mutex needed here? Probably */ if (!ices_config->shutdown) { LOG_INFO0("Shutdown requested..."); ices_config->shutdown = 1; thread_cond_broadcast(&ices_config->queue_cond); /* If user gives a second sigint, just die. */ signal(SIGINT, SIG_DFL); } }
input_module_t *stdin_open_module(module_param_t *params) { input_module_t *mod = calloc(1, sizeof(input_module_t)); stdinpcm_state *s; module_param_t *current; int use_metadata = 1; /* Default to on */ mod->type = ICES_INPUT_PCM; mod->getdata = stdin_read; mod->handle_event = event_handler; mod->metadata_update = metadata_update; mod->internal = malloc(sizeof(stdinpcm_state)); s = mod->internal; s->rate = 44100; /* Defaults */ s->channels = 2; s->metadata = NULL; thread_mutex_create(&s->metadatalock); current = params; while(current) { if(!strcmp(current->name, "rate")) s->rate = atoi(current->value); else if(!strcmp(current->name, "channels")) s->channels = atoi(current->value); else if(!strcmp(current->name, "metadata")) use_metadata = atoi(current->value); else if(!strcmp(current->name, "metadatafilename")) ices_config->metadata_filename = current->value; else LOG_WARN1("Unknown parameter %s for stdinpcm module", current->name); current = current->next; } if(use_metadata) { if (ices_config->metadata_filename) { LOG_INFO0("Starting metadata update thread"); thread_create("im_stdinpcm-metadata", metadata_thread_signal, mod, 1); } } return mod; }
static int event_handler(input_module_t *mod, enum event_type ev, void *param) { switch(ev) { case EVENT_SHUTDOWN: close_module(mod); break; case EVENT_NEXTTRACK: LOG_INFO0("Moving to next file in playlist."); ((playlist_state_t *)mod->internal)->nexttrack = 1; break; default: LOG_WARN1("Unhandled event %d", ev); return -1; } 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 roar_read(void *self, ref_buffer *rb) { ssize_t result; int err; im_roar_state *s = self; roar_plugincontainer_appsched_trigger(s->plugins, ROAR_DL_APPSCHED_UPDATE); rb->buf = malloc(BUFSIZE * roar_info2framesize(&s->info)/8); if(!rb->buf) return -1; result = roar_vs_read(s->vss, rb->buf, BUFSIZE * roar_info2framesize(&s->info)/8, &err); rb->len = result; rb->aux_data = roar_info2bitspersec(&s->info)/8; if(s->newtrack) { rb->critical = 1; s->newtrack = 0; } if(result == -1 && err == ROAR_ERROR_INTERRUPTED) { return 0; /* Non-fatal error */ } else if(result <= 0) { if(result == 0) LOG_INFO0("Reached EOF, no more data available"); else LOG_ERROR1("Error reading from sound server: %s", roar_vs_strerr(err)); free(rb->buf); rb->buf = NULL; return -1; } return rb->len; }
void input_loop(void) { input_module_t *inmod=NULL; instance_t *instance, *prev, *next; queue_item *queued; int shutdown = 0; int current_module = 0; int valid_stream = 1; int inc_count; int not_waiting_for_critical; int foundmodule = 0; thread_cond_create(&ices_config->queue_cond); thread_cond_create(&ices_config->event_pending_cond); thread_mutex_create(&ices_config->refcount_lock); thread_mutex_create(&ices_config->flush_lock); memset (&control, 0, sizeof (control)); while(ices_config->playlist_module && modules[current_module].open) { if(!strcmp(ices_config->playlist_module, modules[current_module].name)) { foundmodule = 1; inmod = modules[current_module].open(ices_config->module_params); break; } current_module++; } if(!inmod) { if(foundmodule) LOG_ERROR1("Couldn't initialise input module \"%s\"", ices_config->playlist_module); else LOG_ERROR1("No input module named \"%s\" could be found", ices_config->playlist_module); return; } ices_config->inmod = inmod; /* ok, basic config stuff done. Now, we want to start all our listening * threads. */ instance = ices_config->instances; while(instance) { stream_description *arg = calloc(1, sizeof(stream_description)); arg->stream = instance; arg->input = inmod; /* if(instance->savefilename != NULL) thread_create("savefile", savefile_stream, arg, 1); */ thread_create("stream", ices_instance_stream, arg, 1); instance = instance->next; } /* treat as if a signal has arrived straight away */ signal_usr1_handler (0); /* now we go into the main loop * We shut down the main thread ONLY once all the instances * have killed themselves. */ while(!shutdown) { ref_buffer *chunk = calloc(1, sizeof(ref_buffer)); buffer_queue *current; int ret; instance = ices_config->instances; prev = NULL; while(instance) { /* if an instance has died, get rid of it ** this should be replaced with something that tries to ** restart the instance, probably. */ if (instance->died) { LOG_DEBUG0("An instance died, removing it"); next = instance->next; if (prev) prev->next = next; else ices_config->instances = next; /* Just in case, flush any existing buffers * Locks shouldn't be needed, but lets be SURE */ thread_mutex_lock(&ices_config->flush_lock); input_flush_queue(instance->queue, 0); thread_mutex_unlock(&ices_config->flush_lock); config_free_instance(instance); free(instance); instance = next; continue; } prev = instance; instance = instance->next; } instance = ices_config->instances; if(!instance) { shutdown = 1; free(chunk); continue; } if(ices_config->shutdown) /* We've been signalled to shut down, but */ { /* the instances haven't done so yet... */ timing_sleep(250); /* sleep for quarter of a second */ free(chunk); continue; } /* If this is the first time through, set initial time. This should * be done before the call to inmod->getdata() below, in order to * properly keep time if this input module blocks. */ if (control.starttime == 0) control.starttime = timing_get_time(); /* get a chunk of data from the input module */ ret = inmod->getdata(inmod->internal, chunk); /* input module signalled non-fatal error. Skip this chunk */ if(ret==0) { free(chunk); continue; } /* Input module signalled fatal error, shut down - nothing we can do * from here */ if(ret < 0) { ices_config->shutdown = 1; thread_cond_broadcast(&ices_config->queue_cond); free(chunk); continue; } if(chunk->critical) valid_stream = 1; if(ret < 0) { /* Tell the input module to go to the next track, hopefully allowing * resync. */ ices_config->inmod->handle_event(ices_config->inmod, EVENT_NEXTTRACK,NULL); valid_stream = 0; } inc_count = 0; not_waiting_for_critical = 0; if(valid_stream) { while(instance) { if(instance->wait_for_critical && !chunk->critical) { instance = instance->next; continue; } not_waiting_for_critical = 1; if(instance->skip) { instance = instance->next; continue; } queued = malloc(sizeof(queue_item)); queued->buf = chunk; current = instance->queue; inc_count++; thread_mutex_lock(¤t->lock); if(current->head == NULL) { current->head = current->tail = queued; current->head->next = current->tail->next = NULL; } else { current->tail->next = queued; queued->next = NULL; current->tail = queued; } current->length++; thread_mutex_unlock(¤t->lock); instance = instance->next; } } /* If everything is waiting for a critical buffer, force one * early. (This will take effect on the next pass through) */ if(valid_stream && !not_waiting_for_critical) { ices_config->inmod->handle_event(ices_config->inmod, EVENT_NEXTTRACK,NULL); instance = ices_config->instances; while(instance) { thread_mutex_lock(&ices_config->flush_lock); input_flush_queue(instance->queue, 0); instance->wait_for_critical = 0; thread_mutex_unlock(&ices_config->flush_lock); instance = instance->next; } } /* Make sure we don't end up with a 0-refcount buffer that * will never hit any of the free points. (this happens * if all threads are set to skip, for example). */ thread_mutex_lock(&ices_config->refcount_lock); chunk->count += inc_count; if(!chunk->count) { free(chunk->buf); free(chunk); } thread_mutex_unlock(&ices_config->refcount_lock); if(valid_stream) { /* wake up the instances */ thread_cond_broadcast(&ices_config->queue_cond); } } LOG_INFO0 ("All instances removed, shutting down..."); ices_config->shutdown = 1; thread_cond_broadcast(&ices_config->event_pending_cond); timing_sleep(250); /* sleep for quarter of a second */ thread_cond_destroy(&ices_config->queue_cond); thread_cond_destroy(&ices_config->event_pending_cond); thread_mutex_destroy(&ices_config->flush_lock); thread_mutex_destroy(&ices_config->refcount_lock); inmod->handle_event(inmod, EVENT_SHUTDOWN, NULL); return; }
/* 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; }
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; }
input_module_t *sndio_open_module(module_param_t *params) { input_module_t *mod = calloc(1, sizeof(input_module_t)); im_sndio_state *s; module_param_t *current; char *device = NULL; /* default device */ int sample_rate = 44100; int channels = 2; int use_metadata = 1; /* Default to on */ mod->type = ICES_INPUT_PCM; #ifdef WORDS_BIGENDIAN mod->subtype = INPUT_PCM_BE_16; #else mod->subtype = INPUT_PCM_LE_16; #endif mod->getdata = sndio_read; mod->handle_event = event_handler; mod->metadata_update = metadata_update; mod->internal = calloc(1, sizeof(im_sndio_state)); s = mod->internal; thread_mutex_create(&s->metadatalock); current = params; while (current) { if (!strcmp(current->name, "rate")) sample_rate = atoi(current->value); else if (!strcmp(current->name, "channels")) channels = atoi(current->value); else if (!strcmp(current->name, "device")) device = current->value; else if (!strcmp(current->name, "metadata")) use_metadata = atoi(current->value); else if(!strcmp(current->name, "metadatafilename")) ices_config->metadata_filename = current->value; else LOG_WARN1("Unknown parameter %s for sndio module", current->name); current = current->next; } /* First up, lets open the audio device */ if((s->hdl = sio_open(device, SIO_REC, 0)) == NULL) { LOG_ERROR0("Failed to open sndio device"); goto fail; } /* Try and set up what we want */ sio_initpar(&s->par); s->par.rate = sample_rate; s->par.rchan = channels; s->par.bits = 16; s->par.sig = 1; s->par.le = SIO_LE_NATIVE; s->par.round = BUFSIZE; s->par.appbufsz = BUFSIZE * 4; if (!sio_setpar(s->hdl, &s->par) || !sio_getpar(s->hdl, &s->par)) { LOG_ERROR0("Failed to configure sndio device"); goto fail; } /* Check all went according to plan */ if (s->par.rate != sample_rate) { LOG_ERROR0("Couldn't set sampling rate"); goto fail; } if (s->par.rchan != channels) { LOG_ERROR0("Couldn't set number of channels"); goto fail; } if (s->par.bits != 16) { LOG_ERROR0("Couldn't set 16 bit precision"); goto fail; } if (s->par.sig != 1) { LOG_ERROR0("Couldn't set signed linear encoding"); goto fail; } if (s->par.le != SIO_LE_NATIVE) { LOG_ERROR0("Couldn't set proper endianness"); goto fail; } if (!sio_start(s->hdl)) { LOG_ERROR0("Couldn't start sndio"); goto fail; } /* We're done, and we didn't fail! */ LOG_INFO2("Opened audio device for %d channel(s), %d Hz", channels, sample_rate); if(use_metadata) { LOG_INFO0("Starting metadata update thread"); if(ices_config->metadata_filename) thread_create("im_sndio-metadata", metadata_thread_signal, mod, 1); else thread_create("im_sndio-metadata", metadata_thread_stdin, mod, 1); } return mod; fail: close_module(mod); /* safe, this checks for valid contents */ return NULL; }
input_module_t *alsa_open_module(module_param_t *params) { input_module_t *mod = calloc(1, sizeof(input_module_t)); im_alsa_state *s; module_param_t *current; char *device = "plughw:0,0"; /* default device */ int format = AFMT_S16_LE; int channels, rate; int use_metadata = 1; /* Default to on */ unsigned int buffered_time; snd_pcm_stream_t stream = SND_PCM_STREAM_CAPTURE; snd_pcm_hw_params_t *hwparams; int err; mod->type = ICES_INPUT_PCM; mod->subtype = INPUT_PCM_LE_16; mod->getdata = alsa_read; mod->handle_event = event_handler; mod->metadata_update = metadata_update; mod->internal = calloc(1, sizeof(im_alsa_state)); s = mod->internal; s->fd = NULL; /* Set it to something invalid, for now */ s->rate = 44100; /* Defaults */ s->channels = 2; thread_mutex_create(&s->metadatalock); current = params; while(current) { if(!strcmp(current->name, "rate")) s->rate = atoi(current->value); else if(!strcmp(current->name, "channels")) s->channels = atoi(current->value); else if(!strcmp(current->name, "device")) device = current->value; else if(!strcmp(current->name, "metadata")) use_metadata = atoi(current->value); else if(!strcmp(current->name, "metadatafilename")) ices_config->metadata_filename = current->value; else LOG_WARN1("Unknown parameter %s for alsa module", current->name); current = current->next; } snd_pcm_hw_params_alloca(&hwparams); if ((err = snd_pcm_open(&s->fd, device, stream, 0)) < 0) { LOG_ERROR2("Failed to open audio device %s: %s", device, snd_strerror(err)); goto fail; } if ((err = snd_pcm_hw_params_any(s->fd, hwparams)) < 0) { LOG_ERROR1("Failed to initialize hwparams: %s", snd_strerror(err)); goto fail; } if ((err = snd_pcm_hw_params_set_access(s->fd, hwparams, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) { LOG_ERROR1("Error setting access: %s", snd_strerror(err)); goto fail; } if ((err = snd_pcm_hw_params_set_format(s->fd, hwparams, SND_PCM_FORMAT_S16_LE)) < 0) { LOG_ERROR1("Couldn't set sample format to SND_PCM_FORMAT_S16_LE: %s", snd_strerror(err)); goto fail; } if ((err = snd_pcm_hw_params_set_rate_near(s->fd, hwparams, &s->rate, 0)) < 0) { LOG_ERROR1("Error setting rate: %s", snd_strerror(err)); goto fail; } if ((err = snd_pcm_hw_params_set_channels(s->fd, hwparams, s->channels)) < 0) { LOG_ERROR1("Error setting channels: %s", snd_strerror(err)); goto fail; } if ((err = snd_pcm_hw_params_set_periods(s->fd, hwparams, 2, 0)) < 0) { LOG_ERROR1("Error setting periods: %s", snd_strerror(err)); goto fail; } buffered_time = 500000; if ((err = snd_pcm_hw_params_set_buffer_time_near(s->fd, hwparams, &buffered_time, 0)) < 0) { LOG_ERROR1("Error setting buffersize: %s", snd_strerror(err)); goto fail; } if ((err = snd_pcm_hw_params(s->fd, hwparams)) < 0) { LOG_ERROR1("Error setting HW params: %s", snd_strerror(err)); goto fail; } /* We're done, and we didn't fail! */ LOG_INFO3("Opened audio device %s at %d channel(s), %d Hz", device, s->channels, s->rate); if(use_metadata) { if(ices_config->metadata_filename) thread_create("im_alsa-metadata", metadata_thread_signal, mod, 1); else thread_create("im_alsa-metadata", metadata_thread_stdin, mod, 1); LOG_INFO0("Started metadata update thread"); } return mod; fail: close_module(mod); /* safe, this checks for valid contents */ return NULL; }
/* 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; }
encoder_state *encode_initialise(int channels, int rate, int managed, int min_br, int nom_br, int max_br, float quality, vorbis_comment *vc) { encoder_state *s = calloc(1, sizeof(encoder_state)); ogg_packet h1,h2,h3; /* Have vorbisenc choose a mode for us */ vorbis_info_init(&s->vi); if (max_br < 0 && nom_br < 0 && min_br < 0) managed = 0; if (managed == 0 && nom_br >= 0) if (min_br >= 0 || max_br >= 0) managed = 1; do { if (managed) { LOG_INFO5("Encoder initialising with bitrate management: %d " "channels, %d Hz, minimum bitrate %d, nominal %d, " "maximum %d", channels, rate, min_br, nom_br, max_br); if (vorbis_encode_setup_managed (&s->vi, channels, rate, max_br, nom_br, min_br)) break; } else { if (nom_br < 0) { LOG_INFO3("Encoder initialising in VBR mode: %d channel(s), " "%d Hz, quality %f", channels, rate, quality); if (min_br > 0 || max_br > 0) LOG_INFO0("ignoring min/max bitrate, not supported in VBR " "mode, use nominal-bitrate instead"); if (vorbis_encode_setup_vbr(&s->vi, channels, rate, quality*0.1)) break; #if 0 if (max_br > 0 || min_br > 0) { struct ovectl_ratemanage_arg ai; if (vorbis_encode_ctl(&s->vi, OV_ECTL_RATEMANAGE_GET, &ai)) break; ai.bitrate_hard_min = min_br; ai.bitrate_hard_max = max_br; ai.management_active = 1; if (vorbis_encode_ctl(&s->vi, OV_ECTL_RATEMANAGE_SET, &ai)) break; } #endif } else { LOG_INFO3("Encoder initialising in VBR mode: %d " "channels, %d Hz, nominal %d", channels, rate, nom_br); if (vorbis_encode_setup_managed (&s->vi, channels, rate, max_br, nom_br, max_br)) break; if (vorbis_encode_ctl (&s->vi, OV_ECTL_RATEMANAGE_SET, NULL)) break; } } if (vorbis_encode_setup_init(&s->vi)) break; vorbis_analysis_init(&s->vd, &s->vi); vorbis_block_init(&s->vd, &s->vb); ogg_stream_init(&s->os, _get_serial()); vorbis_analysis_headerout(&s->vd, vc, &h1,&h2,&h3); ogg_stream_packetin(&s->os, &h1); ogg_stream_packetin(&s->os, &h2); ogg_stream_packetin(&s->os, &h3); s->in_header = 1; s->samplerate = rate; s->samples_in_current_page = 0; s->prevgranulepos = 0; return s; } while (0); LOG_INFO0("Failed to configure encoder, verify settings"); vorbis_info_clear(&s->vi); free (s); return NULL; }
void *metadata_thread_signal(void *arg) { char buf[1024]; input_module_t *mod = arg; while(1) { char **md = NULL; int comments = 0; FILE *file; while(metadata_update_signalled == 0) { thread_cond_wait(&ices_config->event_pending_cond); if (ices_config->shutdown) { LOG_INFO0 ("metadata thread shutting down"); return NULL; } LOG_DEBUG0("meta thread wakeup"); } metadata_update_signalled = 0; file = fopen(ices_config->metadata_filename, "r"); if(!file) { LOG_WARN2("Failed to open file \"%s\" for metadata update: %s", ices_config->metadata_filename, strerror(errno)); continue; } LOG_DEBUG1("reading metadata from \"%s\"", ices_config->metadata_filename); while(fgets(buf, 1024, file)) { if(buf[0] == '\n') break; else { if(buf[strlen(buf)-1] == '\n') buf[strlen(buf)-1] = 0; md = realloc(md, (comments+2)*sizeof(char *)); md[comments] = malloc(strlen(buf)+1); memcpy(md[comments], buf, strlen(buf)+1); comments++; LOG_INFO2 ("tag %d is %s", comments, buf); } } fclose(file); if(md) /* Don't update if there's nothing there */ { md[comments]=0; /* Now, let's actually use the new data */ LOG_INFO0("Updating metadata"); mod->handle_event(mod,EVENT_METADATAUPDATE,md); } else LOG_INFO0("No metadata has been read"); } }
void *metadata_thread_stdin(void *arg) { char buf[1024]; input_module_t *mod = arg; if (ices_config->background) { LOG_INFO0("Metadata thread shutting down, tried to use " "stdin from background"); return NULL; } while(1) { char **md = NULL; int comments = 0; int wait_for_data = 1; /* wait for data */ while (wait_for_data) { struct timeval t; fd_set fds; FD_ZERO (&fds); FD_SET (0, &fds); t.tv_sec = 0; t.tv_usec = 150; switch (select (1, &fds, NULL, NULL, &t)) { case 1: wait_for_data = 0; case 0: break; default: if (errno != EAGAIN) { LOG_INFO1 ("shutting down thread (%d)", errno); return NULL; /* problem get out quick */ } break; } if (ices_config->shutdown) { LOG_INFO0 ("metadata thread shutting down"); return NULL; } } while(fgets(buf, 1024, stdin)) { if(buf[0] == '\n') break; else { if(buf[strlen(buf)-1] == '\n') buf[strlen(buf)-1] = 0; md = realloc(md, (comments+2)*sizeof(char *)); md[comments] = malloc(strlen(buf)+1); memcpy(md[comments], buf, strlen(buf)+1); comments++; } } if(md) /* Don't update if there's nothing there */ { md[comments]=0; /* Now, let's actually use the new data */ LOG_INFO0("Updating metadata"); mod->handle_event(mod,EVENT_METADATAUPDATE,md); } } }
int main(int argc, char **argv) { char logpath[FILENAME_MAX]; int log; if (argc != 2) { fprintf(stderr, PACKAGE_STRING "\n" " (c) Copyright 2001-2004 The IceS Development Team <*****@*****.**>\n" " Michael Smith <*****@*****.**>\n" " Karl Heyes <*****@*****.**>\n" " and others\n" "\n" "Usage: \"ices config.xml\"\n"); return 1; } config_initialize(); if (config_read(argv[1]) <= 0) { fprintf(stderr, "Failed to read config file \"%s\"\n", argv[1]); goto fail; } if (ices_config->background) { #ifndef _WIN32 int ret = 0; /* Start up new session, to lose old session and process group */ switch (fork()) { case 0: break; /* child continues */ case -1: perror ("fork"); ret = -1; default: exit (ret); } /* Disassociate process group and controlling terminal */ setsid(); /* Become a NON-session leader so that a */ /* control terminal can't be reacquired */ switch (fork()) { case 0: break; /* child continues */ case -1: perror ("fork"); ret = -1; default: exit (ret); } #else FreeConsole(); #endif } log_initialize(); thread_initialize(); shout_init(); encode_init(); #ifndef _WIN32 signals_setup(); #endif snprintf(logpath, FILENAME_MAX, "%s/%s", ices_config->logpath, ices_config->logfile); if(ices_config->log_stderr) log = log_open_file(stderr); else { log = log_open(logpath); if (log < 0) fprintf (stderr, "unable to open log %s\n", logpath); log_set_trigger (log, ices_config->logsize); } /* Set the log level, if requested - defaults to 2 (WARN) otherwise */ if (ices_config->loglevel) log_set_level(log, ices_config->loglevel); ices_config->log_id = log; LOG_INFO0(PACKAGE_STRING " started..."); if (ices_config->pidfile != NULL) { FILE *f = fopen (ices_config->pidfile, "w"); if (f) { fprintf (f, "%i", getpid()); fclose (f); } else { LOG_WARN1("pidfile \"%s\" cannot be written to", ices_config->pidfile); xmlFree (ices_config->pidfile); ices_config->pidfile = NULL; } } /* Start the core streaming loop */ input_loop(); if (ices_config->pidfile) remove (ices_config->pidfile); LOG_INFO0("Shutdown complete"); log_close(log); fail: encode_close(); shout_shutdown(); config_shutdown(); thread_shutdown(); log_shutdown(); return 0; }