From fbad8c202c86e4cf6503a99161aabf87ab7a7109 Mon Sep 17 00:00:00 2001 From: Thierry Bultel Date: Wed, 23 Jan 2019 11:27:03 +0100 Subject: bluealsa plugin: monitor the state of service via dbus By monitoring the changes of names on dbus, and by asking dbus for the list of available names at startup, the bluealsa plugin can now be started at whatever time, and is also robust to bluealsa restarts. Moreover, since the bluealsa name has the HCI interface has a suffix (org.bluez-alsa.hci0, for instance), there is not need to hardcode the hci name. Even better, the plugin supports several instances of bluealsa (since the daemon needs one instace per hci device), and will add a watch for added transports for every instance it detects. Complete cleanup of the created streams in softmixer is done when one watched instance disappears. Finnaly, there is a new cmd line '-DWITHOUT_BLUEALSA' option to CMake, for developpers that want to use 4A on host, without the bluealsa library. Default build (SDK, bitbake) consider that the bluealsa shared library is always available. Notice that in the future, the bluealsa library might disappeared, to be replaced by a full dbus interface to bluealsa. BUG-AGL:SPEC-2126 Change-Id: I67fc6edf5147c71dc97f1fc1257d3dadbdde20fc Signed-off-by: Thierry Bultel --- plugins/lib/bluealsa/hal-bluealsa-transports.c | 8 +- plugins/lib/bluealsa/hal-bluealsa-transports.h | 2 +- plugins/lib/bluealsa/hal-bluealsa.c | 234 ++++++++++++++++++++++--- 3 files changed, 216 insertions(+), 28 deletions(-) (limited to 'plugins/lib/bluealsa') 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 853ab05..ebc0560 100644 --- a/plugins/lib/bluealsa/hal-bluealsa.c +++ b/plugins/lib/bluealsa/hal-bluealsa.c @@ -25,6 +25,8 @@ #include #include #include +#include +#include #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; } -- cgit 1.2.3-korg