bluetooth-client: first version
authorJanos Kovacs <jankovac503@gmail.com>
Thu, 6 Jun 2013 13:41:00 +0000 (16:41 +0300)
committerKrisztian Litkey <krisztian.litkey@intel.com>
Thu, 6 Jun 2013 13:54:10 +0000 (16:54 +0300)
src/Makefile.am
src/plugins/bluetooth-client/bluetooth-plugin.c [new file with mode: 0644]
src/plugins/bluetooth-client/bluetooth-plugin.h [new file with mode: 0644]
src/plugins/bluetooth-client/clients.c [new file with mode: 0644]
src/plugins/bluetooth-client/clients.h [new file with mode: 0644]
src/plugins/bluetooth-client/dbusif.c [new file with mode: 0644]
src/plugins/bluetooth-client/dbusif.h [new file with mode: 0644]
src/plugins/bluetooth-client/pulseif.c [new file with mode: 0644]
src/plugins/bluetooth-client/pulseif.h [new file with mode: 0644]

index 843613b..c6a3b82 100644 (file)
@@ -130,20 +130,38 @@ plugin_simple_disambiguator_la_LIBADD  =
 # Mpris2 client plugin
 plugin_LTLIBRARIES += plugin-mpris2-client.la
 
-plugin_mpris2_client_la_SOURCES =                      \
-               plugins/mpris2-client/mpris2-plugin.c   \
-               plugins/mpris2-client/dbusif.c          \
+plugin_mpris2_client_la_SOURCES =                              \
+               plugins/mpris2-client/mpris2-plugin.c           \
+               plugins/mpris2-client/dbusif.c                  \
                plugins/mpris2-client/clients.c
 
-plugin_mpris2_client_la_CFLAGS  =                      \
+plugin_mpris2_client_la_CFLAGS  =                              \
                $(AM_CFLAGS)
 
-plugin_mpris2_client_la_LDFLAGS =                      \
+plugin_mpris2_client_la_LDFLAGS =                              \
                -module -avoid-version
 
 plugin_mpris2_client_la_LIBADD  =
 
 
+# Bluetooth client plugin
+plugin_LTLIBRARIES += plugin-bluetooth-client.la
+
+plugin_bluetooth_client_la_SOURCES =                           \
+               plugins/bluetooth-client/bluetooth-plugin.c     \
+               plugins/bluetooth-client/dbusif.c               \
+               plugins/bluetooth-client/pulseif.c              \
+               plugins/bluetooth-client/clients.c
+
+plugin_bluetooth_client_la_CFLAGS  =                           \
+               $(AM_CFLAGS)
+
+plugin_bluetooth_client_la_LDFLAGS =                           \
+               -module -avoid-version
+
+plugin_bluetooth_client_la_LIBADD  =
+
+
 # cleanup
 clean-local::
        -rm -f *~
