X-Git-Url: http://git.tuebingen.mpg.de/?p=paraslash.git;a=blobdiff_plain;f=audiod.c;h=29a8aa30abec6be72aee946b92d7767f3fc037e5;hp=9b79121b3aa16f51662b9e7e2649e097f81b5a26;hb=672e27768e31a6eaa6854d4fc75ee6885db9abd8;hpb=7c11e1acf8efc93b36fcae77c3f0ce04ae491c23;ds=sidebyside diff --git a/audiod.c b/audiod.c index 9b79121b..29a8aa30 100644 --- a/audiod.c +++ b/audiod.c @@ -1,19 +1,24 @@ /* - * Copyright (C) 2005-2007 Andre Noll + * Copyright (C) 2005-2009 Andre Noll * * Licensed under the GPL v2. For licencing details see COPYING. */ /** \file audiod.c the paraslash's audio daemon */ +#include +#include +#include +#include #include "para.h" - +#include "error.h" +#include "crypt.h" #include "audiod.cmdline.h" #include "list.h" #include "sched.h" +#include "ggo.h" #include "recv.h" #include "filter.h" -#include "grab_client.cmdline.h" #include "grab_client.h" #include "client.cmdline.h" #include "client.h" @@ -24,7 +29,6 @@ #include "fd.h" #include "write.h" #include "write_common.h" -#include "error.h" #include "signal.h" /** define the array of error lists needed by para_audiod */ @@ -32,7 +36,7 @@ INIT_AUDIOD_ERRLISTS; /** define the array containing all supported audio formats */ const char *audio_formats[] = {AUDIOD_AUDIO_FORMAT_ARRAY NULL}; -/** defines how to handle one supported audio format */ +/** Defines how audiod handles one supported audio format. */ struct audio_format_info { /** pointer to the receiver for this audio format */ struct receiver *receiver; @@ -40,14 +44,14 @@ struct audio_format_info { void *receiver_conf; /** the number of filters that should be activated for this audio format */ unsigned int num_filters; - /** pointer to the array of filters to be activated */ - struct filter **filters; - /** pointer to the array of filter configurations */ + /** Array of filter numbers to be activated. */ + unsigned *filter_nums; + /** Pointer to the array of filter configurations. */ void **filter_conf; /** the number of filters that should be activated for this audio format */ unsigned int num_writers; - /** pointer to the array of writers to be activated */ - struct writer **writers; + /** Array of writer numbers to be activated. */ + int *writer_nums; /** pointer to the array of writer configurations */ void **writer_conf; /** do not start receiver/filters/writer before this time */ @@ -63,6 +67,50 @@ struct audio_format_info { * */ struct slot_info slot[MAX_STREAM_SLOTS]; +/** The vss status flags audiod is interested in. */ +enum vss_status_flags { + /** Whether the 'N' flag is set. */ + VSS_STATUS_FLAG_NEXT = 1, + /** The 'P' flag is set. */ + VSS_STATUS_FLAG_PLAYING = 2, +}; + +/** + * The task for obtaining para_server's status (para_client stat). + * + * \sa struct task, struct sched. + */ +struct status_task { + /** The associated task structure of audiod. */ + struct task task; + /** Client data associated with the stat task. */ + struct client_task *ct; + /** Do not restart client command until this time. */ + struct timeval restart_barrier; + /** Last time we received status data from para_server. */ + struct timeval last_status_read; + /** The offset value announced by para_server. */ + int offset_seconds; + /** The length of the current audio file as announced by para_server. */ + int length_seconds; + /** The start of the current stream from the view of para_server. */ + struct timeval server_stream_start; + /** The average time deviation between para_server and para_audiod. */ + struct timeval sa_time_diff; + /** Whether client time is ahead of server time. */ + int sa_time_diff_sign; + /** The 'P' and the 'N' flags as announced by para_server. */ + enum vss_status_flags vss_status; + /** Number of times the clock difference is to be checked. */ + unsigned clock_diff_count; + /** When to start the next check for clock difference. */ + struct timeval clock_diff_barrier; + /** Number of the audio format as announced by para_server. */ + int current_audio_format_num; +}; + +/** The array of status items sent by para_server. */ +char *stat_item_values[NUM_STAT_ITEMS] = {NULL}; /** * the current mode of operation of which can be changed by the on/off/cycle @@ -77,7 +125,6 @@ int audiod_status = AUDIOD_ON; struct audiod_args_info conf; static char *socket_name; -static FILE *logfile; static struct audio_format_info afi[NUM_AUDIO_FORMATS]; static struct signal_task signal_task_struct, *sig_task = &signal_task_struct; @@ -89,7 +136,7 @@ static struct status_task status_task_struct; * * \sa struct status_task */ -struct status_task *stat_task = &status_task_struct; +static struct status_task *stat_task = &status_task_struct; static struct timeval initial_delay_barrier; /** @@ -104,18 +151,6 @@ struct command_task { struct task task; }; -/** - * task for signal handling - */ -struct signal_task { - /** the signal pipe */ - int fd; - /** the number of the most recent signal */ - int signum; - /** the associated task structure */ - struct task task; -}; - /** iterate over all supported audio formats */ #define FOR_EACH_AUDIO_FORMAT(af) for (af = 0; af < NUM_AUDIO_FORMATS; af++) @@ -126,56 +161,122 @@ struct signal_task { * \return The audio format number on success, -E_UNSUPPORTED_AUDIO_FORMAT if * \a name is not a supported audio format. */ -int get_audio_format_num(char *name) +int get_audio_format_num(const char *name) { int i; + + while (para_isspace(*name)) + name++; FOR_EACH_AUDIO_FORMAT(i) if (!strcmp(name, audio_formats[i])) return i; return -E_UNSUPPORTED_AUDIO_FORMAT; } -/** - * the log function of para_audiod - * - * \param ll loglevel - * \param fmt the format string - */ -void para_log(int ll, const char* fmt,...) +char *get_time_string(int slot_num) { - va_list argp; - FILE *outfd; - struct tm *tm; - time_t t1; - char str[MAXLINE] = ""; - static char *hostname; - - if (ll < conf.loglevel_arg) - return; - if (!hostname) - hostname = para_hostname(); - outfd = logfile? logfile : stderr; - time(&t1); - tm = localtime(&t1); - strftime(str, MAXLINE, "%b %d %H:%M:%S", tm); - fprintf(outfd, "%s %s ", str, hostname); - if (conf.loglevel_arg <= INFO) - fprintf(outfd, "%i ", ll); - va_start(argp, fmt); - vfprintf(outfd, fmt, argp); - va_end(argp); + int ret, seconds = 0, length; + struct timeval *tmp, sum, sss, /* server stream start */ + wtime, /* now - writer start */ + rskip; /* receiver start - sss */ + struct slot_info *s = slot_num < 0? NULL : &slot[slot_num]; + + if (audiod_status == AUDIOD_OFF) + goto empty; + if (!(stat_task->vss_status & VSS_STATUS_FLAG_PLAYING)) { + if (stat_task->length_seconds) /* paused */ + return NULL; + goto empty; /* stopped */ + } + if (audiod_status == AUDIOD_ON && !s) + goto empty; + /* valid status items and playing */ + if (s) { /* writer active in this slot */ + length = s->seconds_total; + tmp = &s->server_stream_start; + } else { /* standby mode, rely on status items */ + length = stat_task->length_seconds; + tmp = &stat_task->server_stream_start; + } + if (stat_task->sa_time_diff_sign > 0) + tv_diff(tmp, &stat_task->sa_time_diff, &sss); + else + tv_add(tmp, &stat_task->sa_time_diff, &sss); + if (!s) { + struct timeval diff; + tv_diff(now, &sss, &diff); + seconds = diff.tv_sec + stat_task->offset_seconds; + goto out; + } + tv_diff(now, &s->wstime, &wtime); + seconds = s->offset_seconds; + ret = tv_diff(&s->rstime, &sss, &rskip); + if (ret > 0) { /* audiod was started in the middle of the stream */ + tv_add(&wtime, &rskip, &sum); + seconds += sum.tv_sec; + } else + seconds += wtime.tv_sec; +out: + seconds = PARA_MIN(seconds, length); + seconds = PARA_MAX(seconds, 0); + return make_message( + "%s%d:%02d [%d:%02d] (%d%%/%d:%02d)", + s? "" : "~", + seconds / 60, + seconds % 60, + (length - seconds) / 60, + (length - seconds) % 60, + length? (seconds * 100 + length / 2) / length : 0, + length / 60, + length % 60 + ); +empty: + return para_strdup(NULL); } -static char *configfile_exists(void) +static int want_colors(void) { - char *home = para_homedir(); - char *config_file = make_message("%s/.paraslash/audiod.conf", - home); - free(home); - if (file_exists(config_file)) - return config_file; + if (conf.color_arg == color_arg_no) + return 0; + if (conf.color_arg == color_arg_yes) + return 1; + if (conf.logfile_given) + return 0; + return isatty(STDERR_FILENO); +} + +static void parse_config_or_die(void) +{ + int ret; + char *config_file; + struct audiod_cmdline_parser_params params = { + .override = 0, + .initialize = 0, + .check_required = 1, + .check_ambiguity = 0, + .print_errors = 1 + }; + + if (conf.config_file_given) + config_file = para_strdup(conf.config_file_arg); + else { + char *home = para_homedir(); + config_file = make_message("%s/.paraslash/audiod.conf", home); + free(home); + } + ret = file_exists(config_file); + if (conf.config_file_given && !ret) { + PARA_EMERG_LOG("can not read config file %s\n", config_file); + goto err; + } + if (ret) + audiod_cmdline_parser_config_file(config_file, &conf, ¶ms); + free(config_file); + daemon_set_loglevel(conf.loglevel_arg); + return; +err: free(config_file); - return NULL; + exit(EXIT_FAILURE); } static void setup_signal_handling(void) @@ -185,7 +286,7 @@ static void setup_signal_handling(void) para_install_sighandler(SIGINT); para_install_sighandler(SIGTERM); para_install_sighandler(SIGHUP); - signal(SIGPIPE, SIG_IGN); + para_sigaction(SIGPIPE, SIG_IGN); } static void clear_slot(int slot_num) @@ -205,34 +306,31 @@ static void close_receiver(int slot_num) if (s->format < 0 || !s->receiver_node) return; a = &afi[s->format]; - PARA_NOTICE_LOG("closing %s receiver in slot %d (eof = %d)\n", - audio_formats[s->format] , slot_num, s->receiver_node->eof); + PARA_NOTICE_LOG("closing %s receiver in slot %d\n", + audio_formats[s->format], slot_num); a->receiver->close(s->receiver_node); free(s->receiver_node); s->receiver_node = NULL; } -static void kill_all_decoders(void) +static void kill_all_decoders(int error) { int i; FOR_EACH_SLOT(i) { struct slot_info *s = &slot[i]; - if (s->wng && !s->wng->eof) { - PARA_INFO_LOG("unregistering writer node group in slot %d\n", + if (s->wng && s->wng->task.error >= 0) { + PARA_INFO_LOG("deactivating wng in slot %d\n", i); - wng_unregister(s->wng); - s->wng->eof = 1; + s->wng->task.error = error; } - if (s->fc && !s->fc->eof) { - PARA_INFO_LOG("unregistering filter chain in slot %d\n", i); - unregister_task(&s->fc->task); - s->fc->eof = 1; + if (s->fc && s->fc->task.error >= 0) { + PARA_INFO_LOG("deactivatimg filter chain in slot %d\n", i); + s->fc->task.error = error; } - if (s->receiver_node && !s->receiver_node->eof) { - PARA_INFO_LOG("unregistering receiver_node in slot %d\n", i); - unregister_task(&s->receiver_node->task); - s->receiver_node->eof = 1; + if (s->receiver_node && s->receiver_node->task.error >= 0) { + PARA_INFO_LOG("deactivating receiver_node in slot %d\n", i); + s->receiver_node->task.error = error; } } } @@ -270,18 +368,11 @@ int num_filters(int audio_format_num) return afi[audio_format_num].num_filters; } -static void filter_event_handler(struct task *t) -{ - PARA_NOTICE_LOG("%s\n", PARA_STRERROR(-t->ret)); - struct filter_chain *fc = t->private_data; - fc->eof = 1; - unregister_task(t); -} - static void open_filters(int slot_num) { struct slot_info *s = &slot[slot_num]; struct audio_format_info *a = &afi[s->format]; + struct filter_node *fn; int nf = a->num_filters; int i; @@ -290,43 +381,33 @@ static void open_filters(int slot_num) return; PARA_INFO_LOG("opening %s filters\n", audio_formats[s->format]); s->fc = para_calloc(sizeof(struct filter_chain)); - INIT_LIST_HEAD(&s->fc->filters); - s->fc->inbuf = s->receiver_node->buf; + s->fc->filter_nodes = para_malloc(nf * sizeof(struct filter_node)); + s->fc->inbufp = &s->receiver_node->buf; s->fc->in_loaded = &s->receiver_node->loaded; - s->fc->input_eof = &s->receiver_node->eof; + s->fc->input_error = &s->receiver_node->task.error; s->fc->task.pre_select = filter_pre_select; - s->fc->task.event_handler = filter_event_handler; - s->fc->task.private_data = s->fc; - s->fc->eof = 0; + s->fc->task.post_select = NULL; + s->fc->task.error = 0; + s->fc->num_filters = nf; - s->receiver_node->output_eof = &s->fc->eof; + s->receiver_node->output_error = &s->fc->task.error; sprintf(s->fc->task.status, "filter chain"); - for (i = 0; i < nf; i++) { - struct filter_node *fn = para_calloc(sizeof(struct filter_node)); + FOR_EACH_FILTER_NODE(fn, s->fc, i) { + struct filter *f = filters + a->filter_nums[i]; + fn->filter_num = a->filter_nums[i]; fn->conf = a->filter_conf[i]; fn->fc = s->fc; - fn->filter = a->filters[i]; + fn->loaded = 0; INIT_LIST_HEAD(&fn->callbacks); - list_add_tail(&fn->node, &s->fc->filters); - fn->filter->open(fn); + f->open(fn); PARA_NOTICE_LOG("%s filter %d/%d (%s) started in slot %d\n", - audio_formats[s->format], i + 1, nf, - fn->filter->name, slot_num); - s->fc->outbuf = fn->buf; + audio_formats[s->format], i, nf, f->name, slot_num); + s->fc->outbufp = &fn->buf; s->fc->out_loaded = &fn->loaded; } register_task(&s->fc->task); } -static void wng_event_handler(struct task *t) -{ - struct writer_node_group *wng = t->private_data; - - PARA_INFO_LOG("%s\n", PARA_STRERROR(-t->ret)); - wng->eof = 1; - wng_unregister(wng); -} - static void open_writers(int slot_num) { int ret, i; @@ -339,47 +420,31 @@ static void open_writers(int slot_num) else s->wng = wng_new(a->num_writers); if (s->fc) { - s->wng->buf = s->fc->outbuf; + s->wng->bufp = s->fc->outbufp; s->wng->loaded = s->fc->out_loaded; - s->wng->input_eof = &s->fc->eof; + s->wng->input_error = &s->fc->task.error; s->wng->channels = &s->fc->channels; s->wng->samplerate = &s->fc->samplerate; - s->fc->output_eof = &s->wng->eof; + s->fc->output_error = &s->wng->task.error; PARA_INFO_LOG("samplerate: %d\n", *s->wng->samplerate); } else { - s->wng->buf = s->receiver_node->buf; + s->wng->bufp = &s->receiver_node->buf; s->wng->loaded = &s->receiver_node->loaded; - s->wng->input_eof = &s->receiver_node->eof; + s->wng->input_error = &s->receiver_node->task.error; } - s->wng->task.event_handler = wng_event_handler; for (i = 0; i < a->num_writers; i++) { s->wng->writer_nodes[i].conf = a->writer_conf[i]; - s->wng->writer_nodes[i].writer = a->writers[i]; + s->wng->writer_nodes[i].writer_num = a->writer_nums[i]; } ret = wng_open(s->wng); - if (ret < 0) { - PARA_ERROR_LOG("%s\n", PARA_STRERROR(-ret)); + if (ret < 0) return; - } s->wstime = *now; - activate_inactive_grab_clients(slot_num, s->format, &s->fc->filters); -} - -static void rn_event_handler(struct task *t) -{ - struct receiver_node *rn = t->private_data; - const struct timeval restart_delay = {0, 10 * 1000}; - int i; - - PARA_NOTICE_LOG("%s\n", PARA_STRERROR(-t->ret)); - unregister_task(t); - rn->eof = 1; - /* set restart barrier */ - FOR_EACH_SLOT(i) { - if (slot[i].receiver_node != rn) - continue; - tv_add(now, &restart_delay, &afi[slot[i].format].restart_barrier); - } + s->server_stream_start = stat_task->server_stream_start.tv_sec? + stat_task->server_stream_start : *now; + s->offset_seconds = stat_task->offset_seconds; + s->seconds_total = stat_task->length_seconds; + activate_inactive_grab_clients(s->format, s->fc); } static int open_receiver(int format) @@ -388,7 +453,7 @@ static int open_receiver(int format) struct slot_info *s; int ret, slot_num; struct receiver_node *rn; - const struct timeval restart_delay = {1, 0}; + const struct timeval restart_delay = {2, 0}; ret = get_empty_slot(); if (ret < 0) @@ -408,51 +473,67 @@ static int open_receiver(int format) } PARA_NOTICE_LOG("started %s: %s receiver in slot %d\n", audio_formats[s->format], a->receiver->name, slot_num); - rn->task.private_data = s->receiver_node; rn->task.pre_select = a->receiver->pre_select; rn->task.post_select = a->receiver->post_select; - rn->task.event_handler = rn_event_handler; + s->rstime = *now; sprintf(rn->task.status, "%s receiver node", rn->receiver->name); register_task(&rn->task); - return 1; + ret = 1; err: - PARA_ERROR_LOG("%s\n", PARA_STRERROR(-ret)); + if (ret < 0) + PARA_ERROR_LOG("%s\n", para_strerror(-ret)); tv_add(now, &restart_delay, &afi[format].restart_barrier); return ret; } +/* return: 0: Not running, 1: Running, -1: Running but eof (or error) */ static int receiver_running(int format) { - int i; + int i, ret = 0; FOR_EACH_SLOT(i) { struct slot_info *s = &slot[i]; - if (s->format == format && s->receiver_node - && !s->receiver_node->eof) + if (s->format != format) + continue; + if (!s->receiver_node) + continue; + if (s->receiver_node->task.error >= 0) return 1; + ret = -1; } - return 0; + return ret; } -static int open_current_receiver(struct sched *s) +static void open_current_receiver(struct sched *s) { - int i; struct timeval diff; - char *audio_format = stat_task->stat_item_values[SI_FORMAT]; + int ret, cafn = stat_task->current_audio_format_num; - if (!audio_format || !stat_task->pcd) - return 0; - i = get_audio_format_num(audio_format + strlen( - status_item_list[SI_FORMAT]) + 1); - if (i < 0) - return 0; - if (receiver_running(i)) - return 0; - if (tv_diff(now, &afi[i].restart_barrier, &diff) < 0) { + if (cafn < 0 || !stat_task->ct) + return; + /* Do nothing if the 'N' flag is set or the 'P' flag is unset */ + if (stat_task->vss_status != VSS_STATUS_FLAG_PLAYING) + return; + ret = receiver_running(cafn); + if (ret > 0) /* already running and not eof */ + return; + if (ret < 0) { /* eof */ + /* + * para_server uses a zero start time during the announcement + * period, i.e. before it sends the first chunk. Wait until + * this period begins to avoid restarting the receiver that + * belongs to the file just completed. + */ + if (stat_task->server_stream_start.tv_sec) + return; + } + if (tv_diff(now, &afi[cafn].restart_barrier, &diff) < 0) { + /* avoid busy loop */ s->timeout = diff; - return 0; + return; } - return open_receiver(i) < 0? 0 : 1; + /* start a new receiver */ + open_receiver(cafn); } static unsigned compute_time_diff(const struct timeval *status_time) @@ -497,41 +578,31 @@ out: return count; } -static void check_stat_line(char *line) +static int update_item(int itemnum, char *buf) { - int itemnum; - size_t ilen = 0; long unsigned sec, usec; - char *tmp; -// PARA_INFO_LOG("line: %s\n", line); - if (!line) - return; - itemnum = stat_line_valid(line); - if (itemnum < 0) { - PARA_WARNING_LOG("invalid status line: %s\n", line); - return; - } if (stat_task->clock_diff_count && itemnum != SI_CURRENT_TIME) - return; - tmp = make_message("%s\n", line); - stat_client_write(tmp, itemnum); - free(tmp); - free(stat_task->stat_item_values[itemnum]); - stat_task->stat_item_values[itemnum] = para_strdup(line); - ilen = strlen(status_item_list[itemnum]); + return 1; + free(stat_item_values[itemnum]); + stat_item_values[itemnum] = para_strdup(buf); + stat_client_write_item(itemnum); switch (itemnum) { - case SI_STATUS: - stat_task->playing = strstr(line, "playing")? 1 : 0; + case SI_STATUS_FLAGS: + stat_task->vss_status = 0; + if (strchr(buf, 'N')) + stat_task->vss_status |= VSS_STATUS_FLAG_NEXT; + if (strchr(buf, 'P')) + stat_task->vss_status |= VSS_STATUS_FLAG_PLAYING; break; case SI_OFFSET: - stat_task->offset_seconds = atoi(line + ilen + 1); + stat_task->offset_seconds = atoi(buf); break; - case SI_LENGTH: - stat_task->length_seconds = atoi(line + ilen + 1); + case SI_SECONDS_TOTAL: + stat_task->length_seconds = atoi(buf); break; case SI_STREAM_START: - if (sscanf(line + ilen + 1, "%lu.%lu", &sec, &usec) == 2) { + if (sscanf(buf, "%lu.%lu", &sec, &usec) == 2) { struct timeval a_start, delay; delay.tv_sec = conf.stream_delay_arg / 1000; delay.tv_usec = (conf.stream_delay_arg % 1000) * 1000; @@ -549,105 +620,16 @@ static void check_stat_line(char *line) } break; case SI_CURRENT_TIME: - if (sscanf(line + ilen + 1, "%lu.%lu", &sec, &usec) == 2) { + if (sscanf(buf, "%lu.%lu", &sec, &usec) == 2) { struct timeval tv = {sec, usec}; compute_time_diff(&tv); } - if (stat_task->clock_diff_count) - stat_task->clock_diff_count--; break; + case SI_FORMAT: + stat_task->current_audio_format_num + = get_audio_format_num(buf); } -} - -static void try_to_close_slot(int slot_num) -{ - struct slot_info *s = &slot[slot_num]; - - if (s->format < 0) - return; - if (s->receiver_node && !s->receiver_node->eof) - return; - if (s->fc && !s->fc->eof) - return; - if (s->wng && !s->wng->eof) - return; - PARA_INFO_LOG("closing slot %d \n", slot_num); - wng_close(s->wng); - close_filters(s->fc); - free(s->fc); - close_receiver(slot_num); - clear_slot(slot_num); -} - -/* - * Check if any receivers/filters/writers need to be started and do so if - * neccessary. Since the pre_select function didn't have a chance yet to put - * file descriptors into the fd sets given by s, make the upcoming select() - * return immediately to avoid a long timeout in case we started something. - */ -static void audiod_pre_select(struct sched *s, __a_unused struct task *t) -{ - int i; - struct timeval min_delay = {0, 1}; - - t->ret = 1; - if (audiod_status != AUDIOD_ON || !stat_task->playing) - return kill_all_decoders(); - if (open_current_receiver(s)) - s->timeout = min_delay; - FOR_EACH_SLOT(i) { - struct slot_info *sl = &slot[i]; - struct audio_format_info *a; - struct timeval diff; - - if (sl->format < 0) - continue; - a = &afi[sl->format]; - if (!sl->receiver_node) - continue; - if (!a->num_filters) { - if (sl->receiver_node->loaded && !sl->wng) { - open_writers(i); - s->timeout = min_delay; - } - continue; - } - if (sl->receiver_node->loaded && !sl->fc) { - open_filters(i); - s->timeout = min_delay; - continue; - } - if (!sl->fc || !*sl->fc->out_loaded || sl->wng) - continue; - if (tv_diff(now, &initial_delay_barrier, &diff) > 0) { - open_writers(i); - s->timeout = min_delay; - continue; - } - PARA_INFO_LOG("initial delay: %lu ms left\n", tv2ms(&diff)); - if (tv_diff(&s->timeout, &diff, NULL) > 0) { - s->timeout = diff; - } - } -} - -static void audiod_post_select(__a_unused struct sched *s, - __a_unused struct task *t) -{ - int i; - - t->ret = 1; - FOR_EACH_SLOT(i) - try_to_close_slot(i); -} - -static void init_audiod_task(struct task *t) -{ - t->pre_select = audiod_pre_select; - t->post_select = audiod_post_select; - t->event_handler = NULL; - t->private_data = t; - sprintf(t->status, "audiod task"); + return 1; } static int parse_stream_command(const char *txt, char **cmd) @@ -675,26 +657,25 @@ static int add_filter(int format, char *cmdline) filter_num = check_filter_arg(cmdline, &a->filter_conf[nf]); if (filter_num < 0) return filter_num; - a->filters[nf] = &filters[filter_num]; + a->filter_nums[nf] = filter_num; a->num_filters++; - PARA_INFO_LOG("%s filter %d: %s\n", audio_formats[format], nf + 1, - a->filters[nf]->name); + PARA_INFO_LOG("%s filter %d: %s\n", audio_formats[format], nf, + filters[filter_num].name); return filter_num; } -static int init_writers(void) +static int parse_writer_args(void) { int i, ret, nw; char *cmd; struct audio_format_info *a; - init_supported_writers(); - nw = PARA_MAX(1, conf.writer_given); + nw = PARA_MAX(1U, conf.writer_given); PARA_INFO_LOG("maximal number of writers: %d\n", nw); FOR_EACH_AUDIO_FORMAT(i) { a = &afi[i]; a->writer_conf = para_malloc(nw * sizeof(void *)); - a->writers = para_malloc(nw * sizeof(struct writer *)); + a->writer_nums = para_malloc(nw * sizeof(int)); a->num_writers = 0; } for (i = 0; i < conf.writer_given; i++) { @@ -710,7 +691,7 @@ static int init_writers(void) ret = writer_num; goto out; } - a->writers[nw] = &writers[writer_num]; + a->writer_nums[nw] = writer_num; a->writer_conf[nw] = wconf; PARA_INFO_LOG("%s writer #%d: %s\n", audio_formats[ret], nw, writer_names[writer_num]); @@ -721,16 +702,12 @@ out: return ret; } -static int init_receivers(void) +static int parse_receiver_args(void) { int i, ret, receiver_num; char *cmd = NULL; struct audio_format_info *a; - for (i = 0; receivers[i].name; i++) { - PARA_INFO_LOG("initializing %s receiver\n", receivers[i].name); - receivers[i].init(&receivers[i]); - } for (i = conf.receiver_given - 1; i >= 0; i--) { char *arg = conf.receiver_arg[i]; char *recv_arg = strchr(arg, ':'); @@ -801,16 +778,15 @@ out: return ret; } -static int init_filters(void) +static int parse_filter_args(void) { int i, ret, nf; - filter_init(filters); - nf = PARA_MAX(1, conf.filter_given); + nf = PARA_MAX(1U, conf.filter_given); PARA_INFO_LOG("maximal number of filters: %d\n", nf); FOR_EACH_AUDIO_FORMAT(i) { afi[i].filter_conf = para_malloc(nf * sizeof(void *)); - afi[i].filters = para_malloc(nf * sizeof(struct filter *)); + afi[i].filter_nums = para_malloc(nf * sizeof(unsigned)); } if (!conf.no_default_filters_given) return init_default_filters(); @@ -834,26 +810,27 @@ out: return ret; } -static int init_stream_io(void) +static int parse_stream_args(void) { int ret; - ret = init_writers(); + ret = parse_receiver_args(); if (ret < 0) return ret; - ret = init_receivers(); + ret = parse_filter_args(); if (ret < 0) return ret; - ret = init_filters(); + ret = parse_writer_args(); if (ret < 0) return ret; return 1; } +/* does not unlink socket on errors */ static int audiod_get_socket(void) { struct sockaddr_un unix_addr; - int fd; + int ret, fd; if (conf.socket_given) socket_name = para_strdup(conf.socket_arg); @@ -866,116 +843,93 @@ static int audiod_get_socket(void) PARA_NOTICE_LOG("local socket: %s\n", socket_name); if (conf.force_given) unlink(socket_name); - fd = create_local_socket(socket_name, &unix_addr, - S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IWOTH); - if (fd < 0) { - PARA_EMERG_LOG("can not connect to socket\n"); - exit(EXIT_FAILURE); /* do not unlink socket */ - } + ret = create_local_socket(socket_name, &unix_addr, + S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IWOTH); + if (ret < 0) + goto err; + fd = ret; if (listen(fd , 5) < 0) { - PARA_EMERG_LOG("can not listen on socket\n"); - exit(EXIT_FAILURE); /* do not unlink socket */ + ret = -ERRNO_TO_PARA_ERROR(errno); + goto err; } - mark_fd_nonblock(fd); + ret = mark_fd_nonblocking(fd); + if (ret < 0) + goto err; return fd; -} - -static void signal_event_handler(struct task *t) -{ - struct signal_task *st = t->private_data; - - switch (st->signum) { - case SIGINT: - case SIGTERM: - case SIGHUP: - PARA_EMERG_LOG("terminating on signal %d\n", st->signum); - clean_exit(EXIT_FAILURE, "caught deadly signal"); - } +err: + PARA_EMERG_LOG("%s\n", para_strerror(-ret)); + exit(EXIT_FAILURE); } static void signal_pre_select(struct sched *s, struct task *t) { - struct signal_task *st = t->private_data; - t->ret = 1; + struct signal_task *st = container_of(t, struct signal_task, task); para_fd_set(st->fd, &s->rfds, &s->max_fileno); } static void signal_post_select(struct sched *s, struct task *t) { - struct signal_task *st = t->private_data; - t->ret = 1; + struct signal_task *st = container_of(t, struct signal_task, task); + if (!FD_ISSET(st->fd, &s->rfds)) return; - t->ret = -E_SIGNAL_CAUGHT; + st->signum = para_next_signal(); + switch (st->signum) { + case SIGINT: + case SIGTERM: + case SIGHUP: + PARA_EMERG_LOG("terminating on signal %d\n", st->signum); + clean_exit(EXIT_FAILURE, "caught deadly signal"); + } } static void signal_setup_default(struct signal_task *st) { st->task.pre_select = signal_pre_select; st->task.post_select = signal_post_select; - st->task.private_data = st; sprintf(st->task.status, "signal task"); } static void command_pre_select(struct sched *s, struct task *t) { - struct command_task *ct = t->private_data; - t->ret = 1; + struct command_task *ct = container_of(t, struct command_task, task); para_fd_set(ct->fd, &s->rfds, &s->max_fileno); - } static void command_post_select(struct sched *s, struct task *t) { int ret; - struct command_task *ct = t->private_data; + struct command_task *ct = container_of(t, struct command_task, task); - t->ret = 1; /* always successful */ audiod_status_dump(); if (!FD_ISSET(ct->fd, &s->rfds)) return; ret = handle_connect(ct->fd); if (ret < 0) - PARA_ERROR_LOG("%s\n", PARA_STRERROR(-ret)); + PARA_ERROR_LOG("%s\n", para_strerror(-ret)); } static void init_command_task(struct command_task *ct) { ct->task.pre_select = command_pre_select; ct->task.post_select = command_post_select; - ct->task.event_handler = NULL; - ct->task.private_data = ct; + ct->task.error = 0; ct->fd = audiod_get_socket(); /* doesn't return on errors */ sprintf(ct->task.status, "command task"); } static void close_stat_pipe(void) { - int i; - - if (!stat_task->pcd) + if (!stat_task->ct) return; - client_close(stat_task->pcd); - stat_task->pcd = NULL; - for (i = 0; i < NUM_STAT_ITEMS; i++) { - free(stat_task->stat_item_values[i]); - stat_task->stat_item_values[i] = NULL; - } - dump_empty_status(); + client_close(stat_task->ct); + stat_task->ct = NULL; + clear_and_dump_items(); stat_task->length_seconds = 0; stat_task->offset_seconds = 0; + stat_task->vss_status = 0; audiod_status_dump(); - stat_task->playing = 0; - stat_task->stat_item_values[SI_STATUS_BAR] = make_message( - "%s:no connection to para_server\n", - status_item_list[SI_STATUS_BAR]); - stat_client_write(stat_task->stat_item_values[SI_STATUS_BAR], - SI_STATUS_BAR); - if (stat_task->clock_diff_count) { - stat_task->clock_diff_barrier.tv_sec = now->tv_sec + 1; - stat_task->clock_diff_barrier.tv_usec = now->tv_usec; - } } /** @@ -1000,94 +954,159 @@ void __noreturn clean_exit(int status, const char *msg) } /* avoid busy loop if server is down */ -static void set_stat_task_restart_barrier(void) +static void set_stat_task_restart_barrier(unsigned seconds) { - struct timeval delay = {5, 0}; + struct timeval delay = {seconds, 0}; tv_add(now, &delay, &stat_task->restart_barrier); } -static void client_task_event_handler(__a_unused struct task *t) +static void try_to_close_slot(int slot_num) { - int i; + struct slot_info *s = &slot[slot_num]; - if (t->ret == -E_HANDSHAKE_COMPLETE) + if (s->format < 0) return; - unregister_task(t); - close_stat_pipe(); - if (t->ret != -E_SERVER_EOF) - stat_task->clock_diff_count = conf.clock_diff_count_arg; - set_stat_task_restart_barrier(); - FOR_EACH_AUDIO_FORMAT(i) - afi[i].restart_barrier = stat_task->restart_barrier; + if (s->receiver_node && s->receiver_node->task.error != -E_TASK_UNREGISTERED) + return; + if (s->fc && s->fc->task.error != -E_TASK_UNREGISTERED) + return; + if (s->wng && s->wng->task.error != -E_TASK_UNREGISTERED) + return; + PARA_INFO_LOG("closing slot %d\n", slot_num); + wng_close(s->wng); + close_filters(s->fc); + free(s->fc); + close_receiver(slot_num); + clear_slot(slot_num); } -static void status_pre_select(struct sched *s, struct task *t) +/* + * Check if any receivers/filters/writers need to be started and do so if + * necessary. + */ +static void start_stop_decoders(struct sched *s) { - struct status_task *st = t->private_data; - int ret; + int i; - t->ret = 1; /* always successful */ - if (st->pcd || audiod_status == AUDIOD_OFF) - return; - if (!st->clock_diff_count && tv_diff(now, &st->restart_barrier, NULL) - < 0) - return; - if (st->clock_diff_count) { - char *argv[] = {"audiod", "stat", "1", NULL}; - int argc = 3; - if (tv_diff(now, &st->clock_diff_barrier, NULL) < 0) - return; - PARA_INFO_LOG("clock diff count: %d\n", st->clock_diff_count); - ret = client_open(argc, argv, &st->pcd); + FOR_EACH_SLOT(i) + try_to_close_slot(i); + if (audiod_status != AUDIOD_ON || + !(stat_task->vss_status & VSS_STATUS_FLAG_PLAYING)) + return kill_all_decoders(-E_NOT_PLAYING); + open_current_receiver(s); + FOR_EACH_SLOT(i) { + struct slot_info *sl = &slot[i]; + struct audio_format_info *a; + struct timeval diff; - } else { - char *argv[] = {"audiod", "stat", NULL}; - int argc = 2; - ret = client_open(argc, argv, &st->pcd); + if (sl->format < 0) + continue; + a = &afi[sl->format]; + if (!sl->receiver_node) + continue; + if ((!a->num_filters || sl->fc) && sl->wng) + continue; /* everything already started */ + if (!a->num_filters) { + if (sl->receiver_node->loaded && !sl->wng) { + open_writers(i); + } + continue; + } + if (sl->receiver_node->loaded && !sl->fc) { + open_filters(i); + continue; + } + if (sl->wng || !sl->fc || !*sl->fc->out_loaded) + continue; + if (tv_diff(now, &initial_delay_barrier, &diff) > 0) { + open_writers(i); + continue; + } + PARA_INFO_LOG("initial delay: %lu ms left\n", tv2ms(&diff)); + if (tv_diff(&s->timeout, &diff, NULL) > 0) { + s->timeout = diff; + } } - set_stat_task_restart_barrier(); - if (ret < 0) - return; - st->pcd->task.event_handler = client_task_event_handler; - s->timeout.tv_sec = 0; - s->timeout.tv_usec = 1; } -static void status_post_select(__a_unused struct sched *s, struct task *t) + +/* restart the client task if necessary */ +static void status_pre_select(struct sched *s, struct task *t) { - struct status_task *st = t->private_data; - unsigned bytes_left; + struct status_task *st = container_of(t, struct status_task, task); - t->ret = 1; - if (!st->pcd || st->pcd->status != CL_RECEIVING) - return; - if (st->pcd && audiod_status == AUDIOD_OFF) { - unregister_task(&st->pcd->task); + if (audiod_status == AUDIOD_OFF) { + if (!st->ct) + goto out; + if (st->ct->task.error >= 0) { + st->ct->task.error = -E_AUDIOD_OFF; + goto out; + } + if (st->ct->task.error != -E_TASK_UNREGISTERED) + goto out; close_stat_pipe(); st->clock_diff_count = conf.clock_diff_count_arg; - return; + goto out; } - bytes_left = for_each_line(st->pcd->buf, st->pcd->loaded, - &check_stat_line); - if (st->pcd->loaded != bytes_left) { - st->last_status_read = *now; - st->pcd->loaded = bytes_left; - } else { - struct timeval diff; - tv_diff(now, &st->last_status_read, &diff); - if (diff.tv_sec > 61) + if (st->ct) { + int ret; + if (st->ct->task.error < 0) { + if (st->ct->task.error != -E_TASK_UNREGISTERED) + goto out; close_stat_pipe(); + goto out; + } + if (st->ct->status != CL_RECEIVING) + goto out; + ret = for_each_stat_item(st->ct->buf, st->ct->loaded, + update_item); + if (ret < 0) { + st->ct->task.error = ret; + goto out; + } + if (st->ct->loaded != ret) { + st->last_status_read = *now; + st->ct->loaded = ret; + } else { + struct timeval diff; + tv_diff(now, &st->last_status_read, &diff); + if (diff.tv_sec > 61) + st->ct->task.error = -E_STATUS_TIMEOUT; + } + goto out; } + if (tv_diff(now, &st->restart_barrier, NULL) < 0) + goto out; + if (st->clock_diff_count) { /* get status only one time */ + char *argv[] = {"audiod", "--", "stat", "-p", "1", NULL}; + int argc = 5; + PARA_INFO_LOG("clock diff count: %d\n", st->clock_diff_count); + st->clock_diff_count--; + client_open(argc, argv, &st->ct, NULL); + set_stat_task_restart_barrier(2); + + } else { + char *argv[] = {"audiod", "--", "stat", "-p", NULL}; + int argc = 4; + client_open(argc, argv, &st->ct, NULL); + set_stat_task_restart_barrier(5); + } + free(stat_item_values[SI_BASENAME]); + stat_item_values[SI_BASENAME] = para_strdup( + "no connection to para_server"); + stat_client_write_item(SI_BASENAME); + st->last_status_read = *now; +out: + start_stop_decoders(s); } static void init_status_task(struct status_task *st) { memset(st, 0, sizeof(struct status_task)); st->task.pre_select = status_pre_select; - st->task.post_select = status_post_select; - st->task.private_data = st; st->sa_time_diff_sign = 1; st->clock_diff_count = conf.clock_diff_count_arg; + st->current_audio_format_num = -1; sprintf(st->task.status, "status task"); } @@ -1105,7 +1124,39 @@ static void set_initial_status(void) return; } if (strcmp(conf.mode_arg, "on")) - PARA_WARNING_LOG("%s", "invalid mode\n"); + PARA_WARNING_LOG("invalid mode\n"); +} + +__noreturn static void print_help_and_die(void) +{ + int d = conf.detailed_help_given; + const char **p = d? audiod_args_info_detailed_help + : audiod_args_info_help; + + printf_or_die("%s\n\n", AUDIOD_CMDLINE_PARSER_PACKAGE "-" + AUDIOD_CMDLINE_PARSER_VERSION); + printf_or_die("%s\n\n", audiod_args_info_usage); + for (; *p; p++) + printf_or_die("%s\n", *p); + print_receiver_helps(d); + print_filter_helps(d); + print_writer_helps(d); + exit(0); +} + +static void init_colors_or_die(void) +{ + int ret, i; + + if (!want_colors()) + return; + daemon_set_default_log_colors(); + daemon_set_flag(DF_COLOR_LOG); + for (i = 0; i < conf.log_color_given; i++) { + ret = daemon_set_log_color(conf.log_color_arg[i]); + if (ret < 0) + exit(EXIT_FAILURE); + } } /** @@ -1120,39 +1171,43 @@ static void set_initial_status(void) * */ int main(int argc, char *argv[]) { - char *config_file; int ret, i; - struct sched s; + static struct sched s; struct command_task command_task_struct, *cmd_task = &command_task_struct; - struct task audiod_task_struct, *audiod_task = &audiod_task_struct; + struct audiod_cmdline_parser_params params = { + .override = 0, + .initialize = 1, + .check_required = 0, + .check_ambiguity = 0, + .print_errors = 1 + }; valid_fd_012(); - audiod_cmdline_parser(argc, argv, &conf); + if (audiod_cmdline_parser_ext(argc, argv, &conf, ¶ms)) + exit(EXIT_FAILURE); HANDLE_VERSION_FLAG("audiod", conf); - para_drop_privileges(conf.user_arg, conf.group_arg); - config_file = configfile_exists(); - if (config_file) { - struct audiod_cmdline_parser_params params = { - .override = 0, - .initialize = 0, - .check_required = 0, - .check_ambiguity = 0 - - }; - if (audiod_cmdline_parser_config_file(config_file, &conf, ¶ms)) { - PARA_EMERG_LOG("%s", "parse error in config file\n"); - exit(EXIT_FAILURE); - } - free(config_file); + /* init receivers/filters/writers early to make help work */ + recv_init(); + filter_init(); + writer_init(); + if (conf.help_given || conf.detailed_help_given) + print_help_and_die(); + drop_privileges_or_die(conf.user_arg, conf.group_arg); + parse_config_or_die(); + init_colors_or_die(); + daemon_set_flag(DF_LOG_TIME); + daemon_set_flag(DF_LOG_HOSTNAME); + daemon_set_flag(DF_LOG_LL); + if (conf.logfile_given) { + daemon_set_logfile(conf.logfile_arg); + daemon_open_log_or_die(); } - if (conf.logfile_given) - logfile = open_log(conf.logfile_arg); - log_welcome("para_audiod", conf.loglevel_arg); - i = init_stream_io(); - if (i < 0) { - PARA_EMERG_LOG("init stream io error: %s\n", PARA_STRERROR(-i)); + ret = parse_stream_args(); + if (ret < 0) { + PARA_EMERG_LOG("%s\n", para_strerror(-ret)); exit(EXIT_FAILURE); } + log_welcome("para_audiod"); server_uptime(UPTIME_SET); set_initial_status(); FOR_EACH_SLOT(i) @@ -1160,23 +1215,20 @@ int main(int argc, char *argv[]) init_grabbing(); setup_signal_handling(); signal_setup_default(sig_task); - sig_task->task.event_handler = signal_event_handler; init_status_task(stat_task); init_command_task(cmd_task); - init_audiod_task(audiod_task); if (conf.daemon_given) - daemon_init(); + daemonize(); register_task(&sig_task->task); register_task(&cmd_task->task); register_task(&stat_task->task); - register_task(audiod_task); s.default_timeout.tv_sec = 0; s.default_timeout.tv_usec = 99 * 1000; - ret = sched(&s); + ret = schedule(&s); - PARA_EMERG_LOG("%s\n", PARA_STRERROR(-ret)); + PARA_EMERG_LOG("%s\n", para_strerror(-ret)); return EXIT_FAILURE; }