summaryrefslogtreecommitdiffstats
path: root/plugins/lib/bluealsa/hal-bluealsa.c
diff options
context:
space:
mode:
authorThierry Bultel <thierry.bultel@iot.bzh>2018-12-05 14:05:10 +0100
committerThierry Bultel <thierry.bultel@iot.bzh>2018-12-20 16:55:46 +0000
commit01d55ed8cdd01ca4a7b391ca61c80084bd5a6f2f (patch)
treed73ce778a2177e82e07e9d31891a827467fbadd4 /plugins/lib/bluealsa/hal-bluealsa.c
parentdbe555be80dd10bb315b14bbebcb42f8b883e2c9 (diff)
Adds support for bluetooth audio through bluez-alsaguppy_6.99.4guppy_6.99.3guppy/6.99.4guppy/6.99.36.99.46.99.3
Implements a new bluealsa plugin to the HAL manager, reacting to the changes of the available transports. This plugin is linked with the new bluealsa.so shared library. New transports (SCO & A2DP) result in softmixer invocations of the "attach" verb, that creates the new capture (eg, A2DP capture from bluealsa ioplug PCM, SCO microphone capture), playbacks (SCO playback to a softmixer zone, and SCO output to bluealsa iogplug PCM). When a transport disappears, the hal manager calls the transaction deletion verb that will tell the softmixer to remove the created streams and associated objects. Change-Id: I36037a4f14ef7fee38070fc0df66c40b4ce46e8b Signed-off-by: Thierry Bultel <thierry.bultel@iot.bzh>
Diffstat (limited to 'plugins/lib/bluealsa/hal-bluealsa.c')
-rw-r--r--plugins/lib/bluealsa/hal-bluealsa.c723
1 files changed, 723 insertions, 0 deletions
diff --git a/plugins/lib/bluealsa/hal-bluealsa.c b/plugins/lib/bluealsa/hal-bluealsa.c
new file mode 100644
index 0000000..c8cfe98
--- /dev/null
+++ b/plugins/lib/bluealsa/hal-bluealsa.c
@@ -0,0 +1,723 @@
+/*
+ * Copyright (C) 2018 "IoT.bzh"
+ * Author : Thierry Bultel <thierry.bultel@iot.bzh>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define _GNU_SOURCE
+#include <stdio.h>
+#include <unistd.h>
+
+#include <wrap-json.h>
+
+#include <errno.h>
+#include <ctl-config.h>
+#include <systemd/sd-event.h>
+#include <bluealsa/bluealsa.h>
+
+#include "hal-bluealsa.h"
+
+#define HAL_BLUEALSA_PLUGIN_NAME "hal-bluealsa"
+
+#define SCO_TALK_RATE 44100
+#define SCO_TALK_FORMAT "S16_LE"
+
+#define SCO_TALK_ZONE "sco_talk_zone"
+#define SCO_CHANNEL_MONO "sco-mono"
+
+/* forward static declarations */
+static int halBlueAlsaTransportEventCB(sd_event_source* src, int fd, uint32_t revents, void* userData);
+static int halBlueAlsaFetchTransports(bluealsa_watch * plugin);
+static int halBlueAlsaRegisterAll(CtlPluginT* plugin);
+static int halBlueAlsaRegister(CtlPluginT* plugin, const char * interface);
+
+CTLP_CAPI_REGISTER(HAL_BLUEALSA_PLUGIN_NAME)
+
+
+// Call at initialization time ('requires' are forbidden at this stage)
+CTLP_ONLOAD(plugin, callbacks)
+{
+ AFB_ApiNotice(plugin->api, "%s Plugin Registered correctly: uid='%s' 'info='%s'", HAL_BLUEALSA_PLUGIN_NAME, plugin->uid, plugin->info);
+ return 0;
+}
+
+
+CTLP_INIT(plugin, callbacks)
+{
+ json_object *actionsToAdd = NULL;
+ CtlConfigT *ctrlConfig;
+
+ wrap_json_pack(&actionsToAdd, "{s:s s:s s:s}",
+ "uid", "init-bluealsa-plugin",
+ "info", "Init Bluez-Alsa hal plugin",
+ "action", "plugin://hal-bluealsa#init");
+
+ if (!(ctrlConfig = (CtlConfigT *) AFB_ApiGetUserData(plugin->api))) {
+ AFB_ApiError(plugin->api, "Can't get current hal controller config");
+ goto fail;
+ }
+
+ int idx = 0;
+ while (ctrlConfig->sections[idx].key && strcasecmp(ctrlConfig->sections[idx].key, "onload"))
+ idx++;
+
+ if (!ctrlConfig->sections[idx].key) {
+ AFB_ApiError(plugin->api, "Wasn't able to add '%s' as a new onload, 'onload' section not found", json_object_get_string(actionsToAdd));
+ goto fail;
+ }
+
+ if(AddActionsToSection(plugin->api, &ctrlConfig->sections[idx], actionsToAdd, 0)) {
+ AFB_ApiError(plugin->api, "Wasn't able to add '%s' as a new onload to %s", json_object_get_string(actionsToAdd), ctrlConfig->sections[idx].uid);
+ goto fail;
+ }
+
+ AFB_ApiNotice(plugin->api, "Plugin initialization of %s plugin correctly done", HAL_BLUEALSA_PLUGIN_NAME);
+
+ return 0;
+fail:
+ json_object_put(actionsToAdd);
+ return -1;
+}
+
+
+// Call at controller onload time
+CTLP_CAPI(init, source, argsJ, queryJ)
+{
+ AFB_ApiNotice(source->api, "Controller onload event");
+
+ CtlPluginT * plugin = source->plugin;
+
+ hal_bluealsa_plugin_data_t * pluginData = (hal_bluealsa_plugin_data_t*) calloc(1, sizeof(hal_bluealsa_plugin_data_t));
+ int error;
+
+ /*
+ * "params": {
+ "sco": {
+ "mic": "2CH-GENERIC-USB",
+ "zone": "full-stereo",
+ "delayms": 300
+ },
+ "a2dp": {
+ "zone": "full-stereo",
+ "delayms": 1000
+ }
+ }
+ */
+
+ json_object * scoParamsJ = NULL;
+ json_object * a2dpParamsJ = NULL;
+
+ error = wrap_json_unpack(plugin->paramsJ, "{s?o,s?o !}",
+ "sco", &scoParamsJ,
+ "a2dp", &a2dpParamsJ);
+
+ if (error) {
+ AFB_ApiError(plugin->api, "%s: wrong parameters", __func__);
+ goto fail;
+ }
+
+ if (scoParamsJ) {
+ AFB_ApiInfo(plugin->api, "%s: sco parameters: %s", __func__, json_object_get_string(scoParamsJ));
+ error = wrap_json_unpack(scoParamsJ, "{s:s,s:s,s?i !}",
+ "mic", &pluginData->sco.mic,
+ "zone", &pluginData->sco.speaker,
+ "delayms", &pluginData->sco.delayms);
+ if (error) {
+ AFB_ApiError(plugin->api, "%s: wrong sco parameters: err %s", __func__, wrap_json_get_error_string(error));
+ goto fail;
+ }
+ }
+
+ if (a2dpParamsJ) {
+ AFB_ApiInfo(plugin->api, "%s: a2dp parameters: %s", __func__, json_object_get_string(a2dpParamsJ));
+ error = wrap_json_unpack(a2dpParamsJ, "{s:s,s?i !}",
+ "zone", &pluginData->a2dp.zone,
+ "delayms", &pluginData->a2dp.delayms);
+ if (error) {
+ AFB_ApiError(plugin->api, "%s: wrong a2dp parameters: err=%s", __func__, wrap_json_get_error_string(error));
+ goto fail;
+ }
+ }
+
+ halBlueAlsaTransportsInit(&pluginData->transport_list);
+
+ setPluginContext(plugin, pluginData);
+
+ if (halBlueAlsaRegisterAll(plugin) != 0)
+ goto fail;
+
+ return 0;
+fail:
+ return -1;
+}
+
+
+static int halBlueAlsaTransportEventCB(sd_event_source* src, int fd, uint32_t revents, void* userData) {
+
+ bluealsa_watch * watch = (bluealsa_watch *)userData;
+ CtlPluginT * plugin = watch->plugin;
+
+ struct ba_msg_event event;
+ ssize_t ret;
+
+ AFB_ApiDebug(plugin->api, "--- %s ----!", __func__);
+
+ if ((revents & EPOLLIN) == 0)
+ goto done;
+
+ if (revents & EPOLLHUP) {
+ AFB_ApiInfo(plugin->api, "Lost connection with bluealsa on interface %s", watch->interface);
+ sd_event_source_unref(src);
+ close(fd);
+ halBlueAlsaRegister(plugin, watch->interface);
+ goto done;
+ }
+
+ while ((ret = recv(watch->fd, &event, sizeof(event), MSG_DONTWAIT)) == -1 && errno == EINTR)
+ continue;
+
+ if (ret != sizeof(event)) {
+ AFB_ApiError(plugin->api, "Couldn't read event: %s", strerror(ret == -1 ? errno : EBADMSG));
+ goto done;
+ }
+
+ halBlueAlsaFetchTransports(watch);
+
+done:
+ return 0;
+}
+
+
+/*
+ *
+ * The following function builds that kind of json data and send it to smixer
+ *
+ * A2DP Case:
+ *
+ * {
+ "uid":"a2dp_F6:32:15:2A:80:70",
+ "captures":
+ {
+ "uid":"a2dp_listen_capture",
+ "pcmplug_params" : "bluealsa:HCI=hci0,DEV=F6:32:15:2A:80:70,PROFILE=a2dp",
+ "source" : {
+ "channels": [
+ {
+ "uid": "a2dp-right",
+ "port": 0
+ },
+ {
+ "uid": "a2dp-left",
+ "port": 1
+ }
+ ]
+ }
+ },
+ "streams":
+ {
+ "uid" : "a2dp_listen_stream",
+ "source":"a2dp_listen_capture",
+ "zone": "full-stereo"
+ }
+ }
+ *
+ *-----------------------------------------------------------------------------------
+ *
+ * SCO Case:
+ *
+ * {
+ "uid":"sco_F6:32:15:2A:80:70",",
+ "captures":
+ {
+ "uid":"sco_listen_capture",
+ "pcmplug_params" : "bluealsa:HCI=hci0,DEV=F6:32:15:2A:80:70,PROFILE=sco",
+ "source" : {
+ "channels": [
+ {
+ "uid": "sco-right",
+ "port": 0
+ },
+ {
+ "uid": "sco-left",
+ "port": 1
+ }
+ ]
+ }
+ },
+ "playbacks" :
+ {
+ "uid"="sco_talk_playback",
+ "pcmplug_params" : "bluealsa:HCI=hci0,DEV=F6:32:15:2A:80:70,PROFILE=sco",
+ "params": {
+ "rate": 44100,
+ "format": "S16_LE",
+ },
+ "sink": {
+ "channels": [
+ {
+ "uid": "sco-mono"
+ }
+ ]
+ }
+ },
+ "zones" : [
+ {
+ "uid": "sco_talk_zone",
+ "sink": [
+ {
+ "target": "sco-mono",
+ "channel": 0,
+ "volume": 0.5
+ },
+ {
+ "target": "sco-mono",
+ "channel": 1,
+ "volume": 0.5
+ }
+ ]
+ }
+ ] ,
+ "streams": [
+ {
+ "uid" : "sco_listen_stream",
+ "source":"sco_listen_capture",
+ "zone": "full-stereo",
+ "delayms": 300
+ },
+ {
+ "uid" : "sco_talk_stream",
+ "source": "2CH-GENERIC-USB",
+ "zone": "sco_talk_zone",
+ "delayms": 300
+ }
+ ]
+ }
+ *
+ * */
+
+static json_object* halBlueAlsaA2DPTransportChannels(const char * transportTypeS) {
+ json_object* channelRightJ;
+ json_object* channelLeftJ;
+ json_object* channelsJ = json_object_new_array();
+ char * channelRightS;
+ char * channelLeftS;
+
+ channelRightJ = json_object_new_object();
+ if (asprintf(&channelRightS, "%s-right", transportTypeS) == -1)
+ goto fail;
+
+ json_object_object_add(channelRightJ, "uid", json_object_new_string(channelRightS));
+ json_object_object_add(channelRightJ, "port", json_object_new_int(0));
+
+ channelLeftJ = json_object_new_object();
+ if (asprintf(&channelLeftS, "%s-left", transportTypeS) == -1)
+ goto fail;
+
+ json_object_object_add(channelLeftJ, "uid", json_object_new_string(channelLeftS));
+ json_object_object_add(channelLeftJ, "port", json_object_new_int(1));
+
+ json_object_array_add(channelsJ, channelRightJ);
+ json_object_array_add(channelsJ, channelLeftJ);
+fail:
+ return channelsJ;
+}
+
+
+static json_object* halBlueAlsaSCOTransportChannels(const char * transportTypeS) {
+ json_object* channelMonoJ;
+
+ json_object* channelsJ = json_object_new_array();
+ char * channelMonoS;
+
+ channelMonoJ = json_object_new_object();
+ if (asprintf(&channelMonoS, "%s-mono", transportTypeS) == -1)
+ goto fail;
+
+ json_object_object_add(channelMonoJ, "uid", json_object_new_string(channelMonoS));
+ json_object_object_add(channelMonoJ, "port", json_object_new_int(0));
+
+ json_object_array_add(channelsJ, channelMonoJ);
+
+fail:
+ return channelsJ;
+}
+
+
+static json_object* halBlueAlsaPcmPlugParams(const char * interface, const char * addr, const char * transportTypeS) {
+ char * pcmplug_paramsS = NULL;
+ if (asprintf(&pcmplug_paramsS, "bluealsa:HCI=%s,DEV=%s,PROFILE=%s", interface, addr, transportTypeS) == -1)
+ goto fail;
+ return json_object_new_string(pcmplug_paramsS);
+fail:
+ return NULL;
+}
+
+
+static json_object* halBlueAlsaListenCapture(
+ const char * listenCaptureS,
+ json_object * pcmplugParamsJ,
+ json_object * sourceJ) {
+
+ json_object * captureJ = json_object_new_object();
+
+ json_object_object_add(captureJ, "uid", json_object_new_string(listenCaptureS));
+ json_object_object_add(captureJ, "pcmplug_params", pcmplugParamsJ);
+ json_object_object_add(captureJ, "source", sourceJ);
+
+ return captureJ;
+}
+
+static json_object * halBlueAlsaTalkPlayback(
+ const char * talkPlaybackS,
+ json_object* pcmplugParamsJ,
+ json_object* paramsJ,
+ json_object* sinkJ ) {
+
+ json_object * playbackJ = json_object_new_object();
+
+ json_object_object_add(playbackJ, "uid", json_object_new_string(talkPlaybackS));
+ json_object_object_add(playbackJ, "pcmplug_params", pcmplugParamsJ);
+ json_object_object_add(playbackJ, "params", paramsJ);
+ json_object_object_add(playbackJ, "sink", sinkJ);
+
+ return playbackJ;
+}
+
+static json_object * halBlueAlsaScoTalkParamsJ() {
+ json_object * paramsJ = json_object_new_object();
+
+ json_object_object_add(paramsJ, "rate", json_object_new_int(SCO_TALK_RATE));
+ json_object_object_add(paramsJ, "format", json_object_new_string(SCO_TALK_FORMAT));
+ return paramsJ;
+}
+
+static json_object * halBlueAlsaScoZone() {
+ json_object * zoneJ = json_object_new_object();
+ json_object * sinkJ = json_object_new_array();
+ json_object_object_add(zoneJ, "uid", json_object_new_string(SCO_TALK_ZONE));
+
+ json_object * channel1J = json_object_new_object();
+ json_object_object_add(channel1J, "target" , json_object_new_string(SCO_CHANNEL_MONO));
+ json_object_object_add(channel1J, "channel", json_object_new_int(0));
+ json_object_object_add(channel1J, "volume", json_object_new_double(0.5));
+
+ json_object * channel2J = json_object_new_object();
+ json_object_object_add(channel2J, "target" , json_object_new_string(SCO_CHANNEL_MONO));
+ json_object_object_add(channel2J, "channel", json_object_new_int(1));
+ json_object_object_add(channel2J, "volume", json_object_new_double(0.5));
+
+ json_object_array_add(sinkJ, channel1J);
+ json_object_array_add(sinkJ, channel2J);
+
+ json_object_object_add(zoneJ, "sink", sinkJ);
+
+ return zoneJ;
+}
+
+static int halBlueAlsaAttachTransportStreams(bluealsa_transport_t * transport) {
+ int ret = -1;
+ const char* transportTypeS;
+
+ struct ba_msg_transport * ba_transport = &transport->transport;
+ const bluealsa_watch * watch = transport->watch;
+
+ CtlPluginT* plugin = watch->plugin;
+ hal_bluealsa_plugin_data_t * pluginData=getPluginContext(plugin);
+
+ json_object* requestJ = NULL;
+ json_object* returnJ = NULL;
+
+ json_object* sourceJ = NULL;
+ json_object* playbackJ = NULL;
+
+ json_object* pcmplugParamsJ, *pcmplugParamsJ2 = NULL;
+
+ json_object* streamsJ = NULL;
+ json_object* streamJ = NULL;
+
+ json_object* zonesJ = NULL;
+
+ char * playbackZoneS;
+ uint32_t delayms;
+
+ 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) {
+ transportTypeS = "a2dp";
+ delayms = pluginData->a2dp.delayms;
+ playbackZoneS = pluginData->a2dp.zone;
+ } else {
+ AFB_ApiError(plugin->api, "%s: unsupported transport type", __func__ );
+ goto fail;
+ }
+
+ char * captureS = NULL;
+ if (asprintf(&captureS, "%s_listen_capture", transportTypeS) == -1)
+ goto fail;
+
+ // for SCO only
+ char * playbackS = NULL;
+ if (asprintf(&playbackS, "%s_talk_playback", transportTypeS) == -1)
+ goto fail;
+
+ char * transactionUidS = NULL;
+ char * streamS = NULL;
+
+ char addr[18];
+ ba2str(&ba_transport->addr, addr);
+
+ requestJ = json_object_new_object();
+ if (!requestJ)
+ goto fail;
+
+ if (asprintf(&transactionUidS, "%s_%s", transportTypeS, addr) == -1)
+ goto fail;
+
+ json_object_object_add(requestJ, "uid", json_object_new_string(transactionUidS));
+
+ pcmplugParamsJ = halBlueAlsaPcmPlugParams(watch->interface, addr, transportTypeS);
+
+ sourceJ = json_object_new_object();
+ 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) {
+ 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);
+ json_object * paramsJ = halBlueAlsaScoTalkParamsJ();
+
+ json_object_object_add(playbackJ, "channels", halBlueAlsaSCOTransportChannels(transportTypeS));
+ json_object_object_add(requestJ, "playbacks", halBlueAlsaTalkPlayback(playbackS, pcmplugParamsJ2, paramsJ, playbackJ));
+
+ }
+
+ /* ZONES */
+
+ 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);
+ }
+
+ /* STREAMS */
+
+ /* Build the array of streams */
+ streamsJ = json_object_new_array();
+
+ streamJ = json_object_new_object();
+ if (asprintf(&streamS, "%s_listen_stream", transportTypeS) == -1)
+ goto fail;
+
+ json_object_object_add(streamJ, "uid", json_object_new_string(streamS));
+ json_object_object_add(streamJ, "source", json_object_new_string(captureS));
+ json_object_object_add(streamJ, "zone", json_object_new_string(playbackZoneS));
+ if (delayms != 0) {
+ json_object_object_add(streamJ, "delayms", json_object_new_int(delayms));
+ }
+
+ 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 ) {
+
+ streamJ = json_object_new_object();
+ if (asprintf(&streamS, "%s_talk_stream", transportTypeS) == -1)
+ goto fail;
+
+ json_object_object_add(streamJ, "uid", json_object_new_string(streamS));
+ json_object_object_add(streamJ, "source", json_object_new_string(pluginData->sco.mic));
+ json_object_object_add(streamJ, "zone", json_object_new_string(SCO_TALK_ZONE));
+ if (delayms != 0)
+ json_object_object_add(streamJ, "delayms", json_object_new_int(delayms));
+ json_object_array_add(streamsJ, streamJ);
+ }
+
+ json_object_object_add(requestJ, "streams", streamsJ);
+
+ /* In softmixer, this will create a transaction verb (whose name is transactionUidS),
+ * will be used later to destroy all the created objects upon transport removal */
+
+ if (AFB_ServiceSync(plugin->api, SMIXER_API_NAME, "attach", requestJ, &returnJ)) {
+ AFB_ApiError(plugin->api, "Error calling attach verb of mixer" );
+ goto done;
+ }
+
+ transport->transactionUidS = transactionUidS;
+
+ if (returnJ)
+ json_object_put(returnJ);
+
+ ret = 0;
+ goto done;
+
+fail:
+ return -1;
+done:
+ AFB_ApiDebug(plugin->api, "DONE.");
+ return ret;
+}
+
+
+static int halBluezAlsaRemoveTransportStream(bluealsa_transport_t * transport) {
+
+ CtlPluginT * plugin = transport->watch->plugin;
+ json_object* requestJ = NULL;
+ json_object* returnJ = NULL;
+
+ AFB_ApiInfo(plugin->api, "Call transaction detach verb %s", transport->transactionUidS);
+ if (transport->transactionUidS == NULL)
+ goto fail;
+
+ requestJ = json_object_new_object();
+ if (!requestJ)
+ goto fail;
+
+ json_object_object_add(requestJ, "action", json_object_new_string("remove"));
+
+ if (AFB_ServiceSync(plugin->api, SMIXER_API_NAME, transport->transactionUidS, requestJ, &returnJ)) {
+ AFB_ApiError(plugin->api, "Error calling attach verb of mixer" );
+ goto fail;
+ }
+
+ if (returnJ)
+ json_object_put(returnJ);
+
+ return 0;
+
+fail:
+ return -1;
+}
+
+static int halBlueAlsaFetchTransports(bluealsa_watch * watch) {
+ ssize_t nbTransports;
+ struct ba_msg_transport *transports;
+ CtlPluginT * plugin = watch->plugin;
+
+ hal_bluealsa_plugin_data_t * pluginData = (hal_bluealsa_plugin_data_t*)getPluginContext(plugin);
+ bluealsa_transport_t * transport_list = &pluginData->transport_list;
+ bluealsa_transport_t * transport = NULL;
+
+ AFB_ApiDebug(plugin->api, "Fetching available transports of interface %s", watch->interface);
+
+ if ((nbTransports = bluealsa_get_transports(watch->fd, &transports)) == -1) {
+ AFB_ApiError(plugin->api, "Couldn't get transports: %s", strerror(errno));
+ goto done;
+ }
+
+ AFB_ApiDebug(plugin->api, "Got %zu transport(s)", nbTransports);
+
+ for (int ix=0; ix<nbTransports; ix++) {
+ char addr[18];
+ struct ba_msg_transport * ba_transport = &transports[ix];
+ ba2str(&ba_transport->addr, addr);
+ const char * typeS;
+ if (ba_transport->type == BA_PCM_TYPE_SCO)
+ typeS = "sco";
+ else if (ba_transport->type == BA_PCM_TYPE_A2DP)
+ typeS = "a2dp";
+ else
+ typeS = "unknown";
+
+ AFB_ApiDebug(plugin->api, "Transport %d: type %s, dev %s", ix, typeS, addr);
+
+ if (halBlueAlsaTransportFind(watch, transport_list, ba_transport)) {
+ AFB_ApiDebug(plugin->api, "This transport is already streamed");
+ continue;
+ }
+
+ AFB_ApiInfo(plugin->api, "Registering transport type %s, dev %s", typeS, addr);
+ transport = halBlueAlsaTransportsAdd(watch, transport_list, ba_transport);
+ if (transport == NULL) {
+ AFB_ApiError(plugin->api, "Failed to register this transport");
+ goto done;
+ }
+
+ // Do the softmixer stuff
+ if (halBlueAlsaAttachTransportStreams(transport) != 0) {
+ AFB_ApiError(plugin->api, "Failed create transport streams");
+ goto done;
+ }
+
+ AFB_ApiDebug(plugin->api, "%s: transaction id %s", __func__, transport->transactionUidS);
+
+ }
+
+ halBlueAlsaTransportUpdate(watch, transport_list, transports, nbTransports , halBluezAlsaRemoveTransportStream);
+
+done:
+ free(transports);
+ return 0;
+}
+
+static int halBlueAlsaRegister(CtlPluginT* plugin, const char * interface) {
+ int ret;
+ sd_event *sdLoop;
+ sd_event_source* evtsrc;
+
+ sdLoop = AFB_GetEventLoop(plugin->api);
+
+ enum ba_event transport_mask = BA_EVENT_TRANSPORT_ADDED |
+// 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;
+
+ if ((watch->fd = bluealsa_open(interface)) == -1) {
+ AFB_ApiError(plugin->api, "BlueALSA connection failed: %s", strerror(errno));
+ goto fail;
+ }
+
+ halBlueAlsaFetchTransports(watch);
+
+ if (bluealsa_subscribe(watch->fd, transport_mask) == -1) {
+ AFB_ApiError(plugin->api, "BlueALSA subscription failed: %s", strerror(errno));
+ goto fail;
+ }
+
+ // Register sound event to the main loop of the binder
+ if ((ret = sd_event_add_io(sdLoop, &evtsrc, watch->fd, EPOLLIN, halBlueAlsaTransportEventCB, watch)) < 0) {
+ AFB_ApiError(plugin->api,
+ "%s: Failed to register event fd to io loop",
+ __func__);
+ goto fail;
+ }
+ return 0;
+
+fail:
+ if (watch->fd)
+ close(watch->fd);
+ if (watch)
+ free(watch);
+ return -1;
+}
+
+
+static int halBlueAlsaRegisterAll(CtlPluginT* plugin) {
+ /* TODO add a watch to all the available interfaces */
+ halBlueAlsaRegister(plugin, "hci0");
+ return 0;
+}