diff options
-rw-r--r-- | plugins/lib/CMakeLists.txt | 4 | ||||
-rw-r--r-- | plugins/lib/bluealsa/hal-bluealsa-transports.c | 8 | ||||
-rw-r--r-- | plugins/lib/bluealsa/hal-bluealsa-transports.h | 2 | ||||
-rw-r--r-- | plugins/lib/bluealsa/hal-bluealsa.c | 234 |
4 files changed, 219 insertions, 29 deletions
diff --git a/plugins/lib/CMakeLists.txt b/plugins/lib/CMakeLists.txt index 3fd93ec..b41fb3b 100644 --- a/plugins/lib/CMakeLists.txt +++ b/plugins/lib/CMakeLists.txt @@ -19,4 +19,6 @@ # Include any directory not starting with _ # ----------------------------------------------------- -PROJECT_SUBDIRS_ADD() +if (NOT WITHOUT_BLUEALSA) +PROJECT_SUBDIRS_ADD(bluealsa) +endif() diff --git a/plugins/lib/bluealsa/hal-bluealsa-transports.c b/plugins/lib/bluealsa/hal-bluealsa-transports.c index 56e1a29..590ab5d 100644 --- a/plugins/lib/bluealsa/hal-bluealsa-transports.c +++ b/plugins/lib/bluealsa/hal-bluealsa-transports.c @@ -74,6 +74,7 @@ bool halBlueAlsaTransportFind( } + int halBlueAlsaTransportUpdate( const bluealsa_watch * watch, bluealsa_transport_t * list, @@ -129,7 +130,8 @@ static bool halBlueAlsaTransportMatch(const struct ba_msg_transport * transport1 if (transport1->type != transport2->type) goto done; - if (transport1->stream != transport2->stream) + /* with HFP/ofono, a NULL codec means a hangup. So we consider that the transport has disappeared in that case */ + if (transport1->codec != transport2->codec) goto done; if (bacmp(&transport1->addr, &transport2->addr) != 0) { @@ -146,8 +148,8 @@ char * halBlueAlsaTransportAsString(char * buff, size_t len, const struct ba_msg ba2str(&transport->addr, addr); snprintf(buff, len, "%s,%s,%s", addr, - transport->type == BA_PCM_TYPE_A2DP?"a2dp":"sco", - transport->stream == BA_PCM_STREAM_PLAYBACK?"playback":"capture"); + transport->type & BA_PCM_TYPE_A2DP?"a2dp":"sco", + transport->type & BA_PCM_STREAM_PLAYBACK?"playback":"capture"); return buff; } diff --git a/plugins/lib/bluealsa/hal-bluealsa-transports.h b/plugins/lib/bluealsa/hal-bluealsa-transports.h index d5b277f..0c75f1d 100644 --- a/plugins/lib/bluealsa/hal-bluealsa-transports.h +++ b/plugins/lib/bluealsa/hal-bluealsa-transports.h @@ -26,8 +26,8 @@ #include "hal-bluealsa-watch.h" typedef struct { - const bluealsa_watch * watch; /* owner */ struct cds_list_head list; + const bluealsa_watch * watch; /* owner */ struct ba_msg_transport transport; char * transactionUidS; } bluealsa_transport_t; diff --git a/plugins/lib/bluealsa/hal-bluealsa.c b/plugins/lib/bluealsa/hal-bluealsa.c index b2d4537..10a61ae 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; } @@ -450,12 +456,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; @@ -491,14 +497,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); @@ -511,7 +517,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); @@ -536,7 +542,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) @@ -617,14 +623,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"); @@ -655,23 +667,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)); @@ -680,31 +736,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; } |