From: Andre Noll Date: Sun, 15 May 2011 07:38:04 +0000 (+0200) Subject: Merge branch 't/const' X-Git-Tag: v0.4.7~9 X-Git-Url: http://git.tuebingen.mpg.de/?a=commitdiff_plain;h=e01b2ffa28b6770cd8771860e1e05b9c75beabd9;hp=3923f936294f9f900dc8c6a1c0fe84f0e2852dca;p=paraslash.git Merge branch 't/const' --- diff --git a/FEATURES b/FEATURES index 5b5f4699..2839d79c 100644 --- a/FEATURES +++ b/FEATURES @@ -6,7 +6,9 @@ Features * Runs on Linux, Mac OS, FreeBSD, NetBSD, Solaris and probably other Unixes * Mp3, ogg/vorbis, ogg/speex, aac (m4a) and wma support - * Local or remote http, dccp, and udp network audio streaming + * Native Alsa, OSS, CoreAudio output support + * Support for ESD, Pulseaudio, AIX, Solaris, IRIX through libao + * Local or remote http, dccp and udp network audio streaming * IPv6 support * Forward error correction allows receivers to recover from packet losses * Volume normalizer diff --git a/Makefile.in b/Makefile.in index ca79f8b9..cfc6909a 100644 --- a/Makefile.in +++ b/Makefile.in @@ -196,6 +196,9 @@ $(object_dir)/aac_afh.o: aac_afh.c | $(object_dir) $(object_dir)/gui%.o: gui%.c | $(object_dir) @[ -z "$(Q)" ] || echo 'CC $<' $(Q) $(CC) -c -o $@ $(CPPFLAGS) $(DEBUG_CPPFLAGS) @curses_cppflags@ $< +$(object_dir)/ao_write.o: ao_write.c | $(object_dir) + @[ -z "$(Q)" ] || echo 'CC $<' + $(Q) $(CC) -c -o $@ $(CPPFLAGS) $(DEBUG_CPPFLAGS) @ao_cppflags@ $< $(object_dir)/%.cmdline.o: $(cmdline_dir)/%.cmdline.c $(cmdline_dir)/%.cmdline.h | $(object_dir) @[ -z "$(Q)" ] || echo 'CC $<' diff --git a/NEWS b/NEWS index 32957386..4430343e 100644 --- a/NEWS +++ b/NEWS @@ -2,6 +2,8 @@ 0.4.7 (to be announced) "infinite rollback" ------------------------------------------- + - Support for ESD, Pulseaudio, AIX, Solaris, IRIX and other + platforms through the libao audio library. - configure: improved options for ogg/vorbis/speex. - The git version reported by --version always matches HEAD. - The autogen script detects the number of processors and diff --git a/ao_write.c b/ao_write.c new file mode 100644 index 00000000..bb60e191 --- /dev/null +++ b/ao_write.c @@ -0,0 +1,423 @@ +/* + * Copyright (C) 2011 Andre Noll + * + * Licensed under the GPL v2. For licencing details see COPYING. + */ + +/** \file ao_write.c Paraslash's libao output plugin. */ + +#include +#include +#include +#include + +#include "para.h" +#include "fd.h" +#include "string.h" +#include "list.h" +#include "sched.h" +#include "ggo.h" +#include "buffer_tree.h" +#include "write.h" +#include "write_common.h" +#include "ao_write.cmdline.h" +#include "error.h" + +struct private_aow_data { + ao_device *dev; + int bytes_per_frame; + + pthread_t thread; + pthread_attr_t attr; + pthread_mutex_t mutex; + pthread_cond_t data_available; + struct btr_node *thread_btrn; +}; + +static void aow_close(struct writer_node *wn) +{ + struct private_aow_data *pawd = wn->private_data; + + if (!pawd) + return; + ao_close(pawd->dev); + free(pawd); + wn->private_data = NULL; + ao_shutdown(); +} + +static void aow_pre_select(struct sched *s, struct task *t) +{ + struct writer_node *wn = container_of(t, struct writer_node, task); + int ret = btr_node_status(wn->btrn, wn->min_iqs, BTR_NT_LEAF); + + if (ret == 0) + return; + sched_min_delay(s); +} + +static int aow_set_sample_format(unsigned sample_rate, unsigned channels, + int sample_format, ao_sample_format *result) +{ + memset(result, 0, sizeof(*result)); + switch (sample_format) { + case SF_U8: + case SF_U16_LE: + case SF_U16_BE: + return -E_AO_BAD_SAMPLE_FORMAT; + case SF_S8: + /* no need to set byte_format */ + result->bits = 8; + break; + case SF_S16_LE: + result->bits = 16; + result->byte_format = AO_FMT_LITTLE; + break; + case SF_S16_BE: + result->bits = 16; + result->byte_format = AO_FMT_BIG; + break; + default: + PARA_EMERG_LOG("bug: invalid sample format\n"); + exit(EXIT_FAILURE); + } + result->channels = channels; + result->rate = sample_rate; + return 1; +} + +static int aow_open_device(int id, ao_sample_format *asf, ao_option *options, + ao_device **result) +{ + const char *msg; + ao_device *dev = ao_open_live(id, asf, options); + + if (dev) { + *result = dev; + return 1; + } + switch (errno) { + case AO_ENODRIVER: + msg = "No driver corresponds to driver_id"; + break; + case AO_ENOTLIVE: + msg = "This driver is not a live output device"; + break; + case AO_EBADOPTION: + msg = "A valid option key has an invalid value"; + break; + case AO_EOPENDEVICE: + msg = "Cannot open the device"; + break; + case AO_EFAIL: + msg = "General libao error"; + break; + default: + msg = "Unknown ao error"; + break; + } + PARA_ERROR_LOG("%s\n", msg); + return -E_AO_OPEN_LIVE; +} + +static int aow_init(struct writer_node *wn, unsigned sample_rate, + unsigned channels, int sample_format) +{ + int id, ret, i; + ao_option *aoo = NULL; + ao_sample_format asf; + ao_info *info; + struct private_aow_data *pawd = para_malloc(sizeof(*pawd)); + struct ao_write_args_info *conf = wn->conf; + + ao_initialize(); + if (conf->driver_given) { + ret = -E_AO_BAD_DRIVER; + id = ao_driver_id(conf->driver_arg); + } else { + ret = -E_AO_DEFAULT_DRIVER; + id = ao_default_driver_id(); + } + if (id < 0) + goto fail; + info = ao_driver_info(id); + assert(info && info->short_name); + if (info->type == AO_TYPE_FILE) { + ret = -E_AO_FILE_NOT_SUPP; + goto fail; + } + PARA_INFO_LOG("using %s driver\n", info->short_name); + for (i = 0; i < conf->ao_option_given; i++) { + char *o = para_strdup(conf->ao_option_arg[i]), *value; + + ret = -E_AO_BAD_OPTION; + value = strchr(o, ':'); + if (!value) { + free(o); + goto fail; + } + *value = '\0'; + value++; + PARA_INFO_LOG("appending option: key=%s, value=%s\n", o, value); + ret = ao_append_option(&aoo, o, value); + free(o); + if (ret == 0) { + ret = -E_AO_APPEND_OPTION; + goto fail; + } + } + ret = aow_set_sample_format(sample_rate, channels, sample_format, &asf); + if (ret < 0) + goto fail; + if (sample_format == SF_S8 || sample_format == SF_U8) + pawd->bytes_per_frame = channels; + else + pawd->bytes_per_frame = channels * 2; + ret = aow_open_device(id, &asf, aoo, &pawd->dev); + if (ret < 0) + goto fail; + PARA_INFO_LOG("successfully opened %s\n", info->short_name); + wn->private_data = pawd; + return 1; +fail: + free(pawd); + return ret; +} + +__noreturn static void *aow_play(void *priv) +{ + struct writer_node *wn = priv; + struct private_aow_data *pawd = wn->private_data; + struct btr_node *btrn = pawd->thread_btrn; + size_t frames, bytes; + char *data; + int ret; + + for (;;) { + /* + * Lock mutex and wait for signal. pthread_cond_wait() will + * automatically and atomically unlock mutex while it waits. + */ + pthread_mutex_lock(&pawd->mutex); + for (;;) { + ret = btr_node_status(btrn, wn->min_iqs, BTR_NT_LEAF); + if (ret < 0) + goto unlock; + if (ret > 0) { + btr_merge(btrn, wn->min_iqs); + bytes = btr_next_buffer(btrn, &data); + frames = bytes / pawd->bytes_per_frame; + if (frames > 0) + break; + /* eof and less than a single frame available */ + ret = -E_WRITE_COMMON_EOF; + goto unlock; + } + //PARA_CRIT_LOG("waiting for data\n"); + //usleep(1000); + //pthread_mutex_unlock(&pawd->mutex); + pthread_cond_wait(&pawd->data_available, &pawd->mutex); + } + pthread_mutex_unlock(&pawd->mutex); + assert(frames > 0); + bytes = frames * pawd->bytes_per_frame; + ret = -E_AO_PLAY; + if (ao_play(pawd->dev, data, bytes) == 0) /* failure */ + goto out; + btr_consume(btrn, bytes); + } +unlock: + pthread_mutex_unlock(&pawd->mutex); +out: + assert(ret < 0); + PARA_NOTICE_LOG("%s\n", para_strerror(-ret)); + pthread_exit(NULL); +} + +static int aow_create_thread(struct writer_node *wn) +{ + struct private_aow_data *pawd = wn->private_data; + int ret; + const char *msg; + + /* initialize with default attributes */ + msg = "could not init mutex"; + ret = pthread_mutex_init(&pawd->mutex, NULL); + if (ret < 0) + goto fail; + + msg = "could not initialize condition variable"; + ret = pthread_cond_init(&pawd->data_available, NULL); + if (ret < 0) + goto fail; + + msg = "could not initialize thread attributes"; + ret = pthread_attr_init(&pawd->attr); + if (ret < 0) + goto fail; + + /* schedule this thread under the real-time policy SCHED_FIFO */ + msg = "could not set sched policy"; + ret = pthread_attr_setschedpolicy(&pawd->attr, SCHED_FIFO); + if (ret < 0) + goto fail; + + msg = "could not set detach state to joinable"; + ret = pthread_attr_setdetachstate(&pawd->attr, PTHREAD_CREATE_JOINABLE); + if (ret < 0) + goto fail; + + msg = "could not create thread"; + ret = pthread_create(&pawd->thread, &pawd->attr, aow_play, wn); + if (ret < 0) + goto fail; + return 1; +fail: + PARA_ERROR_LOG("%s (%s)\n", msg, strerror(ret)); + return -E_AO_PTHREAD; +} + +static void aow_post_select(__a_unused struct sched *s, + struct task *t) +{ + struct writer_node *wn = container_of(t, struct writer_node, task); + struct btr_node *btrn = wn->btrn; + struct private_aow_data *pawd = wn->private_data; + int ret; + + if (!pawd) { + int32_t rate, ch, format; + struct btr_node_description bnd; + + ret = btr_node_status(btrn, wn->min_iqs, BTR_NT_LEAF); + if (ret < 0) + goto remove_btrn; + if (ret == 0) + return; + get_btr_sample_rate(btrn, &rate); + get_btr_channels(btrn, &ch); + get_btr_sample_format(btrn, &format); + ret = aow_init(wn, rate, ch, format); + if (ret < 0) + goto remove_btrn; + pawd = wn->private_data; + + /* set up thread btr node */ + bnd.name = "ao_thread_btrn"; + bnd.parent = btrn; + bnd.child = NULL; + bnd.handler = NULL; + bnd.context = pawd; + pawd->thread_btrn = btr_new_node(&bnd); + wn->private_data = pawd; + + ret = aow_create_thread(wn); + if (ret < 0) + goto remove_thread_btrn; + return; + } + pthread_mutex_lock(&pawd->mutex); + ret = btr_node_status(btrn, wn->min_iqs, BTR_NT_LEAF); + if (ret > 0) { + btr_pushdown(btrn); + pthread_cond_signal(&pawd->data_available); + } + pthread_mutex_unlock(&pawd->mutex); + if (ret >= 0) + goto out; + pthread_mutex_lock(&pawd->mutex); + btr_remove_node(btrn); + btrn = NULL; + PARA_INFO_LOG("waiting for thread to terminate\n"); + pthread_cond_signal(&pawd->data_available); + pthread_mutex_unlock(&pawd->mutex); + pthread_join(pawd->thread, NULL); +remove_thread_btrn: + btr_remove_node(pawd->thread_btrn); + btr_free_node(pawd->thread_btrn); +remove_btrn: + if (btrn) + btr_remove_node(btrn); +out: + t->error = ret; +} + +__malloc static void *aow_parse_config_or_die(const char *options) +{ + struct ao_write_args_info *conf = para_calloc(sizeof(*conf)); + + /* exits on errors */ + ao_cmdline_parser_string(options, conf, "ao_write"); + return conf; +} + +static void aow_free_config(void *conf) +{ + ao_cmdline_parser_free(conf); +} + +/** + * The init function of the ao writer. + * + * \param w Pointer to the writer to initialize. + * + * \sa struct writer. + */ +void ao_write_init(struct writer *w) +{ + struct ao_write_args_info dummy; + int i, j, num_drivers, num_lines; + ao_info **driver_list; + char **dh; /* detailed help */ + + ao_cmdline_parser_init(&dummy); + w->close = aow_close; + w->pre_select = aow_pre_select; + w->post_select = aow_post_select; + w->parse_config_or_die = aow_parse_config_or_die; + w->free_config = aow_free_config; + w->shutdown = NULL; + w->help = (struct ggo_help) { + .short_help = ao_write_args_info_help, + }; + /* create detailed help containing all supported drivers/options */ + for (i = 0; ao_write_args_info_detailed_help[i]; i++) + ; /* nothing */ + num_lines = i; + dh = para_malloc((num_lines + 3) * sizeof(char *)); + for (i = 0; i < num_lines; i++) + dh[i] = para_strdup(ao_write_args_info_detailed_help[i]); + dh[num_lines++] = para_strdup("libao drivers available on this host:"); + dh[num_lines++] = para_strdup("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"); + + ao_initialize(); + driver_list = ao_driver_info_list(&num_drivers); + + for (i = 0; i < num_drivers; i++) { + ao_info *info = driver_list[i]; + char *keys = NULL, *tmp = NULL; + + if (info->type == AO_TYPE_FILE) + continue; + for (j = 0; j < info->option_count; j++) { + tmp = make_message("%s%s%s", keys? keys : "", + keys? ", " : "", + info->options[j]); + free(keys); + keys = tmp; + } + dh = para_realloc(dh, (num_lines + 6) * sizeof(char *)); + dh[num_lines++] = make_message("%s: %s", info->short_name, info->name); + dh[num_lines++] = make_message("priority: %d", info->priority); + dh[num_lines++] = make_message("keys: %s", keys? keys : "[none]"); + dh[num_lines++] = make_message("comment: %s", info->comment? + info->comment : "[none]"); + dh[num_lines++] = para_strdup(NULL); + free(keys); + } + dh[num_lines] = NULL; + w->help.detailed_help = (const char **)dh; + ao_cmdline_parser_free(&dummy); + ao_shutdown(); +} + diff --git a/audiod.c b/audiod.c index 9f664e17..6a4c9dbe 100644 --- a/audiod.c +++ b/audiod.c @@ -429,22 +429,15 @@ static void kill_btrn(struct btr_node *btrn, struct task *t, int error) static void kill_all_decoders(int error) { - int i, j; + int i; FOR_EACH_SLOT(i) { - struct slot_info *s = &slot[i]; - struct audio_format_info *a; + struct slot_info *s = slot + i; if (s->format < 0) continue; - a = afi + s->format; - if (s->wns) - for (j = 0; j < a->num_writers; j++) - kill_btrn(s->wns[j].btrn, &s->wns[j].task, error); - if (s->fns) - for (j = 0; j < a->num_writers; j++) - kill_btrn(s->fns[j].btrn, &s->wns[j].task, error); - if (s->receiver_node) - kill_btrn(s->receiver_node->btrn, &s->receiver_node->task, + if (!s->receiver_node) + continue; + kill_btrn(s->receiver_node->btrn, &s->receiver_node->task, error); } } diff --git a/configure.ac b/configure.ac index 32381c6b..89042812 100644 --- a/configure.ac +++ b/configure.ac @@ -727,6 +727,67 @@ fi CPPFLAGS="$OLD_CPPFLAGS" LDFLAGS="$OLD_LDFLAGS" LIBS="$OLD_LIBS" +########################################################################### libao +OLD_CPPFLAGS="$CPPFLAGS" +OLD_LD_FLAGS="$LDFLAGS" +OLD_LIBS="$LIBS" + +have_ao="yes" +AC_ARG_WITH(ao_headers, [AC_HELP_STRING(--with-ao-headers=dir, + [look for ao/ao.h also in dir])]) +if test -n "$with_ao_headers"; then + ao_cppflags="-I$with_ao_headers" + CPPFLAGS="$CPPFLAGS $ao_cppflags" +fi +AC_ARG_WITH(ao_libs, [AC_HELP_STRING(--with-ao-libs=dir, + [look for libao also in dir])]) +if test -n "$with_ao_libs"; then + ao_libs="-L$with_ao_libs" + LDFLAGS="$LDFLAGS $ao_libs" +fi +msg="no libao support for para_audiod/para_write" +AC_CHECK_HEADERS([ao/ao.h], [ + ], [ + have_ao="no" + AC_MSG_WARN([ao.h not found, $msg]) +]) +if test "$have_ao" = "yes"; then + AC_CHECK_LIB([ao], [ao_initialize], [], [ + have_ao="no" + AC_MSG_WARN([ao lib not found or not working, $msg]) + ]) +fi +if test "$have_ao" = "yes"; then + AC_CHECK_HEADERS([pthread.h], [ + ], [ + have_ao="no" + AC_MSG_WARN([pthread.h not found, $msg]) + ]) +fi +if test "$have_ao" = "yes"; then + AC_CHECK_LIB([pthread], [pthread_create], [], [ + have_ao="no" + AC_MSG_WARN([pthread lib not found or not working, $msg]) + ]) +fi +if test "$have_ao" = "yes"; then + all_errlist_objs="$all_errlist_objs ao_write" + audiod_errlist_objs="$audiod_errlist_objs ao_write" + audiod_cmdline_objs="$audiod_cmdline_objs add_cmdline(ao_write)" + audiod_ldflags="$audiod_ldflags -lao -lpthread" + + write_errlist_objs="$write_errlist_objs ao_write" + write_cmdline_objs="$write_cmdline_objs add_cmdline(ao_write)" + write_ldflags="$write_ldflags $ao_libs -lao -lpthread" + writers="$writers ao" + AC_SUBST(ao_cppflags) +fi + +CPPFLAGS="$OLD_CPPFLAGS" +LDFLAGS="$OLD_LDFLAGS" +LIBS="$OLD_LIBS" + + AC_SUBST(install_sh, [$INSTALL]) AC_CONFIG_FILES([Makefile]) diff --git a/error.h b/error.h index 2e32c240..a24af894 100644 --- a/error.h +++ b/error.h @@ -104,6 +104,18 @@ extern const char **para_errlist[]; PARA_ERROR(BAD_SAMPLERATE, "sample rate not supported"), \ +#define AO_WRITE_ERRORS \ + PARA_ERROR(AO_DEFAULT_DRIVER, "ao: no usable output device"), \ + PARA_ERROR(AO_BAD_DRIVER, "ao: invalid driver"), \ + PARA_ERROR(AO_BAD_OPTION, "ao option is not of type key:value"), \ + PARA_ERROR(AO_APPEND_OPTION, "ao append option: memory allocation failure"), \ + PARA_ERROR(AO_OPEN_LIVE, "ao: could not open audio device"), \ + PARA_ERROR(AO_FILE_NOT_SUPP, "ao: file io drivers not supported"), \ + PARA_ERROR(AO_PLAY, "ao_play() failed"), \ + PARA_ERROR(AO_BAD_SAMPLE_FORMAT, "ao: unsigned sample formats not supported"), \ + PARA_ERROR(AO_PTHREAD, "pthread error"), \ + + #define COMPRESS_FILTER_ERRORS \ PARA_ERROR(COMPRESS_SYNTAX, "syntax error in compress filter config"), \ PARA_ERROR(COMPRESS_EOF, "compress: end of file"), \ diff --git a/fd.c b/fd.c index 6f9266f4..ea16bdda 100644 --- a/fd.c +++ b/fd.c @@ -563,6 +563,9 @@ out: int para_munmap(void *start, size_t length) { int err; + + if (!start) + return 0; if (munmap(start, length) >= 0) return 1; err = errno; diff --git a/ggo/.gitignore b/ggo/.gitignore index a0824cf8..8b70bd4f 100644 --- a/ggo/.gitignore +++ b/ggo/.gitignore @@ -8,3 +8,4 @@ gui.ggo recv.ggo server.ggo write.ggo +ao_write.ggo diff --git a/ggo/ao_write.m4 b/ggo/ao_write.m4 new file mode 100644 index 00000000..baccc57f --- /dev/null +++ b/ggo/ao_write.m4 @@ -0,0 +1,26 @@ +include(header.m4) + + +option "driver" d +#~~~~~~~~~~~~~~~~ +"Select a output driver by name" +string typestr = "name" +optional +details = " + If this is not given, the driver with the highest priority + (see below) will be used. +" + +option "ao-option" o +#~~~~~~~~~~~~~~~~~~~ +"Pass a key-value pair to the libao driver" +string typestr = "key:value" +optional +multiple +details = " + For each time this option is given, the supplied key-value + pair is appended to the list of options for the driver. Invalid + keys are silently ignored. +" + + diff --git a/web/manual.m4 b/web/manual.m4 index 30fe922f..ea5ebeb7 100644 --- a/web/manual.m4 +++ b/web/manual.m4 @@ -251,6 +251,10 @@ Optional: Linux, you'll need to have ALSA's development package libasound2-dev installed. + - XREFERENCE(http://downloads.xiph.org/releases/ao/, + libao). Needed to build the ao writer (ESD, PulseAudio,...). + Debian package: libao-dev. + Installation ~~~~~~~~~~~~ @@ -1533,7 +1537,7 @@ From these observations it is clear that there are three different FEC parameters: The slice size, the number of data slices k, and the total number of slices n. It is crucial to choose the slice size such that no fragmentation of network packets takes place because -FEC only guards against losses and reodering but fails if slices are +FEC only guards against losses and reordering but fails if slices are received partially. FEC decoding in paralash is performed through the fecdec filter which @@ -1678,6 +1682,11 @@ write the PCM data to a file on the file system rather than playing it through a sound device. It is supported on all platforms and is always compiled in. +*AO*. _Libao_ is a cross-platform audio library which supports a wide +variety of platforms including PulseAudio (gnome), ESD (Enlightened +Sound Daemon), AIX, Solaris and IRIX. The ao writer plays audio +through an output plugin of libao. + Examples ~~~~~~~~ @@ -2032,7 +2041,7 @@ detection of duplicate or reordered packets. Being a connectionless protocol, only minimal internal state about the connection is maintained, which means that there is no protection against packet loss or network congestion. Error checking and correction (if at all) -are performed in the application.' +are performed in the application. *DCCP*. The _Datagram Congestion Control Protocol_ combines the connection-oriented state maintenance known from TCP with the