blob: 3546212e9cf19c4df0052cc162803006e3b83ab5 [file] [log] [blame]
/** @file
* @brief Bluetooth Basic Audio Profile (BAP) Unicast Server role.
*
* Copyright (c) 2021-2023 Nordic Semiconductor ASA
* Copyright (c) 2022 Codecoup
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/kernel.h>
#include <zephyr/sys/byteorder.h>
#include <zephyr/sys/printk.h>
#include <zephyr/bluetooth/bluetooth.h>
#include <zephyr/bluetooth/conn.h>
#include <zephyr/bluetooth/audio/audio.h>
#include <zephyr/bluetooth/audio/bap.h>
#include <zephyr/bluetooth/audio/pacs.h>
NET_BUF_POOL_FIXED_DEFINE(tx_pool, CONFIG_BT_ASCS_ASE_SRC_COUNT,
BT_ISO_SDU_BUF_SIZE(CONFIG_BT_ISO_TX_MTU),
CONFIG_BT_CONN_TX_USER_DATA_SIZE, NULL);
static const struct bt_audio_codec_cap lc3_codec_cap = BT_AUDIO_CODEC_CAP_LC3(
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_CONTEXT_TYPE_CONVERSATIONAL | BT_AUDIO_CONTEXT_TYPE_MEDIA));
static struct bt_conn *default_conn;
static struct k_work_delayable audio_send_work;
static struct bt_bap_stream streams[CONFIG_BT_ASCS_ASE_SNK_COUNT + CONFIG_BT_ASCS_ASE_SRC_COUNT];
static struct audio_source {
struct bt_bap_stream *stream;
uint16_t seq_num;
} source_streams[CONFIG_BT_ASCS_ASE_SRC_COUNT];
static size_t configured_source_stream_count;
static const struct bt_audio_codec_qos_pref qos_pref =
BT_AUDIO_CODEC_QOS_PREF(true, BT_GAP_LE_PHY_2M, 0x02, 10, 20000, 40000, 20000, 40000);
static uint16_t get_and_incr_seq_num(const struct bt_bap_stream *stream)
{
for (size_t i = 0U; i < configured_source_stream_count; i++) {
if (stream == source_streams[i].stream) {
return source_streams[i].seq_num++;
}
}
printk("Could not find endpoint from stream %p\n", stream);
return 0;
}
static void print_hex(const uint8_t *ptr, size_t len)
{
while (len-- != 0) {
printk("%02x", *ptr++);
}
}
static bool print_cb(struct bt_data *data, void *user_data)
{
const char *str = (const char *)user_data;
printk("%s: type 0x%02x value_len %u\n", str, data->type, data->data_len);
print_hex(data->data, data->data_len);
printk("\n");
return true;
}
static void print_codec_cfg(const struct bt_audio_codec_cfg *codec_cfg)
{
printk("codec_cfg 0x%02x cid 0x%04x vid 0x%04x count %u\n", codec_cfg->id, codec_cfg->cid,
codec_cfg->vid, codec_cfg->data_len);
if (codec_cfg->id == BT_HCI_CODING_FORMAT_LC3) {
enum bt_audio_location chan_allocation;
int ret;
/* LC3 uses the generic LTV format - other codecs might do as well */
bt_audio_data_parse(codec_cfg->data, codec_cfg->data_len, print_cb, "data");
ret = bt_audio_codec_cfg_get_freq(codec_cfg);
if (ret > 0) {
printk(" Frequency: %d Hz\n", bt_audio_codec_cfg_freq_to_freq_hz(ret));
}
ret = bt_audio_codec_cfg_get_frame_dur(codec_cfg);
if (ret > 0) {
printk(" Frame Duration: %d us\n",
bt_audio_codec_cfg_frame_dur_to_frame_dur_us(ret));
}
if (bt_audio_codec_cfg_get_chan_allocation(codec_cfg, &chan_allocation) == 0) {
printk(" Channel allocation: 0x%x\n", chan_allocation);
}
printk(" Octets per frame: %d (negative means value not pressent)\n",
bt_audio_codec_cfg_get_octets_per_frame(codec_cfg));
printk(" Frames per SDU: %d\n",
bt_audio_codec_cfg_get_frame_blocks_per_sdu(codec_cfg, true));
} else {
print_hex(codec_cfg->data, codec_cfg->data_len);
}
bt_audio_data_parse(codec_cfg->meta, codec_cfg->meta_len, print_cb, "meta");
}
static void print_qos(const struct bt_audio_codec_qos *qos)
{
printk("QoS: interval %u framing 0x%02x phy 0x%02x sdu %u "
"rtn %u latency %u pd %u\n",
qos->interval, qos->framing, qos->phy, qos->sdu,
qos->rtn, qos->latency, qos->pd);
}
/**
* @brief Send audio data on timeout
*
* This will send an increasing amount of audio data, starting from 1 octet.
* The data is just mock data, and does not actually represent any audio.
*
* First iteration : 0x00
* Second iteration: 0x00 0x01
* Third iteration : 0x00 0x01 0x02
*
* And so on, until it wraps around the configured MTU (CONFIG_BT_ISO_TX_MTU)
*
* @param work Pointer to the work structure
*/
static void audio_timer_timeout(struct k_work *work)
{
int ret;
static uint8_t buf_data[CONFIG_BT_ISO_TX_MTU];
static bool data_initialized;
struct net_buf *buf;
static size_t len_to_send = 1;
if (!data_initialized) {
/* TODO: Actually encode some audio data */
for (size_t i = 0U; i < ARRAY_SIZE(buf_data); i++) {
buf_data[i] = (uint8_t)i;
}
data_initialized = true;
}
/* We configured the sink streams to be first in `streams`, so that
* we can use `stream[i]` to select sink streams (i.e. streams with
* data going to the server)
*/
for (size_t i = 0; i < configured_source_stream_count; i++) {
struct bt_bap_stream *stream = source_streams[i].stream;
buf = net_buf_alloc(&tx_pool, K_FOREVER);
net_buf_reserve(buf, BT_ISO_CHAN_SEND_RESERVE);
net_buf_add_mem(buf, buf_data, len_to_send);
ret = bt_bap_stream_send(stream, buf,
get_and_incr_seq_num(stream),
BT_ISO_TIMESTAMP_NONE);
if (ret < 0) {
printk("Failed to send audio data on streams[%zu] (%p): (%d)\n",
i, stream, ret);
net_buf_unref(buf);
} else {
printk("Sending mock data with len %zu on streams[%zu] (%p)\n",
len_to_send, i, stream);
}
}
k_work_schedule(&audio_send_work, K_MSEC(1000U));
len_to_send++;
if (len_to_send > ARRAY_SIZE(buf_data)) {
len_to_send = 1;
}
}
static struct bt_bap_stream *stream_alloc(void)
{
for (size_t i = 0; i < ARRAY_SIZE(streams); i++) {
struct bt_bap_stream *stream = &streams[i];
if (!stream->conn) {
return stream;
}
}
return NULL;
}
static int lc3_config(struct bt_conn *conn, const struct bt_bap_ep *ep, enum bt_audio_dir dir,
const struct bt_audio_codec_cfg *codec_cfg, struct bt_bap_stream **stream,
struct bt_audio_codec_qos_pref *const pref, struct bt_bap_ascs_rsp *rsp)
{
printk("ASE Codec Config: conn %p ep %p dir %u\n", conn, ep, dir);
print_codec_cfg(codec_cfg);
*stream = stream_alloc();
if (*stream == NULL) {
printk("No streams available\n");
*rsp = BT_BAP_ASCS_RSP(BT_BAP_ASCS_RSP_CODE_NO_MEM, BT_BAP_ASCS_REASON_NONE);
return -ENOMEM;
}
printk("ASE Codec Config stream %p\n", *stream);
if (dir == BT_AUDIO_DIR_SOURCE) {
source_streams[configured_source_stream_count++].stream = *stream;
}
*pref = qos_pref;
return 0;
}
static int lc3_reconfig(struct bt_bap_stream *stream, enum bt_audio_dir dir,
const struct bt_audio_codec_cfg *codec_cfg,
struct bt_audio_codec_qos_pref *const pref, struct bt_bap_ascs_rsp *rsp)
{
printk("ASE Codec Reconfig: stream %p\n", stream);
print_codec_cfg(codec_cfg);
*rsp = BT_BAP_ASCS_RSP(BT_BAP_ASCS_RSP_CODE_CONF_UNSUPPORTED, BT_BAP_ASCS_REASON_NONE);
/* We only support one QoS at the moment, reject changes */
return -ENOEXEC;
}
static int lc3_qos(struct bt_bap_stream *stream, const struct bt_audio_codec_qos *qos,
struct bt_bap_ascs_rsp *rsp)
{
printk("QoS: stream %p qos %p\n", stream, qos);
print_qos(qos);
return 0;
}
static int lc3_enable(struct bt_bap_stream *stream, const uint8_t meta[], size_t meta_len,
struct bt_bap_ascs_rsp *rsp)
{
printk("Enable: stream %p meta_len %zu\n", stream, meta_len);
return 0;
}
static int lc3_start(struct bt_bap_stream *stream, struct bt_bap_ascs_rsp *rsp)
{
printk("Start: stream %p\n", stream);
if (IS_ENABLED(CONFIG_BT_ASCS_ASE_SRC)) {
for (size_t i = 0U; i < configured_source_stream_count; i++) {
if (source_streams[i].stream == stream) {
source_streams[i].seq_num = 0U;
break;
}
}
if (configured_source_stream_count > 0 &&
!k_work_delayable_is_pending(&audio_send_work)) {
/* Start send timer */
k_work_schedule(&audio_send_work, K_MSEC(0));
}
}
return 0;
}
static bool data_func_cb(struct bt_data *data, void *user_data)
{
struct bt_bap_ascs_rsp *rsp = (struct bt_bap_ascs_rsp *)user_data;
if (!BT_AUDIO_METADATA_TYPE_IS_KNOWN(data->type)) {
printk("Invalid metadata type %u or length %u\n", data->type, data->data_len);
*rsp = BT_BAP_ASCS_RSP(BT_BAP_ASCS_RSP_CODE_METADATA_REJECTED, data->type);
return -EINVAL;
}
return true;
}
static int lc3_metadata(struct bt_bap_stream *stream, const uint8_t meta[], size_t meta_len,
struct bt_bap_ascs_rsp *rsp)
{
printk("Metadata: stream %p meta_len %zu\n", stream, meta_len);
return bt_audio_data_parse(meta, meta_len, data_func_cb, rsp);
}
static int lc3_disable(struct bt_bap_stream *stream, struct bt_bap_ascs_rsp *rsp)
{
printk("Disable: stream %p\n", stream);
return 0;
}
static int lc3_stop(struct bt_bap_stream *stream, struct bt_bap_ascs_rsp *rsp)
{
printk("Stop: stream %p\n", stream);
return 0;
}
static int lc3_release(struct bt_bap_stream *stream, struct bt_bap_ascs_rsp *rsp)
{
printk("Release: stream %p\n", stream);
return 0;
}
static const struct bt_bap_unicast_server_cb unicast_server_cb = {
.config = lc3_config,
.reconfig = lc3_reconfig,
.qos = lc3_qos,
.enable = lc3_enable,
.start = lc3_start,
.metadata = lc3_metadata,
.disable = lc3_disable,
.stop = lc3_stop,
.release = lc3_release,
};
static void stream_recv(struct bt_bap_stream *stream, const struct bt_iso_recv_info *info,
struct net_buf *buf)
{
printk("Incoming audio on stream %p len %u\n", stream, buf->len);
}
static struct bt_bap_stream_ops stream_ops = {
.recv = stream_recv
};
static void connected(struct bt_conn *conn, uint8_t err)
{
char addr[BT_ADDR_LE_STR_LEN];
bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr));
if (err != 0) {
printk("Failed to connect to %s (%u)\n", addr, err);
default_conn = NULL;
return;
}
printk("Connected: %s\n", addr);
default_conn = bt_conn_ref(conn);
}
static void disconnected(struct bt_conn *conn, uint8_t reason)
{
char addr[BT_ADDR_LE_STR_LEN];
struct k_work_sync sync;
if (conn != default_conn) {
return;
}
bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr));
printk("Disconnected: %s (reason 0x%02x)\n", addr, reason);
bt_conn_unref(default_conn);
default_conn = NULL;
if (IS_ENABLED(CONFIG_BT_ASCS_ASE_SRC)) {
/* reset data */
(void)memset(source_streams, 0, sizeof(source_streams));
configured_source_stream_count = 0U;
k_work_cancel_delayable_sync(&audio_send_work, &sync);
}
}
BT_CONN_CB_DEFINE(conn_callbacks) = {
.connected = connected,
.disconnected = disconnected,
};
static struct bt_pacs_cap cap_sink = {
.codec_cap = &lc3_codec_cap,
};
static struct bt_pacs_cap cap_source = {
.codec_cap = &lc3_codec_cap,
};
int bap_unicast_sr_init(void)
{
bt_bap_unicast_server_register_cb(&unicast_server_cb);
bt_pacs_cap_register(BT_AUDIO_DIR_SINK, &cap_sink);
if (IS_ENABLED(CONFIG_BT_PAC_SNK_LOC)) {
if (IS_ENABLED(CONFIG_HAP_HA_HEARING_AID_BANDED)) {
/* HAP_d1.0r00; 3.7 BAP Unicast Server role requirements
* A Banded Hearing Aid in the HA role shall set the
* Front Left and the Front Right bits to a value of 0b1
* in the Sink Audio Locations characteristic value.
*/
bt_pacs_set_location(BT_AUDIO_DIR_SINK,
(BT_AUDIO_LOCATION_FRONT_LEFT |
BT_AUDIO_LOCATION_FRONT_RIGHT));
} else if (IS_ENABLED(CONFIG_HAP_HA_HEARING_AID_LEFT)) {
bt_pacs_set_location(BT_AUDIO_DIR_SINK,
BT_AUDIO_LOCATION_FRONT_LEFT);
} else {
bt_pacs_set_location(BT_AUDIO_DIR_SINK,
BT_AUDIO_LOCATION_FRONT_RIGHT);
}
}
if (IS_ENABLED(CONFIG_BT_ASCS_ASE_SRC)) {
bt_pacs_cap_register(BT_AUDIO_DIR_SOURCE, &cap_source);
}
for (size_t i = 0; i < ARRAY_SIZE(streams); i++) {
bt_bap_stream_cb_register(&streams[i], &stream_ops);
}
if (IS_ENABLED(CONFIG_BT_ASCS_ASE_SRC)) {
k_work_init_delayable(&audio_send_work, audio_timer_timeout);
}
return 0;
}