]> git.tuebingen.mpg.de Git - paraslash.git/commitdiff
Merge branch 't/libao'
authorAndre Noll <maan@systemlinux.org>
Thu, 5 May 2011 13:50:49 +0000 (15:50 +0200)
committerAndre Noll <maan@systemlinux.org>
Thu, 5 May 2011 13:56:52 +0000 (15:56 +0200)
FEATURES
Makefile.in
NEWS
ao_write.c [new file with mode: 0644]
configure.ac
error.h
ggo/.gitignore
ggo/ao_write.m4 [new file with mode: 0644]
web/manual.m4

index 5b5f4699d5123abc5919aaf45369bd804dfefde5..2839d79cc0393f4ee30afa9b86ed7f2a3c3d126f 100644 (file)
--- 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
index 8ecc1b8f6d606497aff2e9291e9b962d698cd28e..24b06fb97c7ba6302c14acc1f06da40b5e0783b6 100644 (file)
@@ -195,6 +195,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 3295738632ef423ca4b36f68f3bcdc533d873e38..4430343ec73e641f401263aee9e3dbefb2aa5993 100644 (file)
--- 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 (file)
index 0000000..bb60e19
--- /dev/null
@@ -0,0 +1,423 @@
+/*
+ * Copyright (C) 2011 Andre Noll <maan@systemlinux.org>
+ *
+ * Licensed under the GPL v2. For licencing details see COPYING.
+ */
+
+/** \file ao_write.c Paraslash's libao output plugin. */
+
+#include <pthread.h>
+#include <ao/ao.h>
+#include <regex.h>
+#include <stdbool.h>
+
+#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();
+}
+
index 32381c6b1ceec6dfd2586a1f73c0cdd4804ce855..8904281235c49caaef2211d008f399c80de19e26 100644 (file)
@@ -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 2e32c24044bd074cc9e311903adde6d85ac5a4c0..a24af894ef1ae49bd505ac82f093fde1e66dc6f1 100644 (file)
--- 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"), \
index a0824cf8cc7b82f109a4fd787d5cfdc4691b42f6..8b70bd4ff3cbedacd8a90dc62f8a19e9f5c28b19 100644 (file)
@@ -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 (file)
index 0000000..baccc57
--- /dev/null
@@ -0,0 +1,26 @@
+include(header.m4)
+<qu>
+
+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.
+"
+
+</qu>
index 5be893787fabd0f051655119af2ee645a42b8e5e..ea5ebeb7ac2cfb701fd0b4b676f525b7fbc5c74f 100644 (file)
@@ -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
 ~~~~~~~~~~~~
 
@@ -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
 ~~~~~~~~