# 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 *~
--- /dev/null
+/*
+ * 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:
+ *
+ */
--- /dev/null
+#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:
+ *
+ */
--- /dev/null
+#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:
+ *
+ */
--- /dev/null
+#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:
+ *
+ */
--- /dev/null
+#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:
+ *
+ */
--- /dev/null
+#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:
+ *
+ */
--- /dev/null
+#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:
+ *
+ */
--- /dev/null
+#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:
+ *
+ */