summaryrefslogtreecommitdiffstats
path: root/meta-agl-profile-graphical/recipes-graphics/wayland
ModeNameSize
-rw-r--r--Readme.weston-ini-conf776logstatsplain
-rw-r--r--agl-compositor_git.bb787logstatsplain
-rw-r--r--waltham-transmitter_git.bb1598logstatsplain
-rw-r--r--waltham_git.bb494logstatsplain
d---------wayland-ivi-extension496logstatsplain
-rw-r--r--wayland-ivi-extension_git.bb1397logstatsplain
d---------wayland157logstatsplain
-rw-r--r--wayland_%.bbappend203logstatsplain
-rw-r--r--weston-init.bbappend2131logstatsplain
d---------weston-init261logstatsplain
d---------weston-ready88logstatsplain
-rw-r--r--weston-ready_1.0.bb630logstatsplain
d---------weston1927logstatsplain
-rw-r--r--weston_%.bbappend158logstatsplain
-rw-r--r--weston_5.0.0.bbappend1508logstatsplain
-rw-r--r--weston_6.0.0.bbappend440logstatsplain
439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 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;
}