From 048498a4f9deff239a8151f8e2488977e4d6e001 Mon Sep 17 00:00:00 2001 From: Mariusz Skamra Date: Thu, 25 Jan 2024 13:30:12 +0100 Subject: [PATCH] nimble/audio: Add initial audio broadcast sink implementation This adds initial implementation of audio broadcast sink. --- apps/btshell/pkg.yml | 1 + nimble/host/audio/include/audio/ble_audio.h | 35 + .../include/audio/ble_audio_broadcast_sink.h | 195 ++++++ nimble/host/audio/pkg.yml | 3 + .../host/audio/src/ble_audio_broadcast_sink.c | 607 ++++++++++++++++++ nimble/host/audio/src/ble_audio_priv.h | 8 + .../host/audio/src/ble_audio_svc_bass_stub.h | 51 ++ nimble/host/audio/syscfg.yml | 32 +- .../audio/targets/btshell_native/syscfg.yml | 3 + 9 files changed, 934 insertions(+), 1 deletion(-) create mode 100644 nimble/host/audio/include/audio/ble_audio_broadcast_sink.h create mode 100644 nimble/host/audio/src/ble_audio_broadcast_sink.c create mode 100644 nimble/host/audio/src/ble_audio_svc_bass_stub.h diff --git a/apps/btshell/pkg.yml b/apps/btshell/pkg.yml index d98f541f06..87d1a90b1d 100644 --- a/apps/btshell/pkg.yml +++ b/apps/btshell/pkg.yml @@ -31,6 +31,7 @@ pkg.deps: - "@apache-mynewt-core/sys/shell" - "@apache-mynewt-core/util/parse_arg" - nimble/host + - nimble/host/audio - nimble/host/services/gap - nimble/host/services/gatt - nimble/host/store/config diff --git a/nimble/host/audio/include/audio/ble_audio.h b/nimble/host/audio/include/audio/ble_audio.h index 288f13ee50..960e02459b 100644 --- a/nimble/host/audio/include/audio/ble_audio.h +++ b/nimble/host/audio/include/audio/ble_audio.h @@ -55,6 +55,9 @@ * @endcond */ +/** Basic Audio Announcement Service UUID. */ +#define BLE_BASIC_AUDIO_ANNOUNCEMENT_SVC_UUID 0x1851 + /** Broadcast Audio Announcement Service UUID. */ #define BLE_BROADCAST_AUDIO_ANNOUNCEMENT_SVC_UUID 0x1852 @@ -387,6 +390,10 @@ /** LE Audio Codec Supported Channel Count: 8. */ #define BLE_AUDIO_CODEC_SUPPORTED_CHANNEL_COUNT_8 0x0080 +#define BLE_AUDIO_BROADCAST_ID_MASK 0xFFFFFF +#define BLE_AUDIO_BROADCAST_BIS_SYNC_NO_PREF 0xFFFFFFFF +#define BLE_AUDIO_BROADCAST_CODE_SIZE 16 + /** @} */ /** @@ -516,6 +523,9 @@ struct ble_audio_broadcast_name { /** BLE Audio event: Codec Unregistered */ #define BLE_AUDIO_EVENT_CODEC_UNREGISTERED 2 +/** BLE Audio event: Basic Audio Announcement */ +#define BLE_AUDIO_EVENT_BASIC_AUDIO_ANNOUNCEMENT 3 + /** @} */ /** @brief Broadcast Announcement */ @@ -551,6 +561,24 @@ struct ble_audio_event_codec_unregistered { const struct ble_audio_codec_record *record; }; +/** @brief Basic Audio Announcement */ +struct ble_audio_event_basic_audio_announcement { + /** Audio Broadcast Sink instance */ + struct ble_audio_bsnk *snk; + + /** Advertiser transmit power in dBm (127 if unavailable) */ + int8_t tx_power; + + /** Received signal strength indication in dBm (127 if unavailable) */ + int8_t rssi; + + /** Broadcast Audio Source Endpoint (BASE) length */ + uint8_t base_len; + + /** Broadcast Audio Source Endpoint (BASE) */ + const uint8_t *base; +}; + /** * Represents a BLE Audio related event. When such an event occurs, the host * notifies the application by passing an instance of this structure to an @@ -588,6 +616,13 @@ struct ble_audio_event { * Represents a codec registration. */ struct ble_audio_event_codec_unregistered codec_unregistered; + + /** + * @ref BLE_AUDIO_EVENT_BASIC_AUDIO_ANNOUNCEMENT + * + * Represents a received Basic Audio Announcement. + */ + struct ble_audio_event_basic_audio_announcement basic_audio_announcement; }; }; diff --git a/nimble/host/audio/include/audio/ble_audio_broadcast_sink.h b/nimble/host/audio/include/audio/ble_audio_broadcast_sink.h new file mode 100644 index 0000000000..c86de55af5 --- /dev/null +++ b/nimble/host/audio/include/audio/ble_audio_broadcast_sink.h @@ -0,0 +1,195 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#ifndef H_BLE_AUDIO_BROADCAST_SINK_ +#define H_BLE_AUDIO_BROADCAST_SINK_ + +/** + * @file ble_audio_broadcast_sink.h + * + * @brief Bluetooth Low Energy Audio Broadcast Sink API + * + * @defgroup ble_audio_broadcast_sink Bluetooth LE Audio Broadcast Sink + * @ingroup bt_host + * @{ + */ + +#include +#include "host/ble_gap.h" +#include "host/ble_iso.h" +#include "ble_audio.h" + +/** @brief Periodic Advertisement Sync parameters */ +struct ble_audio_broadcast_sink_pa_sync_params { + /** Advertiser Address for the Broadcast Source */ + const ble_addr_t *adv_addr; + + /** Advertising Set ID */ + uint8_t adv_sid; + + /** Periodic advertising interval in 1.25 ms units */ + uint16_t pa_interval; + + /** Retry count. The parameter is used to calculate the Sync Timeout */ + uint8_t retry_count; + + /** The maximum number of periodic advertising events that controller can + * skip after a successful receive. + */ + uint16_t skip; +}; + +/** + * @brief Synchronize to Periodic Advertisement + * + * Start receiving the BIGInfo and Broadcast Audio Source Endpoint (BASE) + * reports. + * + * This function initiates Periodic Advertisement Synchronization Procedure. + * The function requires extended scan to be enabled. + * + * @param[in] source_id Audio Broadcast Source ID. + * @param[in] params Periodic Advertisement Sync parameters to use. + * + * @return 0 on success; + * A non-zero value on failure. + */ +int ble_audio_broadcast_sink_pa_sync( + uint8_t source_id, + const struct ble_audio_broadcast_sink_pa_sync_params *params); + +/** @brief Periodic Advertisement Sync Transfer parameters */ +struct ble_audio_broadcast_sink_pa_sync_recv_params { + /** Connection handle */ + uint16_t conn_handle; + + /** Periodic advertising interval in 1.25 ms units */ + uint16_t pa_interval; + + /** Retry count. The parameter is used to calculate the Sync Timeout */ + uint8_t retry_count; + + /** The maximum number of periodic advertising events that controller can + * skip after a successful receive. + */ + uint16_t skip; +}; + +/** + * @brief Synchronize to Periodic Advertisement with PAST + * + * Start receiving the BIGInfo and Broadcast Audio Source Endpoint (BASE) + * reports. + * + * This function initiates Periodic Advertisement Sync Transfer (PAST) procedure + * to offload the scanning. + * + * @param[in] source_id Audio Broadcast Source ID. + * @param[in] params Periodic Advertisement Sync parameters to use. + * + * @return 0 on success; + * A non-zero value on failure. + */ +int ble_audio_broadcast_sink_pa_sync_recv( + uint8_t source_id, + const struct ble_audio_broadcast_sink_pa_sync_recv_params *params); + +/** + * @brief Terminate or cancel pending Periodic Advertisement Sync + * + * This function terminates active Periodic Advertisement Sync or cancels the + * pending sync. + * + * @param[in] source_id Audio Broadcast Source ID. + * + * @return 0 on success; + * A non-zero value on failure. + */ +int ble_audio_broadcast_sink_pa_sync_term(uint8_t source_id); + +/** @brief BIS Sync parameters */ +struct ble_audio_broadcast_sink_bis_sync_params { + /** BIS index */ + uint8_t bis_index; + + /** ISO data received callback */ + ble_iso_event_fn *cb; + + /** Callback argument */ + void *cb_arg; +}; + +/** @brief Broadcast Sink Sync parameters */ +struct ble_audio_broadcast_sink_sync_params { + /** NULL-terminated broadcast code */ + const char *broadcast_code; + + /** Number of BISes */ + uint8_t num_bis; + + /** BIS Sync parameters */ + struct ble_audio_broadcast_sink_bis_sync_params *params; +}; + +/** + * @brief Synchronize to Audio Broadcast + * + * This function is used to synchronize to Audio Broadcast to start + * reception of audio data. + * + * @param[in] source_id Audio Broadcast Source ID. + * @param[in] params Broadcast Sink Sync parameters to use. + * + * @return 0 on success; + * BLE_HS_EINVAL if the parameters are invalid; + * BLE_HS_ENOENT if missing PA synchronization; + * BLE_HS_EAGAIN if PA Sync is in progress; + * BLE_HS_EENCRYPT if Broadcast Code is missing; + * BLE_HS_ENOMEM if memory capacity exceeded; + * Other nonzero on error. + */ +int ble_audio_broadcast_sink_sync( + uint8_t source_id, + const struct ble_audio_broadcast_sink_sync_params *params); + +/** + * @brief Terminate or cancel pending Audio Broadcast synchronization + * + * This function terminates active Audio Broadcast sync or cancels the + * pending sync. + * + * @param[in] source_id Audio Broadcast Source ID. + * + * @return 0 on success; + * A non-zero value on failure. + */ +int ble_audio_broadcast_sink_sync_term(uint8_t source_id); + +/** + * @brief Initialize Broadcast Sink role + * + * This function is restricted to be called by sysinit. + */ +int ble_audio_broadcast_sink_init(void); + +/** + * @} + */ + +#endif /* H_BLE_AUDIO_BROADCAST_SINK_*/ diff --git a/nimble/host/audio/pkg.yml b/nimble/host/audio/pkg.yml index 66d418d311..b60687f849 100644 --- a/nimble/host/audio/pkg.yml +++ b/nimble/host/audio/pkg.yml @@ -30,3 +30,6 @@ pkg.keywords: pkg.deps: - nimble - nimble/host + +pkg.init.BLE_AUDIO_BROADCAST_SINK: + ble_audio_broadcast_sink_init: 'MYNEWT_VAL(BLE_AUDIO_BROADCAST_SINK_SYSINIT_STAGE)' diff --git a/nimble/host/audio/src/ble_audio_broadcast_sink.c b/nimble/host/audio/src/ble_audio_broadcast_sink.c new file mode 100644 index 0000000000..aab1f1e95b --- /dev/null +++ b/nimble/host/audio/src/ble_audio_broadcast_sink.c @@ -0,0 +1,607 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include "sysinit/sysinit.h" + +#if MYNEWT_VAL(BLE_AUDIO_BROADCAST_SINK) +#include "host/ble_hs.h" +#include "host/ble_iso.h" +#include "host/ble_uuid.h" +#include "audio/ble_audio.h" +#include "audio/ble_audio_broadcast_sink.h" + +#include "ble_audio_svc_bass_stub.h" + +#include "ble_audio_priv.h" + +#ifndef MIN +#define MIN(a, b) (((a) < (b)) ? (a) : (b)) +#endif + +#ifndef MAX +#define MAX(a, b) (((a) > (b)) ? (a) : (b)) +#endif + +struct broadcast_sink_pa_sync { + /** Source ID */ + uint8_t source_id; + + /** Periodic advertising interval in 1.25 ms units */ + uint16_t interval; + + /** Periodic sync handle */ + uint16_t sync_handle; + + /** Connection handle or @ref BLE_HS_CONN_HANDLE_NONE */ + uint16_t conn_handle; + + /** ISO Interval */ + uint16_t iso_interval; + + /** Sync pending */ + uint8_t syncing : 1; + + /** BIG is encrypted **/ + uint8_t big_encrypted : 1; + + /** Singly-linked list entry. */ + SLIST_ENTRY(broadcast_sink_pa_sync) next; +}; + +struct broadcast_sink_big_sync { + /** Source ID */ + uint8_t source_id; + + /** BIG Handle */ + uint8_t big_handle; + + /** ISO Interval */ + uint16_t iso_interval; + + /** BIS index bitmask */ + uint8_t bis_sync; + + /** Sync pending */ + uint8_t syncing : 1; + uint8_t big_encrypted : 1; + uint8_t pending_datapath_setup : 1; + + /** Singly-linked list entry. */ + SLIST_ENTRY(broadcast_sink_big_sync) next; +}; + +/* PA sync */ +static SLIST_HEAD(, broadcast_sink_pa_sync) broadcast_sink_pa_sync_list; +static struct os_mempool broadcast_sink_pa_sync_pool; +static os_membuf_t broadcast_sink_pa_sync_mem[ + OS_MEMPOOL_SIZE(MYNEWT_VAL(BLE_MAX_PERIODIC_SYNCS), + sizeof(struct broadcast_sink_pa_sync))]; + +/* BIG sync */ +static SLIST_HEAD(, broadcast_sink_big_sync) broadcast_sink_big_sync_list; +static struct os_mempool broadcast_sink_big_sync_pool; +static os_membuf_t broadcast_sink_big_sync_mem[ + OS_MEMPOOL_SIZE(MYNEWT_VAL(BLE_ISO_MAX_BIGS), + sizeof(struct broadcast_sink_big_sync))]; + + +static struct broadcast_sink_pa_sync * +broadcast_sink_pa_sync_lookup_sync_handle(uint16_t sync_handle) +{ + struct broadcast_sink_pa_sync *pa_sync; + + SLIST_FOREACH(pa_sync, &broadcast_sink_pa_sync_list, next) { + if (sync_handle == pa_sync->sync_handle) { + return pa_sync; + } + } + + return NULL; +} + +static struct broadcast_sink_pa_sync * +broadcast_sink_pa_sync_alloc(void) +{ + struct broadcast_sink_pa_sync *pa_sync; + + pa_sync = os_memblock_get(&broadcast_sink_pa_sync_pool); + if (pa_sync == NULL) { + return NULL; + } + + memset(pa_sync, 0, sizeof(*pa_sync)); + SLIST_INSERT_HEAD(&broadcast_sink_pa_sync_list, pa_sync, next); + + return 0; +} + +static void +broadcast_sink_pa_sync_free(struct broadcast_sink_pa_sync *pa_sync) +{ + os_memblock_put(&broadcast_sink_pa_sync_pool, pa_sync); + SLIST_REMOVE(&broadcast_sink_pa_sync_list, pa_sync, broadcast_sink_pa_sync, next); +} + +static struct broadcast_sink_pa_sync * +broadcast_sink_pa_sync_lookup_source_id(uint8_t source_id) +{ + struct broadcast_sink_pa_sync *pa_sync; + + SLIST_FOREACH(pa_sync, &broadcast_sink_pa_sync_list, next) { + if (source_id == pa_sync->source_id) { + return pa_sync; + } + } + + return NULL; +} + +static void +broadcast_sink_pa_sync_state_changed(struct broadcast_sink_pa_sync *pa_sync, + enum ble_svc_audio_bass_pa_sync_state sync_state) +{ + uint8_t source_id = pa_sync->source_id; + + if (sync_state == BLE_SVC_AUDIO_BASS_PA_SYNC_STATE_SYNCED) { + pa_sync->syncing = false; + } else { + broadcast_sink_pa_sync_free(pa_sync); + } + + ble_svc_audio_bass_pa_sync_state_set(source_id, sync_state); +} + +struct ble_audio_bsnk_gap_event_params { + struct ble_gap_event *event; + void *arg; +}; + +static int +ble_audio_bsnk_per_adv_field_parse(const struct ble_hs_adv_field *field, void *user_data) +{ + struct ble_audio_bsnk_gap_event_params *params = user_data; + const uint8_t value_len = field->length - sizeof(field->length); + ble_uuid16_t uuid16 = BLE_UUID16_INIT(0); + uint8_t offset = 0; + + switch (field->type) { + case BLE_HS_ADV_TYPE_SVC_DATA_UUID16: + if (value_len < 2) { + break; + } + + uuid16.value = get_le16(&field->value[offset]); + offset += 2; + + switch (uuid16.value) { + case BLE_BASIC_AUDIO_ANNOUNCEMENT_SVC_UUID: { + struct ble_audio_event_basic_audio_announcement *baa; + struct ble_audio_event event = { + .type = BLE_AUDIO_EVENT_BASIC_AUDIO_ANNOUNCEMENT, + }; + + baa = &event.basic_audio_announcement; + baa->snk = params->arg; + baa->tx_power = params->event->periodic_report.tx_power; + baa->rssi = params->event->periodic_report.rssi; + baa->base = &field->value[offset]; + baa->base_len = value_len - offset; + + (void)ble_audio_event_listener_call(&event); + + break; + } + + default: + break; + } + break; + + default: + break; + } + + /* Continue */ + return BLE_HS_ENOENT; +} + +static int +broadcast_sink_gap_event_handler(struct ble_gap_event *event, void *arg) +{ + struct broadcast_sink_pa_sync *pa_sync = arg; + + switch (event->type) { + case BLE_GAP_EVENT_PERIODIC_SYNC: + if (event->periodic_sync.status == BLE_ERR_SUCCESS) { + pa_sync->sync_handle = event->periodic_sync.sync_handle; + broadcast_sink_pa_sync_state_changed( + pa_sync, BLE_SVC_AUDIO_BASS_PA_SYNC_STATE_SYNCED); + } else { + broadcast_sink_pa_sync_state_changed( + pa_sync, BLE_SVC_AUDIO_BASS_PA_SYNC_STATE_SYNCED_FAILED); + } + break; + + case BLE_GAP_EVENT_PERIODIC_SYNC_LOST: + broadcast_sink_pa_sync_state_changed( + pa_sync, BLE_SVC_AUDIO_BASS_PA_SYNC_STATE_NOT_SYNCED); + break; + + case BLE_GAP_EVENT_PERIODIC_TRANSFER: + if (event->periodic_transfer.status == BLE_ERR_SUCCESS) { + pa_sync->sync_handle = event->periodic_transfer.sync_handle; + broadcast_sink_pa_sync_state_changed( + pa_sync, BLE_SVC_AUDIO_BASS_PA_SYNC_STATE_SYNCED); + } else if (event->periodic_transfer.status == BLE_HS_ETIMEOUT) { + broadcast_sink_pa_sync_state_changed( + pa_sync, BLE_SVC_AUDIO_BASS_PA_SYNC_STATE_NO_PAST); + } else { + broadcast_sink_pa_sync_state_changed( + pa_sync, BLE_SVC_AUDIO_BASS_PA_SYNC_STATE_SYNCED_FAILED); + } + break; + + case BLE_GAP_EVENT_BIGINFO_REPORT: + pa_sync = broadcast_sink_pa_sync_lookup_sync_handle( + event->biginfo_report.sync_handle); + if (pa_sync == NULL) { + return 0; + } + + pa_sync->iso_interval = event->biginfo_report.iso_interval; + pa_sync->big_encrypted = event->biginfo_report.encryption; + break; + + case BLE_GAP_EVENT_PERIODIC_REPORT: + if (event->periodic_report.data_status != BLE_HCI_PERIODIC_DATA_STATUS_COMPLETE) { + return 0; + } + + (void) ble_hs_adv_parse(event->periodic_report.data, + event->periodic_report.data_length, + ble_audio_bsnk_per_adv_field_parse, ¶ms); + + break; + + default: + break; + } + + return 0; +} + +/** Calculates sync timeout for the PA or BIG in 10ms units */ +static uint16_t +broadcast_sink_sync_timeout_calc(uint16_t interval, uint8_t retry_count) +{ + const uint16_t num_attempts = retry_count + 1; + uint16_t sync_timeout; + + /* TODO: Do not use magic numbers */ + sync_timeout = ((uint32_t)interval * num_attempts) * 0.125; + + sync_timeout = MAX(0x000A, MIN(sync_timeout, 0x4000)); + + return sync_timeout; +} + +int +ble_audio_broadcast_sink_pa_sync( + uint8_t source_id, + const struct ble_audio_broadcast_sink_pa_sync_params *params) +{ + struct broadcast_sink_pa_sync *pa_sync; + struct ble_gap_periodic_sync_params gap_params = { + .reports_disabled = 0, +#if MYNEWT_VAL(BLE_VERSION) >= 53 + .filter_duplicates = 1, +#endif + }; + int rc; + + if (params == NULL) { + BLE_HS_LOG_ERROR("NULL params!\n"); + return BLE_HS_EINVAL; + } + + pa_sync = broadcast_sink_pa_sync_lookup_source_id(source_id); + if (pa_sync != NULL) { + return BLE_HS_EALREADY; + } + + pa_sync = broadcast_sink_pa_sync_alloc(); + if (pa_sync == NULL) { + return BLE_HS_ENOMEM; + } + + pa_sync->source_id = source_id; + + gap_params.skip = params->skip; + gap_params.sync_timeout = broadcast_sink_sync_timeout_calc( + params->pa_interval, params->retry_count); + + rc = ble_gap_periodic_adv_sync_create(params->adv_addr, + params->adv_sid, + &gap_params, + broadcast_sink_gap_event_handler, + pa_sync); + if (rc != 0) { + broadcast_sink_pa_sync_free(pa_sync); + return rc; + } + + pa_sync->syncing = true; + + return 0; +} + +int +ble_audio_broadcast_sink_pa_sync_recv( + uint8_t source_id, + const struct ble_audio_broadcast_sink_pa_sync_recv_params *params) +{ + struct broadcast_sink_pa_sync *pa_sync; + struct ble_gap_periodic_sync_params gap_params = { + .reports_disabled = 0, +#if MYNEWT_VAL(BLE_VERSION) >= 53 + .filter_duplicates = 1, +#endif + }; + int rc; + + if (params == NULL) { + BLE_HS_LOG_ERROR("NULL params!\n"); + return BLE_HS_EINVAL; + } + + pa_sync = broadcast_sink_pa_sync_lookup_source_id(source_id); + if (pa_sync != NULL) { + return BLE_HS_EALREADY; + } + + pa_sync = broadcast_sink_pa_sync_alloc(); + if (pa_sync == NULL) { + return BLE_HS_ENOMEM; + } + + pa_sync->source_id = source_id; + pa_sync->conn_handle = params->conn_handle; + + gap_params.skip = params->skip; + gap_params.sync_timeout = broadcast_sink_sync_timeout_calc( + params->pa_interval, params->retry_count); + + rc = ble_gap_periodic_adv_sync_receive(pa_sync->conn_handle, + &gap_params, + broadcast_sink_gap_event_handler, + pa_sync); + if (rc != 0) { + broadcast_sink_pa_sync_free(pa_sync); + return rc; + } + + pa_sync->syncing = true; + + return 0; +} + +static struct broadcast_sink_big_sync * +broadcast_sink_big_sync_alloc(void) +{ + struct broadcast_sink_big_sync *big_sync; + + big_sync = os_memblock_get(&broadcast_sink_big_sync_pool); + if (big_sync == NULL) { + return NULL; + } + + memset(big_sync, 0, sizeof(*big_sync)); + SLIST_INSERT_HEAD(&broadcast_sink_big_sync_list, big_sync, next); + + return 0; +} + +static void +broadcast_sink_big_sync_free(struct broadcast_sink_big_sync *big_sync) +{ + os_memblock_put(&broadcast_sink_big_sync_pool, big_sync); + SLIST_REMOVE(&broadcast_sink_big_sync_list, big_sync, broadcast_sink_big_sync, next); +} + +static struct broadcast_sink_big_sync * +broadcast_sink_big_sync_lookup_source_id(uint8_t source_id) +{ + struct broadcast_sink_big_sync *big_sync; + + SLIST_FOREACH(big_sync, &broadcast_sink_big_sync_list, next) { + if (source_id == big_sync->source_id) { + return big_sync; + } + } + + return NULL; +} + +static int +broadcast_sink_iso_event_handler(struct ble_iso_event *event, void *arg) +{ + struct broadcast_sink_big_sync *big_sync = arg; + uint8_t source_id = big_sync->source_id; + + switch (event->type) { + case BLE_ISO_EVENT_BIG_SYNC_ESTABLISHED: + BLE_AUDIO_DBG_ASSERT(big_sync->big_handle == + event->big_sync_established.desc.big_handle); + + if (event->big_sync_established.status != 0) { + broadcast_sink_big_sync_free(big_sync); + + ble_svc_audio_bass_big_state_set(source_id, + BLE_SVC_AUDIO_BASS_BIG_ENC_NOT_ENCRYPTED, + 0); + } else { + /* Setup ISO datapath in the next PA BASE event */ + big_sync->pending_datapath_setup = true; + big_sync->iso_interval = event->big_sync_established.desc.iso_interval; + +// enum ble_svc_audio_bass_big_enc big_enc; +// +// if (big_sync->big_encrypted) { +// big_enc = BLE_SVC_AUDIO_BASS_BIG_ENC_DECRYPTING; +// } else { +// big_enc = BLE_SVC_AUDIO_BASS_BIG_ENC_NOT_ENCRYPTED; +// } +// ble_svc_audio_bass_big_state_set(source_id, big_enc, big_sync->bis_sync); + } + + break; + + case BLE_ISO_EVENT_BIG_SYNC_TERMINATED: + BLE_AUDIO_DBG_ASSERT(big_sync->big_handle == + event->big_terminated.big_handle); + + broadcast_sink_big_sync_free(big_sync); + + if (event->big_terminated.reason == BLE_ERR_CONN_TERM_MIC) { + ble_svc_audio_bass_big_state_set(source_id, + BLE_SVC_AUDIO_BASS_BIG_ENC_BAD_CODE, + 0); + } else { + ble_svc_audio_bass_big_state_set(source_id, + BLE_SVC_AUDIO_BASS_BIG_ENC_NOT_ENCRYPTED, + 0); + } + + break; + + default: + break; + } + + return 0; +} + +int +ble_audio_broadcast_sink_sync( + uint8_t source_id, + const struct ble_audio_broadcast_sink_sync_params *params) +{ + struct ble_iso_bis_params bis_params[MYNEWT_VAL(BLE_ISO_MAX_BISES)]; + struct ble_iso_big_sync_create_params big_sync_create_params; + struct broadcast_sink_big_sync *big_sync; + struct broadcast_sink_pa_sync *pa_sync; + int rc; + + if (params == NULL) { + BLE_HS_LOG_ERROR("NULL params!\n"); + return BLE_HS_EINVAL; + } + + if (params->params == NULL) { + BLE_HS_LOG_ERROR("NULL params->params!\n"); + return BLE_HS_EINVAL; + } + + if (params->num_bis == 0 || params->num_bis > ARRAY_SIZE(bis_params)) { + BLE_HS_LOG_ERROR("invalid params->num_bis!\n"); + return BLE_HS_EINVAL; + } + + pa_sync = broadcast_sink_pa_sync_lookup_source_id(source_id); + if (pa_sync == NULL) { + return BLE_HS_ENOENT; + } + + if (pa_sync->syncing) { + return BLE_HS_EAGAIN; + } + + if (pa_sync->big_encrypted && params->broadcast_code == NULL) { + return BLE_HS_EENCRYPT; + } + + big_sync = broadcast_sink_big_sync_lookup_source_id(source_id); + if (big_sync != NULL) { + return BLE_HS_EALREADY; + } + + big_sync = broadcast_sink_big_sync_alloc(); + if (big_sync == NULL) { + return BLE_HS_ENOMEM; + } + + if (pa_sync->big_encrypted) { + big_sync_create_params.broadcast_code = params->broadcast_code; + } else { + big_sync_create_params.broadcast_code = NULL; + } + + for (uint8_t i = 0; i < params->num_bis; i++) { + bis_params[i].bis_index = params->params[i].bis_index; + bis_params[i].cb = params->params[i].cb; + bis_params[i].cb_arg = params->params[i].cb_arg; + } + + big_sync_create_params.sync_handle = pa_sync->sync_handle; + big_sync_create_params.mse = 0; + big_sync_create_params.sync_timeout = broadcast_sink_sync_timeout_calc( + pa_sync->iso_interval, 1); + big_sync_create_params.cb = broadcast_sink_iso_event_handler; + big_sync_create_params.cb_arg = big_sync; + big_sync_create_params.bis_cnt = params->num_bis; + big_sync_create_params.bis_params = bis_params; + + rc = ble_iso_big_sync_create(&big_sync_create_params, + &big_sync->big_handle); + if (rc != 0) { + broadcast_sink_big_sync_free(big_sync); + return rc; + } + + big_sync->syncing = true; + big_sync->big_encrypted = pa_sync->big_encrypted; + + return 0; +} + +int +ble_audio_broadcast_sink_init(void) +{ + int rc; + + /* Ensure this function only gets called by sysinit. */ + SYSINIT_ASSERT_ACTIVE(); + + rc = os_mempool_init(&broadcast_sink_pa_sync_pool, + MYNEWT_VAL(BLE_MAX_PERIODIC_SYNCS), + sizeof(struct broadcast_sink_pa_sync), + broadcast_sink_pa_sync_mem, + "broadcast_sink_pa_sync_pool"); + SYSINIT_PANIC_ASSERT(rc == 0); + + rc = os_mempool_init(&broadcast_sink_big_sync_pool, + MYNEWT_VAL(BLE_ISO_MAX_BIGS), + sizeof(struct broadcast_sink_big_sync), + broadcast_sink_big_sync_mem, + "broadcast_sink_big_sync_pool"); + SYSINIT_PANIC_ASSERT(rc == 0); + + return 0; +} +#endif /* BLE_AUDIO_BROADCAST_SINK */ diff --git a/nimble/host/audio/src/ble_audio_priv.h b/nimble/host/audio/src/ble_audio_priv.h index 10f0109ddc..dc97b1ff55 100644 --- a/nimble/host/audio/src/ble_audio_priv.h +++ b/nimble/host/audio/src/ble_audio_priv.h @@ -22,6 +22,14 @@ #include "audio/ble_audio.h" +#if MYNEWT_VAL(BLE_HS_DEBUG) +#define BLE_AUDIO_DBG_ASSERT(x) assert(x) +#define BLE_AUDIO_DBG_ASSERT_EVAL(x) assert(x) +#else +#define BLE_AUDIO_DBG_ASSERT(x) +#define BLE_AUDIO_DBG_ASSERT_EVAL(x) ((void)(x)) +#endif + int ble_audio_event_listener_call(struct ble_audio_event *event); #endif /* H_BLE_AUDIO_PRIV_ */ diff --git a/nimble/host/audio/src/ble_audio_svc_bass_stub.h b/nimble/host/audio/src/ble_audio_svc_bass_stub.h new file mode 100644 index 0000000000..9c4325e6ce --- /dev/null +++ b/nimble/host/audio/src/ble_audio_svc_bass_stub.h @@ -0,0 +1,51 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#ifndef H_BLE_AUDIO_SVC_BASS_ +#define H_BLE_AUDIO_SVC_BASS_ + +#include +#include "syscfg/syscfg.h" + +enum ble_svc_audio_bass_big_enc { + BLE_SVC_AUDIO_BASS_BIG_ENC_NOT_ENCRYPTED, + BLE_SVC_AUDIO_BASS_BIG_ENC_BROADCAST_CODE_REQ, + BLE_SVC_AUDIO_BASS_BIG_ENC_DECRYPTING, + BLE_SVC_AUDIO_BASS_BIG_ENC_BAD_CODE +}; + +enum ble_svc_audio_bass_pa_sync { + BLE_SVC_AUDIO_BASS_PA_SYNC_DO_NOT_SYNC, + BLE_SVC_AUDIO_BASS_PA_SYNC_SYNC_PAST_AVAILABLE, + BLE_SVC_AUDIO_BASS_PA_SYNC_SYNC_PAST_NOT_AVAILABLE +}; + +enum ble_svc_audio_bass_pa_sync_state { + BLE_SVC_AUDIO_BASS_PA_SYNC_STATE_NOT_SYNCED, + BLE_SVC_AUDIO_BASS_PA_SYNC_STATE_SYNC_INFO_REQ, + BLE_SVC_AUDIO_BASS_PA_SYNC_STATE_SYNCED, + BLE_SVC_AUDIO_BASS_PA_SYNC_STATE_SYNCED_FAILED, + BLE_SVC_AUDIO_BASS_PA_SYNC_STATE_NO_PAST +}; + +int ble_svc_audio_bass_pa_sync_state_set(uint8_t source_id, enum ble_svc_audio_bass_pa_sync_state sync_state); + +int ble_svc_audio_bass_big_state_set(uint8_t source_id, enum ble_svc_audio_bass_big_enc, uint32_t bis_sync_state); + +#endif /* H_BLE_AUDIO_SVC_BASS_ */ \ No newline at end of file diff --git a/nimble/host/audio/syscfg.yml b/nimble/host/audio/syscfg.yml index 5f0269e0ab..6da077a652 100644 --- a/nimble/host/audio/syscfg.yml +++ b/nimble/host/audio/syscfg.yml @@ -22,4 +22,34 @@ syscfg.defs: Maximum number of registered audio codecs. value: 0 -syscfg.logs: + BLE_AUDIO_BROADCAST_SINK: + description: > + This option enables BLE Audio Sink support. + value: 0 + restrictions: + - '(BLE_ISO_BROADCAST_SINK > 0) if 1' + # TODO: - '(BLE_AUDIO_SVC_PACS > 0) if 1' + # TODO: - '(BLE_AUDIO_SVC_BASS > 0) if 1' + + BLE_AUDIO_BROADCAST_SINK_SYSINIT_STAGE: + description: > + Primary sysinit stage for BLE Audio Broadcast Sink. + value: 500 + + BLE_AUDIO_BROADCAST_SINK_MAX: + description: > + Number of supported BLE Audio Broadcast Sink instances + value: 'MYNEWT_VAL_BLE_ISO_MAX_BIGS' + # TODO: Set to BLE_SVC_AUDIO_BASS_RECEIVE_STATE_MAX by default + +# BLE_AUDIO_BROADCAST_SINK_LOG_MOD: +# description: 'Numeric module ID to use for BLE Audio Broadcast Sink log messages.' +# value: 28 +# +# BLE_AUDIO_BROADCAST_SINK_LOG_LVL: +# description: 'Minimum level for the BLE Audio Broadcast Sink log log.' +# value: 1 +# +# BLE_AUDIO_BROADCAST_SINK_LOG: +# module: MYNEWT_VAL(BLE_AUDIO_BROADCAST_SINK_LOG_MOD) +# level: MYNEWT_VAL(BLE_AUDIO_BROADCAST_SINK_LOG_LVL) diff --git a/nimble/host/audio/targets/btshell_native/syscfg.yml b/nimble/host/audio/targets/btshell_native/syscfg.yml index e9db007074..60aff3ae12 100644 --- a/nimble/host/audio/targets/btshell_native/syscfg.yml +++ b/nimble/host/audio/targets/btshell_native/syscfg.yml @@ -64,6 +64,9 @@ syscfg.vals: BLE_ISO_MAX_BIGS: 1 BLE_ISO_MAX_BISES: 2 + BLE_AUDIO: 1 + BLE_AUDIO_BROADCAST_SINK: 1 + CONSOLE_UART: 1 CONSOLE_UART_BAUD: 1000000 CONSOLE_STICKY_PROMPT: 1