Skip to content

Commit

Permalink
nimble/audio: Add LE Audio event listener
Browse files Browse the repository at this point in the history
This adds dedicated event listener for LE Audio events.
  • Loading branch information
MariuszSkamra committed Feb 22, 2024
1 parent de40d41 commit 8cb3124
Show file tree
Hide file tree
Showing 7 changed files with 456 additions and 0 deletions.
138 changes: 138 additions & 0 deletions nimble/host/audio/include/host/audio/ble_audio.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,147 @@
#define H_BLE_AUDIO_

#include <stdint.h>
#include <sys/queue.h>

#include "host/ble_audio_common.h"

/** @brief Public Broadcast Announcement features bits */
enum ble_audio_bcst_public_feature {
/** Broadcast Stream Encryption */
BLE_AUDIO_BCST_PUBLIC_FEATURE_ENCRYPTION = 1 << 0,

/** Standard Quality Public Broadcast Audio */
BLE_AUDIO_BCST_PUBLIC_FEATURE_AUDIO_SQ = 1 << 1,

/** High Quality Public Broadcast Audio */
BLE_AUDIO_BCST_PUBLIC_FEATURE_AUDIO_HQ = 1 << 2,
};

/** @brief Public Broadcast Announcement structure */
struct ble_audio_bcst_public {
/** Public Broadcast Announcement features bitfield */
enum ble_audio_bcst_public_feature features;

/** Metadata length */
uint8_t metadata_len;

/** Metadata */
const uint8_t *metadata;
};

struct ble_audio_bcst_name {
/** Broadcast Name length */
uint8_t name_len;

/** Broadcast Name */
const char *name;
};

/**
* @defgroup ble_audio_events Bluetooth Low Energy Audio Events
* @{
*/

/** BLE Audio event: Broadcast Announcement */
#define BLE_AUDIO_EVENT_BCST_ANNOUNCEMENT 0

/** @} */

/** @brief Broadcast Announcement */
struct ble_audio_event_bcst_announcement {
/** Extended advertising report */
const struct ble_gap_ext_disc_desc *ext_disc;

/** Broadcast ID */
uint32_t broadcast_id;

/** Additional service data included in Broadcast Audio Announcement */
const uint8_t *svc_data;

/** Additional service data length */
uint16_t svc_data_len;

/** Optional Public Broadcast Announcement data */
struct ble_audio_bcst_public *pub;

/** Optional Broadcast Name */
struct ble_audio_bcst_name *name;
};

/**
* 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
* application-specified callback.
*/
struct ble_audio_event {
/**
* Indicates the type of BLE Audio event that occurred. This is one of the
* BLE_AUDIO_EVENT codes.
*/
uint8_t type;

/**
* A discriminated union containing additional details concerning the event.
* The 'type' field indicates which member of the union is valid.
*/
union {
/**
* @ref BLE_AUDIO_EVENT_BCST_ANNOUNCEMENT
*
* Represents a received Broadcast Announcement.
*/
struct ble_audio_event_bcst_announcement bcst_announcement;
};
};

/** Callback function type for handling BLE Audio events. */
typedef int ble_audio_event_fn(struct ble_audio_event *event, void *arg);

/**
* Event listener structure
*
* This should be used as an opaque structure and not modified manually.
*/
struct ble_audio_event_listener {
/** The function to call when a BLE Audio event occurs. */
ble_audio_event_fn *fn;

/** An optional argument to pass to the event handler function. */
void *arg;

/** Singly-linked list entry. */
SLIST_ENTRY(ble_audio_event_listener) next;
};

/**
* Registers listener for BLE Audio events
*
* On success listener structure will be initialized automatically and does not
* need to be initialized prior to calling this function. To change callback
* and/or argument unregister listener first and register it again.
*
* @param[in] listener Listener structure
* @param[in] event_mask Optional event mask
* @param[in] fn Callback function
* @param[in] arg Optional callback argument
*
* @return 0 on success
* BLE_HS_EINVAL if no callback is specified
* BLE_HS_EALREADY if listener is already registered
*/
int ble_audio_event_listener_register(struct ble_audio_event_listener *listener,
ble_audio_event_fn *fn, void *arg);

/**
* Unregisters listener for BLE Audio events
*
* @param[in] listener Listener structure
*
* @return 0 on success
* BLE_HS_ENOENT if listener was not registered
*/
int ble_audio_event_listener_unregister(struct ble_audio_event_listener *listener);

