This updates the Scan Delegator implementation to be handled internally in BASS: The BASS Server is responsible to handle Write Commands for the Add Source operation by creating long-lived PA sync, parsing the BASE, creating and configuring BAP streams, as well as enabling them. --- profiles/audio/bap.c | 18 +-- profiles/audio/bass.c | 332 ++++++++++++++++++++++++++++++++++++------ profiles/audio/bass.h | 4 +- 3 files changed, 297 insertions(+), 57 deletions(-) diff --git a/profiles/audio/bap.c b/profiles/audio/bap.c index 675c33c6d..a1cb3aeae 100644 --- a/profiles/audio/bap.c +++ b/profiles/audio/bap.c @@ -1119,14 +1119,6 @@ static void bis_handler(uint8_t bis, uint8_t sgrp, struct iovec *caps, bass_add_stream(data->device, meta, caps, qos, sgrp, bis); - if (!bass_check_bis(data->device, bis)) - /* If this Broadcast Sink is acting as a Scan - * Delegator, only attempt to create streams - * for the BISes required by the peer Broadcast - * Assistant. - */ - return; - /* Check if this BIS matches any local PAC */ bt_bap_verify_bis(data->bap, bis, caps, &lpac); @@ -1186,9 +1178,6 @@ static gboolean big_info_report_cb(GIOChannel *io, GIOCondition cond, g_io_channel_shutdown(io, TRUE, NULL); } - /* Notify the BASS plugin about the session. */ - bass_bcast_probe(data->device, data->bap); - /* Analyze received BASE data and create remote media endpoints for each * BIS matching our capabilities */ @@ -2598,6 +2587,8 @@ static void bap_state_bcast_sink(struct bt_bap_stream *stream, return; setup = bap_find_setup_by_stream(data, stream); + if (!setup) + return; switch (new_state) { case BT_BAP_STREAM_STATE_IDLE: @@ -3145,6 +3136,7 @@ static int bap_bcast_probe(struct btd_service *service) struct bap_bcast_pa_req *req; uint8_t type = BAP_PA_LONG_REQ; struct bap_data *data; + int ret = 0; if (!btd_adapter_has_exp_feature(adapter, EXP_FEAT_ISO_SOCKET)) { error("BAP requires ISO Socket which is not enabled"); @@ -3198,6 +3190,10 @@ static int bap_bcast_probe(struct btd_service *service) bt_bap_set_user_data(data->bap, service); + if (bass_bcast_probe(service, &ret)) + /* Return if probed device was handled inside BASS. */ + return ret; + /* Start the PA timer if it hasn't been started yet */ if (data->adapter->pa_timer_id == 0) data->adapter->pa_timer_id = g_timeout_add_seconds( diff --git a/profiles/audio/bass.c b/profiles/audio/bass.c index d3b35f62a..21f708ba6 100644 --- a/profiles/audio/bass.c +++ b/profiles/audio/bass.c @@ -29,6 +29,7 @@ #include "lib/bluetooth.h" #include "lib/uuid.h" +#include "lib/iso.h" #include "src/dbus-common.h" #include "src/shared/util.h" @@ -42,6 +43,7 @@ #include "src/shared/bap.h" #include "src/shared/ad.h" +#include "btio/btio.h" #include "src/plugin.h" #include "src/gatt-database.h" #include "src/device.h" @@ -101,16 +103,28 @@ struct bass_assistant { struct bass_delegator { struct btd_device *device; /* Broadcast source device */ + struct btd_service *service; struct bt_bcast_src *src; struct bt_bap *bap; unsigned int state_id; uint8_t *bcode; unsigned int timeout; struct queue *bcode_reqs; + struct queue *setups; + unsigned int io_id; + GIOChannel *io; }; -struct bass_bcode_req { +struct bass_setup { + struct bass_delegator *dg; struct bt_bap_stream *stream; + struct bt_bap_qos qos; + struct iovec *meta; + struct iovec *config; +}; + +struct bass_bcode_req { + struct bass_setup *setup; bt_bass_bcode_func_t cb; void *user_data; }; @@ -153,18 +167,30 @@ static bool delegator_match_bap(const void *data, const void *match_data) return dg->bap == bap; } -static void stream_set_bcode(uint8_t *bcode, struct bt_bap_stream *stream, +static void setup_set_bcode(uint8_t *bcode, struct bass_setup *setup, bt_bass_bcode_func_t cb, void *user_data) { - struct bt_bap_qos *qos = bt_bap_stream_get_qos(stream); + struct bt_bap_qos *qos = bt_bap_stream_get_qos(setup->stream); + + /* Allocate Broadcast Code inside setup QoS */ + util_iov_free(setup->qos.bcast.bcode, 1); + setup->qos.bcast.bcode = util_iov_new(bcode, BT_BASS_BCAST_CODE_SIZE); - /* Allocate Broadcast Code inside stream QoS */ - qos->bcast.bcode = util_iov_new(bcode, BT_BASS_BCAST_CODE_SIZE); + /* Refresh stream bcode */ + qos->bcast.bcode = setup->qos.bcast.bcode; if (cb) cb(user_data, 0); } +static bool match_setup_stream(const void *data, const void *user_data) +{ + const struct bass_setup *setup = data; + const struct bt_bap_stream *stream = user_data; + + return setup->stream == stream; +} + void bass_req_bcode(struct bt_bap_stream *stream, bt_bass_bcode_func_t cb, void *user_data) @@ -172,6 +198,7 @@ void bass_req_bcode(struct bt_bap_stream *stream, struct bt_bap *bap = bt_bap_stream_get_session(stream); struct bass_delegator *dg; struct bass_bcode_req *req; + struct bass_setup *setup; dg = queue_find(delegators, delegator_match_bap, bap); if (!dg) { @@ -179,9 +206,15 @@ void bass_req_bcode(struct bt_bap_stream *stream, return; } + setup = queue_find(dg->setups, match_setup_stream, stream); + if (!setup) { + cb(user_data, -EINVAL); + return; + } + if (dg->bcode) { /* Broadcast Code has already been received before. */ - stream_set_bcode(dg->bcode, stream, cb, user_data); + setup_set_bcode(dg->bcode, setup, cb, user_data); return; } @@ -193,7 +226,7 @@ void bass_req_bcode(struct bt_bap_stream *stream, if (!req) return; - req->stream = stream; + req->setup = setup; req->cb = cb; req->user_data = user_data; @@ -218,18 +251,58 @@ static bool delegator_match_device(const void *data, const void *match_data) return dg->device == device; } -bool bass_check_bis(struct btd_device *device, uint8_t bis) +static int stream_get_bis(struct bt_bap_stream *stream) { - struct bass_delegator *dg; + char *path = bt_bap_stream_get_user_data(stream); + const char *strbis; + int bis; - dg = queue_find(delegators, delegator_match_device, device); - if (!dg) - return true; + strbis = strstr(path, "/bis"); + if (!strbis) + return 0; - if (!bt_bass_check_bis(dg->src, bis)) - return false; + if (sscanf(strbis, "/bis%d", &bis) < 0) + return 0; - return true; + return bis; +} + +static void append_stream(void *data, void *user_data) +{ + struct bt_bap_stream *stream = data; + struct sockaddr_iso_bc *addr = user_data; + uint8_t bis = stream_get_bis(stream); + + DBG("%d", bis); + + addr->bc_bis[addr->bc_num_bis] = bis; + addr->bc_num_bis++; +} + +static bool link_io_unset(const void *data, const void *match_data) +{ + struct bt_bap_stream *link = (struct bt_bap_stream *)data; + + return !bt_bap_stream_get_io(link); +} + +static void connect_cb(GIOChannel *io, GError *err, void *user_data) +{ + struct bt_bap_stream *stream = user_data; + struct queue *links = bt_bap_stream_io_get_links(stream); + int fd; + + DBG(""); + + /* Set fds for the stream and all its links. */ + if (bt_bap_stream_get_io(stream)) + stream = queue_find(links, link_io_unset, NULL); + + fd = g_io_channel_unix_get_fd(io); + + if (bt_bap_stream_set_io(stream, fd)) { + g_io_channel_set_close_on_unref(io, FALSE); + } } static void bap_state_changed(struct bt_bap_stream *stream, uint8_t old_state, @@ -237,31 +310,54 @@ static void bap_state_changed(struct bt_bap_stream *stream, uint8_t old_state, { struct bass_delegator *dg = user_data; int bis; - char *path = bt_bap_stream_get_user_data(stream); struct bt_bap *bap = bt_bap_stream_get_session(stream); - const char *strbis; - int err; + struct sockaddr_iso_bc iso_bc_addr = {0}; + struct queue *links; + GError *gerr = NULL; + struct bt_bap_qos *bap_qos = bt_bap_stream_get_qos(stream); + struct bt_iso_qos qos; if (dg->bap != bap) return; - strbis = strstr(path, "/bis"); - if (strbis == NULL) { - DBG("bis index cannot be found"); - return; - } - - err = sscanf(strbis, "/bis%d", &bis); - if (err < 0) { - DBG("sscanf error"); - return; - } + bis = stream_get_bis(stream); DBG("stream %p: %s(%u) -> %s(%u)", stream, bt_bap_stream_statestr(old_state), old_state, bt_bap_stream_statestr(new_state), new_state); switch (new_state) { + case BT_BAP_STREAM_STATE_ENABLING: + iso_bc_addr.bc_bdaddr_type = + btd_device_get_bdaddr_type(dg->device); + memcpy(&iso_bc_addr.bc_bdaddr, device_get_address(dg->device), + sizeof(bdaddr_t)); + + append_stream(stream, &iso_bc_addr); + + links = bt_bap_stream_io_get_links(stream); + + queue_foreach(links, append_stream, &iso_bc_addr); + + bap_qos_to_iso_qos(bap_qos, &qos); + + if (!bt_io_set(dg->io, &gerr, + BT_IO_OPT_QOS, &qos, + BT_IO_OPT_INVALID)) { + error("bt_io_set: %s", gerr->message); + g_error_free(gerr); + break; + } + + if (!bt_io_bcast_accept(dg->io, + connect_cb, stream, NULL, &gerr, + BT_IO_OPT_ISO_BC_NUM_BIS, + iso_bc_addr.bc_num_bis, BT_IO_OPT_ISO_BC_BIS, + iso_bc_addr.bc_bis, BT_IO_OPT_INVALID)) { + error("bt_io_bcast_accept: %s", gerr->message); + g_error_free(gerr); + } + break; case BT_BAP_STREAM_STATE_STREAMING: /* BAP stream was started. Mark BIS index as synced inside the * Broadcast Receive State characteristic and notify peers about @@ -280,17 +376,99 @@ static void bap_state_changed(struct bt_bap_stream *stream, uint8_t old_state, } } -bool bass_bcast_probe(struct btd_device *device, struct bt_bap *bap) +static void bis_handler(uint8_t bis, uint8_t sgrp, struct iovec *caps, + struct iovec *meta, struct bt_bap_qos *qos, void *user_data) { - struct bass_delegator *dg; + struct bass_delegator *dg = user_data; + struct bt_bap_pac *lpac; + char *path; + struct bass_setup *setup; - dg = queue_find(delegators, delegator_match_device, device); - if (!dg) - return false; + /* Only handle streams required by the Brodcast Assistant. */ + if (!bt_bass_check_bis(dg->src, bis)) + return; - DBG("%p", dg); + /* Check if this stream caps match any local PAC */ + bt_bap_verify_bis(dg->bap, bis, caps, &lpac); + if (!lpac) + return; + + if (asprintf(&path, "%s/bis%d", device_get_path(dg->device), bis) < 0) + return; + + setup = new0(struct bass_setup, 1); + if (!setup) + return; + + setup->dg = dg; + + setup->qos = *qos; + setup->qos.bcast.bcode = util_iov_dup(qos->bcast.bcode, 1); + + setup->meta = util_iov_dup(meta, 1); + setup->config = util_iov_dup(caps, 1); + + setup->stream = bt_bap_stream_new(dg->bap, lpac, NULL, + &setup->qos, setup->config); + if (!setup->stream) + return; + + queue_push_tail(dg->setups, setup); + + bt_bap_stream_set_user_data(setup->stream, path); + bt_bap_stream_config(setup->stream, &setup->qos, + setup->config, NULL, NULL); + bt_bap_stream_metadata(setup->stream, setup->meta, + NULL, NULL); +} + +static gboolean big_info_cb(GIOChannel *io, GIOCondition cond, + gpointer user_data) +{ + struct bass_delegator *dg = user_data; + GError *err = NULL; + struct bt_iso_base base; + struct bt_iso_qos qos; + struct iovec iov; + struct bt_bap_qos bap_qos = {0}; + + dg->io_id = 0; + + bt_io_get(io, &err, + BT_IO_OPT_BASE, &base, + BT_IO_OPT_QOS, &qos, + BT_IO_OPT_INVALID); + if (err) { + error("%s", err->message); + g_error_free(err); + return FALSE; + } + + iov.iov_base = base.base; + iov.iov_len = base.base_len; + + /* Create BAP QoS structure */ + bap_iso_qos_to_bap_qos(&qos, &bap_qos); + + bt_bap_parse_base(&iov, &bap_qos, bass_debug, bis_handler, dg); + + util_iov_free(bap_qos.bcast.bcode, 1); + + return FALSE; +} - dg->bap = bap; +static void confirm_cb(GIOChannel *io, void *user_data) +{ + struct bass_delegator *dg = user_data; + + DBG(""); + + /* Close the listen io */ + g_io_channel_shutdown(dg->io, TRUE, NULL); + g_io_channel_unref(dg->io); + + g_io_channel_ref(io); + dg->io = io; /* Update Broadcast Receive State characteristic value and notify * peers. @@ -298,15 +476,70 @@ bool bass_bcast_probe(struct btd_device *device, struct bt_bap *bap) if (bt_bass_set_pa_sync(dg->src, BT_BASS_SYNCHRONIZED_TO_PA)) DBG("Failed to update Broadcast Receive State characteristic"); - /* Register BAP stream state changed callback, to keep up to - * date with BIG/PA sync state. - */ - dg->state_id = bt_bap_state_register(bap, bap_state_changed, + /* Register BAP stream state changed callback. */ + dg->state_id = bt_bap_state_register(dg->bap, bap_state_changed, NULL, dg, NULL); + dg->io_id = g_io_add_watch(io, G_IO_OUT, big_info_cb, dg); +} + +bool bass_bcast_probe(struct btd_service *service, int *ret) +{ + struct btd_device *device = btd_service_get_device(service); + struct btd_adapter *adapter = device_get_adapter(device); + struct bass_delegator *dg; + GError *err = NULL; + + dg = queue_find(delegators, delegator_match_device, device); + if (!dg) + /* Only probe devices added via Broadcast Assistants */ + return false; + + if (dg->service) { + /* Service has already been probed */ + *ret = -EINVAL; + return true; + } + + dg->service = service; + dg->bap = bap_get_session(device); + + dg->io = bt_io_listen(NULL, confirm_cb, dg, + NULL, &err, + BT_IO_OPT_SOURCE_BDADDR, + btd_adapter_get_address(adapter), + BT_IO_OPT_SOURCE_TYPE, + btd_adapter_get_address_type(adapter), + BT_IO_OPT_DEST_BDADDR, + device_get_address(device), + BT_IO_OPT_DEST_TYPE, + btd_device_get_bdaddr_type(device), + BT_IO_OPT_MODE, BT_IO_MODE_ISO, + BT_IO_OPT_QOS, &bap_sink_pa_qos, + BT_IO_OPT_INVALID); + if (!dg->io) { + error("%s", err->message); + *ret = -err->code; + g_error_free(err); + } + return true; } +static void setup_free(void *data) +{ + struct bass_setup *setup = data; + + DBG("setup %p", setup); + + util_iov_free(setup->qos.bcast.bcode, 1); + util_iov_free(setup->meta, 1); + util_iov_free(setup->config, 1); + + bt_bass_clear_bis_sync(setup->dg->src, + stream_get_bis(setup->stream)); +} + bool bass_bcast_remove(struct btd_device *device) { struct bass_delegator *dg; @@ -317,6 +550,16 @@ bool bass_bcast_remove(struct btd_device *device) DBG("%p", dg); + if (dg->io_id) + g_source_remove(dg->io_id); + + if (dg->io) { + g_io_channel_shutdown(dg->io, TRUE, NULL); + g_io_channel_unref(dg->io); + } + + queue_destroy(dg->setups, setup_free); + /* Update Broadcast Receive State characteristic value and notify * peers. */ @@ -904,6 +1147,7 @@ probe: dg->device = device; dg->src = bcast_src; dg->bcode_reqs = queue_new(); + dg->setups = queue_new(); if (!delegators) delegators = queue_new(); @@ -912,8 +1156,10 @@ probe: DBG("delegator %p", dg); - /* Probe device with BAP. */ - bap_scan_delegator_probe(device); + /* Add Broadcast Audio Announcement Service UUID + * to device and probe service. + */ + btd_device_add_uuid(device, BCAAS_UUID_STR); return 0; } @@ -947,7 +1193,7 @@ static int handle_set_bcode_req(struct bt_bcast_src *bcast_src, /* Set the Broadcast Code for each stream that required it. */ while ((req = queue_pop_head(dg->bcode_reqs))) { - stream_set_bcode(dg->bcode, req->stream, req->cb, + setup_set_bcode(dg->bcode, req->setup, req->cb, req->user_data); free(req); } diff --git a/profiles/audio/bass.h b/profiles/audio/bass.h index 845949117..678532168 100644 --- a/profiles/audio/bass.h +++ b/profiles/audio/bass.h @@ -12,11 +12,9 @@ void bass_add_stream(struct btd_device *device, struct iovec *meta, uint8_t sgrp, uint8_t bis); void bass_remove_stream(struct btd_device *device); -bool bass_bcast_probe(struct btd_device *device, struct bt_bap *bap); +bool bass_bcast_probe(struct btd_service *service, int *ret); bool bass_bcast_remove(struct btd_device *device); -bool bass_check_bis(struct btd_device *device, uint8_t bis); - typedef void (*bt_bass_bcode_func_t)(void *user_data, int err); void bass_req_bcode(struct bt_bap_stream *stream, -- 2.43.0