Bluetooth: BAP: Shell: Moved RX and TX fields into a union

An audio stream is unidirectional, so it's always either
TXing or RXing, so the fields can safely be moved to an
enum.

The struct now has both a is_rx and is_tx field. The reason
why two bools are being used, is that the both of them may
be false (we need 3 states).

All access to the rx and tx structs in the union shall be
guarded by these fields.

Signed-off-by: Emil Gydesen <emil.gydesen@nordicsemi.no>
This commit is contained in:
Emil Gydesen 2024-03-15 14:03:05 +01:00 committed by Carles Cufí
commit e7ee26d5ed
2 changed files with 150 additions and 103 deletions

View file

@ -71,6 +71,8 @@ struct shell_stream {
struct bt_cap_stream stream;
struct bt_audio_codec_cfg codec_cfg;
struct bt_audio_codec_qos qos;
bool is_tx;
bool is_rx;
#if defined(CONFIG_LIBLC3)
uint32_t lc3_freq_hz;
@ -81,8 +83,11 @@ struct shell_stream {
uint8_t lc3_chan_cnt;
#endif /* CONFIG_LIBLC3 */
union {
#if defined(CONFIG_BT_AUDIO_TX)
int64_t connected_at_ticks; /* The uptime tick measured when stream was connected */
struct {
/* The uptime tick measured when stream was connected */
int64_t connected_at_ticks;
uint16_t seq_num;
struct k_work_delayable audio_send_work;
bool tx_active;
@ -90,9 +95,11 @@ struct shell_stream {
atomic_t lc3_enqueue_cnt;
size_t lc3_sdu_cnt;
#endif /* CONFIG_LIBLC3 */
} tx;
#endif /* CONFIG_BT_AUDIO_TX */
#if defined(CONFIG_BT_AUDIO_RX)
struct {
struct bt_iso_recv_info last_info;
size_t empty_sdu_pkts;
size_t lost_pkts;
@ -105,8 +112,10 @@ struct shell_stream {
lc3_decoder_t lc3_decoder;
size_t decoded_cnt;
#endif /* CONFIG_LIBLC3 */
} rx;
#endif /* CONFIG_BT_AUDIO_RX */
};
};
struct broadcast_source {
bool is_cap;

View file

@ -194,9 +194,13 @@ static uint16_t get_next_seq_num(struct bt_bap_stream *bap_stream)
uint64_t delta_us;
uint16_t seq_num;
if (!sh_stream->is_tx) {
return 0;
}
/* Note: This does not handle wrapping of ticks when they go above 2^(62-1) */
uptime_ticks = k_uptime_ticks();
delta_ticks = uptime_ticks - sh_stream->connected_at_ticks;
delta_ticks = uptime_ticks - sh_stream->tx.connected_at_ticks;
delta_us = k_ticks_to_us_near64((uint64_t)delta_ticks);
/* Calculate the sequence number by dividing the stream uptime by the SDU interval */
@ -230,8 +234,10 @@ static void clear_lc3_sine_data(struct bt_bap_stream *bap_stream)
{
struct shell_stream *sh_stream = shell_stream_from_bap_stream(bap_stream);
sh_stream->tx_active = false;
(void)k_work_cancel_delayable(&sh_stream->audio_send_work);
if (sh_stream->is_tx) {
sh_stream->tx.tx_active = false;
(void)k_work_cancel_delayable(&sh_stream->tx.audio_send_work);
}
}
/**
@ -299,7 +305,7 @@ static int init_lc3_encoder(const struct shell_stream *sh_stream)
static void lc3_audio_send_data(struct k_work *work)
{
struct shell_stream *sh_stream = CONTAINER_OF(k_work_delayable_from_work(work),
struct shell_stream, audio_send_work);
struct shell_stream, tx.audio_send_work);
struct bt_bap_stream *bap_stream = bap_stream_from_shell_stream(sh_stream);
const uint16_t tx_sdu_len = sh_stream->lc3_frame_blocks_per_sdu * sh_stream->lc3_chan_cnt *
sh_stream->lc3_octets_per_frame;
@ -308,7 +314,7 @@ static void lc3_audio_send_data(struct k_work *work)
off_t offset = 0;
int err;
if (!sh_stream->tx_active) {
if (!sh_stream->is_tx || !sh_stream->tx.tx_active) {
/* TX has been aborted */
return;
}
@ -333,7 +339,7 @@ static void lc3_audio_send_data(struct k_work *work)
return;
}
if (atomic_get(&sh_stream->lc3_enqueue_cnt) == 0U) {
if (atomic_get(&sh_stream->tx.lc3_enqueue_cnt) == 0U) {
shell_error(ctx_shell, "Stream %p enqueue count was 0", bap_stream);
/* Reschedule for next interval */
@ -369,7 +375,7 @@ static void lc3_audio_send_data(struct k_work *work)
}
}
err = bt_bap_stream_send(bap_stream, buf, sh_stream->seq_num);
err = bt_bap_stream_send(bap_stream, buf, sh_stream->tx.seq_num);
if (err < 0) {
shell_error(ctx_shell, "Failed to send LC3 audio data (%d)", err);
net_buf_unref(buf);
@ -380,16 +386,17 @@ static void lc3_audio_send_data(struct k_work *work)
return;
}
if ((sh_stream->lc3_sdu_cnt % 100) == 0) {
if ((sh_stream->tx.lc3_sdu_cnt % 100) == 0) {
shell_info(ctx_shell, "[%zu]: stream %p : TX LC3: %zu (seq_num %u)",
sh_stream->lc3_sdu_cnt, bap_stream, tx_sdu_len, sh_stream->seq_num);
sh_stream->tx.lc3_sdu_cnt, bap_stream, tx_sdu_len,
sh_stream->tx.seq_num);
}
sh_stream->lc3_sdu_cnt++;
sh_stream->seq_num++;
atomic_dec(&sh_stream->lc3_enqueue_cnt);
sh_stream->tx.lc3_sdu_cnt++;
sh_stream->tx.seq_num++;
atomic_dec(&sh_stream->tx.lc3_enqueue_cnt);
if (atomic_get(&sh_stream->lc3_enqueue_cnt) > 0) {
if (atomic_get(&sh_stream->tx.lc3_enqueue_cnt) > 0) {
/* If we have more buffers available, we reschedule the workqueue item immediately
* to trigger another encode + TX, but without blocking this call for too long
*/
@ -402,14 +409,18 @@ static void lc3_sent_cb(struct bt_bap_stream *bap_stream)
struct shell_stream *sh_stream = shell_stream_from_bap_stream(bap_stream);
int err;
atomic_inc(&sh_stream->lc3_enqueue_cnt);
if (!sh_stream->is_tx) {
return;
}
if (!sh_stream->tx_active) {
atomic_inc(&sh_stream->tx.lc3_enqueue_cnt);
if (!sh_stream->tx.tx_active) {
/* TX has been aborted */
return;
}
err = k_work_schedule(&sh_stream->audio_send_work, K_NO_WAIT);
err = k_work_schedule(&sh_stream->tx.audio_send_work, K_NO_WAIT);
if (err < 0) {
shell_error(ctx_shell, "Failed to schedule TX for stream %p: %d", bap_stream, err);
}
@ -2371,11 +2382,16 @@ static K_FIFO_DEFINE(lc3_in_fifo);
static int init_lc3_decoder(struct shell_stream *sh_stream)
{
if (sh_stream == NULL) {
shell_error(ctx_shell, "invalid stream to init LC3");
shell_error(ctx_shell, "NULL stream to init LC3");
return -EINVAL;
}
if (sh_stream->lc3_decoder != NULL) {
if (!sh_stream->is_rx) {
shell_error(ctx_shell, "Invalid stream to init LC3");
return -EINVAL;
}
if (sh_stream->rx.lc3_decoder != NULL) {
shell_error(ctx_shell, "Already initialized");
return -EALREADY;
}
@ -2391,10 +2407,10 @@ static int init_lc3_decoder(struct shell_stream *sh_stream)
"Initializing the LC3 decoder with %u us duration and %u Hz frequency",
sh_stream->lc3_frame_duration_us, sh_stream->lc3_freq_hz);
/* Create the decoder instance. This shall complete before stream_started() is called. */
sh_stream->lc3_decoder = lc3_setup_decoder(sh_stream->lc3_frame_duration_us,
sh_stream->rx.lc3_decoder = lc3_setup_decoder(sh_stream->lc3_frame_duration_us,
sh_stream->lc3_freq_hz, 0, /* No resampling */
&sh_stream->lc3_decoder_mem);
if (sh_stream->lc3_decoder == NULL) {
&sh_stream->rx.lc3_decoder_mem);
if (sh_stream->rx.lc3_decoder == NULL) {
shell_error(ctx_shell, "Failed to setup LC3 decoder - wrong parameters?\n");
return -EINVAL;
}
@ -2404,33 +2420,35 @@ static int init_lc3_decoder(struct shell_stream *sh_stream)
static void lc3_decoder_stream_clear(struct shell_stream *sh_stream)
{
sh_stream->lc3_decoder = NULL;
if (sh_stream->is_rx) {
sh_stream->rx.lc3_decoder = NULL;
}
}
static bool decode_frame(struct net_buf *buf, struct shell_stream *sh_stream, size_t frame_cnt)
{
const size_t total_frames = sh_stream->lc3_chan_cnt * sh_stream->lc3_frame_blocks_per_sdu;
const uint16_t octets_per_frame = sh_stream->lc3_octets_per_frame;
void *data;
void *iso_data;
int err;
if (buf->len == 0U) {
data = NULL; /* perform PLC */
iso_data = NULL; /* perform PLC */
if ((sh_stream->decoded_cnt % recv_stats_interval) == 0) {
shell_print(ctx_shell, "[%zu]: Performing PLC", sh_stream->decoded_cnt);
if ((sh_stream->rx.decoded_cnt % recv_stats_interval) == 0) {
shell_print(ctx_shell, "[%zu]: Performing PLC", sh_stream->rx.decoded_cnt);
}
} else {
data = net_buf_pull_mem(buf, octets_per_frame);
iso_data = net_buf_pull_mem(buf, octets_per_frame);
if ((sh_stream->decoded_cnt % recv_stats_interval) == 0) {
if ((sh_stream->rx.decoded_cnt % recv_stats_interval) == 0) {
shell_print(ctx_shell, "[%zu]: Decoding frame of size %u (%u/%u)",
sh_stream->decoded_cnt, octets_per_frame, frame_cnt + 1,
sh_stream->rx.decoded_cnt, octets_per_frame, frame_cnt,
total_frames);
}
}
err = lc3_decode(sh_stream->lc3_decoder, data, octets_per_frame, LC3_PCM_FORMAT_S16,
err = lc3_decode(sh_stream->rx.lc3_decoder, iso_data, octets_per_frame, LC3_PCM_FORMAT_S16,
lc3_rx_buf, 1);
if (err < 0) {
shell_error(ctx_shell, "Failed to decode LC3 data (%u/%u - %u/%u)", frame_cnt + 1,
@ -2463,7 +2481,7 @@ static size_t decode_frame_block(struct net_buf *buf, struct shell_stream *sh_st
static void do_lc3_decode(struct shell_stream *sh_stream, struct net_buf *buf)
{
if (sh_stream->lc3_decoder != NULL) {
if (sh_stream->is_rx && sh_stream->rx.lc3_decoder != NULL) {
const uint8_t frame_blocks_per_sdu = sh_stream->lc3_frame_blocks_per_sdu;
size_t frame_cnt;
@ -2478,7 +2496,7 @@ static void do_lc3_decode(struct shell_stream *sh_stream, struct net_buf *buf)
frame_cnt += decoded_frames;
}
sh_stream->decoded_cnt++;
sh_stream->rx.decoded_cnt++;
}
net_buf_unref(buf);
@ -2488,10 +2506,11 @@ static void lc3_decoder_thread_func(void *arg1, void *arg2, void *arg3)
{
while (true) {
struct lc3_data *data = k_fifo_get(&lc3_in_fifo, K_FOREVER);
struct shell_stream *sh_stream = data->sh_stream;
/* Lock to avoid `lc3_decoder` becoming NULL in case of a stream stop */
if (data->sh_stream->lc3_decoder == NULL) {
if (sh_stream->is_rx && sh_stream->rx.lc3_decoder == NULL) {
shell_warn(ctx_shell, "Decoder is NULL, discarding data from FIFO");
k_mem_slab_free(&lc3_data_slab, (void *)data);
continue; /* Wait for new data */
}
@ -2509,46 +2528,47 @@ static void audio_recv(struct bt_bap_stream *stream,
{
struct shell_stream *sh_stream = shell_stream_from_bap_stream(stream);
sh_stream->rx_cnt++;
if ((info->flags & BT_ISO_FLAGS_VALID) != 0) {
/* For valid ISO packets we check if they are invalid in other ways */
if (info->ts == sh_stream->last_info.ts) {
sh_stream->dup_ts++;
if (!sh_stream->is_rx) {
return;
}
if (info->seq_num == sh_stream->last_info.seq_num) {
sh_stream->dup_psn++;
sh_stream->rx.rx_cnt++;
if (info->ts == sh_stream->rx.last_info.ts) {
sh_stream->rx.dup_ts++;
}
if (buf->len == 0U) {
sh_stream->empty_sdu_pkts++;
if (info->seq_num == sh_stream->rx.last_info.seq_num) {
sh_stream->rx.dup_psn++;
}
if ((info->flags & BT_ISO_FLAGS_VALID) != 0 && buf->len == 0U) {
sh_stream->rx.empty_sdu_pkts++;
}
if (info->flags & BT_ISO_FLAGS_ERROR) {
sh_stream->err_pkts++;
sh_stream->rx.err_pkts++;
}
if (info->flags & BT_ISO_FLAGS_LOST) {
sh_stream->lost_pkts++;
sh_stream->rx.lost_pkts++;
}
if ((sh_stream->rx_cnt % recv_stats_interval) == 0) {
shell_print(
ctx_shell,
if ((sh_stream->rx.rx_cnt % recv_stats_interval) == 0) {
shell_print(ctx_shell,
"[%zu]: Incoming audio on stream %p len %u ts %u seq_num %u flags %u "
"(dup ts %zu; dup psn %zu, err_pkts %zu, lost_pkts %zu, empty SDUs %zu)",
sh_stream->rx_cnt, stream, buf->len, info->ts, info->seq_num, info->flags,
sh_stream->dup_ts, sh_stream->dup_psn, sh_stream->err_pkts,
sh_stream->lost_pkts, sh_stream->empty_sdu_pkts);
"(dup ts %zu; dup psn %zu, err_pkts %zu, lost_pkts %zu, "
"empty SDUs %zu)",
sh_stream->rx.rx_cnt, stream, buf->len, info->ts, info->seq_num,
info->flags, sh_stream->rx.dup_ts, sh_stream->rx.dup_psn,
sh_stream->rx.err_pkts, sh_stream->rx.lost_pkts,
sh_stream->rx.empty_sdu_pkts);
}
(void)memcpy(&sh_stream->last_info, info, sizeof(sh_stream->last_info));
(void)memcpy(&sh_stream->rx.last_info, info, sizeof(sh_stream->rx.last_info));
#if defined(CONFIG_LIBLC3)
if (sh_stream->lc3_decoder != NULL) {
if (sh_stream->rx.lc3_decoder != NULL) {
const uint8_t frame_blocks_per_sdu = sh_stream->lc3_frame_blocks_per_sdu;
const uint16_t octets_per_frame = sh_stream->lc3_octets_per_frame;
const uint8_t chan_cnt = sh_stream->lc3_chan_cnt;
@ -2646,24 +2666,29 @@ static uint8_t get_chan_cnt(enum bt_audio_location chan_allocation)
static void stream_started_cb(struct bt_bap_stream *bap_stream)
{
struct shell_stream *sh_stream = shell_stream_from_bap_stream(bap_stream);
struct bt_bap_ep_info info = {0};
int ret;
printk("Stream %p started\n", bap_stream);
ret = bt_bap_ep_get_info(bap_stream->ep, &info);
if (ret != 0) {
shell_error(ctx_shell, "Failed to get EP info: %d", ret);
return;
}
sh_stream->is_rx = info.can_recv;
sh_stream->is_tx = info.can_send;
#if defined(CONFIG_LIBLC3)
const struct bt_audio_codec_cfg *codec_cfg = bap_stream->codec_cfg;
if (codec_cfg->id == BT_HCI_CODING_FORMAT_LC3) {
struct bt_bap_ep_info info = {0};
int ret;
ret = bt_bap_ep_get_info(bap_stream->ep, &info);
if (ret != 0) {
shell_error(ctx_shell, "Failed to get EP info: %d", ret);
if (sh_stream->is_tx) {
atomic_set(&sh_stream->tx.lc3_enqueue_cnt, PRIME_COUNT);
sh_stream->tx.lc3_sdu_cnt = 0U;
}
atomic_set(&sh_stream->lc3_enqueue_cnt, PRIME_COUNT);
sh_stream->lc3_sdu_cnt = 0U;
ret = bt_audio_codec_cfg_get_freq(codec_cfg);
if (ret >= 0) {
ret = bt_audio_codec_cfg_freq_to_freq_hz(ret);
@ -2727,8 +2752,8 @@ static void stream_started_cb(struct bt_bap_stream *bap_stream)
}
#if defined(CONFIG_BT_AUDIO_RX)
if (info.can_recv) {
if (sh_stream->lc3_decoder == NULL) {
if (sh_stream->is_rx) {
if (sh_stream->rx.lc3_decoder == NULL) {
const int err = init_lc3_decoder(sh_stream);
if (err != 0) {
@ -2739,23 +2764,27 @@ static void stream_started_cb(struct bt_bap_stream *bap_stream)
}
}
sh_stream->decoded_cnt = 0U;
#endif /* CONFIG_BT_AUDIO_RX */
sh_stream->rx.decoded_cnt = 0U;
}
#endif /* CONFIG_BT_AUDIO_RX */
}
#endif /* CONFIG_LIBLC3 */
#if defined(CONFIG_BT_AUDIO_TX)
sh_stream->connected_at_ticks = k_uptime_ticks();
if (sh_stream->is_tx) {
sh_stream->tx.connected_at_ticks = k_uptime_ticks();
}
#endif /* CONFIG_BT_AUDIO_TX */
#if defined(CONFIG_BT_AUDIO_RX)
sh_stream->empty_sdu_pkts = 0U;
sh_stream->lost_pkts = 0U;
sh_stream->err_pkts = 0U;
sh_stream->dup_psn = 0U;
sh_stream->rx_cnt = 0U;
sh_stream->dup_ts = 0U;
if (sh_stream->is_rx) {
sh_stream->rx.empty_sdu_pkts = 0U;
sh_stream->rx.lost_pkts = 0U;
sh_stream->rx.err_pkts = 0U;
sh_stream->rx.dup_psn = 0U;
sh_stream->rx.rx_cnt = 0U;
sh_stream->rx.dup_ts = 0U;
}
#endif
}
@ -2795,6 +2824,8 @@ static void stream_configured_cb(struct bt_bap_stream *stream,
static void stream_released_cb(struct bt_bap_stream *stream)
{
struct shell_stream *sh_stream = shell_stream_from_bap_stream(stream);
shell_print(ctx_shell, "Stream %p released\n", stream);
#if defined(CONFIG_BT_BAP_UNICAST_CLIENT)
@ -2837,6 +2868,9 @@ static void stream_released_cb(struct bt_bap_stream *stream)
#if defined(CONFIG_LIBLC3)
lc3_clear_stream(shell_stream_from_bap_stream(stream));
#endif /* CONFIG_LIBLC3 */
sh_stream->is_tx = false;
sh_stream->is_rx = false;
}
#endif /* CONFIG_BT_BAP_UNICAST */
@ -3521,15 +3555,19 @@ static int stream_start_sine(struct shell_stream *sh_stream)
{
int err;
k_work_init_delayable(&sh_stream->audio_send_work, lc3_audio_send_data);
if (!sh_stream->is_tx) {
return -EINVAL;
}
err = k_work_schedule(&sh_stream->audio_send_work, K_NO_WAIT);
k_work_init_delayable(&sh_stream->tx.audio_send_work, lc3_audio_send_data);
err = k_work_schedule(&sh_stream->tx.audio_send_work, K_NO_WAIT);
if (err < 0) {
return -ENOEXEC;
}
sh_stream->tx_active = true;
sh_stream->seq_num = get_next_seq_num(bap_stream_from_shell_stream(sh_stream));
sh_stream->tx.tx_active = true;
sh_stream->tx.seq_num = get_next_seq_num(bap_stream_from_shell_stream(sh_stream));
return 0;
}
@ -3656,7 +3694,7 @@ static int cmd_stop_sine(const struct shell *sh, size_t argc, char *argv[])
struct bt_bap_stream *bap_stream =
bap_stream_from_shell_stream(&unicast_streams[i]);
if (unicast_streams[i].tx_active) {
if (unicast_streams[i].is_tx && unicast_streams[i].tx.tx_active) {
clear_lc3_sine_data(bap_stream);
shell_print(sh, "Stopped transmitting on stream %p", bap_stream);
}
@ -3665,7 +3703,7 @@ static int cmd_stop_sine(const struct shell *sh, size_t argc, char *argv[])
for (size_t i = 0U; i < ARRAY_SIZE(broadcast_source_streams); i++) {
struct bt_bap_stream *bap_stream =
bap_stream_from_shell_stream(&broadcast_source_streams[i]);
if (unicast_streams[i].tx_active) {
if (unicast_streams[i].is_tx && unicast_streams[i].tx.tx_active) {
clear_lc3_sine_data(bap_stream);
shell_print(sh, "Stopped transmitting on stream %p", bap_stream);
}
@ -3673,7 +3711,7 @@ static int cmd_stop_sine(const struct shell *sh, size_t argc, char *argv[])
} else {
struct shell_stream *sh_stream = shell_stream_from_bap_stream(default_stream);
if (sh_stream->tx_active) {
if (sh_stream->is_tx && sh_stream->tx.tx_active) {
clear_lc3_sine_data(default_stream);
shell_print(sh, "Stopped transmitting on stream %p", default_stream);
}