Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bluetooth: Audio: PAC location refactor #42593

Merged

Conversation

Thalley
Copy link
Collaborator

@Thalley Thalley commented Feb 8, 2022

The location values in PACs were build-time initiated and unchangable by the server itself.
This PR adds support for changing the value in the server, as the value will live in the server (currently implemented in the capabilities.c layer), instead of in the service.

This also adds notification for the value when it is changed, by either the server or a remove client.

fixes #42342
fixes #42343

@Thalley
Copy link
Collaborator Author

Thalley commented Feb 8, 2022

Depends on #42314

@github-actions github-actions bot added area: API Changes to public APIs area: Bluetooth area: Bluetooth Audio area: Tests Issues related to a particular existing or missing test labels Feb 8, 2022
@Thalley Thalley force-pushed the pac_location_update branch 5 times, most recently from f07e161 to d07b820 Compare February 9, 2022 14:13
@Thalley Thalley force-pushed the pac_location_update branch 3 times, most recently from 5fae16d to 0127753 Compare February 23, 2022 08:57
Comment on lines +200 to +234
enum bt_audio_location {
BT_AUDIO_LOCATION_FRONT_LEFT = BIT(0),
BT_AUDIO_LOCATION_FRONT_RIGHT = BIT(1),
BT_AUDIO_LOCATION_FRONT_CENTER = BIT(2),
BT_AUDIO_LOCATION_LOW_FREQ_EFFECTS_1 = BIT(3),
BT_AUDIO_LOCATION_BACK_LEFT = BIT(4),
BT_AUDIO_LOCATION_BACK_RIGHT = BIT(5),
BT_AUDIO_LOCATION_FRONT_LEFT_OF_CENTER = BIT(6),
BT_AUDIO_LOCATION_FRONT_RIGHT_OF_CENTER = BIT(7),
BT_AUDIO_LOCATION_BACK_CENTER = BIT(8),
BT_AUDIO_LOCATION_LOW_FREQ_EFFECTS_2 = BIT(9),
BT_AUDIO_LOCATION_SIDE_LEFT = BIT(10),
BT_AUDIO_LOCATION_SIDE_RIGHT = BIT(11),
BT_AUDIO_LOCATION_TOP_FRONT_LEFT = BIT(12),
BT_AUDIO_LOCATION_TOP_FRONT_RIGHT = BIT(13),
BT_AUDIO_LOCATION_TOP_FRONT_CENTER = BIT(14),
BT_AUDIO_LOCATION_TOP_CENTER = BIT(15),
BT_AUDIO_LOCATION_TOP_BACK_LEFT = BIT(16),
BT_AUDIO_LOCATION_TOP_BECK_RIGHT = BIT(17),
BT_AUDIO_LOCATION_TOP_SIDE_LEFT = BIT(18),
BT_AUDIO_LOCATION_TOP_SIDE_RIGHT = BIT(19),
BT_AUDIO_LOCATION_TOP_BACK_CENTER = BIT(20),
BT_AUDIO_LOCATION_BOTTOM_FRONT_CENTER = BIT(21),
BT_AUDIO_LOCATION_BOTTOM_FRONT_LEFT = BIT(22),
BT_AUDIO_LOCATION_BOTTOM_FRONT_RIGHT = BIT(23),
BT_AUDIO_LOCATION_FRONT_LEFT_WIDE = BIT(24),
BT_AUDIO_LOCATION_FRONT_RIGHT_WIDE = BIT(25),
BT_AUDIO_LOCATION_LEFT_SURROUND = BIT(26),
BT_AUDIO_LOCATION_RIGHT_SURROUND = BIT(27),
};

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Even though it makes sense to group these together, I do not think using an enum is a good way to do it.
Enums contains a finite number of possible values - hence you would need to suppress compiler warnings to be able to OR multiple values together - or you would need to typecast the result back to the enum type - which could result in errors if the compiler have made assumptions about the finite number of values (for example a switch statement do not need a default case when doing switch on an enum type and all possible cases are handled). I do not know if newer c-versions have support for this though?
I have seen others define a type and then typecast to this in the define:

typedef uint32_t bt_audio_loc_t
#define BT_AUDIO_LOCATION_FRONT_LEFT ((bt_audio_loc_t) BIT(0))

