1 /*
2 * Copyright (c) 2024 Nordic Semiconductor ASA
3 *
4 * SPDX-License-Identifier: Apache-2.0
5 */
6
7 #include <autoconf.h>
8 #include <errno.h>
9 #include <stdbool.h>
10 #include <stddef.h>
11 #include <stdint.h>
12
13 #include <zephyr/bluetooth/audio/cap.h>
14 #include <zephyr/bluetooth/audio/bap.h>
15 #include <zephyr/bluetooth/bluetooth.h>
16 #include <zephyr/bluetooth/hci_types.h>
17 #include <zephyr/bluetooth/iso.h>
18 #include <zephyr/kernel.h>
19 #include <zephyr/kernel/thread_stack.h>
20 #include <zephyr/logging/log.h>
21 #include <zephyr/logging/log_core.h>
22 #include <zephyr/net_buf.h>
23 #include <zephyr/sys/byteorder.h>
24 #include <zephyr/sys/util.h>
25 #include <zephyr/sys/util_macro.h>
26 #include <zephyr/types.h>
27
28 #include "stream_lc3.h"
29 #include "stream_tx.h"
30
31 LOG_MODULE_REGISTER(stream_tx, LOG_LEVEL_INF);
32
33 static struct tx_stream tx_streams[CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SRC_COUNT];
34
stream_is_streaming(const struct bt_bap_stream * bap_stream)35 static bool stream_is_streaming(const struct bt_bap_stream *bap_stream)
36 {
37 struct bt_bap_ep_info ep_info;
38 int err;
39
40 if (bap_stream == NULL) {
41 return false;
42 }
43
44 /* No-op if stream is not configured */
45 if (bap_stream->ep == NULL) {
46 return false;
47 }
48
49 err = bt_bap_ep_get_info(bap_stream->ep, &ep_info);
50 if (err != 0) {
51 return false;
52 }
53
54 return ep_info.state == BT_BAP_EP_STATE_STREAMING;
55 }
56
tx_thread_func(void * arg1,void * arg2,void * arg3)57 static void tx_thread_func(void *arg1, void *arg2, void *arg3)
58 {
59 NET_BUF_POOL_FIXED_DEFINE(tx_pool, CONFIG_BT_ISO_TX_BUF_COUNT,
60 BT_ISO_SDU_BUF_SIZE(CONFIG_BT_ISO_TX_MTU),
61 CONFIG_BT_CONN_TX_USER_DATA_SIZE, NULL);
62 static uint8_t mock_data[CONFIG_BT_ISO_TX_MTU];
63
64 for (size_t i = 0U; i < ARRAY_SIZE(mock_data); i++) {
65 mock_data[i] = (uint8_t)i;
66 }
67
68 /* This loop will attempt to send on all streams in the streaming state in a round robin
69 * fashion.
70 * The TX is controlled by the number of buffers configured, and increasing
71 * CONFIG_BT_ISO_TX_BUF_COUNT will allow for more streams in parallel, or to submit more
72 * buffers per stream.
73 * Once a buffer has been freed by the stack, it triggers the next TX.
74 */
75 while (true) {
76 int err = -ENOEXEC;
77
78 for (size_t i = 0U; i < ARRAY_SIZE(tx_streams); i++) {
79 struct bt_bap_stream *bap_stream = tx_streams[i].bap_stream;
80
81 if (stream_is_streaming(bap_stream)) {
82 struct net_buf *buf;
83
84 buf = net_buf_alloc(&tx_pool, K_FOREVER);
85 net_buf_reserve(buf, BT_ISO_CHAN_SEND_RESERVE);
86
87 if (IS_ENABLED(CONFIG_LIBLC3) &&
88 bap_stream->codec_cfg->id == BT_HCI_CODING_FORMAT_LC3) {
89 stream_lc3_add_data(&tx_streams[i], buf);
90 } else {
91 net_buf_add_mem(buf, mock_data, bap_stream->qos->sdu);
92 }
93
94 err = bt_bap_stream_send(bap_stream, buf, tx_streams[i].seq_num);
95 if (err == 0) {
96 tx_streams[i].seq_num++;
97 } else {
98 LOG_ERR("Unable to send: %d", err);
99 net_buf_unref(buf);
100 }
101 } /* No-op if stream is not streaming */
102 }
103
104 if (err != 0) {
105 /* In case of any errors, retry with a delay */
106 k_sleep(K_MSEC(10));
107 }
108 }
109 }
110
stream_tx_register(struct bt_bap_stream * bap_stream)111 int stream_tx_register(struct bt_bap_stream *bap_stream)
112 {
113 if (bap_stream == NULL) {
114 return -EINVAL;
115 }
116
117 for (size_t i = 0U; i < ARRAY_SIZE(tx_streams); i++) {
118 if (tx_streams[i].bap_stream == NULL) {
119 tx_streams[i].bap_stream = bap_stream;
120 tx_streams[i].seq_num = 0U;
121
122 if (IS_ENABLED(CONFIG_LIBLC3) &&
123 bap_stream->codec_cfg->id == BT_HCI_CODING_FORMAT_LC3) {
124 const int err = stream_lc3_init(&tx_streams[i]);
125
126 if (err != 0) {
127 tx_streams[i].bap_stream = NULL;
128
129 return err;
130 }
131 }
132
133 LOG_INF("Registered %p for TX", bap_stream);
134
135 return 0;
136 }
137 }
138
139 return -ENOMEM;
140 }
141
stream_tx_unregister(struct bt_bap_stream * bap_stream)142 int stream_tx_unregister(struct bt_bap_stream *bap_stream)
143 {
144 if (bap_stream == NULL) {
145 return -EINVAL;
146 }
147
148 for (size_t i = 0U; i < ARRAY_SIZE(tx_streams); i++) {
149 if (tx_streams[i].bap_stream == bap_stream) {
150 tx_streams[i].bap_stream = NULL;
151
152 LOG_INF("Unregistered %p for TX", bap_stream);
153
154 return 0;
155 }
156 }
157
158 return -ENODATA;
159 }
160
stream_tx_init(void)161 void stream_tx_init(void)
162 {
163 static bool thread_started;
164
165 if (!thread_started) {
166 static K_KERNEL_STACK_DEFINE(tx_thread_stack,
167 IS_ENABLED(CONFIG_LIBLC3) ? 4096U : 1024U);
168 const int tx_thread_prio = K_PRIO_PREEMPT(5);
169 static struct k_thread tx_thread;
170
171 k_thread_create(&tx_thread, tx_thread_stack, K_KERNEL_STACK_SIZEOF(tx_thread_stack),
172 tx_thread_func, NULL, NULL, NULL, tx_thread_prio, 0, K_NO_WAIT);
173 k_thread_name_set(&tx_thread, "TX thread");
174 thread_started = true;
175 }
176 }
177