@@ -226,6 +226,7 @@ struct bt_bap_stream {
struct bt_bap_stream_io *io;
bool client;
void *user_data;
+ struct queue *bcast_links; /* Linked streams from the same BIG */
};
/* TODO: Figure out the capabilities types */
@@ -255,6 +256,30 @@ struct bt_pacs_context {
uint16_t src;
} __packed;
+struct bt_base_data {
+ uint32_t pres_delay;
+ struct queue *base_data_subgroup;
+};
+
+struct bt_stream_base_data {
+ struct queue *ltv_caps;
+ struct queue *ltv_meta;
+ struct bt_bap_stream *stream;
+};
+
+struct bt_base_data_subgroup {
+ uint8_t subgroup_index;
+ struct bt_bap_codec codec;
+ struct queue *ltv_caps;
+ struct queue *ltv_meta;
+ struct queue *bises;
+};
+
+struct bt_base_data_bis {
+ uint8_t bis_index;
+ struct queue *ltv_caps;
+};
+
/* Contains local bt_bap_db */
static struct queue *bap_db;
static struct queue *bap_cbs;
@@ -826,6 +851,7 @@ static struct bt_bap_stream *bap_stream_new(struct bt_bap *bap,
stream->rpac = rpac;
stream->cc = util_iov_dup(data, 1);
stream->client = client;
+ stream->bcast_links = queue_new();
queue_push_tail(bap->streams, stream);
@@ -1010,6 +1036,14 @@ static void stream_io_unref(struct bt_bap_stream_io *io)
stream_io_free(io);
}
+static void bap_stream_unlink(void *data, void *user_data)
+{
+ struct bt_bap_stream *link = data;
+ struct bt_bap_stream *stream = user_data;
+
+ queue_remove(link->bcast_links, stream);
+}
+
static void bap_stream_free(void *data)
{
struct bt_bap_stream *stream = data;
@@ -1020,6 +1054,9 @@ static void bap_stream_free(void *data)
if (stream->link)
stream->link->link = NULL;
+ queue_foreach(stream->bcast_links, bap_stream_unlink, stream);
+ queue_destroy(stream->bcast_links, NULL);
+
stream_io_unref(stream->io);
util_iov_free(stream->cc, 1);
util_iov_free(stream->meta, 1);
@@ -5492,3 +5529,458 @@ void bt_bap_update_bcast_source(struct bt_bap_pac *pac,
bap_pac_merge(pac, data, metadata);
pac->codec = *codec;
}
+
+static void destroy_ltv(void *data)
+{
+ struct bt_ltv *ltv = data;
+
+ if (!ltv)
+ return;
+
+ free(ltv);
+}
+
+static void destroy_base_data_bis(void *data)
+{
+ struct bt_base_data_bis *bis = data;
+
+ if (!bis)
+ return;
+
+ queue_destroy(bis->ltv_caps, destroy_ltv);
+ free(bis);
+}
+
+static void destroy_base_data_subgroup(void *data)
+{
+ struct bt_base_data_subgroup *subgroup = data;
+
+ if (!subgroup)
+ return;
+
+ queue_destroy(subgroup->ltv_caps, destroy_ltv);
+ queue_destroy(subgroup->ltv_meta, destroy_ltv);
+ queue_destroy(subgroup->bises, destroy_base_data_bis);
+
+ free(subgroup);
+}
+
+static void destroy_stream_base_data(void *data)
+{
+ struct bt_stream_base_data *sbd = data;
+
+ if (!sbd)
+ return;
+
+ queue_destroy(sbd->ltv_caps, destroy_ltv);
+ queue_destroy(sbd->ltv_meta, destroy_ltv);
+ sbd->stream = NULL;
+ free(sbd);
+}
+
+static void append_ltv_to_base(void *data, void *user_data)
+{
+ struct bt_ltv *ltv = data;
+ struct iovec *base_iov = user_data;
+
+ if (!util_iov_push_u8(base_iov, ltv->len))
+ return;
+
+ if (!util_iov_push_u8(base_iov, ltv->type))
+ return;
+
+ if (!util_iov_push_mem(base_iov, ltv->len - 1, ltv->value))
+ return;
+}
+
+static void get_ltv_size(void *data, void *user_data)
+{
+ struct bt_ltv *ltv = data;
+ uint8_t *length = user_data;
+
+ *length = *length + ltv->len + 1;
+}
+
+static uint8_t get_size_from_ltv_queue(struct queue *ltv_queue)
+{
+ uint8_t length = 0;
+
+ queue_foreach(ltv_queue, get_ltv_size, &length);
+ return length;
+}
+
+static void generate_bis_base(void *data, void *user_data)
+{
+ struct bt_base_data_bis *bis = data;
+ struct iovec *base_iov = user_data;
+ uint8_t cc_length = get_size_from_ltv_queue(bis->ltv_caps);
+
+ if (!util_iov_push_u8(base_iov, bis->bis_index))
+ return;
+
+ if (!util_iov_push_u8(base_iov, cc_length))
+ return;
+
+ queue_foreach(bis->ltv_caps, append_ltv_to_base, base_iov);
+}
+
+static void generate_subgroup_base(void *data, void *user_data)
+{
+ struct bt_base_data_subgroup *bds = data;
+ struct iovec *base_iov = user_data;
+ uint8_t cc_length = get_size_from_ltv_queue(bds->ltv_caps);
+ uint8_t metadata_length = get_size_from_ltv_queue(bds->ltv_meta);
+
+ if (!util_iov_push_u8(base_iov, queue_length(bds->bises)))
+ return;
+
+ if (!util_iov_push_u8(base_iov, bds->codec.id))
+ return;
+
+ if (!util_iov_push_le16(base_iov, bds->codec.cid))
+ return;
+
+ if (!util_iov_push_le16(base_iov, bds->codec.vid))
+ return;
+
+ if (!util_iov_push_u8(base_iov, cc_length))
+ return;
+
+ queue_foreach(bds->ltv_caps, append_ltv_to_base, base_iov);
+
+ if (!util_iov_push_u8(base_iov, metadata_length))
+ return;
+
+ queue_foreach(bds->ltv_meta, append_ltv_to_base, base_iov);
+
+ queue_foreach(bds->bises, generate_bis_base, base_iov);
+}
+
+static struct iovec *generate_base(struct bt_base_data *base)
+{
+ struct iovec *base_iov = new0(struct iovec, 0x1);
+
+ base_iov->iov_base = util_malloc(BASE_MAX_LENGTH);
+
+ if (!util_iov_push_le24(base_iov, base->pres_delay))
+ return NULL;
+
+ if (!util_iov_push_u8(base_iov,
+ queue_length(base->base_data_subgroup)))
+ return NULL;
+
+ queue_foreach(base->base_data_subgroup, generate_subgroup_base,
+ base_iov);
+
+ return base_iov;
+}
+
+static void get_max_bises_index(void *data, void *user_data)
+{
+ struct bt_base_data_bis *bdb = data;
+ uint8_t *bis_index = user_data;
+
+ if (bdb->bis_index > *bis_index)
+ *bis_index = bdb->bis_index + 1;
+ else if (bdb->bis_index == *bis_index)
+ *bis_index = *bis_index + 1;
+}
+
+static void get_bises_index(void *data, void *user_data)
+{
+ struct bt_base_data_subgroup *bds = data;
+ uint8_t *bis_index = user_data;
+
+ queue_foreach(bds->bises, get_max_bises_index, bis_index);
+}
+
+static uint8_t get_bis_index(struct queue *subgroups)
+{
+ uint8_t bis_index = 1;
+
+ queue_foreach(subgroups, get_bises_index, &bis_index);
+ return bis_index;
+}
+
+static void add_new_bis(struct bt_base_data_subgroup *subgroup,
+ uint8_t bis_index, struct queue *ltv_bis_caps)
+{
+ struct bt_base_data_bis *bdb = new0(struct bt_base_data_bis, 1);
+
+ bdb->bis_index = bis_index;
+ bdb->ltv_caps = ltv_bis_caps;
+ queue_push_tail(subgroup->bises, bdb);
+}
+
+static void add_new_subgroup(struct queue *subgroups,
+ struct bt_stream_base_data *base_data)
+{
+ struct bt_bap_pac *lpac = base_data->stream->lpac;
+ struct bt_base_data_subgroup *bds = new0(
+ struct bt_base_data_subgroup, 1);
+ uint16_t cid = 0;
+ uint16_t vid = 0;
+
+ bt_bap_pac_get_vendor_codec(lpac, &bds->codec.id, &cid,
+ &vid, NULL, NULL);
+ bds->codec.cid = cid;
+ bds->codec.vid = vid;
+ bds->ltv_caps = base_data->ltv_caps;
+ bds->ltv_meta = base_data->ltv_meta;
+ base_data->ltv_caps = NULL;
+ base_data->ltv_meta = NULL;
+ bds->bises = queue_new();
+ base_data->stream->qos.bcast.bis = get_bis_index(subgroups);
+ add_new_bis(bds, base_data->stream->qos.bcast.bis,
+ queue_new());
+ queue_push_tail(subgroups, bds);
+}
+
+static bool ltv_match(const void *data, const void *match_data)
+{
+ const struct bt_ltv *ltv1 = data;
+ const struct bt_ltv *ltv2 = match_data;
+
+ if (ltv1->len == ltv2->len)
+ if (ltv1->type == ltv2->type)
+ if (memcmp(ltv1->value, ltv2->value, ltv1->len - 1)
+ == 0)
+ return true;
+ return false;
+}
+
+
+static bool compare_ltv_lists(struct queue *ltv_list1, struct queue *ltv_list2)
+{
+ const struct queue_entry *entry;
+ /* Compare metadata length */
+ if (queue_length(ltv_list1) != queue_length(ltv_list2))
+ return false;
+
+ /* Compare metadata ltvs */
+ for (entry = queue_get_entries(ltv_list1); entry; entry = entry->next) {
+ struct bt_ltv *ltv = entry->data;
+
+ if (!queue_find(ltv_list2, ltv_match, ltv))
+ return false;
+ }
+
+ return true;
+}
+
+static struct queue *compare_caps_ltv_lists(
+ struct queue *subgroup_caps, struct queue *bis_caps)
+{
+ struct queue *ltv_caps = queue_new();
+ const struct queue_entry *entry;
+
+ /* Compare metadata ltvs */
+ for (entry = queue_get_entries(bis_caps); entry;
+ entry = entry->next) {
+ struct bt_ltv *ltv = entry->data;
+
+ if (!queue_find(subgroup_caps, ltv_match, ltv))
+ queue_push_tail(ltv_caps, ltv);
+ }
+
+ if (queue_isempty(ltv_caps)) {
+ queue_destroy(ltv_caps, NULL);
+ return NULL;
+ } else
+ return ltv_caps;
+}
+
+static void remove_ltv_form_list(void *data, void *user_data)
+{
+ struct bt_ltv *ltv = data;
+ struct queue *ltv_caps = user_data;
+
+ queue_remove(ltv_caps, ltv);
+}
+
+static void set_base_subgroup(void *data, void *user_data)
+{
+ struct bt_stream_base_data *stream_base = data;
+ struct bt_base_data *base = user_data;
+ struct queue *ltv_caps;
+
+ if (queue_isempty(base->base_data_subgroup)) {
+ add_new_subgroup(base->base_data_subgroup, stream_base);
+ } else {
+ /* Verify if a subgroup has the same metadata */
+ const struct queue_entry *entry;
+ struct bt_base_data_subgroup *subgroup_base = NULL;
+ bool same_meta = false;
+
+ for (entry = queue_get_entries(base->base_data_subgroup);
+ entry; entry = entry->next) {
+ subgroup_base = entry->data;
+ if (compare_ltv_lists(subgroup_base->ltv_meta,
+ stream_base->ltv_meta)) {
+ same_meta = true;
+ break;
+ }
+ }
+
+ if (!same_meta) {
+ /* No subgroup with the same metadata found.
+ * Create a new one.
+ */
+ add_new_subgroup(base->base_data_subgroup,
+ stream_base);
+ } else {
+ /* Subgroup found with the same metadata
+ * get different capabilities
+ */
+ ltv_caps = compare_caps_ltv_lists(
+ subgroup_base->ltv_caps,
+ stream_base->ltv_caps);
+
+ queue_foreach(ltv_caps, remove_ltv_form_list,
+ stream_base->ltv_caps);
+ stream_base->stream->qos.bcast.bis = get_bis_index(
+ base->base_data_subgroup);
+ add_new_bis(subgroup_base,
+ stream_base->stream->qos.bcast.bis,
+ ltv_caps);
+ }
+ }
+}
+
+static void set_device_presentation_delay(void *data, void *user_data)
+{
+ struct bt_stream_base_data *sbd = data;
+ struct bt_base_data *base = user_data;
+ struct bt_bap_qos *qos = &sbd->stream->qos;
+
+ if (base->pres_delay < qos->bcast.delay)
+ base->pres_delay = qos->bcast.delay;
+}
+
+static void parse_ltv(size_t i, uint8_t l, uint8_t t, uint8_t *v,
+ void *user_data)
+{
+ struct queue **ltv_queue = user_data;
+ struct bt_ltv *q_ltv_elem = malloc(sizeof(struct bt_ltv) + l);
+
+ q_ltv_elem->len = l + 1;
+ q_ltv_elem->type = t;
+
+ memcpy(q_ltv_elem->value, v, l);
+
+ if (!*ltv_queue)
+ *ltv_queue = queue_new();
+ queue_push_tail(*ltv_queue, q_ltv_elem);
+}
+
+/*
+ * Extract Codec Specific configurations and Metadata information
+ * that will be use in the BASE creation
+ */
+static struct bt_stream_base_data *get_stream_base_info(
+ struct bt_bap_stream *stream)
+{
+ struct bt_stream_base_data *sbd = new0(struct bt_stream_base_data, 1);
+ struct iovec *stream_caps_iov = util_iov_dup(
+ stream->cc, 1);
+ struct iovec *stream_meta_iov = util_iov_dup(
+ stream->meta, 1);
+
+ /*
+ * Copy the Codec Specific configurations from stream
+ */
+ if (stream_caps_iov != NULL) {
+ if (!util_ltv_foreach(stream_caps_iov->iov_base,
+ stream_caps_iov->iov_len, NULL,
+ parse_ltv, &sbd->ltv_caps)) {
+ DBG(stream->bap,
+ "Unable to parse Codec Specific configurations");
+ goto fail;
+ }
+ }
+
+ /*
+ * Copy the Metadata from stream
+ */
+ if (stream_meta_iov != NULL) {
+ if (!util_ltv_foreach(stream_meta_iov->iov_base,
+ stream_meta_iov->iov_len, NULL,
+ parse_ltv, &sbd->ltv_meta)) {
+ DBG(stream->bap,
+ "Unable to parse metadata");
+ goto fail;
+ }
+ }
+
+ sbd->stream = stream;
+
+ util_iov_free(stream_caps_iov, 1);
+ util_iov_free(stream_meta_iov, 1);
+
+ return sbd;
+
+fail:
+ util_iov_free(stream_caps_iov, 1);
+ util_iov_free(stream_meta_iov, 1);
+
+ if (sbd->ltv_caps)
+ queue_destroy(sbd->ltv_caps, destroy_ltv);
+
+ if (sbd->ltv_meta)
+ queue_destroy(sbd->ltv_meta, destroy_ltv);
+
+ free(sbd);
+
+ return NULL;
+}
+
+static void get_stream_base_data(void *data, void *user_data)
+{
+ struct bt_bap_stream *stream = data;
+ struct queue *streams_base_data = user_data;
+ struct bt_stream_base_data *sbd = get_stream_base_info(stream);
+
+ if (sbd)
+ queue_push_tail(streams_base_data, sbd);
+}
+
+/*
+ * Function to update the BASE using configuration data
+ * from each BIS in an BIG
+ */
+struct iovec *bt_bap_update_base(struct bt_bap_stream *stream)
+{
+ struct bt_base_data *base;
+ struct iovec *base_iov;
+ struct queue *streams_base_data = queue_new();
+ struct bt_stream_base_data *sbd = get_stream_base_info(stream);
+
+ /*
+ * Extract Codec Specific configurations and Metadata information
+ * that will be use in the BASE creation from all linked BISes
+ */
+ queue_foreach(stream->bcast_links, get_stream_base_data,
+ streams_base_data);
+
+ queue_push_tail(streams_base_data, sbd);
+
+ base = new0(struct bt_base_data, 1);
+ base->base_data_subgroup = queue_new();
+
+ queue_foreach(streams_base_data, set_device_presentation_delay, base);
+
+ /*
+ * Create subgroups and BISes in a BASE
+ */
+ queue_foreach(streams_base_data, set_base_subgroup, base);
+
+ base_iov = generate_base(base);
+
+ queue_destroy(streams_base_data, destroy_stream_base_data);
+
+ queue_destroy(base->base_data_subgroup, destroy_base_data_subgroup);
+
+ free(base);
+
+ return base_iov;
+}
@@ -88,6 +88,7 @@ struct bt_bap_bcast_qos {
uint16_t timeout;
uint8_t pa_sync;
struct bt_bap_io_qos io_qos;
+ uint32_t delay; /* Presentation Delay */
};
struct bt_bap_qos {
@@ -321,3 +322,4 @@ void bt_bap_update_bcast_source(struct bt_bap_pac *pac,
bool bt_bap_pac_bcast_is_local(struct bt_bap *bap, struct bt_bap_pac *pac);
+struct iovec *bt_bap_update_base(struct bt_bap_stream *stream);