common/pulse-glue: added libmurphy-pulse, a PulseAudio murphy superloop.
authorKrisztian Litkey <kli@iki.fi>
Fri, 8 Jun 2012 22:34:04 +0000 (01:34 +0300)
committerKrisztian Litkey <kli@iki.fi>
Fri, 8 Jun 2012 22:34:04 +0000 (01:34 +0300)
libmurphy-pulse provides the necessary gluing code to enable the murphy
mainloop to be pumped by a PulseAudio mainloop.

configure.ac
src/Makefile.am
src/common/pulse-glue.c [new file with mode: 0644]
src/common/pulse-glue.h [new file with mode: 0644]
src/common/tests/Makefile.am
src/common/tests/mainloop-test.c

index 6bada80..0c04757 100644 (file)
@@ -117,11 +117,40 @@ else
     AC_MSG_NOTICE([D-Bus support is disabled.])
 fi
 
+if test "$enable_dbus" = "yes"; then
+    AC_DEFINE([DBUS_ENABLED], 1, [Enable D-BUS support ?])
+fi
+
 AM_CONDITIONAL(DBUS_ENABLED, [test "$enable_dbus" = "yes"])
 AC_SUBST(DBUS_ENABLED)
 AC_SUBST(DBUS_CFLAGS)
 AC_SUBST(DBUS_LIBS)
 
+# Check if PulseAudio mainloop support was enabled.
+AC_ARG_ENABLE(pulse,
+              [  --enable-pulse        enable PulseAudio mainloop support],
+             [enable_pulse=$enableval], [enable_pulse=auto])
+
+if test "$enable_pulse" != "no"; then
+    PKG_CHECK_MODULES(PULSE, libpulse >= 0.9.22,
+                            [have_pulse=yes], [have_pulse=no])
+    if test "$have_pulse" = "no" -a "$enable_pulse" != "yes"; then
+        AC_MSG_ERROR([PulseAudio development libraries not found.])
+    else
+        enable_pulse="$have_pulse"
+    fi
+else
+    AC_MSG_NOTICE([PulseAudio mainloop support is disabled.])
+fi
+
+if test "$enable_pulse" = "yes"; then
+    AC_DEFINE([PULSE_ENABLED], 1, [Enable PulseAudio mainloop support ?])
+fi
+AM_CONDITIONAL(PULSE_ENABLED, [test "$enable_pulse" = "yes"])
+AC_SUBST(PULSE_ENABLED)
+AC_SUBST(PULSE_CFLAGS)
+AC_SUBST(PULSE_LIBS)
+
 # Set up murphy CFLAGS and LIBS.
 MURPHY_CFLAGS="$GLIB_CFLAGS $DBUS_CFLAGS"
 MURPHY_LIBS="$GLIB_LIBS $DBUS_LIBS"
