From 2574c175df5367d7b2669f0d067004904307a53c Mon Sep 17 00:00:00 2001 From: Martin Mares Date: Sun, 9 Nov 2014 00:58:10 +0100 Subject: [PATCH] Partial interface to PulseAudio --- Makefile | 7 +- ut.c | 404 ++++++++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 404 insertions(+), 7 deletions(-) diff --git a/Makefile b/Makefile index df775e1..f1beb75 100644 --- a/Makefile +++ b/Makefile @@ -1,12 +1,15 @@ LIBUSB_CFLAGS := $(shell pkg-config --cflags libusb-1.0) LIBUSB_LIBS := $(shell pkg-config --libs libusb-1.0) +LIBPULSE_CFLAGS := $(shell pkg-config --cflags libpulse) +LIBPULSE_LIBS := $(shell pkg-config --libs libpulse) + LIBUCW_PKG := /home/mj/src/libucw/run/lib/pkgconfig LIBUCW_CFLAGS := $(shell PKG_CONFIG_PATH=$(LIBUCW_PKG) pkg-config --cflags libucw) LIBUCW_LIBS := $(shell PKG_CONFIG_PATH=$(LIBUCW_PKG) pkg-config --libs libucw) -CFLAGS=-O2 -Wall -W -Wno-parentheses -Wstrict-prototypes -Wmissing-prototypes -Wundef -Wredundant-decls -std=gnu99 $(LIBUCW_CFLAGS) $(LIBUSB_CFLAGS) -LDLIBS=$(LIBUCW_LIBS) $(LIBUSB_LIBS) +CFLAGS=-O2 -Wall -W -Wno-parentheses -Wstrict-prototypes -Wmissing-prototypes -Wundef -Wredundant-decls -std=gnu99 $(LIBUCW_CFLAGS) $(LIBUSB_CFLAGS) $(LIBPULSE_CFLAGS) -g2 +LDLIBS=$(LIBUCW_LIBS) $(LIBUSB_LIBS) $(LIBPULSE_LIBS) all: ut diff --git a/ut.c b/ut.c index 46fd006..afb7089 100644 --- a/ut.c +++ b/ut.c @@ -2,6 +2,7 @@ #include #include +#include #include #include #include @@ -10,8 +11,10 @@ #include #include #include +#include #include +#include /* * Interface to Novation Nocturn @@ -249,19 +252,19 @@ static void noct_sched_write(void) if (noct_dirty_button) { - int i = bit_fls(noct_dirty_button); + int i = bit_ffs(noct_dirty_button); noct_dirty_button ^= 1U << i; noct_do_write(0x70 + i, noct_button_state[i]); } else if (noct_dirty_ring_mode) { - int i = bit_fls(noct_dirty_ring_mode); + int i = bit_ffs(noct_dirty_ring_mode); noct_dirty_ring_mode ^= 1U << i; noct_do_write(0x48 + i, noct_ring_mode[i] << 4); } else if (noct_dirty_ring_val) { - int i = bit_fls(noct_dirty_ring_val); + int i = bit_ffs(noct_dirty_ring_val); noct_dirty_ring_val ^= 1U << i; if (i == 8) noct_do_write(0x50 + i, noct_ring_val[i]); @@ -364,13 +367,404 @@ static void usb_init(void) noct_write_init(); } +/* + * Interface to PulseAudio + * + * FIXME + */ + +struct pmain_io { + cnode n; + struct main_file f; + clist io_events; +}; + +static clist pmain_io_list; + +struct pa_io_event { + cnode n; + cnode gc_n; + struct pmain_io *io; + pa_io_event_flags_t events; + pa_io_event_cb_t callback; + pa_io_event_destroy_cb_t destroy_callback; + void *userdata; +}; + +static clist pmain_io_gc_list; + +static pa_io_event *pmain_io_new(pa_mainloop_api *api, int fd, pa_io_event_flags_t events, pa_io_event_cb_t cb, void *userdata); +static void pmain_io_enable(pa_io_event *e, pa_io_event_flags_t events); +static void pmain_io_free(pa_io_event *e); +static void pmain_io_set_destroy(pa_io_event *e, pa_io_event_destroy_cb_t cb); + +struct pa_time_event { + cnode n; + struct main_timer t; + pa_time_event_cb_t callback; + pa_time_event_destroy_cb_t destroy_callback; + void *userdata; + struct timeval tv; +}; + +static clist pmain_time_gc_list; + +static pa_time_event *pmain_time_new(pa_mainloop_api *api, const struct timeval *tv, pa_time_event_cb_t cb, void *userdata); +static void pmain_time_restart(pa_time_event *e, const struct timeval *tv); +static void pmain_time_free(pa_time_event *e); +static void pmain_time_set_destroy(pa_time_event *e, pa_time_event_destroy_cb_t cb); + +struct pa_defer_event { + cnode n; + struct main_hook h; + pa_defer_event_cb_t callback; + pa_defer_event_destroy_cb_t destroy_callback; + void *userdata; +}; + +static clist pmain_defer_gc_list; + +static pa_defer_event *pmain_defer_new(pa_mainloop_api *api, pa_defer_event_cb_t cb, void *userdata); +static void pmain_defer_enable(pa_defer_event *e, int b); +static void pmain_defer_free(pa_defer_event *e); +static void pmain_defer_set_destroy(pa_defer_event *e, pa_defer_event_destroy_cb_t cb); + +static void pmain_quit(pa_mainloop_api *a, int retval); + +static struct main_hook pmain_gc_hook; + +static void pmain_trigger_gc(void); + +static struct pa_mainloop_api pmainloop_api = { + .io_new = pmain_io_new, + .io_enable = pmain_io_enable, + .io_free = pmain_io_free, + .io_set_destroy = pmain_io_set_destroy, + + .time_new = pmain_time_new, + .time_restart = pmain_time_restart, + .time_free = pmain_time_free, + .time_set_destroy = pmain_time_set_destroy, + + .defer_new = pmain_defer_new, + .defer_enable = pmain_defer_enable, + .defer_free = pmain_defer_free, + .defer_set_destroy = pmain_defer_set_destroy, + + .quit = pmain_quit, +}; + +static struct pmain_io *pmain_get_io(int fd) +{ + CLIST_FOR_EACH(struct pmain_io *, io, pmain_io_list) + if (io->f.fd == fd) + { + DBG("Pulse: Recycling IO master"); + return io; + } + + struct pmain_io *io = xmalloc(sizeof(*io)); + io->f.fd = fd; + io->f.data = io; + clist_add_tail(&pmain_io_list, &io->n); + clist_init(&io->io_events); + return io; +} + +static pa_io_event *pmain_io_new(pa_mainloop_api *api UNUSED, int fd, pa_io_event_flags_t events, pa_io_event_cb_t cb, void *userdata) +{ + struct pa_io_event *e = xmalloc_zero(sizeof(*e)); + DBG("Pulse: Creating new IO %p for fd %u", e, fd); + + e->io = pmain_get_io(fd); + e->callback = cb; + e->userdata = userdata; + clist_add_head(&e->io->io_events, &e->n); // Do not call the new IO if created from another IO on the same fd + pmain_io_enable(e, events); + return e; +} + +static int pmain_io_read(struct main_file *f) +{ + struct pmain_io *io = f->data; + DBG("Pulse: fd %d ready for read", io->f.fd); + + CLIST_FOR_EACH(struct pa_io_event *, e, io->io_events) + if (e->events & PA_IO_EVENT_INPUT) + { + DBG("Pulse: Callback on IO %p", e); + e->callback(&pmainloop_api, e, io->f.fd, PA_IO_EVENT_INPUT, e->userdata); + } + + DBG("Pulse: fd %d read done", io->f.fd); + return HOOK_IDLE; +} + +static int pmain_io_write(struct main_file *f) +{ + struct pmain_io *io = f->data; + DBG("Pulse: fd %d ready for write", io->f.fd); + + CLIST_FOR_EACH(struct pa_io_event *, e, io->io_events) + if (e->events & PA_IO_EVENT_OUTPUT) + { + DBG("Pulse: Callback on IO %p", e); + e->callback(&pmainloop_api, e, io->f.fd, PA_IO_EVENT_OUTPUT, e->userdata); + } + + DBG("Pulse: fd %d write done", io->f.fd); + return HOOK_IDLE; +} + +static void pmain_io_enable(pa_io_event *e, pa_io_event_flags_t events) +{ + struct pmain_io *io = e->io; + DBG("Pulse: Changing IO event mask for IO %p on fd %d to %02x", e, io->f.fd, events); + e->events = events; + + pa_io_event_flags_t mask = 0; + CLIST_FOR_EACH(struct pa_io_event *, f, io->io_events) + mask |= f->events; + DBG("Pulse: Recalculated IO mask for fd %d to %02x", io->f.fd, mask); + + if (mask) + { + io->f.read_handler = (mask & PA_IO_EVENT_INPUT) ? pmain_io_read : NULL; + io->f.write_handler = (mask & PA_IO_EVENT_OUTPUT) ? pmain_io_write : NULL; + if (file_is_active(&io->f)) + file_chg(&io->f); + else + file_add(&io->f); + } + else + file_del(&io->f); +} + +static void pmain_io_free(pa_io_event *e) +{ + DBG("Pulse: Deleting IO %p for fd %d", e, e->io->f.fd); + pmain_io_enable(e, 0); + clist_add_tail(&pmain_io_gc_list, &e->gc_n); + pmain_trigger_gc(); +} + +static void pmain_io_set_destroy(pa_io_event *e, pa_io_event_destroy_cb_t cb) +{ + e->destroy_callback = cb; +} + +static void pmain_time_handler(struct main_timer *t) +{ + struct pa_time_event *e = t->data; + DBG("Pulse: Timer %p triggered", e); + timer_del(t); + e->callback(&pmainloop_api, e, &e->tv, e->userdata); + DBG("Pulse: Timer %p done", e); +} + +static pa_time_event *pmain_time_new(pa_mainloop_api *api UNUSED, const struct timeval *tv, pa_time_event_cb_t cb, void *userdata) +{ + struct pa_time_event *e = xmalloc_zero(sizeof(*e)); + DBG("Pulse: Creating timer %p", e); + e->callback = cb; + e->userdata = userdata; + e->t.handler = pmain_time_handler; + e->t.data = e; + pmain_time_restart(e, tv); + return e; +} + +static timestamp_t timeval_to_timestamp(const struct timeval *tv) +{ + return 1000 * (timestamp_t) tv->tv_sec + tv->tv_usec / 1000; +} + +static void pmain_time_restart(pa_time_event *e, const struct timeval *tv) +{ + struct timeval now; + gettimeofday(&now, NULL); + timestamp_t ts_now = timeval_to_timestamp(&now); + timestamp_t ts_fire = timeval_to_timestamp(tv); + timestamp_t ts_delta = ts_fire - ts_now; + DBG("Pulse: Setting timer %p to %+d", e, (int) ts_delta); + timer_del(&e->t); + e->tv = *tv; + timer_add_rel(&e->t, ts_delta); +} + +static void pmain_time_free(pa_time_event *e) +{ + DBG("Pulse: Timer %p deleted", e); + timer_del(&e->t); + clist_add_tail(&pmain_time_gc_list, &e->n); + pmain_trigger_gc(); +} + +static void pmain_time_set_destroy(pa_time_event *e, pa_time_event_destroy_cb_t cb) +{ + e->destroy_callback = cb; +} + +static int pmain_defer_handler(struct main_hook *h) +{ + struct pa_defer_event *e = h->data; + DBG("Pulse: Deferred event %p triggered", e); + e->callback(&pmainloop_api, e, e->userdata); + DBG("Pulse: Deferred event done"); + return hook_is_active(&e->h) ? HOOK_RETRY : HOOK_IDLE; +} + +static pa_defer_event *pmain_defer_new(pa_mainloop_api *api UNUSED, pa_defer_event_cb_t cb, void *userdata) +{ + struct pa_defer_event *e = xmalloc_zero(sizeof(*e)); + DBG("Pulse: Creating defer %p", e); + e->callback = cb; + e->userdata = userdata; + e->h.handler = pmain_defer_handler; + e->h.data = e; + pmain_defer_enable(e, 1); + return e; +} + +static void pmain_defer_enable(pa_defer_event *e, int b) +{ + DBG("Pulse: %sabling defer %p", (b ? "En" : "Dis"), e); + if (b) + hook_add(&e->h); + else + hook_del(&e->h); +} + +static void pmain_defer_free(pa_defer_event *e) +{ + DBG("Pulse: Deferred event %p deleted", e); + hook_del(&e->h); + clist_add_tail(&pmain_defer_gc_list, &e->n); + pmain_trigger_gc(); +} + +static void pmain_defer_set_destroy(pa_defer_event *e, pa_defer_event_destroy_cb_t cb) +{ + e->destroy_callback = cb; +} + +static void pmain_quit(pa_mainloop_api *a UNUSED, int retval UNUSED) +{ + DBG("Pulse: Main loop quit not implemented"); +} + +static int pmain_gc_handler(struct main_hook *h) +{ + DBG("Pulse: Garbage collector"); + hook_del(h); + + cnode *n; + while (n = clist_remove_head(&pmain_io_gc_list)) + { + struct pa_io_event *ei = SKIP_BACK(struct pa_io_event, gc_n, n); + struct pmain_io *io = ei->io; + DBG("Pulse: GC of IO event %p on fd %d", ei, io->f.fd); + if (ei->destroy_callback) + ei->destroy_callback(&pmainloop_api, ei, ei->userdata); + clist_remove(&ei->n); + if (clist_empty(&io->io_events)) + { + ASSERT(!file_is_active(&io->f)); + DBG("Pulse: GC of IO master for fd %d", io->f.fd); + clist_remove(&io->n); + xfree(io); + } + xfree(ei); + } + + struct pa_time_event *et; + while (et = (struct pa_time_event *) clist_remove_head(&pmain_time_gc_list)) + { + DBG("Pulse: GC for timer %p", et); + if (et->destroy_callback) + et->destroy_callback(&pmainloop_api, et, et->userdata); + xfree(et); + } + + struct pa_defer_event *ed; + while (ed = (struct pa_defer_event *) clist_remove_head(&pmain_defer_gc_list)) + { + DBG("Pulse: GC for defer %p", ed); + if (ed->destroy_callback) + ed->destroy_callback(&pmainloop_api, ed, ed->userdata); + xfree(ed); + } + + DBG("Pulse: Garbage collector done"); + return HOOK_RETRY; +} + +static void pmain_trigger_gc(void) +{ + hook_add(&pmain_gc_hook); +} + +static pa_context *pulse_ctx; +static bool pulse_ready; + +static void pulse_client_info_cb(pa_context *ctx, const pa_client_info *i, int eol, void *userdata) +{ + if (eol) + { + DBG("Pulse: CLIENT DONE"); + return; + } + + DBG("Pulse: CLIENT #%u: %s mod=%u drv=%s", i->index, i->name, i->owner_module, i->driver); +} + +static void pulse_state_cb(pa_context *ctx, void *userdata UNUSED) +{ + int state = pa_context_get_state(ctx); + DBG("Pulse: State callback, new state = %d", state); + if (state == PA_CONTEXT_READY) + { + if (!pulse_ready) + { + pulse_ready = 1; + DBG("Pulse: ONLINE"); + pa_context_get_client_info_list(ctx, pulse_client_info_cb, NULL); + // FIXME: Discard the operation when server goes offline + } + } + else + { + if (pulse_ready) + { + pulse_ready = 0; + DBG("Pulse: OFFLINE"); + } + } +} + +static void pulse_init(void) +{ + clist_init(&pmain_io_list); + clist_init(&pmain_io_gc_list); + clist_init(&pmain_time_gc_list); + clist_init(&pmain_defer_gc_list); + pmain_gc_hook.handler = pmain_gc_handler; + + pulse_ctx = pa_context_new(&pmainloop_api, "ursaryd"); + pa_context_set_state_callback(pulse_ctx, pulse_state_cb, NULL); + pa_context_connect(pulse_ctx, NULL, PA_CONTEXT_NOAUTOSPAWN, NULL); +} + int main(int argc UNUSED, char **argv) { log_init(argv[0]); main_init(); - msg(L_INFO, "Initializing USB"); - usb_init(); + // msg(L_INFO, "Initializing USB"); + // usb_init(); + + msg(L_INFO, "Initializing PulseAudio"); + pulse_init(); msg(L_INFO, "Entering main loop"); main_loop(); -- 2.39.2