/**
* BASE iterator
*
Expand Down
227 changes: 227 additions & 0 deletions nimble/host/audio/src/ble_audio.c
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,233 @@
#include "host/ble_hs.h"
#include "host/audio/ble_audio.h"

#include "ble_audio_priv.h"

static struct ble_gap_event_listener ble_audio_gap_event_listener;
static SLIST_HEAD( , ble_audio_event_listener) ble_audio_event_listener_list =
SLIST_HEAD_INITIALIZER(ble_audio_event_listener_list);

struct ble_audio_adv_parse_bcst_announcement_data {
struct ble_audio_event event;
struct ble_audio_bcst_public pub;
struct ble_audio_bcst_name name;
bool success;
};

static int
ble_audio_adv_parse_bcst_announcement(const struct ble_hs_adv_field *field,
void *user_data)
{
struct ble_audio_adv_parse_bcst_announcement_data *data = user_data;
struct ble_audio_event_bcst_announcement *event;
const uint8_t value_len = field->length - sizeof(field->length);
ble_uuid16_t uuid16 = BLE_UUID16_INIT(0);
uint8_t offset = 0;

event = &data->event.bcst_announcement;

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_BROADCAST_AUDIO_ANNOUNCEMENT_SVC_UUID:
if ((value_len - offset) < 3) {
/* Stop parsing */
return 0;
}

event->broadcast_id = get_le24(&field->value[offset]);
offset += 3;

if (value_len > offset) {
event->svc_data = &field->value[offset];
event->svc_data_len = value_len - offset;
}

data->success = true;
break;

case BLE_BROADCAST_PUBLIC_BROADCAST_ANNOUNCEMENT_SVC_UUID:
if (event->pub != NULL) {
/* Ignore */
break;
}

if ((value_len - offset) < 2) {
break;
}

data->pub.features = field->value[offset++];
data->pub.metadata_len = field->value[offset++];

if ((value_len - offset) < data->pub.metadata_len) {
break;
}

data->pub.metadata = &field->value[offset];

event->pub = &data->pub;
break;

default:
break;
}

break;

case BLE_HS_ADV_TYPE_BROADCAST_NAME:
if (event->name != NULL) {
/* Ignore */
break;
}

if (value_len < 4 || value_len > 32) {
break;
}

data->name.name = (char *)field->value;
data->name.name_len = value_len;

event->name = &data->name;
break;

default:
break;
}

/* Continue parsing */
return BLE_HS_ENOENT;
}

static int
ble_audio_gap_event(struct ble_gap_event *gap_event, void *arg)
{
switch (gap_event->type) {
case BLE_GAP_EVENT_EXT_DISC: {
struct ble_audio_adv_parse_bcst_announcement_data data = { 0 };

(void)ble_hs_adv_parse(gap_event->ext_disc.data,
gap_event->ext_disc.length_data,
ble_audio_adv_parse_bcst_announcement,
&data);

if (data.success) {
data.event.type = BLE_AUDIO_EVENT_BCST_ANNOUNCEMENT;
data.event.bcst_announcement.ext_disc = &gap_event->ext_disc;

(void)ble_audio_event_listener_call(&data.event);
}
break;
}

default:
break;
}

return 0;
}

int
ble_audio_event_listener_register(struct ble_audio_event_listener *listener,
ble_audio_event_fn *fn, void *arg)
{
struct ble_audio_event_listener *evl = NULL;
int rc;

if (listener == NULL) {
BLE_HS_LOG_ERROR("NULL listener!\n");
return BLE_HS_EINVAL;
}

if (fn == NULL) {
BLE_HS_LOG_ERROR("NULL fn!\n");
return BLE_HS_EINVAL;
}

SLIST_FOREACH(evl, &ble_audio_event_listener_list, next) {
if (evl == listener) {
break;
}
}

if (!evl) {
if (SLIST_EMPTY(&ble_audio_event_listener_list)) {
rc = ble_gap_event_listener_register(
&ble_audio_gap_event_listener,
ble_audio_gap_event, NULL);
if (rc != 0) {
return rc;
}
}

memset(listener, 0, sizeof(*listener));
listener->fn = fn;
listener->arg = arg;
SLIST_INSERT_HEAD(&ble_audio_event_listener_list, listener, next);
rc = 0;
} else {
rc = BLE_HS_EALREADY;
}

return rc;
}

int
ble_audio_event_listener_unregister(struct ble_audio_event_listener *listener)
{
struct ble_audio_event_listener *evl = NULL;
int rc;

if (listener == NULL) {
BLE_HS_LOG_ERROR("NULL listener!\n");
return BLE_HS_EINVAL;
}

/*
* We check if element exists on the list only for sanity to let caller
* know whether it registered its listener before.
*/

SLIST_FOREACH(evl, &ble_audio_event_listener_list, next) {
if (evl == listener) {
break;
}
}

if (!evl) {
rc = BLE_HS_ENOENT;
} else {
SLIST_REMOVE(&ble_audio_event_listener_list, listener,
ble_audio_event_listener, next);

if (SLIST_EMPTY(&ble_audio_event_listener_list)) {
rc = ble_gap_event_listener_unregister(
&ble_audio_gap_event_listener);
} else {
rc = 0;
}
}

return rc;
}
int
ble_audio_event_listener_call(struct ble_audio_event *event)
{
struct ble_audio_event_listener *evl = NULL;

SLIST_FOREACH(evl, &ble_audio_event_listener_list, next) {
evl->fn(event, evl->arg);
}

return 0;
}

/* Get the next subgroup data pointer */
static const uint8_t *
ble_audio_base_subgroup_next(uint8_t num_bis, const uint8_t *data,
Expand Down
Loading

0 comments on commit 8cb3124

Please sign in to comment.