then you can use bt_audio_loc_t rather than uint32_t for function parameters that expect audio location(s). And you can freely OR multiple together as the result would still be uint32_t.

To group them together in the API documentation use the @name Audio Locations @{ ... @} grouping feature

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I hear your concerns and see what issues this may cause.

The reasoning behind this approach is:

  1. (named) enums provide an easy way for an API to express the "group" of values expected, without relying on doxygen-specific grouping
  2. https://www.kernel.org/doc/html/v4.10/process/coding-style.html#macros-enums-and-rtl (which we follow) express that enums are preferred for groups of constants
  3. This is the approach (although without the name) used by e.g. BT_LE_ADV_OPT_, and other option enums in bluetooth.h.

If we see compiler issues, then this is indeed something we need to fix. If the issue is caused by using the enum type in the APIs, then we can replace the enum type with uint32_t (similar to how the BT_LE_ADV_OPT_ are used), and simply add a reference to the enum in the function description.

If the issue is caused by just using the enum keyword, then this problem exist in many places in the code base, and we need a general fix as well as an exception to the Linux kernel code guideline.

Do you have any references to the compiler issues (warnings) you are experiencing that I can read?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this is an accepted way to do it in Zephyr, I'm OK with it.
I do not have a reference, but in my early days I was told to refactor a full FM radio API I made due to this issue (not a GCC based compiler) - hence I always try to avoid using enums when I specify bit-values.
I had a look at the different enum related warnings that GCC support, and non of them covers this issue, hence I think we can go ahead using enums in this case.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Otherwise refactoring to a group of #defines can be done without breaking the API if we just change the API from enum bt_audio_location to uint32_t bt_audio_location.

@@ -46,7 +46,7 @@ zephyr_library_sources_ifdef(CONFIG_BT_ASCS ascs.c)
zephyr_library_sources_ifdef(CONFIG_BT_PACS pacs.c)
zephyr_library_sources_ifdef(CONFIG_BT_AUDIO_STREAM stream.c)
zephyr_library_sources_ifdef(CONFIG_BT_AUDIO_UNICAST_SERVER unicast_server.c)
zephyr_library_sources_ifdef(CONFIG_BT_AUDIO_CAPABILITIES capabilities.c)
zephyr_library_sources_ifdef(CONFIG_bt_audio_capability capabilities.c)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wrong renaming I think?
Same below

@Thalley Thalley force-pushed the pac_location_update branch from 0127753 to 894bc92 Compare March 1, 2022 10:32
Copy link
Collaborator

@Casper-Bonde-Bose Casper-Bonde-Bose left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

Copy link
Collaborator

@Casper-Bonde-Bose Casper-Bonde-Bose left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@Thalley
Copy link
Collaborator Author

Thalley commented Mar 10, 2022

@asbjornsabo @MariuszSkamra @jhedberg This is blocking other development, will one (or more) of you please review?

include/bluetooth/audio/audio.h Show resolved Hide resolved
include/bluetooth/audio/audio.h Show resolved Hide resolved

if (unicast_server_cb == NULL ||
unicast_server_cb->publish_location == NULL) {
BT_WARN("Not callback for publish_location");
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not -> No?
(Other places as well)

}

