diff --git a/samples/bluetooth/bap_unicast_client/CMakeLists.txt b/samples/bluetooth/bap_unicast_client/CMakeLists.txt index 4d9f15c0a60..125a189a61b 100644 --- a/samples/bluetooth/bap_unicast_client/CMakeLists.txt +++ b/samples/bluetooth/bap_unicast_client/CMakeLists.txt @@ -8,4 +8,7 @@ target_sources(app PRIVATE src/main.c ) +zephyr_sources_ifdef(CONFIG_LIBLC3 src/stream_lc3.c) +zephyr_sources_ifdef(CONFIG_BT_AUDIO_TX src/stream_tx.c) + zephyr_library_include_directories(${ZEPHYR_BASE}/samples/bluetooth) diff --git a/samples/bluetooth/bap_unicast_client/src/main.c b/samples/bluetooth/bap_unicast_client/src/main.c index 96b36094b6c..cc8e5d7d6d8 100644 --- a/samples/bluetooth/bap_unicast_client/src/main.c +++ b/samples/bluetooth/bap_unicast_client/src/main.c @@ -4,18 +4,21 @@ * SPDX-License-Identifier: Apache-2.0 */ -#include #include #include -#include -#include -#include -#include #include #include #include +#include +#include +#include #include +#include +#include +#include + +#include "stream_tx.h" static void start_scan(void); @@ -23,16 +26,12 @@ uint64_t unicast_audio_recv_ctr; /* This value is exposed to test code */ static struct bt_bap_unicast_client_cb unicast_client_cbs; static struct bt_conn *default_conn; -static struct k_work_delayable audio_send_work; static struct bt_bap_unicast_group *unicast_group; static struct audio_sink { struct bt_bap_ep *ep; uint16_t seq_num; } sinks[CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SNK_COUNT]; static struct bt_bap_ep *sources[CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SRC_COUNT]; -NET_BUF_POOL_FIXED_DEFINE(tx_pool, CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SNK_COUNT, - BT_ISO_SDU_BUF_SIZE(CONFIG_BT_ISO_TX_MTU), - CONFIG_BT_CONN_TX_USER_DATA_SIZE, NULL); static struct bt_bap_stream streams[CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SNK_COUNT + CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SRC_COUNT]; @@ -61,302 +60,6 @@ static K_SEM_DEFINE(sem_stream_enabled, 0, 1); static K_SEM_DEFINE(sem_stream_started, 0, 1); static K_SEM_DEFINE(sem_stream_connected, 0, 1); -#define AUDIO_DATA_TIMEOUT_US 1000000UL /* Send data every 1 second */ - -static uint16_t get_and_incr_seq_num(const struct bt_bap_stream *stream) -{ - for (size_t i = 0U; i < configured_sink_stream_count; i++) { - if (stream->ep == sinks[i].ep) { - uint16_t seq_num; - - seq_num = sinks[i].seq_num; - - if (IS_ENABLED(CONFIG_LIBLC3)) { - sinks[i].seq_num++; - } else { - sinks[i].seq_num += (AUDIO_DATA_TIMEOUT_US / - codec_configuration.qos.interval); - } - - return seq_num; - } - } - - printk("Could not find endpoint from stream %p\n", stream); - - return 0; -} - -#if defined(CONFIG_LIBLC3) - -#include "lc3.h" -#include "math.h" - -#define MAX_SAMPLE_RATE 48000 -#define MAX_FRAME_DURATION_US 10000 -#define MAX_NUM_SAMPLES ((MAX_FRAME_DURATION_US * MAX_SAMPLE_RATE) / USEC_PER_SEC) -#define AUDIO_VOLUME (INT16_MAX - 3000) /* codec does clipping above INT16_MAX - 3000 */ -#define AUDIO_TONE_FREQUENCY_HZ 400 - -static int16_t audio_buf[MAX_NUM_SAMPLES]; -static lc3_encoder_t lc3_encoder; -static lc3_encoder_mem_48k_t lc3_encoder_mem; -static int freq_hz; -static int frame_duration_us; -static int frame_duration_100us; -static int frames_per_sdu; -static int octets_per_frame; - -/** - * Use the math lib to generate a sine-wave using 16 bit samples into a buffer. - * - * @param buf Destination buffer - * @param length_us Length of the buffer in microseconds - * @param frequency_hz frequency in Hz - * @param sample_rate_hz sample-rate in Hz. - */ -static void fill_audio_buf_sin(int16_t *buf, int length_us, int frequency_hz, int sample_rate_hz) -{ - const int sine_period_samples = sample_rate_hz / frequency_hz; - const unsigned int num_samples = (length_us * sample_rate_hz) / USEC_PER_SEC; - const float step = 2 * 3.1415f / sine_period_samples; - - for (unsigned int i = 0; i < num_samples; i++) { - const float sample = sinf(i * step); - - buf[i] = (int16_t)(AUDIO_VOLUME * sample); - } -} - -static void lc3_audio_timer_timeout(struct k_work *work) -{ - /* For the first call-back we push multiple audio frames to the buffer to use the - * controller ISO buffer to handle jitter. - */ - const uint8_t prime_count = 2; - static int64_t start_time; - static int32_t sdu_cnt; - int32_t sdu_goal_cnt; - int64_t uptime, run_time_ms, run_time_100us; - - k_work_schedule(&audio_send_work, K_USEC(codec_configuration.qos.interval)); - - if (lc3_encoder == NULL) { - printk("LC3 encoder not setup, cannot encode data.\n"); - return; - } - - if (start_time == 0) { - /* Read start time and produce the number of frames needed to catch up with any - * inaccuracies in the timer. by calculating the number of frames we should - * have sent and compare to how many were actually sent. - */ - start_time = k_uptime_get(); - } - - uptime = k_uptime_get(); - run_time_ms = uptime - start_time; - - /* PDU count calculations done in 100us units to allow 7.5ms framelength in fixed-point */ - run_time_100us = run_time_ms * 10; - sdu_goal_cnt = run_time_100us / (frame_duration_100us * frames_per_sdu); - - /* Add primer value to ensure the controller do not run low on data due to jitter */ - sdu_goal_cnt += prime_count; - - printk("LC3 encode %d frames in %d SDUs\n", (sdu_goal_cnt - sdu_cnt) * frames_per_sdu, - (sdu_goal_cnt - sdu_cnt)); - - while (sdu_cnt < sdu_goal_cnt) { - const uint16_t tx_sdu_len = frames_per_sdu * octets_per_frame; - struct net_buf *buf; - uint8_t *net_buffer; - off_t offset = 0; - - buf = net_buf_alloc(&tx_pool, K_FOREVER); - net_buf_reserve(buf, BT_ISO_CHAN_SEND_RESERVE); - - net_buffer = net_buf_tail(buf); - buf->len += tx_sdu_len; - - for (int i = 0; i < frames_per_sdu; i++) { - int lc3_ret; - - lc3_ret = lc3_encode(lc3_encoder, LC3_PCM_FORMAT_S16, - audio_buf, 1, octets_per_frame, - net_buffer + offset); - offset += octets_per_frame; - - if (lc3_ret == -1) { - printk("LC3 encoder failed - wrong parameters?: %d", - lc3_ret); - net_buf_unref(buf); - return; - } - } - - for (size_t i = 0U; i < configured_sink_stream_count; i++) { - struct bt_bap_stream *stream = &streams[i]; - struct net_buf *buf_to_send; - int ret; - - /* Clone the buffer if sending on more than 1 stream */ - if (i == configured_sink_stream_count - 1) { - buf_to_send = buf; - } else { - buf_to_send = net_buf_clone(buf, K_FOREVER); - } - - ret = bt_bap_stream_send(stream, buf_to_send, get_and_incr_seq_num(stream)); - if (ret < 0) { - printk(" Failed to send LC3 audio data on streams[%zu] (%d)\n", - i, ret); - net_buf_unref(buf_to_send); - } else { - printk(" TX LC3 l on streams[%zu]: %zu\n", - tx_sdu_len, i); - sdu_cnt++; - } - } - } -} - -static int init_lc3(void) -{ - const struct bt_audio_codec_cfg *codec_cfg = &codec_configuration.codec_cfg; - unsigned int num_samples; - int ret; - - ret = bt_audio_codec_cfg_get_freq(codec_cfg); - if (ret > 0) { - freq_hz = bt_audio_codec_cfg_freq_to_freq_hz(ret); - } else { - return ret; - } - - ret = bt_audio_codec_cfg_get_frame_dur(codec_cfg); - if (ret > 0) { - frame_duration_us = bt_audio_codec_cfg_frame_dur_to_frame_dur_us(ret); - } - - octets_per_frame = bt_audio_codec_cfg_get_octets_per_frame(codec_cfg); - frames_per_sdu = bt_audio_codec_cfg_get_frame_blocks_per_sdu(codec_cfg, true); - octets_per_frame = bt_audio_codec_cfg_get_octets_per_frame(codec_cfg); - - if (freq_hz < 0) { - printk("Error: Codec frequency not set, cannot start codec."); - return -1; - } - - if (frame_duration_us < 0) { - printk("Error: Frame duration not set, cannot start codec."); - return -1; - } - - if (octets_per_frame < 0) { - printk("Error: Octets per frame not set, cannot start codec."); - return -1; - } - - frame_duration_100us = frame_duration_us / 100; - - - /* Fill audio buffer with Sine wave only once and repeat encoding the same tone frame */ - fill_audio_buf_sin(audio_buf, frame_duration_us, AUDIO_TONE_FREQUENCY_HZ, freq_hz); - - num_samples = ((frame_duration_us * freq_hz) / USEC_PER_SEC); - for (unsigned int i = 0; i < num_samples; i++) { - printk("%3i: %6i\n", i, audio_buf[i]); - } - - /* Create the encoder instance. This shall complete before stream_started() is called. */ - lc3_encoder = lc3_setup_encoder(frame_duration_us, - freq_hz, - 0, /* No resampling */ - &lc3_encoder_mem); - - if (lc3_encoder == NULL) { - printk("ERROR: Failed to setup LC3 encoder - wrong parameters?\n"); - return -1; - } - return 0; -} - -#else - -#define init_lc3(...) 0 - -/** - * @brief Send audio data on timeout - * - * This will send an increasing amount of audio data, starting from 1 octet. - * The data is just mock data, and does not actually represent any audio. - * - * First iteration : 0x00 - * Second iteration: 0x00 0x01 - * Third iteration : 0x00 0x01 0x02 - * - * And so on, until it wraps around the configured MTU (CONFIG_BT_ISO_TX_MTU) - * - * @param work Pointer to the work structure - */ -static void audio_timer_timeout(struct k_work *work) -{ - static uint8_t buf_data[CONFIG_BT_ISO_TX_MTU]; - static bool data_initialized; - struct net_buf *buf; - static size_t len_to_send = 1; - - if (!data_initialized) { - /* TODO: Actually encode some audio data */ - for (int i = 0; i < ARRAY_SIZE(buf_data); i++) { - buf_data[i] = (uint8_t)i; - } - - data_initialized = true; - } - - buf = net_buf_alloc(&tx_pool, K_FOREVER); - net_buf_reserve(buf, BT_ISO_CHAN_SEND_RESERVE); - net_buf_add_mem(buf, buf_data, len_to_send); - - /* We configured the sink streams to be first in `streams`, so that - * we can use `stream[i]` to select sink streams (i.e. streams with - * data going to the server) - */ - for (size_t i = 0U; i < configured_sink_stream_count; i++) { - struct bt_bap_stream *stream = &streams[i]; - struct net_buf *buf_to_send; - int ret; - - /* Clone the buffer if sending on more than 1 stream */ - if (i == configured_sink_stream_count - 1) { - buf_to_send = buf; - } else { - buf_to_send = net_buf_clone(buf, K_FOREVER); - } - - ret = bt_bap_stream_send(stream, buf_to_send, get_and_incr_seq_num(stream)); - if (ret < 0) { - printk("Failed to send audio data on streams[%zu]: (%d)\n", - i, ret); - net_buf_unref(buf_to_send); - } else { - printk("Sending mock data with len %zu on streams[%zu]\n", - len_to_send, i); - } - } - - k_work_schedule(&audio_send_work, K_USEC(AUDIO_DATA_TIMEOUT_US)); - - len_to_send++; - if (len_to_send > codec_configuration.qos.sdu) { - len_to_send = 1; - } -} - -#endif - static void print_hex(const uint8_t *ptr, size_t len) { while (len-- != 0) { @@ -519,6 +222,23 @@ static void stream_enabled(struct bt_bap_stream *stream) k_sem_give(&sem_stream_enabled); } +static bool stream_is_tx(const struct bt_bap_stream *stream) +{ + struct bt_bap_ep_info info; + int err; + + if (stream == NULL || stream->ep == NULL) { + return false; + } + + err = bt_bap_ep_get_info(stream->ep, &info); + if (err != 0) { + return false; + } + + return info.can_send; +} + static void stream_connected_cb(struct bt_bap_stream *stream) { printk("Audio Stream %p connected\n", stream); @@ -537,6 +257,14 @@ static void stream_connected_cb(struct bt_bap_stream *stream) static void stream_started(struct bt_bap_stream *stream) { printk("Audio Stream %p started\n", stream); + /* Register the stream for TX if it can send */ + if (IS_ENABLED(CONFIG_BT_AUDIO_TX) && stream_is_tx(stream)) { + const int err = stream_tx_register(stream); + + if (err != 0) { + printk("Failed to register stream %p for TX: %d\n", stream, err); + } + } k_sem_give(&sem_stream_started); } @@ -555,8 +283,14 @@ static void stream_stopped(struct bt_bap_stream *stream, uint8_t reason) { printk("Audio Stream %p stopped with reason 0x%02X\n", stream, reason); - /* Stop send timer */ - k_work_cancel_delayable(&audio_send_work); + /* Unregister the stream for TX if it can send */ + if (IS_ENABLED(CONFIG_BT_AUDIO_TX) && stream_is_tx(stream)) { + const int err = stream_tx_unregister(stream); + + if (err != 0) { + printk("Failed to unregister stream %p for TX: %d", stream, err); + } + } } static void stream_released(struct bt_bap_stream *stream) @@ -774,11 +508,9 @@ static int init(void) bt_gatt_cb_register(&gatt_callbacks); -#if defined(CONFIG_LIBLC3) - k_work_init_delayable(&audio_send_work, lc3_audio_timer_timeout); -#else - k_work_init_delayable(&audio_send_work, audio_timer_timeout); -#endif + if (IS_ENABLED(CONFIG_BT_AUDIO_TX)) { + stream_tx_init(); + } return 0; } @@ -999,14 +731,6 @@ static int set_stream_qos(void) static int enable_streams(void) { - if (IS_ENABLED(CONFIG_LIBLC3)) { - int err = init_lc3(); - - if (err != 0) { - return err; - } - } - for (size_t i = 0U; i < configured_stream_count; i++) { int err; @@ -1200,11 +924,6 @@ int main(void) } printk("Streams started\n"); - if (CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SRC_COUNT > 0) { - /* Start send timer */ - k_work_schedule(&audio_send_work, K_MSEC(0)); - } - /* Wait for disconnect */ err = k_sem_take(&sem_disconnected, K_FOREVER); if (err != 0) { diff --git a/samples/bluetooth/bap_unicast_client/src/stream_lc3.c b/samples/bluetooth/bap_unicast_client/src/stream_lc3.c new file mode 100644 index 00000000000..966df146aac --- /dev/null +++ b/samples/bluetooth/bap_unicast_client/src/stream_lc3.c @@ -0,0 +1,206 @@ +/* + * Copyright (c) 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 "stream_lc3.h" +#include "stream_tx.h" + +LOG_MODULE_REGISTER(lc3, LOG_LEVEL_INF); + +#define LC3_MAX_SAMPLE_RATE 48000U +#define LC3_MAX_FRAME_DURATION_US 10000U +#define LC3_MAX_NUM_SAMPLES ((LC3_MAX_FRAME_DURATION_US * LC3_MAX_SAMPLE_RATE) / USEC_PER_SEC) +/* codec does clipping above INT16_MAX - 3000 */ +#define AUDIO_VOLUME (INT16_MAX - 3000) +#define AUDIO_TONE_FREQUENCY_HZ 400 + +static int16_t audio_buf[LC3_MAX_NUM_SAMPLES]; +/** + * Use the math lib to generate a sine-wave using 16 bit samples into a buffer. + * + * @param stream The TX stream to generate and fill the sine wave for + */ +static void fill_audio_buf_sin(struct tx_stream *stream) +{ + const unsigned int num_samples = + (stream->lc3_tx.frame_duration_us * stream->lc3_tx.freq_hz) / USEC_PER_SEC; + const int sine_period_samples = stream->lc3_tx.freq_hz / AUDIO_TONE_FREQUENCY_HZ; + const float step = 2 * 3.1415f / sine_period_samples; + + for (unsigned int i = 0; i < num_samples; i++) { + const float sample = sinf(i * step); + + audio_buf[i] = (int16_t)(AUDIO_VOLUME * sample); + } +} + +static int extract_lc3_config(struct tx_stream *stream) +{ + const struct bt_audio_codec_cfg *codec_cfg = stream->bap_stream->codec_cfg; + struct stream_lc3_tx *lc3_tx = &stream->lc3_tx; + int ret; + + LOG_INF("Extracting LC3 configuration values"); + + ret = bt_audio_codec_cfg_get_freq(codec_cfg); + if (ret >= 0) { + ret = bt_audio_codec_cfg_freq_to_freq_hz(ret); + if (ret > 0) { + if (LC3_CHECK_SR_HZ(ret)) { + lc3_tx->freq_hz = (uint32_t)ret; + } else { + LOG_ERR("Unsupported sampling frequency for LC3: %d", ret); + + return ret; + } + } else { + LOG_ERR("Invalid frequency: %d", ret); + + return ret; + } + } else { + LOG_ERR("Could not get frequency: %d", ret); + + return ret; + } + + ret = bt_audio_codec_cfg_get_frame_dur(codec_cfg); + if (ret >= 0) { + ret = bt_audio_codec_cfg_frame_dur_to_frame_dur_us(ret); + if (ret > 0) { + if (LC3_CHECK_DT_US(ret)) { + lc3_tx->frame_duration_us = (uint32_t)ret; + } else { + LOG_ERR("Unsupported frame duration for LC3: %d", ret); + + return ret; + } + } else { + LOG_ERR("Invalid frame duration: %d", ret); + + return ret; + } + } else { + LOG_ERR("Could not get frame duration: %d", ret); + + return ret; + } + + ret = bt_audio_codec_cfg_get_chan_allocation(codec_cfg, &lc3_tx->chan_allocation); + if (ret != 0) { + LOG_DBG("Could not get channel allocation: %d", ret); + lc3_tx->chan_allocation = BT_AUDIO_LOCATION_MONO_AUDIO; + } + + lc3_tx->chan_cnt = bt_audio_get_chan_count(lc3_tx->chan_allocation); + + ret = bt_audio_codec_cfg_get_frame_blocks_per_sdu(codec_cfg, true); + if (ret >= 0) { + lc3_tx->frame_blocks_per_sdu = (uint8_t)ret; + } + + ret = bt_audio_codec_cfg_get_octets_per_frame(codec_cfg); + if (ret >= 0) { + lc3_tx->octets_per_frame = (uint16_t)ret; + } else { + LOG_ERR("Could not get octets per frame: %d", ret); + + return ret; + } + + return 0; +} + +static bool encode_frame(struct tx_stream *stream, uint8_t index, struct net_buf *out_buf) +{ + const uint16_t octets_per_frame = stream->lc3_tx.octets_per_frame; + int lc3_ret; + + /* Generate sine wave */ + fill_audio_buf_sin(stream); + + lc3_ret = lc3_encode(stream->lc3_tx.encoder, LC3_PCM_FORMAT_S16, audio_buf, 1, + octets_per_frame, net_buf_tail(out_buf)); + if (lc3_ret < 0) { + LOG_ERR("LC3 encoder failed - wrong parameters?: %d", lc3_ret); + + return false; + } + + out_buf->len += octets_per_frame; + + return true; +} + +static bool encode_frame_block(struct tx_stream *stream, struct net_buf *out_buf) +{ + for (uint8_t i = 0U; i < stream->lc3_tx.chan_cnt; i++) { + /* We provide the total number of decoded frames to `decode_frame` for logging + * purposes + */ + if (!encode_frame(stream, i, out_buf)) { + return false; + } + } + + return true; +} + +void stream_lc3_add_data(struct tx_stream *stream, struct net_buf *buf) +{ + for (uint8_t i = 0U; i < stream->lc3_tx.frame_blocks_per_sdu; i++) { + if (!encode_frame_block(stream, buf)) { + break; + } + } +} + +int stream_lc3_init(struct tx_stream *stream) +{ + int err; + + err = extract_lc3_config(stream); + if (err != 0) { + memset(&stream->lc3_tx, 0, sizeof(stream->lc3_tx)); + + return err; + } + + /* Fill audio buffer with Sine wave only once and repeat encoding the same tone frame */ + LOG_INF("Initializing sine wave data"); + fill_audio_buf_sin(stream); + + LOG_INF("Setting up LC3 encoder"); + stream->lc3_tx.encoder = + lc3_setup_encoder(stream->lc3_tx.frame_duration_us, stream->lc3_tx.freq_hz, 0, + &stream->lc3_tx.encoder_mem); + + if (stream->lc3_tx.encoder == NULL) { + LOG_ERR("Failed to setup LC3 encoder"); + + memset(&stream->lc3_tx, 0, sizeof(stream->lc3_tx)); + + return -ENOEXEC; + } + + return 0; +} diff --git a/samples/bluetooth/bap_unicast_client/src/stream_lc3.h b/samples/bluetooth/bap_unicast_client/src/stream_lc3.h new file mode 100644 index 00000000000..28e921e7e05 --- /dev/null +++ b/samples/bluetooth/bap_unicast_client/src/stream_lc3.h @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef STREAM_LC3_H +#define STREAM_LC3_H + +#include + +#include +#include +#include + +/* Since the lc3.h header file is not available when CONFIG_LIBLC3=n, we need to guard the include + * and use of it + */ +#if defined(CONFIG_LIBLC3) +/* Header file for the liblc3 */ +#include + +struct stream_lc3_tx { + uint32_t freq_hz; + uint32_t frame_duration_us; + uint16_t octets_per_frame; + uint8_t frame_blocks_per_sdu; + uint8_t chan_cnt; + enum bt_audio_location chan_allocation; + lc3_encoder_t encoder; + lc3_encoder_mem_48k_t encoder_mem; +}; +#endif /* CONFIG_LIBLC3 */ + +/* Opaque definition to avoid including stream_tx.h */ +struct tx_stream; + +/* + * @brief Initialize LC3 encoder for a stream + * + * This will initialize the encoder for the provided TX stream + */ +int stream_lc3_init(struct tx_stream *stream); + +/** + * Add LC3 encoded data to the provided buffer from the provided stream + * + * @param stream The TX stream to add data from + * @param buf The buffer to store the encoded audio data in + */ +void stream_lc3_add_data(struct tx_stream *stream, struct net_buf *buf); + +#endif /* STREAM_LC3_H */ diff --git a/samples/bluetooth/bap_unicast_client/src/stream_tx.c b/samples/bluetooth/bap_unicast_client/src/stream_tx.c new file mode 100644 index 00000000000..91f668db17a --- /dev/null +++ b/samples/bluetooth/bap_unicast_client/src/stream_tx.c @@ -0,0 +1,176 @@ +/* + * Copyright (c) 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 +#include +#include +#include + +#include "stream_lc3.h" +#include "stream_tx.h" + +LOG_MODULE_REGISTER(stream_tx, LOG_LEVEL_INF); + +static struct tx_stream tx_streams[CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SRC_COUNT]; + +static bool stream_is_streaming(const struct bt_bap_stream *bap_stream) +{ + struct bt_bap_ep_info ep_info; + int err; + + if (bap_stream == NULL) { + return false; + } + + /* No-op if stream is not configured */ + if (bap_stream->ep == NULL) { + return false; + } + + err = bt_bap_ep_get_info(bap_stream->ep, &ep_info); + if (err != 0) { + false; + } + + return ep_info.state == BT_BAP_EP_STATE_STREAMING; +} + +static void tx_thread_func(void *arg1, void *arg2, void *arg3) +{ + NET_BUF_POOL_FIXED_DEFINE(tx_pool, CONFIG_BT_ISO_TX_BUF_COUNT, + BT_ISO_SDU_BUF_SIZE(CONFIG_BT_ISO_TX_MTU), + CONFIG_BT_CONN_TX_USER_DATA_SIZE, NULL); + static uint8_t mock_data[CONFIG_BT_ISO_TX_MTU]; + + for (size_t i = 0U; i < ARRAY_SIZE(mock_data); i++) { + mock_data[i] = (uint8_t)i; + } + + /* This loop will attempt to send on all streams in the streaming state in a round robin + * fashion. + * The TX is controlled by the number of buffers configured, and increasing + * CONFIG_BT_ISO_TX_BUF_COUNT will allow for more streams in parallel, or to submit more + * buffers per stream. + * Once a buffer has been freed by the stack, it triggers the next TX. + */ + while (true) { + int err = -ENOEXEC; + + for (size_t i = 0U; i < ARRAY_SIZE(tx_streams); i++) { + struct bt_bap_stream *bap_stream = tx_streams[i].bap_stream; + + if (stream_is_streaming(bap_stream)) { + struct net_buf *buf; + + buf = net_buf_alloc(&tx_pool, K_FOREVER); + net_buf_reserve(buf, BT_ISO_CHAN_SEND_RESERVE); + + if (IS_ENABLED(CONFIG_LIBLC3) && + bap_stream->codec_cfg->id == BT_HCI_CODING_FORMAT_LC3) { + stream_lc3_add_data(&tx_streams[i], buf); + } else { + net_buf_add_mem(buf, mock_data, bap_stream->qos->sdu); + } + + err = bt_bap_stream_send(bap_stream, buf, tx_streams[i].seq_num); + if (err == 0) { + tx_streams[i].seq_num++; + } else { + LOG_ERR("Unable to send: %d", err); + net_buf_unref(buf); + } + } /* No-op if stream is not streaming */ + } + + if (err != 0) { + /* In case of any errors, retry with a delay */ + k_sleep(K_MSEC(10)); + } + } +} + +int stream_tx_register(struct bt_bap_stream *bap_stream) +{ + if (bap_stream == NULL) { + return -EINVAL; + } + + for (size_t i = 0U; i < ARRAY_SIZE(tx_streams); i++) { + if (tx_streams[i].bap_stream == NULL) { + tx_streams[i].bap_stream = bap_stream; + tx_streams[i].seq_num = 0U; + + if (IS_ENABLED(CONFIG_LIBLC3) && + bap_stream->codec_cfg->id == BT_HCI_CODING_FORMAT_LC3) { + const int err = stream_lc3_init(&tx_streams[i]); + + if (err != 0) { + tx_streams[i].bap_stream = NULL; + + return err; + } + } + + LOG_INF("Registered %p for TX", bap_stream); + + return 0; + } + } + + return -ENOMEM; +} + +int stream_tx_unregister(struct bt_bap_stream *bap_stream) +{ + if (bap_stream == NULL) { + return -EINVAL; + } + + for (size_t i = 0U; i < ARRAY_SIZE(tx_streams); i++) { + if (tx_streams[i].bap_stream == bap_stream) { + tx_streams[i].bap_stream = NULL; + + LOG_INF("Unregistered %p for TX", bap_stream); + + return 0; + } + } + + return -ENODATA; +} + +void stream_tx_init(void) +{ + static bool thread_started; + + if (!thread_started) { + static K_KERNEL_STACK_DEFINE(tx_thread_stack, + IS_ENABLED(CONFIG_LIBLC3) ? 4096U : 1024U); + const int tx_thread_prio = K_PRIO_PREEMPT(5); + static struct k_thread tx_thread; + + k_thread_create(&tx_thread, tx_thread_stack, K_KERNEL_STACK_SIZEOF(tx_thread_stack), + tx_thread_func, NULL, NULL, NULL, tx_thread_prio, 0, K_NO_WAIT); + k_thread_name_set(&tx_thread, "TX thread"); + thread_started = true; + } +} diff --git a/samples/bluetooth/bap_unicast_client/src/stream_tx.h b/samples/bluetooth/bap_unicast_client/src/stream_tx.h new file mode 100644 index 00000000000..d5b17ccc796 --- /dev/null +++ b/samples/bluetooth/bap_unicast_client/src/stream_tx.h @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef STREAM_TX_H +#define STREAM_TX_H + +#include +#include +#include +#include + +#include "stream_lc3.h" + +struct tx_stream { + struct bt_bap_stream *bap_stream; + uint16_t seq_num; + +#if defined(CONFIG_LIBLC3) + struct stream_lc3_tx lc3_tx; +#endif /* CONFIG_LIBLC3 */ +}; + +/** + * @brief Initialize TX + * + * This will initialize TX if not already initialized. This creates and starts a thread that + * will attempt to send data on all streams registered with stream_tx_register(). + */ +void stream_tx_init(void); + +/** + * @brief Register a stream for TX + * + * This will add it to the list of streams the TX thread will attempt to send on. + * + * @retval 0 on success + * @retval -EINVAL if @p bap_stream is NULL + * @retval -EINVAL if @p bap_stream.codec_cfg contains invalid values + * @retval -ENOEXEC if the LC3 encoder failed to initialize + * @retval -ENOMEM if not more streams can be registered + */ +int stream_tx_register(struct bt_bap_stream *bap_stream); + +/** + * @brief Unregister a stream for TX + * + * This will remove it to the list of streams the TX thread will attempt to send on. + * + * @retval 0 on success + * @retval -EINVAL if @p bap_stream is NULL + * @retval -EALREADY if the stream is currently not registered + */ +int stream_tx_unregister(struct bt_bap_stream *bap_stream); + +#endif /* STREAM_TX_H */ diff --git a/samples/bluetooth/bap_unicast_server/boards/nrf5340_audio_dk_nrf5340_cpuapp.conf b/samples/bluetooth/bap_unicast_server/boards/nrf5340_audio_dk_nrf5340_cpuapp.conf index 76df8dba27a..7c6a3aecc26 100644 --- a/samples/bluetooth/bap_unicast_server/boards/nrf5340_audio_dk_nrf5340_cpuapp.conf +++ b/samples/bluetooth/bap_unicast_server/boards/nrf5340_audio_dk_nrf5340_cpuapp.conf @@ -1,9 +1,6 @@ # For LC3 the following configs are needed CONFIG_FPU=y CONFIG_LIBLC3=y -# The LC3 codec uses a large amount of stack. This app runs the codec in the work-queue, hence -# inctease stack size for that thread. -CONFIG_SYSTEM_WORKQUEUE_STACK_SIZE=4096 CONFIG_BT_BUF_EVT_RX_SIZE=255 CONFIG_BT_BUF_ACL_RX_SIZE=255 diff --git a/tests/bsim/bluetooth/audio_samples/bap_unicast_client/CMakeLists.txt b/tests/bsim/bluetooth/audio_samples/bap_unicast_client/CMakeLists.txt index d91b6e05f69..8d8b25668b4 100644 --- a/tests/bsim/bluetooth/audio_samples/bap_unicast_client/CMakeLists.txt +++ b/tests/bsim/bluetooth/audio_samples/bap_unicast_client/CMakeLists.txt @@ -11,6 +11,9 @@ target_sources(app PRIVATE ${unicast_client_path}/src/main.c ) +zephyr_sources_ifdef(CONFIG_LIBLC3 ${unicast_client_path}/src/stream_lc3.c) +zephyr_sources_ifdef(CONFIG_BT_AUDIO_TX ${unicast_client_path}/src/stream_tx.c) + target_sources(app PRIVATE src/unicast_client_sample_test.c src/test_main.c diff --git a/tests/bsim/bluetooth/audio_samples/bap_unicast_client/prj.conf b/tests/bsim/bluetooth/audio_samples/bap_unicast_client/prj.conf index c7385ae8cda..430d4276b02 100644 --- a/tests/bsim/bluetooth/audio_samples/bap_unicast_client/prj.conf +++ b/tests/bsim/bluetooth/audio_samples/bap_unicast_client/prj.conf @@ -1,16 +1,2 @@ -# This file content is just a copy of the unicast client sample prj.conf - -CONFIG_BT=y -CONFIG_LOG=y -CONFIG_BT_CENTRAL=y -CONFIG_BT_AUDIO=y -CONFIG_BT_BAP_UNICAST_CLIENT=y -CONFIG_BT_ISO_TX_BUF_COUNT=4 -# Support an ISO channel per ASE -CONFIG_BT_ISO_MAX_CHAN=4 -CONFIG_BT_BAP_UNICAST_CLIENT_GROUP_STREAM_COUNT=4 -CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SNK_COUNT=2 -CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SRC_COUNT=2 -CONFIG_BT_KEYS_OVERWRITE_OLDEST=y - -CONFIG_BT_EXT_ADV=y +# Please build using the sample configuration file: +# ${ZEPHYR_BASE}/samples/bluetooth/uncast_audio_client/prj.conf diff --git a/tests/bsim/bluetooth/audio_samples/compile.sh b/tests/bsim/bluetooth/audio_samples/compile.sh index ffff8928847..b5b19f325f9 100755 --- a/tests/bsim/bluetooth/audio_samples/compile.sh +++ b/tests/bsim/bluetooth/audio_samples/compile.sh @@ -14,7 +14,11 @@ source ${ZEPHYR_BASE}/tests/bsim/compile.source if [ "${BOARD_TS}" == "nrf5340bsim_nrf5340_cpuapp" ]; then app=samples/bluetooth/bap_unicast_server sysbuild=1 compile app=samples/bluetooth/bap_broadcast_source sysbuild=1 compile - app=tests/bsim/bluetooth/audio_samples/bap_unicast_client sysbuild=1 compile + app=tests/bsim/bluetooth/audio_samples/bap_unicast_client \ + sample=${ZEPHYR_BASE}/samples/bluetooth/bap_unicast_client \ + conf_file=${sample}/prj.conf \ + conf_overlay=${sample}/boards/nrf5340_audio_dk_nrf5340_cpuapp.conf \ + exe_name=bs_${BOARD_TS}_${app}_prj_conf sysbuild=1 compile app=tests/bsim/bluetooth/audio_samples/bap_broadcast_sink sysbuild=1 \ conf_file=${ZEPHYR_BASE}/samples/bluetooth/bap_broadcast_sink/prj.conf \ exe_name=bs_${BOARD_TS}_${app}_prj_conf sysbuild=1 compile @@ -24,7 +28,9 @@ else app=samples/bluetooth/bap_broadcast_source conf_overlay=overlay-bt_ll_sw_split.conf \ exe_name=bs_${BOARD_TS}_${app}_prj_conf sysbuild=1 compile app=tests/bsim/bluetooth/audio_samples/bap_unicast_client \ - conf_overlay=${ZEPHYR_BASE}/samples/bluetooth/bap_unicast_client/overlay-bt_ll_sw_split.conf \ + sample=${ZEPHYR_BASE}/samples/bluetooth/bap_unicast_client \ + conf_file=${sample}/prj.conf \ + conf_overlay=${sample}/overlay-bt_ll_sw_split.conf \ exe_name=bs_${BOARD_TS}_${app}_prj_conf sysbuild=1 compile app=tests/bsim/bluetooth/audio_samples/bap_broadcast_sink \ conf_file=${ZEPHYR_BASE}/samples/bluetooth/bap_broadcast_sink/prj.conf \