/* 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 sndio_read(void *self, ref_buffer *rb) { int result; im_sndio_state *s = self; rb->buf = malloc(BUFSIZE * s->par.bps * s->par.rchan); if(!rb->buf) return -1; result = sio_read(s->hdl, rb->buf, BUFSIZE * s->par.bps * s->par.rchan); rb->len = result; rb->aux_data = s->par.rate * s->par.rchan * s->par.bps; if(s->newtrack) { rb->critical = 1; s->newtrack = 0; } if(result == 0) { if(sio_eof(s->hdl)) { LOG_ERROR0("Error reading from audio device"); free(rb->buf); return -1; } } return rb->len; }
/* 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 alsa_read(void *self, ref_buffer *rb) { int result; im_alsa_state *s = self; rb->buf = malloc(SAMPLES*2*s->channels); if(!rb->buf) return -1; result = snd_pcm_readi(s->fd, rb->buf, SAMPLES); rb->len = result*4; rb->aux_data = s->rate*s->channels*2; if(s->newtrack) { rb->critical = 1; s->newtrack = 0; } if (result == -EPIPE) { snd_pcm_prepare(s->fd); return 0; } else if (result == -EBADFD) { LOG_ERROR0("Bad descriptor passed to snd_pcm_readi"); free(rb->buf); return -1; } return rb->len; }
int playlist_script_initialise(module_param_t *params, playlist_state_t *pl) { script_playlist *data; pl->get_filename = playlist_script_get_filename; pl->clear = playlist_script_clear; pl->free_filename = playlist_script_free_filename; pl->file_ended = playlist_script_file_ended; pl->data = calloc(1, sizeof(script_playlist)); if(!pl->data) return -1; data = (script_playlist *)pl->data; while(params != NULL) { if(!strcmp(params->name, "program")) { if(data->program) free(data->program); data->program = params->value; } else if(!strcmp(params->name, "on-ended")) { if(data->on_ended) free(data->on_ended); data->on_ended = params->value; } else if(!strcmp(params->name, "allow-repeats")) pl->allow_repeat = atoi(params->value); else if(!strcmp(params->name, "type")) { /* We ignore this one */ } else LOG_WARN1("Unknown parameter to playlist script module: %s", params->name); params = params->next; } if(!data->program) { LOG_ERROR0("No program name specified for playlist module"); free(data); return -1; } return 0; }
int input_calculate_ogg_sleep(ogg_page *page) { static ogg_stream_state os; ogg_packet op; static vorbis_info vi; static vorbis_comment vc; static input_type codec = ICES_INPUT_UNKNOWN; static int need_start_pos, need_headers, state_in_use = 0; static int serialno = 0; static uint64_t offset; static uint64_t first_granulepos; if (ogg_page_granulepos(page) == -1) { LOG_ERROR0("Timing control: corrupt timing information in vorbis file, cannot stream."); return -1; } if (ogg_page_bos (page)) { control.oldsamples = 0; if (state_in_use) ogg_stream_clear (&os); ogg_stream_init (&os, ogg_page_serialno (page)); serialno = ogg_page_serialno (page); state_in_use = 1; vorbis_info_init (&vi); vorbis_comment_init (&vc); need_start_pos = 1; need_headers = 3; codec = ICES_INPUT_UNKNOWN; offset = (uint64_t)0; } if (need_start_pos) { int found_first_granulepos = 0; ogg_stream_pagein (&os, page); while (ogg_stream_packetout (&os, &op) == 1) { if (need_headers) { /* check for Vorbis. For Vorbis the Magic is {0x01|0x03|0x05}"vorbis" */ if (op.bytes > 7 && memcmp(op.packet+1, "vorbis", 6) == 0) { if (vorbis_synthesis_headerin (&vi, &vc, &op) < 0) { LOG_ERROR0("Timing control: can't determine sample rate for input, not vorbis."); control.samplerate = 0; vorbis_info_clear (&vi); ogg_stream_clear (&os); return -1; } control.samplerate = vi.rate; codec = ICES_INPUT_VORBIS; } /* check for Opus. For Opus the magic is "OpusHead" */ else if (op.bytes == 19 && memcmp(op.packet, "OpusHead", 8) == 0) { if (op.packet[8] != 1) { LOG_ERROR0("Timing control: can't determine sample rate for input, unsupported Opus version."); control.samplerate = 0; vorbis_info_clear (&vi); ogg_stream_clear (&os); return -1; } /* Sample rate is fixed for Opus: 48kHz */ control.samplerate = 48000; codec = ICES_INPUT_OGG; /* No more headers after this one needed */ need_headers = 1; } else if (op.bytes >= 80 && memcmp(op.packet, "Speex ", 8) == 0) { if (__read_int32_le(op.packet+28) != 1 || __read_int32_le(op.packet+32) != op.bytes) { LOG_ERROR0("Timing control: can't determine sample rate for input, bad or unsupported Speex header."); control.samplerate = 0; vorbis_info_clear (&vi); ogg_stream_clear (&os); return -1; } control.samplerate = __read_int32_le(op.packet+36); codec = ICES_INPUT_OGG; /* No more headers after this one needed */ need_headers = 1; } else if (op.bytes >= 51 && memcmp(op.packet, "\177FLAC\1\0", 7) == 0 && memcmp(op.packet+9, "fLaC\0", 5) == 0) { control.samplerate = __read_int20_be(op.packet+27); codec = ICES_INPUT_OGG; /* No more headers after this one needed */ need_headers = 1; } else if (codec == ICES_INPUT_UNKNOWN) { LOG_ERROR0("Timing control: can't determine sample rate for input, unsupported input format."); control.samplerate = 0; vorbis_info_clear (&vi); ogg_stream_clear (&os); return -1; } need_headers--; if (need_headers == 0) { vorbis_comment_clear (&vc); first_granulepos = (uint64_t)0; return 0; } continue; } /* headers have been read */ if (first_granulepos == 0 && op.granulepos > 0) { first_granulepos = op.granulepos; found_first_granulepos = 1; } if (codec == ICES_INPUT_VORBIS) { offset += vorbis_packet_blocksize (&vi, &op) / 4; } } if (!found_first_granulepos) return 0; need_start_pos = 0; control.oldsamples = first_granulepos - offset; vorbis_info_clear (&vi); ogg_stream_clear (&os); state_in_use = 0; } if (serialno != ogg_page_serialno (page)) { LOG_ERROR0 ("Found page which does not belong to current logical stream"); return -1; } control.samples = ogg_page_granulepos (page) - control.oldsamples; control.oldsamples = ogg_page_granulepos (page); control.senttime += ((uint64_t)control.samples * 1000000 / (uint64_t)control.samplerate); return 0; }
/* Process a buffer (including reencoding or encoding, if desired). * Returns: >0 - success * 0 - shout error occurred * -1 - no data produced * -2 - fatal error occurred */ int process_and_send_buffer(stream_description *sdsc, ref_buffer *buffer) { if(sdsc->reenc) { unsigned char *buf; int buflen,ret; ret = reencode_page(sdsc->reenc, buffer, &buf, &buflen); if(ret > 0) { ret = stream_send_data(sdsc, buf, buflen); free(buf); return ret; } else if(ret==0) /* No data produced by reencode */ return -1; else { LOG_ERROR0("Fatal reencoding error encountered"); return -2; } } else if (sdsc->enc) { ogg_page og; int be = (sdsc->input->subtype == INPUT_PCM_BE_16)?1:0; int ret=1; /* We use critical as a flag to say 'start a new stream' */ if(buffer->critical) { if(sdsc->resamp) { resample_finish(sdsc->resamp); encode_data_float(sdsc->enc, sdsc->resamp->buffers, sdsc->resamp->buffill); resample_clear(sdsc->resamp); sdsc->resamp = resample_initialise (sdsc->stream->channels, sdsc->stream->resampleinrate, sdsc->stream->resampleoutrate); } encode_finish(sdsc->enc); while(encode_flush(sdsc->enc, &og) != 0) { if ((ret = stream_send_data(sdsc, og.header, og.header_len)) == 0) return 0; if ((ret = stream_send_data(sdsc, og.body, og.body_len)) == 0) return 0; } encode_clear(sdsc->enc); if(sdsc->input->metadata_update) { vorbis_comment_clear(&sdsc->vc); vorbis_comment_init(&sdsc->vc); sdsc->input->metadata_update(sdsc->input->internal, &sdsc->vc); } sdsc->enc = encode_initialise(sdsc->stream->channels, sdsc->stream->samplerate, sdsc->stream->managed, sdsc->stream->min_br, sdsc->stream->nom_br, sdsc->stream->max_br, sdsc->stream->quality, &sdsc->vc); if(!sdsc->enc) { LOG_ERROR0("Failed to initialise encoder"); return -2; } sdsc->enc->max_samples_ppage = sdsc->stream->max_samples_ppage; } if(sdsc->downmix) { downmix_buffer(sdsc->downmix, (signed char *)buffer->buf, buffer->len, be); if(sdsc->resamp) { resample_buffer_float(sdsc->resamp, &sdsc->downmix->buffer, buffer->len/4); encode_data_float(sdsc->enc, sdsc->resamp->buffers, sdsc->resamp->buffill); } else encode_data_float(sdsc->enc, &sdsc->downmix->buffer, buffer->len/4); } else if(sdsc->resamp) { resample_buffer(sdsc->resamp, (signed char *)buffer->buf, buffer->len, be); encode_data_float(sdsc->enc, sdsc->resamp->buffers, sdsc->resamp->buffill); } else { encode_data(sdsc->enc, (signed char *)(buffer->buf), buffer->len, be); } while(encode_dataout(sdsc->enc, &og) > 0) { if ((ret = stream_send_data(sdsc, og.header, og.header_len)) == 0) return 0; if ((ret = stream_send_data(sdsc, og.body, og.body_len)) == 0) return 0; } return ret; } else return stream_send_data(sdsc, buffer->buf, buffer->len); }
/* 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 *playlist_open_module(module_param_t *params) { input_module_t *mod = calloc(1, sizeof(input_module_t)); playlist_state_t *pl; module_param_t *current; int (*init)(module_param_t *, playlist_state_t *)=NULL; mod->type = ICES_INPUT_VORBIS; mod->getdata = playlist_read; mod->handle_event = event_handler; mod->metadata_update = NULL; /* Not used for playlists */ mod->internal = calloc(1, sizeof(playlist_state_t)); pl = (playlist_state_t *)mod->internal; current = params; while(current) { if (!strcmp(current->name, "type")) { int current_module = 0; while(modules[current_module].init) { if(!strcmp(current->value, modules[current_module].name)) { init = modules[current_module].init; break; } current_module++; } if(!init) { LOG_ERROR1("Unknown playlist type \"%s\"", current->value); goto fail; } } current = current->next; } if(init) { if(init(params, pl)) { LOG_ERROR0("Playlist initialisation failed"); goto fail; } else { ogg_sync_init(&pl->oy); return mod; /* Success. Finished initialising */ } } else LOG_ERROR0("No playlist type given, cannot initialise playlist module"); fail: if (mod) { if (mod->internal) free(mod->internal); free(mod); } return NULL; }
int input_calculate_ogg_sleep(ogg_page *page) { static ogg_stream_state os; ogg_packet op; static vorbis_info vi; static vorbis_comment vc; static int need_start_pos, need_headers, state_in_use = 0; static int serialno = 0; static uint64_t offset; static uint64_t first_granulepos; if (ogg_page_granulepos(page) == -1) { LOG_ERROR0("Timing control: corrupt timing information in vorbis file, cannot stream."); return -1; } if (ogg_page_bos (page)) { control.oldsamples = 0; if (state_in_use) ogg_stream_clear (&os); ogg_stream_init (&os, ogg_page_serialno (page)); serialno = ogg_page_serialno (page); state_in_use = 1; vorbis_info_init (&vi); vorbis_comment_init (&vc); need_start_pos = 1; need_headers = 3; offset = (uint64_t)0; } if (need_start_pos) { int found_first_granulepos = 0; ogg_stream_pagein (&os, page); while (ogg_stream_packetout (&os, &op) == 1) { if (need_headers) { if (vorbis_synthesis_headerin (&vi, &vc, &op) < 0) { LOG_ERROR0("Timing control: can't determine sample rate for input, not vorbis."); control.samplerate = 0; return -1; } need_headers--; control.samplerate = vi.rate; if (need_headers == 0) { vorbis_comment_clear (&vc); first_granulepos = (uint64_t)0; return 0; } continue; } /* headers have been read */ if (first_granulepos == 0 && op.granulepos > 0) { first_granulepos = op.granulepos; found_first_granulepos = 1; } offset += vorbis_packet_blocksize (&vi, &op) / 4; } if (!found_first_granulepos) return 0; need_start_pos = 0; control.oldsamples = first_granulepos - offset; vorbis_info_clear (&vi); ogg_stream_clear (&os); state_in_use = 0; } if (serialno != ogg_page_serialno (page)) { LOG_ERROR0 ("Found page which does not belong to current logical stream"); return -1; } control.samples = ogg_page_granulepos (page) - control.oldsamples; control.oldsamples = ogg_page_granulepos (page); control.senttime += ((uint64_t)control.samples * 1000000 / (uint64_t)control.samplerate); return 0; }
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; }
/* 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; }