summaryrefslogtreecommitdiffstats
path: root/plugins/lib/bluealsa/hal-bluealsa.c
diff options
context:
space:
mode:
Diffstat (limited to 'plugins/lib/bluealsa/hal-bluealsa.c')
-rw-r--r--plugins/lib/bluealsa/hal-bluealsa.c234
1 files changed, 210 insertions, 24 deletions
diff --git a/plugins/lib/bluealsa/hal-bluealsa.c b/plugins/lib/bluealsa/hal-bluealsa.c
index 853ab05..ebc0560 100644
--- a/plugins/lib/bluealsa/hal-bluealsa.c
+++ b/plugins/lib/bluealsa/hal-bluealsa.c
@@ -25,6 +25,8 @@
#include <ctl-config.h>
#include <systemd/sd-event.h>
#include <bluealsa/bluealsa.h>
+#include <systemd/sd-bus.h>
+#include <urcu/list.h>
#include "hal-bluealsa.h"
@@ -36,6 +38,12 @@
#define SCO_TALK_ZONE "sco_talk_zone"
#define SCO_CHANNEL_MONO "sco-mono"
+#define ORG_FREEDESKTOP_DBUS_NAME "org.freedesktop.DBus"
+#define ORG_FREEDESKTOP_DBUS_PATH "/org/freedesktop/DBus"
+#define ORG_FREEDESKTOP_DBUS_INTERFACE ORG_FREEDESKTOP_DBUS_NAME
+
+#define BLUEZALSA_DBUS_NAME_PREFIX "org.bluez-alsa"
+
/* forward static declarations */
static int halBlueAlsaTransportEventCB(sd_event_source* src, int fd, uint32_t revents, void* userData);
static int halBlueAlsaFetchTransports(bluealsa_watch * plugin);
@@ -171,7 +179,7 @@ static int halBlueAlsaTransportEventCB(sd_event_source* src, int fd, uint32_t re
struct ba_msg_event event;
ssize_t ret;
- AFB_API_DEBUG(plugin->api, "--- %s ----!", __func__);
+ AFB_API_DEBUG(plugin->api, "------ %s ------!", __func__);
if ((revents & EPOLLIN) == 0)
goto done;
@@ -179,8 +187,6 @@ static int halBlueAlsaTransportEventCB(sd_event_source* src, int fd, uint32_t re
if (revents & EPOLLHUP) {
AFB_API_INFO(plugin->api, "Lost connection with bluealsa on interface %s", watch->interface);
sd_event_source_unref(src);
- close(fd);
- halBlueAlsaRegister(plugin, watch->interface);
goto done;
}
@@ -451,12 +457,12 @@ static int halBlueAlsaAttachTransportStreams(bluealsa_transport_t * transport) {
char * playbackZoneS;
uint32_t delayms;
- if (ba_transport->type == BA_PCM_TYPE_SCO) {
+ if (ba_transport->type & BA_PCM_TYPE_SCO) {
transportTypeS = "sco";
delayms = pluginData->sco.delayms;
playbackZoneS = pluginData->sco.speaker;
}
- else if (ba_transport->type == BA_PCM_TYPE_A2DP) {
+ else if (ba_transport->type & BA_PCM_TYPE_A2DP) {
transportTypeS = "a2dp";
delayms = pluginData->a2dp.delayms;
playbackZoneS = pluginData->a2dp.zone;
@@ -492,14 +498,14 @@ static int halBlueAlsaAttachTransportStreams(bluealsa_transport_t * transport) {
pcmplugParamsJ = halBlueAlsaPcmPlugParams(watch->interface, addr, transportTypeS);
sourceJ = json_object_new_object();
- if (ba_transport->type == BA_PCM_TYPE_A2DP)
+ if (ba_transport->type & BA_PCM_TYPE_A2DP)
json_object_object_add(sourceJ, "channels", halBlueAlsaA2DPTransportChannels(transportTypeS));
else
json_object_object_add(sourceJ, "channels", halBlueAlsaSCOTransportChannels(transportTypeS));
json_object_object_add(requestJ, "captures", halBlueAlsaListenCapture(captureS, pcmplugParamsJ, sourceJ));
- if (ba_transport->type == BA_PCM_TYPE_SCO) {
+ if (ba_transport->type & BA_PCM_TYPE_SCO) {
playbackJ = json_object_new_object();
// that is a shame that deep copy in not available in the current json-c version
pcmplugParamsJ2 = halBlueAlsaPcmPlugParams(watch->interface, addr, transportTypeS);
@@ -512,7 +518,7 @@ static int halBlueAlsaAttachTransportStreams(bluealsa_transport_t * transport) {
/* ZONES */
- if (ba_transport->type == BA_PCM_TYPE_SCO) {
+ if (ba_transport->type & BA_PCM_TYPE_SCO) {
zonesJ = json_object_new_array();
json_object_array_add(zonesJ, halBlueAlsaScoZone());
json_object_object_add(requestJ, "zones", zonesJ);
@@ -537,7 +543,7 @@ static int halBlueAlsaAttachTransportStreams(bluealsa_transport_t * transport) {
json_object_array_add(streamsJ, streamJ);
/* In case of SCO, to have full-duplex, we instantiate a stream for talk */
- if (ba_transport->type == BA_PCM_TYPE_SCO ) {
+ if (ba_transport->type & BA_PCM_TYPE_SCO ) {
streamJ = json_object_new_object();
if (asprintf(&streamS, "%s_talk_stream", transportTypeS) == -1)
@@ -630,14 +636,20 @@ static int halBlueAlsaFetchTransports(bluealsa_watch * watch) {
struct ba_msg_transport * ba_transport = &transports[ix];
ba2str(&ba_transport->addr, addr);
const char * typeS;
- if (ba_transport->type == BA_PCM_TYPE_SCO)
+ if (ba_transport->type & BA_PCM_TYPE_SCO)
typeS = "sco";
- else if (ba_transport->type == BA_PCM_TYPE_A2DP)
+ else if (ba_transport->type & BA_PCM_TYPE_A2DP)
typeS = "a2dp";
else
typeS = "unknown";
- AFB_API_DEBUG(plugin->api, "Transport %d: type %s, dev %s", ix, typeS, addr);
+ AFB_API_DEBUG(plugin->api, "Transport %d: type %s (%x), dev %s", ix, typeS, ba_transport->type, addr);
+
+ if (BA_PCM_TYPE(ba_transport->type) == BA_PCM_TYPE_SCO &&
+ ba_transport->codec == 0) {
+ AFB_API_DEBUG(plugin->api, "Transport has no codec, skip it");
+ continue;
+ }
if (halBlueAlsaTransportFind(watch, transport_list, ba_transport)) {
AFB_API_DEBUG(plugin->api, "This transport is already streamed");
@@ -668,23 +680,67 @@ done:
return 0;
}
-static int halBlueAlsaRegister(CtlPluginT* plugin, const char * interface) {
+
+static int halBlueAlsaUnregister(CtlPluginT * plugin, const char * dbus_name) {
+ hal_bluealsa_plugin_data_t * pluginData = getPluginContext(plugin);
+
+ const char * interface = dbus_name + strlen(BLUEZALSA_DBUS_NAME_PREFIX) + 1;
+
+ bluealsa_transport_t * transport_list = &pluginData->transport_list;
+
+ const bluealsa_watch * watcher = NULL;
+
+ bluealsa_transport_t * tmp, *sav;
+ cds_list_for_each_entry_safe(tmp, sav, &transport_list->list, list) {
+
+ const bluealsa_watch * watch = tmp->watch;
+ if (!watch)
+ continue;
+
+ if (strcmp(watch->interface, interface) != 0)
+ continue;
+
+ watcher = watch;
+
+ cds_list_del(&tmp->list);
+ halBluezAlsaRemoveTransportStream(tmp);
+
+ free(tmp->transactionUidS);
+ free(tmp);
+ }
+
+ if (watcher) {
+ close(watcher->fd);
+ free((char*)watcher->interface);
+ free((bluealsa_watch*)watcher);
+ }
+
+ return 0;
+}
+
+static int halBlueAlsaRegister(CtlPluginT* plugin, const char * dbus_name) {
int ret;
- sd_event *sdLoop;
- sd_event_source* evtsrc;
+ sd_event * sdLoop;
+ sd_event_source * evtsrc;
+
+ const char * interface = dbus_name + strlen(BLUEZALSA_DBUS_NAME_PREFIX) +1 ;
sdLoop = afb_api_get_event_loop(plugin->api);
enum ba_event transport_mask = BA_EVENT_TRANSPORT_ADDED |
-// BA_EVENT_TRANSPORT_CHANGED |
+ BA_EVENT_TRANSPORT_CHANGED |
BA_EVENT_TRANSPORT_REMOVED;
bluealsa_watch * watch = (bluealsa_watch *)malloc(sizeof(bluealsa_watch));
if (watch == NULL)
goto fail;
- watch->interface = interface;
- watch->plugin = plugin;
+ watch->interface = strdup(interface);
+ if (!watch->interface) {
+ AFB_API_ERROR(plugin->api, "%s: Insufficient memory", __func__);
+ goto fail;
+ }
+ watch->plugin = plugin;
if ((watch->fd = bluealsa_open(interface)) == -1) {
AFB_API_ERROR(plugin->api, "BlueALSA connection failed: %s", strerror(errno));
@@ -693,31 +749,161 @@ static int halBlueAlsaRegister(CtlPluginT* plugin, const char * interface) {
halBlueAlsaFetchTransports(watch);
- if (bluealsa_subscribe(watch->fd, transport_mask) == -1) {
- AFB_API_ERROR(plugin->api, "BlueALSA subscription failed: %s", strerror(errno));
+ if (bluealsa_event_subscribe(watch->fd, transport_mask) == -1) {
+ AFB_API_ERROR(plugin->api, "BlueALSA subscription failed: %s", strerror(errno));
goto fail;
}
- // Register sound event to the main loop of the binder
+ // Register transport change event to the main loop of the binder
if ((ret = sd_event_add_io(sdLoop, &evtsrc, watch->fd, EPOLLIN, halBlueAlsaTransportEventCB, watch)) < 0) {
- AFB_API_ERROR(plugin->api,
+ AFB_API_ERROR(plugin->api,
"%s: Failed to register event fd to io loop",
__func__);
goto fail;
}
+
return 0;
fail:
+ if (watch->interface)
+ free((char*)watch->interface);
if (watch->fd)
close(watch->fd);
if (watch)
free(watch);
+
return -1;
}
+/* Notification callback for DBus "NameOwnerChanged event.
+ * Notice that since the dbus name of bluez-alsa daemon has a prefix
+ * for the hci interface,
+ * (eg, org.bluez-alsa.hci0, org.bluez-alsa.hci1 .., it is not possible
+ * to set a match rule when registering the callback, so we have to perform
+ * the filtering for the service name within the callback instead */
+
+static int name_changed_cb(sd_bus_message *m, void *userdata, sd_bus_error * ret_error) {
+
+ CtlPluginT * plugin = (CtlPluginT*) userdata;
+
+ const char * signature = sd_bus_message_get_signature(m, 1);
+
+ if (strcmp(signature,"sss") != 0) {
+ AFB_API_ERROR(plugin->api, "%s: wrong message signature '%s'", __func__, signature);
+ goto done;
+ }
+
+ const char *name;
+ const char *owner_old;
+ const char *owner_new;
+
+ if (sd_bus_message_read(m, "sss", &name, &owner_old, &owner_new) < 0) {
+ AFB_API_ERROR(plugin->api, "%s: failed to read message parameters", __func__);
+ goto done;
+ }
+
+ if (!strstr(name, BLUEZALSA_DBUS_NAME_PREFIX))
+ goto done;
+
+ if (owner_old != NULL && owner_old[0] != '\0') {
+ AFB_API_INFO(plugin->api, "bluez-alsa: %s disappeared", name);
+ halBlueAlsaUnregister(plugin, name);
+ }
+
+ if (owner_new != NULL && owner_new[0] != '\0') {
+ AFB_API_INFO(plugin->api, "bluez-alsa %s appeared", name);
+ halBlueAlsaRegister(plugin, name);
+ }
+
+done:
+ return 0;
+}
+
+/*
+ * Callback for the DBus "ListNames" call.
+ * This will list all the available services on DBus (or more precisely,
+ * the ones that have registered a name, because it is not mandatory)
+ * The "ListNames" is done at startup to get the initial list of available
+ * bluez-alsa daemons to connect to.
+ * After, we use the NameOwnerChanged signals for the notification of new
+ * instances of bluez-alsa, or when it disappears */
+
+static int sd_bus_list_message_handler (sd_bus_message *m, void *userdata, sd_bus_error *ret_error) {
+
+ const char * signature = sd_bus_message_get_signature(m, 1);
+ int ret;
+
+ CtlPluginT * plugin = (CtlPluginT*) userdata;
+
+ if (strcmp(signature,"as") != 0) {
+ AFB_API_ERROR(plugin->api, "%s: wrong message signature: '%s'", __func__, signature);
+ goto done;
+ }
+
+ const char * string = NULL;
+
+ if ( (ret = sd_bus_message_enter_container(m, SD_BUS_TYPE_ARRAY, "s")) < 0) {
+ AFB_API_ERROR(plugin->api, "failed to enter container, err=%s\n", strerror(-ret));
+ goto failed;
+ }
+
+ for (;;) {
+ ret = sd_bus_message_read(m, "s", &string);
+ if (ret < 0) {
+ AFB_API_ERROR(plugin->api, "%s: failed to read string", __func__);
+ goto done;
+ }
+
+ if (ret == 0)
+ break;
+
+ if (strstr(string, BLUEZALSA_DBUS_NAME_PREFIX) != 0) {
+ halBlueAlsaRegister(plugin, string);
+ }
+ }
+
+done:
+ sd_bus_message_exit_container(m);
+
+failed:
+ return 0;
+}
+
static int halBlueAlsaRegisterAll(CtlPluginT* plugin) {
- /* TODO add a watch to all the available interfaces */
- halBlueAlsaRegister(plugin, "hci0");
+ sd_bus_slot * slot = NULL;
+ struct sd_bus *bus = afb_api_get_system_bus(plugin->api);
+ int ret;
+
+ if (!bus) {
+ AFB_API_ERROR(plugin->api, "%s: Unable to get a DBus connection", __func__);
+ goto failed;
+ }
+
+ AFB_API_ERROR(plugin->api, "Ask DBus for the list of services");
+
+ ret = sd_bus_call_method_async(bus,
+ &slot,
+ ORG_FREEDESKTOP_DBUS_NAME,
+ ORG_FREEDESKTOP_DBUS_PATH,
+ ORG_FREEDESKTOP_DBUS_INTERFACE,
+ "ListNames",
+ sd_bus_list_message_handler,
+ plugin,
+ NULL);
+ if (ret < 0) {
+ AFB_API_ERROR(plugin->api, "Unable to request service list from DBus: %s", strerror(-ret));
+ goto failed;
+ }
+
+ char * match = "type='signal',sender='" ORG_FREEDESKTOP_DBUS_NAME "', interface='" ORG_FREEDESKTOP_DBUS_INTERFACE "'";
+
+ if (sd_bus_add_match(bus, &slot, match, name_changed_cb, plugin) < 0) {
+ AFB_API_ERROR(plugin->api, "Unable to add the dbus signal match rule");
+ goto failed;
+ }
+
return 0;
+failed:
+ return -1;
}