@@ -271,6 +300,7 @@ AC_CONFIG_FILES([build-aux/shave
                 src/daemon/tests/Makefile
                 src/common/murphy-common.pc
                 src/common/murphy-dbus.pc
+                src/common/murphy-pulse.pc
                 src/core/murphy-core.pc
                  src/murphy-db/Makefile
                  src/murphy-db/mdb/Makefile
@@ -290,6 +320,7 @@ AC_OUTPUT
 echo "----- configuration -----"
 echo "Extra C warnings flags: $WARNING_CFLAGS"
 echo "D-Bus support: $enable_dbus"
+echo "PulseAudio mainloop support: $enable_pulse"
 echo "Plugins:"
 echo "  - linked-in:"
 for plugin in ${INTERNAL_PLUGINS:-none}; do
index 2d5a647..a9b5233 100644 (file)
@@ -23,7 +23,8 @@ libmurphy_commonh_ladir      =                \
                $(includedir)/murphy
 
 libmurphy_commonh_la_HEADERS =         \
-               common.h
+               common.h                \
+               config.h
 
 libmurphy_common_ladir      =          \
                $(includedir)/murphy/common
@@ -167,6 +168,47 @@ clean-linker-script::
 endif
 
 ###################################
+# murphy pulse glue library
+#
+
+if PULSE_ENABLED
+lib_LTLIBRARIES += libmurphy-pulse.la
+EXTRA_DIST      += common/murphy-pulse.pc
+pkgconfig_DATA  += common/murphy-pulse.pc
+
+libmurphy_pulse_ladir      =           \
+               $(includedir)/murphy/common
+
+libmurphy_pulse_la_HEADERS =           \
+               common/pulse-glue.h
+
+libmurphy_pulse_la_SOURCES =           \
+               common/pulse-glue.c
+
+libmurphy_pulse_la_CFLAGS  =           \
+               $(AM_CFLAGS)            \
+               $(PULSE_CFLAGS)
+
+libmurphy_pulse_la_LDFLAGS =           \
+               -Wl,-version-script=linker-script.pulse \
+               -version-info @MURPHY_VERSION_INFO@
+
+libmurphy_pulse_la_LIBADD  =
+
+libmurphy_pulse_la_DEPENDENCIES = linker-script.pulse
+
+libpulseincludedir      = $(includedir)/murphy/pulse
+libpulseinclude_HEADERS = $(libmurphy_pulse_la_HEADERS)
+
+# linker script generation
+linker-script.pulse: $(libmurphy_pulse_la_HEADERS)
+       $(QUIET_GEN)$(top_builddir)/build-aux/gen-linker-script -q -o $@ $^
+
+clean-linker-script::
+       -rm -f linker-script.pulse
+endif
+
+###################################
 # murphy plugins
 #
 
diff --git a/src/common/pulse-glue.c b/src/common/pulse-glue.c
new file mode 100644 (file)
index 0000000..1669245
--- /dev/null
@@ -0,0 +1,310 @@
+
+#include <pulse/mainloop-api.h>
+#include <pulse/timeval.h>
+
+#include <murphy/common/mm.h>
+#include <murphy/common/mainloop.h>
+
+
+
+
+typedef struct {
+    mrp_mainloop_t  *ml;
+    pa_mainloop_api *pa;
+} pulse_glue_t;
+
+
+typedef struct {
+    pa_io_event  *pa_io;
+    void        (*cb)(void *glue_data,
+                     void *id, int fd, mrp_io_event_t events,
+                     void *user_data);
+    void         *user_data;
+    void         *glue_data;
+} io_t;
+
+
+typedef struct {
+    pa_time_event  *pa_t;
+    void          (*cb)(void *glue_data, void *id, void *user_data);
+    void           *user_data;
+    void           *glue_data;
+} tmr_t;
+
+
+typedef struct {
+    pa_defer_event  *pa_d;
+    void           (*cb)(void *glue_data, void *id, void *user_data);
+    void            *user_data;
+    void            *glue_data;
+} dfr_t;
+
+
+static void *add_io(void *glue_data, int fd, mrp_io_event_t events,
+                   void (*cb)(void *glue_data, void *id, int fd,
+                              mrp_io_event_t events, void *user_data),
+                   void *user_data);
+static void  del_io(void *glue_data, void *id);
+
+static void *add_timer(void *glue_data, unsigned int msecs,
+                      void (*cb)(void *glue_data, void *id, void *user_data),
+                      void *user_data);
+static void  del_timer(void *glue_data, void *id);
+static void  mod_timer(void *glue_data, void *id, unsigned int msecs);
+
+static void *add_defer(void *glue_data,
+                      void (*cb)(void *glue_data, void *id, void *user_data),
+                      void *user_data);
+static void  del_defer(void *glue_data, void *id);
+static void  mod_defer(void *glue_data, void *id, int enabled);
+
+
+static void io_cb(pa_mainloop_api *pa, pa_io_event *e, int fd,
+                 pa_io_event_flags_t mask, void *user_data)
+{
+    io_t           *io     = (io_t *)user_data;
+    mrp_io_event_t  events = MRP_IO_EVENT_NONE;
+
+    MRP_UNUSED(pa);
+    MRP_UNUSED(e);
+
+    if (mask & PA_IO_EVENT_INPUT)  events |= MRP_IO_EVENT_IN;
+    if (mask & PA_IO_EVENT_OUTPUT) events |= MRP_IO_EVENT_OUT;
+    if (mask & PA_IO_EVENT_HANGUP) events |= MRP_IO_EVENT_HUP;
+    if (mask & PA_IO_EVENT_ERROR)  events |= MRP_IO_EVENT_ERR;
+    
+    io->cb(io->glue_data, io, fd, events, io->user_data);
+}
+
+
+static void *add_io(void *glue_data, int fd, mrp_io_event_t events,
+                   void (*cb)(void *glue_data, void *id, int fd,
+                              mrp_io_event_t events, void *user_data),
+                   void *user_data)
+{
+    pulse_glue_t        *glue = (pulse_glue_t *)glue_data;
+    pa_mainloop_api     *pa   = glue->pa;
+    pa_io_event_flags_t  mask = PA_IO_EVENT_NULL;
+    io_t                *io;
+
+    io = mrp_allocz(sizeof(*io));
+
+    if (io != NULL) {
+       if (events & MRP_IO_EVENT_IN)  mask |= PA_IO_EVENT_INPUT;
+       if (events & MRP_IO_EVENT_OUT) mask |= PA_IO_EVENT_OUTPUT;
+       if (events & MRP_IO_EVENT_HUP) mask |= PA_IO_EVENT_HANGUP;
+       if (events & MRP_IO_EVENT_ERR) mask |= PA_IO_EVENT_ERROR;
+
+       io->pa_io = pa->io_new(pa, fd, mask, io_cb, io);
+
+       if (io->pa_io != NULL) {
+           io->cb        = cb;
+           io->user_data = user_data;
+           io->glue_data = glue_data;
+
+           return io;
+       }
+       else
+           mrp_free(io);
+    }
+
+    return NULL;
+}
+
+
+static void del_io(void *glue_data, void *id)
+{
+    pulse_glue_t    *glue = (pulse_glue_t *)glue_data;
+    pa_mainloop_api *pa   = glue->pa;
+    io_t            *io   = (io_t *)id;
+    
+    pa->io_free(io->pa_io);
+    mrp_free(io);
+}
+
+
+static void timer_cb(pa_mainloop_api *pa, pa_time_event *e,
+                    const struct timeval *tv, void *user_data)
+{
+    tmr_t *t = (tmr_t *)user_data;
+
+    MRP_UNUSED(pa);
+    MRP_UNUSED(e);
+    MRP_UNUSED(tv);
+    
+    t->cb(t->glue_data, t, t->user_data);
+}
+
+
+static void *add_timer(void *glue_data, unsigned int msecs,
+                      void (*cb)(void *glue_data, void *id, void *user_data),
+                      void *user_data)
+{
+    pulse_glue_t    *glue = (pulse_glue_t *)glue_data;
+    pa_mainloop_api *pa   = glue->pa;
+    struct timeval   tv;
+    tmr_t           *t;
+
+    t = mrp_allocz(sizeof(*t));
+
+    if (t != NULL) {
+       pa_gettimeofday(&tv);
+
+       tv.tv_sec  += msecs / 1000;
+       tv.tv_usec += 1000 * (msecs % 1000);
+
+       t->pa_t = pa->time_new(pa, &tv, timer_cb, t);
+
+       if (t->pa_t != NULL) {
+           t->cb        = cb;
+           t->user_data = user_data;
+           t->glue_data = glue_data;
+
+           return t;
+       }
+       else
+           mrp_free(t);
+    }
+
+    return NULL;
+}
+
+
+static void del_timer(void *glue_data, void *id)
+{
+    pulse_glue_t    *glue = (pulse_glue_t *)glue_data;
+    pa_mainloop_api *pa   = glue->pa;
+    tmr_t           *t    = (tmr_t *)id;
+    
+    pa->time_free(t->pa_t);
+    mrp_free(t);
+}
+
+
+static void mod_timer(void *glue_data, void *id, unsigned int msecs)
+{
+    pulse_glue_t    *glue = (pulse_glue_t *)glue_data;
+    pa_mainloop_api *pa   = glue->pa;
+    tmr_t           *t    = (tmr_t *)id;
+    struct timeval   tv;
+
+    if (t != NULL) {
+       pa_gettimeofday(&tv);
+
+       tv.tv_sec  += msecs / 1000;
+       tv.tv_usec += 1000 * (msecs % 1000);
+
+       pa->time_restart(t->pa_t, &tv);
+    }
+}
+
+
+void defer_cb(pa_mainloop_api *pa, pa_defer_event *e, void *user_data)
+{
+    dfr_t *d = (dfr_t *)user_data;
+
+    MRP_UNUSED(pa);
+    MRP_UNUSED(e);
+    
+    d->cb(d->glue_data, d, d->user_data);
+}
+
+
+static void *add_defer(void *glue_data,
+                      void (*cb)(void *glue_data, void *id, void *user_data),
+                      void *user_data)
+{
+    pulse_glue_t    *glue = (pulse_glue_t *)glue_data;
+    pa_mainloop_api *pa   = glue->pa;
+    dfr_t           *d;
+
+    d = mrp_allocz(sizeof(*d));
+
+    if (d != NULL) {
+       d->pa_d = pa->defer_new(pa, defer_cb, d);
+
+       if (d->pa_d != NULL) {
+           d->cb        = cb;
+           d->user_data = user_data;
+           d->glue_data = glue_data;
+
+           return d;
+       }
+       else
+           mrp_free(d);
+    }
+
+    return NULL;
+}
+
+
+static void del_defer(void *glue_data, void *id)
+{
+    pulse_glue_t    *glue = (pulse_glue_t *)glue_data;
+    pa_mainloop_api *pa   = glue->pa;
+    dfr_t           *d    = (dfr_t *)id;
+    
+    pa->defer_free(d->pa_d);
+    mrp_free(d);
+}
+
+
+static void mod_defer(void *glue_data, void *id, int enabled)
+{
+    pulse_glue_t    *glue = (pulse_glue_t *)glue_data;
+    pa_mainloop_api *pa   = glue->pa;
+    dfr_t           *d    = (dfr_t *)id;
+    
+    pa->defer_enable(d->pa_d, !!enabled);
+}
+
+
+static mrp_superloop_ops_t pa_ops = {
+    .add_io    = add_io,
+    .del_io    = del_io,
+    .add_timer = add_timer,
+    .del_timer = del_timer,
+    .mod_timer = mod_timer,
+    .add_defer = add_defer,
+    .del_defer = del_defer,
+    .mod_defer = mod_defer,
+};
+
+
+void *mrp_mainloop_register_with_pulse(mrp_mainloop_t *ml, pa_mainloop_api *pa)
+{
+    pulse_glue_t *glue;
+
+    glue = mrp_allocz(sizeof(*glue));
+
+    if (glue != NULL) {
+       glue->ml = ml;
+       glue->pa = pa;
+
+       if (mrp_set_superloop(ml, &pa_ops, glue))
+           return glue;
+       else
+           mrp_free(glue);
+    }
+
+    return NULL;
+}
+
+
+int mrp_mainloop_unregister_from_pulse(mrp_mainloop_t *ml, pa_mainloop_api *pa,
+                                      void *data)
+{
+    pulse_glue_t *glue = (pulse_glue_t *)data;
+    
+    if (glue->ml == ml && glue->pa == pa) {
+       if (mrp_clear_superloop(ml, &pa_ops, data)) {
+           mrp_free(glue);
+
+           return TRUE;
+       }
+    }
+    
+    return FALSE;
+}
+
+
diff --git a/src/common/pulse-glue.h b/src/common/pulse-glue.h
new file mode 100644 (file)
index 0000000..3621f88
--- /dev/null
@@ -0,0 +1,10 @@
+#ifndef __MURPHY_PULSE_H__
+#define __MURPHY_PULSE_H__
+
+/** Register the given murphy mainloop with the given pulse mainloop. */
+int mrp_mainloop_register_with_pulse(mrp_mainloop_t *ml, pa_mainloop_api *pa);
+
+/** Unrgister the given murphy mainloop from the given pulse mainloop. */
+int mrp_mainloop_unregister_from_pulse(mrp_mainloop_t *ml, pa_mainloop_api *pa);
+
+#endif /* __MURPHY_PULSE_H__ */
index 378d177..6c44010 100644 (file)
@@ -19,6 +19,10 @@ hash_test_LDADD   = ../../libmurphy-common.la
 mainloop_test_SOURCES = mainloop-test.c
 mainloop_test_CFLAGS  = $(AM_CFLAGS) $(GLIB_CFLAGS) $(DBUS_CFLAGS)
 mainloop_test_LDADD   = ../../libmurphy-common.la $(GLIB_LIBS) $(DBUS_LIBS)
+if PULSE_ENABLED
+mainloop_test_CFLAGS += $(PULSE_CFLAGS)
+mainloop_test_LDADD  += ../../libmurphy-pulse.la $(PULSE_LIBS)
+endif
 
 # msg test
 msg_test_SOURCES = msg-test.c
index 7a63428..d5b7b4f 100644 (file)
@@ -1,9 +1,12 @@
 #include <stdio.h>
 #include <stdlib.h>
+#include <errno.h>
 #include <stdint.h>
 #include <string.h>
+#include <stdarg.h>
 #include <unistd.h>
 #include <signal.h>
+#include <getopt.h>
 #include <sys/time.h>
 #include <sys/types.h>
 #include <sys/wait.h>
 #include <glib.h>
 #include <dbus/dbus.h>
 
+#include <murphy/config.h>
 #include <murphy/common/macros.h>
 #include <murphy/common/mm.h>
 #include <murphy/common/mainloop.h>
 
+#ifdef PULSE_ENABLED
+#  include <pulse/mainloop.h>
+#  include <murphy/common/pulse-glue.h>
+#endif
 
 #define info(fmt, args...) do {                                \
        fprintf(stdout, "I: "fmt"\n" ,  ## args);       \
@@ -56,6 +64,14 @@ typedef struct {
     int ndbus_method;
     int ndbus_signal;
 
+    int         log_mask;
+    const char *log_target;
+
+#ifdef PULSE_ENABLED
+    pa_mainloop     *pa_main;
+    pa_mainloop_api *pa;
+#endif
+
     int nrunning;
     int runtime;
 } test_config_t;
@@ -497,6 +513,13 @@ static void check_quit(mrp_mainloop_t *ml, mrp_timer_t *timer, void *user_data)
 
     if (cfg.nrunning <= 0) {
        mrp_del_timer(timer);
+#ifdef PULSE_ENABLED
+       MRP_UNUSED(ml);
+
+       if (cfg.pa_main != NULL)
+           pa_mainloop_quit(cfg.pa_main, 0);
+       else
+#endif
        mrp_mainloop_quit(ml, 0);
     }
 }
@@ -1144,29 +1167,216 @@ static void check_dbus(void)
 #include "glib-pump.c"
 #include "dbus-pump.c"
 
-int main(int argc, char *argv[])
+
+
+static void config_set_defaults(test_config_t *cfg)
 {
-    mrp_mainloop_t *ml;
+    mrp_clear(cfg);
 
-    mrp_clear(&cfg);
+    cfg->nio     = 5;
+    cfg->ntimer  = 10;
+    cfg->nsignal = 5;
+    cfg->ngio    = 5;
+    cfg->ngtimer = 10;
 
-    cfg.nio     = 5;
-    cfg.ntimer  = 10;
-    cfg.nsignal = 5;
-    cfg.ngio    = 5;
-    cfg.ngtimer = 10;
+    cfg->ndbus_method = 10;
+    cfg->ndbus_signal = 10;
 
-    cfg.ndbus_method = 10;
-    cfg.ndbus_signal = 10;
+    cfg->log_mask   = MRP_LOG_UPTO(MRP_LOG_DEBUG);
+    cfg->log_target = MRP_LOG_TO_STDERR;
 
-    if (argc == 2)
-       cfg.runtime = (int)strtoul(argv[1], NULL, 10);
+    cfg->runtime = DEFAULT_RUNTIME;
+}
+
+
+static void print_usage(const char *argv0, int exit_code, const char *fmt, ...)
+{
+    va_list ap;
+    
+    if (fmt && *fmt) {
+        va_start(ap, fmt);
+        vprintf(fmt, ap);
+        va_end(ap);
+    }
+    
+    printf("usage: %s [options]\n\n"
+           "The possible options are:\n"
+          "  -r, --runtime                  how many seconds to run tests\n"
+           "  -i, --ios                      number of I/O watches\n"
+          "  -t, --timers                   number of timers\n"
+          "  -I, --glib-ios                 number of glib I/O watches\n"
+          "  -T, --glib-timers              number of glib timers\n"
+          "  -S, --dbus-signals             number of D-Bus signals\n"
+          "  -M, --dbus-methods             number of D-Bus methods\n"
+           "  -o, --log-target=TARGET        log target to use\n"
+           "      TARGET is one of stderr,stdout,syslog, or a logfile path\n"
+           "  -l, --log-level=LEVELS         logging level to use\n"
+           "      LEVELS is a comma separated list of info, error and warning\n"
+           "  -v, --verbose                  increase logging verbosity\n"
+           "  -d, --debug                    enable debug messages\n"
+#ifdef PULSE_ENABLED
+          "  -p, --pulse                    use pulse mainloop\n"
+#endif
+           "  -h, --help                     show help on usage\n",
+           argv0);
+    
+    if (exit_code < 0)
+       return;
     else
-       cfg.runtime = DEFAULT_RUNTIME;
+       exit(exit_code);
+}
+
+
+int parse_cmdline(test_config_t *cfg, int argc, char **argv)
+{
+#ifdef PULSE_ENABLED
+#   define PULSE_OPTION "p"
+#else
+#   define PULSE_OPTION ""
+#endif
+#   define OPTIONS "r:i:t:s:I:T:S:M:l:o:vdh"PULSE_OPTION
+    struct option options[] = {
+       { "runtime"     , required_argument, NULL, 'r' },
+       { "ios"         , required_argument, NULL, 'i' },
+       { "timers"      , required_argument, NULL, 't' },
+       { "signals"     , required_argument, NULL, 's' },
+       { "glib-ios"    , required_argument, NULL, 'I' },
+       { "glib-timers" , required_argument, NULL, 'T' },
+       { "dbus-signals", required_argument, NULL, 'S' },
+       { "dbus-methods", required_argument, NULL, 'M' },
+#ifdef PULSE_ENABLED
+       { "pulse-main"  , no_argument      , NULL, 'p' },
+#endif
+       { "log-level"   , required_argument, NULL, 'l' },
+       { "log-target"  , required_argument, NULL, 'o' },
+       { "verbose"     , optional_argument, NULL, 'v' },
+       { "debug"       , no_argument      , NULL, 'd' },
+       { "help"        , no_argument      , NULL, 'h' },
+       { NULL, 0, NULL, 0 }
+    };
+    char *end;
+    int   opt, debug;
+    
+    debug = FALSE;
+    config_set_defaults(cfg);
+    
+    while ((opt = getopt_long(argc, argv, OPTIONS, options, NULL)) != -1) {
+        switch (opt) {
+        case 'r':
+           cfg->runtime = (int)strtoul(optarg, &end, 10);
+           if (end && *end)
+               print_usage(argv[0], EINVAL,
+                           "invalid runtime length '%s'.", optarg);
+           break;
+
+        case 'i':
+           cfg->nio = (int)strtoul(optarg, &end, 10);
+           if (end && *end)
+               print_usage(argv[0], EINVAL,
+                           "invalid number of I/O watches '%s'.", optarg);
+           break;
+
+        case 't':
+           cfg->ntimer = (int)strtoul(optarg, &end, 10);
+           if (end && *end)
+               print_usage(argv[0], EINVAL,
+                           "invalid number of timers '%s'.", optarg);
+           break;
+
+        case 's':
+           cfg->nsignal = (int)strtoul(optarg, &end, 10);
+           if (end && *end)
+               print_usage(argv[0], EINVAL,
+                           "invalid number of signals '%s'.", optarg);
+           break;
+
+        case 'I':
+           cfg->ngio = (int)strtoul(optarg, &end, 10);
+           if (end && *end)
+               print_usage(argv[0], EINVAL,
+                           "invalid number of glib I/O watches '%s'.", optarg);
+           break;
+
+        case 'T':
+           cfg->ngtimer = (int)strtoul(optarg, &end, 10);
+           if (end && *end)
+               print_usage(argv[0], EINVAL,
+                           "invalid number of glib timers '%s'.", optarg);
+           break;
+
+        case 'S':
+           cfg->ndbus_signal = (int)strtoul(optarg, &end, 10);
+           if (end && *end)
+               print_usage(argv[0], EINVAL,
+                           "invalid number of DBUS signals '%s'.", optarg);
+           break;
+
+        case 'M':
+           cfg->ndbus_method = (int)strtoul(optarg, &end, 10);
+           if (end && *end)
+               print_usage(argv[0], EINVAL,
+                           "invalid number of DBUS methods '%s'.", optarg);
+           break;
+
+#ifdef PULSE_ENABLED
+       case 'p':
+           cfg->pa_main = pa_mainloop_new();
+           if (cfg->pa_main == NULL) {
+               mrp_log_error("Failed to create PulseAudio mainloop.");
+               exit(1);
+           }
+           cfg->pa = pa_mainloop_get_api(cfg->pa_main);
+           break;
+#endif
 
-    mrp_log_set_mask(MRP_LOG_UPTO(MRP_LOG_INFO));
-    mrp_log_set_target(MRP_LOG_TO_STDOUT);
+       case 'v':
+           cfg->log_mask <<= 1;
+           cfg->log_mask  |= 1;
+           break;
 
+       case 'l':
+           cfg->log_mask = mrp_log_parse_levels(optarg);
+           if (cfg->log_mask < 0)
+               print_usage(argv[0], EINVAL, "invalid log level '%s'", optarg);
+           break;
+
+       case 'o':
+           cfg->log_target = mrp_log_parse_target(optarg);
+           if (!cfg->log_target)
+               print_usage(argv[0], EINVAL, "invalid log target '%s'", optarg);
+           break;
+
+       case 'd':
+           debug = TRUE;
+           break;
+           
+       case 'h':
+           print_usage(argv[0], -1, "");
+           exit(0);
+           break;
+
+        default:
+           print_usage(argv[0], EINVAL, "invalid option '%c'", opt);
+       }
+    }
+
+    if (debug)
+       cfg->log_mask |= MRP_LOG_MASK_DEBUG;
+    
+    return TRUE;
+}
+
+
+int main(int argc, char *argv[])
+{
+    mrp_mainloop_t *ml;
+    int             retval;
+
+    mrp_clear(&cfg);
+    parse_cmdline(&cfg, argc, argv);
+
+    mrp_log_set_mask(cfg.log_mask);
+    mrp_log_set_target(cfg.log_target);
     
     if ((ml = mrp_mainloop_create()) == NULL)
        fatal("failed to create main loop.");
@@ -1187,8 +1397,27 @@ int main(int argc, char *argv[])
     if (mrp_add_timer(ml, 1000, check_quit, NULL) == NULL)
        fatal("failed to create quit-check timer");
     
-    mrp_mainloop_run(ml);
+#ifdef PULSE_ENABLED
+    if (cfg.pa != NULL) {
+       mrp_log_info("Running with PulseAudio mainloop.");
+
+       if (!mrp_mainloop_register_with_pulse(ml, cfg.pa)) {
+           mrp_log_error("Failed to register with PulseAudio mainloop.");
+           exit(1);
+       }
+       
+       pa_mainloop_run(cfg.pa_main, &retval);
+       
+       mrp_log_info("PulseAudio mainloop exited with status %d.", retval);
 
+       mrp_mainloop_unregister_from_pulse(ml, cfg.pa);
+
+       pa_mainloop_free(cfg.pa_main);
+    }
+    else
+#endif
+       retval = mrp_mainloop_run(ml);
+    
     check_io();
     check_timers();
     check_signals();