[PATCH BlueZ] shared/bap: improve channel allocation logic in bt_bap_select()

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Currently bt_bap_select() determines the need for multi-stream
configurations in a way that depends on accidental GATT detail (whether
Locations is read before or after the PAC) that shouldn't have any
effect on the configuration.

Whether a device gets configured with 1 (AC 1) or 2 (AC 6i) sink ASEs,
consider two devices: Galaxy Buds2 Pro:

    profiles/audio/bap.c:bap_attached() 0x60e000001c40
    src/shared/bap.c:foreach_pacs_char() PAC Context found: handle 0x0050
    src/shared/bap.c:foreach_pacs_char() PAC Supported Context found: handle 0x0053
    src/shared/bap.c:foreach_pacs_char() Sink PAC Location found: handle 0x0056
    src/shared/bap.c:foreach_pacs_char() Sink PAC found: handle 0x0059

    Sink PACS Locations: 0x401
    One local PAC, with Locations: all bits
    rpac bt_bap_chan.location == 0x401 (PACS Locations read before PAC)
    -> bt_select() ChannelAllocation: 0x401

Earfun Air Pro 3:

    profiles/audio/bap.c:bap_attached() 0x60e000001e00
    src/shared/bap.c:foreach_pacs_char() Sink PAC found: handle 0x004b
    src/shared/bap.c:foreach_pacs_char() Sink PAC found: handle 0x004e
    src/shared/bap.c:foreach_pacs_char() Sink PAC found: handle 0x0051
    src/shared/bap.c:foreach_pacs_char() Sink PAC found: handle 0x0054
    src/shared/bap.c:foreach_pacs_char() Sink PAC Location found: handle 0x0057

    Sink PACS Locations: 0x3
    One local PAC, with Locations: all bits
    rpac bt_bap_chan.location == 0x0 (PACS Locations read after PAC)
    -> bt_select() ChannelAllocation: 0x1 (ASE 1)
    -> bt_select() ChannelAllocation: 0x2 (ASE 2)

Both devices have 2+ Sink ASEs and two bits set in Locations, but
bt_bap_select() configures them differently. This occurs due to the
"if (!rc->location) { ... rc->location &= BIT(i);" logic which gets entered
only because of the ordering in which characteristics was read.  Without
"&= BIT(i)" branch, "map.location &= ~(map.location & rc->location)"
causes only one ASE be configured.  The behavior appears accidental,
and e.g. the BIT(i) logic in general is wrong as the rpac index i doesn't
have a relation to channel locations.

Rewrite the multi-ASE configuration logic, and make it independent of
chr read ordering.

Decide first whether to configure 1 or 2 ASE.  Use 2 ASE if locations
contain left/right channel pair and only 1 channel per ASE is supported.

The "right" result depends on what you want to do so this heuristic
tries to aim for a "common" configuration. See eg the above two devices:
their PAC/ASE data only differs in which two location bits are set, but
they need to be configured differently.

If 1 ASE, leave channel allocation to sound server.

If multiple ASE, multiplex as many as possible on each of them.

Remove bt_bap_chan as it's not needed -- also the PACS Locations
is global to the whole device and there are no per-PAC Locations in the
specification, so probably we shouldn't have something like that in the
internal model.
---

Notes:
    The BAP specification doesn't say that the ordering of characteristics
    in the PACS service should have a meaning, and the current logic looks
    wrong to me.
    
    From what's written in the spec, I don't see that there is a "right"
    configuration here to pick, so it seems we are forced to use some
    heuristic here.  It is hardcoded here, and we probably should make some
    changes later on to make it possible for the sound server to control
    which locations exactly are selected.
    
    One design could be to have the local PAC provide a list of Locations
    bitmasks, and BlueZ checks *in order* if the remote device has all the
    bits set in its Locations. If a match is found, BlueZ assigns exactly
    those bits to ASEs using multiple ASE, if needed. Otherwise, BlueZ uses
    a single ASE and punts the channel decision to the sound server.
    
    This would more or less be the same as what is done in this patch,
    except that the configs[] array is provided by the sound server in the
    local Client PAC.
    
    Also, if there are multiple codecs, bt_bap_select() probably should try
    to select them in order, and stop at the first successful selection.
    
    An alternative would be to leave all this to the sound server: change
    API so that SelectProperties() can return a multi-ASE configuration.
    
    The sound server already has to handle low-level details such as
    deciding which PAC configuration is best. This choice however depends on
    whether ASE multiplexing is done or not, so trying to do the decision
    partly in BlueZ and partly in sound server probably just makes things
    more complicated than they need to be. Sound server can first decide
    what Locations to connect, then allocate PACs multiplexing as much as
    possible. BAP Sec. 3.5.3 guarantees there are enough ASEs for all
    location bits.

 src/shared/bap.c | 196 ++++++++++++++++++++++-------------------------
 1 file changed, 93 insertions(+), 103 deletions(-)

