Bluetooth: Audio: Refactor ISO allocation endpoints

Modify how the ISO channels are allocated for the audio streams,
as well as how the CIG is allocated for the unicast group.

This fixes an issue where the unicast group could not be
fully allocated before _after_ the bt_audio_streams had been
configured by bt_audio_stream_config, thus making it impossible
to create a unicast group before the connections have been established.

This leaves us with 3 basic data types:
1) Streams allocated by the application
2) Endpoints that represent ASEs
3) Audio_iso which is used to couple streams and ISO channels.

The Unicast Group for the unicast client will now have the same
lifetime as the ISO CIG for the central.

Signed-off-by: Emil Gydesen <emil.gydesen@nordicsemi.no>
This commit is contained in:
Emil Gydesen 2022-05-24 13:59:45 +02:00 committed by Carles Cufí
commit 9b15426455
12 changed files with 601 additions and 521 deletions

View file

@ -1878,13 +1878,11 @@ int bt_audio_stream_reconfig(struct bt_audio_stream *stream,
*
* @param conn Connection object
* @param group Unicast group object
* @param qos Quality of Service configuration
*
* @return 0 in case of success or negative value in case of error.
*/
int bt_audio_stream_qos(struct bt_conn *conn,
struct bt_audio_unicast_group *group,
struct bt_codec_qos *qos);
struct bt_audio_unicast_group *group);
/** @brief Enable Audio Stream
*
@ -1998,21 +1996,41 @@ int bt_audio_stream_release(struct bt_audio_stream *stream, bool cache);
int bt_audio_stream_send(struct bt_audio_stream *stream, struct net_buf *buf,
uint32_t seq_num, uint32_t ts);
/** @brief Parameter struct for the unicast group functions
*
* Parameter struct for the bt_audio_unicast_group_create() and
* bt_audio_unicast_group_add_streams() functions.
*/
struct bt_audio_unicast_group_param {
/** Pointer to a stream object. */
struct bt_audio_stream *stream;
/** The QoS settings for the @ref bt_audio_unicast_group_param.stream. */
struct bt_codec_qos *qos;
/** @brief The direction of the @ref bt_audio_unicast_group_param.stream
*
* If two streams are being used for the same ACL connection but in
* different directions, they may use the same CIS.
*/
enum bt_audio_dir dir;
};
/** @brief Create audio unicast group.
*
* Create a new audio unicast group with one or more audio streams as a
* unicast client. Streams in a unicast group shall share the same interval,
* framing and latency (see @ref bt_codec_qos).
*
* @param[in] streams Array of stream object pointers being used for
* @param[in] params Array of stream parameters being used for
* the group.
* @param[in] num_stream Number of streams in @p streams.
* @param[in] num_param Number of parameters in @p params.
* @param[out] unicast_group Pointer to the unicast group created
*
* @return Zero on success or (negative) error code otherwise.
*/
int bt_audio_unicast_group_create(struct bt_audio_stream *streams[],
size_t num_stream,
int bt_audio_unicast_group_create(struct bt_audio_unicast_group_param params[],
size_t num_param,
struct bt_audio_unicast_group **unicast_group);
/** @brief Add streams to a unicast group as a unicast client
@ -2025,34 +2043,21 @@ int bt_audio_unicast_group_create(struct bt_audio_stream *streams[],
* This can also be called after the streams have been stopped
* (see bt_audio_stream_ops.stopped()).
*
* Once a stream has been added to a unicast group, it cannot be removed.
* To remove a stream from a group, the group must be deleted with
* bt_audio_unicast_group_delete(), but this will require all streams in the
* group to be released first.
*
* @param unicast_group Pointer to the unicast group
* @param streams Array of stream object pointers being added to the
* group.
* @param num_stream Number of streams in @p streams.
* @param params Array of stream parameters with streams being added
* to the group.
* @param num_param Number of paramers in @p params.
*
* @return 0 in case of success or negative value in case of error.
*/
int bt_audio_unicast_group_add_streams(struct bt_audio_unicast_group *unicast_group,
struct bt_audio_stream *streams[],
size_t num_stream);
/** @brief Remove streams from a unicast group as a unicast client
*
* This function can be used to remove streams from a bt_audio_unicast_group.
*
* This can be called at any time before any of the streams in the
* group has been QoS configured (see bt_audio_stream_ops.qos_set()).
*
* @param unicast_group Pointer to the unicast group
* @param streams Array of stream object pointers removed from the
* group.
* @param num_stream Number of streams in @p streams.
*
* @return 0 in case of success or negative value in case of error.
*/
int bt_audio_unicast_group_remove_streams(struct bt_audio_unicast_group *unicast_group,
struct bt_audio_stream *streams[],
size_t num_stream);
struct bt_audio_unicast_group_param params[],
size_t num_param);
/** @brief Delete audio unicast group.
*

View file

@ -327,6 +327,25 @@ static void audio_timer_timeout(struct k_work *work)
#endif
static enum bt_audio_dir stream_dir(const struct bt_audio_stream *stream)
{
for (size_t i = 0U; i < ARRAY_SIZE(sinks); i++) {
if (sinks[i].ep != NULL && stream->ep == sinks[i].ep) {
return BT_AUDIO_DIR_SINK;
}
}
for (size_t i = 0U; i < ARRAY_SIZE(sources); i++) {
if (sources[i] != NULL && stream->ep == sources[i]) {
return BT_AUDIO_DIR_SOURCE;
}
}
__ASSERT(false, "Invalid stream");
return 0;
}
static void print_hex(const uint8_t *ptr, size_t len)
{
while (len-- != 0) {
@ -845,14 +864,16 @@ static int configure_streams(void)
static int create_group(void)
{
struct bt_audio_stream *streams_p[ARRAY_SIZE(streams)];
struct bt_audio_unicast_group_param params[ARRAY_SIZE(streams)];
int err;
for (size_t i = 0U; i < configured_stream_count; i++) {
streams_p[i] = &streams[i];
params[i].stream = &streams[i];
params[i].qos = &codec_configuration.qos;
params[i].dir = stream_dir(params[i].stream);
}
err = bt_audio_unicast_group_create(streams_p, configured_stream_count,
err = bt_audio_unicast_group_create(params, configured_stream_count,
&unicast_group);
if (err != 0) {
printk("Could not create unicast group (err %d)\n", err);
@ -866,10 +887,9 @@ static int set_stream_qos(void)
{
int err;
err = bt_audio_stream_qos(default_conn, unicast_group,
&codec_configuration.qos);
err = bt_audio_stream_qos(default_conn, unicast_group);
if (err != 0) {
printk("Unable to setup QoS: %d", err);
printk("Unable to setup QoS: %d\n", err);
return err;
}

View file

@ -75,7 +75,7 @@ config BT_AUDIO_UNICAST_CLIENT_GROUP_COUNT
int "Basic Audio Unicast Group count"
depends on BT_AUDIO_UNICAST
default BT_ISO_MAX_CIG
range 0 BT_ISO_MAX_CIG
range 1 BT_ISO_MAX_CIG
help
This option sets the number of connected audio groups to support as
the unicast client.
@ -84,8 +84,8 @@ config BT_AUDIO_UNICAST_CLIENT_GROUP_STREAM_COUNT
int "Basic Audio Unicast Group Stream count"
depends on BT_AUDIO_UNICAST_CLIENT_GROUP_COUNT > 0
default 1
range 0 BT_ISO_MAX_CHAN if BT_ISO_MAX_CHAN < 31
range 0 31
range 1 BT_ISO_MAX_CHAN if BT_ISO_MAX_CHAN < 31
range 1 31
help
This option sets the maximum number of streams per unicast group
to support.

View file

@ -80,27 +80,28 @@ static void ase_status_changed(struct bt_audio_ep *ep, uint8_t old_state,
static void ascs_ep_unbind_audio_iso(struct bt_audio_ep *ep)
{
struct bt_audio_iso *audio_iso = ep->iso;
struct bt_iso_chan_qos *qos;
const uint8_t dir = ep->dir;
struct bt_audio_stream *stream = ep->stream;
BT_DBG("ep %p, dir %u audio_iso %p", ep, dir, audio_iso);
if (audio_iso != NULL) {
struct bt_iso_chan_qos *qos;
if (audio_iso == NULL) {
return;
qos = audio_iso->iso_chan.qos;
BT_ASSERT_MSG(stream != NULL, "Stream was NULL");
stream->iso = NULL;
if (audio_iso->sink_stream == stream) {
audio_iso->sink_stream = NULL;
qos->rx = NULL;
} else if (audio_iso->source_stream == stream) {
audio_iso->source_stream = NULL;
qos->tx = NULL;
} else {
BT_ERR("Stream %p not linked to audio_iso %p", stream, audio_iso);
}
}
ep->iso = NULL;
qos = audio_iso->iso_chan.qos;
if (dir == BT_AUDIO_DIR_SOURCE) {
audio_iso->source_ep = NULL;
qos->tx = NULL;
} else if (dir == BT_AUDIO_DIR_SINK) {
audio_iso->sink_ep = NULL;
qos->rx = NULL;
} else {
__ASSERT(false, "Invalid dir: %u", dir);
}
}
void ascs_ep_set_state(struct bt_audio_ep *ep, uint8_t state)
@ -323,23 +324,26 @@ static void ascs_iso_recv(struct bt_iso_chan *chan,
{
struct bt_audio_iso *audio_iso = CONTAINER_OF(chan, struct bt_audio_iso,
iso_chan);
struct bt_audio_ep *ep = audio_iso->sink_ep;
struct bt_audio_stream *stream = audio_iso->sink_stream;
const struct bt_audio_stream_ops *ops;
if (ep == NULL) {
BT_ERR("Could not lookup ep by iso %p", chan);
if (stream == NULL) {
BT_ERR("Could not lookup stream by iso %p", chan);
return;
} else if (stream->ep == NULL) {
BT_ERR("Stream not associated with an ep");
return;
}
ops = ep->stream->ops;
ops = stream->ops;
if (IS_ENABLED(CONFIG_BT_AUDIO_DEBUG_STREAM_DATA)) {
BT_DBG("stream %p ep %p len %zu",
chan, ep, net_buf_frags_len(buf));
stream, stream->ep, net_buf_frags_len(buf));
}
if (ops != NULL && ops->recv != NULL) {
ops->recv(ep->stream, info, buf);
ops->recv(stream, info, buf);
} else {
BT_WARN("No callback for recv set");
}
@ -349,13 +353,13 @@ static void ascs_iso_sent(struct bt_iso_chan *chan)
{
struct bt_audio_iso *audio_iso = CONTAINER_OF(chan, struct bt_audio_iso,
iso_chan);
struct bt_audio_ep *ep = audio_iso->source_ep;
struct bt_audio_stream_ops *ops = ep->stream->ops;
struct bt_audio_stream *stream = audio_iso->source_stream;
struct bt_audio_stream_ops *ops = stream->ops;
BT_DBG("stream %p ep %p", chan, ep);
BT_DBG("stream %p ep %p", stream, stream->ep);
if (ops != NULL && ops->sent != NULL) {
ops->sent(ep->stream);
ops->sent(stream);
}
}
@ -363,22 +367,28 @@ static void ascs_iso_connected(struct bt_iso_chan *chan)
{
struct bt_audio_iso *audio_iso = CONTAINER_OF(chan, struct bt_audio_iso,
iso_chan);
struct bt_audio_ep *source_ep = audio_iso->source_ep;
struct bt_audio_ep *sink_ep = audio_iso->sink_ep;
struct bt_audio_stream *source_stream = audio_iso->source_stream;
struct bt_audio_stream *sink_stream = audio_iso->sink_stream;
struct bt_audio_stream *stream;
struct bt_audio_ep *ep;
if (&sink_ep->iso->iso_chan == chan) {
ep = sink_ep;
if (sink_stream->iso == chan) {
stream = sink_stream;
} else {
ep = source_ep;
stream = source_stream;
}
if (ep == NULL) {
BT_ERR("Could not lookup ep by iso %p", chan);
if (stream == NULL) {
BT_ERR("Could not lookup stream by iso %p", chan);
return;
} else if (stream->ep == NULL) {
BT_ERR("Stream not associated with an ep");
return;
}
BT_DBG("stream %p ep %p dir %u", chan, ep, ep != NULL ? ep->dir : 0);
ep = stream->ep;
BT_DBG("stream %p ep %p dir %u", stream, ep, ep->dir);
if (ep->status.state != BT_AUDIO_EP_STATE_ENABLING) {
BT_DBG("endpoint not in enabling state: %s",
@ -393,27 +403,29 @@ static void ascs_iso_disconnected(struct bt_iso_chan *chan, uint8_t reason)
{
struct bt_audio_iso *audio_iso = CONTAINER_OF(chan, struct bt_audio_iso,
iso_chan);
struct bt_audio_ep *source_ep = audio_iso->source_ep;
struct bt_audio_ep *sink_ep = audio_iso->sink_ep;
struct bt_audio_stream *source_stream = audio_iso->source_stream;
struct bt_audio_stream *sink_stream = audio_iso->sink_stream;
const struct bt_audio_stream_ops *ops;
struct bt_audio_stream *stream;
struct bt_audio_ep *ep;
if (&sink_ep->iso->iso_chan == chan) {
ep = sink_ep;
if (sink_stream->iso == chan) {
stream = sink_stream;
} else {
ep = source_ep;
stream = source_stream;
}
if (ep == NULL) {
BT_ERR("Could not lookup ep by iso %p", chan);
if (stream == NULL) {
BT_ERR("Could not lookup stream by iso %p", chan);
return;
} else if (stream->ep == NULL) {
BT_ERR("Stream not associated with an ep");
return;
}
ops = ep->stream->ops;
stream = ep->stream;
ops = stream->ops;
BT_DBG("stream %p ep %p reason 0x%02x", chan, ep, reason);
BT_DBG("stream %p ep %p reason 0x%02x", stream, stream->ep, reason);
if (ops != NULL && ops->stopped != NULL) {
ops->stopped(stream);
@ -421,6 +433,7 @@ static void ascs_iso_disconnected(struct bt_iso_chan *chan, uint8_t reason)
BT_WARN("No callback for stopped set");
}
ep = stream->ep;
if (ep->status.state == BT_AUDIO_EP_STATE_RELEASING) {
ascs_ep_set_state(ep, BT_AUDIO_EP_STATE_CODEC_CONFIGURED);
} else {
@ -759,46 +772,50 @@ struct bt_audio_iso *ascs_new_audio_iso(const struct bt_audio_stream *stream,
uint8_t cig_id,
uint8_t cis_id)
{
struct bt_conn *acl_conn = stream->conn;
struct bt_audio_iso *free_audio_iso;
const enum bt_audio_dir dir = stream->ep->dir;
struct bt_audio_iso *free_audio_iso = NULL;
BT_DBG("stream %p ascs %p", stream, ascs);
BT_DBG("stream %p dir %u ascs %p", stream, dir, ascs);
free_audio_iso = NULL;
/* Look through the audio_isos and find a free one, or one for the same
* ACL connection where the opposite direction is what we want, and is
* free.
*
* The returned audio_iso is either an unused one, or one where @p
* stream can be used in the opposite direction.
*/
for (size_t i = 0; i < ARRAY_SIZE(ascs->isos); i++) {
struct bt_audio_iso *audio_iso = &ascs->isos[i];
struct bt_audio_ep *ep;
const struct bt_conn *acl_conn = stream->conn;
const struct bt_audio_stream *paired_stream;
const struct bt_audio_stream *tmp_stream;
switch (stream->ep->dir) {
switch (dir) {
case BT_AUDIO_DIR_SINK:
ep = audio_iso->sink_ep;
if (ep == NULL) {
if (free_audio_iso == NULL) {
free_audio_iso = audio_iso;
}
} else if (ep->cig_id == cig_id &&
ep->cis_id == cis_id &&
ep->stream->conn == acl_conn) {
return audio_iso;
}
tmp_stream = audio_iso->sink_stream;
paired_stream = audio_iso->source_stream;
break;
case BT_AUDIO_DIR_SOURCE:
ep = audio_iso->source_ep;
if (ep == NULL) {
if (free_audio_iso == NULL) {
free_audio_iso = audio_iso;
}
} else if (ep->cig_id == cig_id &&
ep->cis_id == cis_id &&
ep->stream->conn == acl_conn) {
return audio_iso;
}
tmp_stream = audio_iso->source_stream;
paired_stream = audio_iso->sink_stream;
break;
default:
return NULL;
}
if (tmp_stream == NULL) {
if (free_audio_iso == NULL) {
free_audio_iso = audio_iso;
}
} else if (paired_stream != NULL &&
paired_stream->ep->cig_id == cig_id &&
paired_stream->ep->cis_id == cis_id &&
paired_stream->ep->stream->conn == acl_conn) {
BT_DBG("Use existing audio_iso %p together with stream %p",
audio_iso, paired_stream);
return audio_iso;
}
}
return free_audio_iso;
@ -931,40 +948,39 @@ static uint8_t ase_attr_cb(const struct bt_gatt_attr *attr, uint16_t handle,
return BT_GATT_ITER_CONTINUE;
}
static void ascs_ep_bind_audio_iso(struct bt_audio_ep *ep,
struct bt_audio_iso *audio_iso)
static void ascs_ep_stream_bind_audio_iso(struct bt_audio_stream *stream,
struct bt_audio_iso *audio_iso)
{
const enum bt_audio_dir dir = stream->ep->dir;
struct bt_iso_chan *iso_chan;
struct bt_iso_chan_qos *qos;
const uint8_t dir = ep->dir;
BT_DBG("ep %p, dir %u audio_iso %p", ep, dir, audio_iso);
BT_DBG("stream %p, dir %u audio_iso %p", stream, dir, audio_iso);
ep->iso = audio_iso;
iso_chan = &ep->iso->iso_chan;
iso_chan = &audio_iso->iso_chan;
iso_chan->ops = &ascs_iso_ops;
qos = iso_chan->qos = &ep->iso->iso_qos;
qos = iso_chan->qos = &audio_iso->iso_qos;
if (dir == BT_AUDIO_DIR_SOURCE) {
audio_iso->source_ep = ep;
qos->tx = &ep->iso_io_qos;
audio_iso->source_stream = stream;
qos->tx = &audio_iso->source_io_qos;
} else if (dir == BT_AUDIO_DIR_SINK) {
audio_iso->sink_ep = ep;
qos->rx = &ep->iso_io_qos;
audio_iso->sink_stream = stream;
qos->rx = &audio_iso->sink_io_qos;
} else {
__ASSERT(false, "Invalid dir: %u", dir);
}
ep->stream->iso = iso_chan;
stream->iso = iso_chan;
stream->ep->iso = audio_iso;
}
void ascs_ep_init(struct bt_audio_ep *ep, uint8_t id)
{
BT_DBG("ep %p id 0x%02x", ep, id);
memset(ep, 0, sizeof(*ep));
(void)memset(ep, 0, sizeof(*ep));
ep->status.id = id;
ep->dir = ASE_DIR(id);
}
@ -1348,9 +1364,10 @@ static int ase_stream_qos(struct bt_audio_stream *stream,
struct bt_audio_iso *audio_iso;
struct bt_audio_ep *ep;
BT_DBG("stream %p qos %p", stream, qos);
BT_DBG("stream %p ep %p qos %p", stream, stream->ep, qos);
if (stream == NULL || stream->ep == NULL || qos == NULL) {
BT_DBG("Invalid input stream, ep or qos pointers");
return -EINVAL;
}
@ -1381,12 +1398,14 @@ static int ase_stream_qos(struct bt_audio_stream *stream,
err = unicast_server_cb->qos(stream, qos);
if (err != 0) {
BT_DBG("Application returned error: %d", err);
return err;
}
}
audio_iso = ascs_new_audio_iso(stream, ascs, cig_id, cis_id);
if (audio_iso == NULL) {
BT_DBG("Could not allocated new audio_iso");
return -ENOMEM;
}
@ -1394,7 +1413,7 @@ static int ase_stream_qos(struct bt_audio_stream *stream,
ascs_ep_set_state(ep, BT_AUDIO_EP_STATE_QOS_CONFIGURED);
ascs_ep_bind_audio_iso(ep, audio_iso);
ascs_ep_stream_bind_audio_iso(stream, audio_iso);
bt_audio_stream_iso_listen(stream);
return 0;

View file

@ -118,23 +118,26 @@ static void broadcast_sink_iso_recv(struct bt_iso_chan *chan,
{
struct bt_audio_iso *audio_iso = CONTAINER_OF(chan, struct bt_audio_iso,
iso_chan);
struct bt_audio_ep *ep = audio_iso->sink_ep;
struct bt_audio_stream *stream = audio_iso->sink_stream;
const struct bt_audio_stream_ops *ops;
if (ep == NULL) {
BT_ERR("Could not lookup ep by iso %p", chan);
if (stream == NULL) {
BT_ERR("Could not lookup stream by iso %p", chan);
return;
} else if (stream->ep == NULL) {
BT_ERR("Stream not associated with an ep");
return;
}
ops = ep->stream->ops;
ops = stream->ops;
if (IS_ENABLED(CONFIG_BT_AUDIO_DEBUG_STREAM_DATA)) {
BT_DBG("stream %p ep %p len %zu",
chan, ep, net_buf_frags_len(buf));
stream, stream->ep, net_buf_frags_len(buf));
}
if (ops != NULL && ops->recv != NULL) {
ops->recv(ep->stream, info, buf);
ops->recv(stream, info, buf);
} else {
BT_WARN("No callback for recv set");
}
@ -144,22 +147,25 @@ static void broadcast_sink_iso_connected(struct bt_iso_chan *chan)
{
struct bt_audio_iso *audio_iso = CONTAINER_OF(chan, struct bt_audio_iso,
iso_chan);
struct bt_audio_ep *ep = audio_iso->sink_ep;
struct bt_audio_stream *stream = audio_iso->sink_stream;
const struct bt_audio_stream_ops *ops;
if (ep == NULL) {
BT_ERR("Could not lookup ep by iso %p", chan);
if (stream == NULL) {
BT_ERR("Could not lookup stream by iso %p", chan);
return;
} else if (stream->ep == NULL) {
BT_ERR("Stream not associated with an ep");
return;
}
ops = ep->stream->ops;
ops = stream->ops;
BT_DBG("stream %p ep %p", chan, ep);
BT_DBG("stream %p", stream);
broadcast_sink_set_ep_state(ep, BT_AUDIO_EP_STATE_STREAMING);
broadcast_sink_set_ep_state(stream->ep, BT_AUDIO_EP_STATE_STREAMING);
if (ops != NULL && ops->started != NULL) {
ops->started(ep->stream);
ops->started(stream);
} else {
BT_WARN("No callback for connected set");
}
@ -170,22 +176,23 @@ static void broadcast_sink_iso_disconnected(struct bt_iso_chan *chan,
{
struct bt_audio_iso *audio_iso = CONTAINER_OF(chan, struct bt_audio_iso,
iso_chan);
struct bt_audio_ep *ep = audio_iso->sink_ep;
struct bt_audio_stream *stream = audio_iso->sink_stream;
const struct bt_audio_stream_ops *ops;
struct bt_audio_broadcast_sink *sink;
struct bt_audio_stream *stream;
if (ep == NULL) {
if (stream == NULL) {
BT_ERR("Could not lookup ep by iso %p", chan);
return;
} else if (stream->ep == NULL) {
BT_ERR("Stream not associated with an ep");
return;
}
ops = ep->stream->ops;
stream = ep->stream;
ops = stream->ops;
BT_DBG("stream %p ep %p reason 0x%02x", chan, ep, reason);
BT_DBG("stream %p ep %p reason 0x%02x", stream, stream->ep, reason);
broadcast_sink_set_ep_state(ep, BT_AUDIO_EP_STATE_IDLE);
broadcast_sink_set_ep_state(stream->ep, BT_AUDIO_EP_STATE_IDLE);
if (ops != NULL && ops->stopped != NULL) {
ops->stopped(stream);
@ -864,13 +871,12 @@ static void broadcast_sink_ep_init(struct bt_audio_ep *ep,
(void)memset(ep, 0, sizeof(*ep));
ep->dir = BT_AUDIO_DIR_SINK;
ep->iso = iso;
iso->sink_ep = ep;
iso_chan = &ep->iso->iso_chan;
iso_chan = &iso->iso_chan;
iso_chan->ops = &broadcast_sink_iso_ops;
iso_chan->qos = &ep->iso->iso_qos;
iso_chan->qos->rx = &ep->iso_io_qos;
iso_chan->qos->rx = &iso->sink_io_qos;
iso_chan->qos->tx = NULL;
}
@ -913,6 +919,7 @@ static int bt_audio_broadcast_sink_setup_stream(uint8_t index,
}
bt_audio_stream_attach(NULL, stream, ep, codec);
ep->iso->sink_stream = stream;
/* TODO: The values of sink_chan_io_qos and codec_qos are not used,
* but the `rx` and `qos` pointers need to be set. This should be fixed.
*/

View file

@ -127,13 +127,13 @@ static void broadcast_source_iso_sent(struct bt_iso_chan *chan)
{
struct bt_audio_iso *audio_iso = CONTAINER_OF(chan, struct bt_audio_iso,
iso_chan);
struct bt_audio_ep *ep = audio_iso->source_ep;
struct bt_audio_stream_ops *ops = ep->stream->ops;
struct bt_audio_stream *stream = audio_iso->source_stream;
struct bt_audio_stream_ops *ops = stream->ops;
BT_DBG("stream %p ep %p", chan, ep);
BT_DBG("stream %p ep %p", stream, stream->ep);
if (ops != NULL && ops->sent != NULL) {
ops->sent(ep->stream);
ops->sent(stream);
}
}
@ -141,22 +141,27 @@ static void broadcast_source_iso_connected(struct bt_iso_chan *chan)
{
struct bt_audio_iso *audio_iso = CONTAINER_OF(chan, struct bt_audio_iso,
iso_chan);
struct bt_audio_ep *ep = audio_iso->source_ep;
struct bt_audio_stream *stream = audio_iso->source_stream;
const struct bt_audio_stream_ops *ops;
struct bt_audio_ep *ep;
if (ep == NULL) {
BT_ERR("Could not lookup ep by iso %p", chan);
if (stream == NULL) {
BT_ERR("Could not lookup stream by iso %p", chan);
return;
} else if (stream->ep == NULL) {
BT_ERR("Stream not associated with an ep");
return;
}
ops = ep->stream->ops;
ops = stream->ops;
ep = stream->ep;
BT_DBG("stream %p ep %p", chan, ep);
BT_DBG("stream %p ep %p", stream, ep);
broadcast_source_set_ep_state(ep, BT_AUDIO_EP_STATE_STREAMING);
if (ops != NULL && ops->started != NULL) {
ops->started(ep->stream);
ops->started(stream);
} else {
BT_WARN("No callback for connected set");
}
@ -166,21 +171,22 @@ static void broadcast_source_iso_disconnected(struct bt_iso_chan *chan, uint8_t
{
struct bt_audio_iso *audio_iso = CONTAINER_OF(chan, struct bt_audio_iso,
iso_chan);
struct bt_audio_ep *ep = audio_iso->source_ep;
struct bt_audio_stream *stream = audio_iso->source_stream;
const struct bt_audio_stream_ops *ops;
struct bt_audio_stream *stream;
if (ep == NULL) {
BT_ERR("Could not lookup ep by iso %p", chan);
if (stream == NULL) {
BT_ERR("Could not lookup stream by iso %p", chan);
return;
} else if (stream->ep == NULL) {
BT_ERR("Stream not associated with an ep");
return;
}
ops = ep->stream->ops;
stream = ep->stream;
ops = stream->ops;
BT_DBG("stream %p ep %p reason 0x%02x", chan, ep, reason);
BT_DBG("stream %p ep %p reason 0x%02x", stream, stream->ep, reason);
broadcast_source_set_ep_state(ep, BT_AUDIO_EP_STATE_QOS_CONFIGURED);
broadcast_source_set_ep_state(stream->ep, BT_AUDIO_EP_STATE_QOS_CONFIGURED);
if (ops != NULL && ops->stopped != NULL) {
ops->stopped(stream);
@ -216,14 +222,13 @@ static void broadcast_source_ep_init(struct bt_audio_ep *ep,
(void)memset(ep, 0, sizeof(*ep));
ep->dir = BT_AUDIO_DIR_SOURCE;
ep->iso = iso;
iso->source_ep = ep;
iso_chan = &ep->iso->iso_chan;
iso_chan->ops = &broadcast_source_iso_ops;
iso_chan->qos = &ep->iso->iso_qos;
iso_chan->qos = &iso->iso_qos;
iso_chan->qos->rx = NULL;
iso_chan->qos->tx = &ep->iso_io_qos;
iso_chan->qos->tx = &iso->source_io_qos;
}
static struct bt_audio_ep *broadcast_source_new_ep(uint8_t index)
@ -263,6 +268,7 @@ static int bt_audio_broadcast_source_setup_stream(uint8_t index,
}
bt_audio_stream_attach(NULL, stream, ep, codec);
ep->iso->source_stream = stream;
stream->qos = qos;
stream->iso->qos->rx = NULL;
bt_audio_codec_qos_to_iso_qos(stream->iso->qos->tx, qos);

View file

@ -32,8 +32,10 @@ struct bt_audio_ep;
struct bt_audio_iso {
struct bt_iso_chan iso_chan;
struct bt_iso_chan_qos iso_qos;
struct bt_audio_ep *sink_ep;
struct bt_audio_ep *source_ep;
struct bt_iso_chan_io_qos sink_io_qos;
struct bt_iso_chan_io_qos source_io_qos;
struct bt_audio_stream *sink_stream;
struct bt_audio_stream *source_stream;
};
struct bt_audio_ep {
@ -48,7 +50,6 @@ struct bt_audio_ep {
struct bt_codec_qos qos;
struct bt_codec_qos_pref qos_pref;
struct bt_audio_iso *iso;
struct bt_iso_chan_io_qos iso_io_qos;
struct bt_gatt_subscribe_params subscribe;
struct bt_gatt_discover_params discover;
@ -59,9 +60,10 @@ struct bt_audio_ep {
};
struct bt_audio_unicast_group {
uint8_t index;
bool allocated;
/* QoS used to create the CIG */
struct bt_codec_qos *qos;
const struct bt_codec_qos *qos;
struct bt_iso_cig *cig;
/* The ISO API for CIG creation requires an array of pointers to ISO channels */
struct bt_iso_chan *cis[UNICAST_GROUP_STREAM_CNT];

View file

@ -400,7 +400,7 @@ static void bt_audio_codec_qos_to_cig_param(struct bt_iso_cig_param *cig_param,
}
static int bt_audio_cig_create(struct bt_audio_unicast_group *group,
struct bt_codec_qos *qos)
const struct bt_codec_qos *qos)
{
struct bt_iso_cig_param param;
struct bt_audio_stream *stream;
@ -443,7 +443,7 @@ static int bt_audio_cig_create(struct bt_audio_unicast_group *group,
}
static int bt_audio_cig_reconfigure(struct bt_audio_unicast_group *group,
struct bt_codec_qos *qos)
const struct bt_codec_qos *qos)
{
struct bt_iso_cig_param param;
struct bt_audio_stream *stream;
@ -488,8 +488,7 @@ static void audio_stream_qos_cleanup(const struct bt_conn *conn,
}
int bt_audio_stream_qos(struct bt_conn *conn,
struct bt_audio_unicast_group *group,
struct bt_codec_qos *qos)
struct bt_audio_unicast_group *group)
{
struct bt_audio_stream *stream;
struct net_buf_simple *buf;
@ -500,7 +499,7 @@ int bt_audio_stream_qos(struct bt_conn *conn,
uint8_t role;
int err;
BT_DBG("conn %p group %p qos %p", conn, group, qos);
BT_DBG("conn %p group %p", conn, group);
CHECKIF(conn == NULL) {
BT_DBG("conn is NULL");
@ -512,21 +511,11 @@ int bt_audio_stream_qos(struct bt_conn *conn,
return -EINVAL;
}
CHECKIF(qos == NULL) {
BT_DBG("qos is NULL");
return -EINVAL;
}
if (sys_slist_is_empty(&group->streams)) {
BT_DBG("group stream list is empty");
return -ENOEXEC;
}
CHECKIF(!bt_audio_valid_qos(qos)) {
BT_DBG("Invalid QoS");
return -EINVAL;
}
role = conn->role;
if (role != BT_HCI_ROLE_CENTRAL) {
BT_DBG("Invalid conn role: %u, shall be central", role);
@ -576,7 +565,7 @@ int bt_audio_stream_qos(struct bt_conn *conn,
return -EINVAL;
}
if (!bt_audio_valid_stream_qos(stream, qos)) {
if (!bt_audio_valid_stream_qos(stream, stream->qos)) {
return -EINVAL;
}
@ -589,6 +578,14 @@ int bt_audio_stream_qos(struct bt_conn *conn,
__ASSERT(false, "invalid endpoint dir: %u", ep->dir);
return -EINVAL;
}
if (bt_unicast_client_audio_iso_by_stream(stream) == NULL) {
/* This can only happen if the stream was somehow added
* to a group without the audio_iso being bound to it
*/
BT_ERR("Could not find audio_iso for stream %p", stream);
return -EINVAL;
}
}
if (!conn_stream_found) {
@ -596,68 +593,19 @@ int bt_audio_stream_qos(struct bt_conn *conn,
return -EINVAL;
}
/* Setup streams before starting the QoS execution */
/* Setup endpoints before starting the QoS execution */
SYS_SLIST_FOR_EACH_CONTAINER(&group->streams, stream, node) {
struct bt_iso_chan_qos *iso_qos;
struct bt_audio_iso *audio_iso;
struct bt_iso_chan_io_qos *io;
if (stream->conn != conn) {
/* Channel not part of this ACL, skip */
continue;
}
ep = stream->ep;
if (ep->iso != NULL) {
/* already setup */
BT_DBG("Stream %p ep %p already setup with iso %p",
stream, ep, ep->iso);
audio_iso = bt_unicast_client_audio_iso_by_stream(stream);
/* Don't need to check for audio_iso validity as we have already verified that */
continue;
}
audio_iso = bt_unicast_client_new_audio_iso(stream);
if (audio_iso == NULL) {
audio_stream_qos_cleanup(conn, group);
return -ENOMEM;
}
bt_unicast_client_ep_bind_audio_iso(ep, audio_iso);
iso_qos = stream->iso->qos;
if (ep->dir == BT_AUDIO_DIR_SINK) {
/* If the endpoint is a sink, then we need to
* configure our TX parameters
*/
io = iso_qos->tx;
} else {
/* If the endpoint is a source, then we need to
* configure our RX parameters
*/
io = iso_qos->rx;
}
bt_audio_codec_qos_to_iso_qos(io, qos);
}
/* Create or reconfigure the CIG */
if (group->cig == NULL) {
err = bt_audio_cig_create(group, qos);
if (err != 0) {
BT_DBG("bt_audio_cig_create failed: %d", err);
audio_stream_qos_cleanup(conn, group);
return err;
}
} else {
err = bt_audio_cig_reconfigure(group, qos);
if (err != 0) {
BT_DBG("bt_audio_cig_reconfigure failed: %d", err);
audio_stream_qos_cleanup(conn, group);
return err;
}
bt_unicast_client_ep_bind_audio_iso(stream->ep, audio_iso);
}
/* Generate the control point write */
@ -675,7 +623,7 @@ int bt_audio_stream_qos(struct bt_conn *conn,
op->num_ases++;
err = bt_unicast_client_ep_qos(stream->ep, buf, qos);
err = bt_unicast_client_ep_qos(stream->ep, buf, stream->qos);
if (err) {
audio_stream_qos_cleanup(conn, group);
return err;
@ -693,13 +641,6 @@ int bt_audio_stream_qos(struct bt_conn *conn,
return err;
}
SYS_SLIST_FOR_EACH_CONTAINER(&group->streams, stream, node) {
stream->qos = qos;
bt_unicast_client_ep_set_state(stream->ep,
BT_AUDIO_EP_STATE_QOS_CONFIGURED);
}
return 0;
}
@ -824,7 +765,7 @@ int bt_audio_stream_start(struct bt_audio_stream *stream)
uint8_t role;
int err;
BT_DBG("stream %p", stream);
BT_DBG("stream %p ep %p", stream, stream == NULL ? NULL : stream->ep);
if (stream == NULL || stream->ep == NULL || stream->conn == NULL) {
BT_DBG("Invalid stream");
@ -971,13 +912,40 @@ int bt_audio_stream_connect(struct bt_audio_stream *stream)
}
}
int bt_audio_unicast_group_create(struct bt_audio_stream *streams[],
size_t num_stream,
static bool unicast_group_valid_qos(const struct bt_codec_qos *group_qos,
const struct bt_codec_qos *stream_qos)
{
if (group_qos->framing != stream_qos->framing ||
group_qos->interval != stream_qos->interval ||
group_qos->latency != stream_qos->latency) {
return false;
}
return true;
}
static void unicast_group_param_cleanup(struct bt_audio_unicast_group *group,
const struct bt_audio_unicast_group_param params[],
size_t num_param)
{
for (size_t i = 0U; i < num_param; i++) {
if (!sys_slist_find_and_remove(&group->streams, &params[i].stream->node)) {
/* We can stop once `sys_slist_find_and_remove` fails as
* that means that the this and the following streams
* were never added to the group
*/
return;
}
}
}
int bt_audio_unicast_group_create(struct bt_audio_unicast_group_param params[],
size_t num_param,
struct bt_audio_unicast_group **out_unicast_group)
{
struct bt_audio_unicast_group *unicast_group;
uint8_t index;
const struct bt_codec_qos *group_qos = NULL;
int err;
CHECKIF(out_unicast_group == NULL) {
BT_DBG("out_unicast_group is NULL");
@ -986,34 +954,51 @@ int bt_audio_unicast_group_create(struct bt_audio_stream *streams[],
/* Set out_unicast_group to NULL until the source has actually been created */
*out_unicast_group = NULL;
CHECKIF(streams == NULL) {
CHECKIF(params == NULL) {
BT_DBG("streams is NULL");
return -EINVAL;
}
CHECKIF(num_stream > UNICAST_GROUP_STREAM_CNT) {
CHECKIF(num_param > UNICAST_GROUP_STREAM_CNT) {
BT_DBG("Too many streams provided: %u/%u",
num_stream, UNICAST_GROUP_STREAM_CNT);
num_param, UNICAST_GROUP_STREAM_CNT);
return -EINVAL;
}
for (size_t i = 0; i < num_stream; i++) {
CHECKIF(streams[i] == NULL) {
for (size_t i = 0U; i < num_param; i++) {
CHECKIF(params[i].stream == NULL ||
params[i].qos == NULL ||
(params[i].dir != BT_AUDIO_DIR_SINK &&
params[i].dir != BT_AUDIO_DIR_SOURCE)) {
BT_DBG("Invalid params[%zu] values", i);
return -EINVAL;
}
if (streams[i]->group != NULL) {
BT_DBG("Channel[%u] (%p) already part of group %p",
i, streams[i], streams[i]->group);
if (params[i].stream->group != NULL) {
BT_DBG("params[%zu] stream (%p) already part of group %p",
i, params[i].stream, params[i].stream->group);
return -EALREADY;
}
if (group_qos == NULL) {
group_qos = params[i].qos;
} else if (!unicast_group_valid_qos(group_qos, params[i].qos)) {
BT_DBG("Stream[%zu] QoS incompatible with group QoS", i);
return -EINVAL;
}
CHECKIF(!bt_audio_valid_qos(params[i].qos)) {
BT_DBG("Invalid QoS");
return -EINVAL;
}
}
unicast_group = NULL;
for (index = 0; index < ARRAY_SIZE(unicast_groups); index++) {
for (size_t i = 0U; i < ARRAY_SIZE(unicast_groups); i++) {
/* Find free entry */
if (!unicast_groups[index].allocated) {
unicast_group = &unicast_groups[index];
if (!unicast_groups[i].allocated) {
unicast_group = &unicast_groups[i];
unicast_group->index = i;
break;
}
}
@ -1024,13 +1009,51 @@ int bt_audio_unicast_group_create(struct bt_audio_stream *streams[],
}
unicast_group->allocated = true;
for (size_t i = 0; i < num_stream; i++) {
for (size_t i = 0U; i < num_param; i++) {
sys_slist_t *group_streams = &unicast_group->streams;
struct bt_audio_stream *stream;
const enum bt_audio_dir dir = params[i].dir;
struct bt_iso_chan_qos *iso_qos;
struct bt_audio_stream *stream = params[i].stream;
struct bt_audio_iso *audio_iso;
struct bt_iso_chan_io_qos *io;
stream = streams[i];
audio_iso = bt_unicast_client_new_audio_iso(unicast_group, stream, dir);
if (audio_iso == NULL) {
unicast_group_param_cleanup(unicast_group, params, num_param);
return -ENOMEM;
}
bt_unicast_client_stream_bind_audio_iso(stream, audio_iso, dir);
iso_qos = &audio_iso->iso_qos;
if (dir == BT_AUDIO_DIR_SINK) {
/* If the endpoint is a sink, then we need to
* configure our TX parameters
*/
io = iso_qos->tx;
} else {
/* If the endpoint is a source, then we need to
* configure our RX parameters
*/
io = iso_qos->rx;
}
bt_audio_codec_qos_to_iso_qos(io, params[i].qos);
stream->unicast_group = unicast_group;
stream->qos = params[i].qos;
sys_slist_append(group_streams, &stream->node);
BT_DBG("Added stream %p to group %p", stream, unicast_group);
}
err = bt_audio_cig_create(unicast_group, group_qos);
if (err != 0) {
BT_DBG("bt_audio_cig_create failed: %d", err);
unicast_group_param_cleanup(unicast_group, params, num_param);
return err;
}
*out_unicast_group = unicast_group;
@ -1039,35 +1062,54 @@ int bt_audio_unicast_group_create(struct bt_audio_stream *streams[],
}
int bt_audio_unicast_group_add_streams(struct bt_audio_unicast_group *unicast_group,
struct bt_audio_stream *streams[],
size_t num_stream)
struct bt_audio_unicast_group_param params[],
size_t num_param)
{
const struct bt_codec_qos *group_qos = unicast_group->qos;
struct bt_audio_stream *tmp_stream;
size_t total_stream_cnt;
struct bt_iso_cig *cig;
int err;
CHECKIF(unicast_group == NULL) {
BT_DBG("unicast_group is NULL");
return -EINVAL;
}
CHECKIF(streams == NULL) {
BT_DBG("streams is NULL");
CHECKIF(params == NULL) {
BT_DBG("params is NULL");
return -EINVAL;
}
CHECKIF(num_stream == 0) {
BT_DBG("num_stream is 0");
CHECKIF(num_param == 0) {
BT_DBG("num_param is 0");
return -EINVAL;
}
for (size_t i = 0; i < num_stream; i++) {
CHECKIF(streams[i] == NULL) {
for (size_t i = 0U; i < num_param; i++) {
CHECKIF(params[i].stream == NULL ||
params[i].qos == NULL ||
(params[i].dir != BT_AUDIO_DIR_SINK &&
params[i].dir != BT_AUDIO_DIR_SOURCE)) {
BT_DBG("Invalid params[%zu] values", i);
return -EINVAL;
}
if (params[i].stream->group != NULL) {
BT_DBG("params[%zu] stream (%p) already part of group %p",
i, params[i].stream, params[i].stream->group);
return -EALREADY;
}
if (group_qos == NULL) {
group_qos = params[i].qos;
} else if (!unicast_group_valid_qos(group_qos, params[i].qos)) {
BT_DBG("Stream[%zu] QoS incompatible with group QoS", i);
return -EINVAL;
}
}
total_stream_cnt = num_stream;
total_stream_cnt = num_param;
SYS_SLIST_FOR_EACH_CONTAINER(&unicast_group->streams, tmp_stream, node) {
total_stream_cnt++;
}
@ -1079,15 +1121,6 @@ int bt_audio_unicast_group_add_streams(struct bt_audio_unicast_group *unicast_gr
}
/* Validate input */
for (size_t i = 0; i < num_stream; i++) {
if (streams[i]->group != NULL) {
BT_DBG("stream[%zu] is already part of group %p",
i, streams[i]->group);
return -EINVAL;
}
}
/* We can just check the CIG state to see if any streams have started as
* that would start the ISO connection procedure
*/
@ -1097,67 +1130,22 @@ int bt_audio_unicast_group_add_streams(struct bt_audio_unicast_group *unicast_gr
return -EBADMSG;
}
for (size_t i = 0; i < num_stream; i++) {
for (size_t i = 0U; i < num_param; i++) {
sys_slist_t *group_streams = &unicast_group->streams;
struct bt_audio_stream *stream = streams[i];
struct bt_audio_stream *stream = params[i].stream;
stream->unicast_group = unicast_group;
sys_slist_append(group_streams, &stream->node);
BT_DBG("Added stream %p to group %p", stream, unicast_group);
}
return 0;
}
err = bt_audio_cig_reconfigure(unicast_group, group_qos);
if (err != 0) {
BT_DBG("bt_audio_cig_reconfigure failed: %d", err);
unicast_group_param_cleanup(unicast_group, params, num_param);
int bt_audio_unicast_group_remove_streams(struct bt_audio_unicast_group *unicast_group,
struct bt_audio_stream *streams[],
size_t num_stream)
{
struct bt_iso_cig *cig;
CHECKIF(unicast_group == NULL) {
BT_DBG("unicast_group is NULL");
return -EINVAL;
}
CHECKIF(streams == NULL) {
BT_DBG("streams is NULL");
return -EINVAL;
}
CHECKIF(num_stream == 0) {
BT_DBG("num_stream is 0");
return -EINVAL;
}
/* Validate input */
for (size_t i = 0; i < num_stream; i++) {
CHECKIF(streams[i] == NULL) {
return -EINVAL;
}
if (streams[i]->group != unicast_group) {
BT_DBG("stream[%zu] group %p is not group %p",
i, streams[i]->group, unicast_group);
return -EINVAL;
}
}
/* We can just check the CIG state to see if any streams have started as
* that would start the ISO connection procedure
*/
cig = unicast_group->cig;
if (cig != NULL) {
BT_DBG("At least one unicast group stream has configured QoS");
return -EBADMSG;
}
for (size_t i = 0; i < num_stream; i++) {
sys_slist_t *group_streams = &unicast_group->streams;
struct bt_audio_stream *stream = streams[i];
stream->unicast_group = NULL;
(void)sys_slist_find_and_remove(group_streams,
&stream->node);
return err;
}
return 0;
@ -1165,7 +1153,7 @@ int bt_audio_unicast_group_remove_streams(struct bt_audio_unicast_group *unicast
int bt_audio_unicast_group_delete(struct bt_audio_unicast_group *unicast_group)
{
struct bt_audio_stream *stream;
struct bt_audio_stream *stream, *tmp;
CHECKIF(unicast_group == NULL) {
BT_DBG("unicast_group is NULL");
@ -1183,8 +1171,10 @@ int bt_audio_unicast_group_delete(struct bt_audio_unicast_group *unicast_group)
}
}
SYS_SLIST_FOR_EACH_CONTAINER(&unicast_group->streams, stream, node) {
SYS_SLIST_FOR_EACH_CONTAINER_SAFE(&unicast_group->streams, stream, tmp, node) {
stream->unicast_group = NULL;
sys_slist_remove(&unicast_group->streams, NULL, &stream->node);
bt_unicast_client_stream_unbind_audio_iso(stream);
}
(void)memset(unicast_group, 0, sizeof(*unicast_group));

View file

@ -47,8 +47,8 @@ static const struct bt_uuid *cp_uuid = BT_UUID_ASCS_ASE_CP;
static struct bt_audio_ep snks[CONFIG_BT_MAX_CONN][CONFIG_BT_AUDIO_UNICAST_CLIENT_ASE_SNK_COUNT];
static struct bt_audio_ep srcs[CONFIG_BT_MAX_CONN][CONFIG_BT_AUDIO_UNICAST_CLIENT_ASE_SRC_COUNT];
static struct bt_audio_iso isos[CONFIG_BT_MAX_CONN][CONFIG_BT_AUDIO_UNICAST_CLIENT_ASE_SNK_COUNT +
CONFIG_BT_AUDIO_UNICAST_CLIENT_ASE_SRC_COUNT];
static struct bt_audio_iso audio_isos[CONFIG_BT_AUDIO_UNICAST_CLIENT_GROUP_COUNT]
[CONFIG_BT_AUDIO_UNICAST_CLIENT_GROUP_STREAM_COUNT];
static struct bt_gatt_subscribe_params cp_subscribe[CONFIG_BT_MAX_CONN];
static struct bt_gatt_discover_params cp_disc[CONFIG_BT_MAX_CONN];
@ -70,23 +70,26 @@ static void unicast_client_ep_iso_recv(struct bt_iso_chan *chan,
{
struct bt_audio_iso *audio_iso = CONTAINER_OF(chan, struct bt_audio_iso,
iso_chan);
struct bt_audio_ep *ep = audio_iso->sink_ep;
struct bt_audio_stream *stream = audio_iso->sink_stream;
const struct bt_audio_stream_ops *ops;
if (ep == NULL) {
BT_ERR("Could not lookup ep by iso %p", chan);
if (stream == NULL) {
BT_ERR("Could not lookup stream by iso %p", chan);
return;
} else if (stream->ep == NULL) {
BT_ERR("Stream not associated with an ep");
return;
}
ops = ep->stream->ops;
ops = stream->ops;
if (IS_ENABLED(CONFIG_BT_AUDIO_DEBUG_STREAM_DATA)) {
BT_DBG("stream %p ep %p len %zu",
chan, ep, net_buf_frags_len(buf));
stream, stream->ep, net_buf_frags_len(buf));
}
if (ops != NULL && ops->recv != NULL) {
ops->recv(ep->stream, info, buf);
ops->recv(stream, info, buf);
} else {
BT_WARN("No callback for recv set");
}
@ -96,13 +99,23 @@ static void unicast_client_ep_iso_sent(struct bt_iso_chan *chan)
{
struct bt_audio_iso *audio_iso = CONTAINER_OF(chan, struct bt_audio_iso,
iso_chan);
struct bt_audio_ep *ep = audio_iso->source_ep;
struct bt_audio_stream_ops *ops = ep->stream->ops;
struct bt_audio_stream *stream = audio_iso->source_stream;
struct bt_audio_stream_ops *ops;
BT_DBG("stream %p ep %p", chan, ep);
if (stream == NULL) {
BT_ERR("Could not lookup stream by iso %p", chan);
return;
} else if (stream->ep == NULL) {
BT_ERR("Stream not associated with an ep");
return;
}
ops = stream->ops;
BT_DBG("stream %p", stream);
if (ops != NULL && ops->sent != NULL) {
ops->sent(ep->stream);
ops->sent(stream);
}
}
@ -110,22 +123,28 @@ static void unicast_client_ep_iso_connected(struct bt_iso_chan *chan)
{
struct bt_audio_iso *audio_iso = CONTAINER_OF(chan, struct bt_audio_iso,
iso_chan);
struct bt_audio_ep *source_ep = audio_iso->source_ep;
struct bt_audio_ep *sink_ep = audio_iso->sink_ep;
struct bt_audio_stream *source_stream = audio_iso->source_stream;
struct bt_audio_stream *sink_stream = audio_iso->sink_stream;
struct bt_audio_stream *stream;
struct bt_audio_ep *ep;
if (&sink_ep->iso->iso_chan == chan) {
ep = sink_ep;
if (sink_stream->iso == chan) {
stream = sink_stream;
} else {
ep = source_ep;
stream = source_stream;
}
if (ep == NULL) {
BT_ERR("Could not lookup ep by iso %p", chan);
if (stream == NULL) {
BT_ERR("Could not lookup stream by iso %p", chan);
return;
} else if (stream->ep == NULL) {
BT_ERR("Stream not associated with an ep");
return;
}
BT_DBG("stream %p ep %p dir %u", chan, ep, ep != NULL ? ep->dir : 0);
ep = stream->ep;
BT_DBG("stream %p ep %p dir %u", stream, ep, ep->dir);
if (ep->status.state != BT_AUDIO_EP_STATE_ENABLING) {
BT_DBG("endpoint not in enabling state: %s",
@ -139,27 +158,30 @@ static void unicast_client_ep_iso_disconnected(struct bt_iso_chan *chan,
{
struct bt_audio_iso *audio_iso = CONTAINER_OF(chan, struct bt_audio_iso,
iso_chan);
struct bt_audio_ep *source_ep = audio_iso->source_ep;
struct bt_audio_ep *sink_ep = audio_iso->sink_ep;
struct bt_audio_stream *source_stream = audio_iso->source_stream;
struct bt_audio_stream *sink_stream = audio_iso->sink_stream;
const struct bt_audio_stream_ops *ops;
struct bt_audio_stream *stream;
struct bt_audio_ep *ep;
if (&sink_ep->iso->iso_chan == chan) {
ep = sink_ep;
if (sink_stream->iso == chan) {
stream = sink_stream;
} else {
ep = source_ep;
stream = source_stream;
}
if (ep == NULL) {
BT_ERR("Could not lookup ep by iso %p", chan);
if (stream == NULL) {
BT_ERR("Could not lookup stream by iso %p", chan);
return;
} else if (stream->ep == NULL) {
BT_ERR("Stream not associated with an ep");
return;
}
ops = ep->stream->ops;
stream = ep->stream;
ep = stream->ep;
ops = stream->ops;
BT_DBG("stream %p ep %p reason 0x%02x", chan, ep, reason);
BT_DBG("stream %p ep %p reason 0x%02x", stream, ep, reason);
if (ops != NULL && ops->stopped != NULL) {
ops->stopped(stream);
@ -177,72 +199,95 @@ static struct bt_iso_chan_ops unicast_client_iso_ops = {
.disconnected = unicast_client_ep_iso_disconnected,
};
struct bt_audio_iso *bt_unicast_client_audio_iso_by_stream(const struct bt_audio_stream *stream)
{
uint8_t group_index;
if (stream == NULL || stream->unicast_group == NULL) {
return NULL;
}
group_index = stream->unicast_group->index;
for (size_t i = 0U; i < ARRAY_SIZE(audio_isos[group_index]); i++) {
struct bt_audio_iso *audio_iso = &audio_isos[group_index][i];
if (audio_iso->sink_stream == stream ||
audio_iso->source_stream == stream) {
return audio_iso;
}
}
return NULL;
}
void bt_unicast_client_ep_unbind_audio_iso(struct bt_audio_ep *ep)
{
struct bt_audio_iso *audio_iso = ep->iso;
struct bt_iso_chan_qos *qos;
const uint8_t dir = ep->dir;
BT_DBG("ep %p dir %u audio_iso %p", ep, ep->dir, audio_iso);
if (audio_iso == NULL) {
return;
}
ep->iso = NULL;
qos = audio_iso->iso_chan.qos;
if (dir == BT_AUDIO_DIR_SOURCE) {
/* If the endpoint is a source, then we need to
* reset our RX parameters
*/
audio_iso->sink_ep = NULL;
qos->rx = NULL;
} else if (dir == BT_AUDIO_DIR_SINK) {
/* If the endpoint is a sink, then we need to
* reset our TX parameters
*/
audio_iso->source_ep = NULL;
qos->tx = NULL;
} else {
__ASSERT(false, "Invalid dir: %u", dir);
}
}
void bt_unicast_client_ep_bind_audio_iso(struct bt_audio_ep *ep,
struct bt_audio_iso *audio_iso)
{
struct bt_iso_chan *iso_chan;
struct bt_iso_chan_qos *qos;
const uint8_t dir = ep->dir;
BT_DBG("ep %p audio_iso %p", ep, audio_iso);
ep->iso = audio_iso;
}
iso_chan = &ep->iso->iso_chan;
void bt_unicast_client_stream_bind_audio_iso(struct bt_audio_stream *stream,
struct bt_audio_iso *audio_iso,
enum bt_audio_dir dir)
{
struct bt_iso_chan *iso_chan;
struct bt_iso_chan_qos *qos;
iso_chan = &audio_iso->iso_chan;
iso_chan->ops = &unicast_client_iso_ops;
qos = iso_chan->qos = &ep->iso->iso_qos;
BT_DBG("ep %p, audio_iso %p, iso_chan %p, dir %u",
ep, audio_iso, iso_chan, ep->dir);
qos = iso_chan->qos = &audio_iso->iso_qos;
if (dir == BT_AUDIO_DIR_SOURCE) {
/* If the endpoint is a source, then we need to
* configure our RX parameters
*/
audio_iso->sink_ep = ep;
qos->rx = &ep->iso_io_qos;
audio_iso->sink_stream = stream;
qos->rx = &audio_iso->sink_io_qos;
} else if (dir == BT_AUDIO_DIR_SINK) {
/* If the endpoint is a sink, then we need to
* configure our TX parameters
*/
audio_iso->source_ep = ep;
qos->tx = &ep->iso_io_qos;
audio_iso->source_stream = stream;
qos->tx = &audio_iso->source_io_qos;
} else {
__ASSERT(false, "Invalid dir: %u", dir);
}
ep->stream->iso = iso_chan;
stream->iso = iso_chan;
}
void bt_unicast_client_stream_unbind_audio_iso(struct bt_audio_stream *stream)
{
BT_DBG("stream %p", stream);
if (stream->iso != NULL) {
struct bt_audio_iso *audio_iso = CONTAINER_OF(stream->iso,
struct bt_audio_iso,
iso_chan);
struct bt_iso_chan_qos *qos;
qos = audio_iso->iso_chan.qos;
stream->iso = NULL;
if (audio_iso->sink_stream == stream) {
audio_iso->sink_stream = NULL;
qos->rx = NULL;
} else if (audio_iso->source_stream == stream) {
audio_iso->source_stream = NULL;
qos->tx = NULL;
} else {
BT_ERR("Stream %p not linked to audio_iso %p", stream, audio_iso);
}
}
}
static void unicast_client_ep_init(struct bt_audio_ep *ep, uint16_t handle,
@ -286,49 +331,59 @@ static struct bt_audio_ep *unicast_client_ep_find(struct bt_conn *conn,
return NULL;
}
struct bt_audio_iso *bt_unicast_client_new_audio_iso(const struct bt_audio_stream *stream)
struct bt_audio_iso *bt_unicast_client_new_audio_iso(
const struct bt_audio_unicast_group *unicast_group,
const struct bt_audio_stream *stream,
enum bt_audio_dir dir)
{
const struct bt_audio_unicast_group *unicast_group = stream->unicast_group;
struct bt_conn *acl_conn = stream->conn;
const uint8_t index = bt_conn_index(acl_conn);
struct bt_audio_iso *cache = isos[index];
struct bt_audio_iso *free_audio_iso;
const uint8_t group_index = unicast_group->index;
struct bt_audio_iso *free_audio_iso = NULL;
free_audio_iso = NULL;
for (size_t i = 0; i < ARRAY_SIZE(isos[index]); i++) {
struct bt_audio_iso *audio_iso = &cache[i];
struct bt_audio_ep *ep;
BT_DBG("stream %p dir %u", stream, dir);
switch (stream->ep->dir) {
/* Look through the audio_isos and find a free one, or one for the same
* ACL connection where the opposite direction is what we want, and is
* free.
*
* The returned audio_iso is either an unused one, or one where @p
* stream can be used in the opposite direction.
*/
for (size_t i = 0U; i < ARRAY_SIZE(audio_isos[group_index]); i++) {
struct bt_audio_iso *audio_iso = &audio_isos[group_index][i];
const struct bt_conn *acl_conn = stream->conn;
const struct bt_audio_stream *paired_stream;
const struct bt_audio_stream *tmp_stream;
switch (dir) {
case BT_AUDIO_DIR_SINK:
ep = audio_iso->source_ep;
if (ep == NULL) {
if (free_audio_iso == NULL) {
free_audio_iso = audio_iso;
}
} else if (ep->unicast_group == unicast_group &&
ep->stream->conn == acl_conn) {
return audio_iso;
}
tmp_stream = audio_iso->source_stream;
paired_stream = audio_iso->sink_stream;
break;
case BT_AUDIO_DIR_SOURCE:
ep = audio_iso->sink_ep;
if (ep == NULL) {
if (free_audio_iso == NULL) {
free_audio_iso = audio_iso;
}
} else if (ep->unicast_group == unicast_group &&
ep->stream->conn == acl_conn) {
return audio_iso;
}
tmp_stream = audio_iso->sink_stream;
paired_stream = audio_iso->source_stream;
break;
default:
return NULL;
}
if (tmp_stream == NULL) {
if (paired_stream == NULL) {
if (free_audio_iso == NULL) {
free_audio_iso = audio_iso;
}
} else if (paired_stream->unicast_group == unicast_group &&
acl_conn != NULL &&
paired_stream->conn == acl_conn) {
BT_DBG("Use existing audio_iso %p", audio_iso);
return audio_iso;
}
}
}
BT_DBG("New audio_iso %p", free_audio_iso);
return free_audio_iso;
}
@ -404,20 +459,20 @@ static void unicast_client_ep_idle_state(struct bt_audio_ep *ep,
static void unicast_client_ep_qos_reset(struct bt_audio_ep *ep)
{
BT_DBG("ep %p dir %u", ep, ep->dir);
BT_DBG("ep %p dir %u audio_iso %p", ep, ep->dir, ep->iso);
if (ep->dir == BT_AUDIO_DIR_SOURCE) {
/* If the endpoint is a source, then we need to
* configure our RX parameters
* reset our RX parameters
*/
ep->iso->iso_qos.rx = memset(&ep->iso_io_qos, 0,
sizeof(ep->iso_io_qos));
ep->iso->iso_qos.rx = memset(&ep->iso->sink_io_qos, 0,
sizeof(ep->iso->sink_io_qos));
} else if (ep->dir == BT_AUDIO_DIR_SINK) {
/* If the endpoint is a sink, then we need to
* configure our TX parameters
* reset our TX parameters
*/
ep->iso->iso_qos.tx = memset(&ep->iso_io_qos, 0,
sizeof(ep->iso_io_qos));
ep->iso->iso_qos.tx = memset(&ep->iso->source_io_qos, 0,
sizeof(ep->iso->source_io_qos));
} else {
__ASSERT(false, "Invalid ep->dir: %u", ep->dir);
}
@ -443,8 +498,12 @@ static void unicast_client_ep_config_state(struct bt_audio_ep *ep,
cfg = net_buf_simple_pull_mem(buf, sizeof(*cfg));
if (stream->codec == NULL || stream->codec->id != cfg->codec.id) {
BT_ERR("Codec configuration mismatched");
if (stream->codec == NULL) {
BT_ERR("Stream %p does not have a codec configured", stream);
return;
} else if (stream->codec->id != cfg->codec.id) {
BT_ERR("Codec configuration mismatched: %u, %u",
stream->codec->id, cfg->codec.id);
/* TODO: Release the stream? */
return;
}
@ -1422,18 +1481,6 @@ static void unicast_client_reset(struct bt_audio_ep *ep)
bt_audio_stream_reset(ep->stream);
switch (ep->dir) {
case BT_AUDIO_DIR_SINK:
ep->iso->source_ep = NULL;
break;
case BT_AUDIO_DIR_SOURCE:
ep->iso->sink_ep = NULL;
break;
default:
BT_DBG("Invalid ep->dir %u", ep->dir);
break;
}
(void)memset(ep, 0, offsetof(struct bt_audio_ep, subscribe));
}

View file

@ -54,9 +54,18 @@ void bt_unicast_client_ep_detach(struct bt_audio_ep *ep, struct bt_audio_stream
*
* @return An audio_iso struct fit for the stream, or NULL.
*/
struct bt_audio_iso *bt_unicast_client_new_audio_iso(const struct bt_audio_stream *stream);
struct bt_audio_iso *bt_unicast_client_new_audio_iso(
const struct bt_audio_unicast_group *unicast_group,
const struct bt_audio_stream *stream,
enum bt_audio_dir dir);
struct bt_audio_iso *bt_unicast_client_audio_iso_by_stream(const struct bt_audio_stream *stream);
void bt_unicast_client_stream_bind_audio_iso(struct bt_audio_stream *stream,
struct bt_audio_iso *audio_iso,
enum bt_audio_dir dir);
void bt_unicast_client_stream_unbind_audio_iso(struct bt_audio_stream *stream);
void bt_unicast_client_ep_bind_audio_iso(struct bt_audio_ep *ep,
struct bt_audio_iso *iso);
struct bt_audio_iso *audio_iso);
void bt_unicast_client_ep_unbind_audio_iso(struct bt_audio_ep *ep);

View file

@ -37,6 +37,8 @@ static struct bt_audio_unicast_group *default_unicast_group;
static struct bt_codec *rcodecs[2][CONFIG_BT_AUDIO_UNICAST_CLIENT_PAC_COUNT];
static struct bt_audio_ep *snks[CONFIG_BT_AUDIO_UNICAST_CLIENT_ASE_SNK_COUNT];
static struct bt_audio_ep *srcs[CONFIG_BT_AUDIO_UNICAST_CLIENT_ASE_SNK_COUNT];
static uint8_t stream_dir(const struct bt_audio_stream *stream);
#endif /* CONFIG_BT_AUDIO_UNICAST_CLIENT */
#endif /* CONFIG_BT_AUDIO_UNICAST */
@ -758,15 +760,20 @@ static int cmd_qos(const struct shell *sh, size_t argc, char *argv[])
}
if (default_unicast_group == NULL) {
err = bt_audio_unicast_group_create(&default_stream, 1, &default_unicast_group);
struct bt_audio_unicast_group_param params = {
.stream = default_stream,
.qos = &default_preset->preset.qos,
.dir = stream_dir(default_stream)
};
err = bt_audio_unicast_group_create(&params, 1, &default_unicast_group);
if (err != 0) {
shell_error(sh, "Unable to create default unicast group: %d", err);
return -ENOEXEC;
}
}
err = bt_audio_stream_qos(default_conn, default_unicast_group,
&named_preset->preset.qos);
err = bt_audio_stream_qos(default_conn, default_unicast_group);
if (err) {
shell_error(sh, "Unable to setup QoS");
return -ENOEXEC;

View file

@ -310,74 +310,42 @@ static size_t release_streams(size_t stream_cnt)
return stream_cnt;
}
static void create_unicast_group(struct bt_audio_unicast_group **unicast_group,
size_t stream_cnt)
{
struct bt_audio_stream *streams[ARRAY_SIZE(g_streams)];
int err;
struct bt_audio_unicast_group_param params[ARRAY_SIZE(g_streams)];
for (size_t i = 0U; i < stream_cnt; i++) {
streams[i] = &g_streams[i];
params[i].stream = &g_streams[i];
params[i].qos = &preset_16_2_1.qos;
params[i].dir = BT_AUDIO_DIR_SINK; /* we only configure sinks */
}
#if defined(CONFIG_BT_CTLR_CENTRAL_ISO)
int err;
/* Require controller support for CIGs */
printk("Creating unicast group\n");
err = bt_audio_unicast_group_create(streams, 1, unicast_group);
err = bt_audio_unicast_group_create(&params, 1, unicast_group);
if (err != 0) {
FAIL("Unable to create unicast group: %d", err);
return;
}
/* Test removing streams from group before adding them */
if (stream_cnt > 1) {
const size_t remaining_streams = stream_cnt - 1;
err = bt_audio_unicast_group_remove_streams(*unicast_group,
&streams[1],
remaining_streams);
if (err == 0) {
FAIL("Able to remove stream not in group");
return;
}
/* Test adding streams to group after creation */
err = bt_audio_unicast_group_add_streams(*unicast_group,
&streams[1],
remaining_streams);
if (err != 0) {
FAIL("Unable to add streams to unicast group: %d", err);
return;
}
}
#endif /* CONFIG_BT_CTLR_CENTRAL_ISO */
}
static void delete_unicast_group(struct bt_audio_unicast_group *unicast_group,
size_t stream_cnt)
static void delete_unicast_group(struct bt_audio_unicast_group *unicast_group)
{
struct bt_audio_stream *streams[ARRAY_SIZE(g_streams)];
#if defined(CONFIG_BT_CTLR_CENTRAL_ISO)
int err;
for (size_t i = 0U; i < stream_cnt; i++) {
streams[i] = &g_streams[i];
}
if (stream_cnt > 1) {
const size_t remove_streams_cnt = stream_cnt - 1;
err = bt_audio_unicast_group_remove_streams(unicast_group,
&streams[1],
remove_streams_cnt);
if (err != 0) {
FAIL("Unable to remove streams from unicast group: %d",
err);
return;
}
}
/* Require controller support for CIGs */
err = bt_audio_unicast_group_delete(unicast_group);
if (err != 0) {
FAIL("Unable to delete unicast group: %d", err);
return;
}
#endif /* CONFIG_BT_CTLR_CENTRAL_ISO */
}
static void test_main(void)
@ -412,7 +380,7 @@ static void test_main(void)
/* Test removing streams from group after creation */
printk("Deleting unicast group\n");
delete_unicast_group(unicast_group, stream_cnt);
delete_unicast_group(unicast_group);
unicast_group = NULL;
}