]> mj.ucw.cz Git - misc.git/commitdiff
Ursary: Partial interface to PulseAudio
authorMartin Mares <mj@ucw.cz>
Sat, 8 Nov 2014 23:58:10 +0000 (00:58 +0100)
committerMartin Mares <mj@ucw.cz>
Sat, 8 Nov 2014 23:58:10 +0000 (00:58 +0100)
ursaryd/Makefile
ursaryd/ut.c

index df775e15d4c8928d130350b8e601317bf3c9091e..f1beb7565dca0f4507504871ce692cb7275bc4e2 100644 (file)
@@ -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
 
index 46fd00615017a881018abb6f956e9951d9fe2a4a..afb70890f4ce2a7bf071e5560856fc15779db51e 100644 (file)
@@ -2,6 +2,7 @@
 
 #include <ucw/lib.h>
 #include <ucw/bitops.h>
+#include <ucw/clists.h>
 #include <ucw/gary.h>
 #include <ucw/mainloop.h>
 #include <ucw/string.h>
 #include <string.h>
 #include <stdlib.h>
 #include <sys/poll.h>
+#include <sys/time.h>
 
 #include <libusb.h>
+#include <pulse/pulseaudio.h>
 
 /*
  *  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();