| /* |
| * Copyright (c) 2022-2023 Nordic Semiconductor ASA |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #if defined(CONFIG_BT_CAP_ACCEPTOR) |
| |
| #include <zephyr/bluetooth/bluetooth.h> |
| #include <zephyr/bluetooth/audio/bap_lc3_preset.h> |
| #include <zephyr/bluetooth/audio/cap.h> |
| #include <zephyr/bluetooth/audio/pacs.h> |
| #include <zephyr/bluetooth/audio/micp.h> |
| #include <zephyr/bluetooth/audio/vcp.h> |
| #include <zephyr/sys/byteorder.h> |
| #include "common.h" |
| #include "bap_common.h" |
| |
| extern enum bst_result_t bst_result; |
| |
| #define SINK_CONTEXT \ |
| (BT_AUDIO_CONTEXT_TYPE_UNSPECIFIED | BT_AUDIO_CONTEXT_TYPE_MEDIA | \ |
| BT_AUDIO_CONTEXT_TYPE_CONVERSATIONAL) |
| #define SOURCE_CONTEXT (BT_AUDIO_CONTEXT_TYPE_UNSPECIFIED | BT_AUDIO_CONTEXT_TYPE_NOTIFICATIONS) |
| |
| CREATE_FLAG(flag_broadcaster_found); |
| CREATE_FLAG(flag_base_received); |
| CREATE_FLAG(flag_pa_synced); |
| CREATE_FLAG(flag_syncable); |
| CREATE_FLAG(flag_received); |
| CREATE_FLAG(flag_pa_sync_lost); |
| |
| static struct bt_bap_broadcast_sink *g_broadcast_sink; |
| static struct bt_le_scan_recv_info broadcaster_info; |
| static bt_addr_le_t broadcaster_addr; |
| static struct bt_le_per_adv_sync *pa_sync; |
| static uint32_t broadcaster_broadcast_id; |
| static struct audio_test_stream broadcast_sink_streams[CONFIG_BT_BAP_BROADCAST_SNK_STREAM_COUNT]; |
| |
| static const struct bt_audio_codec_cap codec_cap = BT_AUDIO_CODEC_CAP_LC3( |
| BT_AUDIO_CODEC_CAP_FREQ_ANY, BT_AUDIO_CODEC_CAP_DURATION_ANY, |
| BT_AUDIO_CODEC_CAP_CHAN_COUNT_SUPPORT(1, 2), 30, 240, 2, |
| (BT_AUDIO_CONTEXT_TYPE_CONVERSATIONAL | BT_AUDIO_CONTEXT_TYPE_MEDIA)); |
| |
| static const struct bt_audio_codec_qos_pref unicast_qos_pref = |
| BT_AUDIO_CODEC_QOS_PREF(true, BT_GAP_LE_PHY_2M, 0u, 60u, 20000u, 40000u, 20000u, 40000u); |
| |
| static bool auto_start_sink_streams; |
| |
| static K_SEM_DEFINE(sem_broadcast_started, 0U, ARRAY_SIZE(broadcast_sink_streams)); |
| static K_SEM_DEFINE(sem_broadcast_stopped, 0U, ARRAY_SIZE(broadcast_sink_streams)); |
| |
| /* Create a mask for the maximum BIS we can sync to using the number of |
| * broadcast_sink_streams we have. We add an additional 1 since the bis indexes |
| * start from 1 and not 0. |
| */ |
| static const uint32_t bis_index_mask = BIT_MASK(ARRAY_SIZE(broadcast_sink_streams) + 1U); |
| static uint32_t bis_index_bitfield; |
| |
| #define UNICAST_CHANNEL_COUNT_1 BIT(0) |
| |
| static struct bt_cap_stream unicast_streams[CONFIG_BT_ASCS_ASE_SNK_COUNT + |
| CONFIG_BT_ASCS_ASE_SRC_COUNT]; |
| |
| CREATE_FLAG(flag_unicast_stream_configured); |
| |
| static bool subgroup_data_func_cb(struct bt_data *data, void *user_data) |
| { |
| bool *stream_context_found = (bool *)user_data; |
| |
| printk("type %u len %u\n", data->type, data->data_len); |
| |
| if (!valid_metadata_type(data->type, data->data_len)) { |
| return false; |
| } |
| |
| if (data->type == BT_AUDIO_METADATA_TYPE_STREAM_CONTEXT) { |
| if (data->data_len != 2) { /* Stream context size */ |
| return false; |
| } |
| |
| *stream_context_found = true; |
| return false; |
| } |
| |
| return true; |
| } |
| |
| static bool valid_subgroup_metadata_cb(const struct bt_bap_base_subgroup *subgroup, void *user_data) |
| { |
| bool stream_context_found = false; |
| uint8_t *meta; |
| int ret; |
| |
| ret = bt_bap_base_get_subgroup_codec_meta(subgroup, &meta); |
| if (ret < 0) { |
| FAIL("Could not get subgroup meta: %d\n", ret); |
| return false; |
| } |
| |
| ret = bt_audio_data_parse(meta, (size_t)ret, subgroup_data_func_cb, &stream_context_found); |
| if (ret != 0 && ret != -ECANCELED) { |
| return false; |
| } |
| |
| if (!stream_context_found) { |
| printk("Subgroup did not have streaming context\n"); |
| } |
| |
| /* if this is false, the iterater will return early with an error */ |
| return stream_context_found; |
| } |
| |
| static void base_recv_cb(struct bt_bap_broadcast_sink *sink, const struct bt_bap_base *base, |
| size_t base_size) |
| { |
| uint32_t base_bis_index_bitfield = 0U; |
| int ret; |
| |
| if (TEST_FLAG(flag_base_received)) { |
| return; |
| } |
| |
| ret = bt_bap_base_get_subgroup_count(base); |
| if (ret < 0) { |
| FAIL("Failed to get subgroup count: %d\n", ret); |
| return; |
| } |
| |
| printk("Received BASE with %d subgroups from broadcast sink %p\n", ret, sink); |
| |
| if (ret == 0) { |
| FAIL("subgroup_count was 0"); |
| return; |
| } |
| |
| ret = bt_bap_base_foreach_subgroup(base, valid_subgroup_metadata_cb, NULL); |
| if (ret != 0) { |
| FAIL("Failed to parse subgroups: %d\n", ret); |
| return; |
| } |
| |
| ret = bt_bap_base_get_bis_indexes(base, &base_bis_index_bitfield); |
| if (ret != 0) { |
| FAIL("Failed to BIS indexes: %d\n", ret); |
| return; |
| } |
| |
| bis_index_bitfield = base_bis_index_bitfield & bis_index_mask; |
| |
| SET_FLAG(flag_base_received); |
| } |
| |
| static void syncable_cb(struct bt_bap_broadcast_sink *sink, const struct bt_iso_biginfo *biginfo) |
| { |
| printk("Broadcast sink %p syncable with%s encryption\n", |
| sink, biginfo->encryption ? "" : "out"); |
| SET_FLAG(flag_syncable); |
| } |
| |
| static struct bt_bap_broadcast_sink_cb broadcast_sink_cbs = { |
| .base_recv = base_recv_cb, |
| .syncable = syncable_cb, |
| }; |
| |
| static bool scan_check_and_sync_broadcast(struct bt_data *data, void *user_data) |
| { |
| const struct bt_le_scan_recv_info *info = user_data; |
| char le_addr[BT_ADDR_LE_STR_LEN]; |
| struct bt_uuid_16 adv_uuid; |
| uint32_t broadcast_id; |
| |
| if (TEST_FLAG(flag_broadcaster_found)) { |
| /* no-op*/ |
| return false; |
| } |
| |
| if (data->type != BT_DATA_SVC_DATA16) { |
| return true; |
| } |
| |
| if (data->data_len < BT_UUID_SIZE_16 + BT_AUDIO_BROADCAST_ID_SIZE) { |
| return true; |
| } |
| |
| if (!bt_uuid_create(&adv_uuid.uuid, data->data, BT_UUID_SIZE_16)) { |
| return true; |
| } |
| |
| if (bt_uuid_cmp(&adv_uuid.uuid, BT_UUID_BROADCAST_AUDIO)) { |
| return true; |
| } |
| |
| broadcast_id = sys_get_le24(data->data + BT_UUID_SIZE_16); |
| |
| bt_addr_le_to_str(info->addr, le_addr, sizeof(le_addr)); |
| |
| printk("Found broadcaster with ID 0x%06X and addr %s and sid 0x%02X\n", broadcast_id, |
| le_addr, info->sid); |
| |
| SET_FLAG(flag_broadcaster_found); |
| |
| /* Store info for PA sync parameters */ |
| memcpy(&broadcaster_info, info, sizeof(broadcaster_info)); |
| bt_addr_le_copy(&broadcaster_addr, info->addr); |
| broadcaster_broadcast_id = broadcast_id; |
| |
| /* Stop parsing */ |
| return false; |
| } |
| |
| static void broadcast_scan_recv(const struct bt_le_scan_recv_info *info, struct net_buf_simple *ad) |
| { |
| if (info->interval != 0U) { |
| bt_data_parse(ad, scan_check_and_sync_broadcast, (void *)info); |
| } |
| } |
| |
| static struct bt_le_scan_cb bap_scan_cb = { |
| .recv = broadcast_scan_recv, |
| }; |
| |
| static void bap_pa_sync_synced_cb(struct bt_le_per_adv_sync *sync, |
| struct bt_le_per_adv_sync_synced_info *info) |
| { |
| if (sync == pa_sync) { |
| printk("PA sync %p synced for broadcast sink with broadcast ID 0x%06X\n", sync, |
| broadcaster_broadcast_id); |
| |
| SET_FLAG(flag_pa_synced); |
| } |
| } |
| |
| static void bap_pa_sync_terminated_cb(struct bt_le_per_adv_sync *sync, |
| const struct bt_le_per_adv_sync_term_info *info) |
| { |
| if (sync == pa_sync) { |
| printk("PA sync %p lost with reason %u\n", sync, info->reason); |
| pa_sync = NULL; |
| |
| SET_FLAG(flag_pa_sync_lost); |
| } |
| } |
| |
| static struct bt_le_per_adv_sync_cb bap_pa_sync_cb = { |
| .synced = bap_pa_sync_synced_cb, |
| .term = bap_pa_sync_terminated_cb, |
| }; |
| |
| static void started_cb(struct bt_bap_stream *stream) |
| { |
| printk("Stream %p started\n", stream); |
| k_sem_give(&sem_broadcast_started); |
| } |
| |
| static void 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); |
| } |
| |
| static void recv_cb(struct bt_bap_stream *stream, const struct bt_iso_recv_info *info, |
| struct net_buf *buf) |
| { |
| struct audio_test_stream *test_stream = audio_test_stream_from_bap_stream(stream); |
| |
| if ((test_stream->rx_cnt % 100U) == 0U) { |
| printk("[%zu]: Incoming audio on stream %p len %u and ts %u\n", test_stream->rx_cnt, |
| stream, buf->len, info->ts); |
| } |
| |
| if (test_stream->rx_cnt > 0U && info->ts == test_stream->last_info.ts) { |
| FAIL("Duplicated timestamp received: %u\n", test_stream->last_info.ts); |
| return; |
| } |
| |
| if (test_stream->rx_cnt > 0U && info->seq_num == test_stream->last_info.seq_num) { |
| FAIL("Duplicated PSN received: %u\n", test_stream->last_info.seq_num); |
| return; |
| } |
| |
| if (info->flags & BT_ISO_FLAGS_ERROR) { |
| /* Fail the test if we have not received what we expected */ |
| if (!TEST_FLAG(flag_received)) { |
| FAIL("ISO receive error\n"); |
| } |
| |
| return; |
| } |
| |
| if (info->flags & BT_ISO_FLAGS_LOST) { |
| FAIL("ISO receive lost\n"); |
| return; |
| } |
| |
| if (memcmp(buf->data, mock_iso_data, buf->len) == 0) { |
| test_stream->rx_cnt++; |
| |
| if (test_stream->rx_cnt >= MIN_SEND_COUNT) { |
| /* We set the flag is just one stream has received the expected */ |
| SET_FLAG(flag_received); |
| } |
| } else { |
| FAIL("Unexpected data received\n"); |
| } |
| } |
| |
| static struct bt_bap_stream_ops broadcast_stream_ops = { |
| .started = started_cb, .stopped = stopped_cb, .recv = recv_cb}; |
| |
| static void unicast_stream_enabled_cb(struct bt_bap_stream *stream) |
| { |
| struct bt_bap_ep_info ep_info; |
| int err; |
| |
| printk("Enabled: stream %p (auto_start_sink_streams %d)\n", stream, |
| auto_start_sink_streams); |
| |
| err = bt_bap_ep_get_info(stream->ep, &ep_info); |
| if (err != 0) { |
| FAIL("Failed to get ep info: %d\n", err); |
| return; |
| } |
| |
| if (auto_start_sink_streams && ep_info.dir == BT_AUDIO_DIR_SINK) { |
| /* Automatically do the receiver start ready operation */ |
| err = bt_bap_stream_start(stream); |
| |
| if (err != 0) { |
| FAIL("Failed to start stream: %d\n", err); |
| return; |
| } |
| } |
| } |
| |
| static struct bt_bap_stream_ops unicast_stream_ops = { |
| .enabled = unicast_stream_enabled_cb, |
| }; |
| |
| /* TODO: Expand with CAP service data */ |
| static const struct bt_data cap_acceptor_ad[] = { |
| BT_DATA_BYTES(BT_DATA_FLAGS, (BT_LE_AD_GENERAL | BT_LE_AD_NO_BREDR)), |
| BT_DATA_BYTES(BT_DATA_UUID16_ALL, BT_UUID_16_ENCODE(BT_UUID_CAS_VAL)), |
| }; |
| |
| static struct bt_csip_set_member_svc_inst *csip_set_member; |
| |
| static struct bt_bap_stream *unicast_stream_alloc(void) |
| { |
| for (size_t i = 0; i < ARRAY_SIZE(unicast_streams); i++) { |
| struct bt_bap_stream *stream = &unicast_streams[i].bap_stream; |
| |
| if (!stream->conn) { |
| return stream; |
| } |
| } |
| |
| return NULL; |
| } |
| |
| static int unicast_server_config(struct bt_conn *conn, const struct bt_bap_ep *ep, |
| enum bt_audio_dir dir, const struct bt_audio_codec_cfg *codec_cfg, |
| struct bt_bap_stream **stream, |
| struct bt_audio_codec_qos_pref *const pref, |
| struct bt_bap_ascs_rsp *rsp) |
| { |
| printk("ASE Codec Config: conn %p ep %p dir %u\n", conn, ep, dir); |
| |
| print_codec_cfg(codec_cfg); |
| |
| *stream = unicast_stream_alloc(); |
| if (*stream == NULL) { |
| printk("No streams available\n"); |
| *rsp = BT_BAP_ASCS_RSP(BT_BAP_ASCS_RSP_CODE_NO_MEM, BT_BAP_ASCS_REASON_NONE); |
| |
| return -ENOMEM; |
| } |
| |
| printk("ASE Codec Config stream %p\n", *stream); |
| |
| SET_FLAG(flag_unicast_stream_configured); |
| |
| *pref = unicast_qos_pref; |
| |
| return 0; |
| } |
| |
| static int unicast_server_reconfig(struct bt_bap_stream *stream, enum bt_audio_dir dir, |
| const struct bt_audio_codec_cfg *codec_cfg, |
| struct bt_audio_codec_qos_pref *const pref, |
| struct bt_bap_ascs_rsp *rsp) |
| { |
| printk("ASE Codec Reconfig: stream %p\n", stream); |
| |
| print_codec_cfg(codec_cfg); |
| |
| *pref = unicast_qos_pref; |
| |
| *rsp = BT_BAP_ASCS_RSP(BT_BAP_ASCS_RSP_CODE_CONF_UNSUPPORTED, BT_BAP_ASCS_REASON_NONE); |
| |
| /* We only support one QoS at the moment, reject changes */ |
| return -ENOEXEC; |
| } |
| |
| static int unicast_server_qos(struct bt_bap_stream *stream, const struct bt_audio_codec_qos *qos, |
| struct bt_bap_ascs_rsp *rsp) |
| { |
| printk("QoS: stream %p qos %p\n", stream, qos); |
| |
| print_qos(qos); |
| |
| return 0; |
| } |
| |
| static int unicast_server_enable(struct bt_bap_stream *stream, const uint8_t meta[], |
| size_t meta_len, struct bt_bap_ascs_rsp *rsp) |
| { |
| printk("Enable: stream %p meta_len %zu\n", stream, meta_len); |
| |
| return 0; |
| } |
| |
| static int unicast_server_start(struct bt_bap_stream *stream, struct bt_bap_ascs_rsp *rsp) |
| { |
| printk("Start: stream %p\n", stream); |
| |
| return 0; |
| } |
| |
| static bool ascs_data_func_cb(struct bt_data *data, void *user_data) |
| { |
| struct bt_bap_ascs_rsp *rsp = (struct bt_bap_ascs_rsp *)user_data; |
| |
| if (!BT_AUDIO_METADATA_TYPE_IS_KNOWN(data->type)) { |
| printk("Invalid metadata type %u or length %u\n", data->type, data->data_len); |
| *rsp = BT_BAP_ASCS_RSP(BT_BAP_ASCS_RSP_CODE_METADATA_REJECTED, data->type); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| static int unicast_server_metadata(struct bt_bap_stream *stream, const uint8_t meta[], |
| size_t meta_len, struct bt_bap_ascs_rsp *rsp) |
| { |
| printk("Metadata: stream %p meta_len %zu\n", stream, meta_len); |
| |
| return bt_audio_data_parse(meta, meta_len, ascs_data_func_cb, rsp); |
| } |
| |
| static int unicast_server_disable(struct bt_bap_stream *stream, struct bt_bap_ascs_rsp *rsp) |
| { |
| printk("Disable: stream %p\n", stream); |
| |
| return 0; |
| } |
| |
| static int unicast_server_stop(struct bt_bap_stream *stream, struct bt_bap_ascs_rsp *rsp) |
| { |
| printk("Stop: stream %p\n", stream); |
| |
| return 0; |
| } |
| |
| static int unicast_server_release(struct bt_bap_stream *stream, struct bt_bap_ascs_rsp *rsp) |
| { |
| printk("Release: stream %p\n", stream); |
| |
| return 0; |
| } |
| |
| static struct bt_bap_unicast_server_cb unicast_server_cbs = { |
| .config = unicast_server_config, |
| .reconfig = unicast_server_reconfig, |
| .qos = unicast_server_qos, |
| .enable = unicast_server_enable, |
| .start = unicast_server_start, |
| .metadata = unicast_server_metadata, |
| .disable = unicast_server_disable, |
| .stop = unicast_server_stop, |
| .release = unicast_server_release, |
| }; |
| |
| static void set_location(void) |
| { |
| int err; |
| |
| if (IS_ENABLED(CONFIG_BT_PAC_SNK_LOC)) { |
| err = bt_pacs_set_location(BT_AUDIO_DIR_SINK, |
| BT_AUDIO_LOCATION_FRONT_CENTER); |
| if (err != 0) { |
| FAIL("Failed to set sink location (err %d)\n", err); |
| return; |
| } |
| } |
| |
| if (IS_ENABLED(CONFIG_BT_PAC_SRC_LOC)) { |
| err = bt_pacs_set_location(BT_AUDIO_DIR_SOURCE, |
| BT_AUDIO_LOCATION_FRONT_LEFT | |
| BT_AUDIO_LOCATION_FRONT_RIGHT); |
| if (err != 0) { |
| FAIL("Failed to set source location (err %d)\n", err); |
| return; |
| } |
| } |
| |
| printk("Location successfully set\n"); |
| } |
| |
| static int set_supported_contexts(void) |
| { |
| int err; |
| |
| if (IS_ENABLED(CONFIG_BT_PAC_SNK)) { |
| err = bt_pacs_set_supported_contexts(BT_AUDIO_DIR_SINK, SINK_CONTEXT); |
| if (err != 0) { |
| printk("Failed to set sink supported contexts (err %d)\n", |
| err); |
| |
| return err; |
| } |
| } |
| |
| if (IS_ENABLED(CONFIG_BT_PAC_SRC)) { |
| err = bt_pacs_set_supported_contexts(BT_AUDIO_DIR_SOURCE, SOURCE_CONTEXT); |
| if (err != 0) { |
| printk("Failed to set source supported contexts (err %d)\n", |
| err); |
| |
| return err; |
| } |
| } |
| |
| printk("Supported contexts successfully set\n"); |
| |
| return 0; |
| } |
| |
| static void set_available_contexts(void) |
| { |
| int err; |
| |
| err = bt_pacs_set_available_contexts(BT_AUDIO_DIR_SINK, SINK_CONTEXT); |
| if (IS_ENABLED(CONFIG_BT_PAC_SNK) && err != 0) { |
| FAIL("Failed to set sink available contexts (err %d)\n", err); |
| return; |
| } |
| |
| err = bt_pacs_set_available_contexts(BT_AUDIO_DIR_SOURCE, SOURCE_CONTEXT); |
| if (IS_ENABLED(CONFIG_BT_PAC_SRC) && err != 0) { |
| FAIL("Failed to set source available contexts (err %d)\n", err); |
| return; |
| } |
| |
| printk("Available contexts successfully set\n"); |
| } |
| |
| static void init(void) |
| { |
| const struct bt_csip_set_member_register_param csip_set_member_param = { |
| .set_size = 3, |
| .rank = 1, |
| .lockable = true, |
| /* Using the CSIP_SET_MEMBER test sample SIRK */ |
| .set_sirk = { 0xcd, 0xcc, 0x72, 0xdd, 0x86, 0x8c, 0xcd, 0xce, |
| 0x22, 0xfd, 0xa1, 0x21, 0x09, 0x7d, 0x7d, 0x45 }, |
| }; |
| |
| int err; |
| |
| err = bt_enable(NULL); |
| if (err != 0) { |
| FAIL("Bluetooth enable failed (err %d)\n", err); |
| return; |
| } |
| |
| printk("Bluetooth initialized\n"); |
| |
| if (IS_ENABLED(CONFIG_BT_CAP_ACCEPTOR_SET_MEMBER)) { |
| err = bt_cap_acceptor_register(&csip_set_member_param, &csip_set_member); |
| if (err != 0) { |
| FAIL("CAP acceptor failed to register (err %d)\n", err); |
| return; |
| } |
| } |
| |
| if (IS_ENABLED(CONFIG_BT_BAP_UNICAST_SERVER)) { |
| static struct bt_pacs_cap unicast_cap = { |
| .codec_cap = &codec_cap, |
| }; |
| |
| err = bt_pacs_cap_register(BT_AUDIO_DIR_SINK, &unicast_cap); |
| if (err != 0) { |
| FAIL("Broadcast capability register failed (err %d)\n", |
| err); |
| |
| return; |
| } |
| |
| err = bt_pacs_cap_register(BT_AUDIO_DIR_SOURCE, &unicast_cap); |
| if (err != 0) { |
| FAIL("Broadcast capability register failed (err %d)\n", err); |
| |
| return; |
| } |
| |
| err = bt_bap_unicast_server_register_cb(&unicast_server_cbs); |
| if (err != 0) { |
| FAIL("Failed to register unicast server callbacks (err %d)\n", |
| err); |
| |
| return; |
| } |
| |
| for (size_t i = 0U; i < ARRAY_SIZE(unicast_streams); i++) { |
| bt_cap_stream_ops_register(&unicast_streams[i], &unicast_stream_ops); |
| } |
| |
| err = bt_le_adv_start(BT_LE_ADV_CONN, cap_acceptor_ad, |
| ARRAY_SIZE(cap_acceptor_ad), NULL, 0); |
| if (err != 0) { |
| FAIL("Advertising failed to start (err %d)\n", err); |
| return; |
| } |
| } |
| |
| if (IS_ENABLED(CONFIG_BT_BAP_BROADCAST_SINK)) { |
| static struct bt_pacs_cap broadcast_cap = { |
| .codec_cap = &codec_cap, |
| }; |
| |
| err = bt_pacs_cap_register(BT_AUDIO_DIR_SINK, &broadcast_cap); |
| if (err != 0) { |
| FAIL("Broadcast capability register failed (err %d)\n", |
| err); |
| |
| return; |
| } |
| |
| bt_bap_broadcast_sink_register_cb(&broadcast_sink_cbs); |
| bt_le_per_adv_sync_cb_register(&bap_pa_sync_cb); |
| bt_le_scan_cb_register(&bap_scan_cb); |
| |
| UNSET_FLAG(flag_broadcaster_found); |
| UNSET_FLAG(flag_base_received); |
| UNSET_FLAG(flag_pa_synced); |
| |
| for (size_t i = 0U; i < ARRAY_SIZE(broadcast_sink_streams); i++) { |
| bt_cap_stream_ops_register( |
| cap_stream_from_audio_test_stream(&broadcast_sink_streams[i]), |
| &broadcast_stream_ops); |
| } |
| } |
| |
| if (IS_ENABLED(CONFIG_BT_PACS)) { |
| set_supported_contexts(); |
| set_available_contexts(); |
| set_location(); |
| } |
| |
| if (IS_ENABLED(CONFIG_BT_VCP_VOL_REND)) { |
| char output_desc[CONFIG_BT_VCP_VOL_REND_VOCS_INSTANCE_COUNT][16]; |
| char input_desc[CONFIG_BT_VCP_VOL_REND_AICS_INSTANCE_COUNT][16]; |
| struct bt_vcp_vol_rend_register_param vcp_param = {0}; |
| |
| for (size_t i = 0U; i < ARRAY_SIZE(vcp_param.vocs_param); i++) { |
| vcp_param.vocs_param[i].location_writable = true; |
| vcp_param.vocs_param[i].desc_writable = true; |
| snprintf(output_desc[i], sizeof(output_desc[i]), "Output %d", i + 1); |
| vcp_param.vocs_param[i].output_desc = output_desc[i]; |
| vcp_param.vocs_param[i].cb = NULL; |
| } |
| |
| for (size_t i = 0U; i < ARRAY_SIZE(vcp_param.aics_param); i++) { |
| vcp_param.aics_param[i].desc_writable = true; |
| snprintf(input_desc[i], sizeof(input_desc[i]), "VCP Input %d", i + 1); |
| vcp_param.aics_param[i].description = input_desc[i]; |
| vcp_param.aics_param[i].type = BT_AICS_INPUT_TYPE_DIGITAL; |
| vcp_param.aics_param[i].status = true; |
| vcp_param.aics_param[i].gain_mode = BT_AICS_MODE_MANUAL; |
| vcp_param.aics_param[i].units = 1; |
| vcp_param.aics_param[i].min_gain = 0; |
| vcp_param.aics_param[i].max_gain = 100; |
| vcp_param.aics_param[i].cb = NULL; |
| } |
| |
| vcp_param.step = 1; |
| vcp_param.mute = BT_VCP_STATE_UNMUTED; |
| vcp_param.volume = 100; |
| vcp_param.cb = NULL; |
| err = bt_vcp_vol_rend_register(&vcp_param); |
| if (err != 0) { |
| FAIL("Failed to register VCS (err %d)\n", err); |
| |
| return; |
| } |
| } |
| |
| if (IS_ENABLED(CONFIG_BT_MICP_MIC_DEV)) { |
| struct bt_micp_mic_dev_register_param micp_param = {0}; |
| |
| #if defined(CONFIG_BT_MICP_MIC_DEV_AICS) |
| char input_desc[CONFIG_BT_MICP_MIC_DEV_AICS_INSTANCE_COUNT][16]; |
| |
| for (int i = 0; i < ARRAY_SIZE(micp_param.aics_param); i++) { |
| micp_param.aics_param[i].desc_writable = true; |
| snprintf(input_desc[i], sizeof(input_desc[i]), "MICP Input %d", i + 1); |
| micp_param.aics_param[i].description = input_desc[i]; |
| micp_param.aics_param[i].type = BT_AICS_INPUT_TYPE_DIGITAL; |
| micp_param.aics_param[i].status = true; |
| micp_param.aics_param[i].gain_mode = BT_AICS_MODE_MANUAL; |
| micp_param.aics_param[i].units = 1; |
| micp_param.aics_param[i].min_gain = 0; |
| micp_param.aics_param[i].max_gain = 100; |
| micp_param.aics_param[i].cb = NULL; |
| } |
| #endif /* CONFIG_BT_MICP_MIC_DEV_AICS */ |
| |
| err = bt_micp_mic_dev_register(&micp_param); |
| if (err != 0) { |
| FAIL("Failed to register MICS (err %d)\n", err); |
| return; |
| } |
| } |
| } |
| |
| static void test_cap_acceptor_unicast(void) |
| { |
| init(); |
| |
| auto_start_sink_streams = true; |
| |
| /* TODO: wait for audio stream to pass */ |
| |
| WAIT_FOR_FLAG(flag_connected); |
| |
| PASS("CAP acceptor unicast passed\n"); |
| } |
| |
| static void test_cap_acceptor_unicast_timeout(void) |
| { |
| init(); |
| |
| auto_start_sink_streams = false; /* Cause unicast_audio_start timeout */ |
| |
| /* TODO: wait for audio stream to pass */ |
| |
| WAIT_FOR_FLAG(flag_connected); |
| |
| PASS("CAP acceptor unicast passed\n"); |
| } |
| |
| static uint16_t interval_to_sync_timeout(uint16_t pa_interval) |
| { |
| uint16_t pa_timeout; |
| |
| if (pa_interval == BT_BAP_PA_INTERVAL_UNKNOWN) { |
| /* Use maximum value to maximize chance of success */ |
| pa_timeout = BT_GAP_PER_ADV_MAX_TIMEOUT; |
| } else { |
| uint32_t interval_ms; |
| uint32_t timeout; |
| |
| /* Add retries and convert to unit in 10's of ms */ |
| interval_ms = BT_GAP_PER_ADV_INTERVAL_TO_MS(pa_interval); |
| timeout = (interval_ms * PA_SYNC_INTERVAL_TO_TIMEOUT_RATIO) / 10; |
| |
| /* Enforce restraints */ |
| pa_timeout = CLAMP(timeout, BT_GAP_PER_ADV_MIN_TIMEOUT, BT_GAP_PER_ADV_MAX_TIMEOUT); |
| } |
| |
| return pa_timeout; |
| } |
| |
| static int pa_sync_create(void) |
| { |
| struct bt_le_per_adv_sync_param create_params = {0}; |
| |
| bt_addr_le_copy(&create_params.addr, &broadcaster_addr); |
| create_params.options = BT_LE_PER_ADV_SYNC_OPT_FILTER_DUPLICATE; |
| create_params.sid = broadcaster_info.sid; |
| create_params.skip = PA_SYNC_SKIP; |
| create_params.timeout = interval_to_sync_timeout(broadcaster_info.interval); |
| |
| return bt_le_per_adv_sync_create(&create_params, &pa_sync); |
| } |
| |
| static void test_cap_acceptor_broadcast(void) |
| { |
| static struct bt_bap_stream *bap_streams[ARRAY_SIZE(broadcast_sink_streams)]; |
| size_t stream_count; |
| int err; |
| |
| init(); |
| |
| printk("Scanning for broadcast sources\n"); |
| err = bt_le_scan_start(BT_LE_SCAN_ACTIVE, NULL); |
| if (err != 0) { |
| FAIL("Unable to start scan for broadcast sources: %d", err); |
| return; |
| } |
| |
| WAIT_FOR_FLAG(flag_broadcaster_found); |
| |
| printk("Broadcast source found, stopping scan\n"); |
| err = bt_le_scan_stop(); |
| if (err != 0) { |
| FAIL("bt_le_scan_stop failed with %d\n", err); |
| return; |
| } |
| |
| printk("Scan stopped, attempting to PA sync to the broadcaster with id 0x%06X\n", |
| broadcaster_broadcast_id); |
| err = pa_sync_create(); |
| if (err != 0) { |
| FAIL("Could not create Broadcast PA sync: %d\n", err); |
| return; |
| } |
| |
| printk("Broadcast source found, waiting for PA sync\n"); |
| WAIT_FOR_FLAG(flag_pa_synced); |
| |
| printk("Creating the broadcast sink\n"); |
| err = bt_bap_broadcast_sink_create(pa_sync, broadcaster_broadcast_id, &g_broadcast_sink); |
| if (err != 0) { |
| FAIL("Unable to create the sink: %d\n", err); |
| return; |
| } |
| |
| printk("Broadcast source PA synced, waiting for BASE\n"); |
| WAIT_FOR_FLAG(flag_base_received); |
| printk("BASE received\n"); |
| |
| printk("Waiting for BIG syncable\n"); |
| WAIT_FOR_FLAG(flag_syncable); |
| |
| for (size_t i = 0U; i < ARRAY_SIZE(broadcast_sink_streams); i++) { |
| bap_streams[i] = bap_stream_from_audio_test_stream(&broadcast_sink_streams[i]); |
| } |
| |
| printk("Syncing the sink\n"); |
| stream_count = 0; |
| for (int i = 1; i < BT_ISO_MAX_GROUP_ISO_COUNT; i++) { |
| if ((bis_index_bitfield & BIT(i)) != 0) { |
| stream_count++; |
| } |
| } |
| |
| err = bt_bap_broadcast_sink_sync(g_broadcast_sink, bis_index_bitfield, bap_streams, NULL); |
| if (err != 0) { |
| FAIL("Unable to sync the sink: %d\n", err); |
| return; |
| } |
| |
| /* Wait for all to be started */ |
| printk("Waiting for %zu streams to be started\n", stream_count); |
| for (size_t i = 0U; i < stream_count; i++) { |
| k_sem_take(&sem_broadcast_started, K_FOREVER); |
| } |
| |
| printk("Waiting for data\n"); |
| WAIT_FOR_FLAG(flag_received); |
| backchannel_sync_send_all(); /* let other devices know we have received what we wanted */ |
| |
| /* The order of PA sync lost and BIG Sync lost is irrelevant |
| * and depend on timeout parameters. We just wait for PA first, but |
| * either way will work. |
| */ |
| printk("Waiting for PA disconnected\n"); |
| WAIT_FOR_FLAG(flag_pa_sync_lost); |
| |
| printk("Waiting for %zu streams to be stopped\n", stream_count); |
| for (size_t i = 0U; i < stream_count; i++) { |
| k_sem_take(&sem_broadcast_stopped, K_FOREVER); |
| } |
| |
| PASS("CAP acceptor broadcast passed\n"); |
| } |
| |
| static void test_cap_acceptor_capture_and_render(void) |
| { |
| init(); |
| |
| WAIT_FOR_FLAG(flag_connected); |
| |
| PASS("CAP acceptor unicast passed\n"); |
| } |
| |
| static const struct bst_test_instance test_cap_acceptor[] = { |
| { |
| .test_id = "cap_acceptor_unicast", |
| .test_post_init_f = test_init, |
| .test_tick_f = test_tick, |
| .test_main_f = test_cap_acceptor_unicast, |
| }, |
| { |
| .test_id = "cap_acceptor_unicast_timeout", |
| .test_post_init_f = test_init, |
| .test_tick_f = test_tick, |
| .test_main_f = test_cap_acceptor_unicast_timeout, |
| }, |
| { |
| .test_id = "cap_acceptor_broadcast", |
| .test_post_init_f = test_init, |
| .test_tick_f = test_tick, |
| .test_main_f = test_cap_acceptor_broadcast, |
| }, |
| { |
| .test_id = "cap_acceptor_capture_and_render", |
| .test_post_init_f = test_init, |
| .test_tick_f = test_tick, |
| .test_main_f = test_cap_acceptor_capture_and_render, |
| }, |
| BSTEST_END_MARKER, |
| }; |
| |
| struct bst_test_list *test_cap_acceptor_install(struct bst_test_list *tests) |
| { |
| return bst_add_tests(tests, test_cap_acceptor); |
| } |
| |
| #else /* !(CONFIG_BT_CAP_ACCEPTOR) */ |
| |
| struct bst_test_list *test_cap_acceptor_install(struct bst_test_list *tests) |
| { |
| return tests; |
| } |
| |
| #endif /* CONFIG_BT_CAP_ACCEPTOR */ |