diff --git a/include/zephyr/bluetooth/audio/bap.h b/include/zephyr/bluetooth/audio/bap.h index 38356a175271b..17e53ca6c14f8 100644 --- a/include/zephyr/bluetooth/audio/bap.h +++ b/include/zephyr/bluetooth/audio/bap.h @@ -2001,6 +2001,53 @@ int bt_bap_base_subgroup_bis_codec_to_codec_cfg(const struct bt_bap_base_subgrou * @{ */ +/** + * @brief Struct to hold the Broadcast Source callbacks + * + * These can be registered for usage with bt_bap_broadcast_source_register_cb(). + */ +struct bt_bap_broadcast_source_cb { + /** + * @brief The Broadcast Source has started and all of the streams are ready for audio data + * + * @param source The started Broadcast Source + */ + void (*started)(struct bt_bap_broadcast_source *source); + + /** + * @brief The Broadcast Source has stopped and none of the streams are ready for audio data + * + * @param source The stopped Broadcast Source + * @param reason The reason why the Broadcast Source stopped (see the BT_HCI_ERR_* values) + */ + void (*stopped)(struct bt_bap_broadcast_source *source, uint8_t reason); + + /** @internal Internally used field for list handling */ + sys_snode_t _node; +}; + +/** + * @brief Registers callbacks for Broadcast Sources + * + * @param cb Pointer to the callback structure. + * + * @retval 0 on success + * @retval -EINVAL if @p cb is NULL + * @retval -EEXIST if @p cb is already registered + */ +int bt_bap_broadcast_source_register_cb(struct bt_bap_broadcast_source_cb *cb); + +/** + * @brief Unregisters callbacks for Broadcast Sources + * + * @param cb Pointer to the callback structure. + * + * @retval 0 on success + * @retval -EINVAL if @p cb is NULL + * @retval -ENOENT if @p cb is not registered + */ +int bt_bap_broadcast_source_unregister_cb(struct bt_bap_broadcast_source_cb *cb); + /** Broadcast Source stream parameters */ struct bt_bap_broadcast_source_stream_param { /** Audio stream */ @@ -2245,6 +2292,22 @@ struct bt_bap_broadcast_sink_cb { */ void (*syncable)(struct bt_bap_broadcast_sink *sink, const struct bt_iso_biginfo *biginfo); + /** + * @brief The Broadcast Sink has started and audio data may be received from all of the + * streams + * + * @param sink The started Broadcast Sink + */ + void (*started)(struct bt_bap_broadcast_sink *sink); + + /** + * @brief The Broadcast Sink has stopped and none of the streams will receive audio data + * + * @param sink The stopped Broadcast Sink + * @param reason The reason why the Broadcast Sink stopped (see the BT_HCI_ERR_* values) + */ + void (*stopped)(struct bt_bap_broadcast_sink *sink, uint8_t reason); + /** @internal Internally used list node */ sys_snode_t _node; }; @@ -2255,11 +2318,12 @@ struct bt_bap_broadcast_sink_cb { * It is possible to register multiple struct of callbacks, but a single struct can only be * registered once. * Registering the same callback multiple times is undefined behavior and may break the stack. - * + * @param cb Broadcast sink callback structure. * - * @retval 0 in case of success + * @retval 0 on success * @retval -EINVAL if @p cb is NULL + * @retval -EALREADY if @p cb was already registered */ int bt_bap_broadcast_sink_register_cb(struct bt_bap_broadcast_sink_cb *cb); diff --git a/include/zephyr/bluetooth/audio/cap.h b/include/zephyr/bluetooth/audio/cap.h index 34b5b0f3e1cc0..b539a463c2689 100644 --- a/include/zephyr/bluetooth/audio/cap.h +++ b/include/zephyr/bluetooth/audio/cap.h @@ -128,6 +128,22 @@ struct bt_cap_initiator_cb { */ void (*unicast_stop_complete)(int err, struct bt_conn *conn); #endif /* CONFIG_BT_BAP_UNICAST_CLIENT */ +#if defined(CONFIG_BT_BAP_BROADCAST_SOURCE) + /** + * @brief The Broadcast Source has started and all of the streams are ready for audio data + * + * @param source The started Broadcast Source + */ + void (*broadcast_started)(struct bt_cap_broadcast_source *source); + + /** + * @brief The Broadcast Source has stopped and none of the streams are ready for audio data + * + * @param source The stopped Broadcast Source + * @param reason The reason why the Broadcast Source stopped (see the BT_HCI_ERR_* values) + */ + void (*broadcast_stopped)(struct bt_cap_broadcast_source *source, uint8_t reason); +#endif /* CONFIG_BT_BAP_BROADCAST_SOURCE */ }; /** diff --git a/include/zephyr/bluetooth/iso.h b/include/zephyr/bluetooth/iso.h index 4251a2c87cb1a..fea94f6212c6c 100644 --- a/include/zephyr/bluetooth/iso.h +++ b/include/zephyr/bluetooth/iso.h @@ -34,6 +34,7 @@ #include #include #include +#include #ifdef __cplusplus extern "C" { @@ -1102,6 +1103,42 @@ int bt_iso_chan_get_info(const struct bt_iso_chan *chan, struct bt_iso_info *inf */ int bt_iso_chan_get_tx_sync(const struct bt_iso_chan *chan, struct bt_iso_tx_info *info); +/** + * @brief Struct to hold the Broadcast Isochronous Group callbacks + * + * These can be registered for usage with bt_iso_big_register_cb(). + */ +struct bt_iso_big_cb { + /** + * @brief The BIG has started and all of the streams are ready for data + * + * @param big The started BIG + */ + void (*started)(struct bt_iso_big *big); + + /** + * @brief The BIG has stopped and none of the streams are ready for data + * + * @param big The stopped BIG + * @param reason The reason why the BIG stopped (see the BT_HCI_ERR_* values) + */ + void (*stopped)(struct bt_iso_big *big, uint8_t reason); + + /** @internal Internally used field for list handling */ + sys_snode_t _node; +}; + +/** + * @brief Registers callbacks for Broadcast Sources + * + * @param cb Pointer to the callback structure. + * + * @retval 0 on success + * @retval -EINVAL if @p cb is NULL + * @retval -EEXIST if @p cb is already registered + */ +int bt_iso_big_register_cb(struct bt_iso_big_cb *cb); + /** * @brief Creates a BIG as a broadcaster * diff --git a/samples/bluetooth/bap_broadcast_sink/src/main.c b/samples/bluetooth/bap_broadcast_sink/src/main.c index cee4f5b008cb9..902670aaee56a 100644 --- a/samples/bluetooth/bap_broadcast_sink/src/main.c +++ b/samples/bluetooth/bap_broadcast_sink/src/main.c @@ -536,11 +536,6 @@ static void stream_started_cb(struct bt_bap_stream *stream) #endif /* CONFIG_LIBLC3 */ k_sem_give(&sem_stream_started); - if (k_sem_count_get(&sem_stream_started) == stream_count) { - big_synced = true; - printk("BIG synced\n"); - k_sem_give(&sem_big_synced); - } } static void stream_stopped_cb(struct bt_bap_stream *stream, uint8_t reason) @@ -553,11 +548,6 @@ static void stream_stopped_cb(struct bt_bap_stream *stream, uint8_t reason) if (err != 0) { printk("Failed to take sem_stream_started: %d\n", err); } - - if (k_sem_count_get(&sem_stream_started) != stream_count) { - printk("BIG sync terminated\n"); - big_synced = false; - } } static void stream_recv_cb(struct bt_bap_stream *stream, const struct bt_iso_recv_info *info, @@ -818,9 +808,27 @@ static void syncable_cb(struct bt_bap_broadcast_sink *sink, const struct bt_iso_ } } +static void broadcast_sink_started_cb(struct bt_bap_broadcast_sink *sink) +{ + printk("Broadcast sink %p started\n", sink); + + big_synced = true; + k_sem_give(&sem_big_synced); +} + +static void broadcast_sink_stopped_cb(struct bt_bap_broadcast_sink *sink, uint8_t reason) +{ + printk("Broadcast sink %p stopped with reason 0x%02X\n", sink, reason); + + big_synced = false; + k_sem_give(&sem_broadcast_sink_stopped); +} + static struct bt_bap_broadcast_sink_cb broadcast_sink_cbs = { .base_recv = base_recv_cb, .syncable = syncable_cb, + .started = broadcast_sink_started_cb, + .stopped = broadcast_sink_stopped_cb, }; static void pa_timer_handler(struct k_work *work) @@ -1027,8 +1035,6 @@ static int bis_sync_req_cb(struct bt_conn *conn, return err; } - - k_sem_give(&sem_broadcast_sink_stopped); } broadcaster_broadcast_id = recv_state->broadcast_id; @@ -1242,7 +1248,7 @@ static void bap_pa_sync_terminated_cb(struct bt_le_per_adv_sync *sync, if (info->reason != BT_HCI_ERR_LOCALHOST_TERM_CONN && req_recv_state != NULL) { int err; - if (k_sem_count_get(&sem_stream_connected) > 0) { + if (big_synced) { err = bt_bap_broadcast_sink_stop(broadcast_sink); if (err != 0) { printk("Failed to stop Broadcast Sink: %d\n", err); @@ -1257,8 +1263,6 @@ static void bap_pa_sync_terminated_cb(struct bt_le_per_adv_sync *sync, return; } - - k_sem_give(&sem_broadcast_sink_stopped); } } } diff --git a/samples/bluetooth/bap_broadcast_source/src/main.c b/samples/bluetooth/bap_broadcast_source/src/main.c index b0238ade6bd02..0df0773cbf846 100644 --- a/samples/bluetooth/bap_broadcast_source/src/main.c +++ b/samples/bluetooth/bap_broadcast_source/src/main.c @@ -153,8 +153,8 @@ static int16_t send_pcm_data[MAX_NUM_SAMPLES]; static uint16_t seq_num; static bool stopping; -static K_SEM_DEFINE(sem_started, 0U, ARRAY_SIZE(streams)); -static K_SEM_DEFINE(sem_stopped, 0U, ARRAY_SIZE(streams)); +static K_SEM_DEFINE(sem_started, 0U, 1U); +static K_SEM_DEFINE(sem_stopped, 0U, 1U); #define BROADCAST_SOURCE_LIFETIME 120U /* seconds */ @@ -365,12 +365,6 @@ static void stream_started_cb(struct bt_bap_stream *stream) source_stream->seq_num = 0U; source_stream->sent_cnt = 0U; - k_sem_give(&sem_started); -} - -static void stream_stopped_cb(struct bt_bap_stream *stream, uint8_t reason) -{ - k_sem_give(&sem_stopped); } static void stream_sent_cb(struct bt_bap_stream *stream) @@ -387,7 +381,9 @@ static void stream_sent_cb(struct bt_bap_stream *stream) } static struct bt_bap_stream_ops stream_ops = { - .started = stream_started_cb, .stopped = stream_stopped_cb, .sent = stream_sent_cb}; + .started = stream_started_cb, + .sent = stream_sent_cb, +}; static int setup_broadcast_source(struct bt_bap_broadcast_source **source) { @@ -439,8 +435,24 @@ static int setup_broadcast_source(struct bt_bap_broadcast_source **source) return 0; } +static void source_started_cb(struct bt_bap_broadcast_source *source) +{ + printk("Broadcast source %p started\n", source); + k_sem_give(&sem_started); +} + +static void source_stopped_cb(struct bt_bap_broadcast_source *source, uint8_t reason) +{ + printk("Broadcast source %p stopped with reason 0x%02X\n", source, reason); + k_sem_give(&sem_stopped); +} + int main(void) { + static struct bt_bap_broadcast_source_cb broadcast_source_cb = { + .started = source_started_cb, + .stopped = source_stopped_cb, + }; struct bt_le_ext_adv *adv; int err; @@ -451,6 +463,12 @@ int main(void) } printk("Bluetooth initialized\n"); + err = bt_bap_broadcast_source_register_cb(&broadcast_source_cb); + if (err != 0) { + printk("Failed to register broadcast source callbacks (err %d)\n", err); + return 0; + } + for (size_t i = 0U; i < ARRAY_SIZE(send_pcm_data); i++) { /* Initialize mock data */ send_pcm_data[i] = i; @@ -581,10 +599,8 @@ int main(void) return 0; } - /* Wait for all to be started */ - for (size_t i = 0U; i < ARRAY_SIZE(streams); i++) { - k_sem_take(&sem_started, K_FOREVER); - } + /* Wait for broadcast source to be started */ + k_sem_take(&sem_started, K_FOREVER); printk("Broadcast source started\n"); /* Initialize sending */ @@ -608,10 +624,8 @@ int main(void) return 0; } - /* Wait for all to be stopped */ - for (size_t i = 0U; i < ARRAY_SIZE(streams); i++) { - k_sem_take(&sem_stopped, K_FOREVER); - } + /* Wait for broadcast source to be stopped */ + k_sem_take(&sem_stopped, K_FOREVER); printk("Broadcast source stopped\n"); printk("Deleting broadcast source\n"); diff --git a/subsys/bluetooth/audio/bap_broadcast_sink.c b/subsys/bluetooth/audio/bap_broadcast_sink.c index d6b8578696805..6a6056ec0ce42 100644 --- a/subsys/bluetooth/audio/bap_broadcast_sink.c +++ b/subsys/bluetooth/audio/bap_broadcast_sink.c @@ -1,7 +1,7 @@ /* Bluetooth Audio Broadcast Sink */ /* - * Copyright (c) 2021-2023 Nordic Semiconductor ASA + * Copyright (c) 2021-2024 Nordic Semiconductor ASA * * SPDX-License-Identifier: Apache-2.0 */ @@ -402,11 +402,6 @@ static void broadcast_sink_iso_disconnected(struct bt_iso_chan *chan, if (!sys_slist_find_and_remove(&sink->streams, &stream->_node)) { LOG_DBG("Could not find and remove stream %p from sink %p", stream, sink); } - - /* Clear sink->big if not already cleared */ - if (sys_slist_is_empty(&sink->streams) && sink->big) { - broadcast_sink_clear_big(sink, reason); - } } if (ops != NULL && ops->stopped != NULL) { @@ -449,6 +444,17 @@ static struct bt_bap_broadcast_sink *broadcast_sink_get_by_pa(struct bt_le_per_a return NULL; } +static struct bt_bap_broadcast_sink *broadcast_sink_get_by_big(const struct bt_iso_big *big) +{ + for (size_t i = 0U; i < ARRAY_SIZE(broadcast_sinks); i++) { + if (broadcast_sinks[i].big == big) { + return &broadcast_sinks[i]; + } + } + + return NULL; +} + static void broadcast_sink_add_src(struct bt_bap_broadcast_sink *sink) { struct bt_bap_scan_delegator_add_src_param add_src_param; @@ -966,13 +972,72 @@ static uint16_t interval_to_sync_timeout(uint16_t interval) return (uint16_t)timeout; } +static void big_started_cb(struct bt_iso_big *big) +{ + struct bt_bap_broadcast_sink *sink = broadcast_sink_get_by_big(big); + struct bt_bap_broadcast_sink_cb *listener; + + if (sink == NULL) { + /* Not one of ours */ + return; + } + + SYS_SLIST_FOR_EACH_CONTAINER(&sink_cbs, listener, _node) { + if (listener->started != NULL) { + listener->started(sink); + } + } +} + +static void big_stopped_cb(struct bt_iso_big *big, uint8_t reason) +{ + struct bt_bap_broadcast_sink *sink = broadcast_sink_get_by_big(big); + struct bt_bap_broadcast_sink_cb *listener; + + if (sink == NULL) { + /* Not one of ours */ + return; + } + + broadcast_sink_clear_big(sink, reason); + + SYS_SLIST_FOR_EACH_CONTAINER(&sink_cbs, listener, _node) { + if (listener->stopped != NULL) { + listener->stopped(sink, reason); + } + } +} + int bt_bap_broadcast_sink_register_cb(struct bt_bap_broadcast_sink_cb *cb) { + static bool iso_big_cb_registered; + CHECKIF(cb == NULL) { LOG_DBG("cb is NULL"); + return -EINVAL; } + if (sys_slist_find(&sink_cbs, &cb->_node, NULL)) { + LOG_DBG("cb %p is already registered", cb); + + return -EEXIST; + } + + if (!iso_big_cb_registered) { + static struct bt_iso_big_cb big_cb = { + .started = big_started_cb, + .stopped = big_stopped_cb, + }; + const int err = bt_iso_big_register_cb(&big_cb); + + if (err != 0) { + __ASSERT(false, "Failed to register ISO BIG callbacks: %d", err); + } + + iso_big_cb_registered = true; + } + sys_slist_append(&sink_cbs, &cb->_node); return 0; @@ -1314,9 +1379,6 @@ int bt_bap_broadcast_sink_stop(struct bt_bap_broadcast_sink *sink) return err; } - broadcast_sink_clear_big(sink, BT_HCI_ERR_LOCALHOST_TERM_CONN); - /* Channel states will be updated in the broadcast_sink_iso_disconnected function */ - return 0; } diff --git a/subsys/bluetooth/audio/bap_broadcast_source.c b/subsys/bluetooth/audio/bap_broadcast_source.c index 93186af67f0f7..132dca5580a37 100644 --- a/subsys/bluetooth/audio/bap_broadcast_source.c +++ b/subsys/bluetooth/audio/bap_broadcast_source.c @@ -1,7 +1,7 @@ /* Bluetooth Audio Broadcast Source */ /* - * Copyright (c) 2021-2023 Nordic Semiconductor ASA + * Copyright (c) 2021-2024 Nordic Semiconductor ASA * * SPDX-License-Identifier: Apache-2.0 */ @@ -25,6 +25,7 @@ #include #include #include +#include #include #include #include @@ -55,6 +56,7 @@ static struct bt_bap_broadcast_subgroup broadcast_source_subgroups[CONFIG_BT_BAP_BROADCAST_SRC_COUNT] [CONFIG_BT_BAP_BROADCAST_SRC_SUBGROUP_COUNT]; static struct bt_bap_broadcast_source broadcast_sources[CONFIG_BT_BAP_BROADCAST_SRC_COUNT]; +static sys_slist_t bap_broadcast_source_cbs = SYS_SLIST_STATIC_INIT(&bap_broadcast_source_cbs); /** * 2 octets UUID @@ -238,9 +240,9 @@ static void broadcast_source_iso_disconnected(struct bt_iso_chan *chan, uint8_t } static struct bt_iso_chan_ops broadcast_source_iso_ops = { - .sent = broadcast_source_iso_sent, - .connected = broadcast_source_iso_connected, - .disconnected = broadcast_source_iso_disconnected, + .sent = broadcast_source_iso_sent, + .connected = broadcast_source_iso_connected, + .disconnected = broadcast_source_iso_disconnected, }; bool bt_bap_ep_is_broadcast_src(const struct bt_bap_ep *ep) @@ -440,8 +442,7 @@ static bool encode_base(struct bt_bap_broadcast_source *source, struct net_buf_s */ streams_encoded = 0; SYS_SLIST_FOR_EACH_CONTAINER(&source->subgroups, subgroup, _node) { - if (!encode_base_subgroup(subgroup, - &source->stream_data[streams_encoded], + if (!encode_base_subgroup(subgroup, &source->stream_data[streams_encoded], &streams_encoded, buf)) { return false; } @@ -454,12 +455,10 @@ static void broadcast_source_cleanup(struct bt_bap_broadcast_source *source) { struct bt_bap_broadcast_subgroup *subgroup, *next_subgroup; - SYS_SLIST_FOR_EACH_CONTAINER_SAFE(&source->subgroups, subgroup, - next_subgroup, _node) { + SYS_SLIST_FOR_EACH_CONTAINER_SAFE(&source->subgroups, subgroup, next_subgroup, _node) { struct bt_bap_stream *stream, *next_stream; - SYS_SLIST_FOR_EACH_CONTAINER_SAFE(&subgroup->streams, stream, - next_stream, _node) { + SYS_SLIST_FOR_EACH_CONTAINER_SAFE(&subgroup->streams, stream, next_stream, _node) { bt_bap_iso_unbind_ep(stream->ep->iso, stream->ep); stream->ep->stream = NULL; stream->ep = NULL; @@ -467,8 +466,7 @@ static void broadcast_source_cleanup(struct bt_bap_broadcast_source *source) stream->qos = NULL; stream->group = NULL; - sys_slist_remove(&subgroup->streams, NULL, - &stream->_node); + sys_slist_remove(&subgroup->streams, NULL, &stream->_node); } sys_slist_remove(&source->subgroups, NULL, &subgroup->_node); } @@ -777,8 +775,7 @@ int bt_bap_broadcast_source_create(struct bt_bap_broadcast_source_param *param, bis_count++; } - err = broadcast_source_setup_stream(index, stream, - codec_cfg, qos, source); + err = broadcast_source_setup_stream(index, stream, codec_cfg, qos, source); if (err != 0) { LOG_DBG("Failed to setup streams[%zu]: %d", i, err); broadcast_source_cleanup(source); @@ -1039,7 +1036,7 @@ int bt_bap_broadcast_source_update_metadata(struct bt_bap_broadcast_source *sour int bt_bap_broadcast_source_start(struct bt_bap_broadcast_source *source, struct bt_le_ext_adv *adv) { struct bt_iso_chan *bis[BROADCAST_STREAM_CNT]; - struct bt_iso_big_create_param param = { 0 }; + struct bt_iso_big_create_param param = {0}; struct bt_bap_broadcast_subgroup *subgroup; enum bt_bap_ep_state broadcast_state; struct bt_bap_stream *stream; @@ -1078,8 +1075,7 @@ int bt_bap_broadcast_source_start(struct bt_bap_broadcast_source *source, struct param.latency = source->qos->latency; param.encryption = source->encryption; if (param.encryption) { - (void)memcpy(param.bcode, source->broadcast_code, - sizeof(param.bcode)); + (void)memcpy(param.bcode, source->broadcast_code, sizeof(param.bcode)); } #if defined(CONFIG_BT_ISO_TEST_PARAMS) param.irc = source->irc; @@ -1125,14 +1121,12 @@ int bt_bap_broadcast_source_stop(struct bt_bap_broadcast_source *source) return -EALREADY; } - err = bt_iso_big_terminate(source->big); + err = bt_iso_big_terminate(source->big); if (err) { LOG_DBG("Failed to terminate BIG (err %d)", err); return err; } - source->big = NULL; - return 0; } @@ -1188,3 +1182,102 @@ int bt_bap_broadcast_source_get_base(struct bt_bap_broadcast_source *source, return 0; } + +static struct bt_bap_broadcast_source *get_broadcast_source_by_big(const struct bt_iso_big *big) +{ + for (size_t i = 0U; i < ARRAY_SIZE(broadcast_sources); i++) { + if (broadcast_sources[i].big == big) { + return &broadcast_sources[i]; + } + } + + return NULL; +} + +static void big_started_cb(struct bt_iso_big *big) +{ + struct bt_bap_broadcast_source *source = get_broadcast_source_by_big(big); + struct bt_bap_broadcast_source_cb *listener; + + if (source == NULL) { + /* Not one of ours */ + return; + } + + SYS_SLIST_FOR_EACH_CONTAINER(&bap_broadcast_source_cbs, listener, _node) { + if (listener->started != NULL) { + listener->started(source); + } + } +} + +static void big_stopped_cb(struct bt_iso_big *big, uint8_t reason) +{ + struct bt_bap_broadcast_source *source = get_broadcast_source_by_big(big); + struct bt_bap_broadcast_source_cb *listener; + + if (source == NULL) { + /* Not one of ours */ + return; + } + + source->big = NULL; + + SYS_SLIST_FOR_EACH_CONTAINER(&bap_broadcast_source_cbs, listener, _node) { + if (listener->stopped != NULL) { + listener->stopped(source, reason); + } + } +} + +int bt_bap_broadcast_source_register_cb(struct bt_bap_broadcast_source_cb *cb) +{ + static bool iso_big_cb_registered; + + CHECKIF(cb == NULL) { + LOG_DBG("cb is NULL"); + + return -EINVAL; + } + + if (sys_slist_find(&bap_broadcast_source_cbs, &cb->_node, NULL)) { + LOG_DBG("cb %p is already registered", cb); + + return -EEXIST; + } + + if (!iso_big_cb_registered) { + static struct bt_iso_big_cb big_cb = { + .started = big_started_cb, + .stopped = big_stopped_cb, + }; + const int err = bt_iso_big_register_cb(&big_cb); + + if (err != 0) { + __ASSERT(false, "Failed to register ISO BIG callbacks: %d", err); + } + + iso_big_cb_registered = true; + } + + sys_slist_append(&bap_broadcast_source_cbs, &cb->_node); + + return 0; +} + +int bt_bap_broadcast_source_unregister_cb(struct bt_bap_broadcast_source_cb *cb) +{ + CHECKIF(cb == NULL) { + LOG_DBG("cb is NULL"); + + return -EINVAL; + } + + if (!sys_slist_find_and_remove(&bap_broadcast_source_cbs, &cb->_node)) { + LOG_DBG("cb %p is not registered", cb); + + return -ENOENT; + } + + return 0; +} diff --git a/subsys/bluetooth/audio/cap_initiator.c b/subsys/bluetooth/audio/cap_initiator.c index d6485e5f094a8..b1f8bedb734ff 100644 --- a/subsys/bluetooth/audio/cap_initiator.c +++ b/subsys/bluetooth/audio/cap_initiator.c @@ -257,9 +257,54 @@ int bt_cap_initiator_broadcast_audio_create( &(*broadcast_source)->bap_broadcast); } +static struct bt_cap_broadcast_source *get_cap_broadcast_source_by_bap_broadcast_source( + const struct bt_bap_broadcast_source *bap_broadcast_source) +{ + for (size_t i = 0U; i < ARRAY_SIZE(broadcast_sources); i++) { + if (broadcast_sources[i].bap_broadcast == bap_broadcast_source) { + return &broadcast_sources[i]; + } + } + + return NULL; +} + +static void broadcast_source_started_cb(struct bt_bap_broadcast_source *bap_broadcast_source) +{ + if (cap_cb && cap_cb->broadcast_started) { + struct bt_cap_broadcast_source *source = + get_cap_broadcast_source_by_bap_broadcast_source(bap_broadcast_source); + + if (source == NULL) { + /* Not one of ours */ + return; + } + + cap_cb->broadcast_started(source); + } +} + +static void broadcast_source_stopped_cb(struct bt_bap_broadcast_source *bap_broadcast_source, + uint8_t reason) +{ + if (cap_cb && cap_cb->broadcast_stopped) { + struct bt_cap_broadcast_source *source = + get_cap_broadcast_source_by_bap_broadcast_source(bap_broadcast_source); + + if (source == NULL) { + /* Not one of ours */ + return; + } + + cap_cb->broadcast_stopped(source, reason); + } +} + int bt_cap_initiator_broadcast_audio_start(struct bt_cap_broadcast_source *broadcast_source, struct bt_le_ext_adv *adv) { + static bool broadcast_source_cbs_registered; + CHECKIF(adv == NULL) { LOG_DBG("adv is NULL"); return -EINVAL; @@ -270,6 +315,21 @@ int bt_cap_initiator_broadcast_audio_start(struct bt_cap_broadcast_source *broad return -EINVAL; } + if (!broadcast_source_cbs_registered) { + static struct bt_bap_broadcast_source_cb broadcast_source_cb = { + .started = broadcast_source_started_cb, + .stopped = broadcast_source_stopped_cb, + }; + const int err = bt_bap_broadcast_source_register_cb(&broadcast_source_cb); + + if (err != 0) { + __ASSERT(false, "Failed to register BAP broadcast source callbacks: %d", + err); + } + + broadcast_source_cbs_registered = true; + } + return bt_bap_broadcast_source_start(broadcast_source->bap_broadcast, adv); } diff --git a/subsys/bluetooth/host/iso.c b/subsys/bluetooth/host/iso.c index 3b363d13f4c41..f44acbcc522d3 100644 --- a/subsys/bluetooth/host/iso.c +++ b/subsys/bluetooth/host/iso.c @@ -2525,6 +2525,8 @@ int bt_iso_chan_connect(const struct bt_iso_connect_param *param, size_t count) #endif /* CONFIG_BT_ISO_UNICAST */ #if defined(CONFIG_BT_ISO_BROADCAST) +static sys_slist_t iso_big_cbs = SYS_SLIST_STATIC_INIT(&iso_big_cbs); + static struct bt_iso_big *lookup_big_by_handle(uint8_t big_handle) { return &bigs[big_handle]; @@ -2587,6 +2589,16 @@ static void big_disconnect(struct bt_iso_big *big, uint8_t reason) bt_iso_chan_disconnected(bis, reason); } + + if (!sys_slist_is_empty(&iso_big_cbs)) { + struct bt_iso_big_cb *listener; + + SYS_SLIST_FOR_EACH_CONTAINER(&iso_big_cbs, listener, _node) { + if (listener->stopped != NULL) { + listener->stopped(big, reason); + } + } + } } static int big_init_bis(struct bt_iso_big *big, struct bt_iso_chan **bis_channels, uint8_t num_bis, @@ -2617,6 +2629,25 @@ static int big_init_bis(struct bt_iso_big *big, struct bt_iso_chan **bis_channel return 0; } +int bt_iso_big_register_cb(struct bt_iso_big_cb *cb) +{ + CHECKIF(cb == NULL) { + LOG_DBG("cb is NULL"); + + return -EINVAL; + } + + if (sys_slist_find(&iso_big_cbs, &cb->_node, NULL)) { + LOG_DBG("cb %p is already registered", cb); + + return -EEXIST; + } + + sys_slist_append(&iso_big_cbs, &cb->_node); + + return 0; +} + #if defined(CONFIG_BT_ISO_BROADCASTER) static int hci_le_create_big(struct bt_le_ext_adv *padv, struct bt_iso_big *big, struct bt_iso_big_create_param *param) @@ -3006,6 +3037,16 @@ void hci_le_big_complete(struct net_buf *buf) store_bis_broadcaster_info(evt, &iso_conn->iso.info); bt_conn_set_state(iso_conn, BT_CONN_CONNECTED); } + + if (!sys_slist_is_empty(&iso_big_cbs)) { + struct bt_iso_big_cb *listener; + + SYS_SLIST_FOR_EACH_CONTAINER(&iso_big_cbs, listener, _node) { + if (listener->started != NULL) { + listener->started(big); + } + } + } } void hci_le_big_terminate(struct net_buf *buf) @@ -3186,6 +3227,16 @@ void hci_le_big_sync_established(struct net_buf *buf) store_bis_sync_receiver_info(evt, &iso_conn->iso.info); bt_conn_set_state(iso_conn, BT_CONN_CONNECTED); } + + if (!sys_slist_is_empty(&iso_big_cbs)) { + struct bt_iso_big_cb *listener; + + SYS_SLIST_FOR_EACH_CONTAINER(&iso_big_cbs, listener, _node) { + if (listener->started != NULL) { + listener->started(big); + } + } + } } void hci_le_big_sync_lost(struct net_buf *buf) diff --git a/tests/bluetooth/audio/bap_broadcast_source/CMakeLists.txt b/tests/bluetooth/audio/bap_broadcast_source/CMakeLists.txt index ef097b64e7a95..d79b0f147a697 100644 --- a/tests/bluetooth/audio/bap_broadcast_source/CMakeLists.txt +++ b/tests/bluetooth/audio/bap_broadcast_source/CMakeLists.txt @@ -15,4 +15,5 @@ target_include_directories(testbinary PRIVATE include) target_sources(testbinary PRIVATE src/main.c + src/test_callback_register.c ) diff --git a/tests/bluetooth/audio/bap_broadcast_source/src/main.c b/tests/bluetooth/audio/bap_broadcast_source/src/main.c index 82e6406919f1b..38a2b0a6e5061 100644 --- a/tests/bluetooth/audio/bap_broadcast_source/src/main.c +++ b/tests/bluetooth/audio/bap_broadcast_source/src/main.c @@ -1,28 +1,41 @@ /* main.c - Application main entry point */ /* - * Copyright (c) 2023 Nordic Semiconductor ASA + * Copyright (c) 2023-2024 Nordic Semiconductor ASA * * SPDX-License-Identifier: Apache-2.0 */ +#include +#include #include #include #include #include +#include +#include +#include #include #include #include +#include +#include #include -#include "bluetooth.h" +#include "bap_broadcast_source.h" +#include "bap_stream.h" #include "bap_stream_expects.h" +#include "bluetooth.h" +#include "expects_util.h" +#include "ztest_assert.h" +#include "ztest_test.h" DEFINE_FFF_GLOBALS; static void mock_init_rule_before(const struct ztest_unit_test *test, void *fixture) { + mock_bap_broadcast_source_init(); mock_bap_stream_init(); } @@ -136,8 +149,13 @@ static void *bap_broadcast_source_test_suite_setup(void) static void bap_broadcast_source_test_suite_before(void *f) { + int err; + memset(f, 0, sizeof(struct bap_broadcast_source_test_suite_fixture)); bap_broadcast_source_test_suite_fixture_init(f); + + err = bt_bap_broadcast_source_register_cb(&mock_bap_broadcast_source_cb); + zassert_equal(0, err, "Unexpected return value %d", err); } static void bap_broadcast_source_test_suite_after(void *f) @@ -164,6 +182,8 @@ static void bap_broadcast_source_test_suite_after(void *f) free(param->params); free(param->qos); free(param); + + bt_bap_broadcast_source_unregister_cb(&mock_bap_broadcast_source_cb); } static void bap_broadcast_source_test_suite_teardown(void *f) @@ -224,6 +244,8 @@ ZTEST_F(bap_broadcast_source_test_suite, test_broadcast_source_create_start_send mock_bap_stream_connected_cb_fake.call_count); zexpect_call_count("bt_bap_stream_ops.started", fixture->stream_cnt, mock_bap_stream_started_cb_fake.call_count); + zexpect_call_count("bt_bap_broadcast_source_cb.started", 1, + mock_bap_broadcast_source_started_cb_fake.call_count); for (size_t i = 0U; i < create_param->params_count; i++) { for (size_t j = 0U; j < create_param->params[i].params_count; j++) { @@ -260,6 +282,8 @@ ZTEST_F(bap_broadcast_source_test_suite, test_broadcast_source_create_start_send mock_bap_stream_disconnected_cb_fake.call_count); zexpect_call_count("bt_bap_stream_ops.stopped", fixture->stream_cnt, mock_bap_stream_stopped_cb_fake.call_count); + zexpect_call_count("bt_bap_broadcast_source_cb.stopped", 1, + mock_bap_broadcast_source_stopped_cb_fake.call_count); err = bt_bap_broadcast_source_delete(fixture->source); zassert_equal(0, err, "Unable to delete broadcast source: err %d", err); diff --git a/tests/bluetooth/audio/bap_broadcast_source/src/test_callback_register.c b/tests/bluetooth/audio/bap_broadcast_source/src/test_callback_register.c new file mode 100644 index 0000000000000..0e221a685512b --- /dev/null +++ b/tests/bluetooth/audio/bap_broadcast_source/src/test_callback_register.c @@ -0,0 +1,98 @@ +/* test_callback_register.c - Test bt_bap_broadcast_source_register and unregister */ + +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include + +#include +#include + +#include "bap_broadcast_source.h" +#include "ztest_assert.h" +#include "ztest_test.h" + +#define FFF_GLOBALS + +static void mock_init_rule_before(const struct ztest_unit_test *test, void *fixture) +{ + mock_bap_broadcast_source_init(); +} + +ZTEST_RULE(mock_rule, mock_init_rule_before, NULL); + +static void bap_broadcast_source_test_cb_register_suite_after(void *f) +{ + bt_bap_broadcast_source_unregister_cb(&mock_bap_broadcast_source_cb); +} + +ZTEST_SUITE(bap_broadcast_source_test_cb_register_suite, NULL, NULL, NULL, + bap_broadcast_source_test_cb_register_suite_after, NULL); + +static ZTEST(bap_broadcast_source_test_cb_register_suite, test_broadcast_source_register_cb) +{ + int err; + + err = bt_bap_broadcast_source_register_cb(&mock_bap_broadcast_source_cb); + zassert_equal(0, err, "Unexpected return value %d", err); +} + +static ZTEST(bap_broadcast_source_test_cb_register_suite, + test_broadcast_source_register_cb_inval_param_null) +{ + int err; + + err = bt_bap_broadcast_source_register_cb(NULL); + zassert_equal(err, -EINVAL, "Unexpected return value %d", err); +} + +static ZTEST(bap_broadcast_source_test_cb_register_suite, + test_broadcast_source_register_cb_inval_double_register) +{ + int err; + + err = bt_bap_broadcast_source_register_cb(&mock_bap_broadcast_source_cb); + zassert_equal(err, 0, "Unexpected return value %d", err); + + err = bt_bap_broadcast_source_register_cb(&mock_bap_broadcast_source_cb); + zassert_equal(err, -EEXIST, "Unexpected return value %d", err); +} + +static ZTEST(bap_broadcast_source_test_cb_register_suite, test_broadcast_source_unregister_cb) +{ + int err; + + err = bt_bap_broadcast_source_register_cb(&mock_bap_broadcast_source_cb); + zassert_equal(err, 0, "Unexpected return value %d", err); + + err = bt_bap_broadcast_source_unregister_cb(&mock_bap_broadcast_source_cb); + zassert_equal(err, 0, "Unexpected return value %d", err); +} + +static ZTEST(bap_broadcast_source_test_cb_register_suite, + test_broadcast_source_unregister_cb_inval_param_null) +{ + int err; + + err = bt_bap_broadcast_source_unregister_cb(NULL); + zassert_equal(err, -EINVAL, "Unexpected return value %d", err); +} + +static ZTEST(bap_broadcast_source_test_cb_register_suite, + test_broadcast_source_unregister_cb_inval_double_unregister) +{ + int err; + + err = bt_bap_broadcast_source_register_cb(&mock_bap_broadcast_source_cb); + zassert_equal(err, 0, "Unexpected return value %d", err); + + err = bt_bap_broadcast_source_unregister_cb(&mock_bap_broadcast_source_cb); + zassert_equal(err, 0, "Unexpected return value %d", err); + + err = bt_bap_broadcast_source_unregister_cb(&mock_bap_broadcast_source_cb); + zassert_equal(err, -ENOENT, "Unexpected return value %d", err); +} diff --git a/tests/bluetooth/audio/bap_broadcast_source/uut/CMakeLists.txt b/tests/bluetooth/audio/bap_broadcast_source/uut/CMakeLists.txt index 879a50720b3b7..76dd64501cf8c 100644 --- a/tests/bluetooth/audio/bap_broadcast_source/uut/CMakeLists.txt +++ b/tests/bluetooth/audio/bap_broadcast_source/uut/CMakeLists.txt @@ -14,6 +14,7 @@ add_library(uut STATIC ${ZEPHYR_BASE}/subsys/bluetooth/audio/codec.c ${ZEPHYR_BASE}/subsys/logging/log_minimal.c ${ZEPHYR_BASE}/lib/net_buf/buf_simple.c + bap_broadcast_source.c ) add_subdirectory(${ZEPHYR_BASE}/tests/bluetooth/audio/mocks mocks) diff --git a/tests/bluetooth/audio/bap_broadcast_source/uut/bap_broadcast_source.c b/tests/bluetooth/audio/bap_broadcast_source/uut/bap_broadcast_source.c new file mode 100644 index 0000000000000..e0e2944836b1d --- /dev/null +++ b/tests/bluetooth/audio/bap_broadcast_source/uut/bap_broadcast_source.c @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include + +#include + +#include "bap_broadcast_source.h" +#include "zephyr/fff.h" + +/* List of fakes used by this unit tester */ +#define FFF_FAKES_LIST(FAKE) \ + FAKE(mock_bap_broadcast_source_started_cb) \ + FAKE(mock_bap_broadcast_source_stopped_cb) + +DEFINE_FAKE_VOID_FUNC(mock_bap_broadcast_source_started_cb, struct bt_bap_broadcast_source *); + +DEFINE_FAKE_VOID_FUNC(mock_bap_broadcast_source_stopped_cb, struct bt_bap_broadcast_source *, + uint8_t); + +struct bt_bap_broadcast_source_cb mock_bap_broadcast_source_cb = { + .started = mock_bap_broadcast_source_started_cb, + .stopped = mock_bap_broadcast_source_stopped_cb, +}; + +void mock_bap_broadcast_source_init(void) +{ + FFF_FAKES_LIST(RESET_FAKE); +} diff --git a/tests/bluetooth/audio/cap_initiator/include/cap_initiator.h b/tests/bluetooth/audio/cap_initiator/include/cap_initiator.h index 9b69f7d837f7e..57104e7ef03e0 100644 --- a/tests/bluetooth/audio/cap_initiator/include/cap_initiator.h +++ b/tests/bluetooth/audio/cap_initiator/include/cap_initiator.h @@ -23,5 +23,8 @@ DECLARE_FAKE_VOID_FUNC(mock_cap_initiator_unicast_discovery_complete_cb, struct DECLARE_FAKE_VOID_FUNC(mock_cap_initiator_unicast_start_complete_cb, int, struct bt_conn *); DECLARE_FAKE_VOID_FUNC(mock_cap_initiator_unicast_update_complete_cb, int, struct bt_conn *); DECLARE_FAKE_VOID_FUNC(mock_cap_initiator_unicast_stop_complete_cb, int, struct bt_conn *); +DECLARE_FAKE_VOID_FUNC(mock_cap_initiator_broadcast_started_cb, struct bt_cap_broadcast_source *); +DECLARE_FAKE_VOID_FUNC(mock_cap_initiator_broadcast_stopped_cb, struct bt_cap_broadcast_source *, + uint8_t); #endif /* MOCKS_CAP_INITIATOR_H_ */ diff --git a/tests/bluetooth/audio/cap_initiator/uut/cap_initiator.c b/tests/bluetooth/audio/cap_initiator/uut/cap_initiator.c index d094cc3c5b944..78dea5f4685c7 100644 --- a/tests/bluetooth/audio/cap_initiator/uut/cap_initiator.c +++ b/tests/bluetooth/audio/cap_initiator/uut/cap_initiator.c @@ -25,6 +25,9 @@ DEFINE_FAKE_VOID_FUNC(mock_cap_initiator_unicast_discovery_complete_cb, struct b DEFINE_FAKE_VOID_FUNC(mock_cap_initiator_unicast_start_complete_cb, int, struct bt_conn *); DEFINE_FAKE_VOID_FUNC(mock_cap_initiator_unicast_update_complete_cb, int, struct bt_conn *); DEFINE_FAKE_VOID_FUNC(mock_cap_initiator_unicast_stop_complete_cb, int, struct bt_conn *); +DEFINE_FAKE_VOID_FUNC(mock_cap_initiator_broadcast_started_cb, struct bt_cap_broadcast_source *); +DEFINE_FAKE_VOID_FUNC(mock_cap_initiator_broadcast_stopped_cb, struct bt_cap_broadcast_source *, + uint8_t); const struct bt_cap_initiator_cb mock_cap_initiator_cb = { #if defined(CONFIG_BT_BAP_UNICAST_CLIENT) @@ -33,6 +36,10 @@ const struct bt_cap_initiator_cb mock_cap_initiator_cb = { .unicast_update_complete = mock_cap_initiator_unicast_update_complete_cb, .unicast_stop_complete = mock_cap_initiator_unicast_stop_complete_cb, #endif /* CONFIG_BT_BAP_UNICAST_CLIENT */ +#if defined(CONFIG_BT_BAP_BROADCAST_SOURCE) + .broadcast_started = mock_cap_initiator_broadcast_started_cb, + .broadcast_stopped = mock_cap_initiator_broadcast_stopped_cb, +#endif /* CONFIG_BT_BAP_BROADCAST_SOURCE */ }; void mock_cap_initiator_init(void) diff --git a/tests/bluetooth/audio/mocks/include/bap_broadcast_source.h b/tests/bluetooth/audio/mocks/include/bap_broadcast_source.h new file mode 100644 index 0000000000000..d6b75e7c51aa4 --- /dev/null +++ b/tests/bluetooth/audio/mocks/include/bap_broadcast_source.h @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef MOCKS_BAP_BROADCAST_SOURCE_H_ +#define MOCKS_BAP_BROADCAST_SOURCE_H_ + +#include + +#include +#include + +extern struct bt_bap_broadcast_source_cb mock_bap_broadcast_source_cb; + +void mock_bap_broadcast_source_init(void); + +DECLARE_FAKE_VOID_FUNC(mock_bap_broadcast_source_started_cb, struct bt_bap_broadcast_source *); +DECLARE_FAKE_VOID_FUNC(mock_bap_broadcast_source_stopped_cb, struct bt_bap_broadcast_source *, + uint8_t); + +#endif /* MOCKS_BAP_BROADCAST_SOURCE_H_ */ diff --git a/tests/bluetooth/audio/mocks/src/iso.c b/tests/bluetooth/audio/mocks/src/iso.c index 7271a66242eaa..bb344f81a773d 100644 --- a/tests/bluetooth/audio/mocks/src/iso.c +++ b/tests/bluetooth/audio/mocks/src/iso.c @@ -1,10 +1,13 @@ /* * Copyright (c) 2023 Codecoup + * Copyright (c) 2024 Nordic Semiconductor ASA * * SPDX-License-Identifier: Apache-2.0 */ +#include #include + #include #include "conn.h" @@ -123,6 +126,19 @@ int mock_bt_iso_disconnected(struct bt_iso_chan *chan, uint8_t err) } #if defined(CONFIG_BT_BAP_BROADCAST_SOURCE) +static struct bt_iso_big_cb *iso_cb; + +int bt_iso_big_register_cb(struct bt_iso_big_cb *cb) +{ + if (cb == NULL) { + return -EINVAL; + } + + iso_cb = cb; + + return 0; +} + int bt_iso_big_create(struct bt_le_ext_adv *padv, struct bt_iso_big_create_param *param, struct bt_iso_big **out_big) { @@ -153,6 +169,10 @@ int bt_iso_big_create(struct bt_le_ext_adv *padv, struct bt_iso_big_create_param *out_big = big; + if (iso_cb != NULL && iso_cb->started != NULL) { + iso_cb->started(big); + } + return 0; } @@ -169,6 +189,10 @@ int bt_iso_big_terminate(struct bt_iso_big *big) mock_bt_iso_disconnected(bis, BT_HCI_ERR_LOCALHOST_TERM_CONN); } + if (iso_cb != NULL && iso_cb->stopped != NULL) { + iso_cb->stopped(big, BT_HCI_ERR_LOCALHOST_TERM_CONN); + } + free(big); return 0; diff --git a/tests/bsim/bluetooth/audio/src/bap_broadcast_sink_test.c b/tests/bsim/bluetooth/audio/src/bap_broadcast_sink_test.c index 52df6b4337381..6fadeb3f0106e 100644 --- a/tests/bsim/bluetooth/audio/src/bap_broadcast_sink_test.c +++ b/tests/bsim/bluetooth/audio/src/bap_broadcast_sink_test.c @@ -48,6 +48,7 @@ CREATE_FLAG(flag_pa_sync_lost); CREATE_FLAG(flag_pa_request); CREATE_FLAG(flag_bis_sync_requested); CREATE_FLAG(flag_big_sync_mic_failure); +CREATE_FLAG(flag_sink_started); static struct bt_bap_broadcast_sink *g_sink; static struct bt_le_scan_recv_info broadcaster_info; @@ -80,8 +81,8 @@ static const struct bt_audio_codec_cap codec_cap = BT_AUDIO_CODEC_CAP_LC3( SUPPORTED_MIN_OCTETS_PER_FRAME, SUPPORTED_MAX_OCTETS_PER_FRAME, SUPPORTED_MAX_FRAMES_PER_SDU, SUPPORTED_CONTEXTS); -static K_SEM_DEFINE(sem_started, 0U, ARRAY_SIZE(streams)); -static K_SEM_DEFINE(sem_stopped, 0U, ARRAY_SIZE(streams)); +static K_SEM_DEFINE(sem_stream_started, 0U, ARRAY_SIZE(streams)); +static K_SEM_DEFINE(sem_stream_stopped, 0U, ARRAY_SIZE(streams)); /* Create a mask for the maximum BIS we can sync to using the number of streams * we have. We add an additional 1 since the bis indexes start from 1 and not @@ -257,9 +258,27 @@ static void syncable_cb(struct bt_bap_broadcast_sink *sink, const struct bt_iso_ SET_FLAG(flag_syncable); } +static void broadcast_sink_started_cb(struct bt_bap_broadcast_sink *sink) +{ + printk("Broadcast sink %p started\n", sink); + SET_FLAG(flag_sink_started); +} + +static void broadcast_sink_stopped_cb(struct bt_bap_broadcast_sink *sink, uint8_t reason) +{ + printk("Broadcast sink %p stopped with reason 0x%02X\n", sink, reason); + UNSET_FLAG(flag_sink_started); + + if (reason == BT_HCI_ERR_TERM_DUE_TO_MIC_FAIL) { + SET_FLAG(flag_big_sync_mic_failure); + } +} + static struct bt_bap_broadcast_sink_cb broadcast_sink_cbs = { .base_recv = base_recv_cb, .syncable = syncable_cb, + .started = broadcast_sink_started_cb, + .stopped = broadcast_sink_stopped_cb, }; static bool scan_check_and_sync_broadcast(struct bt_data *data, void *user_data) @@ -526,7 +545,7 @@ static void validate_stream_codec_cfg(const struct bt_bap_stream *stream) } } -static void started_cb(struct bt_bap_stream *stream) +static void stream_started_cb(struct bt_bap_stream *stream) { struct audio_test_stream *test_stream = audio_test_stream_from_bap_stream(stream); struct bt_bap_ep_info info; @@ -567,24 +586,20 @@ static void started_cb(struct bt_bap_stream *stream) } printk("Stream %p started\n", stream); - k_sem_give(&sem_started); + k_sem_give(&sem_stream_started); validate_stream_codec_cfg(stream); } -static void stopped_cb(struct bt_bap_stream *stream, uint8_t reason) +static void stream_stopped_cb(struct bt_bap_stream *stream, uint8_t reason) { printk("Stream %p stopped with reason 0x%02X\n", stream, reason); - k_sem_give(&sem_stopped); - - if (reason == BT_HCI_ERR_TERM_DUE_TO_MIC_FAIL) { - SET_FLAG(flag_big_sync_mic_failure); - } + k_sem_give(&sem_stream_stopped); } static struct bt_bap_stream_ops stream_ops = { - .started = started_cb, - .stopped = stopped_cb, + .started = stream_started_cb, + .stopped = stream_stopped_cb, .recv = bap_stream_rx_recv_cb, }; @@ -707,6 +722,8 @@ static void test_broadcast_sink_create(void) FAIL("Unable to create the sink: %d\n", err); return; } + + printk("Created broadcast sink %p\n", g_sink); } static void test_broadcast_sink_create_inval(void) @@ -736,7 +753,7 @@ static void test_broadcast_sync(const uint8_t broadcast_code[BT_ISO_BROADCAST_CO { int err; - printk("Syncing the sink\n"); + printk("Syncing sink %p\n", g_sink); err = bt_bap_broadcast_sink_sync(g_sink, bis_index_bitfield, streams, broadcast_code); if (err != 0) { FAIL("Unable to sync the sink: %d\n", err); @@ -808,16 +825,20 @@ static void test_broadcast_stop(void) { int err; + printk("Stopping broadcast sink %p\n", g_sink); + err = bt_bap_broadcast_sink_stop(g_sink); if (err != 0) { FAIL("Unable to stop sink: %d", err); return; } - printk("Waiting for streams to be stopped\n"); + printk("Waiting for %zu streams to be stopped\n", ARRAY_SIZE(streams)); for (size_t i = 0U; i < ARRAY_SIZE(streams); i++) { - k_sem_take(&sem_stopped, K_FOREVER); + k_sem_take(&sem_stream_stopped, K_FOREVER); } + + WAIT_FOR_UNSET_FLAG(flag_sink_started); } static void test_broadcast_stop_inval(void) @@ -914,10 +935,12 @@ static void test_common(void) test_broadcast_sync_inval(); test_broadcast_sync(NULL); + WAIT_FOR_FLAG(flag_sink_started); + /* Wait for all to be started */ - printk("Waiting for streams to be started\n"); + printk("Waiting for %zu streams to be started\n", ARRAY_SIZE(streams)); for (size_t i = 0U; i < ARRAY_SIZE(streams); i++) { - k_sem_take(&sem_started, K_FOREVER); + k_sem_take(&sem_stream_started, K_FOREVER); } printk("Waiting for data\n"); @@ -944,10 +967,11 @@ static void test_main(void) printk("Waiting for PA disconnected\n"); WAIT_FOR_FLAG(flag_pa_sync_lost); - printk("Waiting for streams to be stopped\n"); + printk("Waiting for %zu streams to be stopped\n", ARRAY_SIZE(streams)); for (size_t i = 0U; i < ARRAY_SIZE(streams); i++) { - k_sem_take(&sem_stopped, K_FOREVER); + k_sem_take(&sem_stream_stopped, K_FOREVER); } + WAIT_FOR_UNSET_FLAG(flag_sink_started); PASS("Broadcast sink passed\n"); } @@ -962,10 +986,12 @@ static void test_sink_disconnect(void) /* Retry sync*/ test_broadcast_sync(NULL); + WAIT_FOR_FLAG(flag_sink_started); + /* Wait for all to be started */ - printk("Waiting for streams to be started\n"); + printk("Waiting for %zu streams to be started\n", ARRAY_SIZE(streams)); for (size_t i = 0U; i < ARRAY_SIZE(streams); i++) { - k_sem_take(&sem_started, K_FOREVER); + k_sem_take(&sem_stream_started, K_FOREVER); } test_broadcast_stop(); @@ -1001,10 +1027,12 @@ static void test_sink_encrypted(void) test_broadcast_sync(BROADCAST_CODE); + WAIT_FOR_FLAG(flag_sink_started); + /* Wait for all to be started */ - printk("Waiting for streams to be started\n"); + printk("Waiting for %zu streams to be started\n", ARRAY_SIZE(streams)); for (size_t i = 0U; i < ARRAY_SIZE(streams); i++) { - k_sem_take(&sem_started, K_FOREVER); + k_sem_take(&sem_stream_started, K_FOREVER); } printk("Waiting for data\n"); @@ -1021,9 +1049,9 @@ static void test_sink_encrypted(void) printk("Waiting for PA disconnected\n"); WAIT_FOR_FLAG(flag_pa_sync_lost); - printk("Waiting for streams to be stopped\n"); + printk("Waiting for %zu streams to be stopped\n", ARRAY_SIZE(streams)); for (size_t i = 0U; i < ARRAY_SIZE(streams); i++) { - k_sem_take(&sem_stopped, K_FOREVER); + k_sem_take(&sem_stream_stopped, K_FOREVER); } PASS("Broadcast sink encrypted passed\n"); @@ -1090,10 +1118,12 @@ static void broadcast_sink_with_assistant(void) WAIT_FOR_FLAG(flag_bis_sync_requested); test_broadcast_sync(NULL); + WAIT_FOR_FLAG(flag_sink_started); + /* Wait for all to be started */ - printk("Waiting for streams to be started\n"); + printk("Waiting for %zu streams to be started\n", ARRAY_SIZE(streams)); for (size_t i = 0U; i < ARRAY_SIZE(streams); i++) { - k_sem_take(&sem_started, K_FOREVER); + k_sem_take(&sem_stream_started, K_FOREVER); } printk("Waiting for data\n"); diff --git a/tests/bsim/bluetooth/audio/src/bap_broadcast_source_test.c b/tests/bsim/bluetooth/audio/src/bap_broadcast_source_test.c index de0d776477982..0006c54508be3 100644 --- a/tests/bsim/bluetooth/audio/src/bap_broadcast_source_test.c +++ b/tests/bsim/bluetooth/audio/src/bap_broadcast_source_test.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021-2023 Nordic Semiconductor ASA + * Copyright (c) 2021-2024 Nordic Semiconductor ASA * * SPDX-License-Identifier: Apache-2.0 */ @@ -35,6 +35,8 @@ #define SUPPORTED_MAX_FRAMES_PER_SDU 1 #if defined(CONFIG_BT_BAP_BROADCAST_SOURCE) +CREATE_FLAG(flag_source_started); + /* When BROADCAST_ENQUEUE_COUNT > 1 we can enqueue enough buffers to ensure that * the controller is never idle */ @@ -62,8 +64,8 @@ static uint8_t bis_codec_data[] = { BT_BYTES_LIST_LE32(BT_AUDIO_LOCATION_FRONT_CENTER)), }; -static K_SEM_DEFINE(sem_started, 0U, ARRAY_SIZE(broadcast_source_streams)); -static K_SEM_DEFINE(sem_stopped, 0U, ARRAY_SIZE(broadcast_source_streams)); +static K_SEM_DEFINE(sem_stream_started, 0U, ARRAY_SIZE(broadcast_source_streams)); +static K_SEM_DEFINE(sem_stream_stopped, 0U, ARRAY_SIZE(broadcast_source_streams)); static void validate_stream_codec_cfg(const struct bt_bap_stream *stream) { @@ -174,7 +176,7 @@ static void validate_stream_codec_cfg(const struct bt_bap_stream *stream) } } -static void started_cb(struct bt_bap_stream *stream) +static void stream_started_cb(struct bt_bap_stream *stream) { struct audio_test_stream *test_stream = audio_test_stream_from_bap_stream(stream); struct bt_bap_ep_info info; @@ -216,13 +218,13 @@ static void started_cb(struct bt_bap_stream *stream) printk("Stream %p started\n", stream); validate_stream_codec_cfg(stream); - k_sem_give(&sem_started); + k_sem_give(&sem_stream_started); } -static void stopped_cb(struct bt_bap_stream *stream, uint8_t reason) +static void steam_stopped_cb(struct bt_bap_stream *stream, uint8_t reason) { printk("Stream %p stopped with reason 0x%02X\n", stream, reason); - k_sem_give(&sem_stopped); + k_sem_give(&sem_stream_stopped); } static void stream_sent_cb(struct bt_bap_stream *stream) @@ -265,11 +267,23 @@ static void stream_sent_cb(struct bt_bap_stream *stream) } static struct bt_bap_stream_ops stream_ops = { - .started = started_cb, - .stopped = stopped_cb, + .started = stream_started_cb, + .stopped = steam_stopped_cb, .sent = stream_sent_cb, }; +static void source_started_cb(struct bt_bap_broadcast_source *source) +{ + printk("Broadcast source %p started\n", source); + SET_FLAG(flag_source_started); +} + +static void source_stopped_cb(struct bt_bap_broadcast_source *source, uint8_t reason) +{ + printk("Broadcast source %p stopped with reason 0x%02X\n", source, reason); + UNSET_FLAG(flag_source_started); +} + static int setup_broadcast_source(struct bt_bap_broadcast_source **source, bool encryption) { struct bt_bap_broadcast_source_stream_param @@ -467,10 +481,12 @@ static void test_broadcast_source_start(struct bt_bap_broadcast_source *source, } /* Wait for all to be started */ - printk("Waiting for streams to be started\n"); + printk("Waiting for %zu streams to be started\n", ARRAY_SIZE(broadcast_source_streams)); for (size_t i = 0U; i < ARRAY_SIZE(broadcast_source_streams); i++) { - k_sem_take(&sem_started, K_FOREVER); + k_sem_take(&sem_stream_started, K_FOREVER); } + + WAIT_FOR_FLAG(flag_source_started); } static void test_broadcast_source_update_metadata(struct bt_bap_broadcast_source *source, @@ -520,10 +536,12 @@ static void test_broadcast_source_stop(struct bt_bap_broadcast_source *source) } /* Wait for all to be stopped */ - printk("Waiting for streams to be stopped\n"); + printk("Waiting for %zu streams to be stopped\n", ARRAY_SIZE(broadcast_source_streams)); for (size_t i = 0U; i < ARRAY_SIZE(broadcast_source_streams); i++) { - k_sem_take(&sem_stopped, K_FOREVER); + k_sem_take(&sem_stream_stopped, K_FOREVER); } + + WAIT_FOR_UNSET_FLAG(flag_source_started); } static void test_broadcast_source_delete(struct bt_bap_broadcast_source *source) @@ -564,10 +582,12 @@ static int stop_extended_adv(struct bt_le_ext_adv *adv) return 0; } -static void test_main(void) +static void init(void) { - struct bt_bap_broadcast_source *source; - struct bt_le_ext_adv *adv; + static struct bt_bap_broadcast_source_cb broadcast_source_cb = { + .started = source_started_cb, + .stopped = source_stopped_cb, + }; int err; err = bt_enable(NULL); @@ -578,6 +598,21 @@ static void test_main(void) printk("Bluetooth initialized\n"); + err = bt_bap_broadcast_source_register_cb(&broadcast_source_cb); + if (err != 0) { + FAIL("Failed to register broadcast source callbacks (err %d)\n", err); + return; + } +} + +static void test_main(void) +{ + struct bt_bap_broadcast_source *source; + struct bt_le_ext_adv *adv; + int err; + + init(); + err = setup_broadcast_source(&source, false); if (err != 0) { FAIL("Unable to setup broadcast source: %d\n", err); @@ -650,13 +685,7 @@ static void test_main_encrypted(void) struct bt_le_ext_adv *adv; int err; - err = bt_enable(NULL); - if (err) { - FAIL("Bluetooth init failed (err %d)\n", err); - return; - } - - printk("Bluetooth initialized\n"); + init(); err = setup_broadcast_source(&source, true); if (err != 0) { diff --git a/tests/bsim/bluetooth/audio/src/cap_initiator_broadcast_test.c b/tests/bsim/bluetooth/audio/src/cap_initiator_broadcast_test.c index ea6eacef4fb7a..9fd54af563c04 100644 --- a/tests/bsim/bluetooth/audio/src/cap_initiator_broadcast_test.c +++ b/tests/bsim/bluetooth/audio/src/cap_initiator_broadcast_test.c @@ -33,6 +33,8 @@ #include "common.h" #if defined(CONFIG_BT_CAP_INITIATOR) && defined(CONFIG_BT_BAP_BROADCAST_SOURCE) +CREATE_FLAG(flag_source_started); + /* Zephyr Controller works best while Extended Advertising interval to be a multiple * of the ISO Interval minus 10 ms (max. advertising random delay). This is * required to place the AUX_ADV_IND PDUs in a non-overlapping interval with the @@ -75,8 +77,8 @@ static struct bt_bap_lc3_preset broadcast_preset_16_2_1 = BT_BAP_LC3_BROADCAST_PRESET_16_2_1(LOCATION, CONTEXT); static size_t stream_count; -static K_SEM_DEFINE(sem_broadcast_started, 0U, ARRAY_SIZE(broadcast_streams)); -static K_SEM_DEFINE(sem_broadcast_stopped, 0U, ARRAY_SIZE(broadcast_streams)); +static K_SEM_DEFINE(sem_broadcast_stream_started, 0U, ARRAY_SIZE(broadcast_streams)); +static K_SEM_DEFINE(sem_broadcast_stream_stopped, 0U, ARRAY_SIZE(broadcast_streams)); static const struct named_lc3_preset lc3_broadcast_presets[] = { {"8_1_1", BT_BAP_LC3_BROADCAST_PRESET_8_1_1(LOCATION, CONTEXT)}, @@ -114,7 +116,7 @@ static const struct named_lc3_preset lc3_broadcast_presets[] = { {"48_6_2", BT_BAP_LC3_BROADCAST_PRESET_48_6_2(LOCATION, CONTEXT)}, }; -static void broadcast_started_cb(struct bt_bap_stream *stream) +static void broadcast_stream_started_cb(struct bt_bap_stream *stream) { struct audio_test_stream *test_stream = audio_test_stream_from_bap_stream(stream); @@ -122,16 +124,16 @@ static void broadcast_started_cb(struct bt_bap_stream *stream) test_stream->tx_cnt = 0U; printk("Stream %p started\n", stream); - k_sem_give(&sem_broadcast_started); + k_sem_give(&sem_broadcast_stream_started); } -static void broadcast_stopped_cb(struct bt_bap_stream *stream, uint8_t reason) +static void broadcast_stream_stopped_cb(struct bt_bap_stream *stream, uint8_t reason) { printk("Stream %p stopped with reason 0x%02X\n", stream, reason); - k_sem_give(&sem_broadcast_stopped); + k_sem_give(&sem_broadcast_stream_stopped); } -static void broadcast_sent_cb(struct bt_bap_stream *bap_stream) +static void broadcast_stream_sent_cb(struct bt_bap_stream *bap_stream) { struct audio_test_stream *test_stream = audio_test_stream_from_bap_stream(bap_stream); struct bt_cap_stream *cap_stream = cap_stream_from_audio_test_stream(test_stream); @@ -178,13 +180,30 @@ static void broadcast_sent_cb(struct bt_bap_stream *bap_stream) } static struct bt_bap_stream_ops broadcast_stream_ops = { - .started = broadcast_started_cb, - .stopped = broadcast_stopped_cb, - .sent = broadcast_sent_cb, + .started = broadcast_stream_started_cb, + .stopped = broadcast_stream_stopped_cb, + .sent = broadcast_stream_sent_cb, }; +static void broadcast_source_started_cb(struct bt_cap_broadcast_source *broadcast_source) +{ + printk("Broadcast source %p started\n", broadcast_source); + SET_FLAG(flag_source_started); +} + +static void broadcast_source_stopped_cb(struct bt_cap_broadcast_source *broadcast_source, + uint8_t reason) +{ + printk("Broadcast source %p stopped with reason 0x%02X\n", broadcast_source, reason); + UNSET_FLAG(flag_source_started); +} + static void init(void) { + static struct bt_cap_initiator_cb broadcast_cbs = { + .broadcast_started = broadcast_source_started_cb, + .broadcast_stopped = broadcast_source_stopped_cb, + }; int err; err = bt_enable(NULL); @@ -221,6 +240,12 @@ static void init(void) printk("Registered GTBS\n"); } + + err = bt_cap_initiator_register_cb(&broadcast_cbs); + if (err != 0) { + FAIL("Failed to register broadcast callbacks: %d\n", err); + return; + } } static void setup_extended_adv(struct bt_le_ext_adv **adv) @@ -605,9 +630,11 @@ static void test_broadcast_audio_stop(struct bt_cap_broadcast_source *broadcast_ /* Wait for all to be stopped */ printk("Waiting for broadcast_streams to be stopped\n"); for (size_t i = 0U; i < stream_count; i++) { - k_sem_take(&sem_broadcast_stopped, K_FOREVER); + k_sem_take(&sem_broadcast_stream_stopped, K_FOREVER); } + WAIT_FOR_UNSET_FLAG(flag_source_started); + printk("Broadcast source stopped\n"); /* Verify that it cannot be stopped twice */ @@ -679,9 +706,11 @@ static void test_main_cap_initiator_broadcast(void) /* Wait for all to be started */ printk("Waiting for broadcast_streams to be started\n"); for (size_t i = 0U; i < stream_count; i++) { - k_sem_take(&sem_broadcast_started, K_FOREVER); + k_sem_take(&sem_broadcast_stream_started, K_FOREVER); } + WAIT_FOR_FLAG(flag_source_started); + /* Initialize sending */ for (size_t i = 0U; i < stream_count; i++) { struct audio_test_stream *test_stream = &broadcast_source_streams[i]; @@ -689,7 +718,7 @@ static void test_main_cap_initiator_broadcast(void) test_stream->tx_active = true; for (unsigned int j = 0U; j < BROADCAST_ENQUEUE_COUNT; j++) { - broadcast_sent_cb(bap_stream_from_audio_test_stream(test_stream)); + broadcast_stream_sent_cb(bap_stream_from_audio_test_stream(test_stream)); } } @@ -785,9 +814,11 @@ static int test_cap_initiator_ac(const struct cap_initiator_ac_param *param) /* Wait for all to be started */ printk("Waiting for broadcast_streams to be started\n"); for (size_t i = 0U; i < stream_count; i++) { - k_sem_take(&sem_broadcast_started, K_FOREVER); + k_sem_take(&sem_broadcast_stream_started, K_FOREVER); } + WAIT_FOR_FLAG(flag_source_started); + /* Initialize sending */ for (size_t i = 0U; i < stream_count; i++) { struct audio_test_stream *test_stream = &broadcast_source_streams[i]; @@ -795,7 +826,7 @@ static int test_cap_initiator_ac(const struct cap_initiator_ac_param *param) test_stream->tx_active = true; for (unsigned int j = 0U; j < BROADCAST_ENQUEUE_COUNT; j++) { - broadcast_sent_cb(bap_stream_from_audio_test_stream(test_stream)); + broadcast_stream_sent_cb(bap_stream_from_audio_test_stream(test_stream)); } }