return bt_gatt_attr_read(conn, attr, buf, len, offset,
&sys_cpu_to_le32(location_32),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesn't the result have to be assigned to a variable before applying the & operator (so that the operand is an "lvalue")?
(Also elsewhere)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i.e. if we need to do something like

	uint32_t le_location = sys_cpu_to_le32(location_32);

	return bt_gatt_attr_read(conn, attr, buf, len, offset,
				 &le_location, sizeof(le_location)); 

instead?

I actually do not really know. It doesn't throw any errors.

Keep in mind that sys_cpu_to_le32 is a macro, and for LE it will be

#define sys_cpu_to_le32(val) (val)

and

#define sys_cpu_to_le32(val) __bswap_32(val)

for BE. Not sure if this will give an error for BE, and I am not sure how I can test it.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried to use the same thing in some code here, and I get compile errors for both variants.
In my understanding, this should not work - I think one ends up with a value, not a variable, and one can take take the address of a value.
Will check again - I may not have tested correctly.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tested again. This time it worked for the "le" variant, but fails for the "be" variant.

	uint32_t location;
	uint32_t *p;

	location = 7;
	p = &sys_cpu_to_be32(location);

gives
error: lvalue required as unary '&' operand
324 | p = &sys_cpu_to_be32(location);
| ^

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

Comment on lines 956 to 900
/** @brief Audio Capability type */
enum bt_audio_pac_type {
BT_AUDIO_SINK = 0x01,
BT_AUDIO_SOURCE = 0x02,
};

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know you move the code only, but I saw this is used for ASE direction as well. Maybe the enum name should be different then.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree. Do you have any suggestions? bt_audio_type seems a bit too generic for my taste. bt_audio_ep_type perhaps?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's fix this in a new issue: #43693

Comment on lines +1119 to +1085
*
* Publish location callback is called whenever a remote client
* requests to read the Published Audio Capabilities (PAC) location,
* or if the location needs to be notified.
*
* @param[in] conn The connection that requests the location.
* Will be NULL if the location is requested
* for sending a notification, as a result of
* callling
* bt_audio_unicast_server_location_changed().
* @param[in] type Type of the endpoint.
* @param[out] location Pointer to the location that needs to be set.
*
* @return 0 in case of success or negative value in case of error.
*/
int (*publish_location)(struct bt_conn *conn,
enum bt_audio_pac_type type,
enum bt_audio_location *location);

/** @brief Write location callback
*
* Write location callback is called whenever a remote client
* requests to write the Published Audio Capabilities (PAC) location.
*
* @param conn The connection that requests the write.
* @param type Type of the endpoint.
* @param location The location being written.
*
* @return 0 in case of success or negative value in case of error.
*/
int (*write_location)(struct bt_conn *conn, enum bt_audio_pac_type type,
enum bt_audio_location location);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why the location cannot be stored internally?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It can, however PACS was (is) designed to be stateless, i.e. all the values are located in an upper layer.

It would have been easy to store the locaiton in PACS and still keep e.g. the capabilities in an upper layer, but I did not want to mix-match where the service data/states were stored, so used the same approach for the location as we have for the other values in PACS.

This of course is a more complex implementation and possibly erroneous as the notifications when the location is changed is no longer ensured by the stack.

We have yet to decided what final approach we want to take for the LE Audio services, as both approaches have their pros and cons.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe an application can change the value run-time - hence I think this implementation is how it should be.
But reading the description again I think:
* Write location callback is called whenever a remote client
* requests to write the Published Audio Capabilities (PAC) location.
should be:
* Write location callback is called whenever a remote client
* requests to read the Published Audio Capabilities (PAC) location.

PAC's are read only and my assumption was that the callback was called write as the application (PAC server) needs to write the value that the client wants to read?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Casper-Bonde-Bose No, the write description is correct. The Sink and Source location characteristics are (optionally) writeable:
image

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see - Maybe a feature to be able to configure a speaker to be at a specific location?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Possibly, yes. We (@asbjornsabo and I) raised this in the GAWG some time ago, and it seems that in hindsight it shouldn't have been writable.

Refactor the PAC location read and write. Instead
of storing the location in the service, the
location is now stored in the application, and
is retrieved by the service via callbacks.

Similarly, if a client writes the location, this
request is being sent to the application.

Signed-off-by: Emil Gydesen <[email protected]>
@Thalley
Copy link
Collaborator Author

Thalley commented Mar 11, 2022

Re-pushed to fix a bad merge conflict fix

Copy link
Collaborator

@MariuszSkamra MariuszSkamra left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

Copy link
Collaborator

@asbjornsabo asbjornsabo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My comments have been addressed.

@mbolivar-nordic mbolivar-nordic merged commit 1846653 into zephyrproject-rtos:main Mar 11, 2022
@Thalley Thalley deleted the pac_location_update branch April 5, 2022 11:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area: API Changes to public APIs area: Bluetooth Audio area: Bluetooth area: Tests Issues related to a particular existing or missing test
Projects
None yet
Development

Successfully merging this pull request may close these issues.

LE Audio: PACS: Server change location LE Audio: PACS notify changes to locations
5 participants