Bluetooth: Audio: Add BAP broadcast source support
Add the BAP broadcast source implementation. This role allows a device to broadcast ISO data. Signed-off-by: Emil Gydesen <emil.gydesen@nordicsemi.no>
This commit is contained in:
parent
f24d4bcf75
commit
ab87e0a2ba
7 changed files with 783 additions and 8 deletions
|
@ -30,6 +30,8 @@
|
|||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define BT_AUDIO_BROADCAST_ID_SIZE 3 /* octets */
|
||||
|
||||
/* Audio Context Type, Generic Audio */
|
||||
#define BT_AUDIO_CONTEXT_TYPE_PROHIBITED BIT(0)
|
||||
#define BT_AUDIO_CONTEXT_TYPE_UNSPECIFIED BIT(1)
|
||||
|
|
|
@ -48,3 +48,4 @@ zephyr_library_sources_ifdef(CONFIG_BT_AUDIO_STREAM stream.c)
|
|||
zephyr_library_sources_ifdef(CONFIG_BT_AUDIO_UNICAST_SERVER unicast_server.c)
|
||||
zephyr_library_sources_ifdef(CONFIG_BT_AUDIO_CAPABILITIES capabilities.c)
|
||||
zephyr_library_sources_ifdef(CONFIG_BT_AUDIO_UNICAST_CLIENT unicast_client.c)
|
||||
zephyr_library_sources_ifdef(CONFIG_BT_AUDIO_BROADCAST_SOURCE broadcast_source.c)
|
||||
|
|
|
@ -120,13 +120,34 @@ config BT_AUDIO_UNICAST_CLIENT_ASE_SRC_COUNT
|
|||
endif # BT_AUDIO_UNICAST_CLIENT
|
||||
endif # BT_AUDIO_UNICAST
|
||||
|
||||
config BT_AUDIO_BROADCAST
|
||||
bool "Bluetooth Broadcast Audio Support"
|
||||
config BT_AUDIO_BROADCAST_SOURCE
|
||||
bool "Bluetooth Broadcast Source Audio Support [EXPERIMENTAL]"
|
||||
select EXPERIMENTAL
|
||||
select BT_ISO_BROADCASTER
|
||||
select BT_ISO_SYNC_RECEIVER
|
||||
help
|
||||
This option enables support for Bluetooth Broadcast Audio using
|
||||
This option enables support for Bluetooth Broadcast Source Audio using
|
||||
Isochronous channels.
|
||||
if BT_AUDIO_BROADCAST_SOURCE
|
||||
|
||||
config BT_AUDIO_BROADCAST_SRC_COUNT
|
||||
int "Basic Audio Broadcaster source count"
|
||||
default 1
|
||||
range 0 BT_ISO_MAX_BIG
|
||||
help
|
||||
This option sets the number of broadcast sources to support.
|
||||
One broadcast source can send multiple streams
|
||||
(up to BT_AUDIO_BROADCAST_SRC_STREAM_COUNT per broadcast source).
|
||||
|
||||
|
||||
config BT_AUDIO_BROADCAST_SRC_STREAM_COUNT
|
||||
int "Basic Audio Broadcast Source Stream count"
|
||||
default 1
|
||||
range 0 BT_ISO_MAX_CHAN
|
||||
help
|
||||
This option sets the maximum number of streams per broadcast source
|
||||
to support.
|
||||
|
||||
endif # BT_AUDIO_BROADCAST_SOURCE
|
||||
|
||||
config BT_AUDIO_DEBUG
|
||||
bool "Enable debug logs"
|
||||
|
@ -179,12 +200,19 @@ config BT_AUDIO_DEBUG_UNICAST_CLIENT
|
|||
Use this option to enable Basic Audio Profile debug logs for the
|
||||
Bluetooth Audio functionality.
|
||||
|
||||
config BT_AUDIO_DEBUG_BROADCAST_SOURCE
|
||||
bool "Bluetooth Audio Broadcast Source debug"
|
||||
depends on BT_AUDIO_BROADCAST_SOURCE
|
||||
help
|
||||
Use this option to enable Bluetooth Audio Broadcast Source debug logs
|
||||
for the Bluetooth Audio functionality.
|
||||
|
||||
endif # BT_AUDIO_DEBUG
|
||||
|
||||
config BT_AUDIO_STREAM
|
||||
# Virtual/hidden option
|
||||
bool
|
||||
default y if BT_ASCS || BT_AUDIO_UNICAST_CLIENT
|
||||
default y if BT_ASCS || BT_AUDIO_UNICAST_CLIENT || BT_AUDIO_BROADCAST_SOURCE
|
||||
|
||||
config BT_AUDIO_CAPABILITIES
|
||||
# Virtual/hidden option
|
||||
|
|
739
subsys/bluetooth/audio/broadcast_source.c
Normal file
739
subsys/bluetooth/audio/broadcast_source.c
Normal file
|
@ -0,0 +1,739 @@
|
|||
/* Bluetooth Audio Broadcast Source */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2021-2022 Nordic Semiconductor ASA
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <zephyr.h>
|
||||
#include <sys/byteorder.h>
|
||||
#include <sys/check.h>
|
||||
|
||||
#include <bluetooth/bluetooth.h>
|
||||
#include <bluetooth/conn.h>
|
||||
#include <bluetooth/gatt.h>
|
||||
#include <bluetooth/audio/audio.h>
|
||||
|
||||
#define BT_DBG_ENABLED IS_ENABLED(CONFIG_BT_AUDIO_DEBUG_BROADCAST_SOURCE)
|
||||
#define LOG_MODULE_NAME bt_audio_broadcast_source
|
||||
#include "common/log.h"
|
||||
|
||||
#include "endpoint.h"
|
||||
|
||||
/* internal bt_audio_base_ad* structs are primarily designed to help
|
||||
* calculate the maximum advertising data length
|
||||
*/
|
||||
struct bt_audio_base_ad_bis_specific_data {
|
||||
uint8_t index;
|
||||
uint8_t codec_config_len; /* currently unused and shall always be 0 */
|
||||
} __packed;
|
||||
|
||||
struct bt_audio_base_ad_codec_data {
|
||||
uint8_t type;
|
||||
uint8_t data_len;
|
||||
uint8_t data[CONFIG_BT_CODEC_MAX_DATA_LEN];
|
||||
} __packed;
|
||||
|
||||
struct bt_audio_base_ad_codec_metadata {
|
||||
uint8_t type;
|
||||
uint8_t data_len;
|
||||
uint8_t data[CONFIG_BT_CODEC_MAX_METADATA_LEN];
|
||||
} __packed;
|
||||
|
||||
struct bt_audio_base_ad_subgroup {
|
||||
uint8_t bis_cnt;
|
||||
uint8_t codec_id;
|
||||
uint16_t company_id;
|
||||
uint16_t vendor_id;
|
||||
uint8_t codec_config_len;
|
||||
struct bt_audio_base_ad_codec_data codec_config[CONFIG_BT_CODEC_MAX_DATA_COUNT];
|
||||
uint8_t metadata_len;
|
||||
struct bt_audio_base_ad_codec_metadata metadata[CONFIG_BT_CODEC_MAX_METADATA_COUNT];
|
||||
struct bt_audio_base_ad_bis_specific_data bis_data[BROADCAST_STREAM_CNT];
|
||||
} __packed;
|
||||
|
||||
struct bt_audio_base_ad {
|
||||
uint16_t uuid_val;
|
||||
struct bt_audio_base_ad_subgroup subgroups[BROADCAST_SUBGROUP_CNT];
|
||||
} __packed;
|
||||
|
||||
static struct bt_audio_ep broadcast_source_eps
|
||||
[CONFIG_BT_AUDIO_BROADCAST_SRC_COUNT][BROADCAST_STREAM_CNT];
|
||||
static struct bt_audio_broadcast_source broadcast_sources[CONFIG_BT_AUDIO_BROADCAST_SRC_COUNT];
|
||||
|
||||
static int bt_audio_set_base(const struct bt_audio_broadcast_source *source,
|
||||
struct bt_codec *codec);
|
||||
|
||||
static void broadcast_source_set_ep_state(struct bt_audio_ep *ep, uint8_t state)
|
||||
{
|
||||
uint8_t old_state;
|
||||
|
||||
old_state = ep->status.state;
|
||||
|
||||
BT_DBG("ep %p id 0x%02x %s -> %s", ep, ep->status.id,
|
||||
bt_audio_ep_state_str(old_state),
|
||||
bt_audio_ep_state_str(state));
|
||||
|
||||
|
||||
switch (old_state) {
|
||||
case BT_AUDIO_EP_STATE_IDLE:
|
||||
if (state != BT_AUDIO_EP_STATE_QOS_CONFIGURED) {
|
||||
BT_DBG("Invalid broadcast sync endpoint state transition");
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case BT_AUDIO_EP_STATE_QOS_CONFIGURED:
|
||||
if (state != BT_AUDIO_EP_STATE_IDLE &&
|
||||
state != BT_AUDIO_EP_STATE_STREAMING) {
|
||||
BT_DBG("Invalid broadcast sync endpoint state transition");
|
||||
return;
|
||||
}
|
||||
case BT_AUDIO_EP_STATE_STREAMING:
|
||||
if (state != BT_AUDIO_EP_STATE_QOS_CONFIGURED) {
|
||||
BT_DBG("Invalid broadcast sync endpoint state transition");
|
||||
return;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
BT_ERR("Invalid broadcast sync endpoint state: %s",
|
||||
bt_audio_ep_state_str(old_state));
|
||||
return;
|
||||
}
|
||||
|
||||
ep->status.state = state;
|
||||
|
||||
if (state == BT_AUDIO_EP_STATE_IDLE) {
|
||||
struct bt_audio_stream *stream = ep->stream;
|
||||
|
||||
if (stream != NULL) {
|
||||
stream->ep = NULL;
|
||||
stream->codec = NULL;
|
||||
ep->stream = NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void broadcast_source_iso_recv(struct bt_iso_chan *chan,
|
||||
const struct bt_iso_recv_info *info,
|
||||
struct net_buf *buf)
|
||||
{
|
||||
struct bt_audio_ep *ep = CONTAINER_OF(chan, struct bt_audio_ep, iso);
|
||||
struct bt_audio_stream_ops *ops = ep->stream->ops;
|
||||
|
||||
BT_DBG("stream %p ep %p len %zu", chan, ep, net_buf_frags_len(buf));
|
||||
|
||||
if (ops != NULL && ops->recv != NULL) {
|
||||
ops->recv(ep->stream, buf);
|
||||
}
|
||||
}
|
||||
|
||||
static void broadcast_source_iso_connected(struct bt_iso_chan *chan)
|
||||
{
|
||||
struct bt_audio_ep *ep = CONTAINER_OF(chan, struct bt_audio_ep, iso);
|
||||
struct bt_audio_stream_ops *ops = ep->stream->ops;
|
||||
|
||||
BT_DBG("stream %p ep %p", chan, ep);
|
||||
|
||||
broadcast_source_set_ep_state(ep, BT_AUDIO_EP_STATE_STREAMING);
|
||||
|
||||
if (ops != NULL && ops->connected != NULL) {
|
||||
ops->connected(ep->stream);
|
||||
}
|
||||
}
|
||||
|
||||
static void broadcast_source_iso_disconnected(struct bt_iso_chan *chan, uint8_t reason)
|
||||
{
|
||||
struct bt_audio_ep *ep = CONTAINER_OF(chan, struct bt_audio_ep, iso);
|
||||
struct bt_audio_stream *stream = ep->stream;
|
||||
struct bt_audio_stream_ops *ops = stream->ops;
|
||||
|
||||
BT_DBG("stream %p ep %p reason 0x%02x", chan, ep, reason);
|
||||
|
||||
broadcast_source_set_ep_state(ep, BT_AUDIO_EP_STATE_IDLE);
|
||||
|
||||
if (ops != NULL && ops->disconnected != NULL) {
|
||||
ops->disconnected(stream, reason);
|
||||
}
|
||||
}
|
||||
|
||||
static struct bt_iso_chan_ops broadcast_source_iso_ops = {
|
||||
.recv = broadcast_source_iso_recv,
|
||||
.connected = broadcast_source_iso_connected,
|
||||
.disconnected = broadcast_source_iso_disconnected,
|
||||
};
|
||||
|
||||
bool bt_audio_ep_is_broadcast_src(const struct bt_audio_ep *ep)
|
||||
{
|
||||
for (int i = 0; i < ARRAY_SIZE(broadcast_source_eps); i++) {
|
||||
if (PART_OF_ARRAY(broadcast_source_eps[i], ep)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static void broadcast_source_ep_init(struct bt_audio_ep *ep)
|
||||
{
|
||||
BT_DBG("ep %p", ep);
|
||||
|
||||
(void)memset(ep, 0, sizeof(*ep));
|
||||
ep->iso.ops = &broadcast_source_iso_ops;
|
||||
ep->iso.qos = &ep->iso_qos;
|
||||
ep->iso.qos->rx = &ep->iso_rx;
|
||||
ep->iso.qos->tx = &ep->iso_tx;
|
||||
}
|
||||
|
||||
static struct bt_audio_ep *broadcast_source_new_ep(uint8_t index)
|
||||
{
|
||||
struct bt_audio_ep *cache = NULL;
|
||||
size_t size;
|
||||
|
||||
cache = broadcast_source_eps[index];
|
||||
size = ARRAY_SIZE(broadcast_source_eps[index]);
|
||||
|
||||
for (size_t i = 0; i < ARRAY_SIZE(broadcast_source_eps[index]); i++) {
|
||||
struct bt_audio_ep *ep = &cache[i];
|
||||
|
||||
/* If ep->stream is NULL the endpoint is unallocated */
|
||||
if (ep->stream == NULL) {
|
||||
/* Initialize - It is up to the caller to allocate the
|
||||
* stream pointer.
|
||||
*/
|
||||
broadcast_source_ep_init(ep);
|
||||
return ep;
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int bt_audio_broadcast_source_setup_stream(uint8_t index,
|
||||
struct bt_audio_stream *stream,
|
||||
struct bt_codec *codec,
|
||||
struct bt_codec_qos *qos)
|
||||
{
|
||||
struct bt_audio_ep *ep;
|
||||
int err;
|
||||
|
||||
if (stream->group != NULL) {
|
||||
BT_DBG("Channel %p already in group %p", stream, stream->group);
|
||||
return -EALREADY;
|
||||
}
|
||||
|
||||
ep = broadcast_source_new_ep(index);
|
||||
if (ep == NULL) {
|
||||
BT_DBG("Could not allocate new broadcast endpoint");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
bt_audio_stream_attach(NULL, stream, ep, codec);
|
||||
stream->qos = qos;
|
||||
err = bt_audio_codec_qos_to_iso_qos(stream->iso->qos, qos);
|
||||
if (err) {
|
||||
BT_ERR("Unable to convert codec QoS to ISO QoS");
|
||||
return err;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void bt_audio_encode_base(const struct bt_audio_broadcast_source *source,
|
||||
struct bt_codec *codec,
|
||||
struct net_buf_simple *buf)
|
||||
{
|
||||
uint8_t bis_index;
|
||||
uint8_t *start;
|
||||
uint8_t len;
|
||||
|
||||
__ASSERT(source->subgroup_count == BROADCAST_SUBGROUP_CNT,
|
||||
"Cannot encode BASE with more than a single subgroup");
|
||||
|
||||
net_buf_simple_add_le16(buf, BT_UUID_BASIC_AUDIO_VAL);
|
||||
net_buf_simple_add_le24(buf, source->pd);
|
||||
net_buf_simple_add_u8(buf, source->subgroup_count);
|
||||
/* TODO: The following encoding should be done for each subgroup once
|
||||
* supported
|
||||
*/
|
||||
net_buf_simple_add_u8(buf, source->stream_count);
|
||||
net_buf_simple_add_u8(buf, codec->id);
|
||||
net_buf_simple_add_le16(buf, codec->cid);
|
||||
net_buf_simple_add_le16(buf, codec->vid);
|
||||
|
||||
/* Insert codec configuration data in LTV format */
|
||||
start = net_buf_simple_add(buf, sizeof(len));
|
||||
for (int i = 0; i < codec->data_count; i++) {
|
||||
const struct bt_data *codec_data = &codec->data[i].data;
|
||||
|
||||
net_buf_simple_add_u8(buf, codec_data->data_len);
|
||||
net_buf_simple_add_u8(buf, codec_data->type);
|
||||
net_buf_simple_add_mem(buf, codec_data->data,
|
||||
codec_data->data_len -
|
||||
sizeof(codec_data->type));
|
||||
|
||||
}
|
||||
/* Calcute length of codec config data */
|
||||
len = net_buf_simple_tail(buf) - start - sizeof(len);
|
||||
/* Update the length field */
|
||||
*start = len;
|
||||
|
||||
/* Insert codec metadata in LTV format*/
|
||||
start = net_buf_simple_add(buf, sizeof(len));
|
||||
for (int i = 0; i < codec->meta_count; i++) {
|
||||
const struct bt_data *metadata = &codec->meta[i].data;
|
||||
|
||||
net_buf_simple_add_u8(buf, metadata->data_len);
|
||||
net_buf_simple_add_u8(buf, metadata->type);
|
||||
net_buf_simple_add_mem(buf, metadata->data,
|
||||
metadata->data_len -
|
||||
sizeof(metadata->type));
|
||||
}
|
||||
/* Calcute length of codec config data */
|
||||
len = net_buf_simple_tail(buf) - start - sizeof(len);
|
||||
/* Update the length field */
|
||||
*start = len;
|
||||
|
||||
/* Create BIS index bitfield */
|
||||
bis_index = 0;
|
||||
for (int i = 0; i < source->stream_count; i++) {
|
||||
bis_index++;
|
||||
net_buf_simple_add_u8(buf, bis_index);
|
||||
net_buf_simple_add_u8(buf, 0); /* unused length field */
|
||||
}
|
||||
|
||||
/* NOTE: It is also possible to have the codec configuration data per
|
||||
* BIS index. As our API does not support such specialized BISes we
|
||||
* currently don't do that.
|
||||
*/
|
||||
}
|
||||
|
||||
|
||||
static int generate_broadcast_id(struct bt_audio_broadcast_source *source)
|
||||
{
|
||||
bool unique;
|
||||
|
||||
do {
|
||||
int err;
|
||||
|
||||
err = bt_rand(&source->broadcast_id,
|
||||
BT_AUDIO_BROADCAST_ID_SIZE);
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
|
||||
/* Ensure uniqueness */
|
||||
unique = true;
|
||||
for (int i = 0; i < ARRAY_SIZE(broadcast_sources); i++) {
|
||||
if (&broadcast_sources[i] == source) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (broadcast_sources[i].broadcast_id == source->broadcast_id) {
|
||||
unique = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} while (!unique);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int bt_audio_set_base(const struct bt_audio_broadcast_source *source,
|
||||
struct bt_codec *codec)
|
||||
{
|
||||
struct bt_data base_ad_data;
|
||||
int err;
|
||||
|
||||
/* Broadcast Audio Streaming Endpoint advertising data */
|
||||
NET_BUF_SIMPLE_DEFINE(base_buf, sizeof(struct bt_audio_base_ad));
|
||||
|
||||
bt_audio_encode_base(source, codec, &base_buf);
|
||||
|
||||
base_ad_data.type = BT_DATA_SVC_DATA16;
|
||||
base_ad_data.data_len = base_buf.len;
|
||||
base_ad_data.data = base_buf.data;
|
||||
|
||||
err = bt_le_per_adv_set_data(source->adv, &base_ad_data, 1);
|
||||
if (err != 0) {
|
||||
BT_DBG("Failed to set extended advertising data (err %d)", err);
|
||||
return err;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void broadcast_source_cleanup(struct bt_audio_broadcast_source *source)
|
||||
{
|
||||
for (size_t i = 0; i < source->stream_count; i++) {
|
||||
struct bt_audio_stream *stream = &source->streams[i];
|
||||
|
||||
stream->ep->stream = NULL;
|
||||
stream->ep = NULL;
|
||||
stream->codec = NULL;
|
||||
stream->qos = NULL;
|
||||
stream->iso = NULL;
|
||||
stream->group = NULL;
|
||||
}
|
||||
|
||||
(void)memset(source, 0, sizeof(*source));
|
||||
}
|
||||
|
||||
int bt_audio_broadcast_source_create(struct bt_audio_stream *streams,
|
||||
uint8_t num_stream,
|
||||
struct bt_codec *codec,
|
||||
struct bt_codec_qos *qos,
|
||||
struct bt_audio_broadcast_source **out_source)
|
||||
{
|
||||
struct bt_audio_broadcast_source *source;
|
||||
struct bt_data ad;
|
||||
uint8_t index;
|
||||
int err;
|
||||
|
||||
/* Broadcast Audio Streaming Endpoint advertising data */
|
||||
NET_BUF_SIMPLE_DEFINE(ad_buf,
|
||||
BT_UUID_SIZE_16 + BT_AUDIO_BROADCAST_ID_SIZE);
|
||||
|
||||
/* TODO: Validate codec and qos values */
|
||||
|
||||
/* TODO: The API currently only requires a bt_audio_stream object from
|
||||
* the user. We could modify the API such that the extended (and
|
||||
* periodic advertising enabled) advertiser was provided by the user as
|
||||
* well (similar to the ISO API), or even provide the BIG.
|
||||
*
|
||||
* The caveat of that type of API, instead of this, where we, the stack,
|
||||
* control the advertiser, is that the user will be able to change the
|
||||
* advertising data (thus making the broadcast source non-functional in
|
||||
* terms of BAP compliance), or even stop the advertiser without
|
||||
* stopping the BIG (which also goes against the BAP specification).
|
||||
*/
|
||||
|
||||
CHECKIF(out_source == NULL) {
|
||||
BT_DBG("out_source is NULL");
|
||||
return -EINVAL;
|
||||
}
|
||||
/* Set out_source to NULL until the source has actually been created */
|
||||
*out_source = NULL;
|
||||
|
||||
CHECKIF(streams == NULL) {
|
||||
BT_DBG("streams is NULL");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
CHECKIF(codec == NULL) {
|
||||
BT_DBG("codec is NULL");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
CHECKIF(num_stream > BROADCAST_STREAM_CNT) {
|
||||
BT_DBG("Too many streams provided: %u/%u",
|
||||
num_stream, BROADCAST_STREAM_CNT);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
source = NULL;
|
||||
for (index = 0; index < ARRAY_SIZE(broadcast_sources); index++) {
|
||||
if (broadcast_sources[index].bis[0] == NULL) { /* Find free entry */
|
||||
source = &broadcast_sources[index];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (source == NULL) {
|
||||
BT_DBG("Could not allocate any more broadcast sources");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
source->streams = streams;
|
||||
source->stream_count = num_stream;
|
||||
for (size_t i = 0; i < num_stream; i++) {
|
||||
struct bt_audio_stream *stream = &streams[i];
|
||||
|
||||
err = bt_audio_broadcast_source_setup_stream(index, stream,
|
||||
codec, qos);
|
||||
if (err != 0) {
|
||||
BT_DBG("Failed to setup streams[%zu]: %d", i, err);
|
||||
broadcast_source_cleanup(source);
|
||||
return err;
|
||||
}
|
||||
|
||||
source->bis[i] = &stream->ep->iso;
|
||||
}
|
||||
|
||||
/* Create a non-connectable non-scannable advertising set */
|
||||
err = bt_le_ext_adv_create(BT_LE_EXT_ADV_NCONN_NAME, NULL,
|
||||
&source->adv);
|
||||
if (err != 0) {
|
||||
BT_DBG("Failed to create advertising set (err %d)", err);
|
||||
broadcast_source_cleanup(source);
|
||||
return err;
|
||||
}
|
||||
|
||||
/* Set periodic advertising parameters */
|
||||
err = bt_le_per_adv_set_param(source->adv, BT_LE_PER_ADV_DEFAULT);
|
||||
if (err != 0) {
|
||||
BT_DBG("Failed to set periodic advertising parameters (err %d)",
|
||||
err);
|
||||
broadcast_source_cleanup(source);
|
||||
return err;
|
||||
}
|
||||
|
||||
/* TODO: If updating the API to have a user-supplied advertiser, we
|
||||
* should simply add the data here, instead of changing all of it.
|
||||
* Similar, if the application changes the data, we should ensure
|
||||
* that the audio advertising data is still present, similar to how
|
||||
* the GAP device name is added.
|
||||
*/
|
||||
err = generate_broadcast_id(source);
|
||||
if (err != 0) {
|
||||
BT_DBG("Could not generate broadcast id: %d", err);
|
||||
return err;
|
||||
}
|
||||
net_buf_simple_add_le16(&ad_buf, BT_UUID_BROADCAST_AUDIO_VAL);
|
||||
net_buf_simple_add_le24(&ad_buf, source->broadcast_id);
|
||||
ad.type = BT_DATA_SVC_DATA16;
|
||||
ad.data_len = ad_buf.len + sizeof(ad.type);
|
||||
ad.data = ad_buf.data;
|
||||
err = bt_le_ext_adv_set_data(source->adv, &ad, 1, NULL, 0);
|
||||
if (err != 0) {
|
||||
BT_DBG("Failed to set extended advertising data (err %d)", err);
|
||||
broadcast_source_cleanup(source);
|
||||
return err;
|
||||
}
|
||||
|
||||
source->subgroup_count = BROADCAST_SUBGROUP_CNT;
|
||||
source->pd = qos->pd;
|
||||
err = bt_audio_set_base(source, codec);
|
||||
if (err != 0) {
|
||||
BT_DBG("Failed to set base data (err %d)", err);
|
||||
broadcast_source_cleanup(source);
|
||||
return err;
|
||||
}
|
||||
|
||||
/* Enable Periodic Advertising */
|
||||
err = bt_le_per_adv_start(source->adv);
|
||||
if (err != 0) {
|
||||
BT_DBG("Failed to enable periodic advertising (err %d)", err);
|
||||
broadcast_source_cleanup(source);
|
||||
return err;
|
||||
}
|
||||
|
||||
/* Start extended advertising */
|
||||
err = bt_le_ext_adv_start(source->adv,
|
||||
BT_LE_EXT_ADV_START_DEFAULT);
|
||||
if (err != 0) {
|
||||
BT_DBG("Failed to start extended advertising (err %d)", err);
|
||||
broadcast_source_cleanup(source);
|
||||
return err;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < source->stream_count; i++) {
|
||||
struct bt_audio_ep *ep = streams[i].ep;
|
||||
|
||||
ep->broadcast_source = source;
|
||||
broadcast_source_set_ep_state(ep,
|
||||
BT_AUDIO_EP_STATE_QOS_CONFIGURED);
|
||||
}
|
||||
|
||||
source->qos = qos;
|
||||
|
||||
BT_DBG("Broadcasting with ID 0x%6X", source->broadcast_id);
|
||||
|
||||
*out_source = source;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int bt_audio_broadcast_source_reconfig(struct bt_audio_broadcast_source *source,
|
||||
struct bt_codec *codec,
|
||||
struct bt_codec_qos *qos)
|
||||
{
|
||||
struct bt_audio_stream *stream;
|
||||
int err;
|
||||
|
||||
CHECKIF(source == NULL) {
|
||||
BT_DBG("source is NULL");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
stream = &source->streams[0];
|
||||
|
||||
if (stream == NULL) {
|
||||
BT_DBG("stream is NULL");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (stream->ep == NULL) {
|
||||
BT_DBG("stream->ep is NULL");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (stream->ep->status.state != BT_AUDIO_EP_STATE_QOS_CONFIGURED) {
|
||||
BT_DBG("Broadcast source stream %p invalid state: %u",
|
||||
stream, stream->ep->status.state);
|
||||
return -EBADMSG;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < source->stream_count; i++) {
|
||||
stream = &source->streams[i];
|
||||
|
||||
bt_audio_stream_attach(NULL, stream, stream->ep, codec);
|
||||
}
|
||||
|
||||
err = bt_audio_set_base(source, codec);
|
||||
if (err != 0) {
|
||||
BT_DBG("Failed to set base data (err %d)", err);
|
||||
return err;
|
||||
}
|
||||
|
||||
source->qos = qos;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int bt_audio_broadcast_source_start(struct bt_audio_broadcast_source *source)
|
||||
{
|
||||
struct bt_iso_big_create_param param = { 0 };
|
||||
struct bt_audio_stream *stream;
|
||||
int err;
|
||||
|
||||
CHECKIF(source == NULL) {
|
||||
BT_DBG("source is NULL");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
stream = &source->streams[0];
|
||||
|
||||
if (stream == NULL) {
|
||||
BT_DBG("stream is NULL");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (stream->ep == NULL) {
|
||||
BT_DBG("stream->ep is NULL");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (stream->ep->status.state != BT_AUDIO_EP_STATE_QOS_CONFIGURED) {
|
||||
BT_DBG("Broadcast source stream %p invalid state: %u",
|
||||
stream, stream->ep->status.state);
|
||||
return -EBADMSG;
|
||||
}
|
||||
|
||||
/* Create BIG */
|
||||
param.num_bis = source->stream_count;
|
||||
param.bis_channels = source->bis;
|
||||
param.framing = source->qos->framing;
|
||||
param.packing = 0; /* TODO: Add to QoS struct */
|
||||
param.interval = source->qos->interval;
|
||||
param.latency = source->qos->latency;
|
||||
|
||||
err = bt_iso_big_create(source->adv, ¶m, &source->big);
|
||||
if (err != 0) {
|
||||
BT_DBG("Failed to create BIG: %d", err);
|
||||
return err;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int bt_audio_broadcast_source_stop(struct bt_audio_broadcast_source *source)
|
||||
{
|
||||
struct bt_audio_stream *stream;
|
||||
int err;
|
||||
|
||||
CHECKIF(source == NULL) {
|
||||
BT_DBG("source is NULL");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
stream = &source->streams[0];
|
||||
|
||||
if (stream == NULL) {
|
||||
BT_DBG("stream is NULL");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (stream->ep == NULL) {
|
||||
BT_DBG("stream->ep is NULL");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (stream->ep->status.state != BT_AUDIO_EP_STATE_STREAMING) {
|
||||
BT_DBG("Broadcast source stream %p invalid state: %u",
|
||||
stream, stream->ep->status.state);
|
||||
return -EBADMSG;
|
||||
}
|
||||
|
||||
if (source->big == NULL) {
|
||||
BT_DBG("Source is not started");
|
||||
return -EALREADY;
|
||||
}
|
||||
|
||||
err = bt_iso_big_terminate(source->big);
|
||||
if (err) {
|
||||
BT_DBG("Failed to terminate BIG (err %d)", err);
|
||||
return err;
|
||||
}
|
||||
|
||||
source->big = NULL;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int bt_audio_broadcast_source_delete(struct bt_audio_broadcast_source *source)
|
||||
{
|
||||
struct bt_audio_stream *stream;
|
||||
struct bt_le_ext_adv *adv;
|
||||
int err;
|
||||
|
||||
CHECKIF(source == NULL) {
|
||||
BT_DBG("source is NULL");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
stream = &source->streams[0];
|
||||
|
||||
if (stream != NULL) {
|
||||
if (stream->ep == NULL) {
|
||||
BT_DBG("stream->ep is NULL");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (stream->ep->status.state != BT_AUDIO_EP_STATE_QOS_CONFIGURED) {
|
||||
BT_DBG("Broadcast source stream %p invalid state: %u",
|
||||
stream, stream->ep->status.state);
|
||||
return -EBADMSG;
|
||||
}
|
||||
}
|
||||
|
||||
adv = source->adv;
|
||||
|
||||
__ASSERT(adv != NULL, "source %p adv is NULL", source);
|
||||
|
||||
/* Stop periodic advertising */
|
||||
err = bt_le_per_adv_stop(adv);
|
||||
if (err != 0) {
|
||||
BT_DBG("Failed to stop periodic advertising (err %d)", err);
|
||||
return err;
|
||||
}
|
||||
|
||||
/* Stop extended advertising */
|
||||
err = bt_le_ext_adv_stop(adv);
|
||||
if (err != 0) {
|
||||
BT_DBG("Failed to stop extended advertising (err %d)", err);
|
||||
return err;
|
||||
}
|
||||
|
||||
/* Delete extended advertising set */
|
||||
err = bt_le_ext_adv_delete(adv);
|
||||
if (err != 0) {
|
||||
BT_DBG("Failed to delete extended advertising set (err %d)", err);
|
||||
return err;
|
||||
}
|
||||
|
||||
/* Reset the broadcast source */
|
||||
broadcast_source_cleanup(source);
|
||||
|
||||
return 0;
|
||||
}
|
|
@ -17,7 +17,11 @@
|
|||
#define UNICAST_GROUP_CNT 0
|
||||
#define UNICAST_GROUP_STREAM_CNT 0
|
||||
#endif /* CONFIG_BT_AUDIO_UNICAST_CLIENT && CONFIG_BT_AUDIO_UNICAST */
|
||||
#if defined(CONFIG_BT_AUDIO_BROADCAST_SOURCE)
|
||||
#define BROADCAST_STREAM_CNT CONFIG_BT_AUDIO_BROADCAST_SRC_STREAM_COUNT
|
||||
#else /* !CONFIG_BT_AUDIO_BROADCAST_SOURCE */
|
||||
#define BROADCAST_STREAM_CNT 0
|
||||
#endif /* CONFIG_BT_AUDIO_BROADCAST_SOURCE */
|
||||
|
||||
#define BT_AUDIO_EP_LOCAL 0x00
|
||||
#define BT_AUDIO_EP_REMOTE 0x01
|
||||
|
|
|
@ -185,8 +185,9 @@ bool bt_audio_valid_qos(const struct bt_codec_qos *qos)
|
|||
|
||||
static bool bt_audio_stream_is_broadcast(const struct bt_audio_stream *stream)
|
||||
{
|
||||
/* TODO: Implement */
|
||||
return false;
|
||||
/* TODO: Add broadcast sink */
|
||||
return (IS_ENABLED(CONFIG_BT_AUDIO_BROADCAST_SOURCE) &&
|
||||
bt_audio_ep_is_broadcast_src(stream->ep));
|
||||
}
|
||||
|
||||
bool bt_audio_valid_stream_qos(const struct bt_audio_stream *stream,
|
||||
|
|
|
@ -38,7 +38,7 @@ CONFIG_BT_AUTO_PHY_UPDATE=y
|
|||
CONFIG_BT_AUDIO=y
|
||||
CONFIG_BT_AUDIO_UNICAST_SERVER=y
|
||||
CONFIG_BT_AUDIO_UNICAST_CLIENT=y
|
||||
CONFIG_BT_AUDIO_BROADCAST=y
|
||||
CONFIG_BT_AUDIO_BROADCAST_SOURCE=y
|
||||
CONFIG_BT_ISO_TX_BUF_COUNT=5
|
||||
CONFIG_BT_VOCS_MAX_INSTANCE_COUNT=1
|
||||
CONFIG_BT_VOCS_CLIENT_MAX_INSTANCE_COUNT=1
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue