Commit 038fc8b6 authored by Emil Gydesen's avatar Emil Gydesen Committed by Johan Hedberg
Browse files

samples: Bluetooth: Refactor TX for unicast audio client



Refactored the unicast audio client sample to use a
separate thread for TXing, rather than the system workqueue.

This allows us to do blocking waiting operations like
buf = net_buf_alloc(&tx_pool, K_FOREVER);
and also splits the responsibility of TXing to a new
file as well.

LC3 handling have also been moved to a new file, so
that it does not pollute the source code for non-LC3
supported builds. This also fixes various issues and
improves the LC3 handling in the sample.

Signed-off-by: default avatarEmil Gydesen <emil.gydesen@nordicsemi.no>
parent ea05d61c
Loading
Loading
Loading
Loading
+3 −0
Original line number Diff line number Diff line
@@ -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)
+44 −325
Original line number Diff line number Diff line
@@ -4,18 +4,21 @@
 * SPDX-License-Identifier: Apache-2.0
 */

#include <zephyr/types.h>
#include <stddef.h>
#include <errno.h>
#include <zephyr/kernel.h>
#include <zephyr/sys/printk.h>

#include <zephyr/bluetooth/bluetooth.h>
#include <zephyr/bluetooth/conn.h>
#include <zephyr/bluetooth/audio/audio.h>
#include <zephyr/bluetooth/audio/bap.h>
#include <zephyr/bluetooth/audio/bap_lc3_preset.h>
#include <zephyr/bluetooth/bluetooth.h>
#include <zephyr/bluetooth/conn.h>
#include <zephyr/kernel.h>
#include <zephyr/sys/byteorder.h>
#include <zephyr/sys/printk.h>
#include <zephyr/sys/util_macro.h>
#include <zephyr/types.h>

#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) {
+206 −0
Original line number Diff line number Diff line
/*
 * Copyright (c) 2024 Nordic Semiconductor ASA
 *
 * SPDX-License-Identifier: Apache-2.0
 */
#include <errno.h>
#include <errno.h>
#include <stdbool.h>
#include <stdint.h>
#include <string.h>

#include <zephyr/bluetooth/audio/audio.h>
#include <zephyr/kernel.h>
#include <zephyr/logging/log.h>
#include <zephyr/logging/log_core.h>
#include <zephyr/net/buf.h>
#include <zephyr/sys/printk.h>
#include <zephyr/sys_clock.h>

#include <lc3.h>
#include <sys/errno.h>
#include <math.h>

#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;
}
+53 −0
Original line number Diff line number Diff line
/*
 * Copyright (c) 2024 Nordic Semiconductor ASA
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#ifndef STREAM_LC3_H
#define STREAM_LC3_H

#include <stdint.h>

#include <zephyr/bluetooth/audio/audio.h>
#include <zephyr/net/buf.h>
#include <zephyr/sys_clock.h>

/* 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 <lc3.h>

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 */
+176 −0
Original line number Diff line number Diff line
/*
 * Copyright (c) 2024 Nordic Semiconductor ASA
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#include <autoconf.h>
#include <errno.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>

#include <zephyr/bluetooth/audio/cap.h>
#include <zephyr/bluetooth/audio/bap.h>
#include <zephyr/bluetooth/bluetooth.h>
#include <zephyr/bluetooth/hci_types.h>
#include <zephyr/bluetooth/iso.h>
#include <zephyr/kernel.h>
#include <zephyr/kernel/thread_stack.h>
#include <zephyr/logging/log.h>
#include <zephyr/logging/log_core.h>
#include <zephyr/net/buf.h>
#include <zephyr/sys/byteorder.h>
#include <zephyr/sys/util.h>
#include <zephyr/sys/util_macro.h>
#include <zephyr/types.h>

#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;
	}
}
Loading