From 82ad7599a706b4be8d055e913ad629ffce492cab Mon Sep 17 00:00:00 2001 From: Mariusz Skamra Date: Wed, 21 Feb 2024 12:56:26 +0100 Subject: [PATCH] nimble/audio: Add LE Audio event listener This adds dedicated event listener for LE Audio events. --- .../host/audio/include/host/audio/ble_audio.h | 138 +++++++++++ nimble/host/audio/src/ble_audio.c | 232 ++++++++++++++++++ nimble/host/audio/src/ble_audio_priv.h | 27 ++ nimble/host/audio/test/src/ble_audio_test.c | 2 + .../ble_audio_listener_register_test.c | 59 +++++ nimble/host/audio/test/syscfg.yml | 2 + nimble/host/include/host/ble_audio_common.h | 4 +- .../services/auracast/src/ble_svc_auracast.c | 2 +- 8 files changed, 464 insertions(+), 2 deletions(-) create mode 100644 nimble/host/audio/src/ble_audio_priv.h create mode 100644 nimble/host/audio/test/src/testcases/ble_audio_listener_register_test.c diff --git a/nimble/host/audio/include/host/audio/ble_audio.h b/nimble/host/audio/include/host/audio/ble_audio.h index f15b235358..6ede276da0 100644 --- a/nimble/host/audio/include/host/audio/ble_audio.h +++ b/nimble/host/audio/include/host/audio/ble_audio.h @@ -21,9 +21,147 @@ #define H_BLE_AUDIO_ #include +#include #include "host/ble_audio_common.h" +/** @brief Public Broadcast Announcement features bits */ +enum ble_audio_pub_bcst_announcement_feat { + /** Broadcast Stream Encryption */ + BLE_AUDIO_PUB_BCST_ANNOUNCEMENT_FEAT_ENCRYPTION = 1 << 0, + + /** Standard Quality Public Broadcast Audio */ + BLE_AUDIO_PUB_BCST_ANNOUNCEMENT_FEAT_SQ = 1 << 1, + + /** High Quality Public Broadcast Audio */ + BLE_AUDIO_PUB_BCST_ANNOUNCEMENT_FEAT_HQ = 1 << 2, +}; + +/** @brief Public Broadcast Announcement structure */ +struct ble_audio_pub_bcst_announcement { + /** Public Broadcast Announcement features bitfield */ + enum ble_audio_pub_bcst_announcement_feat features; + + /** Metadata length */ + uint8_t metadata_len; + + /** Metadata */ + const uint8_t *metadata; +}; + +struct ble_audio_bcst_name { + /** Broadcast Name length */ + uint8_t name_len; + + /** Broadcast Name */ + const char *name; +}; + +/** + * @defgroup ble_audio_events Bluetooth Low Energy Audio Events + * @{ + */ + +/** BLE Audio event: Broadcast Announcement */ +#define BLE_AUDIO_EVENT_BCST_ANNOUNCEMENT 0 + +/** @} */ + +/** @brief Broadcast Announcement */ +struct ble_audio_event_bcst_announcement { + /** Extended advertising report */ + const struct ble_gap_ext_disc_desc *ext_disc; + + /** Broadcast ID */ + uint32_t broadcast_id; + + /** Additional service data included in Broadcast Audio Announcement */ + const uint8_t *svc_data; + + /** Additional service data length */ + uint16_t svc_data_len; + + /** Optional Public Broadcast Announcement data */ + struct ble_audio_pub_bcst_announcement *pub_announcement_data; + + /** Optional Broadcast Name */ + struct ble_audio_bcst_name *name; +}; + +/** + * Represents a BLE Audio related event. When such an event occurs, the host + * notifies the application by passing an instance of this structure to an + * application-specified callback. + */ +struct ble_audio_event { + /** + * Indicates the type of BLE Audio event that occurred. This is one of the + * BLE_AUDIO_EVENT codes. + */ + uint8_t type; + + /** + * A discriminated union containing additional details concerning the event. + * The 'type' field indicates which member of the union is valid. + */ + union { + /** + * @ref BLE_AUDIO_EVENT_BCST_ANNOUNCEMENT + * + * Represents a received Broadcast Announcement. + */ + struct ble_audio_event_bcst_announcement bcst_announcement; + }; +}; + +/** Callback function type for handling BLE Audio events. */ +typedef int ble_audio_event_fn(struct ble_audio_event *event, void *arg); + +/** + * Event listener structure + * + * This should be used as an opaque structure and not modified manually. + */ +struct ble_audio_event_listener { + /** The function to call when a BLE Audio event occurs. */ + ble_audio_event_fn *fn; + + /** An optional argument to pass to the event handler function. */ + void *arg; + + /** Singly-linked list entry. */ + SLIST_ENTRY(ble_audio_event_listener) next; +}; + +/** + * Registers listener for BLE Audio events + * + * On success listener structure will be initialized automatically and does not + * need to be initialized prior to calling this function. To change callback + * and/or argument unregister listener first and register it again. + * + * @param[in] listener Listener structure + * @param[in] event_mask Optional event mask + * @param[in] fn Callback function + * @param[in] arg Optional callback argument + * + * @return 0 on success + * BLE_HS_EINVAL if no callback is specified + * BLE_HS_EALREADY if listener is already registered + */ +int ble_audio_event_listener_register(struct ble_audio_event_listener *listener, + ble_audio_event_fn *fn, void *arg); + +/** + * Unregisters listener for BLE Audio events + * + * @param[in] listener Listener structure + * + * @return 0 on success + * BLE_HS_ENOENT if listener was not registered + */ +int ble_audio_event_listener_unregister(struct ble_audio_event_listener *listener); + /** * BASE iterator * diff --git a/nimble/host/audio/src/ble_audio.c b/nimble/host/audio/src/ble_audio.c index 6c2727086a..a858eef5ad 100644 --- a/nimble/host/audio/src/ble_audio.c +++ b/nimble/host/audio/src/ble_audio.c @@ -23,6 +23,238 @@ #include "host/ble_hs.h" #include "host/audio/ble_audio.h" +#include "ble_audio_priv.h" + +static struct ble_gap_event_listener ble_audio_gap_event_listener; +static SLIST_HEAD(, ble_audio_event_listener) ble_audio_event_listener_list = + SLIST_HEAD_INITIALIZER(ble_audio_event_listener_list); + +struct ble_audio_adv_parse_bcst_announcement_data { + struct ble_audio_event event; + struct ble_audio_pub_bcst_announcement pub; + struct ble_audio_bcst_name name; + bool success; +}; + +static int +ble_audio_adv_parse_bcst_announcement(const struct ble_hs_adv_field *field, + void *user_data) +{ + struct ble_audio_adv_parse_bcst_announcement_data *data = user_data; + struct ble_audio_event_bcst_announcement *event; + const uint8_t value_len = field->length - sizeof(field->length); + ble_uuid16_t uuid16 = BLE_UUID16_INIT(0); + uint8_t offset = 0; + + event = &data->event.bcst_announcement; + + data->success = false; + + switch (field->type) { + case BLE_HS_ADV_TYPE_SVC_DATA_UUID16: + if (value_len < 2) { + break; + } + + uuid16.value = get_le16(&field->value[offset]); + offset += 2; + + switch (uuid16.value) { + case BLE_BROADCAST_AUDIO_ANNOUNCEMENT_SVC_UUID: + if ((value_len - offset) < 3) { + /* Stop parsing */ + data->success = false; + return 0; + } + + event->broadcast_id = get_le24(&field->value[offset]); + offset += 3; + + if (value_len > offset) { + event->svc_data = &field->value[offset]; + event->svc_data_len = value_len - offset; + } + + data->success = true; + break; + + case BLE_BROADCAST_PUB_ANNOUNCEMENT_SVC_UUID: + if (event->pub_announcement_data != NULL) { + /* Ignore */ + break; + } + + if ((value_len - offset) < 2) { + /* Stop parsing */ + data->success = false; + return 0; + } + + data->pub.features = field->value[offset++]; + data->pub.metadata_len = field->value[offset++]; + + if ((value_len - offset) < data->pub.metadata_len) { + break; + } + + data->pub.metadata = &field->value[offset]; + + event->pub_announcement_data = &data->pub; + break; + + default: + break; + } + + break; + + case BLE_HS_ADV_TYPE_BROADCAST_NAME: + if (event->name != NULL) { + /* Ignore */ + break; + } + + if (value_len < 4 || value_len > 32) { + /* Stop parsing */ + data->success = false; + return 0; + } + + data->name.name = (char *)field->value; + data->name.name_len = value_len; + + event->name = &data->name; + break; + + default: + break; + } + + /* Continue parsing */ + return BLE_HS_ENOENT; +} + +static int +ble_audio_gap_event(struct ble_gap_event *gap_event, void *arg) +{ + switch (gap_event->type) { + case BLE_GAP_EVENT_EXT_DISC: { + struct ble_audio_adv_parse_bcst_announcement_data data = { 0 }; + int rc; + + rc = ble_hs_adv_parse(gap_event->ext_disc.data, + gap_event->ext_disc.length_data, + ble_audio_adv_parse_bcst_announcement, &data); + if (rc == 0 && data.success) { + data.event.type = BLE_AUDIO_EVENT_BCST_ANNOUNCEMENT; + data.event.bcst_announcement.ext_disc = &gap_event->ext_disc; + + (void)ble_audio_event_listener_call(&data.event); + } + break; + } + + default: + break; + } + + return 0; +} + +int +ble_audio_event_listener_register(struct ble_audio_event_listener *listener, + ble_audio_event_fn *fn, void *arg) +{ + struct ble_audio_event_listener *evl = NULL; + int rc; + + if (listener == NULL) { + BLE_HS_LOG_ERROR("NULL listener!\n"); + return BLE_HS_EINVAL; + } + + if (fn == NULL) { + BLE_HS_LOG_ERROR("NULL fn!\n"); + return BLE_HS_EINVAL; + } + + SLIST_FOREACH(evl, &ble_audio_event_listener_list, next) { + if (evl == listener) { + break; + } + } + + if (!evl) { + if (SLIST_EMPTY(&ble_audio_event_listener_list)) { + rc = ble_gap_event_listener_register( + &ble_audio_gap_event_listener, + ble_audio_gap_event, NULL); + if (rc != 0) { + return rc; + } + } + + memset(listener, 0, sizeof(*listener)); + listener->fn = fn; + listener->arg = arg; + SLIST_INSERT_HEAD(&ble_audio_event_listener_list, listener, next); + rc = 0; + } else { + rc = BLE_HS_EALREADY; + } + + return rc; +} + +int +ble_audio_event_listener_unregister(struct ble_audio_event_listener *listener) +{ + struct ble_audio_event_listener *evl = NULL; + int rc; + + if (listener == NULL) { + BLE_HS_LOG_ERROR("NULL listener!\n"); + return BLE_HS_EINVAL; + } + + /* We check if element exists on the list only for sanity to let caller + * know whether it registered its listener before. + */ + SLIST_FOREACH(evl, &ble_audio_event_listener_list, next) { + if (evl == listener) { + break; + } + } + + if (!evl) { + rc = BLE_HS_ENOENT; + } else { + SLIST_REMOVE(&ble_audio_event_listener_list, listener, + ble_audio_event_listener, next); + + if (SLIST_EMPTY(&ble_audio_event_listener_list)) { + rc = ble_gap_event_listener_unregister( + &ble_audio_gap_event_listener); + } else { + rc = 0; + } + } + + return rc; +} + +int +ble_audio_event_listener_call(struct ble_audio_event *event) +{ + struct ble_audio_event_listener *evl = NULL; + + SLIST_FOREACH(evl, &ble_audio_event_listener_list, next) { + evl->fn(event, evl->arg); + } + + return 0; +} + /* Get the next subgroup data pointer */ static const uint8_t * ble_audio_base_subgroup_next(uint8_t num_bis, const uint8_t *data, diff --git a/nimble/host/audio/src/ble_audio_priv.h b/nimble/host/audio/src/ble_audio_priv.h new file mode 100644 index 0000000000..bedbdaf058 --- /dev/null +++ b/nimble/host/audio/src/ble_audio_priv.h @@ -0,0 +1,27 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + +#ifndef H_BLE_AUDIO_PRIV_ +#define H_BLE_AUDIO_PRIV_ + +#include "host/audio/ble_audio.h" + +int ble_audio_event_listener_call(struct ble_audio_event *event); + +#endif /* H_BLE_AUDIO_PRIV_ */ diff --git a/nimble/host/audio/test/src/ble_audio_test.c b/nimble/host/audio/test/src/ble_audio_test.c index 14fcf15f5f..91e4a91449 100644 --- a/nimble/host/audio/test/src/ble_audio_test.c +++ b/nimble/host/audio/test/src/ble_audio_test.c @@ -21,10 +21,12 @@ #include "testutil/testutil.h" TEST_SUITE_DECL(ble_audio_base_parse_test_suite); +TEST_CASE_DECL(ble_audio_listener_register_test); TEST_SUITE(ble_audio_test) { ble_audio_base_parse_test_suite(); + ble_audio_listener_register_test(); } int diff --git a/nimble/host/audio/test/src/testcases/ble_audio_listener_register_test.c b/nimble/host/audio/test/src/testcases/ble_audio_listener_register_test.c new file mode 100644 index 0000000000..f55b51a3c3 --- /dev/null +++ b/nimble/host/audio/test/src/testcases/ble_audio_listener_register_test.c @@ -0,0 +1,59 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + +#include "testutil/testutil.h" + +#include "host/ble_hs.h" +#include "host/audio/ble_audio.h" + +static struct ble_audio_event_listener event_listener; + +static int +event_handler(struct ble_audio_event *event, void *arg) +{ + return 0; +} + +TEST_CASE_SELF(ble_audio_listener_register_test) +{ + int rc; + + rc = ble_audio_event_listener_register(&event_listener, event_handler, + NULL); + TEST_ASSERT(rc == 0); + + rc = ble_audio_event_listener_register(&event_listener, event_handler, + NULL); + TEST_ASSERT(rc != 0); + + rc = ble_audio_event_listener_unregister(&event_listener); + TEST_ASSERT(rc == 0); + + rc = ble_audio_event_listener_register(NULL, event_handler, NULL); + TEST_ASSERT(rc != 0); + + rc = ble_audio_event_listener_register(&event_listener, NULL, NULL); + TEST_ASSERT(rc != 0); + + rc = ble_audio_event_listener_unregister(NULL); + TEST_ASSERT(rc != 0); + + rc = ble_audio_event_listener_unregister(&event_listener); + TEST_ASSERT(rc != 0); +} diff --git a/nimble/host/audio/test/syscfg.yml b/nimble/host/audio/test/syscfg.yml index 3c760594c5..7fad93f3fe 100644 --- a/nimble/host/audio/test/syscfg.yml +++ b/nimble/host/audio/test/syscfg.yml @@ -27,3 +27,5 @@ syscfg.vals: BLE_VERSION: 54 BLE_HS_DEBUG: 1 + + BLE_EXT_ADV: 1 diff --git a/nimble/host/include/host/ble_audio_common.h b/nimble/host/include/host/ble_audio_common.h index 815d3f4c0d..05780c0797 100644 --- a/nimble/host/include/host/ble_audio_common.h +++ b/nimble/host/include/host/ble_audio_common.h @@ -67,7 +67,9 @@ /** Broadcast Audio Announcement Service UUID. */ #define BLE_BROADCAST_AUDIO_ANNOUNCEMENT_SVC_UUID 0x1852 -#define BLE_BROADCAST_PUBLIC_BROADCAST_ANNOUNCEMENT_SVC_UUID 0x1856 + +/** Public Broadcast Announcement Service UUID. */ +#define BLE_BROADCAST_PUB_ANNOUNCEMENT_SVC_UUID 0x1856 /** * @defgroup ble_audio_sampling_rates Bluetooth Low Energy Audio Sampling Rates diff --git a/nimble/host/services/auracast/src/ble_svc_auracast.c b/nimble/host/services/auracast/src/ble_svc_auracast.c index b1bc7d33a6..7bd9112dfc 100644 --- a/nimble/host/services/auracast/src/ble_svc_auracast.c +++ b/nimble/host/services/auracast/src/ble_svc_auracast.c @@ -62,7 +62,7 @@ ble_svc_auracast_create(const struct ble_svc_auracast_create_params *params, auracast_svc_data[data_offset] = BLE_HS_ADV_TYPE_SVC_DATA_UUID16; data_offset++; put_le16(&auracast_svc_data[data_offset], - BLE_BROADCAST_PUBLIC_BROADCAST_ANNOUNCEMENT_SVC_UUID); + BLE_BROADCAST_PUB_ANNOUNCEMENT_SVC_UUID); data_offset += 2; auracast_svc_data[data_offset] = features; data_offset++;