samples: Bluetooth: Add stereo support for broadcast audio sink
The broadcast audio sink now supports stereo if CONFIG_TARGET_BROADCAST_CHANNEL=3 (LEFT | RIGHT). It parses the BASE to find a set of BIS (1 or 2) that contain the channel allocation from CONFIG_TARGET_BROADCAST_CHANNEL. Signed-off-by: Emil Gydesen <emil.gydesen@nordicsemi.no>
This commit is contained in:
parent
121896c0cf
commit
4d2bc5f5e8
2 changed files with 339 additions and 116 deletions
|
@ -35,6 +35,14 @@ config TARGET_BROADCAST_NAME
|
||||||
Name of target broadcast device. If not empty string, sink device
|
Name of target broadcast device. If not empty string, sink device
|
||||||
will only listen to the specified broadcast source. Not case sensitive.
|
will only listen to the specified broadcast source. Not case sensitive.
|
||||||
|
|
||||||
|
config MAX_CODEC_FRAMES_PER_SDU
|
||||||
|
int "The maximum number of codec frame per SDU supported"
|
||||||
|
default 1
|
||||||
|
range 1 255
|
||||||
|
help
|
||||||
|
Maximum number of codec frames per SDU supported by this device. Increasing this value
|
||||||
|
allows support for a greater variaty of broadcasts, but also increases memory usage.
|
||||||
|
|
||||||
config ENABLE_LC3
|
config ENABLE_LC3
|
||||||
bool "Enable the LC3 codec"
|
bool "Enable the LC3 codec"
|
||||||
# By default let's enable it in the platforms we know are capable of supporting it
|
# By default let's enable it in the platforms we know are capable of supporting it
|
||||||
|
@ -64,7 +72,7 @@ config USE_SPECIFIC_BROADCAST_CHANNEL
|
||||||
|
|
||||||
config TARGET_BROADCAST_CHANNEL
|
config TARGET_BROADCAST_CHANNEL
|
||||||
int "Broadcast Channel Audio Location to sync to"
|
int "Broadcast Channel Audio Location to sync to"
|
||||||
range 0 2
|
range 0 3
|
||||||
default 1
|
default 1
|
||||||
depends on USE_SPECIFIC_BROADCAST_CHANNEL
|
depends on USE_SPECIFIC_BROADCAST_CHANNEL
|
||||||
help
|
help
|
||||||
|
|
|
@ -112,7 +112,8 @@ static struct bt_le_ext_adv *ext_adv;
|
||||||
|
|
||||||
static const struct bt_audio_codec_cap codec_cap = BT_AUDIO_CODEC_CAP_LC3(
|
static const struct bt_audio_codec_cap codec_cap = BT_AUDIO_CODEC_CAP_LC3(
|
||||||
BT_AUDIO_CODEC_CAP_FREQ_16KHZ | BT_AUDIO_CODEC_CAP_FREQ_24KHZ,
|
BT_AUDIO_CODEC_CAP_FREQ_16KHZ | BT_AUDIO_CODEC_CAP_FREQ_24KHZ,
|
||||||
BT_AUDIO_CODEC_CAP_DURATION_10, BT_AUDIO_CODEC_CAP_CHAN_COUNT_SUPPORT(1), 40u, 60u, 1u,
|
BT_AUDIO_CODEC_CAP_DURATION_10, BT_AUDIO_CODEC_CAP_CHAN_COUNT_SUPPORT(1), 40u, 60u,
|
||||||
|
CONFIG_MAX_CODEC_FRAMES_PER_SDU,
|
||||||
(BT_AUDIO_CONTEXT_TYPE_CONVERSATIONAL | BT_AUDIO_CONTEXT_TYPE_MEDIA));
|
(BT_AUDIO_CONTEXT_TYPE_CONVERSATIONAL | BT_AUDIO_CONTEXT_TYPE_MEDIA));
|
||||||
|
|
||||||
/* Create a mask for the maximum BIS we can sync to using the number of streams
|
/* Create a mask for the maximum BIS we can sync to using the number of streams
|
||||||
|
@ -132,53 +133,18 @@ static int stop_adv(void);
|
||||||
RING_BUF_DECLARE(usb_ring_buf, USB_RING_BUF_SIZE);
|
RING_BUF_DECLARE(usb_ring_buf, USB_RING_BUF_SIZE);
|
||||||
NET_BUF_POOL_DEFINE(usb_tx_buf_pool, USB_ENQUEUE_COUNT, USB_STEREO_SAMPLE_SIZE, 0, net_buf_destroy);
|
NET_BUF_POOL_DEFINE(usb_tx_buf_pool, USB_ENQUEUE_COUNT, USB_STEREO_SAMPLE_SIZE, 0, net_buf_destroy);
|
||||||
|
|
||||||
static void mix_mono_to_stereo(int16_t audio_buf[LC3_MAX_NUM_SAMPLES_STEREO]);
|
|
||||||
static void add_to_usb_ring_buf(const int16_t audio_buf[LC3_MAX_NUM_SAMPLES_STEREO]);
|
static void add_to_usb_ring_buf(const int16_t audio_buf[LC3_MAX_NUM_SAMPLES_STEREO]);
|
||||||
#endif /* defined(CONFIG_USB_DEVICE_AUDIO) */
|
#endif /* defined(CONFIG_USB_DEVICE_AUDIO) */
|
||||||
|
|
||||||
#if defined(CONFIG_LIBLC3)
|
#if defined(CONFIG_LIBLC3)
|
||||||
static K_SEM_DEFINE(lc3_decoder_sem, 0, 1);
|
static K_SEM_DEFINE(lc3_decoder_sem, 0, 1);
|
||||||
|
|
||||||
static bool do_lc3_decode(struct broadcast_sink_stream *sink_stream,
|
static void do_lc3_decode(lc3_decoder_t decoder, const void *in_data, uint8_t octets_per_frame,
|
||||||
int16_t audio_buf[LC3_MAX_NUM_SAMPLES_STEREO]);
|
int16_t out_data[LC3_MAX_NUM_SAMPLES_MONO]);
|
||||||
static void lc3_decoder_thread(void *arg1, void *arg2, void *arg3);
|
static void lc3_decoder_thread(void *arg1, void *arg2, void *arg3);
|
||||||
K_THREAD_DEFINE(decoder_tid, LC3_ENCODER_STACK_SIZE, lc3_decoder_thread,
|
K_THREAD_DEFINE(decoder_tid, LC3_ENCODER_STACK_SIZE, lc3_decoder_thread,
|
||||||
NULL, NULL, NULL, LC3_ENCODER_PRIORITY, 0, -1);
|
NULL, NULL, NULL, LC3_ENCODER_PRIORITY, 0, -1);
|
||||||
|
|
||||||
/* Consumer thread of the decoded stream data */
|
|
||||||
static void lc3_decoder_thread(void *arg1, void *arg2, void *arg3)
|
|
||||||
{
|
|
||||||
while (true) {
|
|
||||||
static int16_t lc3_audio_buf[LC3_MAX_NUM_SAMPLES_STEREO];
|
|
||||||
|
|
||||||
k_sem_take(&lc3_decoder_sem, K_FOREVER);
|
|
||||||
#if defined(CONFIG_USB_DEVICE_AUDIO)
|
|
||||||
|
|
||||||
/* For now we only handle one BIS, so always only decode the first element
|
|
||||||
* in streams.
|
|
||||||
*/
|
|
||||||
struct broadcast_sink_stream *stream_for_usb = &streams[0];
|
|
||||||
|
|
||||||
/* Not enough space to store data */
|
|
||||||
if (ring_buf_space_get(&usb_ring_buf) < sizeof(lc3_audio_buf)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* lc3_audio_buf will be filled with the last decoded value, so e.g. if the stream
|
|
||||||
* contains both left and right, the lc3_audio_buf will always contain right.
|
|
||||||
*/
|
|
||||||
if (do_lc3_decode(stream_for_usb, lc3_audio_buf)) {
|
|
||||||
mix_mono_to_stereo(lc3_audio_buf);
|
|
||||||
add_to_usb_ring_buf(lc3_audio_buf);
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
for (size_t i = 0; i < ARRAY_SIZE(streams); i++) {
|
|
||||||
(void)do_lc3_decode(&streams[i], lc3_audio_buf);
|
|
||||||
}
|
|
||||||
#endif /* #if defined(CONFIG_USB_DEVICE_AUDIO) */
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static size_t get_chan_cnt(enum bt_audio_location chan_allocation)
|
static size_t get_chan_cnt(enum bt_audio_location chan_allocation)
|
||||||
{
|
{
|
||||||
size_t cnt = 0U;
|
size_t cnt = 0U;
|
||||||
|
@ -195,61 +161,171 @@ static size_t get_chan_cnt(enum bt_audio_location chan_allocation)
|
||||||
return cnt;
|
return cnt;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Decode LC3 data on a stream and returns true if successful */
|
/* Consumer thread of the decoded stream data */
|
||||||
static bool do_lc3_decode(struct broadcast_sink_stream *sink_stream,
|
static void lc3_decoder_thread(void *arg1, void *arg2, void *arg3)
|
||||||
int16_t audio_buf[LC3_MAX_NUM_SAMPLES_STEREO])
|
|
||||||
{
|
{
|
||||||
const uint8_t frames_blocks_per_sdu = sink_stream->lc3_frames_blocks_per_sdu;
|
while (true) {
|
||||||
const uint16_t octets_per_frame = sink_stream->lc3_octets_per_frame;
|
#if defined(CONFIG_USB_DEVICE_AUDIO)
|
||||||
uint16_t frames_per_block;
|
static int16_t right_frames[CONFIG_MAX_CODEC_FRAMES_PER_SDU]
|
||||||
struct net_buf *buf;
|
[LC3_MAX_NUM_SAMPLES_MONO];
|
||||||
|
static int16_t left_frames[CONFIG_MAX_CODEC_FRAMES_PER_SDU]
|
||||||
|
[LC3_MAX_NUM_SAMPLES_MONO];
|
||||||
|
size_t right_frames_cnt = 0;
|
||||||
|
size_t left_frames_cnt = 0;
|
||||||
|
|
||||||
k_mutex_lock(&sink_stream->lc3_decoder_mutex, K_FOREVER);
|
memset(right_frames, 0, sizeof(right_frames));
|
||||||
|
memset(left_frames, 0, sizeof(left_frames));
|
||||||
|
#else
|
||||||
|
static int16_t lc3_audio_buf[LC3_MAX_NUM_SAMPLES_MONO];
|
||||||
|
#endif /* CONFIG_USB_DEVICE_AUDIO */
|
||||||
|
|
||||||
if (sink_stream->in_buf == NULL) {
|
k_sem_take(&lc3_decoder_sem, K_FOREVER);
|
||||||
k_mutex_unlock(&sink_stream->lc3_decoder_mutex);
|
|
||||||
|
|
||||||
return false;
|
for (size_t i = 0; i < ARRAY_SIZE(streams); i++) {
|
||||||
}
|
struct broadcast_sink_stream *stream = &streams[i];
|
||||||
|
const uint8_t frames_blocks_per_sdu = stream->lc3_frames_blocks_per_sdu;
|
||||||
|
const uint16_t octets_per_frame = stream->lc3_octets_per_frame;
|
||||||
|
uint16_t frames_per_block;
|
||||||
|
struct net_buf *buf;
|
||||||
|
|
||||||
buf = net_buf_ref(sink_stream->in_buf);
|
k_mutex_lock(&stream->lc3_decoder_mutex, K_FOREVER);
|
||||||
net_buf_unref(sink_stream->in_buf);
|
|
||||||
sink_stream->in_buf = NULL;
|
|
||||||
k_mutex_unlock(&sink_stream->lc3_decoder_mutex);
|
|
||||||
|
|
||||||
frames_per_block = get_chan_cnt(sink_stream->chan_allocation);
|
if (stream->in_buf == NULL) {
|
||||||
if (buf->len != (frames_per_block * octets_per_frame * frames_blocks_per_sdu)) {
|
k_mutex_unlock(&stream->lc3_decoder_mutex);
|
||||||
printk("Expected %u frame blocks with %u frames of size %u, but length is %u\n",
|
|
||||||
frames_blocks_per_sdu, frames_per_block, octets_per_frame, buf->len);
|
|
||||||
|
|
||||||
net_buf_unref(buf);
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
return false;
|
buf = net_buf_ref(stream->in_buf);
|
||||||
}
|
net_buf_unref(stream->in_buf);
|
||||||
|
stream->in_buf = NULL;
|
||||||
|
k_mutex_unlock(&stream->lc3_decoder_mutex);
|
||||||
|
|
||||||
for (uint8_t i = 0U; i < frames_blocks_per_sdu; i++) {
|
frames_per_block = get_chan_cnt(stream->chan_allocation);
|
||||||
for (uint16_t j = 0U; j < frames_per_block; j++) {
|
if (buf->len !=
|
||||||
const void *data = net_buf_pull_mem(buf, octets_per_frame);
|
(frames_per_block * octets_per_frame * frames_blocks_per_sdu)) {
|
||||||
int err;
|
printk("Expected %u frame blocks with %u frames of size %u, but "
|
||||||
|
"length is %u\n",
|
||||||
err = lc3_decode(sink_stream->lc3_decoder, data, octets_per_frame,
|
frames_blocks_per_sdu, frames_per_block, octets_per_frame,
|
||||||
LC3_PCM_FORMAT_S16, audio_buf, 1);
|
buf->len);
|
||||||
|
|
||||||
if (err == 1) {
|
|
||||||
printk(" decoder performed PLC\n");
|
|
||||||
} else if (err < 0) {
|
|
||||||
printk(" decoder failed - wrong parameters? (err = %d)\n", err);
|
|
||||||
|
|
||||||
net_buf_unref(buf);
|
net_buf_unref(buf);
|
||||||
|
|
||||||
return false;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if defined(CONFIG_USB_DEVICE_AUDIO)
|
||||||
|
const bool has_left =
|
||||||
|
(stream->chan_allocation & BT_AUDIO_LOCATION_FRONT_LEFT) != 0;
|
||||||
|
const bool has_right =
|
||||||
|
(stream->chan_allocation & BT_AUDIO_LOCATION_FRONT_RIGHT) != 0;
|
||||||
|
const bool is_mono =
|
||||||
|
stream->chan_allocation == BT_AUDIO_LOCATION_MONO_AUDIO;
|
||||||
|
|
||||||
|
/* Split the SDU into frames*/
|
||||||
|
for (uint8_t i = 0U; i < frames_blocks_per_sdu; i++) {
|
||||||
|
for (uint16_t j = 0U; j < frames_per_block; j++) {
|
||||||
|
const bool is_left = j == 0 && has_left;
|
||||||
|
const bool is_right =
|
||||||
|
has_right && (j == 0 || (j == 1 && has_left));
|
||||||
|
const void *data = net_buf_pull_mem(buf, octets_per_frame);
|
||||||
|
int16_t *out_frame;
|
||||||
|
|
||||||
|
if (is_left) {
|
||||||
|
out_frame = left_frames[left_frames_cnt++];
|
||||||
|
} else if (is_right) {
|
||||||
|
out_frame = right_frames[right_frames_cnt++];
|
||||||
|
} else if (is_mono) {
|
||||||
|
/* Use left as mono*/
|
||||||
|
out_frame = left_frames[left_frames_cnt++];
|
||||||
|
} else {
|
||||||
|
/* unused channel */
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
do_lc3_decode(stream->lc3_decoder, data, octets_per_frame,
|
||||||
|
out_frame);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
/* Dummy behavior: Decode and discard data */
|
||||||
|
for (uint8_t i = 0U; i < frames_blocks_per_sdu; i++) {
|
||||||
|
for (uint16_t j = 0U; j < frames_per_block; j++) {
|
||||||
|
const void *data = net_buf_pull_mem(buf, octets_per_frame);
|
||||||
|
|
||||||
|
do_lc3_decode(stream->lc3_decoder, data, octets_per_frame,
|
||||||
|
lc3_audio_buf);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif /* CONFIG_USB_DEVICE_AUDIO */
|
||||||
|
|
||||||
|
net_buf_unref(buf);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if defined(CONFIG_USB_DEVICE_AUDIO)
|
||||||
|
const bool is_left_only = right_frames_cnt == 0U;
|
||||||
|
const bool is_right_only = left_frames_cnt == 0U;
|
||||||
|
|
||||||
|
if (!is_left_only && !is_right_only && left_frames_cnt != right_frames_cnt) {
|
||||||
|
printk("Mismatch between number of left (%zu) and right (%zu) frames, "
|
||||||
|
"discard SDU",
|
||||||
|
left_frames_cnt, right_frames_cnt);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Send frames to USB - If we only have a single channel we mix it to stereo */
|
||||||
|
for (size_t i = 0U; i < MAX(left_frames_cnt, right_frames_cnt); i++) {
|
||||||
|
const bool is_single_channel = is_left_only || is_right_only;
|
||||||
|
static int16_t stereo_frame[LC3_MAX_NUM_SAMPLES_STEREO];
|
||||||
|
int16_t *right_frame = right_frames[i];
|
||||||
|
int16_t *left_frame = left_frames[i];
|
||||||
|
|
||||||
|
/* Not enough space to store data */
|
||||||
|
if (ring_buf_space_get(&usb_ring_buf) < sizeof(stereo_frame)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
memset(stereo_frame, 0, sizeof(stereo_frame));
|
||||||
|
|
||||||
|
/* Generate the stereo frame
|
||||||
|
*
|
||||||
|
* If we only have single channel then that is always stored in the
|
||||||
|
* left_frame, and we mix that to stereo
|
||||||
|
*/
|
||||||
|
for (int j = 0; j < LC3_MAX_NUM_SAMPLES_MONO; j++) {
|
||||||
|
if (is_single_channel) {
|
||||||
|
/* Mix to stereo */
|
||||||
|
if (is_left_only) {
|
||||||
|
stereo_frame[j * 2] = left_frame[j];
|
||||||
|
stereo_frame[j * 2 + 1] = left_frame[j];
|
||||||
|
} else if (is_right_only) {
|
||||||
|
stereo_frame[j * 2] = right_frame[j];
|
||||||
|
stereo_frame[j * 2 + 1] = right_frame[j];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
stereo_frame[j * 2] = left_frame[j];
|
||||||
|
stereo_frame[j * 2 + 1] = right_frame[j];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
add_to_usb_ring_buf(stereo_frame);
|
||||||
|
}
|
||||||
|
#endif /* CONFIG_USB_DEVICE_AUDIO */
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
net_buf_unref(buf);
|
/** Decode LC3 data on a stream and returns true if successful */
|
||||||
|
static void do_lc3_decode(lc3_decoder_t decoder, const void *in_data, uint8_t octets_per_frame,
|
||||||
|
int16_t out_data[LC3_MAX_NUM_SAMPLES_MONO])
|
||||||
|
{
|
||||||
|
int err;
|
||||||
|
|
||||||
return true;
|
err = lc3_decode(decoder, in_data, octets_per_frame, LC3_PCM_FORMAT_S16, out_data, 1);
|
||||||
|
if (err == 1) {
|
||||||
|
printk(" decoder performed PLC\n");
|
||||||
|
} else if (err < 0) {
|
||||||
|
printk(" decoder failed - wrong parameters? (err = %d)\n", err);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static int lc3_enable(struct broadcast_sink_stream *sink_stream)
|
static int lc3_enable(struct broadcast_sink_stream *sink_stream)
|
||||||
|
@ -342,21 +418,6 @@ static int lc3_enable(struct broadcast_sink_stream *sink_stream)
|
||||||
#endif /* defined(CONFIG_LIBLC3) */
|
#endif /* defined(CONFIG_LIBLC3) */
|
||||||
|
|
||||||
#if defined(CONFIG_USB_DEVICE_AUDIO)
|
#if defined(CONFIG_USB_DEVICE_AUDIO)
|
||||||
/* Duplicate the audio from one channel and put it in both channels */
|
|
||||||
static void mix_mono_to_stereo(int16_t audio_buf[LC3_MAX_NUM_SAMPLES_STEREO])
|
|
||||||
{
|
|
||||||
/* Interleave the channel sample inline
|
|
||||||
* Take the first LC3_MAX_NUM_SAMPLES_MONO samples from audio_buf and mix it to
|
|
||||||
* interleaved stereo, so that 012345 becomes 001122334455
|
|
||||||
*/
|
|
||||||
for (int i = LC3_MAX_NUM_SAMPLES_MONO - 1; i >= 0; i--) {
|
|
||||||
const int16_t sample = audio_buf[i];
|
|
||||||
|
|
||||||
audio_buf[i * 2] = sample;
|
|
||||||
audio_buf[i * 2 + 1] = sample;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Move the LC3 data to the USB ring buffer */
|
/* Move the LC3 data to the USB ring buffer */
|
||||||
static void add_to_usb_ring_buf(const int16_t audio_buf[LC3_MAX_NUM_SAMPLES_STEREO])
|
static void add_to_usb_ring_buf(const int16_t audio_buf[LC3_MAX_NUM_SAMPLES_STEREO])
|
||||||
{
|
{
|
||||||
|
@ -508,67 +569,221 @@ static struct bt_bap_stream_ops stream_ops = {
|
||||||
};
|
};
|
||||||
|
|
||||||
#if defined(CONFIG_TARGET_BROADCAST_CHANNEL)
|
#if defined(CONFIG_TARGET_BROADCAST_CHANNEL)
|
||||||
|
struct find_valid_bis_data {
|
||||||
|
struct {
|
||||||
|
uint8_t index;
|
||||||
|
enum bt_audio_location chan_allocation;
|
||||||
|
} bis[BT_ISO_BIS_INDEX_MAX];
|
||||||
|
|
||||||
|
uint8_t cnt;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is called for each BIS in a subgroup
|
||||||
|
*
|
||||||
|
* It returns `false` if the current BIS contains all of the channels we are looking for,
|
||||||
|
* or if it does not contain any and we are looking for BT_AUDIO_LOCATION_MONO_AUDIO. This stops
|
||||||
|
* the iteration of the remaining BIS in the subgroup.
|
||||||
|
*
|
||||||
|
* It returns `true` if the BIS either contains none or some of the channels we are looking for.
|
||||||
|
* If it contains some, then that is being stored in the user_data, so that the calling function
|
||||||
|
* can check if a combination of the BIS satisfy the channel allocations we want.
|
||||||
|
*/
|
||||||
static bool find_valid_bis_cb(const struct bt_bap_base_subgroup_bis *bis,
|
static bool find_valid_bis_cb(const struct bt_bap_base_subgroup_bis *bis,
|
||||||
void *user_data)
|
void *user_data)
|
||||||
{
|
{
|
||||||
int err;
|
struct find_valid_bis_data *data = user_data;
|
||||||
struct bt_audio_codec_cfg codec_cfg = {0};
|
struct bt_audio_codec_cfg codec_cfg = {0};
|
||||||
enum bt_audio_location chan_allocation;
|
enum bt_audio_location chan_allocation;
|
||||||
uint8_t *bis_index = user_data;
|
int err;
|
||||||
|
|
||||||
err = bt_bap_base_subgroup_bis_codec_to_codec_cfg(bis, &codec_cfg);
|
err = bt_bap_base_subgroup_bis_codec_to_codec_cfg(bis, &codec_cfg);
|
||||||
if (err != 0) {
|
if (err != 0) {
|
||||||
printk("Could not find codec configuration (err=%d)\n", err);
|
printk("Could not get codec configuration for BIS: %d\n", err);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
err = bt_audio_codec_cfg_get_chan_allocation(&codec_cfg, &chan_allocation);
|
err = bt_audio_codec_cfg_get_chan_allocation(&codec_cfg, &chan_allocation);
|
||||||
if (err != 0) {
|
if (err != 0) {
|
||||||
printk("Could not find channel allocation (err=%d)\n", err);
|
printk("Could not find channel allocation for BIS: %d\n", err);
|
||||||
if (err == -ENODATA && strlen(CONFIG_TARGET_BROADCAST_NAME) > 0U) {
|
|
||||||
|
/* Absence of channel allocation is implicitly mono as per the BAP spec */
|
||||||
|
if (CONFIG_TARGET_BROADCAST_CHANNEL == BT_AUDIO_LOCATION_MONO_AUDIO) {
|
||||||
|
data->bis[0].index = bis->index;
|
||||||
|
data->bis[0].chan_allocation = chan_allocation;
|
||||||
|
data->cnt = 1;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
} else if (err == -ENODATA && strlen(CONFIG_TARGET_BROADCAST_NAME) > 0U) {
|
||||||
/* Accept no channel allocation data available
|
/* Accept no channel allocation data available
|
||||||
* if TARGET_BROADCAST_NAME defined. Use current index.
|
* if TARGET_BROADCAST_NAME defined. Use current index.
|
||||||
*/
|
*/
|
||||||
*bis_index = bis->index;
|
data->bis[0].index = bis->index;
|
||||||
|
data->bis[0].chan_allocation = chan_allocation;
|
||||||
|
data->cnt = 1;
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
if ((chan_allocation & CONFIG_TARGET_BROADCAST_CHANNEL) ==
|
||||||
|
CONFIG_TARGET_BROADCAST_CHANNEL) {
|
||||||
|
/* Found single BIS with all channels we want - keep as only and stop
|
||||||
|
* parsing
|
||||||
|
*/
|
||||||
|
data->bis[0].index = bis->index;
|
||||||
|
data->bis[0].chan_allocation = chan_allocation;
|
||||||
|
data->cnt = 1;
|
||||||
|
|
||||||
return true;
|
return false;
|
||||||
}
|
} else if ((chan_allocation & CONFIG_TARGET_BROADCAST_CHANNEL) != 0) {
|
||||||
|
/* BIS contains part of what we are looking for - Store and see if there are
|
||||||
if ((CONFIG_TARGET_BROADCAST_CHANNEL == BT_AUDIO_LOCATION_MONO_AUDIO &&
|
* other BIS that may fill the gaps
|
||||||
chan_allocation == BT_AUDIO_LOCATION_MONO_AUDIO) ||
|
*/
|
||||||
chan_allocation & CONFIG_TARGET_BROADCAST_CHANNEL) {
|
data->bis[data->cnt].index = bis->index;
|
||||||
*bis_index = bis->index;
|
data->bis[data->cnt].chan_allocation = chan_allocation;
|
||||||
|
data->cnt++;
|
||||||
return false;
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function searches all the BIS in a subgroup for a set of BIS indexes that satisfy
|
||||||
|
* CONFIG_TARGET_BROADCAST_CHANNEL
|
||||||
|
*
|
||||||
|
* Returns `true` if the right channels were found, otherwise `false`.
|
||||||
|
*/
|
||||||
|
static bool find_valid_bis_in_subgroup_bis(const struct bt_bap_base_subgroup *subgroup,
|
||||||
|
uint32_t *bis_indexes)
|
||||||
|
{
|
||||||
|
struct find_valid_bis_data data = {0};
|
||||||
|
int err;
|
||||||
|
|
||||||
|
err = bt_bap_base_subgroup_foreach_bis(subgroup, find_valid_bis_cb, &data);
|
||||||
|
if (err == -ECANCELED) {
|
||||||
|
/* We found what we are looking for in a single BIS */
|
||||||
|
|
||||||
|
*bis_indexes = BIT(data.bis[0].index);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
} else if (err == 0) {
|
||||||
|
/* We are finished parsing all BIS - Try to find a combination that satisfy our
|
||||||
|
* channel allocation. For simplicity this is using a greedy approach, rather than
|
||||||
|
* an optimal one.
|
||||||
|
*/
|
||||||
|
enum bt_audio_location chan_allocation = BT_AUDIO_LOCATION_MONO_AUDIO;
|
||||||
|
*bis_indexes = 0;
|
||||||
|
|
||||||
|
for (uint8_t i = 0U; i < data.cnt; i++) {
|
||||||
|
chan_allocation |= data.bis[i].chan_allocation;
|
||||||
|
*bis_indexes |= BIT(data.bis[i].index);
|
||||||
|
|
||||||
|
if ((chan_allocation & CONFIG_TARGET_BROADCAST_CHANNEL) ==
|
||||||
|
CONFIG_TARGET_BROADCAST_CHANNEL) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Some error occurred or we did not find expected channel allocation */
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called for each subgroup in the BASE. Will populate the 32-bit bitfield of BIS indexes if the
|
||||||
|
* subgroup contains it.
|
||||||
|
*
|
||||||
|
* The channel allocation may
|
||||||
|
* - Not exist at all, implicitly meaning BT_AUDIO_LOCATION_MONO_AUDIO
|
||||||
|
* - Exist only in the subgroup codec configuration
|
||||||
|
* - Exist only in the BIS codec configuration
|
||||||
|
* - Exist in both the subgroup and BIS codec configuration, in which case, the BIS codec
|
||||||
|
* configuration overwrites the subgroup values
|
||||||
|
*
|
||||||
|
* This function returns `true` if the subgroup does not support the channels in
|
||||||
|
* CONFIG_TARGET_BROADCAST_CHANNEL which makes it iterate over the next subgroup, and returns
|
||||||
|
* `false` if this subgroup satisfies our CONFIG_TARGET_BROADCAST_CHANNEL.
|
||||||
|
*/
|
||||||
static bool find_valid_bis_in_subgroup_cb(const struct bt_bap_base_subgroup *subgroup,
|
static bool find_valid_bis_in_subgroup_cb(const struct bt_bap_base_subgroup *subgroup,
|
||||||
void *user_data)
|
void *user_data)
|
||||||
{
|
{
|
||||||
return bt_bap_base_subgroup_foreach_bis(subgroup, find_valid_bis_cb, user_data)
|
enum bt_audio_location chan_allocation;
|
||||||
== -ECANCELED ? false : true;
|
struct bt_audio_codec_cfg codec_cfg;
|
||||||
|
uint32_t *bis_indexes = user_data;
|
||||||
|
int err;
|
||||||
|
|
||||||
|
/* We only want indexes from a single subgroup, so reset between each of them*/
|
||||||
|
*bis_indexes = 0U;
|
||||||
|
|
||||||
|
err = bt_bap_base_subgroup_codec_to_codec_cfg(subgroup, &codec_cfg);
|
||||||
|
if (err != 0) {
|
||||||
|
printk("Could not get codec configuration: %d\n", err);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
err = bt_audio_codec_cfg_get_chan_allocation(&codec_cfg, &chan_allocation);
|
||||||
|
if (err != 0) {
|
||||||
|
printk("Could not find subgroup channel allocation: %d - Looking in the BISes\n",
|
||||||
|
err);
|
||||||
|
|
||||||
|
/* Find chan alloc in BIS */
|
||||||
|
if (find_valid_bis_in_subgroup_bis(subgroup, bis_indexes)) {
|
||||||
|
/* Found BISes with correct channel allocation */
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
/* If the subgroup contains a single channel, then we just grab the first BIS index
|
||||||
|
*/
|
||||||
|
if (get_chan_cnt(chan_allocation) == 1 &&
|
||||||
|
chan_allocation == CONFIG_TARGET_BROADCAST_CHANNEL) {
|
||||||
|
uint32_t subgroup_bis_indexes;
|
||||||
|
|
||||||
|
/* Set bis_indexes to the first bit set */
|
||||||
|
err = bt_bap_base_subgroup_get_bis_indexes(subgroup, &subgroup_bis_indexes);
|
||||||
|
if (err != 0) {
|
||||||
|
/* Should never happen as that would indicate an invalid
|
||||||
|
* subgroup If it does, we just parse the next subgroup
|
||||||
|
*/
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* We found the BIS index we want, stop parsing*/
|
||||||
|
*bis_indexes = BIT(find_lsb_set(subgroup_bis_indexes) - 1);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
} else if ((chan_allocation & CONFIG_TARGET_BROADCAST_CHANNEL) ==
|
||||||
|
CONFIG_TARGET_BROADCAST_CHANNEL) {
|
||||||
|
/* The subgroup contains all channels we are looking for/
|
||||||
|
* We continue searching each BIS to get the minimal amount of BIS that
|
||||||
|
* satisfy CONFIG_TARGET_BROADCAST_CHANNEL.
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (find_valid_bis_in_subgroup_bis(subgroup, bis_indexes)) {
|
||||||
|
/* Found BISes with correct channel allocation */
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int base_get_first_valid_bis(const struct bt_bap_base *base, uint32_t *bis_index)
|
/**
|
||||||
|
* This function gets a 32-bit bitfield of BIS indexes that cover the channel allocation values in
|
||||||
|
* CONFIG_TARGET_BROADCAST_CHANNEL.
|
||||||
|
*/
|
||||||
|
static int base_get_valid_bis_indexes(const struct bt_bap_base *base, uint32_t *bis_indexes)
|
||||||
{
|
{
|
||||||
int err;
|
int err;
|
||||||
uint8_t valid_bis_index = 0U;
|
|
||||||
|
|
||||||
err = bt_bap_base_foreach_subgroup(base, find_valid_bis_in_subgroup_cb, &valid_bis_index);
|
err = bt_bap_base_foreach_subgroup(base, find_valid_bis_in_subgroup_cb, bis_indexes);
|
||||||
if (err != -ECANCELED) {
|
if (err != -ECANCELED) {
|
||||||
printk("Failed to parse subgroups: %d\n", err);
|
printk("Failed to parse subgroups: %d\n", err);
|
||||||
return err != 0 ? err : -ENOENT;
|
return err != 0 ? err : -ENOENT;
|
||||||
}
|
}
|
||||||
|
|
||||||
*bis_index = 0;
|
|
||||||
*bis_index |= ((uint8_t)1 << valid_bis_index);
|
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
#endif /* CONFIG_TARGET_BROADCAST_CHANNEL */
|
#endif /* CONFIG_TARGET_BROADCAST_CHANNEL */
|
||||||
|
@ -587,7 +802,7 @@ static void base_recv_cb(struct bt_bap_broadcast_sink *sink, const struct bt_bap
|
||||||
bt_bap_base_get_subgroup_count(base), sink);
|
bt_bap_base_get_subgroup_count(base), sink);
|
||||||
|
|
||||||
#if defined(CONFIG_TARGET_BROADCAST_CHANNEL)
|
#if defined(CONFIG_TARGET_BROADCAST_CHANNEL)
|
||||||
err = base_get_first_valid_bis(base, &base_bis_index_bitfield);
|
err = base_get_valid_bis_indexes(base, &base_bis_index_bitfield);
|
||||||
if (err != 0) {
|
if (err != 0) {
|
||||||
printk("Failed to find a valid BIS\n");
|
printk("Failed to find a valid BIS\n");
|
||||||
return;
|
return;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue