| /* |
| * Copyright (c) 2021-2023 Nordic Semiconductor ASA |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #if defined(CONFIG_BT_BAP_UNICAST_CLIENT) |
| |
| #include <zephyr/bluetooth/bluetooth.h> |
| #include <zephyr/bluetooth/audio/audio.h> |
| #include <zephyr/bluetooth/audio/bap.h> |
| #include <zephyr/bluetooth/audio/bap_lc3_preset.h> |
| #include <zephyr/bluetooth/audio/pacs.h> |
| #include "common.h" |
| #include "bap_common.h" |
| |
| #define BAP_STREAM_RETRY_WAIT K_MSEC(100) |
| |
| #define ENQUEUE_COUNT 2U |
| #define TOTAL_BUF_NEEDED (ENQUEUE_COUNT * CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SNK_COUNT) |
| NET_BUF_POOL_FIXED_DEFINE(tx_pool, TOTAL_BUF_NEEDED, BT_ISO_SDU_BUF_SIZE(CONFIG_BT_ISO_TX_MTU), |
| CONFIG_BT_CONN_TX_USER_DATA_SIZE, NULL); |
| |
| extern enum bst_result_t bst_result; |
| |
| static volatile size_t sent_count; |
| static struct audio_test_stream test_streams[CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SNK_COUNT]; |
| static struct bt_bap_ep *g_sinks[CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SNK_COUNT]; |
| static struct bt_bap_ep *g_sources[CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SRC_COUNT]; |
| |
| static struct bt_bap_unicast_group_stream_pair_param pair_params[ARRAY_SIZE(test_streams)]; |
| static struct bt_bap_unicast_group_stream_param stream_params[ARRAY_SIZE(test_streams)]; |
| |
| /* Mandatory support preset by both client and server */ |
| static struct bt_bap_lc3_preset preset_16_2_1 = BT_BAP_LC3_UNICAST_PRESET_16_2_1( |
| BT_AUDIO_LOCATION_FRONT_LEFT, BT_AUDIO_CONTEXT_TYPE_UNSPECIFIED); |
| |
| CREATE_FLAG(flag_mtu_exchanged); |
| CREATE_FLAG(flag_sink_discovered); |
| CREATE_FLAG(flag_source_discovered); |
| CREATE_FLAG(flag_codec_cap_found); |
| CREATE_FLAG(flag_endpoint_found); |
| CREATE_FLAG(flag_stream_codec_configured); |
| static atomic_t flag_stream_qos_configured; |
| CREATE_FLAG(flag_stream_enabled); |
| CREATE_FLAG(flag_stream_metadata); |
| CREATE_FLAG(flag_stream_started); |
| CREATE_FLAG(flag_stream_connected); |
| CREATE_FLAG(flag_stream_disconnected); |
| CREATE_FLAG(flag_stream_disabled); |
| CREATE_FLAG(flag_stream_stopped); |
| CREATE_FLAG(flag_stream_released); |
| CREATE_FLAG(flag_operation_success); |
| |
| static void stream_configured(struct bt_bap_stream *stream, |
| const struct bt_audio_codec_qos_pref *pref) |
| { |
| printk("Configured stream %p\n", stream); |
| |
| /* TODO: The preference should be used/taken into account when |
| * setting the QoS |
| */ |
| |
| SET_FLAG(flag_stream_codec_configured); |
| } |
| |
| static void stream_qos_set(struct bt_bap_stream *stream) |
| { |
| struct audio_test_stream *test_stream = audio_test_stream_from_bap_stream(stream); |
| |
| printk("QoS set stream %p\n", stream); |
| |
| test_stream->tx_sdu_size = stream->qos->sdu; |
| |
| atomic_inc(&flag_stream_qos_configured); |
| } |
| |
| static void stream_enabled(struct bt_bap_stream *stream) |
| { |
| printk("Enabled stream %p\n", stream); |
| |
| SET_FLAG(flag_stream_enabled); |
| } |
| |
| static void stream_started(struct bt_bap_stream *stream) |
| { |
| printk("Started stream %p\n", stream); |
| |
| SET_FLAG(flag_stream_started); |
| } |
| |
| static void stream_connected(struct bt_bap_stream *stream) |
| { |
| printk("Connected stream %p\n", stream); |
| |
| SET_FLAG(flag_stream_connected); |
| } |
| |
| static void stream_disconnected(struct bt_bap_stream *stream, uint8_t reason) |
| { |
| printk("Disconnected stream %p with reason %u\n", stream, reason); |
| |
| SET_FLAG(flag_stream_disconnected); |
| } |
| |
| static void stream_metadata_updated(struct bt_bap_stream *stream) |
| { |
| printk("Metadata updated stream %p\n", stream); |
| |
| SET_FLAG(flag_stream_metadata); |
| } |
| |
| static void stream_disabled(struct bt_bap_stream *stream) |
| { |
| struct audio_test_stream *test_stream = audio_test_stream_from_bap_stream(stream); |
| |
| test_stream->tx_active = false; |
| |
| printk("Disabled stream %p\n", stream); |
| |
| SET_FLAG(flag_stream_disabled); |
| } |
| |
| static void stream_stopped(struct bt_bap_stream *stream, uint8_t reason) |
| { |
| printk("Stopped stream %p with reason 0x%02X\n", stream, reason); |
| |
| SET_FLAG(flag_stream_stopped); |
| } |
| |
| static void stream_released(struct bt_bap_stream *stream) |
| { |
| printk("Released stream %p\n", stream); |
| |
| SET_FLAG(flag_stream_released); |
| } |
| |
| static void stream_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("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++; |
| } else { |
| FAIL("Unexpected data received\n"); |
| } |
| } |
| |
| static void stream_sent_cb(struct bt_bap_stream *stream) |
| { |
| struct audio_test_stream *test_stream = audio_test_stream_from_bap_stream(stream); |
| struct net_buf *buf; |
| int ret; |
| |
| if (!test_stream->tx_active) { |
| return; |
| } |
| |
| buf = net_buf_alloc(&tx_pool, K_FOREVER); |
| if (buf == NULL) { |
| printk("Could not allocate buffer when sending on %p\n", stream); |
| return; |
| } |
| |
| net_buf_reserve(buf, BT_ISO_CHAN_SEND_RESERVE); |
| net_buf_add_mem(buf, mock_iso_data, test_stream->tx_sdu_size); |
| ret = bt_bap_stream_send(stream, buf, test_stream->seq_num++); |
| if (ret < 0) { |
| /* This will end broadcasting on this stream. */ |
| net_buf_unref(buf); |
| |
| /* Only fail if tx is active (may fail if we are disabling the stream) */ |
| if (test_stream->tx_active) { |
| FAIL("Unable to send data on %p: %d\n", stream, ret); |
| } |
| |
| return; |
| } |
| |
| test_stream->tx_cnt++; |
| } |
| |
| static struct bt_bap_stream_ops stream_ops = { |
| .configured = stream_configured, |
| .qos_set = stream_qos_set, |
| .enabled = stream_enabled, |
| .started = stream_started, |
| .metadata_updated = stream_metadata_updated, |
| .disabled = stream_disabled, |
| .stopped = stream_stopped, |
| .released = stream_released, |
| .recv = stream_recv_cb, |
| .sent = stream_sent_cb, |
| .connected = stream_connected, |
| .disconnected = stream_disconnected, |
| }; |
| |
| static void unicast_client_location_cb(struct bt_conn *conn, |
| enum bt_audio_dir dir, |
| enum bt_audio_location loc) |
| { |
| printk("dir %u loc %X\n", dir, loc); |
| } |
| |
| static void available_contexts_cb(struct bt_conn *conn, |
| enum bt_audio_context snk_ctx, |
| enum bt_audio_context src_ctx) |
| { |
| printk("snk ctx %u src ctx %u\n", snk_ctx, src_ctx); |
| } |
| |
| |
| static void config_cb(struct bt_bap_stream *stream, enum bt_bap_ascs_rsp_code rsp_code, |
| enum bt_bap_ascs_reason reason) |
| { |
| printk("stream %p config operation rsp_code %u reason %u\n", stream, rsp_code, reason); |
| |
| if (rsp_code == BT_BAP_ASCS_RSP_CODE_SUCCESS) { |
| SET_FLAG(flag_operation_success); |
| } |
| } |
| |
| static void qos_cb(struct bt_bap_stream *stream, enum bt_bap_ascs_rsp_code rsp_code, |
| enum bt_bap_ascs_reason reason) |
| { |
| printk("stream %p qos operation rsp_code %u reason %u\n", stream, rsp_code, reason); |
| |
| if (rsp_code == BT_BAP_ASCS_RSP_CODE_SUCCESS) { |
| SET_FLAG(flag_operation_success); |
| } |
| } |
| |
| static void enable_cb(struct bt_bap_stream *stream, enum bt_bap_ascs_rsp_code rsp_code, |
| enum bt_bap_ascs_reason reason) |
| { |
| printk("stream %p enable operation rsp_code %u reason %u\n", stream, rsp_code, reason); |
| |
| if (rsp_code == BT_BAP_ASCS_RSP_CODE_SUCCESS) { |
| SET_FLAG(flag_operation_success); |
| } |
| } |
| |
| static void start_cb(struct bt_bap_stream *stream, enum bt_bap_ascs_rsp_code rsp_code, |
| enum bt_bap_ascs_reason reason) |
| { |
| printk("stream %p start operation rsp_code %u reason %u\n", stream, rsp_code, reason); |
| |
| if (rsp_code == BT_BAP_ASCS_RSP_CODE_SUCCESS) { |
| SET_FLAG(flag_operation_success); |
| } |
| } |
| |
| static void stop_cb(struct bt_bap_stream *stream, enum bt_bap_ascs_rsp_code rsp_code, |
| enum bt_bap_ascs_reason reason) |
| { |
| printk("stream %p stop operation rsp_code %u reason %u\n", stream, rsp_code, reason); |
| |
| if (rsp_code == BT_BAP_ASCS_RSP_CODE_SUCCESS) { |
| SET_FLAG(flag_operation_success); |
| } |
| } |
| |
| static void disable_cb(struct bt_bap_stream *stream, enum bt_bap_ascs_rsp_code rsp_code, |
| enum bt_bap_ascs_reason reason) |
| { |
| printk("stream %p disable operation rsp_code %u reason %u\n", stream, rsp_code, reason); |
| |
| if (rsp_code == BT_BAP_ASCS_RSP_CODE_SUCCESS) { |
| SET_FLAG(flag_operation_success); |
| } |
| } |
| |
| static void metadata_cb(struct bt_bap_stream *stream, enum bt_bap_ascs_rsp_code rsp_code, |
| enum bt_bap_ascs_reason reason) |
| { |
| printk("stream %p metadata operation rsp_code %u reason %u\n", stream, rsp_code, reason); |
| |
| if (rsp_code == BT_BAP_ASCS_RSP_CODE_SUCCESS) { |
| SET_FLAG(flag_operation_success); |
| } |
| } |
| |
| static void release_cb(struct bt_bap_stream *stream, enum bt_bap_ascs_rsp_code rsp_code, |
| enum bt_bap_ascs_reason reason) |
| { |
| printk("stream %p release operation rsp_code %u reason %u\n", stream, rsp_code, reason); |
| |
| if (rsp_code == BT_BAP_ASCS_RSP_CODE_SUCCESS) { |
| SET_FLAG(flag_operation_success); |
| } |
| } |
| |
| static void add_remote_sink(struct bt_bap_ep *ep) |
| { |
| for (size_t i = 0U; i < ARRAY_SIZE(g_sinks); i++) { |
| if (g_sinks[i] == NULL) { |
| printk("Sink #%zu: ep %p\n", i, ep); |
| g_sinks[i] = ep; |
| return; |
| } |
| } |
| |
| FAIL("Could not add sink ep\n"); |
| } |
| |
| static void add_remote_source(struct bt_bap_ep *ep) |
| { |
| for (size_t i = 0U; i < ARRAY_SIZE(g_sources); i++) { |
| if (g_sources[i] == NULL) { |
| printk("Source #%u: ep %p\n", i, ep); |
| g_sources[i] = ep; |
| return; |
| } |
| } |
| |
| FAIL("Could not add source ep\n"); |
| } |
| |
| static void print_remote_codec_cap(const struct bt_audio_codec_cap *codec_cap, |
| enum bt_audio_dir dir) |
| { |
| printk("codec %p dir 0x%02x\n", codec_cap, dir); |
| |
| print_codec_cap(codec_cap); |
| } |
| |
| static void discover_sinks_cb(struct bt_conn *conn, int err, enum bt_audio_dir dir) |
| { |
| if (err != 0) { |
| FAIL("Discovery failed: %d\n", err); |
| return; |
| } |
| |
| printk("Discover complete\n"); |
| |
| SET_FLAG(flag_sink_discovered); |
| } |
| |
| static void discover_sources_cb(struct bt_conn *conn, int err, enum bt_audio_dir dir) |
| { |
| if (err != 0) { |
| FAIL("Discovery failed: %d\n", err); |
| return; |
| } |
| |
| printk("Sources discover complete\n"); |
| |
| SET_FLAG(flag_source_discovered); |
| } |
| |
| static void pac_record_cb(struct bt_conn *conn, enum bt_audio_dir dir, |
| const struct bt_audio_codec_cap *codec_cap) |
| { |
| print_remote_codec_cap(codec_cap, dir); |
| SET_FLAG(flag_codec_cap_found); |
| } |
| |
| static void endpoint_cb(struct bt_conn *conn, enum bt_audio_dir dir, struct bt_bap_ep *ep) |
| { |
| if (dir == BT_AUDIO_DIR_SINK) { |
| add_remote_sink(ep); |
| } else { |
| add_remote_source(ep); |
| } |
| |
| SET_FLAG(flag_endpoint_found); |
| } |
| |
| static struct bt_bap_unicast_client_cb unicast_client_cbs = { |
| .location = unicast_client_location_cb, |
| .available_contexts = available_contexts_cb, |
| .config = config_cb, |
| .qos = qos_cb, |
| .enable = enable_cb, |
| .start = start_cb, |
| .stop = stop_cb, |
| .disable = disable_cb, |
| .metadata = metadata_cb, |
| .release = release_cb, |
| .pac_record = pac_record_cb, |
| .endpoint = endpoint_cb, |
| }; |
| |
| static void att_mtu_updated(struct bt_conn *conn, uint16_t tx, uint16_t rx) |
| { |
| printk("MTU exchanged\n"); |
| SET_FLAG(flag_mtu_exchanged); |
| } |
| |
| static struct bt_gatt_cb gatt_callbacks = { |
| .att_mtu_updated = att_mtu_updated, |
| }; |
| |
| static bool parse_ascs_ad_data(struct bt_data *data, void *user_data) |
| { |
| const struct bt_le_scan_recv_info *info = user_data; |
| uint16_t available_source_context; |
| uint16_t available_sink_context; |
| struct net_buf_simple net_buf; |
| struct bt_uuid_16 adv_uuid; |
| uint8_t announcement_type; |
| void *uuid; |
| int err; |
| |
| const size_t min_data_len = BT_UUID_SIZE_16 + sizeof(announcement_type) + |
| sizeof(available_sink_context) + |
| sizeof(available_source_context); |
| |
| if (data->type != BT_DATA_SVC_DATA16) { |
| return true; |
| } |
| |
| if (data->data_len < min_data_len) { |
| |
| return true; |
| } |
| |
| net_buf_simple_init_with_data(&net_buf, (void *)data->data, data->data_len); |
| |
| uuid = net_buf_simple_pull_mem(&net_buf, BT_UUID_SIZE_16); |
| if (!bt_uuid_create(&adv_uuid.uuid, uuid, BT_UUID_SIZE_16)) { |
| return true; |
| } |
| |
| if (bt_uuid_cmp(&adv_uuid.uuid, BT_UUID_ASCS)) { |
| return true; |
| } |
| |
| announcement_type = net_buf_simple_pull_u8(&net_buf); |
| available_sink_context = net_buf_simple_pull_le16(&net_buf); |
| available_source_context = net_buf_simple_pull_le16(&net_buf); |
| |
| printk("Found ASCS with announcement type 0x%02X, sink ctx 0x%04X, source ctx 0x%04X\n", |
| announcement_type, available_sink_context, available_source_context); |
| |
| printk("Stopping scan\n"); |
| if (bt_le_scan_stop()) { |
| FAIL("Could not stop scan"); |
| return false; |
| } |
| |
| err = bt_conn_le_create(info->addr, BT_CONN_LE_CREATE_CONN, BT_LE_CONN_PARAM_DEFAULT, |
| &default_conn); |
| if (err) { |
| FAIL("Could not connect to peer: %d", err); |
| return false; |
| } |
| |
| /* Stop parsing */ |
| return false; |
| } |
| |
| static void broadcast_scan_recv(const struct bt_le_scan_recv_info *info, struct net_buf_simple *ad) |
| { |
| char addr_str[BT_ADDR_LE_STR_LEN]; |
| |
| if (default_conn) { |
| return; |
| } |
| |
| /* We're only interested in connectable events */ |
| if ((info->adv_props & BT_GAP_ADV_PROP_CONNECTABLE) == 0) { |
| return; |
| } |
| /* connect only to devices in close proximity */ |
| if (info->rssi < -70) { |
| return; |
| } |
| |
| bt_addr_le_to_str(info->addr, addr_str, sizeof(addr_str)); |
| printk("Device found: %s (RSSI %d)\n", addr_str, info->rssi); |
| |
| bt_data_parse(ad, parse_ascs_ad_data, (void *)info); |
| } |
| |
| static struct bt_le_scan_cb bap_scan_cb = { |
| .recv = broadcast_scan_recv, |
| }; |
| |
| static void init(void) |
| { |
| int err; |
| |
| err = bt_enable(NULL); |
| if (err != 0) { |
| FAIL("Bluetooth enable failed (err %d)\n", err); |
| return; |
| } |
| |
| for (size_t i = 0; i < ARRAY_SIZE(test_streams); i++) { |
| struct bt_bap_stream *bap_stream = |
| bap_stream_from_audio_test_stream(&test_streams[i]); |
| |
| bap_stream->ops = &stream_ops; |
| } |
| |
| bt_le_scan_cb_register(&bap_scan_cb); |
| bt_gatt_cb_register(&gatt_callbacks); |
| |
| err = bt_bap_unicast_client_register_cb(&unicast_client_cbs); |
| if (err != 0) { |
| FAIL("Failed to register client callbacks: %d", err); |
| return; |
| } |
| } |
| |
| static void scan_and_connect(void) |
| { |
| int err; |
| |
| err = bt_le_scan_start(BT_LE_SCAN_PASSIVE, NULL); |
| if (err != 0) { |
| FAIL("Scanning failed to start (err %d)\n", err); |
| return; |
| } |
| |
| printk("Scanning successfully started\n"); |
| WAIT_FOR_FLAG(flag_connected); |
| } |
| |
| static void disconnect_acl(void) |
| { |
| int err; |
| |
| err = bt_conn_disconnect(default_conn, BT_HCI_ERR_REMOTE_USER_TERM_CONN); |
| if (err != 0) { |
| FAIL("Failed to disconnect (err %d)\n", err); |
| return; |
| } |
| |
| WAIT_FOR_UNSET_FLAG(flag_connected); |
| } |
| |
| static void exchange_mtu(void) |
| { |
| WAIT_FOR_FLAG(flag_mtu_exchanged); |
| } |
| |
| static void discover_sinks(void) |
| { |
| int err; |
| |
| unicast_client_cbs.discover = discover_sinks_cb; |
| |
| UNSET_FLAG(flag_codec_cap_found); |
| UNSET_FLAG(flag_sink_discovered); |
| UNSET_FLAG(flag_endpoint_found); |
| |
| err = bt_bap_unicast_client_discover(default_conn, BT_AUDIO_DIR_SINK); |
| if (err != 0) { |
| printk("Failed to discover sink: %d\n", err); |
| return; |
| } |
| |
| memset(g_sinks, 0, sizeof(g_sinks)); |
| |
| WAIT_FOR_FLAG(flag_codec_cap_found); |
| WAIT_FOR_FLAG(flag_endpoint_found); |
| WAIT_FOR_FLAG(flag_sink_discovered); |
| } |
| |
| static void discover_sources(void) |
| { |
| int err; |
| |
| unicast_client_cbs.discover = discover_sources_cb; |
| |
| UNSET_FLAG(flag_codec_cap_found); |
| UNSET_FLAG(flag_source_discovered); |
| |
| err = bt_bap_unicast_client_discover(default_conn, BT_AUDIO_DIR_SOURCE); |
| if (err != 0) { |
| printk("Failed to discover sink: %d\n", err); |
| return; |
| } |
| |
| memset(g_sources, 0, sizeof(g_sources)); |
| |
| WAIT_FOR_FLAG(flag_codec_cap_found); |
| WAIT_FOR_FLAG(flag_source_discovered); |
| } |
| |
| static int codec_configure_stream(struct bt_bap_stream *stream, struct bt_bap_ep *ep) |
| { |
| int err; |
| |
| UNSET_FLAG(flag_stream_codec_configured); |
| UNSET_FLAG(flag_operation_success); |
| |
| do { |
| |
| err = bt_bap_stream_config(default_conn, stream, ep, &preset_16_2_1.codec_cfg); |
| if (err == -EBUSY) { |
| k_sleep(BAP_STREAM_RETRY_WAIT); |
| } else if (err != 0) { |
| FAIL("Could not configure stream %p: %d\n", stream, err); |
| return err; |
| } |
| } while (err == -EBUSY); |
| |
| WAIT_FOR_FLAG(flag_stream_codec_configured); |
| WAIT_FOR_FLAG(flag_operation_success); |
| |
| return 0; |
| } |
| |
| static void codec_configure_streams(size_t stream_cnt) |
| { |
| for (size_t i = 0U; i < ARRAY_SIZE(pair_params); i++) { |
| if (pair_params[i].rx_param != NULL && g_sources[i] != NULL) { |
| struct bt_bap_stream *stream = pair_params[i].rx_param->stream; |
| const int err = codec_configure_stream(stream, g_sources[i]); |
| |
| if (err != 0) { |
| FAIL("Unable to configure source stream[%zu]: %d", i, err); |
| return; |
| } |
| } |
| |
| if (pair_params[i].tx_param != NULL && g_sinks[i] != NULL) { |
| struct bt_bap_stream *stream = pair_params[i].tx_param->stream; |
| const int err = codec_configure_stream(stream, g_sinks[i]); |
| |
| if (err != 0) { |
| FAIL("Unable to configure sink stream[%zu]: %d", i, err); |
| return; |
| } |
| } |
| } |
| } |
| |
| static void qos_configure_streams(struct bt_bap_unicast_group *unicast_group, |
| size_t stream_cnt) |
| { |
| int err; |
| |
| UNSET_FLAG(flag_stream_qos_configured); |
| |
| do { |
| err = bt_bap_stream_qos(default_conn, unicast_group); |
| if (err == -EBUSY) { |
| k_sleep(BAP_STREAM_RETRY_WAIT); |
| } else if (err != 0) { |
| FAIL("Unable to QoS configure streams: %d\n", err); |
| return; |
| } |
| } while (err == -EBUSY); |
| |
| while (atomic_get(&flag_stream_qos_configured) != stream_cnt) { |
| (void)k_sleep(K_MSEC(1)); |
| } |
| } |
| |
| static int enable_stream(struct bt_bap_stream *stream) |
| { |
| int err; |
| |
| UNSET_FLAG(flag_stream_enabled); |
| |
| do { |
| err = bt_bap_stream_enable(stream, NULL, 0); |
| if (err == -EBUSY) { |
| k_sleep(BAP_STREAM_RETRY_WAIT); |
| } else if (err != 0) { |
| FAIL("Could not enable stream %p: %d\n", stream, err); |
| return err; |
| } |
| } while (err == -EBUSY); |
| |
| WAIT_FOR_FLAG(flag_stream_enabled); |
| |
| return 0; |
| } |
| |
| static void enable_streams(size_t stream_cnt) |
| { |
| for (size_t i = 0U; i < stream_cnt; i++) { |
| struct bt_bap_stream *stream = bap_stream_from_audio_test_stream(&test_streams[i]); |
| int err; |
| |
| err = enable_stream(stream); |
| if (err != 0) { |
| FAIL("Unable to enable stream[%zu]: %d", i, err); |
| |
| return; |
| } |
| } |
| } |
| |
| static int metadata_update_stream(struct bt_bap_stream *stream) |
| { |
| const uint8_t new_meta[] = { |
| BT_AUDIO_CODEC_DATA(BT_AUDIO_METADATA_TYPE_VENDOR, LONG_META), |
| }; |
| int err; |
| |
| UNSET_FLAG(flag_stream_metadata); |
| |
| do { |
| err = bt_bap_stream_metadata(stream, new_meta, ARRAY_SIZE(new_meta)); |
| if (err == -EBUSY) { |
| k_sleep(BAP_STREAM_RETRY_WAIT); |
| } else if (err != 0) { |
| FAIL("Could not metadata update stream %p: %d\n", stream, err); |
| return err; |
| } |
| } while (err == -EBUSY); |
| |
| WAIT_FOR_FLAG(flag_stream_metadata); |
| |
| return 0; |
| } |
| |
| static void metadata_update_streams(size_t stream_cnt) |
| { |
| for (size_t i = 0U; i < stream_cnt; i++) { |
| struct bt_bap_stream *stream = bap_stream_from_audio_test_stream(&test_streams[i]); |
| int err; |
| |
| err = metadata_update_stream(stream); |
| if (err != 0) { |
| FAIL("Unable to metadata update stream[%zu]: %d", i, err); |
| |
| return; |
| } |
| } |
| } |
| |
| static int start_stream(struct bt_bap_stream *stream) |
| { |
| int err; |
| |
| UNSET_FLAG(flag_stream_started); |
| |
| do { |
| err = bt_bap_stream_start(stream); |
| if (err == -EBUSY) { |
| k_sleep(BAP_STREAM_RETRY_WAIT); |
| } else if (err != 0) { |
| FAIL("Could not start stream %p: %d\n", stream, err); |
| return err; |
| } |
| } while (err == -EBUSY); |
| |
| WAIT_FOR_FLAG(flag_stream_started); |
| |
| return 0; |
| } |
| |
| static void start_streams(void) |
| { |
| struct bt_bap_stream *source_stream; |
| struct bt_bap_stream *sink_stream; |
| |
| /* We only support a single CIS so far, so only start one. We can use the group pair |
| * params to start both a sink and source stream that use the same CIS |
| */ |
| |
| source_stream = pair_params[0].rx_param == NULL ? NULL : pair_params[0].rx_param->stream; |
| sink_stream = pair_params[0].tx_param == NULL ? NULL : pair_params[0].tx_param->stream; |
| |
| UNSET_FLAG(flag_stream_connected); |
| |
| if (sink_stream != NULL) { |
| const int err = start_stream(sink_stream); |
| |
| if (err != 0) { |
| FAIL("Unable to start sink: %d", err); |
| |
| return; |
| } |
| } |
| |
| if (source_stream != NULL) { |
| const int err = start_stream(source_stream); |
| |
| if (err != 0) { |
| FAIL("Unable to start source stream: %d", err); |
| |
| return; |
| } |
| } |
| |
| WAIT_FOR_FLAG(flag_stream_connected); |
| } |
| |
| static void transceive_streams(void) |
| { |
| struct bt_bap_stream *source_stream; |
| struct bt_bap_stream *sink_stream; |
| |
| source_stream = pair_params[0].rx_param == NULL ? NULL : pair_params[0].rx_param->stream; |
| sink_stream = pair_params[0].tx_param == NULL ? NULL : pair_params[0].tx_param->stream; |
| |
| if (sink_stream != NULL) { |
| struct audio_test_stream *test_stream = |
| audio_test_stream_from_bap_stream(sink_stream); |
| |
| test_stream->tx_active = true; |
| for (unsigned int i = 0U; i < ENQUEUE_COUNT; i++) { |
| stream_sent_cb(sink_stream); |
| } |
| |
| /* Keep sending until we reach the minimum expected */ |
| while (test_stream->tx_cnt < MIN_SEND_COUNT) { |
| k_sleep(K_MSEC(100)); |
| } |
| } |
| |
| if (source_stream != NULL) { |
| const struct audio_test_stream *test_stream = |
| audio_test_stream_from_bap_stream(source_stream); |
| |
| /* Keep receiving until we reach the minimum expected */ |
| while (test_stream->rx_cnt < MIN_SEND_COUNT) { |
| k_sleep(K_MSEC(100)); |
| } |
| } |
| } |
| |
| static void disable_streams(size_t stream_cnt) |
| { |
| for (size_t i = 0; i < stream_cnt; i++) { |
| int err; |
| |
| UNSET_FLAG(flag_operation_success); |
| UNSET_FLAG(flag_stream_disabled); |
| |
| do { |
| err = bt_bap_stream_disable( |
| bap_stream_from_audio_test_stream(&test_streams[i])); |
| if (err == -EBUSY) { |
| k_sleep(BAP_STREAM_RETRY_WAIT); |
| } else if (err != 0) { |
| FAIL("Could not disable stream: %d\n", err); |
| return; |
| } |
| } while (err == -EBUSY); |
| |
| WAIT_FOR_FLAG(flag_operation_success); |
| WAIT_FOR_FLAG(flag_stream_disabled); |
| } |
| } |
| |
| static void stop_streams(size_t stream_cnt) |
| { |
| UNSET_FLAG(flag_stream_disconnected); |
| |
| for (size_t i = 0; i < stream_cnt; i++) { |
| struct bt_bap_stream *source_stream; |
| int err; |
| |
| /* We can only stop source streams */ |
| source_stream = |
| pair_params[i].rx_param == NULL ? NULL : pair_params[i].rx_param->stream; |
| |
| if (source_stream == NULL) { |
| continue; |
| } |
| |
| UNSET_FLAG(flag_operation_success); |
| UNSET_FLAG(flag_stream_stopped); |
| |
| do { |
| err = bt_bap_stream_stop(source_stream); |
| if (err == -EBUSY) { |
| k_sleep(BAP_STREAM_RETRY_WAIT); |
| } else if (err != 0) { |
| FAIL("Could not stop stream: %d\n", err); |
| return; |
| } |
| } while (err == -EBUSY); |
| |
| WAIT_FOR_FLAG(flag_operation_success); |
| WAIT_FOR_FLAG(flag_stream_stopped); |
| } |
| |
| WAIT_FOR_FLAG(flag_stream_disconnected); |
| } |
| |
| static void release_streams(size_t stream_cnt) |
| { |
| for (size_t i = 0; i < stream_cnt; i++) { |
| int err; |
| |
| UNSET_FLAG(flag_operation_success); |
| UNSET_FLAG(flag_stream_released); |
| |
| do { |
| err = bt_bap_stream_release( |
| bap_stream_from_audio_test_stream(&test_streams[i])); |
| if (err == -EBUSY) { |
| k_sleep(BAP_STREAM_RETRY_WAIT); |
| } else if (err != 0) { |
| FAIL("Could not release stream: %d\n", err); |
| return; |
| } |
| } while (err == -EBUSY); |
| |
| WAIT_FOR_FLAG(flag_operation_success); |
| WAIT_FOR_FLAG(flag_stream_released); |
| } |
| } |
| |
| static size_t create_unicast_group(struct bt_bap_unicast_group **unicast_group) |
| { |
| struct bt_bap_unicast_group_param param; |
| size_t stream_cnt = 0; |
| size_t pair_cnt = 0; |
| int err; |
| |
| memset(stream_params, 0, sizeof(stream_params)); |
| memset(pair_params, 0, sizeof(pair_params)); |
| |
| for (size_t i = 0U; i < MIN(ARRAY_SIZE(g_sinks), ARRAY_SIZE(test_streams)); i++) { |
| if (g_sinks[i] == NULL) { |
| break; |
| } |
| |
| stream_params[stream_cnt].stream = |
| bap_stream_from_audio_test_stream(&test_streams[stream_cnt]); |
| stream_params[stream_cnt].qos = &preset_16_2_1.qos; |
| pair_params[i].tx_param = &stream_params[stream_cnt]; |
| |
| stream_cnt++; |
| |
| break; |
| } |
| |
| for (size_t i = 0U; i < MIN(ARRAY_SIZE(g_sources), ARRAY_SIZE(test_streams)); i++) { |
| if (g_sources[i] == NULL) { |
| break; |
| } |
| |
| stream_params[stream_cnt].stream = |
| bap_stream_from_audio_test_stream(&test_streams[stream_cnt]); |
| stream_params[stream_cnt].qos = &preset_16_2_1.qos; |
| pair_params[i].rx_param = &stream_params[stream_cnt]; |
| |
| stream_cnt++; |
| |
| break; |
| } |
| |
| for (pair_cnt = 0U; pair_cnt < ARRAY_SIZE(pair_params); pair_cnt++) { |
| if (pair_params[pair_cnt].rx_param == NULL && |
| pair_params[pair_cnt].tx_param == NULL) { |
| break; |
| } |
| } |
| |
| if (stream_cnt == 0U) { |
| FAIL("No streams added to group"); |
| |
| return 0; |
| } |
| |
| param.params = pair_params; |
| param.params_count = pair_cnt; |
| param.packing = BT_ISO_PACKING_SEQUENTIAL; |
| |
| /* Require controller support for CIGs */ |
| err = bt_bap_unicast_group_create(¶m, unicast_group); |
| if (err != 0) { |
| FAIL("Unable to create unicast group: %d", err); |
| |
| return 0; |
| } |
| |
| return stream_cnt; |
| } |
| |
| static void delete_unicast_group(struct bt_bap_unicast_group *unicast_group) |
| { |
| int err; |
| |
| /* Require controller support for CIGs */ |
| err = bt_bap_unicast_group_delete(unicast_group); |
| if (err != 0) { |
| FAIL("Unable to delete unicast group: %d", err); |
| return; |
| } |
| } |
| |
| static void test_main(void) |
| { |
| /* TODO: Temporarily reduce to 1 due to bug in controller. Set to > 1 value again when |
| * https://github.com/zephyrproject-rtos/zephyr/issues/57904 has been resolved. |
| */ |
| const unsigned int iterations = 1; |
| |
| init(); |
| |
| scan_and_connect(); |
| |
| exchange_mtu(); |
| |
| discover_sinks(); |
| discover_sinks(); /* test that we can discover twice */ |
| |
| discover_sources(); |
| discover_sources(); /* test that we can discover twice */ |
| |
| /* Run the stream setup multiple time to ensure states are properly |
| * set and reset |
| */ |
| for (unsigned int i = 0U; i < iterations; i++) { |
| struct bt_bap_unicast_group *unicast_group; |
| size_t stream_cnt; |
| |
| printk("\n########### Running iteration #%u\n\n", i); |
| |
| printk("Creating unicast group\n"); |
| stream_cnt = create_unicast_group(&unicast_group); |
| |
| printk("Codec configuring streams\n"); |
| codec_configure_streams(stream_cnt); |
| |
| printk("QoS configuring streams\n"); |
| qos_configure_streams(unicast_group, stream_cnt); |
| |
| printk("Enabling streams\n"); |
| enable_streams(stream_cnt); |
| |
| printk("Metadata update streams\n"); |
| metadata_update_streams(stream_cnt); |
| |
| printk("Starting streams\n"); |
| start_streams(); |
| |
| printk("Starting transceiving\n"); |
| transceive_streams(); |
| |
| printk("Disabling streams\n"); |
| disable_streams(stream_cnt); |
| |
| printk("Stopping streams\n"); |
| stop_streams(stream_cnt); |
| |
| printk("Releasing streams\n"); |
| release_streams(stream_cnt); |
| |
| /* Test removing streams from group after creation */ |
| printk("Deleting unicast group\n"); |
| delete_unicast_group(unicast_group); |
| unicast_group = NULL; |
| } |
| |
| disconnect_acl(); |
| |
| PASS("Unicast client passed\n"); |
| } |
| |
| static void test_main_acl_disconnect(void) |
| { |
| struct bt_bap_unicast_group *unicast_group; |
| size_t stream_cnt; |
| |
| init(); |
| |
| stream_ops.recv = NULL; /* We do not care about data in this test */ |
| |
| scan_and_connect(); |
| |
| exchange_mtu(); |
| |
| discover_sinks(); |
| |
| discover_sources(); |
| |
| printk("Creating unicast group\n"); |
| stream_cnt = create_unicast_group(&unicast_group); |
| |
| printk("Codec configuring streams\n"); |
| codec_configure_streams(stream_cnt); |
| |
| printk("QoS configuring streams\n"); |
| qos_configure_streams(unicast_group, stream_cnt); |
| |
| printk("Enabling streams\n"); |
| enable_streams(stream_cnt); |
| |
| printk("Metadata update streams\n"); |
| metadata_update_streams(stream_cnt); |
| |
| printk("Starting streams\n"); |
| start_streams(); |
| |
| disconnect_acl(); |
| |
| printk("Deleting unicast group\n"); |
| delete_unicast_group(unicast_group); |
| unicast_group = NULL; |
| |
| /* Reconnect */ |
| scan_and_connect(); |
| |
| disconnect_acl(); |
| |
| PASS("Unicast client ACL disconnect passed\n"); |
| } |
| |
| static const struct bst_test_instance test_unicast_client[] = { |
| { |
| .test_id = "unicast_client", |
| .test_post_init_f = test_init, |
| .test_tick_f = test_tick, |
| .test_main_f = test_main, |
| }, |
| { |
| .test_id = "unicast_client_acl_disconnect", |
| .test_post_init_f = test_init, |
| .test_tick_f = test_tick, |
| .test_main_f = test_main_acl_disconnect, |
| }, |
| BSTEST_END_MARKER, |
| }; |
| |
| struct bst_test_list *test_unicast_client_install(struct bst_test_list *tests) |
| { |
| return bst_add_tests(tests, test_unicast_client); |
| } |
| |
| #else /* !(CONFIG_BT_BAP_UNICAST_CLIENT) */ |
| |
| struct bst_test_list *test_unicast_client_install(struct bst_test_list *tests) |
| { |
| return tests; |
| } |
| |
| #endif /* CONFIG_BT_BAP_UNICAST_CLIENT */ |