diff --git a/src/plugins/bluetooth-client/bluetooth-plugin.c b/src/plugins/bluetooth-client/bluetooth-plugin.c
new file mode 100644 (file)
index 0000000..0ec182e
--- /dev/null
@@ -0,0 +1,164 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *   * Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *   * Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ *   * Neither the name of Intel Corporation nor the names of its contributors
+ *     may be used to endorse or promote products derived from this software
+ *     without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+
+
+#include <murphy/common/debug.h>
+#include <murphy/common/mainloop.h>
+
+#include "dbusif.h"
+#include "pulseif.h"
+#include "clients.h"
+
+
+#define PLUGIN_DESCRIPTION "Bluetooth voice recognition for external devices"
+#define PLUGIN_AUTHORS     "Janos Kovacs <janos.kovacs@intel.com>"
+#define PLUGIN_VERSION     "0.0.1"
+
+
+
+static int create_bt_voicerec(srs_plugin_t *plugin)
+{
+    srs_context_t *srs = plugin->srs;
+    context_t     *ctx = NULL;
+    int            sts;
+
+    mrp_debug("creating bluetooth voice recognition client plugin");
+
+    if ((ctx = mrp_allocz(sizeof(context_t)))) {
+        ctx->plugin = plugin;
+
+        if (dbusif_create(ctx, srs->ml) == 0 &&
+            clients_create(ctx)         == 0  )
+        {
+            plugin->plugin_data = ctx;
+            return TRUE;
+        }
+
+        mrp_free(ctx);
+    }
+
+    mrp_log_error("Failed to create bluetooth voice "
+                  "recognition client plugin.");
+
+    return FALSE;
+}
+
+
+static int config_bt_voicerec(srs_plugin_t *plugin, srs_cfg_t *settings)
+{
+    context_t *ctx = (context_t *)plugin->plugin_data;
+    srs_cfg_t *cfgs, *c, *s;
+    const char *key;
+    int pfxlen;
+    int n, i;
+    int success;
+
+    mrp_debug("configuring bluetooth voice recognition client plugin");
+
+    n = srs_collect_config(settings, BLUETOOTH_PREFIX, &cfgs);
+    pfxlen = strlen(BLUETOOTH_PREFIX);
+
+    mrp_log_info("Found %d bluetooth voice recognition configuration keys.",n);
+
+    for (i = 0, success = TRUE;   i < n ;   i++) {
+        c = cfgs + i;
+        key = c->key + pfxlen;
+
+        c->used = FALSE;
+        success = FALSE;
+    }
+
+    srs_free_config(cfgs);
+
+    return TRUE;
+}
+
+
+static int start_bt_voicerec(srs_plugin_t *plugin)
+{
+    srs_context_t *srs = plugin->srs;
+    context_t *ctx = (context_t *)plugin->plugin_data;
+
+    mrp_debug("start bluetooth voice recognition client plugin");
+
+    if (clients_start(ctx)           < 0 ||
+        pulseif_create(ctx, srs->pa) < 0 ||
+        dbusif_start(ctx)            < 0  )
+    {
+        mrp_log_error("Failed to start bluetooth voice "
+                      "recognition client plugin.");
+        return FALSE;
+    }
+
+    return TRUE;
+}
+
+
+static void stop_bt_voicerec(srs_plugin_t *plugin)
+{
+    context_t *ctx = (context_t *)plugin->plugin_data;
+
+    mrp_debug("stop bluetooth voice recognition client plugin");
+
+    pulseif_destroy(ctx);
+    dbusif_stop(ctx);
+    clients_stop(ctx);
+}
+
+
+static void destroy_bt_voicerec(srs_plugin_t *plugin)
+{
+    srs_context_t *srs = plugin->srs;
+    context_t     *ctx = (context_t *)plugin->plugin_data;
+
+    mrp_debug("destroy bluetooth voice recognition client plugin");
+
+    dbusif_destroy(ctx);
+    clients_destroy(ctx);
+}
+
+
+
+SRS_DECLARE_PLUGIN(PLUGIN_NAME, PLUGIN_DESCRIPTION, PLUGIN_AUTHORS,
+                   PLUGIN_VERSION, create_bt_voicerec, config_bt_voicerec,
+                   start_bt_voicerec, stop_bt_voicerec, destroy_bt_voicerec)
+
+
+/*
+ * Local Variables:
+ * c-basic-offset: 4
+ * indent-tabs-mode: nil
+ * End:
+ *
+ */
diff --git a/src/plugins/bluetooth-client/bluetooth-plugin.h b/src/plugins/bluetooth-client/bluetooth-plugin.h
new file mode 100644 (file)
index 0000000..3d91f61
--- /dev/null
@@ -0,0 +1,39 @@
+#ifndef __SRS_BLUETOOTH_PLUGIN_H__
+#define __SRS_BLUETOOTH_PLUGIN_H__
+
+#include <stdint.h>
+#include <stdbool.h>
+
+#include "src/daemon/plugin.h"
+#include "src/daemon/client.h"
+
+#define PLUGIN_NAME           "bluetooth-voice-recognition"
+#define BLUETOOTH_PREFIX      "bluetooth."
+
+typedef enum   hfp_state_e     hfp_state_t;
+
+typedef struct context_s       context_t;
+typedef struct dbusif_s        dbusif_t;
+typedef struct pulseif_s       pulseif_t;
+typedef struct clients_s       clients_t;
+typedef struct modem_s         modem_t;
+typedef struct card_s          card_t;
+typedef struct device_s        device_t;
+
+struct context_s {
+    srs_plugin_t *plugin;
+    dbusif_t *dbusif;
+    pulseif_t *pulseif;
+    clients_t *clients;
+};
+
+
+#endif /* __SRS_BLUETOOTH_PLUGIN_H__ */
+
+/*
+ * Local Variables:
+ * c-basic-offset: 4
+ * indent-tabs-mode: nil
+ * End:
+ *
+ */
diff --git a/src/plugins/bluetooth-client/clients.c b/src/plugins/bluetooth-client/clients.c
new file mode 100644 (file)
index 0000000..25f4d37
--- /dev/null
@@ -0,0 +1,399 @@
+#include <sys/time.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <math.h>
+#include <errno.h>
+
+#include <murphy/common/debug.h>
+
+#include <murphy/common/hashtbl.h>
+#include <murphy/common/utils.h>
+
+#include "clients.h"
+#include "dbusif.h"
+#include "pulseif.h"
+
+
+struct clients_s {
+    srs_client_t *srs_client;
+    mrp_htbl_t *devices;
+    device_t *current;
+};
+
+static char *commands[] = {
+    "call",
+    "listen to",
+    NULL
+};
+static int ncommand = (sizeof(commands) / sizeof(commands[0])) - 1;
+
+static int play_samples(context_t *, size_t, int16_t *);
+static int notify_focus(srs_client_t *, srs_voice_focus_t);
+static int notify_command(srs_client_t *, int, char **);
+static device_t *device_find(clients_t *, const char *);
+static void device_free(void *, void *);
+
+/*****************************************************************/
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <fcntl.h>
+
+static void *samples;
+static size_t nsample;
+
+static void init_samples(const char *file)
+{
+    struct stat buf;
+    size_t size, len;
+    int l;
+    int fd;
+
+    if (stat(file, &buf) < 0) {
+        printf("*** could not stat sample file '%s'\n");
+        return;
+    }
+
+    if ((size = buf.st_size) > 500000) {
+        printf("*** file too length %u exceeds the max 500000\n", size);
+        return;
+    }
+
+    if (!(samples = mrp_alloc(size))) {
+        printf("*** failed to allocate memory for samples\n");
+        return;
+    }
+
+
+    if ((fd = open(file, O_RDWR)) < 0) {
+        printf("*** could not open file %s: %s\n", file, strerror(errno));
+        return;
+    }
+
+    for (len = 0;  len < size;  len += l) {
+        l = size - len;
+
+        if ((l = read(fd, samples + len, l)) <  0) {
+            if (errno == EINTR) {
+                l = 0;
+                continue;
+            }
+            printf("*** failed to read samples: %s\n", strerror(errno));
+            return;
+        }
+
+        if (l == 0)
+            break;
+    }
+
+    nsample = len / sizeof(int16_t);
+
+    printf("*** succesfully read %u samples\n", nsample);
+}
+
+
+/*****************************************************************/
+
+int clients_create(context_t *ctx)
+{
+    clients_t *clients;
+    mrp_htbl_config_t cfg;
+
+    if (!ctx)
+        return -1;
+
+    if (!(clients = mrp_allocz(sizeof(clients_t))))
+        return -1;
+
+    memset(&cfg, 0, sizeof(cfg));
+    cfg.nentry = 10;
+    cfg.comp = mrp_string_comp;
+    cfg.hash = mrp_string_hash;
+    cfg.free = device_free;
+    cfg.nbucket = cfg.nentry;
+
+    clients->devices = mrp_htbl_create(&cfg);
+    clients->current = NULL;
+
+    ctx->clients = clients;
+
+    init_samples("/home/jko/Sources/protos/srec/hmm/wav/speaker_05/"
+                 "play_music.wav");
+
+    return 0;
+}
+
+
+void clients_destroy(context_t *ctx)
+{
+    clients_t *clients;
+
+    if (ctx && (clients = ctx->clients)) {
+        ctx->clients = NULL;
+
+        client_destroy(clients->srs_client);
+        mrp_htbl_destroy(clients->devices, TRUE);
+
+        free(clients);
+    }
+}
+
+int clients_start(context_t *ctx)
+{
+    srs_plugin_t *pl;
+    srs_context_t *srs;
+    clients_t *clients;
+    srs_client_ops_t callbacks;
+
+    if (!ctx || !(pl = ctx->plugin) || !(srs = pl->srs) ||
+        !(clients = ctx->clients))
+        return -1;
+
+    callbacks.notify_focus = notify_focus;
+    callbacks.notify_command = notify_command;
+
+    clients->srs_client = client_create(srs, SRS_CLIENT_TYPE_BUILTIN,
+                                        PLUGIN_NAME, "voicerec",
+                                        commands, ncommand,
+                                        PLUGIN_NAME, &callbacks, ctx);
+
+    client_request_focus(clients->srs_client, SRS_VOICE_FOCUS_SHARED);
+
+    return 0;
+}
+
+int clients_stop(context_t *ctx)
+{
+    return 0;
+}
+
+device_t *clients_add_device(context_t *ctx, const char *btaddr)
+{
+    clients_t *clients;
+    device_t *device = NULL;
+
+    if (ctx && btaddr && (clients = ctx->clients)) {
+        if (device_find(clients, btaddr)) {
+            mrp_log_error("bluetooth blugin: attempt to add already "
+                          "existing device @ %s", btaddr);
+        }
+        else if ((device = mrp_allocz(sizeof(device_t)))) {
+            device->ctx = ctx;
+            device->btaddr = mrp_strdup(btaddr);
+
+            mrp_htbl_insert(clients->devices, (void *)device->btaddr, device);
+        }
+    }
+
+    return device;
+}
+
+void clients_remove_device(device_t *device)
+{
+    context_t *ctx;
+    clients_t *clients;
+    modem_t *modem;
+    card_t *card;
+
+    if (device && (ctx = device->ctx) && (clients = ctx->clients)) {
+        if (device == clients->current)
+            clients->current = NULL;
+
+        if ((card = device->card))
+            card->device = NULL;
+
+        if ((modem = device->modem))
+            modem->device = NULL;
+
+        mrp_htbl_remove(clients->devices, (void *)device->btaddr, TRUE);
+    }
+}
+
+device_t *clients_find_device(context_t *ctx, const char *btaddr)
+{
+    clients_t *clients;
+    device_t *device;
+
+    if (!ctx || !btaddr || !(clients = ctx->clients) || !clients->devices)
+        device = NULL;
+    else
+        device = device_find(clients, btaddr);
+
+    return device;
+}
+
+bool clients_device_is_ready(device_t *device)
+{
+    return device && device->modem && device->card;
+}
+
+void clients_add_card_to_device(device_t *device, card_t *card)
+{
+    context_t *ctx;
+    clients_t *clients;
+
+    if (device && card && (ctx = device->ctx) && (clients = ctx->clients)) {
+        if (device->card && card != device->card) {
+            mrp_log_error("bluetooth client: refuse to add card to client @ %s"
+                          ". It has already one", device->btaddr);
+        }
+        device->card = card;
+
+        if (clients_device_is_ready(device)) {
+            mrp_log_info("added bluetooth device '%s' @ %s",
+                         device->modem->name, device->btaddr);
+            if (!clients->current)
+                clients->current = device;
+        }
+    }
+}
+
+void clients_remove_card_from_device(device_t *device)
+{
+    context_t *ctx;
+    clients_t *clients;
+    card_t *card;
+
+    if (device && (ctx = device->ctx) && (clients = ctx->clients)) {
+        if ((card = device->card)) {
+            device->card = NULL;
+
+            if (device == clients->current)
+                clients->current = NULL;
+        }
+    }
+}
+
+void clients_stop_recognising_voice(device_t *device)
+{
+    modem_t *modem;
+    card_t *card;
+
+    if (device) {
+        mrp_free(device->samples);
+        device->nsample = 0;
+        device->samples = NULL;
+
+        if ((modem = device->modem) && modem->state == VOICE_RECOGNITION_ON) {
+            dbusif_set_voice_recognition(modem, VOICE_RECOGNITION_OFF);
+        }
+
+        if ((card = device->card)) {
+            pulseif_remove_input_stream_from_card(card);
+            pulseif_remove_output_stream_from_card(card);
+        }
+    }
+}
+
+static int play_samples(context_t *ctx, size_t nsample, int16_t *samples)
+{
+    clients_t *clients;
+    device_t *device;
+    modem_t *modem;
+    card_t *card;
+
+    if (!ctx || !nsample || !samples || !(clients = ctx->clients))
+        return -1;
+
+    if (!(device = clients->current)) {
+        mrp_log_error("bluetooth client: can't play samples: no device");
+        return -1;
+    }
+
+    if (!(modem = device->modem) || !(card = device->card))
+        return -1;
+
+    if (device->nsample || device->samples ||
+        modem->state == VOICE_RECOGNITION_ON)
+    {
+        mrp_log_error("bluetooth client: can't play samples: voicerec "
+                      "already in progress");
+        return -1;
+    }
+
+    device->nsample = nsample;
+    device->samples = samples;
+
+    if (dbusif_set_voice_recognition(modem, VOICE_RECOGNITION_ON) < 0 ||
+        pulseif_add_input_stream_to_card(card)                    < 0  )
+    {
+        return -1;
+    }
+
+    return 0;
+}
+
+static int notify_focus(srs_client_t *srs_client, srs_voice_focus_t focus)
+{
+    return TRUE;
+}
+
+static int notify_command(srs_client_t *srs_client, int ntoken, char **tokens)
+{
+    context_t *ctx;
+    clients_t *clients;
+    device_t *device;
+    char cmd[2048];
+    char *e, *p, *sep;
+    int i;
+
+    if (!srs_client || !(ctx = srs_client->user_data) ||
+        !(clients = ctx->clients))
+        return FALSE;
+
+    e = (p = cmd) + (sizeof(cmd) - 1);
+
+    for (i = 0, sep = "", *p = 0;   i < ntoken && p < e;   i++, sep = " ")
+        p += snprintf(p, e-p, "%s%s", sep, tokens[i]);
+
+    if (!(device = clients->current)) {
+        mrp_log_info("no bluetooth device to execute command '%s'", cmd);
+        return FALSE;
+    }
+
+    mrp_log_info("Bluetooth client got command '%s'\n", cmd);
+
+    { /* !!! TEMPORARY !!! */
+        int16_t *buf = mrp_alloc(sizeof(int16_t)* nsample);
+        memcpy(buf, samples, sizeof(int16_t) * nsample);
+        play_samples(ctx, nsample, buf);
+    } /* !!! TEMPORARY !!! */
+
+    return TRUE;
+}
+
+static device_t *device_find(clients_t *clients, const char *btaddr)
+{
+    device_t *device;
+
+    if (!clients || !clients->devices || !btaddr)
+        device = NULL;
+    else
+        device = mrp_htbl_lookup(clients->devices, (void *)btaddr);
+
+    return device;
+}
+
+static void device_free(void *key, void *object)
+{
+    device_t *device = (device_t *)object;
+
+    if (strcmp(key, device->btaddr)) {
+        mrp_log_error("bluetooth plugin: %s() confused with internal "
+                      "data structures", __FUNCTION__);
+    }
+    else {
+        mrp_free((void *)device->btaddr);
+        mrp_free((void *)device);
+    }
+}
+
+
+/*
+ * Local Variables:
+ * c-basic-offset: 4
+ * indent-tabs-mode: nil
+ * End:
+ *
+ */
diff --git a/src/plugins/bluetooth-client/clients.h b/src/plugins/bluetooth-client/clients.h
new file mode 100644 (file)
index 0000000..1ea7dd7
--- /dev/null
@@ -0,0 +1,46 @@
+#ifndef __SRS_BLUETOOTH_CLIENT_H__
+#define __SRS_BLUETOOTH_CLIENT_H__
+
+#include "bluetooth-plugin.h"
+
+
+/*
+ * A bluetooth connected device
+ * capable of voice recognition
+ */
+struct device_s {
+    context_t *ctx;
+    const char *btaddr;
+    modem_t *modem;
+    card_t *card;
+    bool active;
+    size_t nsample;
+    int16_t *samples;
+};
+
+
+int  clients_create(context_t *ctx);
+void clients_destroy(context_t *ctx);
+
+int clients_start(context_t *ctx);
+int clients_stop(context_t *ctx);
+
+device_t *clients_add_device(context_t *ctx, const char *btaddr);
+void clients_remove_device(device_t *device);
+device_t *clients_find_device(context_t *ctx, const char *btaddr);
+bool clients_device_is_ready(device_t *device);
+
+void clients_add_card_to_device(device_t *device, card_t *card);
+void clients_remove_card_from_device(device_t *device);
+
+void clients_stop_recognising_voice(device_t *device);
+
+#endif /* __SRS_BLUETOOTH_CLIENT_H__ */
+
+/*
+ * Local Variables:
+ * c-basic-offset: 4
+ * indent-tabs-mode: nil
+ * End:
+ *
+ */
diff --git a/src/plugins/bluetooth-client/dbusif.c b/src/plugins/bluetooth-client/dbusif.c
new file mode 100644 (file)
index 0000000..9175dab
--- /dev/null
@@ -0,0 +1,746 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <murphy/common/debug.h>
+#include <murphy/common/dbus.h>
+#include <murphy/common/list.h>
+
+#include "dbusif.h"
+#include "clients.h"
+#include "pulseif.h"
+
+#define MAKE_DBUS_VERSION(major, minor, patch)  \
+    (((major) << 16) | ((minor) << 8) | (patch))
+
+#if DBUS_VERSION < MAKE_DBUS_VERSION(1, 6, 8)
+/* For old versions, we define DBusBasicValue with the member we use... */
+typedef union {
+    char        *str;
+    double       dbl;
+    dbus_bool_t  bool_val;
+} DBusBasicValue;
+#endif
+
+struct dbusif_s {
+    const char *bustype;
+    mrp_dbus_t *dbus;
+    mrp_list_hook_t modems;
+};
+
+
+static modem_t *create_modem(context_t *, const char *, const char *,
+                             const char *);
+static void destroy_modem(modem_t *);
+static modem_t *reference_modem(modem_t *);
+static void unreference_modem(modem_t *);
+static modem_t *find_modem_by_path(context_t *, const char *);
+static void track_modems(context_t *, bool);
+static void query_all_modems(context_t *);
+static void query_modem(modem_t *);
+static void query_handsfree(modem_t *);
+
+static int modem_property_changed_cb(mrp_dbus_t *, DBusMessage *, void *);
+static int handsfree_property_changed_cb(mrp_dbus_t *, DBusMessage *, void *);
+static void modem_query_all_cb(mrp_dbus_t *, DBusMessage *, void *);
+static void modem_query_cb(mrp_dbus_t *, DBusMessage *, void *);
+static void handsfree_query_cb(mrp_dbus_t *, DBusMessage *, void *);
+
+static void set_modem_state(modem_t *, hfp_state_t);
+
+static void parse_modem_properties(context_t *,DBusMessageIter *,const char **,
+                                   const char **, dbus_bool_t *);
+static void parse_handsfree_properties(modem_t *,  DBusMessageIter *,
+                                       hfp_state_t *);
+static void parse_property(DBusMessageIter *, const char **name,
+                           int *type,  DBusBasicValue *);
+static void set_property(DBusMessageIter *, const char *, int, void *);
+
+
+int dbusif_create(context_t *ctx, mrp_mainloop_t *ml)
+{
+    dbusif_t *dbusif;
+
+    if (!(dbusif = mrp_allocz(sizeof(dbusif_t))))
+        return -1;
+
+    dbusif->bustype = mrp_strdup("system");
+    dbusif->dbus = mrp_dbus_get(ml, dbusif->bustype, NULL);
+
+    if (!dbusif->dbus) {
+        mrp_log_error("bluetooth voice recognition plugin: "
+                      "failed to obtain DBus");
+        mrp_free(dbusif);
+        return -1;
+    }
+
+    mrp_list_init(&dbusif->modems);
+
+    ctx->dbusif = dbusif;
+
+    return 0;
+}
+
+void dbusif_destroy(context_t *ctx)
+{
+    dbusif_t *dbusif;
+
+    if (ctx && (dbusif = ctx->dbusif)) {
+        ctx->dbusif = NULL;
+
+        mrp_dbus_unref(dbusif->dbus);
+
+        mrp_free((void *)dbusif->bustype);
+        mrp_free((void *)dbusif);
+    }
+}
+
+int dbusif_start(context_t *ctx)
+{
+    track_modems(ctx, TRUE);
+    query_all_modems(ctx);
+    return 0;
+}
+
+void dbusif_stop(context_t *ctx)
+{
+    dbusif_t *dbusif;
+    modem_t *modem;
+    mrp_list_hook_t *entry, *n;
+
+    track_modems(ctx, FALSE);
+
+    if (ctx && (dbusif = ctx->dbusif)) {
+        mrp_list_foreach(&dbusif->modems, entry, n) {
+            modem = mrp_list_entry(entry, modem_t, link);
+            destroy_modem(modem);
+        }
+    }
+}
+
+int dbusif_set_voice_recognition(modem_t *modem, hfp_state_t state)
+{
+    context_t *ctx;
+    dbusif_t *dbusif;
+    DBusMessage *msg;
+    DBusMessageIter mit;
+    dbus_bool_t value;
+
+    if (!modem || !modem->path || !(ctx = modem->ctx) ||
+        !(dbusif = ctx->dbusif))
+        return -1;
+
+    switch (state) {
+    case VOICE_RECOGNITION_ON:   value = TRUE;    break;
+    case VOICE_RECOGNITION_OFF:  value = FALSE;   break;
+    default:                     /* invalid */    return -1;
+    }
+
+    msg = dbus_message_new_method_call("org.ofono",
+                                       modem->path,
+                                       "org.ofono.Handsfree",
+                                       "SetProperty");
+    if (!msg)
+        return -1;
+
+    dbus_message_iter_init_append(msg, &mit);
+    set_property(&mit, "VoiceRecognition", DBUS_TYPE_BOOLEAN, &value);
+
+    mrp_dbus_send_msg(dbusif->dbus, msg);
+
+    dbus_message_unref(msg);
+
+    return 0;
+}
+
+
+static modem_t *create_modem(context_t *ctx,
+                             const char *path,
+                             const char *name,
+                             const char *addr)
+{
+    dbusif_t *dbusif;
+    modem_t *modem;
+
+    if (!ctx || !path || !addr || !(dbusif = ctx->dbusif))
+        return NULL;
+
+    if (find_modem_by_path(ctx, path))
+        return NULL;
+
+    if (!(modem = mrp_allocz(sizeof(modem_t))))
+        return NULL;
+
+    modem->path = mrp_strdup(path);
+    modem->name = mrp_strdup(name ? name : "<unknown>");
+    modem->addr = mrp_strdup(addr);
+    modem->ctx = ctx;
+
+    reference_modem(modem);
+
+    mrp_list_prepend(&dbusif->modems, &modem->link);
+}
+
+static void destroy_modem(modem_t *modem)
+{
+    context_t *ctx;
+    dbusif_t *dbusif;
+
+    if (modem && (ctx = modem->ctx) && (dbusif = ctx->dbusif)) {
+        mrp_list_delete(&modem->link);
+        unreference_modem(modem);
+    }
+}
+
+static modem_t *reference_modem(modem_t *modem)
+{
+    if (modem && modem->refcnt >= 0)
+        modem->refcnt++;
+
+    return modem->refcnt < 0 ? NULL : modem;
+}
+
+static void unreference_modem(modem_t *modem)
+{
+    device_t *dev;
+
+    if (modem) {
+        if (modem->refcnt > 1)
+            modem->refcnt--;
+        else {
+            mrp_log_info("remove bluetooth modem '%s' @ %s (paths %s)",
+                         modem->name, modem->addr, modem->path);
+
+            if ((dev = modem->device)) {
+                modem->device = NULL;
+                clients_remove_device(dev);
+            }
+
+            mrp_list_delete(&modem->link);
+
+            mrp_free((void *)modem->path);
+            mrp_free((void *)modem->name);
+            mrp_free((void *)modem->addr);
+
+            mrp_free((void *)modem);
+        }
+    }
+}
+
+
+static modem_t *find_modem_by_path(context_t *ctx, const char *path)
+{
+    dbusif_t *dbusif;
+    modem_t *modem;
+    mrp_list_hook_t *entry, *n;
+
+    if (!ctx || !path || !(dbusif = ctx->dbusif))
+        return NULL;
+
+    mrp_list_foreach(&dbusif->modems, entry, n) {
+        modem = mrp_list_entry(entry, modem_t, link);
+        if (!strcmp(path, modem->path))
+            return modem;
+    }
+
+    return NULL;
+}
+
+static void track_modems(context_t *ctx, bool track)
+{
+    static const char *modem_interface = "org.ofono.Modem";
+    static const char *handsfree_interface = "org.ofono.Handsfree";
+    static const char *member = "PropertyChanged";
+
+    dbusif_t *dbusif;
+
+    if (ctx && (dbusif = ctx->dbusif)) {
+        if (track) {
+            mrp_dbus_add_signal_handler(dbusif->dbus, NULL, NULL,
+                                        modem_interface, member,
+                                        modem_property_changed_cb, ctx);
+            mrp_dbus_add_signal_handler(dbusif->dbus, NULL, NULL,
+                                        handsfree_interface, member,
+                                        handsfree_property_changed_cb, ctx);
+            mrp_dbus_install_filter(dbusif->dbus, NULL, NULL,
+                                    modem_interface, member, NULL);
+            mrp_dbus_install_filter(dbusif->dbus, NULL, NULL,
+                                    handsfree_interface, member, NULL);
+        }
+        else {
+            mrp_dbus_del_signal_handler(dbusif->dbus, NULL, NULL,
+                                        modem_interface, member,
+                                        modem_property_changed_cb, ctx);
+            mrp_dbus_del_signal_handler(dbusif->dbus, NULL, NULL,
+                                        handsfree_interface, member,
+                                        handsfree_property_changed_cb, ctx);
+            mrp_dbus_remove_filter(dbusif->dbus, NULL, NULL,
+                                   modem_interface, member, NULL);
+            mrp_dbus_remove_filter(dbusif->dbus, NULL, NULL,
+                                   handsfree_interface, member, NULL);
+        }
+    }
+}
+
+
+static void query_all_modems(context_t *ctx)
+{
+    dbusif_t *dbusif;
+    DBusMessage *msg;
+
+    if (!ctx || !(dbusif = ctx->dbusif))
+        return;
+
+    msg = dbus_message_new_method_call("org.ofono", "/", "org.ofono.Manager",
+                                       "GetModems");
+    if (msg) {
+        mrp_dbus_send(dbusif->dbus, "org.ofono", "/", "org.ofono.Manager",
+                      "GetModems", 1000, modem_query_all_cb, ctx, msg);
+    }
+}
+
+static void query_modem(modem_t *modem)
+{
+    context_t *ctx;
+    dbusif_t *dbusif;
+    DBusMessage *msg;
+    const char *path;
+    modem_t *ref;
+
+    if (!modem || !(ctx = modem->ctx) || !(path = modem->path) ||
+        !(dbusif = ctx->dbusif))
+        return;
+
+    msg = dbus_message_new_method_call("org.ofono", path, "org.ofono.Modem",
+                                       "GetProperties");
+    if (msg) {
+        if ((ref = reference_modem(modem))) {
+            mrp_dbus_send(dbusif->dbus, "org.ofono", path, "org.ofono.Modem",
+                          "GetProperties", 1000, modem_query_cb, ref, msg);
+        }
+    }
+}
+
+static void query_handsfree(modem_t *modem)
+{
+    context_t *ctx;
+    dbusif_t *dbusif;
+    DBusMessage *msg;
+    const char *path;
+    modem_t *ref;
+
+    if (!modem || !(ctx = modem->ctx) || !(path = modem->path) ||
+        !(dbusif = ctx->dbusif))
+        return;
+
+    msg = dbus_message_new_method_call("org.ofono",path,"org.ofono.Handsfree",
+                                       "GetProperties");
+    if (msg) {
+        if ((ref = reference_modem(modem))) {
+            mrp_dbus_send(dbusif->dbus, "org.ofono",path,"org.ofono.Handsfree",
+                          "GetProperties", 1000, handsfree_query_cb, ref, msg);
+        }
+    }
+}
+
+static int modem_property_changed_cb(mrp_dbus_t *dbus,
+                                     DBusMessage *msg,
+                                     void *user_data)
+{
+    context_t *ctx = (context_t *)user_data;
+    dbusif_t *dbusif;
+    DBusMessageIter mit;
+    const char *path;
+    modem_t *modem;
+    const char *prop;
+    int type;
+    DBusBasicValue value;
+
+    if (ctx && (dbusif = ctx->dbusif) && dbus_message_iter_init(msg, &mit)) {
+        path = dbus_message_get_path(msg);
+
+        parse_property(&mit, &prop, &type, &value);
+
+        if (path && prop) {
+            if (!strcmp(prop, "Online")) {
+                modem = find_modem_by_path(ctx, path);
+
+                if (value.bool_val) {
+                    if (path && !modem) {
+                        if ((modem = create_modem(ctx, path, "", ""))) {
+                            query_modem(modem);
+                            query_handsfree(modem);
+                        }
+                    }
+                }
+                else {
+                    if (modem)
+                        destroy_modem(modem);
+                }
+            }
+        }
+    }
+
+    return FALSE;
+}
+
+static int handsfree_property_changed_cb(mrp_dbus_t *dbus,
+                                         DBusMessage *msg,
+                                         void *user_data)
+{
+    context_t *ctx = (context_t *)user_data;
+    dbusif_t *dbusif;
+    DBusMessageIter mit;
+    const char *path;
+    modem_t *modem;
+    const char *prop;
+    int type;
+    DBusBasicValue value;
+    hfp_state_t state;
+
+    if (ctx && (dbusif = ctx->dbusif) && dbus_message_iter_init(msg, &mit)) {
+        path = dbus_message_get_path(msg);
+
+        parse_property(&mit, &prop, &type, &value);
+
+        if (path && prop) {
+            if (!strcmp(prop, "VoiceRecognition")) {
+                if ((modem = find_modem_by_path(ctx, path))) {
+                    if (value.bool_val)
+                        state = VOICE_RECOGNITION_ON;
+                    else
+                        state = VOICE_RECOGNITION_OFF;
+
+                    set_modem_state(modem, state);
+               }
+            }
+        }
+    }
+
+    return FALSE;
+}
+
+static void modem_query_all_cb(mrp_dbus_t *dbus,
+                               DBusMessage *msg,
+                               void *user_data)
+{
+    context_t *ctx = (context_t *)user_data;
+    dbusif_t *dbusif;
+    device_t *dev;
+    const char *path;
+    const char *addr;
+    const char *name;
+    dbus_bool_t online;
+    hfp_state_t state;
+    modem_t *modem;
+    DBusMessageIter mit, ait, sit;
+
+    if (ctx && (dbusif = ctx->dbusif) && dbus_message_iter_init(msg, &mit)) {
+        if (dbus_message_iter_get_arg_type(&mit) != DBUS_TYPE_ARRAY)
+            return;
+
+        dbus_message_iter_recurse(&mit, &ait);
+
+        while (dbus_message_iter_get_arg_type(&ait) == DBUS_TYPE_STRUCT) {
+            dbus_message_iter_recurse(&ait, &sit);
+
+            if (dbus_message_iter_get_arg_type(&sit) == DBUS_TYPE_OBJECT_PATH){
+                dbus_message_iter_get_basic(&sit, &path);
+                dbus_message_iter_next(&sit);
+                parse_modem_properties(ctx, &sit, &addr, &name, &online);
+
+                if (path && online) {
+                    if ((dev = clients_add_device(ctx, addr)) &&
+                        (modem = create_modem(ctx, path, name, addr)))
+                    {
+                        modem->device = dev;
+                        dev->modem = modem;
+
+                        mrp_log_info("created bluetooth modem '%s' @ %s "
+                                     "(path %s)", modem->name, modem->addr,
+                                     modem->path);
+                        query_handsfree(modem);
+                    }
+                }
+            }
+
+            dbus_message_iter_next(&ait);
+        }
+    }
+}
+
+static void modem_query_cb(mrp_dbus_t *dbus,
+                           DBusMessage *msg,
+                           void *user_data)
+{
+    modem_t *modem = (modem_t *)user_data;
+    device_t *dev;
+    context_t *ctx;
+    const char *addr;
+    const char *name;
+    dbus_bool_t online;
+    hfp_state_t state;
+    DBusMessageIter mit, ait, sit;
+
+    if (modem && (ctx = modem->ctx)) {
+
+        if (dbus_message_get_type(msg) != DBUS_MESSAGE_TYPE_ERROR) {
+            if (dbus_message_iter_init(msg, &mit)) {
+                parse_modem_properties(ctx, &mit, &addr, &name, &online);
+
+                if (!online || !name || !(dev = clients_add_device(ctx, addr)))
+                    destroy_modem(modem);
+                else {
+                    mrp_free((void *)modem->addr);
+                    mrp_free((void *)modem->name);
+
+                    modem->addr = mrp_strdup(addr);
+                    modem->name = mrp_strdup(name);
+                    modem->device = dev;
+
+                    dev->modem = modem;
+
+                    mrp_log_info("created bluetooth modem '%s' @ %s (path %s)",
+                                 modem->name, modem->addr, modem->path);
+                }
+            }
+        }
+
+        unreference_modem(modem);
+    }
+}
+
+static void handsfree_query_cb(mrp_dbus_t *dbus,
+                               DBusMessage *msg,
+                               void *user_data)
+{
+    modem_t *modem = (modem_t *)user_data;
+    context_t *ctx;
+    hfp_state_t state;
+    DBusMessageIter mit, ait, sit;
+
+    if (modem && (ctx = modem->ctx)) {
+
+        if (dbus_message_get_type(msg) != DBUS_MESSAGE_TYPE_ERROR) {
+
+            if (dbus_message_iter_init(msg, &mit)) {
+                parse_handsfree_properties(modem, &mit, &state);
+                set_modem_state(modem, state);
+            }
+
+            unreference_modem(modem);
+        }
+    }
+}
+
+static void set_modem_state(modem_t *modem, hfp_state_t state)
+{
+    device_t *device;
+    card_t *card;
+
+    if (state == modem->state)
+        return;
+
+    switch (state) {
+
+    case VOICE_RECOGNITION_ON:
+        mrp_log_info("bluetooth modem: setting voicerecognition on "
+                     "for modem %s", modem->addr);
+        modem->state = VOICE_RECOGNITION_ON;
+        if ((device = modem->device) && (card = device->card)) {
+            pulseif_set_card_profile(card, "hfgw");
+        }
+        break;
+
+    case VOICE_RECOGNITION_OFF:
+        mrp_log_info("bluetooth modem: setting voicerecognition off "
+                     "for modem %s", modem->addr);
+        modem->state = VOICE_RECOGNITION_OFF;
+        if ((device = modem->device)) {
+            clients_stop_recognising_voice(device);
+        }
+        break;
+
+    default:
+        mrp_log_error("bluetooth plugin: attempt to set invalid stte "
+                      "for modem %s", modem->addr);
+        break;
+    }
+}
+
+static void parse_modem_properties(context_t *ctx,
+                                   DBusMessageIter *sit,
+                                   const char **btaddr,
+                                   const char **btname,
+                                   dbus_bool_t *online)
+{
+    modem_t *modem;
+    const char *prop;
+    int type;
+    DBusBasicValue value;
+    DBusMessageIter ait, dit, vit, iit;
+    dbus_bool_t has_handsfree_interface = FALSE;
+
+    if (!ctx || !sit || !btaddr || !btname || !online)
+        return;
+
+    *btname = "<unknown>";
+    *btaddr = "<unknown>";
+    *online = FALSE;
+
+    if (dbus_message_iter_get_arg_type(sit) != DBUS_TYPE_ARRAY)
+        return;
+
+    dbus_message_iter_recurse(sit, &ait);
+
+    while (dbus_message_iter_get_arg_type(&ait) == DBUS_TYPE_DICT_ENTRY) {
+
+        dbus_message_iter_recurse(&ait, &dit);
+
+        dbus_message_iter_get_basic(&dit, &prop);
+        dbus_message_iter_next(&dit);
+
+        dbus_message_iter_recurse(&dit, &vit);
+        type = dbus_message_iter_get_arg_type(&vit);
+
+        if (type != DBUS_TYPE_ARRAY) {
+            memset(&value, 0, sizeof(value));
+            dbus_message_iter_get_basic(&vit, &value);
+
+            if (!strcmp(prop, "Online"))
+                *online = value.bool_val;
+            else if(!strcmp(prop, "Name"))
+                *btname = value.str;
+            else if (!strcmp(prop, "Serial"))
+                *btaddr = value.str;
+        }
+        else {
+            if (!strcmp(prop, "Interfaces")) {
+                dbus_message_iter_recurse(&vit, &iit);
+
+                while (dbus_message_iter_get_arg_type(&iit)==DBUS_TYPE_STRING){
+                    dbus_message_iter_get_basic(&iit, &value);
+
+                    if (!strcmp(value.str, "org.ofono.Handsfree")) {
+                        has_handsfree_interface = TRUE;
+                        break;
+                    }
+
+                    dbus_message_iter_next(&iit);
+                }
+            }
+        }
+
+        dbus_message_iter_next(&ait);
+    }
+
+    if (!has_handsfree_interface) {
+        *btname = "<unknown>";
+        *btaddr = "<unknown>";
+        *online = FALSE;
+    }
+}
+
+static void parse_handsfree_properties(modem_t *modem,
+                                       DBusMessageIter *sit,
+                                       hfp_state_t *state)
+{
+    const char *prop;
+    int type;
+    DBusBasicValue value;
+    DBusMessageIter ait, dit, vit, iit;
+    dbus_bool_t has_handsfree_interface = FALSE;
+
+    if (!modem || !sit || !state)
+        return;
+
+    *state = VOICE_RECOGNITION_UNKNOWN;
+
+    if (dbus_message_iter_get_arg_type(sit) != DBUS_TYPE_ARRAY)
+        return;
+
+    dbus_message_iter_recurse(sit, &ait);
+
+    while (dbus_message_iter_get_arg_type(&ait) == DBUS_TYPE_DICT_ENTRY) {
+
+        dbus_message_iter_recurse(&ait, &dit);
+
+        dbus_message_iter_get_basic(&dit, &prop);
+        dbus_message_iter_next(&dit);
+
+        dbus_message_iter_recurse(&dit, &vit);
+        type = dbus_message_iter_get_arg_type(&vit);
+
+        if (type != DBUS_TYPE_ARRAY) {
+            memset(&value, 0, sizeof(value));
+            dbus_message_iter_get_basic(&vit, &value);
+
+            if (!strcmp(prop, "VoiceRecognition")) {
+                if (value.bool_val)
+                    *state = VOICE_RECOGNITION_ON;
+                else
+                    *state = VOICE_RECOGNITION_OFF;
+            }
+        }
+
+        dbus_message_iter_next(&ait);
+    }
+}
+
+static void parse_property(DBusMessageIter *it,
+                           const char **name,
+                           int *type,
+                           DBusBasicValue *value)
+{
+    DBusMessageIter vit;
+
+    if (it && name && type && value) {
+        if (dbus_message_iter_get_arg_type(it) != DBUS_TYPE_STRING)
+            goto failed;
+
+        dbus_message_iter_get_basic(it, name);
+
+        if (!dbus_message_iter_next(it))
+            goto failed;
+
+        if (dbus_message_iter_get_arg_type(it) != DBUS_TYPE_VARIANT)
+            goto failed;
+
+        dbus_message_iter_recurse(it, &vit);
+
+        if ((*type = dbus_message_iter_get_arg_type(&vit)) == DBUS_TYPE_ARRAY)
+            goto failed;
+
+        dbus_message_iter_get_basic(&vit, value);
+
+        return;
+    }
+
+ failed:
+    *name = "<unknown>";
+    *type = 0;
+    memset(value, 0, sizeof(*value));
+}
+
+static void set_property(DBusMessageIter *it,
+                         const char *name,
+                         int type,
+                         void *value)
+{
+    char type_str[2] = { type, 0 };
+    DBusMessageIter vit;
+
+    dbus_message_iter_append_basic(it, DBUS_TYPE_STRING, &name);
+
+    dbus_message_iter_open_container(it, DBUS_TYPE_VARIANT, type_str, &vit);
+    dbus_message_iter_append_basic(&vit, type, value);
+    dbus_message_iter_close_container(it, &vit);
+}
+
+/*
+ * Local Variables:
+ * c-basic-offset: 4
+ * indent-tabs-mode: nil
+ * End:
+ *
+ */
diff --git a/src/plugins/bluetooth-client/dbusif.h b/src/plugins/bluetooth-client/dbusif.h
new file mode 100644 (file)
index 0000000..a7c5084
--- /dev/null
@@ -0,0 +1,45 @@
+#ifndef __SRS_MPRIS2_DBUS_INTERFACE_H__
+#define __SRS_MPRIS2_DBUS_INTERFACE_H__
+
+#include <stdint.h>
+#include <stdbool.h>
+
+#include <murphy/common/mainloop.h>
+
+#include "bluetooth-plugin.h"
+
+enum hfp_state_e {
+    VOICE_RECOGNITION_UNKNOWN,
+    VOICE_RECOGNITION_ON,
+    VOICE_RECOGNITION_OFF
+};
+
+struct modem_s {
+    mrp_list_hook_t link;
+    const char *path;
+    const char *name;
+    const char *addr;
+    context_t *ctx;
+    hfp_state_t state;
+    device_t *device;
+    int refcnt;
+};
+
+int  dbusif_create(context_t *ctx, mrp_mainloop_t *ml);
+void dbusif_destroy(context_t *ctx);
+
+int  dbusif_start(context_t *ctx);
+void dbusif_stop(context_t *ctx);
+
+int dbusif_set_voice_recognition(modem_t *modem, hfp_state_t state);
+
+
+#endif /* __SRS_MPRIS2_DBUS_INTERFACE_H__ */
+
+/*
+ * Local Variables:
+ * c-basic-offset: 4
+ * indent-tabs-mode: nil
+ * End:
+ *
+ */
diff --git a/src/plugins/bluetooth-client/pulseif.c b/src/plugins/bluetooth-client/pulseif.c
new file mode 100644 (file)
index 0000000..2d71dab
--- /dev/null
@@ -0,0 +1,1100 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+
+#include <pulse/pulseaudio.h>
+#include <pulse/mainloop.h>
+#include <pulse/introspect.h>
+
+#include <murphy/common/mm.h>
+#include <murphy/common/log.h>
+
+#include "pulseif.h"
+#include "dbusif.h"
+#include "clients.h"
+
+typedef struct {
+    mrp_list_hook_t link;
+    context_t *ctx;
+    pa_operation *op;
+} pending_op_t;
+
+
+
+static card_t *add_card(context_t *, uint32_t, const char *, const char *,
+                        const char *);
+static void remove_card(card_t *);
+
+static card_t *find_card_by_index(context_t *, uint32_t);
+static card_t *find_card_by_address(context_t *, const char *);
+static card_t *find_card_by_sink(context_t *, uint32_t);
+static card_t *find_card_by_source(context_t *, uint32_t);
+
+static pending_op_t *add_pending_op(context_t *);
+static void remove_pending_op(pending_op_t *);
+
+static void connect_to_server(context_t *);
+
+static int input_stream_create(card_t *);
+static int output_stream_create(card_t *);
+
+static void state_callback(pa_stream *, void *);
+static void read_callback(pa_stream *, size_t, void *);
+static void write_callback(pa_stream *, size_t, void *);
+
+static void subscribe_succes_callback(pa_context *, int, void *);
+static void profile_succes_callback(pa_context *, int, void *);
+
+static void context_callback(pa_context *, void *);
+static void event_callback(pa_context *, pa_subscription_event_type_t,
+                           uint32_t, void *);
+
+static void card_info_callback(pa_context *, const pa_card_info *,
+                               int, void *);
+static void source_info_callback(pa_context *, const pa_source_info *,
+                                 int, void *);
+static void sink_info_callback(pa_context *, const pa_sink_info *,
+                               int, void *);
+
+
+int pulseif_create(context_t *ctx, pa_mainloop *mloop)
+{
+    pulseif_t *pulseif = NULL;
+    pa_mainloop_api *api = NULL;
+
+    if (!(pulseif = mrp_allocz(sizeof(pulseif_t))))
+        goto failed;
+
+    api = pa_mainloop_get_api(mloop);
+
+#if 0
+    if (pa_signal_init(api) < 0)
+        goto failed;
+#endif
+
+    pulseif->mloop = mloop;
+    pulseif->rate = 16000;
+    pulseif->limit.upper = 1500;
+    pulseif->limit.lower = 100;
+
+    mrp_list_init(&pulseif->cards);
+    mrp_list_init(&pulseif->pending_ops);
+
+    ctx->pulseif = pulseif;
+
+    connect_to_server(ctx);
+
+    return 0;
+
+ failed:
+    mrp_log_error("bluetooth plugin: failed to create pulseaudio interface");
+    if (pulseif)
+        mrp_free(pulseif);
+    return -1;
+}
+
+void pulseif_destroy(context_t *ctx)
+{
+    pulseif_t *pulseif;
+    mrp_list_hook_t *entry, *n;
+    card_t *card;
+    pending_op_t *pend;
+
+    if (ctx && (pulseif = ctx->pulseif)) {
+        ctx->pulseif = NULL;
+
+        if (pulseif->subscr)
+            pa_operation_cancel(pulseif->subscr);
+
+        mrp_list_foreach(&pulseif->cards, entry, n) {
+            card = mrp_list_entry(entry, card_t, link);
+            remove_card(card);
+        }
+
+        mrp_list_foreach(&pulseif->pending_ops, entry, n) {
+            pend = mrp_list_entry(entry, pending_op_t, link);
+            remove_pending_op(pend);
+        }
+
+
+        if (pulseif->pactx) {
+            pa_context_set_state_callback(pulseif->pactx, NULL, NULL);
+            pa_context_set_subscribe_callback(pulseif->pactx, NULL, NULL);
+            pa_context_unref(pulseif->pactx);
+        }
+
+        mrp_free(pulseif);
+    }
+}
+
+int pulseif_set_card_profile(card_t *card, const char *profnam)
+{
+    context_t *ctx;
+    pulseif_t *pulseif;
+    pending_op_t *pend;
+
+    if (!card || !profnam || !(ctx = card->ctx) || !(pulseif = ctx->pulseif))
+        return -1;
+
+    if (card->profnam && !strcmp(profnam, card->profnam))
+        return 0;
+
+    pend = add_pending_op(ctx);
+    pend->op = pa_context_set_card_profile_by_index(pulseif->pactx, card->idx,
+                                                    profnam,
+                                                    profile_succes_callback,
+                                                    pend);
+    return 0;
+}
+
+int pulseif_add_input_stream_to_card(card_t *card)
+{
+    device_t *device;
+    context_t *ctx;
+    pulseif_t *pulseif;
+
+    if (!card || !(device = card->device) || !(ctx = device->ctx) ||
+        !(pulseif = ctx->pulseif))
+        return -1;
+
+    if (!card->source.name)
+        return 0;
+
+    printf("*** creating input stream\n");
+
+    return input_stream_create(card);
+}
+
+int pulseif_remove_input_stream_from_card(card_t *card)
+{
+    device_t *device;
+    context_t *ctx;
+    pulseif_t *pulseif;
+
+    if (!card || !(device = card->device) || !(ctx = device->ctx) ||
+        !(pulseif = ctx->pulseif))
+        return -1;
+
+    if (card->input.stream) {
+        printf("*** destroying input stream\n");
+        pa_stream_disconnect(card->input.stream);
+    }
+
+    return 0;
+}
+
+
+int pulseif_add_output_stream_to_card(card_t *card)
+{
+    device_t *device;
+    context_t *ctx;
+    pulseif_t *pulseif;
+
+    if (!card || !(device = card->device) || !(ctx = device->ctx) ||
+        !(pulseif = ctx->pulseif))
+        return -1;
+
+    if (!card->sink.name)
+        return 0;
+
+    printf("*** creating output stream\n");
+
+    return output_stream_create(card);
+}
+
+int pulseif_remove_output_stream_from_card(card_t *card)
+{
+    device_t *device;
+    context_t *ctx;
+    pulseif_t *pulseif;
+
+    if (!card || !(device = card->device) || !(ctx = device->ctx) ||
+        !(pulseif = ctx->pulseif))
+        return -1;
+
+    if (card->output.stream) {
+        printf("*** destroying output stream\n");
+        pa_stream_disconnect(card->output.stream);
+    }
+
+    return 0;
+}
+
+
+static card_t *add_card(context_t *ctx,
+                        uint32_t idx,
+                        const char *name,
+                        const char *btaddr,
+                        const char *profnam)
+{
+    pulseif_t *pulseif;
+    card_t *card = NULL;
+
+    if (ctx &&  (pulseif = ctx->pulseif) && name && btaddr && profnam) {
+        if (!find_card_by_index(ctx, idx)) {
+            if ((card = mrp_allocz(sizeof(*card)))) {
+                mrp_list_prepend(&pulseif->cards, &card->link);
+                card->ctx = ctx;
+                card->idx = idx;
+                card->name = mrp_strdup(name);
+                card->btaddr = mrp_strdup(btaddr);
+                card->profnam = mrp_strdup(profnam);
+                card->sink.idx = -1;
+                card->source.idx = -1;
+            }
+        }
+    }
+
+    return card;
+}
+
+static void remove_card(card_t *card)
+{
+    pa_stream *stream;
+
+    if (card) {
+        if ((stream = card->input.stream)) {
+            pa_stream_set_state_callback(stream, NULL, NULL);
+            pa_stream_set_underflow_callback(stream, NULL, NULL);
+            pa_stream_set_suspended_callback(stream, NULL, NULL);
+            pa_stream_set_read_callback(stream, NULL, NULL);
+        }
+
+        if ((stream = card->output.stream)) {
+            pa_stream_set_state_callback(stream, NULL, NULL);
+            pa_stream_set_underflow_callback(stream, NULL, NULL);
+            pa_stream_set_suspended_callback(stream, NULL, NULL);
+            pa_stream_set_write_callback(stream, NULL, NULL);
+        }
+
+        mrp_list_delete(&card->link);
+        mrp_free((void *)card->name);
+        mrp_free((void *)card->btaddr);
+        mrp_free((void *)card->profnam);
+        mrp_free((void *)card->sink.name);
+        mrp_free((void *)card->source.name);
+
+        mrp_free((void *)card);
+    }
+}
+
+static card_t *find_card_by_index(context_t *ctx, uint32_t idx)
+{
+    pulseif_t *pulseif;
+    mrp_list_hook_t *entry, *n;
+    card_t *card;
+
+    if (ctx && (pulseif = ctx->pulseif)) {
+        mrp_list_foreach(&pulseif->cards, entry, n) {
+            card = mrp_list_entry(entry, card_t, link);
+
+            if (idx == card->idx)
+                return card;
+        }
+    }
+
+    return NULL;
+}
+
+
+static card_t *find_card_by_address(context_t *ctx, const char *addr)
+{
+    pulseif_t *pulseif;
+    mrp_list_hook_t *entry, *n;
+    card_t *card;
+
+    if (ctx && (pulseif = ctx->pulseif) && addr) {
+        mrp_list_foreach(&pulseif->cards, entry, n) {
+            card = mrp_list_entry(entry, card_t, link);
+
+            if (!strcmp(addr, card->btaddr))
+                return card;
+        }
+    }
+
+    return NULL;
+}
+
+
+static card_t *find_card_by_sink(context_t *ctx, uint32_t idx)
+{
+    pulseif_t *pulseif;
+    mrp_list_hook_t *entry, *n;
+    card_t *card;
+
+    if (ctx && (pulseif = ctx->pulseif)) {
+        mrp_list_foreach(&pulseif->cards, entry, n) {
+            card = mrp_list_entry(entry, card_t, link);
+
+            if (idx == card->sink.idx)
+                return card;
+        }
+    }
+
+    return NULL;
+}
+
+
+static card_t *find_card_by_source(context_t *ctx, uint32_t idx)
+{
+    pulseif_t *pulseif;
+    mrp_list_hook_t *entry, *n;
+    card_t *card;
+
+    if (ctx && (pulseif = ctx->pulseif)) {
+        mrp_list_foreach(&pulseif->cards, entry, n) {
+            card = mrp_list_entry(entry, card_t, link);
+
+            if (idx == card->source.idx)
+                return card;
+        }
+    }
+
+    return NULL;
+}
+
+
+static pending_op_t *add_pending_op(context_t *ctx)
+{
+    pulseif_t *pulseif;
+    pending_op_t *pending = NULL;
+
+    if (ctx && (pulseif = ctx->pulseif)) {
+        if ((pending = mrp_allocz(sizeof(pending_op_t)))) {
+            mrp_list_prepend(&pulseif->pending_ops, &pending->link);
+            pending->ctx = ctx;
+        }
+    }
+
+    return pending;
+}
+
+static void remove_pending_op(pending_op_t *pending)
+{
+    if (pending) {
+        mrp_list_delete(&pending->link);
+        pa_operation_cancel(pending->op);
+        mrp_free((void *)pending);
+    }
+}
+
+static void connect_to_server(context_t *ctx)
+{
+    pulseif_t *pulseif = ctx->pulseif;
+    pa_mainloop_api *api = pa_mainloop_get_api(pulseif->mloop);
+    pa_context *pactx;
+
+    if (pulseif->subscr)
+        pa_operation_cancel(pulseif->subscr);
+
+    if (pulseif->pactx) {
+        pa_context_set_state_callback(pulseif->pactx, NULL, NULL);
+        pa_context_set_subscribe_callback(pulseif->pactx, NULL, NULL);
+        pa_context_unref(pulseif->pactx);
+        pulseif->pactx = NULL;
+    }
+
+    if (!(pulseif->pactx = pactx = pa_context_new(api, "bluetooth"))) {
+        mrp_log_error("pa_context_new() failed");
+        return;
+    }
+
+    pa_context_set_state_callback(pactx, context_callback, ctx);
+    pa_context_set_subscribe_callback(pactx, event_callback, ctx);
+
+    mrp_log_error("bluetooth-plugin: Trying to connect to pulseaudio ...");
+    pa_context_connect(pactx, NULL, PA_CONTEXT_NOAUTOSPAWN, NULL);
+}
+
+
+static int input_stream_create(card_t *card)
+{
+    device_t *device;
+    context_t *ctx;
+    pulseif_t *pulseif;
+    uint32_t minreq = 100;      /* length in msecs */
+    uint32_t target = 1000;     /* length in msecs */
+    pa_sample_spec spec;
+    pa_buffer_attr battr;
+    pa_proplist *pl;
+    size_t minsiz, bufsiz, extra, size;
+
+    if (!card || !(device = card->device) || !(ctx = device->ctx) ||
+        !(pulseif = ctx->pulseif) || !(card->source.name))
+        return -1;
+
+    if (card->input.stream)
+        return 0;
+
+    memset(&spec, 0, sizeof(spec));
+    spec.format = PA_SAMPLE_S16LE;
+    spec.rate = pulseif->rate;
+    spec.channels = 1; /* ie. MONO */
+
+    minsiz = pa_usec_to_bytes(minreq * PA_USEC_PER_MSEC, &spec);
+    bufsiz = pa_usec_to_bytes(target * PA_USEC_PER_MSEC, &spec);
+    extra  = minsiz * 2;
+    size   = bufsiz + extra;
+
+
+    pl = pa_proplist_new();
+    pa_proplist_sets(pl, PA_PROP_MEDIA_ROLE, "speech");
+
+    card->input.stream = pa_stream_new_with_proplist(pulseif->pactx,
+                                                     "speech-recognition",
+                                                     &spec, NULL, pl);
+    pa_proplist_free(pl);
+
+    if (!card->input.stream) {
+        mrp_log_error("bluetooth client: failed to create input stream "
+                      "for card %s", card->btaddr);
+        return -1;
+    }
+
+    battr.maxlength = -1;       /* default (4MB) */
+    battr.tlength   = size;
+    battr.minreq    = minsiz;
+    battr.prebuf    = 2 * battr.tlength;
+    battr.fragsize  = battr.tlength;
+
+    pa_stream_set_state_callback(card->input.stream, state_callback, card);
+    pa_stream_set_read_callback(card->input.stream, read_callback, card);
+
+    pa_stream_connect_record(card->input.stream, card->source.name, &battr,
+                             PA_STREAM_ADJUST_LATENCY);
+
+    card->input.state = ST_BEGIN;
+
+    return 0;
+}
+
+
+static int output_stream_create(card_t *card)
+{
+    device_t *device;
+    context_t *ctx;
+    pulseif_t *pulseif;
+    uint32_t minreq = 100;     /* length in msecs */
+    uint32_t target = 1000;    /* length in msecs */
+    pa_sample_spec spec;
+    pa_buffer_attr battr;
+    pa_proplist *pl;
+    size_t minsiz, bufsiz, extra, size;
+
+    if (!card || !(device = card->device) || !(ctx = device->ctx) ||
+        !(pulseif = ctx->pulseif) || !(card->sink.name))
+        return -1;
+
+    if (card->output.stream)
+        return 0;
+
+    memset(&spec, 0, sizeof(spec));
+    spec.format = PA_SAMPLE_S16LE;
+    spec.rate = pulseif->rate;
+    spec.channels = 1; /* ie. MONO */
+
+    minsiz = pa_usec_to_bytes(minreq * PA_USEC_PER_MSEC, &spec);
+    bufsiz = pa_usec_to_bytes(target * PA_USEC_PER_MSEC, &spec);
+    extra  = minsiz * 2;
+    size   = bufsiz + extra;
+
+    pl = pa_proplist_new();
+    pa_proplist_sets(pl, PA_PROP_MEDIA_ROLE, "speech");
+
+    card->output.stream = pa_stream_new_with_proplist(pulseif->pactx,
+                                                      "speech-recognition",
+                                                      &spec, NULL, pl);
+    pa_proplist_free(pl);
+
+    if (!card->output.stream) {
+        mrp_log_error("bluetooth client: failed to create output stream "
+                      "for card %s", card->btaddr);
+        return -1;
+    }
+
+    battr.maxlength = -1;       /* default (4MB) */
+    battr.tlength   = size;
+    battr.minreq    = minsiz;
+    battr.prebuf    = 2 * battr.tlength;
+    battr.fragsize  = battr.tlength;
+
+    pa_stream_set_state_callback(card->output.stream, state_callback, card);
+    pa_stream_set_write_callback(card->output.stream, write_callback, card);
+
+    pa_stream_connect_playback(card->output.stream, card->sink.name, &battr,
+                               PA_STREAM_ADJUST_LATENCY, NULL, NULL);
+    return 0;
+}
+
+
+static void state_callback(pa_stream *stream, void *userdata)
+{
+    card_t *card = (card_t *)userdata;
+    pa_context *pactx = pa_stream_get_context(stream);
+    pa_context_state_t ctxst = pa_context_get_state(pactx);
+    device_t *device;
+    context_t *ctx;
+    pulseif_t *pulseif;
+    int err;
+    const char *strerr;
+    const char *type;
+
+    if (ctxst == PA_CONTEXT_TERMINATED || ctxst == PA_CONTEXT_FAILED)
+        return;
+
+    if (!card || !(device = card->device) || !(ctx = device->ctx) ||
+        !(pulseif = ctx->pulseif))
+        return;
+
+    switch (pa_stream_get_state(stream)) {
+
+    case PA_STREAM_CREATING:
+        if (stream == card->input.stream) {
+            mrp_debug("bluetooth plugin: input stream on %s creating",
+                      card->btaddr);
+        }
+        else if (stream == card->output.stream) {
+            mrp_debug("bluetooth plugin: output stream on %s creating",
+                      card->btaddr);
+        }
+        break;
+
+    case PA_STREAM_TERMINATED:
+        if (stream == card->input.stream) {
+            mrp_log_info("bluetooth plugin: input stream on %s terminated",
+                         card->btaddr);
+            card->input.stream = NULL;
+            pa_stream_set_state_callback(stream, NULL, NULL);
+            pa_stream_set_underflow_callback(stream, NULL, NULL);
+            pa_stream_set_suspended_callback(stream, NULL, NULL);
+            pa_stream_set_read_callback(stream, NULL, NULL);
+        }
+        else if (stream == card->output.stream) {
+            mrp_log_info("bluetooth plugin: output stream on %s terminated",
+                         card->btaddr);
+            card->output.stream = NULL;
+            card->output.sent = 0;
+            pa_stream_set_state_callback(stream, NULL, NULL);
+            pa_stream_set_underflow_callback(stream, NULL, NULL);
+            pa_stream_set_suspended_callback(stream, NULL, NULL);
+            pa_stream_set_write_callback(stream, NULL, NULL);
+        }
+        break;
+
+    case PA_STREAM_READY:
+        if (stream == card->input.stream) {
+            mrp_log_info("bluetooth plugin: input stream on %s is ready",
+                         card->btaddr);
+        }
+        else if (stream == card->output.stream) {
+            mrp_log_info("bluetooth plugin: output stream on %s is ready",
+                         card->btaddr);
+        }
+        break;
+
+    case PA_STREAM_FAILED:
+    default:
+        if ((err = pa_context_errno(pactx))) {
+            if (stream == card->input.stream)
+                type = "input";
+            else if (stream == card->output.stream)
+                type = "output";
+            else
+                break;
+
+            if (!(strerr = pa_strerror(err))) {
+                mrp_log_error("bluetooth plugin: %s stream error on %s",
+                              type, card->btaddr);
+            }
+            else {
+                mrp_log_error("bluetooth plugin: %s stream error on %s: %s",
+                              type, card->btaddr, strerr);
+            }
+        }
+        break;
+    }
+}
+
+static void read_callback(pa_stream *stream, size_t bytes, void *userdata)
+{
+    card_t *card = (card_t *)userdata;
+    device_t *device;
+    context_t *ctx;
+    pulseif_t *pulseif;
+    const void *data;
+    size_t size;
+    size_t n, i;
+    double sample;
+    double m;
+    int16_t *s;
+
+    if (!card || !(device = card->device) || !(ctx = device->ctx) ||
+        !(pulseif = ctx->pulseif))
+        goto confused;
+
+    if (card->input.stream && stream != card->input.stream)
+        goto confused;
+
+
+    pa_stream_peek(stream, &data, &size);
+
+    if (data && size && card->input.stream) {
+        if (card->input.state == ST_BEGIN || card->input.state == ST_CLING) {
+            n = size / sizeof(int16_t);
+            s = (int16_t *)data;
+            m = 0.0;
+
+            for (i = 0;   i < n;   i++) {
+                sample = (double)s[i];
+                m += sample > 0.0 ? sample : -sample;
+            }
+
+            m /= (double)n;
+
+            if (card->input.state == ST_BEGIN) {
+                if (m > pulseif->limit.upper)
+                    card->input.state = ST_CLING;
+            }
+            else {
+                if (m < pulseif->limit.lower) {
+                    printf("*** cling ends\n");
+                    card->input.state = ST_READY;
+
+                    if (device->nsample && device->samples)
+                        pulseif_add_output_stream_to_card(card);
+                }
+            }
+        }
+    }
+
+    if (size)
+        pa_stream_drop(stream);
+
+    return;
+
+ confused:
+    mrp_log_error("bluetooth plugin: %s() confused with internal "
+                  "data structures", __FUNCTION__);
+}
+
+
+static void write_callback(pa_stream *stream, size_t bytes, void *userdata)
+{
+    static int16_t silence[16000 * sizeof(int16_t)]; /* 1 sec @ 16KHz mono */
+
+    card_t *card = (card_t *)userdata;
+    device_t *device;
+    context_t *ctx;
+    pulseif_t *pulseif;
+    size_t size, len;
+    int16_t *data;
+
+    if (!card || !(device = card->device) || !(ctx = device->ctx) ||
+        !(pulseif = ctx->pulseif))
+        goto confused;
+
+    if (card->output.stream && stream != card->output.stream)
+        goto confused;
+
+    while (bytes > 0) {
+
+        if (card->input.state != ST_READY || !device->samples ||
+            device->nsample <= card->output.sent)
+        {
+            len = (sizeof(silence) < bytes) ? len : bytes;
+
+            if (pa_stream_write(stream,silence,len,NULL,0,PA_SEEK_RELATIVE)<0)
+                goto could_not_write;
+        }
+        else {
+            size = device->nsample - card->output.sent;
+            len = size * 2;
+
+            if (len > bytes)
+                len = bytes;
+
+            data = device->samples + card->output.sent;
+
+            if (pa_stream_write(stream, data,len, NULL,0,PA_SEEK_RELATIVE) < 0)
+                goto could_not_write;
+
+            card->output.sent += len / 2;
+        }
+
+        if (bytes < len)
+            bytes = 0;
+        else
+            bytes -= len;
+    }
+
+    return;
+
+ confused:
+    mrp_log_error("bluetooth plugin: %s() confused with internal "
+                  "data structures", __FUNCTION__);
+    return;
+
+ could_not_write:
+    mrp_log_error("bluetooth plugin: could not write %u bytes to stream %s",
+                  bytes, device->btaddr);
+}
+
+
+static void subscribe_succes_callback(pa_context *c,int success,void *userdata)
+{
+    context_t *ctx = (context_t *)userdata;
+    pulseif_t *pulseif;
+
+    if (!ctx || !(pulseif = ctx->pulseif) || c != pulseif->pactx) {
+        mrp_log_error("bluetooth plugin: confused with internal data "
+                      "structures");
+        return;
+    }
+
+    if (!success) {
+        mrp_log_error("bluetooth plugin: failed to subscribe "
+                      "pulseaudio events");
+    }
+
+    pulseif->subscr = NULL;
+}
+
+static void profile_succes_callback(pa_context *c, int success, void *userdata)
+{
+    context_t *ctx = (context_t *)userdata;
+    pulseif_t *pulseif;
+
+    if (!ctx || !(pulseif = ctx->pulseif) || c != pulseif->pactx) {
+        mrp_log_error("bluetooth plugin: confused with internal data "
+                      "structures");
+        return;
+    }
+
+    if (!success) {
+        mrp_log_error("bluetooth plugin: failed to subscribe "
+                      "pulseaudio events");
+    }
+
+    pulseif->subscr = NULL;
+}
+
+static void context_callback(pa_context *pactx, void *userdata)
+{
+    static pa_subscription_mask_t mask =
+        PA_SUBSCRIPTION_MASK_CARD   |
+        PA_SUBSCRIPTION_MASK_SINK   |
+        PA_SUBSCRIPTION_MASK_SOURCE ;
+
+    context_t *ctx = (context_t *)userdata;
+    pulseif_t *pulseif = ctx->pulseif;
+    int err = 0;
+    const char *strerr;
+    pending_op_t *pend;
+
+    if (!pactx) {
+        mrp_log_error("bluetooth plugin: %s() called with zero context",
+                      __FUNCTION__);
+        return;
+    }
+
+    if (pulseif->pactx != pactx) {
+        mrp_log_error("bluetooth plugin: %s(): Confused with data structures",
+                      __FUNCTION__);
+        return;
+    }
+
+    switch (pa_context_get_state(pactx)) {
+
+    case PA_CONTEXT_CONNECTING:
+        pulseif->conup = false;
+        mrp_debug("bleutooth plugin: connecting to pulseaudio server");
+        break;
+
+    case PA_CONTEXT_AUTHORIZING:
+        pulseif->conup = false;
+        mrp_debug("   bluetooth plugin: authorizing");
+        break;
+
+    case PA_CONTEXT_SETTING_NAME:
+        pulseif->conup = false;
+        mrp_debug("   bluetooth plugin: setting name");
+        break;
+
+    case PA_CONTEXT_READY:
+        pulseif->conup = true;
+        pulseif->subscr = pa_context_subscribe(pactx, mask,
+                                               subscribe_succes_callback, ctx);
+        pend = add_pending_op(ctx);
+        pend->op = pa_context_get_card_info_list(pactx,
+                                                 card_info_callback, pend);
+        pend = add_pending_op(ctx);
+        pend->op = pa_context_get_sink_info_list(pactx,
+                                                 sink_info_callback, pend);
+        pend = add_pending_op(ctx);
+        pend->op = pa_context_get_source_info_list(pactx,
+                                                   source_info_callback, pend);
+        mrp_log_info("bluetooth plugin: pulseaudio connection established");
+        break;
+
+    case PA_CONTEXT_TERMINATED:
+        mrp_log_info("bluetooth plugin: pulseaudio connection terminated");
+        goto disconnect;
+
+    case PA_CONTEXT_FAILED:
+    default:
+        if ((err = pa_context_errno(pactx)) != 0) {
+            if ((strerr = pa_strerror(err)) == NULL)
+                strerr = "<unknown>";
+            mrp_log_error("bluetooth plugin: pulseaudio server "
+                          "connection error: %s", strerr);
+        }
+
+    disconnect:
+        pulseif->conup = false;
+        break;
+    }
+}
+
+
+static void event_callback(pa_context *c,
+                           pa_subscription_event_type_t t,
+                           uint32_t idx,
+                           void *userdata)
+{
+    context_t *ctx;
+    pulseif_t *pulseif;
+    pa_subscription_event_type_t facility;
+    pa_subscription_event_type_t type;
+    card_t *card;
+    device_t *dev;
+    pending_op_t *pend;
+
+    ctx = (context_t *)userdata;
+    facility = t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK;
+    type = t & PA_SUBSCRIPTION_EVENT_TYPE_MASK;
+
+    if (!ctx || !(pulseif = ctx->pulseif) || c != pulseif->pactx) {
+        mrp_log_error("bluetooth plugin: %s() confused with internal"
+                      "data structures", __FUNCTION__);
+        return;
+    }
+
+    switch (facility) {
+
+    case PA_SUBSCRIPTION_EVENT_CARD:
+        switch (type) {
+        case PA_SUBSCRIPTION_EVENT_NEW:
+            mrp_debug("bletooth module: pulseudio module %u appeared\n", idx);
+            pend = add_pending_op(ctx);
+            pend->op = pa_context_get_card_info_by_index(c, idx,
+                                                         card_info_callback,
+                                                         pend);
+            break;
+        case PA_SUBSCRIPTION_EVENT_REMOVE:
+            if ((card = find_card_by_index(ctx, idx))) {
+                printf("*** card %u gone\n", idx);
+                if ((dev = card->device))
+                    clients_remove_card_from_device(dev);
+                remove_card(card);
+            }
+            break;
+        case PA_SUBSCRIPTION_EVENT_CHANGE:
+            break;
+        default:
+            goto cant_handle_it;
+        }
+        break;
+
+    case PA_SUBSCRIPTION_EVENT_SINK:
+        switch (type) {
+        case PA_SUBSCRIPTION_EVENT_NEW:
+            mrp_debug("bletooth module: pulseudio sink %u appeared\n", idx);
+            pend = add_pending_op(ctx);
+            pend->op = pa_context_get_sink_info_by_index(c, idx,
+                                                         sink_info_callback,
+                                                         pend);
+            break;
+        case PA_SUBSCRIPTION_EVENT_REMOVE:
+            if ((card = find_card_by_sink(ctx, idx))) {
+                printf("*** sink %u gone\n", idx);
+                mrp_free((void *)card->sink.name);
+                card->sink.name = NULL;
+                card->sink.idx = -1;
+            }
+            break;
+        case PA_SUBSCRIPTION_EVENT_CHANGE:
+            break;
+        default:
+            goto cant_handle_it;
+        }
+        break;
+
+    case PA_SUBSCRIPTION_EVENT_SOURCE:
+        switch (type) {
+        case PA_SUBSCRIPTION_EVENT_NEW:
+            mrp_debug("bletooth module: pulseudio source %u appeared\n", idx);
+            pend = add_pending_op(ctx);
+            pend->op = pa_context_get_source_info_by_index(c, idx,
+                                                          source_info_callback,
+                                                          pend);
+            break;
+        case PA_SUBSCRIPTION_EVENT_REMOVE:
+            if ((card = find_card_by_source(ctx, idx))) {
+                printf("*** source %u gone\n", idx);
+                mrp_free((void *)card->source.name);
+                card->source.name = NULL;
+                card->source.idx = -1;
+            }
+            break;
+        case PA_SUBSCRIPTION_EVENT_CHANGE:
+            break;
+        default:
+            goto cant_handle_it;
+        }
+        break;
+
+    default:
+        goto cant_handle_it;
+    }
+
+    return;
+
+ cant_handle_it:
+    mrp_log_error("bluetooth plugin: invalid pulseaudio event");
+}
+
+
+static void card_info_callback(pa_context *c,
+                               const pa_card_info *i,
+                               int eol,
+                               void *userdata)
+{
+    pending_op_t *pend = (pending_op_t *)userdata;
+    context_t *ctx;
+    card_t *card;
+    device_t *dev;
+    pa_card_profile_info *p;
+    bool has_hfgw_profile;
+    const char *btaddr;
+    uint32_t j;
+
+    if (pend && (ctx = pend->ctx)) {
+        if (eol)
+            remove_pending_op(pend);
+        else if (i && !strncmp(i->name, "bluez_card.", 11)) {
+            btaddr = pa_proplist_gets(i->proplist, "device.string");
+
+            for (j = 0, has_hfgw_profile = false;   j < i->n_profiles;   j++) {
+                p = i->profiles + j;
+
+                if (!strcmp(p->name, "hfgw") && p->n_sinks && p->n_sources) {
+                    has_hfgw_profile = true;
+                    break;
+                }
+            }
+
+            if (btaddr && has_hfgw_profile && (p = i->active_profile)) {
+                printf("*** card %u '%s' %s %s\n",
+                       i->index, i->name, btaddr, p->name);
+
+                if ((dev = clients_find_device(ctx, btaddr)) && !dev->card &&
+                    (card = add_card(ctx, i->index, i->name, btaddr, p->name)))
+                {
+                    printf("*** card added\n");
+
+                    card->device = dev;
+                    clients_add_card_to_device(dev, card);
+                }
+            }
+        }
+    }
+}
+
+static void source_info_callback(pa_context *c,
+                                 const pa_source_info *i,
+                                 int eol,
+                                 void *userdata)
+{
+    pending_op_t *pend = (pending_op_t *)userdata;
+    context_t *ctx;
+    card_t *card;
+    device_t *dev;
+    modem_t *modem;
+    const char *proto;
+    const char *btaddr;
+    uint32_t j;
+
+    if (pend && (ctx = pend->ctx)) {
+        if (eol)
+            remove_pending_op(pend);
+        else if (i && !strncmp(i->name, "bluez_source.", 11)) {
+            proto  = pa_proplist_gets(i->proplist, "bluetooth.protocol");
+            btaddr = pa_proplist_gets(i->proplist, "device.string");
+
+            if (btaddr && proto && !strcmp(proto, "hfgw")) {
+                if ((dev = clients_find_device(ctx, btaddr)) &&
+                    (card = dev->card) && (modem = dev->modem))
+                {
+                    printf("*** source %u %s %s\n", i->index, i->name, btaddr);
+                    mrp_free((void *)card->source.name);
+                    card->source.name = mrp_strdup(i->name);
+                    card->source.idx = i->index;
+
+                    if (modem->state == VOICE_RECOGNITION_ON)
+                        pulseif_add_input_stream_to_card(card);
+                }
+            }
+        }
+    }
+}
+
+static void sink_info_callback(pa_context *c,
+                               const pa_sink_info *i,
+                               int eol,
+                               void *userdata)
+{
+    pending_op_t *pend = (pending_op_t *)userdata;
+    context_t *ctx;
+    card_t *card;
+    device_t *dev;
+    modem_t *modem;
+    const char *proto;
+    const char *btaddr;
+    uint32_t j;
+
+    if (pend && (ctx = pend->ctx)) {
+        if (eol)
+            remove_pending_op(pend);
+        else if (i && !strncmp(i->name, "bluez_sink.", 11)) {
+            proto  = pa_proplist_gets(i->proplist, "bluetooth.protocol");
+            btaddr = pa_proplist_gets(i->proplist, "device.string");
+
+            if (btaddr && proto && !strcmp(proto, "hfgw")) {
+                if ((dev = clients_find_device(ctx, btaddr)) &&
+                    (card = dev->card) && (modem = dev->modem))
+                {
+                    printf("*** sink %u %s %s\n", i->index, i->name, btaddr);
+                    mrp_free((void *)card->sink.name);
+                    card->sink.name = mrp_strdup(i->name);
+                    card->sink.idx = i->index;
+
+                    if (modem->state == VOICE_RECOGNITION_ON &&
+                        card->input.state == ST_READY)
+                    {
+                        pulseif_add_output_stream_to_card(card);
+                    }
+                }
+            }
+        }
+    }
+}
+
+/*
+ * Local Variables:
+ * c-basic-offset: 4
+ * indent-tabs-mode: nil
+ * End:
+ *
+ */
diff --git a/src/plugins/bluetooth-client/pulseif.h b/src/plugins/bluetooth-client/pulseif.h
new file mode 100644 (file)
index 0000000..715dc08
--- /dev/null
@@ -0,0 +1,77 @@
+#ifndef __SRS_BLUETOOTH_PULSEIF_H__
+#define __SRS_BLUETOOTH_PULSEIF_H__
+
+#include <pulse/pulseaudio.h>
+#include <pulse/mainloop.h>
+#include <pulse/subscribe.h>
+
+#include "bluetooth-plugin.h"
+
+struct pulseif_s {
+    pa_mainloop *mloop;
+    pa_context *pactx;
+    pa_operation *subscr;
+    mrp_list_hook_t cards;
+    mrp_list_hook_t pending_ops;
+    bool conup;
+    uint32_t rate;
+    struct {
+        double upper;
+        double lower;
+    } limit;
+};
+
+struct card_s {
+    mrp_list_hook_t link;
+    context_t *ctx;
+    uint32_t idx;
+    const char *name;
+    const char *btaddr;
+    const char *profnam;
+    device_t *device;
+    struct {
+        uint32_t idx;
+        const char *name;
+    } sink;
+    struct {
+        uint32_t idx;
+        const char *name;
+    } source;
+    struct {
+        pa_stream *stream;
+        enum {
+            ST_BEGIN = 0,
+            ST_CLING,
+            ST_READY
+        } state;
+    } input;
+    struct {
+        pa_stream *stream;
+        size_t sent;
+    } output;
+};
+
+
+
+
+int  pulseif_create(context_t *ctx, pa_mainloop *mloop);
+void pulseif_destroy(context_t *ctx);
+
+int pulseif_set_card_profile(card_t *card, const char *profnam);
+
+int pulseif_add_output_stream_to_card(card_t *card);
+int pulseif_remove_output_stream_from_card(card_t *card);
+
+int pulseif_add_input_stream_to_card(card_t *card);
+int pulseif_remove_input_stream_from_card(card_t *card);
+
+
+#endif /* __SRS_BLUETOOTH_PULSEIF_H__ */
+
+/*
+ * Local Variables:
+ * c-basic-offset: 4
+ * indent-tabs-mode: nil
+ * End:
+ *
+ */