From 809014f18a440fc23c8fd24546548643272f3746 Mon Sep 17 00:00:00 2001 From: Krisztian Litkey Date: Sat, 9 Jun 2012 01:34:04 +0300 Subject: [PATCH] common/pulse-glue: added libmurphy-pulse, a PulseAudio murphy superloop. libmurphy-pulse provides the necessary gluing code to enable the murphy mainloop to be pumped by a PulseAudio mainloop. --- configure.ac | 31 ++++ src/Makefile.am | 44 +++++- src/common/pulse-glue.c | 310 +++++++++++++++++++++++++++++++++++++++ src/common/pulse-glue.h | 10 ++ src/common/tests/Makefile.am | 4 + src/common/tests/mainloop-test.c | 261 ++++++++++++++++++++++++++++++-- 6 files changed, 643 insertions(+), 17 deletions(-) create mode 100644 src/common/pulse-glue.c create mode 100644 src/common/pulse-glue.h diff --git a/configure.ac b/configure.ac index 6bada80..0c04757 100644 --- a/configure.ac +++ b/configure.ac @@ -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 diff --git a/src/Makefile.am b/src/Makefile.am index 2d5a647..a9b5233 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -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 index 0000000..1669245 --- /dev/null +++ b/src/common/pulse-glue.c @@ -0,0 +1,310 @@ + +#include +#include + +#include +#include + + + + +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 index 0000000..3621f88 --- /dev/null +++ b/src/common/pulse-glue.h @@ -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__ */ diff --git a/src/common/tests/Makefile.am b/src/common/tests/Makefile.am index 378d177..6c44010 100644 --- a/src/common/tests/Makefile.am +++ b/src/common/tests/Makefile.am @@ -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 diff --git a/src/common/tests/mainloop-test.c b/src/common/tests/mainloop-test.c index 7a63428..d5b7b4f 100644 --- a/src/common/tests/mainloop-test.c +++ b/src/common/tests/mainloop-test.c @@ -1,9 +1,12 @@ #include #include +#include #include #include +#include #include #include +#include #include #include #include @@ -11,10 +14,15 @@ #include #include +#include #include #include #include +#ifdef PULSE_ENABLED +# include +# include +#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(); -- 2.7.4