diff --git a/include/zephyr/bluetooth/audio/bap.h b/include/zephyr/bluetooth/audio/bap.h index 33ba8ca3cff..17e53ca6c14 100644 --- a/include/zephyr/bluetooth/audio/bap.h +++ b/include/zephyr/bluetooth/audio/bap.h @@ -2292,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; }; @@ -2302,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/subsys/bluetooth/audio/bap_broadcast_sink.c b/subsys/bluetooth/audio/bap_broadcast_sink.c index d6b85786968..6a6056ec0ce 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/tests/bsim/bluetooth/audio/src/bap_broadcast_sink_test.c b/tests/bsim/bluetooth/audio/src/bap_broadcast_sink_test.c index 52df6b43373..6fadeb3f010 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");