diff --git a/src/shared/bap.c b/src/shared/bap.c
index 499e740c9..b64399193 100644
--- a/src/shared/bap.c
+++ b/src/shared/bap.c
@@ -187,11 +187,6 @@ struct bt_bap {
 	void *user_data;
 };
 
-struct bt_bap_chan {
-	uint8_t count;
-	uint32_t location;
-};
-
 struct bt_bap_pac {
 	struct bt_bap_db *bdb;
 	char *name;
@@ -3249,50 +3244,6 @@ static void *ltv_merge(struct iovec *data, struct iovec *cont)
 	return util_iov_append(data, cont->iov_base, cont->iov_len);
 }
 
-static void bap_pac_chan_add(struct bt_bap_pac *pac, uint8_t count,
-				uint32_t location)
-{
-	struct bt_bap_chan *chan;
-
-	if (!pac->channels)
-		pac->channels = queue_new();
-
-	chan = new0(struct bt_bap_chan, 1);
-	chan->count = count;
-	chan->location = location;
-
-	queue_push_tail(pac->channels, chan);
-}
-
-static void bap_pac_foreach_channel(size_t i, uint8_t l, uint8_t t, uint8_t *v,
-					void *user_data)
-{
-	struct bt_bap_pac *pac = user_data;
-
-	if (!v)
-		return;
-
-	bap_pac_chan_add(pac, *v, bt_bap_pac_get_locations(pac));
-}
-
-static void bap_pac_update_channels(struct bt_bap_pac *pac, struct iovec *data)
-{
-	uint8_t type = 0x03;
-
-	if (!data)
-		return;
-
-	util_ltv_foreach(data->iov_base, data->iov_len, &type,
-				bap_pac_foreach_channel, pac);
-
-	/* If record didn't set a channel count but set a location use that as
-	 * channel count.
-	 */
-	if (queue_isempty(pac->channels) && pac->qos.location)
-		bap_pac_chan_add(pac, pac->qos.location, pac->qos.location);
-
-}
-
 static void bap_pac_merge(struct bt_bap_pac *pac, struct iovec *data,
 					struct iovec *metadata)
 {
@@ -3302,9 +3253,6 @@ static void bap_pac_merge(struct bt_bap_pac *pac, struct iovec *data,
 	else
 		pac->data = util_iov_dup(data, 1);
 
-	/* Update channels */
-	bap_pac_update_channels(pac, data);
-
 	/* Merge metadata into existing record */
 	if (pac->metadata)
 		ltv_merge(pac->metadata, metadata);
@@ -4178,6 +4126,8 @@ static void read_source_pac_loc(bool success, uint8_t att_ecode,
 
 	pacs->source_loc_value = get_le32(value);
 
+	DBG(bap, "PACS Source Locations: 0x%08x", pacs->source_loc_value);
+
 	/* Resume reading sinks if supported but for some reason is empty */
 	if (pacs->source && queue_isempty(bap->rdb->sources)) {
 		uint16_t value_handle;
@@ -4211,6 +4161,8 @@ static void read_sink_pac_loc(bool success, uint8_t att_ecode,
 
 	pacs->sink_loc_value = get_le32(value);
 
+	DBG(bap, "PACS Sink Locations: 0x%08x", pacs->sink_loc_value);
+
 	/* Resume reading sinks if supported but for some reason is empty */
 	if (pacs->sink && queue_isempty(bap->rdb->sinks)) {
 		uint16_t value_handle;
@@ -5386,12 +5338,53 @@ static bool match_pac(struct bt_bap_pac *lpac, struct bt_bap_pac *rpac,
 	return false;
 }
 
+static void bap_pac_ltv_ch_counts(size_t i, uint8_t l, uint8_t t, uint8_t *v,
+								void *user_data)
+{
+	uint8_t *mask = user_data;
+
+	if (v)
+		*mask = *v;
+}
+
+static uint8_t bap_pac_ch_counts(struct bt_bap_pac *pac)
+{
+	uint8_t type = 0x03;
+	uint8_t mask = 0;
+
+	if (!pac->data)
+		return 0;
+
+	util_ltv_foreach(pac->data->iov_base, pac->data->iov_len, &type,
+						bap_pac_ltv_ch_counts, &mask);
+
+	if (!mask)
+		mask = 0x01;  /* default (BAP v1.0.1 Sec 4.3.1) */
+
+	return mask;
+}
+
 int bt_bap_select(struct bt_bap_pac *lpac, struct bt_bap_pac *rpac,
 			int *count, bt_bap_pac_select_t func,
 			void *user_data)
 {
-	const struct queue_entry *lchan, *rchan;
-	int selected = 0;
+	uint32_t locations;
+	uint8_t ch_counts;
+	unsigned int i, num_ase;
+
+	/* Hardcoded supported (multi-ASE) configurations: L/R channel pairs */
+	static const uint32_t configs[] = {
+		0x1 | 0x2,
+		0x10 | 0x20,
+		0x40 | 0x80,
+		0x400 | 0x800,
+		0x1000 | 0x2000,
+		0x10000 | 0x30000,
+		0x40000 | 0x80000,
+		0x400000 | 0x800000,
+		0x1000000 | 0x2000000,
+		0x4000000 | 0x8000000,
+	};
 
 	if (!lpac || !rpac || !func)
 		return -EINVAL;
@@ -5399,66 +5392,63 @@ int bt_bap_select(struct bt_bap_pac *lpac, struct bt_bap_pac *rpac,
 	if (!lpac->ops || !lpac->ops->select)
 		return -EOPNOTSUPP;
 
-	for (lchan = queue_get_entries(lpac->channels); lchan;
-					lchan = lchan->next) {
-		struct bt_bap_chan *lc = lchan->data;
-		struct bt_bap_chan map = *lc;
-		int i;
+	ch_counts = bap_pac_ch_counts(lpac) & bap_pac_ch_counts(rpac);
+	locations = bt_bap_pac_get_locations(rpac) & lpac->qos.location;
+	num_ase = 1;
 
-		for (i = 0, rchan = queue_get_entries(rpac->channels); rchan;
-					rchan = rchan->next, i++) {
-			struct bt_bap_chan *rc = rchan->data;
+	/* Check if multi-ASE configuration is needed */
+	for (i = 0; i < ARRAY_SIZE(configs); ++i) {
+		unsigned int n = __builtin_popcount(configs[i]);
 
-			/* Try matching the channel count */
-			if (!(map.count & rc->count))
-				break;
+		if (n == 0 || n > 8 || (ch_counts & BIT(n - 1)))
+			continue;
 
-			/* Check if location was set otherwise attempt to
-			 * assign one based on the number of channels it
-			 * supports.
-			 */
-			if (!rc->location) {
-				rc->location = bt_bap_pac_get_locations(rpac);
-				/* If channel count is 1 use a single
-				 * location
-				 */
-				if (rc->count == 0x01)
-					rc->location &= BIT(i);
-			}
+		if ((locations & configs[i]) == configs[i]) {
+			num_ase = n;
+			locations = configs[i];
+			break;
+		}
+	}
 
-			/* Try matching the channel location */
-			if (!(map.location & rc->location))
-				continue;
+	/* Otherwise leave channel allocation to sound server */
+	if (!locations || !ch_counts || num_ase == 1) {
+		lpac->ops->select(lpac, rpac, 0, &rpac->qos, func, user_data,
+							lpac->user_data);
+		if (count)
+			(*count)++;
+		return 0;
+	}
 
-			lpac->ops->select(lpac, rpac, map.location &
-						rc->location, &rpac->qos,
-						func, user_data,
-						lpac->user_data);
-			selected++;
+	/* Allocate channels */
+	while (locations && num_ase > 0) {
+		uint32_t allocation = 0, alloc = 0;
+		unsigned int n;
 
-			/* Check if there are any channels left to select */
-			map.count &= ~(map.count & rc->count);
-			/* Check if there are any locations left to select */
-			map.location &= ~(map.location & rc->location);
+		/* Multiplex as many as possible */
+		for (i = 0, n = 0; i < 32 && n < 8; ++i) {
+			if (!(locations & BIT(i)))
+				continue;
 
-			if (!map.count || !map.location)
-				break;
+			alloc |= BIT(i);
+			if (BIT(n) & ch_counts)
+				allocation = alloc;
 
-			/* Check if device require AC*(i) settings */
-			if (rc->count == 0x01)
-				map.count = map.count >> 1;
+			++n;
 		}
-	}
 
-	/* Fallback to no channel allocation since none could be matched. */
-	if (!selected) {
-		lpac->ops->select(lpac, rpac, 0, &rpac->qos, func, user_data,
-					lpac->user_data);
-		selected++;
-	}
+		if (!allocation)
+			break;
+
+		/* Select */
+		lpac->ops->select(lpac, rpac, allocation, &rpac->qos,
+					func, user_data, lpac->user_data);
 
-	if (count)
-		*count += selected;
+		locations &= ~allocation;
+		--num_ase;
+
+		if (count)
+			(*count)++;
+	}
 
 	return 0;
 }
-- 
2.45.2





[Index of Archives]     [Bluez Devel]     [Linux Wireless Networking]     [Linux Wireless Personal Area Networking]     [Linux ATH6KL]     [Linux USB Devel]     [Linux Media Drivers]     [Linux Audio Users]     [Linux Kernel]     [Linux SCSI]     [Big List of Linux Books]

  Powered by Linux