跳转至

B91m BLE音频SDK


背景介绍

Bluetooth LE Audio

蓝牙低功耗音频(Bluetooth LE Audio,以下简称LE Audio)自2020年1月首次发布,到2022年7月Bluetooth SIG宣布,已经完成低功耗音频的全套规范的定义。LE Audio是下一代蓝牙音频技术,使通过低功耗蓝牙传输音频成为可能。与经典(BR/EDR)音频相比,它支持新的用例并显著降低功耗。蓝牙 LE 音频旨在降低功耗、减小延迟、缩小传输带宽、并最终提升性能。LE Audio和Classic Audio标准将继续共存,并具有两者都支持的功能。LE Audio将具有更广泛的功能集、更低的功耗和更好的可感知音频质量。LE Audio不仅具有更好的音频质量和更长的播放时间,还引入了新的功能和应用场景。

多流音频

多流音频允许在音频源和不同的接收器之间存在多个音频流。这些流可以将同步偏差缩小到10µs。通过创建协调的设备集,可以将多个独立设备识别成一组彼此相关的设备。例如,多流音频允许将真正的无线耳塞连接到音频源,而无需使用其中一个耳塞来中继数据。

同步通道

LE Audio支持同步通道。有些应用很难用经典蓝牙音频实现,比如真无线耳塞。这些应用通常的做法是将信息从音源传递到一个耳塞。立体声流被分成单声道并从一个耳塞传输到另一个耳塞。在许多情况下,这会导致更高的延迟和更高的功耗。Classic Audio难以实现的功能从一开始就内置在LE Audio中。LE Audio为这个和类似的用例带来了巨大的改进。

经典蓝牙和LE音频流区别

音频分享

LE Audio 支持广播音频。广播音频使音频源通过一个或多个音频流广播到无限数量的音频接收设备。广播音频开辟了一个全新的音频应用场景,Auracast™ 广播音频是针对音频共享领域的新消费品牌。

(1) 个人音频共享

广播音频允许将一个或多个音频流广播到无限数量的设备。它支持诸如个人音频共享之类的应用程序,用户可以在其中与附近其他用户的耳机共享他们的音频流,例如来自手机或平板电脑的音频流。

(2) 基于位置的音频共享

在更大范围内,可以使用基于位置的音频共享。示例应用程序包括一家电影院,游客可以在其中观看同一部电影,但耳机上启用了不同的语言。它可以在有多台电视的健身房中使用,从您想要观看的频道中选择音频。进一步的例子是机场、会议、博物馆等。

低复杂度通信编解码器 (LC3)

Low Complexity Communication Codec(LC3)是LE Audio配置文件中规定必须要支持的音频编解码器。

SBC,低复杂度子带编解码器,在 Classic Audio 中用作成熟的编解码器,效果很好,但已经很老了。新的 LC3 使用大约一半的数据以获得更高的感知音频质量。编解码器的低复杂性导致低延迟和最小的存储要求。可以在不损失太多感知音频质量的情况下将比特率降低得更低。LC3 也将在以后作为蓝牙经典音频的可选编解码器提供。

主要特点:

  • 低复杂度、低功耗
  • 基于块的变换音频编解码器
  • 支持更广泛的可用比特率
  • 支持 10ms 和 7.5ms 两种帧间隔
  • 支持无限数量的音频通道
  • 支持多个采样率:8kHz、16kHz、24kHz、32kHz、44.1kHz、48kHz

注意:

有关 telink_b91m_ble_audio_sdk 中 LC3 库 API 的用法,可以参考附录LC3 编解码器 章节。

关于LE Audio更多的知识,推荐Nick Hunn所著的《Introducing Bluetooth LE Audio》,本书将指导您了解LE Audio规范,说明它们是如何协同工作以及如何使用开发创新的新应用程序。

SDK介绍

Telink Bluetooth LE Audio SDK(泰凌低功耗蓝牙音频SDK)提供基于B9X系列MCU的LE音频应用的参考代码,用户可以在此基础上开发自己的LE音频应用程序。

该SDK基于Telink B91 BLE Multiple Connection SDK开发,关于BLE相关部分可以参考如下文档,本文档将重点介绍LE音频相关的内容,包括Controller/HOST/Profile和音频参考用例等。

适用IC介绍

telink_b91m_ble_audio_sdk 适用如下几种型号的 MCU,B91和B92系列(B9x系列)。

软件组织架构

Telink Bluetooth LE Audio SDK 软件架构包括应用层和 BLE Stack 两部分。

文件结构

在 IDE 中导入 telink_b91m_ble_audio_sdk 后,显示的文件组织结构如下图所示。顶层文件夹有8个:algorithm,application,boot,common,drivers,proj_lib,stack,vendor。

官方推荐IDE: B9X系列使用 Telink IoT Studio。

SDK文件结构

algorithm: 提供一些通用的算法,如aes_ccm,LC3编解码器。大多数算法对应的C文件被封装在库文件中,只留对应的头文件。

application: 提供一些通用的应用处理程序,如print、keyboard等。

boot: 提供MCU的software bootloader,即MCU上电启动或deep sleep唤醒后的汇编处理程序,为后面C程序的运行做准备。

common: 提供一些通用的跨平台的处理函数,如内存处理函数、字符串处理函数等。

drivers: 提供MCU外设驱动程序,如Clock、Flash、I2C、USB、GPIO、UART等。

proj_lib: 存放SDK运行所必需的库文件。BLE协议栈、RF驱动、PM驱动等文件,被封装在库文件中。

stack: 存放BLE协议栈相关的文件。大部分源文件被编译到库文件里面,对于用户不可见。

vendor: 用于存放demo code或者用户自己的code。

telink_b91m_ble_audio_sdk 提供了 5 个 demo,分别是 audio_unicast_server、audio_unicast_client、audio_broadcast_source、audio_broadcast_sink、audio_broadcast_assistant。前两个 demo 表示 unicast (CIS) 相关的音频 demo,另外三个 demo表示broadcast (BIS) 相关的音频 demo。

BLE stack entry

BLE 中断处理入口函数是 blc_sdk_irq_handler()

BLE 逻辑和数据处理入口函数是 blc_sdk_main_loop (),它负责处理BLE协议栈相关的数据和事件。

BLE audio entry

Audio profile 初始化需注册音频事件回调函数和音频角色,代码如下:

blc_audio_registerProfileEventCallback(app_audio_prfEvtCb);
app_audio_acceptor_init();

Audio profile 逻辑和数据处理入口函数是 blc_audio_main_loop(),它负责处理 Audio profile 协议相关的数据和事件。

使用 telink_b91m_ble_audio_sdk 进行 LE Audio 应用开发时,需要注意如下2点:

    blc_hci_registerControllerDataHandler (blc_l2cap_pktHandler_5_3);
  • 为了适应 LE Audio profile 开发的多 GATT service 的特点,telink_b91m_ble_audio_sdk 设计了attribute table 和 service group 相结合的方式。如下是一段示例代码,细节请参考ATT章节。
    /* Register GAP/GATT/BAS/server */
    blc_svc_addCoreGroup();
    blc_svc_addBasGroup();

BLE模块

本手册以Bluetooth Core Specification 5.4版本为参考。

Controller新增特性介绍

扩展广播 (Extended Advertising)

SDK中关于扩展广播的介绍,请参考Telink_BLE_Multi_Connection_SDK_Developer_Handbook。蓝⽛核⼼规范包含完整的详细信息,请参考《Core_V5.4》, Vol 6, Part B 4.6.12 LE Extended Advertising。

周期广播 (Periodic Advertising)

SDK中关于周期广播的介绍,请参考Telink_BLE_Multi_Connection_SDK_Developer_Handbook。蓝⽛核⼼规范包含完整的详细信息,请参考《Core_V5.4》, Vol 6, Part B 4.6.13 LE Periodic Advertising。

扩展扫描 (Extended SCAN)

SDK中关于扩展扫描的介绍,请参考Telink_BLE_Multi_Connection_SDK_Developer_Handbook。蓝⽛核⼼规范包含完整的详细信息,请参考《Core_V5.4》, Vol 6, Part B 4.4.3 Scanning state 和 4.6.29 Synchronized Receiver。

同步 (Synchronization) 建立

扫描设备可以使用以下两种方式中的一种与周期广播序列 (train) 同步:

  • 通过扫描扩展广播辅助通道上的 AUX_ADV_IND PDU,获取其中 SyncInfo 字段(包含周期广播间隔、时序偏移和要使用的信道信息),以此信息建立周期广播序列同步(参考 周期广播 (Periodic Advertising))和扩展扫描 (Extended SCAN)小节);
  • 通过 LE-ACL 连接,从对端设备接收此信息,该设备本身已确定此信息来自 AUX_ADV_IND PDU 的信息, 此方法称为周期广播同步传输规程 (参考 Periodic Advertising Sync Transfer (PAST)小节)。

在同步 (Synchronization) 状态下,链路层监听来自另一个设备的周期广播。这种广播有两种类型:周期广播序列 (trains) 和广播同步流 (参考Broadcaster Isochronous Stream (BIS)小节)。

Periodic Advertising Sync Transfer (PAST)

PAST 是《Core_V5.1》新增加的特性,主要设计用于通知接收设备如何同步到周期广播。 蓝⽛核⼼规范包含完整的详细信息,请参考《Core_V5.4》, Vol 6, Part B 4.6.23 Periodic Advertising Sync Transfer - Sender 和 4.6.24 Periodic Advertising Sync Transfer - Recipient。

外部设备(辅助器)可以通知接收器如何同步到一个周期性的广播序列,本质上是接收器从辅助器 (或者称为遥控器) 询问方向并被告知去哪里的能力,这是通过称为 PAST (周期广播同步传输) 的核心功能实现的,这是使周期性的广播获取变得简单的关键。 PAST 对助听器来说是一个非常有用的功能,因为扫描需要很大的功率。最小化扫描是一个非常有用的功能,有助于延长助听器的电池寿命。

需要说明的是:辅助器获取广播 (或者扩展广播) 的复杂性没有降低,其必须具备扩展扫描的能力,只是接收端可以无需实现扩展扫描功能,这大大降低了接收端的功率损耗。

PAST 规程涉及三方设备:广播设备 A、辅助器 B (作为 Central)、接收设备 C (作为 Peripheral),其中 A 和 B 在实现上是可以共置的 (如手机设备),也可以独立存在,这意味着 PAST 规程存在两种模式:(1) 辅助器自身就是广播源;(2) 辅助器通过扩展扫描获取广播源时序信息, 下图展示的是模式 (2) 的情形。

周期广播同步传输规程时序图示

其中 Broadcast Device 可能包含BIS音频广播,如下图所示。

包含BIS音频的广播事件

辅助器通过扩展扫描跟 Broadcast Device 建立周期广播序列的同步后,通过 PAST 规程 LL_PERIODIC_SYNC_IND 控制指令将周期广播时序信息发送给音频接收设备,接收设备进而可以立即同步到PA,并且通过解析 PA 字段中包含的BigInfo字段(BIG/BIS时序信息) ,获取BIG时序信息,进而可以建立音频流同步关系。

PAST相关API介绍:

void        blc_ll_initPAST_module(void);

初始化PAST module,只有进行了相应模块的初始化,相应的feature才会生效,对应的功能函数才会链接到执行(bin)文件中。

ble_sts_t   blc_ll_periodicAdvSyncTransfer(u16 connHandle, u16 serviceData, u16  syncHandle);

BLE Spec 标准接⼝,指示controller发送LL_PERIODIC_SYNC_IND给到与自己ACL连接的设备,具体设备由API参数指定。详细可参考《Core_5.4》 (Vol 4/Part E/7.8.89 “LE Periodic Advertising Sync Transfer command”),并结合SDK该API的注释来理解。

注意:

这个API应用场景是PAST模式1,即广播设备、辅助设备不是同一个设备。syncHandle是LE Periodic Advertising Sync Established event上报上来的,作为periodic adv ID。

ble_sts_t   blc_ll_periodicAdvSetInfoTransfer(u16   connHandle, u16     serviceData, u8     advHandle);

BLE Spec 标准接⼝,指示controller发送LL_PERIODIC_SYNC_IND给到与自己ACL连接的设备,具体设备由API参数指定。详细可参考《Core_5.4》 (Vol 4/Part E/7.8.90 “LE Periodic Advertising Set Info Transfer command”),并结合SDK该API的注释来理解。

注意:

这个API应用场景是PAST模式2,即广播设备、辅助设备是同一个设备。同一个设备,不会有LE Periodic Advertising Sync Established event,就不会有syncHandle来标记的periodic advertising。但是设备自身是知道advertising set ID的,所以可以通过advertising set ID来指定periodic advertising。

ble_sts_t   blc_ll_setPeriodicAdvSyncTransferParams(u16 connHandle, u8 mode, u16 skip, u16 syncTimeout, u8 cteType);

BLE Spec 标准接⼝,设置接收设备(模式1/2中的Peripheral)接收到LL_PERIODIC_SYNC_IND时,controller该如何处理。可以根据LL_PERIODIC_SYNC_IND包含的信息同步到相应的周期广播,也可以忽略LL_PERIODIC_SYNC_IND。详细可参考《Core_5.4》 (Vol 4/Part E/7.8.91 “LE Set Periodic Advertising Sync Transfer Parameters command”),并结合SDK该API的注释来理解。接收设备可能存在多个ACL连接,所以使用connHandle来指定是哪个ACL连接。

ble_sts_t   blc_ll_setDftPeriodicAdvSyncTransferParams(u8 mode, u8 skip, u16 syncTimeout, u8 cteType);

BLE Spec 标准接⼝,作用同blc_ll_setPeriodicAdvSyncTransferParams。不同之处是:该API对所有的ACL连接都起作用。如果某个ACL连接需要不同于其他的连接,可以使用blc_ll_setPeriodicAdvSyncTransferParams来设定特定的ACL connect(connHandle)。详细可参考《Core_5.4》 (Vol 4/Part E/7.8.92 “LE Set Default Periodic Advertising Sync Transfer Parameters command”),并结合SDK该API的注释来理解。

BIS和CIS同步通信

同步通信定义了一个具有时间依赖的数据传输通道和传输策略,允许多个接收器设备在不同时间从同一源接收数据并能够在同一时间渲染出数据,通道的数据有一个时间限制的有效期,在有效期内数据有效,如果超过有效期还没有传输的数据将会被丢弃。

CIG/BIG 是由一个或者多个CIS/BIS组成的组,组中的数据流(CIS/BIS)具有一定的时间关系。例如立体声音乐可以使用两个流传输,一个用于左立体声通道,另一个用于右立体声通道,这两个流将是同一组的成员,两个流分别在不同的耳机设备端接收,并且能够在同一时间渲染出来,以实现音频的数据同步播放。

核心规范定义了两个使用 LE 同步物理信道的逻辑传输:

  • 连接同步流 (LE CIS 或简称CIS, 也称单播音频流) 使用面向连接的通信并支持数据的双向传输;
  • 广播同步流 (LE BIS 或简称BIS, 也称广播音频流) 使用无连接广播通信并提供单向数据通信。

Connected Isochronous Stream (CIS)

CIS 是《Core_V5.2》新增加的特性,主要用于面向连接的音频产品和系统。本节重点介绍CIS同步通信的关键方面,蓝牙核心规范包含完整的详细信息,请参考《Core_V5.4》, Vol 6, Part B 4.6.27 Connected Isochronous Stream - Central and Connected Isochronous Stream - Peripheral。

单播音频流典型拓扑图:

单播音频流拓扑结构

(1) 组、流、事件、子事件

CIG (Connected Isochronous Group) 由CIS 事件和CIS子事件组成。CIS 流是面向连接的同步组 (CIG) 的成员,每个组包含一个或者多个 CIS 实例,在一个组内,对于每个 CIS,都有一个被称为事件和子事件的传输和接收时隙。

CIG 事件标志着属于 CIG 中 CIS 调度的开始,这发生在CIG中第一个 CIS 的锚点。 CIG 事件以ISO_Interval的参数指定的时间间隔发生,它可以是 5ms 到 4s范围内 1.25ms 倍数的任何值,每个事件包含一个或者多个子事件,在子事件期间,Central 先传输 (T), Peripheral 收到后响应 (R),如下图所示,需要注意的是,每次子事件都会更新信道 (使用自适应跳频和信道选择算法#2, 简称CSA#2)。

CIS事件和子事件

CIG 重要参数:

  • ISO_Interval:CIG调度周期;
  • NSE:每个CIS事件中最大发送机会;
  • BN:在当前CIS Event上有效载荷PDU数量;
  • FT:有效载荷重发最大 CIS 事件数量,每个Payload Number都有Flush Point, FT越大,重传的次数越多,但是TrasnportLatency也会变大。

注意:

目前telink_b91m_ble_audio_sdk支持创建:

  • 最多 1 个CIG central ,每个 CIG central 中支持最大 CIS 数量为 4,每一个 CIS 中最大支持子事件数 8 个。
  • 最多 2 个 CIG peripheral ,每个 CIG peripheral 中支持最大 CIS 数量为 4,每一个 CIS 中最大支持子事件数 8 个。

(2) CIS同步处理

CIG 有一个关联的时序参数,称为CIG_Sync_Delay, CIG 中的每个 CIS 也都有一个称为CIS_Sync_Delay 的时序参数,这两个参数在CIG创建时就已确定,并且整个生命周期内不会修改,通过这两个参数,组中的每个CIS可以推算到一公共的同步点(CIG synchronization point),不同的CIS设备可以将数据流在这个同步点上同步渲染,实现音频同步。

在CIG中同步呈现CIS数据

(3) CIS流创建状态机

建立连接的同步流首先需要创建一个 ACL 连接,此连接有两个目的,首先它允许交换链路层控制 PDU;其次它提供了一个时序参考点,一旦建立了流,就可以根据该参考点来安排 CIS 事件。创建流后,它独立于用于创建它的 ACL 连接并与它一起运行,但是如果 ACL 连接关闭,则关联的 CIS 也必须终止。

只有Central端才可以启动 CIS 的创建过程,它通过发送称为 LL_CIS_REQ PDU 的链路层控制 PDU 来实现,如果一切顺利,外围设备会回复一个 LL_CIS_RSP PDU,Central端收到后再发送一个 LL_CIS_IND PDU,此时流已经建立。LL_CIS_IND PDU PDU 包含有描述CIS流时序的重要信息,其中CIS_Offset 提供ACL 锚点 (在连接事件中发送第一个数据包的时间) 和流的第一个 CIS事件之间的偏移量 (以微秒为单位)。CIG_Sync_Delay 包含以微秒为单位的总体 CIG 同步延迟值,而 CIS_Sync_Delay 包含此流要使用的同步延迟值。

多个 CIS 和关联的 ACL 链接

CIG及其CIS配置的状态机

(4) CIS和BIS公用的接口

(a) blc_iso_sendData

用于CIS/BIS broadcast端发送ISO data数据,调用该API后,数据会被先缓存到SDU FIFO, 然后根据当前ISO流参数拆分为PDU数据格式,在合适的时间发送到对方设备。

ble_sts_t blc_iso_sendData(u16 handle, u8 *pData, u16 len);

(b) blc_iso_enableSduToHostTimestamp

接收到对方设备发来的数据后,会上报给应用层,而上报的数据是否需要带timestamp时间戳,可以通过此API来使能和失能,默认是失能状态。

void blc_iso_enableSduToHostTimestamp(u8 en);

结构体sdu_packet_t

CIS或者BisSync 端将接收的数据根据相关参数信息组包成完整的SDU数据,并上报给应用层,上报的数据结构为sdu_packet_t,结构体元素可参考《Core_V5.4》, Vol 6, Part 4,Part E 5.4.5 HCI ISO Data Packet。

其中参数sduOffset,numHciPkt,numOfCmplt_en为协议栈底层所用,客户可不用关注。

typedef struct{
    u16 pkt_seq_num;       /*Packet_Sequence_Number*/
    u16 iso_sdu_len;       /*ISO_SDU_Length*/

    u32 timestamp;         /*Time_Stamp*/
    u16 sduOffset;
    u8  numHciPkt;

    u8 pkt_st       :2;   /*Packet_Status_Flag*/
    u8 pb           :2;   /*PB_FLAG*/
    u8 ts           :1;   /*TS_Flag*/
    u8 numOfCmplt_en:1;
    u8 rsvd         :2;

    u8 isoHandle;        /*connection_handle*/
    u8 data[1];          /*SDU payload*/

}sdu_packet_t;

(5) CIS common相关的API

(a) blc_ll_initCisConnModule_initCisConnParametersBuffer

ble_sts_t   blc_ll_initCisConnModule_initCisConnParametersBuffer(u8 *pCisConnParaBuf, u32 cis_cen_num, u32 cis_per_num);

(b) blc_ll_initCisTxFifo

ble_sts_t   blc_ll_initCisTxFifo(u8 *pTxbuf, int fifo_size, int fifo_number);

(c) blc_ll_initCisRxFifo

ble_sts_t   blc_ll_initCisRxFifo(u8 *pRxbuf, int fifo_size, int fifo_number);

(d) blc_ll_initCisSduBuffer

void        blc_ll_initCisSduBuffer(u8 *in_fifo, int in_fifo_size, u8 in_fifo_num, u8 *out_fifo, int out_fifo_size, u8 out_fifo_num);

(e) blc_ll_cis_disconnect

ble_sts_t   blc_ll_cis_disconnect(u16 cisHandle, u8 reason);

(f) blc_ll_getCisSduInBufferFreeNum

int blc_ll_getCisSduInBufferFreeNum(u16 cisHandle);

(g) blc_ll_popCisRxSduData

弹出接收到的SDU数据,也可以用来检查SDU fifo中是否有未被处理的SDU数据。

sdu_packet_t* blc_ll_popCisRxSduData(u16 cis_connHandle);

cis_connHandle:用于识别CIS数据流的handle。

返回类型参考sdu_packet_t定义章节。

(h) blc_ll_setCisSupplementPDUStrategy

当上层没有数据时,此函数用来设置controller发送Empty PDU还是NULL PDU,当然这两种PDU类型都符合core spec协议。

两种数据类型的差异:

  • NULL PDU 数据结构中没有SN字域,没有payloadNum,当你认为上层数据只是延时,在有效期内数据还会到来,发NULL PDU会比较友好;
  • Empty PDU 数据结构中有SN字域,有payloadNum,当你认为上层的数据在有效期内不会再来,发Empty PDU会比较友好。
void blc_ll_setCisSupplementPDUStrategy(cis_pdu_strategy_t stgy);

输入参数stgy 如下

typedef enum{
    CIS_PDU_STRATEGY0=0, /*上层没有数据时,controller自动补Empty PDU发送*/
    CIS_PDU_STRATEGY1,   /*上层没有数据时,controller自动补NULL PDU发送*/
    CIS_PDU_STRATEGY2,   /*当FT>1 时,如果上层没有数据,第一个ISO interval内,controller自动补NULL包发送,之后补Empty PDU发送,目的是contoller会等1个iso_interval的时间*/
}cis_pdu_strategy_t;

(6) CIS perpheral相关的API

(a) blc_ll_initCisPeriphrModule_initCisPeriphrParametersBuffer

ble_sts_t   blc_ll_initCisPeriphrModule_initCisPeriphrParametersBuffer(u8 *pCisPerParamBuf, int cis_per_num);

(b) blc_ll_acceptCisRequest

ble_sts_t   blc_ll_acceptCisRequest(u16 cisHandle);

(c) blc_ll_rejectCisReq

ble_sts_t   blc_ll_rejectCisReq(u16 cisHandle, u8 reason);

(7) cis_central.h cis central相关的API

(a) blc_ll_initCisCentralModule_initCigParametersBuffer

ble_sts_t   blc_ll_initCisCentralModule_initCigParametersBuffer(u8 *pCigParamBuf, int cig_num);

(b) blc_ll_setCigTimingOffsetOfAclCentral

ble_sts_t   blc_ll_setCigTimingOffsetOfAclCentral(u8 acl_cen_index, u16 offset_custom_us);

Broadcaster Isochronous Stream (BIS)

BIS是《Core_V5.2》新增加的特性,主要设计用于面向无连接的单向通信音频产品和系统。本节重点介绍BIS同步通信的关键方面,蓝牙核心规范包含完整的详细信息,请参考《Core_V5.4》, Vol 6, Part B 4.6.28 Isochronous Broadcaster。

广播音频流典型拓扑图:

广播音频流拓扑结构

(1) 组、流、事件、子事件

BIG (Broadcaster Isochronous Group) 由 BIS 事件和BIS子事件组成,此外还定义了一个特殊的控制子事件,用于传输与整个 BIG 相关的控制 PDU,BIS 流是面向无连接的单向通信,每个组包含一个或多个 BIS 实例,在一个组内,对于每个 BIS,都有一个被称为事件和子事件的传输时隙。

与 CIS 不同,BIS 不包含数据ACK机制,这使得 BIS 传输本质上是不可靠的,但BIS 不需要为外设响应保留时间槽 (Slots),因此在给定的广播时间内,可以安排两倍数量的子事件用于传输,通过增加多次重传机会来提高数据的可靠性,此外由于重传是在不同的子事件中发送的,不同子事件所用的通道不同,所选频道必须距离上次传输至少 6 MHz,这有助于减少由于特定频道上的干扰而导致的潜在数据包丢失。

BIG时序

BIG 重要参数:

  • Num_BIS:BIG中包含BIS的个数;
  • ISO_Interval:BIG 事件间以ISO_Interval 为间隔,即BIG调度时间间隔,它可以是 5ms 到 4s范围内 1.25ms 倍数的任何值;
  • SDU_Interval:上层数据SDU下发的时间间隔或者是数据流采样时间间隔;
  • MAX_SDU:上层数据SDU最大数据长度,长度范围1到4095;
  • MAX_PDU:链路层最大PDU数据长度,长度范围1到251;
  • NSE:每个BIS事件中最大发送机会次数,即BIS事件中包含的最大sub event 个数;
  • IRC (Immediate Repetition Count):发送当前BIS事件上数据的组数;
  • PTO (Pre-Transmission Offset):发送未来事件数据偏移量。

两个BIS组成的BIG时序

(2) BIG的时序引导

BIG时序通过多重数据引导,先是在37,38,39 primary advertising channels上发送Adv_Ext_IND, Adv_Ext_IND通过字段AuxPtr引导出Aux_Adv_IND, Aux_Adv_IND通过字段SyncInfor字段引导出周期性广播时序,周期性广播数据Aux_Sync_IND通过BIGInfor字段引导出BIG时序,如下图所示。

BIG时序引导图示

注意:

目前 telink_b91m_ble_audio_sdk 支持创建:

  • 最多 2 个 BIG broadcast,每个 BIG broadcast 中支持最大 BIS 数量为 4,每一个 BIS 中最大支持子事件数 12 个。
  • 最多 2 个 BIG sync,每个 BIG sync 中支持最大BIS数量为 4,每一个 BIS 中最大支持子事件数 12 个。

(3) bis.h BIS broadcast和bis synchronization receiver公用函数API

blc_ll_InitBisParametersBuffer

BIS 功能code架构,采用的是模块化的设计思路,主要目的是为了节省SRAM,如下面的BIS控制参数的buffer初始化API,因不确定客户的产品会支持几个BIS,所以该API由用户层根据自己的产品所支持的BIS个数来分配控制buffer的大小,一般在初始化时调用。

ble_sts_t   blc_ll_InitBisParametersBuffer(u8 *pBisPara, u8 bis_bcst_num, u8 bis_sync_num);

a) pBisPara:指向BIS控制块首地址

b) bis_bcst_num:定义产品所支持最大BIS broadcast 个数

c) bis_sync_num:定义产品所支持的最大BIS Sync 个数

(4) bis_bcst.h BIS broadcast相关的API

(a) blc_ll_initBigBcstModule_initBigBcstParametersBuffer

由上层传输BIG broadcast控制块所需的buffer,一般在初始化时调用。

ble_sts_t   blc_ll_initBigBcstModule_initBigBcstParametersBuffer(u8 *pBigBcstPara, u8 bigBcstNum);

a) pBigBcstPara:指向BIG控制块首地址

b) bigBcstNum:定义产品所支持的最大BIG个数

(b) blc_ll_initBisTxFifo 和 blc_ial_initBisBcstSduInBuffer

由上层用户传入bis broadcast 端所需的PDU Tx FIFO和SDU buffer,上层如果有数据需要发送,会先将要发送的ISO data缓存到SDU buffer中,然后IAL层根据BIG参数,将SDU分发成unframed/framed PDU存放在Tx FIFO中,并在对应的bis event事件中将PDU发送出去。

bis broadcast SDU&PDU buffer关系图

ble_sts_t   blc_ll_initBisTxFifo(u8 *pTxbuf, int fifo_size, int fifo_number);

a) pTxbuf:指向PDU buffer首地址;

b) fifo_size:每个PDU buffer的长度;

c) fifo_number:PDU buffer个数,最小值为 ((NSE/BN - IRC)*PTO + 1)*BN,另外值必须是2的指数幂,比如4,8,16,32......

ble_sts_t blc_ll_initBisBcstSduInBuffer(u8 *in_fifo,u16 in_fifo_size, u8 in_fifo_num);

a) in_fifo:指向SDU buffer首地址;

b) in_fifo_size:每个SDU buffer的长度;

c) in_fifo_num:SDU buffer个数,必须是2的指数幂,比如4,8,16,32......

(c) blc_ll_getBisSduInBufferFreeNum

获取空闲的SDU buffer个数,用户层可用于检查底层是否还有足够的buffer存放上层发来的数据。

int blc_ll_getBisSduInBufferFreeNum(u16 bisHandle);

bisHandle:对应BIS流的handle。

(d) blc_hci_le_createBigParamsTest

用于创建BIG, 详细细节请参考《Core_V5.4》, Vol 4, Part E 7.8.104 LE Create BIG command。

ble_sts_t   blc_hci_le_createBigParamsTest(hci_le_createBigParamsTest_t* pCmdParam)

输入参数hci_le_createBigParamsTest_t 详细定义请参考《Core_V5.4》, Vol 4, Part E 7.8.104 LE Create BIG command。

typedef struct
{
    u8      big_handle;         /* Used to identify the BIG */
    u8      adv_handle;         /* Used to identify the periodic advertising train */
    u8      num_bis;            /* Total number of BISes in the BIG */
    u8      sdu_intvl[3];       /* The interval, in microseconds, of periodic SDUs */
    u16     iso_intvl;          /* The time between consecutive BIG anchor points, Time = N * 1.25 ms */
    u8      nse;                /* The total number of subevents in each interval of each BIS in the BIG */
    u16     max_sdu;            /* Maximum size of an SDU, in octets */
    u16     max_pdu;            /* Maximum size, in octets, of payload */
    u8      phy;                /* The transmitter PHY of packets, BIT(0): LE 1M; BIT(1): LE 2M; BIT(3): LE Coded PHY */
    u8      packing;
    u8      framing;
    u8      bn;                 /* The number of new payloads in each interval for each BIS */
    u8      irc;                /* The number of times the scheduled payload(s) are transmitted in a given event*/
    u8      pto;                /* Offset used for pre-transmissions */
    u8      enc;                /* Encryption flag */
    u8      broadcast_code[16]; /* The code used to derive the session key that is used to encrypt and decrypt BIS payloads */
} hci_le_createBigParamsTest_t;

返回值ble_sts_t可能出现的值和原因如下表所示:

ble_sts_t Value ERR Reason
BLE_SUCCESS 0 操作成功

(e) blc_hci_le_createBigParams

用于创建BIG,当前api暂时还不可用,后续完善。

(f) blc_hci_le_terminateBig

用于终止当前BIG中所有bis的发送,在终止之前会先发送BIG_TERMINATE_IND PDU来通知接收端。

ble_sts_t   blc_hci_le_terminateBig(hci_le_terminateBigParams_t* pCmdParam)

参数hci_le_terminateBigParams_t 详细定义请参考《Core_V5.4》, Vol 4, Part E 7.8.105 LE Terminate BIG command。

typedef struct
{
    u8      big_handle; 
    u8      reason;     //terminate reason
} hci_le_terminateBigParams_t;

(5) bis_sync.h BIS sync相关的API

(a) blc_ll_initBigSyncModule_initBigSyncParametersBuffer

由上层传输BIG Sync 端控制块所需的buffer,并初始化控制参数,一般在初始化时调用。

ble_sts_t   blc_ll_initBigSyncModule_initBigSyncParametersBuffer(u8 *pBigSyncPara, u8 bigSyncNum);

a) pBigSyncPara:指向BigSync控制块首地址;

b) bigSyncNum:定义产品支持的最大BIG个数。

(b) blc_ll_initBisRxFifo 和 blc_ll_initBisSyncSduOutBuffer

由上层传入BIS sync 端所需要的Rx PDU FIFO和 SDU buffer,Link Layer 收到的RF PDU会先缓存到Rx FIFO中,然后IAL层根据BIG参数,将PDU 数据拼包为完整的SDU数据,放到SDU buffer中供上层用户使用。

bis Sync SDU&PDU buffer关系图

ble_sts_t blc_ll_initBisRxFifo(u8 *pRxbuf, int full_size, int fifo_number, u8 bis_sync_num);

a) pRxbuf:指向RX FIFO首地址;

b) full_size:每个Rx FIFO 的长度;

c) fifo_number:Rx FIFO 个数;

d) bis_sync_num:所支持的 BIS Sync 最大个数。

ble_sts_t blc_ll_initBisSyncSduOutBuffer(u8 *out_fifo, u16 out_fifo_size, u8 out_fifo_num);

注意:

这里的out是相对Controller来看,Controller将接收的RF PDU组包到SDU,然后上报到应用层

a) out_fifo:指向SDU Out FIFO首地址;

b) out_fifo_size:每个SDU FIFO 的长度;

c) out_fifo_num:SDU Out FIFO 个数。

(c) blc_ll_popBisSyncRxSduData

用于弹出SDU FIFO 中的数据,返回类型参考sdu_packet_t定义章节。

sdu_packet_t* blc_bisSync_popRxSdu(u16 bis_connHandle);

(d) blc_hci_le_bigCreateSync

根据传入的参数创建BIG同步任务

ble_sts_t   blc_hci_le_bigCreateSync(hci_le_bigCreateSyncParams_t* pCmdParam);

输入参数hci_le_bigCreateSyncParams_t详细细节请参考《Core_V5.4》, Vol 4, Part E 7.8.106 LE BIG Create Sync command。

typedef struct
{
    u8      big_handle;         /* Used to identify the BIG */
    u16     sync_handle;        /* Identifier of the periodic advertising train */
    u8      enc;                /* Encryption flag */
    u8      broadcast_code[16]; /* The code used to derive the session key that is used to encrypt and decrypt BIS payloads */
    u8      mse;                /* The Controller can schedule reception of any number of subevents up to NSE */
    u16     big_sync_timeout;   /* Synchronization timeout for the BIG, Time = N*10 ms, Time Range: 100 ms to 163.84 s */
    u8      num_bis;            /* Total number of BISes to synchronize */
    u8      bis[1];             /* List of indices of BISes */
} hci_le_bigCreateSyncParams_t;

(e) blc_ll_bigTerminateSync

用于断开BIG中所有BIS的同步

ble_sts_t   blc_ll_bigTerminateSync(u8 bigHandle);

bigHandle:用于识别需要断开BIG

(6) BIG 发送和同步接收流程图

BIG Broadcast & BIG Sync 工作流程图

参数Transport Latency的计算

Transport Latency 为ISO data在空中的延时,即从CIG reference point(空中发包的开始点)到CIG synchronization point(空中最后一包PDU到达的最晚时间点)之间的时间。

对于 unframed SDU,CIG 传输延迟为:

Transport_Latency = CIG_Sync_Delay + FT × ISO_Interval ‑ SDU_Interval

参数CIG_Sync_Delay、FT 和 SDU_Interval 的值分别用 Central 到 Peripheral 和 Peripheral 到 Central 方向值代替。

对于 unframed SDU,BIG 传输延迟为:

Transport_Latency = BIG_Sync_Delay + (PTO × (NSE÷BN‑IRC) + 1) × ISO_Interval ‑ SDU_Interval

使用unframed PDU的SDU同步参考

对于 framed SDU,CIG 传输延迟为:

Transport_Latency = CIG_Sync_Delay + FT × ISO_Interval + SDU_Interval

对于 Central 到 Peripheral 和 Peripheral 到 Central 方向,需要使用 CIG_Sync_Delay、FT 和 SDU_Interval 的各自值进行单独计算。

对于 framed SDU,BIG 传输延迟为:

Transport_Latency = BIG_Sync_Delay + (PTO × (NSE÷BN–IRC)) × ISO_Interval+ ISO_Interval + SDU_Interval

使用framed PDU的SDU同步参考

Host新增特性介绍

L2CAP

逻辑链路控制与适配协议通常简称为 L2CAP(Logical Link Control and Adaptation Protocol),它向上连接应⽤层,向下连接控制器层,发挥主机与控制器之间的适配器的作⽤,使上层应⽤操作⽆需关⼼控制器的数据处理细节。

BLE 的 L2CAP 层是经典蓝⽛ L2CAP 层的简化版本,它在基础模式下,不执⾏分段和重组,不涉及流程控制和重传机制,仅使⽤固定信道进⾏通信。 L2CAP 的简化结构简单说就是将应⽤层数据分包发给 BLE controller,将 BLE controller 收到的数据打包成不同 CID 数据上报给 host 层。

BLE L2CAP结构以及ATT组包模型

L2CAP 根据 Bluetooth Core Specification 设计,主要功能是完成 Controller 和 Host 的数据对接,绝⼤部分都在协议栈底层完成,需要 user 参与的地⽅很少。 user 根据以下⼏个 API 进⾏设置即可。

注册L2CAP数据处理函数:

telink_b91m_ble_audio_sdk 架构中, Controller上报的ACL数据首先需要在L2CAP层处理,根据CID值在分发到ATT、SMP、L2CAP Signaling、EATT、COC等层处理。注册L2CAP数据处理函数,使用下面API。

void blc_hci_registerControllerDataHandler(hci_data_handler_t handle);

SDK中已经基于《Core_5.4》, Vol 3章节实现了L2CAP的全部功能,SDK中L2CAP层处理ACL数据的函数。

int blc_l2cap_pktHandler_5_3(u16 connHandle, u8 *raw_pkt);

该函数已经在协议栈中实现,它会解析接收到的数据,并向上传给对应的层处理。

user在开发中,必须要初始化L2CAP函数。

blc_hci_registerControllerDataHandler(blc_l2cap_pktHandler_5_3);

注意:

telink_b91m_ble_audio_sdk 进行 LE Audio应用开发时,L2CAP 数据处理函数注册回调必须使用blc_l2cap_pktHandler_5_3。 API: blc_l2cap_pktHandler 在 audio sdk 中已经不再使用。

ATT

(1) GATT基本单位Attribute

GATT定义了两种角色:Server和Client。Server通常包含多组service,client通过ATT层的指令操作service。一组service是由一个service UUID与多个characteristic UUID组成,每个UUID会有多条Attribute构成,每条Attribute都具有⼀定的信息量,用来描述UUID的信息。

  • 在Telink BLE Audio SDK中,将ACL的两种角色central和peripheral,与GATT层的server和client解耦。peripheral角色既可以是GATT server也可以是GATT client,central角色同理。
  • 后续介绍到的所有API,ACL连接句柄,通常不区分ACL角色。除非profile中有明确规定该profile只能在某个ACL角色下使用。

GATT server的结构

⼀条 Attribute 包含Attitude handle、Attribute Type、Attribute value、Attribute table部分。

(a) Attribute Type: UUID

UUID 用来区分每⼀个 attribute 的类型,其全⻓为16个bytes。 BLE 标准协议中UUID⻓度定义为2个bytes,这是因为所有设备都遵循同⼀套转换⽅法,将2个bytes的UUID转换成16 bytes。

Server使用蓝⽛标准协议中的2 bytes的UUID时,client都会把它转换为16 bytes的UUID匹配。

常用的UUID实例在stack/ble/profile/services/svc_uuid.c。

Telink 私有的⼀些 profile(OTA、 MIC、Speaker等),标准蓝牙里面不支持,在 stack/ble/service/uuid.h 中定义这些私有的UUID⻓度为16 bytes。

(b) Attribute Handle

Server拥有多个Attribute,这些Attribute组成⼀个Attribute Table。在 Attribute Table中,每⼀个Attribute都有⼀个唯一Attribute Handle值,用来区分Attribute。server和client建⽴连接后,client通过Service Discovery过程解析读取到server的 Attribute Table,并根据Attribute Handle的值来对应每⼀个不同的Attribute,这样它们后⾯的数据通信只要带上Attribute Handle,对⽅就知道是哪个Attribute的数据了。

(c) Attribute Value

每个 Attribute 都有对应的 Attribute Value,用来作为 request、 response、 notification 和 indication 的数据。在该BLE SDK中, Attribute Value 用指针和指针所指区域的⻓度来描述。

(d) Attribute table

一些常用的attribute table,该BLE SDK已经帮忙实现了,实例文件在stack/ble/profile/services中。开发者可用直接使用,或者修改后使用。

(2) Attribute Table and Service Group

为了适应LE Audio profile开发的多GATT service的特点,LE Audio SDK设计了attribute table和service group结合的方式。

Attribute table由多个基本的Attribute组成,attribute table通常只包含一个service UUID和多条characteristic UUID。这不是强制的要求,只是建议

Service group包含一个完整的attribute table,起始句柄,结束句柄,读attribute回调,写attribute回调,指向新service group指针。

Attribute 基本定义为:

typedef struct
{
    u8      perm;
    u8      uuidLen;        //UUID size only set 2 on 16
    u8*     uuid;
    u16*    attrValueLen;
    u16     maxAttrLen;
    u8*     attrValue;
    u8      settings;
} atts_attribute_t;

结合LE Audio SDK给的Generic Access service的attribute table来说明每个参数的含义。代码见stack/ble/profile/services/svc_gatt/svc_core.c。

static const u8 gapDevNameCharVal[] = SERVICE_CHAR_READ(GAP_DEVICE_NAME_DP_HDL, GATT_UUID_DEVICE_NAME);

static const u8 defaultDevName[] = DEFAULT_DEV_NAME;
static const u16 defaultDevNameLen = MAX_DEV_NAME_LEN;

static const u8 gapAppearanceCharVal[] = SERVICE_CHAR_READ(GAP_APPEARANCE_DP_HDL, GATT_UUID_APPEARANCE);
static const u16 defaultAppearance = DEFAULT_DEV_APPEARE;
static const u16 defaultAppearanceLen = sizeof(defaultAppearance);

static const u8 gapPeriConnParamCharVal[] = SERVICE_CHAR_READ(GAP_CONN_PERI_PARAM_DP_HDL, GATT_UUID_PERI_CONN_PARAM);
static const u16 defaultPeriConnParameters[] = {20, 40, 0, 100};        //gap_periConnectParams_t
static const u16 defaultPeriConnParametersLen = sizeof(defaultPeriConnParameters);

static const atts_attribute_t gapList[] =
{

    ATTS_PRIMARY_SERVICE(serviceGenericAccessUuid),

    //device name
    ATTS_CHARACTER_DEFINE(gapDevNameCharVal),
    ATTS_CHAR_UUID_READ_POINT_NOCB(gattDeviceNameUuid, MAX_DEV_NAME_LEN, defaultDevName),

    //Appearance
    ATTS_CHARACTER_DEFINE(gapAppearanceCharVal),
    ATTS_CHAR_UUID_READ_ENTITY_NOCB(gattAppearanceUuid, defaultAppearance),

    //period connect parameter
    ATTS_CHARACTER_DEFINE(gapPeriConnParamCharVal),
    ATTS_CHAR_UUID_READ_ENTITY_NOCB(gattPeriConnParamUuid, defaultPeriConnParameters),

};

请注意,attribute table的定义前面加了static const:

static const atts_attribute_t gapList[] = {...};

const关键字会让编译器将这个数组的数据最终都存储到flash,以节省ram空间。这个Attribute Table 定义在 Flash 上的所有内容是只读的,不能改写。

(a) perm

perm是permission的简写。

perm⽤于指定当前Attribute被Client访问的权限。

权限有以下10种,每个Attribute的权限都必须为下面的值或它们的组合。

#define ATT_PERMISSIONS_READ 0x01
#define ATT_PERMISSIONS_WRITE 0x02
#define ATT_PERMISSIONS_AUTHEN_READ 0x61
#define ATT_PERMISSIONS_AUTHEN_WRITE 0x62
#define ATT_PERMISSIONS_SECURE_CONN_READ 0xE1
#define ATT_PERMISSIONS_SECURE_CONN_WRITE 0xE2
#define ATT_PERMISSIONS_AUTHOR_READ 0x11
#define ATT_PERMISSIONS_AUTHOR_WRITE 0x12
#define ATT_PERMISSIONS_ENCRYPT_READ 0x21
#define ATT_PERMISSIONS_ENCRYPT_WRITE 0x22

⽬前Telink BLE Audio SDK暂不支持授权读和授权写。

(b)uuid and uuidLen

按照之前所述, UUID分两种:BLE标准的2 bytes UUID和Telink私有的16 bytes UUID。通过uuid和uuidLen可以同时描述这两种UUID。

uuid是⼀个u8型指针,uuidLen表示从指针开始的地⽅连续uuidLen个byte的内容为当前UUID。Attribute Table是存在flash上的,所有的 UUID也是存在flash上的,所以uuid是指向flash的⼀个指针。

a) BLE标准的2 bytes UUID

如device name uuid定义,相关代码如下:

#define GATT_UUID_DEVICE_NAME            0x2a00
const u8 gattDeviceNameUuid[ATT_16_UUID_LEN]    =   {U16_TO_BYTES(GATT_UUID_DEVICE_NAME)};

static const u8 gapDevNameCharVal[] = {CHAR_PROP_READ, U16_TO_BYTES(GAP_DEVICE_NAME_DP_HDL), U16_TO_BYTES(GATT_UUID_DEVICE_NAME)};

b) Telink私有16 bytes UUID

如 OTA 的 Attribute,相关代码:

#define TELINK_MIC_DATA
{0x12,0x2B,0x0d,0x0c,0x0b,0x0a,0x09,0x08,0x07,0x06,0x05,0x04,0x03,0x02,0x01,0x0}
const u8 my_OtaUUID[16] = TELINK_SPP_DATA_OTA;
static u8 my_OtaData = 0x00;

(c) attrValueLen and attrValue

每⼀个Attribute都会有对应的Attribute Value。 pAttrValue是⼀个u8型指针,指向Attribute Value所在RAM/Flash的地址,attrValueLen⽤来反映该数据在RAM/Flash上的⻓度。当client读取server某个Attribute的Attribute Value时,BLE SDK从Attribute的pAttrValue指针指向的区域(RAM/Flash)开始,取attrValueLen个数据回给client。

UUID是只读的,所以uuid通常是指向flash的指针;⽽Attribute Value有可能会涉及到写操作,如果有写操作必须放在RAM上,所以pAttrValue可能指向 RAM,也可能指向 Flash。

当client写入server某个attribute的attribute Value时,该BLE SDK会判断该attribute有无写权限,并且判断写入的长度是否符合配置。如果符合所有要求的情况下,该BLE SDK会将写入的数据和长度存储到attrValueLen和AttrValue中。

  • attrValueLen是一个指向u16型指针,这意味在attribute value length是可变的长度。
  • attrValueLen如果是空指针,ATT层在读取属性值时,会返回长度0。

(d) maxAttrLen

每一个attribute都会有对应的attribute Value,Value对应会有一个最大允许写入长度。当配置attribute value可以写入时,有两种情况:当写入长度为可变长度时,该BLE SDK允许小于等于maxAttrLen的配置写入到attrValue。为不可变长度时,该BLE SDK只允许等于maxAttrLEn的配置写入到attrValue。

(E) setting

每一个attribute的需求可能都不一样,setting是为了满足不同的需求,可以定义BLE SDK对attribute的操作功能。目前可用的setting如下表:

Bit Name Description
0 ATTS_SET_WRITE_CBACK 如果service group有写回调,先运行写回调函数。
1 ATTS_SET_READ_CBACK 如果service group有读回调,先运行读回调函数。
2 ATTS_SET_VARIABLE_LEN Attribute value是否是可变长度,如果同时配置为允许写入。可变长度,允许小于等于maxAttrLen的写操作;不可变长度,允许等于maxAttrLen的写操作。
3 ATTS_SET_ALLOW_WRITE Attribute value是否允许写操作。
4-7 RFU 留做未来使用。

(3) Service Group

为了实现server端可添加多个attribute table,LE Audio SDK定义了attribute group来实现这个功能。通过attribute group的加入,更高层可以在初始化或者运行过程中,将完成attribute table的添加/删除操作。

service group定义为:

typedef struct atts_group
{
    struct atts_group           *pNext;
    atts_attribute_t            *pAttr;

    atts_r_cb_t                 readCback;
    atts_w_cb_t                 writeCback;
    u16                         startHandle;
    u16                         endHandle;
} atts_group_t;

结合LE Audio SDK给出的Generic Access Group来说明以上各项的含义。GAP(Generic Access Profile)的service group代码见:stack/ble/profile/services/svc_gatt/svc_core.c。

/* GAP group structure */
_attribute_ble_data_retention_ 
static atts_group_t svcGapGroup =
{
    NULL,
    (atts_attribute_t *) gapList,
    NULL,
    NULL,
    GAP_START_HDL,
    GAP_END_HDL
};

请注意,attribute group的定义前面加了_attribute_ble_data_retention_。这个关键字会让编译器将变量svcGapGroup最终存到retention ram里面,芯片进入sleep后,数据不会丢失。

如果想要节省retention ram的资源,可用将这部分内存存储到flash中,只需要提前初始化pNext、readCback、writeCback参数即可实现同样的效果。将起始的Group指针存放到gAttributeGroup.head,尾Group指针存放到gAttributeGroup.tail中,queue的数量存放到gAttributeGroup.curNum即可。(不推荐采用)

LE Audio SDK将所有的attribute group存在retention ram中是为了适配所有需求,灵活配置。

(a) pNext

pNext是指向下一个attribute group的指针,正常初始化,配置为NULL即可。在调用blc_atts_addAttGroup或者blc_atts_removeAttGroup时,BLE SDK会修改该指针。

(b) pAttr

pAttr是指向attribute table的指针。

(c) readCback

Attribute table中所有attribute的读回调函数,配置了允许读回调的attribute才会触发。

回调函数readCback是读函数。函数原型:

typedef int (*atts_r_cb_t)(u16 connHandle, u8 opcode, u16 attrHandle, u8** outValue, u16* outValueLen);

user如果需要定义回调读函数,须遵循上面格式。回调函数 readCback 是 optional 的,对某⼀个具体的 Attribute group来说,user 可以设置回调读函数,也可以不设置回调(不设置回调的时候用空指针NULL表示)。

回调函数readCback触发条件为:当server端收到的Attribute PDU的 Attribute Opcode为以下五个时,server会检查回调函数readCback是否被设置:

a) opcode = 0x0A, Read Request.

b) opcode = 0x0C, Read Blob Request.

c) opcode = 0x08, Read By Type Request. (通常不会由这个opcode触发,协议上是允许的)

d) opcode = 0x0E, Read Multiple Request.

e) opcode = 0x20, Read Multiple Variable Request.

server收到以上读命令后:

a) 如果 user 设置了回调读函数,执⾏该函数,根据该函数的返回值决定是否回复:

  • 若返回值为0x01-0xFF,server回复error response给client,attribute handle in error设置为request里面的handle,error code设置为返回值。

  • 若返回值为ATT_SUCCESS,且outValueLen为0时,server从attrValue指针所指向的区域读attrValueLen个值回复给client。

  • 若返回值为ATT_SUCCESS,且outValueLen不为0时,server会从outValue指针所指向的区域读取数据,回复给client。如果outValueLen长度大于MTU时,会自动拆分为符合要求的数据,outValue必须是全局变量

  • 若返回值为其他值时,server不做任何操作,默认user会回复正确的响应。

b) 如果user没有设置回调读函数,server从pAttrValue指针所指向的区域读attrLen个值回复给client。

(d) writeCback

Attribute table中所有attribute的写回调函数,配置了允许写回调的attribute才会触发。

回调函数writeCback是写函数。函数原型:

typedef int (*atts_w_cb_t)(u16 connHandle, u8 opcode, u16 attrHandle, u8* writeValue, u16 valueLen);

user如果需要定义回调写函数,须遵循上⾯格式。回调函数writeCback是 optional的,对某⼀个具体的Attribute Group来说,user可以设置回调写函数,也可以不设置回调(不设置回调的时候⽤空指针NULL表⽰)。

回调函数writeCback触发条件为:当server收到的 Attribute PDU 的 Attribute Opcode 为以下三个时,server会检查回调函数writeCback是否被设置:

a) opcode = 0x12, Write Request.

b) opcode = 0x52, Write Command.

c) opcode = 0x18, Execute Write Request.

server收到以上写命令后:

a) 如果 user 设置了回调写函数,执⾏该函数,根据该函数的返回值决定是否回复Write Response/None/Execute Write Response:

  • 若返回值为0x01-0xFF,server回复error response给client,attribute handle in error设置为request里面的handle,error code设置为返回值。

  • 若返回值为0,SDK会根据setting和maxAttrLen参数,执行写attribute value操作。

  • 若返回值为其他值时,server不做任何操作。

b) 如果user没有设置回调写函数,SDK会根据setting和maxAttrLen参数,执行写attribute value操作。

(e) startHandle

该attribute group包含的attribute table的起始句柄。

(e) endHandle

该attribute group包含的attribute table的结束句柄。

(4) Attribute handle分配

由于现在Attribute table + service group的方式,LE Audio SDK将Attribute Handle做了一些预分配。如果user有用到SDK定义的service group需要注意attribute handle的分配问题。

SDK将现在使用到的Attribute Handle的定义存放在stack/ble/profile/service/svc.h。SDK将attribute handle大致划分如下表:

UUID Length Servcie Type Start Handle End Handle
16bit UUID GATT servcie 0x0001 0x01FF
Audio service 0x0200 0x05FF
user service 0x0600 0x07FF
128bit UUID Telink ota 0x0800 0x080F
Telink spp 0x0810 0x081F
user server 0x0900 0xFFFF

user如果有自定义的uuid时,最好放在user service区域,按照16bit/128bit UUID类型分配,这样可以加快client查询service速度。

SDK中已经实现的service占用的Attribute Handle情况,参考下表。

Service Name Service UUID Start Handle End Handle
Generic Access 0x1800 0x0001 0x000F
Generic Attribute 0x1801 0x0010 0x001F
Device Information 0x180A 0x0020 0x002F
HID 0x1812 0x0030 0x007F
Battery 0x180F 0x0080 0x008F
RFU 0x0090 0x01FF
Service Name Service UUID Start Handle End Handle
CSIS 0x1846 0x0200 0x020F
AICS 0x1843 0x0210 0x025F
VCS 0x1844 0x0260 0x027F
VOCS 0x1845 0x0280 0x02BF
MCS 0x1848 0x02C0 0x0300
GMCS 0x1849 0x0300 0x033F
OTS 0x1825 0x0340 0x035F
TBS 0x184B 0x0360 0x039F
MICS 0x184D 0x03A0 0x03AF
ASCS 0x184E 0x03B0 0x03EF
BASS 0x184F 0x03F0 0x040F
PACS 0x1850 0x0410 0x042F
CAS 0x1853 0x0430 0x043F
HAS 0x1854 0x0440 0x044F
TMAS 0x1855 0x0450 0x045F

(5) ATT opcode支持情况

该SDK已经支持除signed write command以外的所有ATT packet。user如果想要自行处理需要的ATT packet,需要通过下面API注册。

void blc_gatt_register_data_handler(gatt_handler_t handler);

回调函数原型:

typedef int (*gatt_handler_t)(u16 conn, u8 * p);

ATT层会根据这回调的返回值来判断是否需要继续处理。

  • 返回值为1,ATT层会忽略这个packet的处理,默认user处理。

  • 返回值为0,ATT层会继续处理packet。

(6) blc_atts_findCharacteristicByServiceUuid

该API主要为profile开发的,用来发现特定的服务UUID下拥有的特征UUID。

int blc_atts_findCharacteristicByServiceUuid(u8 *serviceUuid, u8 uuidLen, const atts_findCharList_t *charList, u16 charListLen, void *p);

输入参数:

serviceUuid: 服务UUID数据指针。

uuidLen: 服务UUID长度。默认只支持ATT_16_UUID_LEN和ATT_128_UUID_LEN。

charList: 特征UUID列表。

charListLen: 特征UUID列表长度。

p: 发现特征UUID后回调函数中的返回值。

返回值定义如下表。

返回值 含义
0 查询成功
-1 未查询到服务UUID
-2 特征UUID条目大于50条

特征UUID列表的结构体定义和参数含义:

typedef struct{
    u8 charUuidLen;
    const u8* charUuid;
    atts_charFoundCback_t foundCback;
}atts_findCharList_t;

charUuidLen: 特征UUID长度。默认只支持ATT_16_UUID_LEN和ATT_128_UUID_LEN。

charUuid: 特征UUID数据指针。

foundCback: 发现特征UUID后的回调函数指针。

typedef struct{
    u8 serviceNum;  //the number of the service uuid found
    u8 num; //the number of the characteristic uuid found
    u16 charHandle; //characteristic handle
    u16 *charDataLen;//characteristic data length
    u8 *charData;//characteristic data
    u8 *CCC;//CCC
}atts_foundCharParam_t;

typedef void (*atts_charFoundCback_t)(atts_foundCharParam_t * charParam, void *p);

serviceNum: 属于服务UUID的索引。多个相同的服务UUID时使用,目前未使用,默认0x00。

num: 特征UUID的数量。多个相同的特征UUID时,会触发多次回调。依据该值判断是第几个特征UUID。

charHandle: 特征UUID属性句柄。

charDataLen: 特征UUID属性值长度指针。

charData: 特征UUID属性值指针。

CCC: 拥有CCC属性时,非空。不推荐使用

(7) blc_atts_findCharacteristic

该API主要为profile开发的,用来发现特定的服务UUID以及include UUID下拥有的特征UUID。

int blc_atts_findCharacteristic(const atts_findServiceList_t *serviceList, void *p);

输入参数:

serviceList: 服务UUID列表指针。

p: 发现特征UUID后回调函数中的返回值。

返回值定义如下表。

返回值 含义
0 查询成功
-1 未查询到服务UUID

服务UUID列表数据结构体以及参数含义:

typedef struct{
    u8 serviceUuidLen;
    const u8* serviceUuid;
    u8 charSize;
    const atts_findCharList_t* charList;
    atts_serviceFoundCback_t foundCback;//RFU, Discovery of multiple services may be supported
    u8 inclSize;
    const atts_findInclList_t* inclList[5];
}atts_findServiceList_t;

serviceUuidLen: 服务UUID长度。

serviceUuid: 服务UUID数据指针。

charSize: 特征UUID列表长度。

charList: 特征UUID列表指针。含义参考blc_atts_findCharacteristicByServiceUuid

foundCback: 未使用。

inclSize: include UUID长度。

inclList: include UUID数据指针。这个结构体和atts_findCharList_t结构体类似。

发现include UUID或者服务UUID时的回调函数原型。

typedef bool (*atts_serviceFoundCback_t)(void *p);

返回值:true表示继续查询,false表示停止该UUID查询。

GATT Server

GATT server主要实现了service group的添加和删除,获取attribute information等功能。

(1) blc_gatts_getAttributeServiceGroup

调用如下API可以获得连接句柄对应的Service group起始指针。

atts_group_t *blc_gatts_getAttributeServiceGroup(u16 connHandle);

connHandle: ACL连接句柄,未使用。

返回值:对应的service group第一个指针,为空表示没有ATT Server。

注:这个API user层通常不会使用,协议层使用比较多。

(2) blc_gatts_addAttributeServiceGroup

调用该API可以添加service group到server。

void blc_gatts_addAttributeServiceGroup(atts_group_t *pGroup);

pGroup: 需要添加的service group指针。同一个group只会第一次添加成功,不会反复添加。

(3) blc_gatts_removeAttributeServiceGroup

下面的API可以从sever中删除特定的service group。

void blc_gatts_removeAttributeServiceGroup(u16 startHandle);

输入参数:

startHandle: 需要删除的service group起始句柄。如果句柄不存在,不会做任何操作。

(4) blc_gatts_getAttributeInformationByHandle

下面的API可以获取特定句柄对应attribute信息。

ble_sts_t blc_gatts_getAttributeInformationByHandle(u16 connHandle, u16 handle, u8** attrValue, u16** attrValueLen);

输入参数:

connHandle: 连接句柄,未使用。

handle: 属性句柄,

输出参数:

attrValue: 属性句柄对应的属性数据指针。

attrValueLen: 属性句柄对应的属性数据长度指针。

返回类型ble_sts_t可能返回的结构如下表所示:

ble_sts_t Value ERR Reason
BLE_SUCCESS 0 操作成功
LE_AUDIO_SERVER_INVALID_HANDLE 241 属性句柄不存在

(5) blc_gatts_getAttributeValueByHandle

下面的API可以获取特定句柄的attribute value。

u8* blc_gatts_getAttributeValueByHandle(u16 connHandle, u16 handle);

输入参数:

connHandle: ACL连接句柄,未使用。

handle: 属性句柄,

返回值:句柄对应的attribute value指针。

这个API主要用于获取已知长度的attribute value。

(6) blc_gatts_calculateDatabaseHash

下面的API可以计算database hash值。

bool blc_gatts_calculateDatabaseHash(u16 connHandle, u8* databaseHash);

输入参数:

connHandle:ACL连接句柄,未使用。

输出参数:

databaseHash: 计算结果的存放位置。

返回类型bool可能返回的结构如下表所示:

bool Value ERR Reason
true 1 计算完成
false 0 没有属性表

User在使用blc_svc_addCoreGroup添加GATT服务的情况下,该API不建议直接调用。在完成服务表的所有操作后(包括添加、删除),调用blc_svc_calcDatabaseHash。SDK会自动完成计算和存储。

void blc_svc_calcDatabaseHash(void);

GATT Client

基于ATT目前实现了所有ATT数据包的解析,该SDK在GATT层封装了client需要的所有操作。如read attributes、write attributes、find information、server initiated等。每个操作都是独立原子操作,方便GAP/user调用。

注意:

  • GATT client和GATT server所说的client和server与GAP层定义的peripheral和central不是一个概念。
  • peripheral和central是ACL连接情况下的主从概念。client和server允许在一个ACL角色下同时存在。所说的ACL连接句柄,不区分主从。

(1) blc_gattc_readAttributeValue

该API将read attribute的操作打包成一个原子操作来实现。

ble_sts_t blc_gattc_readAttributeValue(u16 connHandle, gattc_read_cfg_t *pRdCfg);

输入参数:

connHandle: ACL连接句柄。

pRdCfg: read attribute的结构体,需要是一个全局变量,底层会使用。

返回类型ble_sts_t可能返回的结构如下表所示:

ble_sts_t Value ERR Reason
BLE_SUCCESS 0 操作成功
其他值 other 操作失败

blc_gattc_readAttributeValue将ATT_READ_BY_TYPE_REQATT_READ_REQATT_READ_BLOB_REQATT_READ_MULTIPLE_REQATT_READ_READ_MULTIPLE_VARIABLE_REQ指令都能通过这个API来发送。下表展示了输入的数据与ATT指令的关系。NA表示不关心这个参数。

hdlCnt offset variable ATT_PDU
1 0 NA ATT_READ_REQ
1 !=0 NA ATT_READ_BLOB_REQ(Not recommended)
>1 NA true ATT_READ_READ_MULTIPLE_VARIABLE_REQ
>1 NA false ATT_READ_MULTIPLE_REQ
0 NA NA ATT_READ_BY_TYPE_REQ

gattc_read_cfg_t结构体定义及参数含义。

typedef u8 (*gattc_read_func_t)(u16 connHandle, u8 err, gatt_read_data_t *rdData, struct gattc_read_cfg *params);
/** @brief GATT Read procedure parameters configuration */
typedef struct gattc_read_cfg{
    /** Read attribute callback. */
    gattc_read_func_t func;
    /** If == 1: single.handle and single.offset are used.
     *  If > 1:  multiple.handles are used.
     *  If == 0: byUuid is used for Read Using Characteristic UUID. */
    u16 hdlCnt;
    union {
        struct {
            /** Attribute handle. */
            u16 handle;
            /** Attribute data offset. */
            u16 offset;
            u8 *wBuff;
            u16 *wBuffLen;
            u16 maxLen;
        } single;
        struct {
            /** Attribute handles to read with Read Multiple Characteristic Values. */
            u16 *handles;
            /** If TRUE: use Read Multiple Variable Length Characteristic Values procedure.
             *  If FALSE: use Read Multiple Characteristic Values procedure. */
            bool variable;
        } multiple;
        struct {
            /** First requested handle number. */
            u16 startHdl;
            /** Last requested handle number. */
            u16 endHdl;
            /** 2 Byte or 16 Byte UUID. */
            uuid_t *uuid;
        } byUuid;
    };
} gattc_read_cfg_t;

func: GATT对数据正常处理后,给用户层的回调函数。

输入参数:

connHandle: ACL连接句柄。

err: ATT层回复ATT_ERROR_RSP携带的error code。ATT_SUCCESS表示指令成功。

rdData: 读指令获取到的数据信息。

rdState: GATT_RD_CONT表示数据没有读完,由于属性值长度可能大于MTU,一条指令无法完全读完;GATT_RD_CMPLT表示数据读取完成。

dataLen: 读取到的数据长度。

dataVal: 读取到的数据指针。

params: 下发read指令的结构体,返回给更高层。

返回值:

GATT_PROC_END: 表示停止读取,即使有更多的数据,也不再关心后面的数据。

GATT_PROC_CONT: 表示继续读,可以通过ATT_READ_BLOB_REQ指令获取。

hdlCnt的含义见下表,hdlCnt不一样需要配置的参数也不同。

hdlCnt union description
1(single) handle 希望读的attribute handle
offset 通常设置为0,GATT层维护。
wBuff 读取到的数据期望写入指针地址,为空时不写入。
wBuffLen 读取到的数据长度期望写入指针地址,为空时不写入。
maxLen 支持读取的最大长度,主要是wBuff对应的长度,防止内存覆盖。
>0(mutiple) handle 希望读的attribute handle表指针。必须等于hdlCnt。
variable 判断attribute value是不是可变长度。true表示是可变长度,false是不可变长度。
0(byUuid) startHdl 读特定的UUID对应的起始句柄,不能为0。
endHdl 读特定的UUID对应的结束句柄,必须大于等于startHdl。
uuid 特定uuid的指针。

(2) blc_gattc_writeAttributeValue

该API将write attribute的操作打包成一个原子操作来实现。

ble_sts_t blc_gattc_writeAttributeValue(u16 connHandle, gattc_write_cfg_t *pWrCfg);

输入参数:

connHandle: ACL连接句柄。

pWrCfg: write attribute的结构体,需要是一个全局变量,底层会使用。

返回类型ble_sts_t可能返回的结构如下表所示:

ble_sts_t Value ERR Reason
BLE_SUCCESS 0 操作成功
其他值 other 操作失败

gattc_write_cfg_t将ATT_WRITE_REQATT_WRITE_CMDATT_PREPARE_WRITE_REQ指令打包为一个结构体,能通过配置来发送不同的指令。

gattc_write_cfg_t结构体定义及参数含义。

typedef void (*gattc_write_func_t)(u16 connHandle, u8 err, struct gattc_write_cfg *params);
/** @brief GATT Write procedure parameters configuration */
typedef struct gattc_write_cfg {
    /** Response callback */
    gattc_write_func_t func;
    /** Attribute handle */
    u16 handle;
    /** Attribute data offset */
    u16 offset;
    /** Data to be written */
    void *data;
    /** Length of the data */
    u16 length;
    /** If true use Write command procedure. */
    bool withoutRsp;
    u8 resverd;
} gattc_write_cfg_t;

func: GATT对数据正常处理后,给用户层的回调函数。

输入参数:

connHandle: ACL连接句柄。

err: ATT层回复ATT_ERROR_RSP携带的error code。ATT_SUCCESS表示指令成功。

params: 下发write指令的结构体,返回给更高层。

其他参数见下表:

name type description
fun gattc_write_func_t write指令后的回调函数
handle u16 属性句柄
offset u16 默认为0
data void* 希望写的数据指针
length u16 数据长度
withoutRsp bool true:ATT_WRITE_CMD

其中writeoutRsp = false, length>ConnMTUSize-3或者offset!=0时,GATT层会使用ATT_PREPARE_WRITE_REQ发送。

offset = 0, length < ConnMTUSize-3时,GATT层会使用ATT_WRITE_REQ发送。

(3) notify/indicate订阅

该API将CCC订阅等操作打包成一个原子操作来实现。

GATT对于notify/indicate消息的订阅方式,提供起始和结束句柄,订阅句柄范围内的消息。user也可以通过ATT层提供的回调解析ATT包数据。

在介绍相应的API前,先介绍订阅两个结构体。

订阅CCC消息的结构体:

typedef struct gattc_sub_ccc_msg {
    /* queue's node */
    struct gattc_sub_ccc_msg *pNext;
    gattc_sub_ccc_msg_func_t ntfOrIndFunc;
    u16 startHdl;
    u16 endHdl;
} gattc_sub_ccc_msg_t;

pNext: 指向下一个订阅消息的指针。

ntfOrIndFunc: 消息上报的回调函数。

startHdl: 订阅CCC消息的起始句柄。

endHdl: 订阅CCC消息的结束句柄。

回调函数的原型:

typedef void (*gattc_sub_ccc_msg_func_t)(u16 connHandle, u16 attHdl, u8 *val, u16 valLen);

输入参数:

connHandle: 连接句柄。

attHdl: 消息的属性句柄。

val: 消息存储的指针。

valLen: 消息长度。

想要订阅CCC消息,需要先打开消息上报开关,配置消息订阅的结构体。

typedef struct gattc_sub_ccc_cfg {

    /** Subscribe characteristic UUID type */
    uuid_t *uuid; //UUID size only set 2 on 16

    /** Subscribe value callback */
    gattc_sub_ccc_func_t func;

    /** Subscribe value handle (as start handle: for automatic discovery) */
    u16 valueHdl;

    /** Subscribe value, BIT(0):subscribe notify, BIT(1):subscribe indicate */
    u16 value;
} gattc_sub_ccc_cfg_t;

uuid: 想要配置消息订阅的属性UUID,主要是回调时回传参数使用,非必须参数。目前GAP层调用时需要使用。

fun: 配置消息订阅的事件回调。

valueHdl: CCC属性句柄。

value: 需要订阅的CCC属性。其中0x01表示订阅notify消息,0x02表示订阅indicate消息,0x03表示订阅notify+indicate消息,0x00表示关闭消息订阅。

配置消息订阅的回调,函数原型。

typedef u8 (*gattc_sub_ccc_func_t)(u16 connHandle, u8 err, struct gattc_sub_ccc_cfg *params);

输入参数:

connHandle: 连接句柄。

err: ATT层回复ATT_ERROR_RSP携带的error code。ATT_SUCCESS表示指令成功。

params: 用户传入的参数。

下面介绍订阅CCC消息相关的API。

(a) blt_gattc_writeSubscribeCCCRequest

该API会执行写订阅CCC参数,实际调用的是Write Attribute操作,发送ATT_WRITE_REQ命令。

ble_sts_t blt_gattc_writeSubscribeCCCRequest(u16 connHandle, gattc_sub_ccc_cfg_t *pSubCccCfg);

输入参数:

connHandle: ACL连接句柄。

pSubCccCfg: 如果希望配置CCC句柄参数,参考前面结构体的说明。

返回类型ble_sts_t可能返回的结构如下表所示:

ble_sts_t Value ERR Reason
BLE_SUCCESS 0 操作成功
其他值 other 操作失败

(b) blt_gattc_addSubscribeCCCNode

该API会添加订阅CCC消息节点。

bool blt_gattc_addSubscribeCCCNode(u16 connHandle, gattc_sub_ccc_msg_t *pSubNode);

输入参数:

connHandle: 连接句柄。

pSubNode: 如果希望订阅CCC节点的指针,参考前面结构体的说明。

返回类型bool可能返回的结构如下表所示:

bool Value ERR Reason
true 1 添加成功
false 0 添加失败

(c) blt_gattc_removeSubscribeCCCNode

该API会删除订阅CCC消息节点。

void blt_gattc_removeSubscribeCCCNode(u16 connHandle, gattc_sub_ccc_msg_t *pSubNode);

输入参数:

connHandle: ACL连接句柄。

pSubNode: 如果希望删除CCC节点的指针,参考前面结构体的说明。

(d) blt_gattc_cleanAllSubscribeCCCNode

该API会清空订阅CCC消息节点,主要是在蓝牙连接断开时调用。

void blt_gattc_cleanAllSubscribeCCCNode(u16 connHandle);

输入参数:

connHandle: ACL连接句柄。

(4) blc_gattc_discovery

该API将服务发现的操作打包成一个原子操作来实现并支持各种方式的服务发现。

ble_sts_t blc_gattc_discovery(u16 connHandle, gattc_sdp_cfg_t *pSdpCfg);

输入参数:

connHandle: ACL连接句柄。

pSdpCfg: 服务发现的结构体,需要是一个全局变量,底层会使用。

返回类型ble_sts_t可能返回的结构如下表所示:

ble_sts_t Value ERR Reason
BLE_SUCCESS 0 操作成功
其他值 other 操作失败

gattc_sdp_cfg_t将ATT_FIND_INFOMATION_REQATT_READ_BY_GROUP_TYPE_REQATT_READ_BY_ TYPE_REQATT_FIND_BY_TYPE_VALUE_REQ指令,打包为一个结构体,通过不同的配置发送不同的指令。

gattc_sdp_cfg_t结构体定义及参数含义。

/** @brief GATT Discovery procedure parameters configuration */
typedef struct gattc_sdp_cfg {
    /** Discover UUID type */
    uuid_t *uuid; //UUID size only set 2 on 16
    /** Discover attribute callback */
    gattc_sdp_func_t func;
    union {
        struct {
            /** Include service attribute declaration handle */
            u16 attrHdl;
            /** Included service start handle */
            u16 startHdl;
            /** Included service end handle */
            u16 endHdl;
        } _included;
        /** Discover start handle */
        u16 startHdl;
    };
    /** Discover end handle */
    u16 endHdl;
    /** Discover type */
    u8 type;
    /** Discover descriptor */
    u8 properties;

    u8 resverd[2];

} gattc_sdp_cfg_t;

uuid: 特殊Type可能需要使用,详细根据下面Type介绍的部分。

fun: 操作的回调。

_included: GATT维护,传入0即可。

startHdl: ATT操作起始句柄,不同Type作用不同。

endHdl: ATT操作结束句柄,不同Type作用不同。

Type: 每个Type对应不同的ATT操作,详细下面看介绍。

properties: GAP sdp discovery才会传入,GATT未使用。

返回类型ble_sts_t可能返回的结构如下表所示:

ble_sts_t Value ERR Reason
BLE_SUCCESS 0 操作成功
GATT_ERR_INVALID_PARAMETER 176 参数不合法
其他值 other 操作失败

服务发现的回调函数,函数原型。

typedef u8 (*gattc_sdp_func_t)(u16 connHandle, gatt_attr_t *attr, struct gattc_sdp_cfg *params);

输入参数:

connHandle: ACL连接句柄。

attr: 服务发现的属性信息,属性句柄,属性uuid,用户指针,用户指针根据不同的Type是不同的类型。下面介绍Type时会介绍,基础结构。

/** @brief GATT Attribute structure. */
typedef struct {
    /* Attribute_handle: handle */
    u16 handle;
    /* Attribute_types: UUID */
    const uuid_t *uuid;
    /* Attribute_value: User data */
    void *user_data;

}gatt_attr_t;

params: 服务发现的参数返回。

返回值:

GATT_PROC_END: 表示服务发现停止,即使有更多的数据,不再关心后面的数据。

GATT_PROC_CONT: 表示继续服务发现,直到startHandle等于endHandle或者触发ATT_ERROR_RSP停止。

服务发现API综合了很多ATT数据包操作,主要是根据Type的类型来发送不同的数据包,下面按照Type来介绍其他参数的作用。

(a) GATT_DISCOVER_PRIMARY

这类型用来发现首要服务。

uuid为空指针时,GATT层会发送ATT_READ_BY_GROUP_TYPE_REQ,其中Attribute Group Type默认配置为0x2800(Primary Service)。 starting Handle等于startHdl,ending Handle等于endHdl。

ATT_READ_BY_GROUP_TYPE_REQ PDU

uuid不为空指针时,GATT层会发送ATT_FIND_BY_TYPE_VALUE_REQ,其中Attribute Type默认配置为0x2800(Primary Service)。starting Handle等于startHdl,ending Handle等于endHdl。

ATT_FIND_BY_TYPE_VALUE_REQ PDU

user_data结构体定义,主要包含发现的primary的结束指针。

/** @brief Service Attribute Value. */
typedef struct {
    /** Service UUID. */
    const uuid_t *uuid;
    /** Service end handle. */
    u16 endHdl;
} gatt_service_val_t;

endHdl: 查询到的服务结束指针。

uuid: 当输入uuid为空指针时,会返回查询到的uuid信息。

(b) GATT_DISCOVER_SECONDARY

这类型用来发现次要服务,基本和GATT_DISCOVER_PRIMARY功能一样,只是默认配置不同。 uuid为空指针时,GATT层会发送ATT_READ_BY_GROUP_TYPE_REQ,其中Attribute Group Type默认配置为0x2801(Secondary Service)。

uuid不为空指针时,GATT层会发送ATT_FIND_BY_TYPE_VALUE_REQ,其中Attribute Type默认配置为0x2801(Secondary Service)。

(c) GATT_DISCOVER_INCLUDE

这类型用来发现include服务,GATT层会通过发送ATT_READ_BY_TYPE_REQ。其中Attribute Type默认配置为0x2802(Include Service)。starting Handle等于startHdl,ending Handle等于endHdl。

ATT_READ_BY_TYPE_REQ PDU

user_data结构体定义,主要包含发现的include信息。

/** @brief Include Attribute Value. */
typedef struct {
    /* Service start handle. */
    u16 startHdl;
    /* Service end handle. */
    u16 endHdl;
    /* Service UUID. */
    uuid_t uuid;
} gatt_include_t;

startHdl: 查询到的include起始句柄。

endHdl: 查询到的include结束句柄。

uuid: 查询到的include uuid信息。

(d) GATT_DISCOVER_CHARACTERISTIC

这类型用来发现特征属性,GATT层会通过发送ATT_READ_BY_TYPE_REQ。其中Attribute Type默认配置为0x2803(Characteristic)。starting Handle等于startHdl,ending Handle等于endHdl。

user_data结构体定义,主要包含发现的特征信息。

/** @brief Characteristic Attribute Value. */
typedef struct {
    /** GATT Characteristic attribute Handle. */
    u16 attrHdl;
    /** GATT Characteristic Properties. */
    u8  properties;
    /** GATT Characteristic Value Attribute Handle. */
    u16 valueHdl;
    /** GATT Characteristic UUID. */
    uuid_t uuid;
} gatt_chrc_t;

attrHdl: 查询到的特征句柄。

properties: 查询到的特征性能。

valueHdl: 查询到的特征数据属性句柄。

uuid: 查询到的特征UUID。

(e) GATT_DISCOVER_DESCRIPTOR

这类型用来发现特征的描述,GATT层会发送ATT_FIND_INFORMATION_REQ。starting Handle等于startHdl,ending Handle等于endHdl。会判断UUID,不能等于0x2800/0x2801/0x2802/0x2803。

find_info_req

(f) GATT_DISCOVER_ATTRIBUTE

这类型和GATT_DISCOVER_DESCRIPTOR一样,只是不会判断UUID的类型,需要更高层保证uuid合法。

(g) GATT_DISCOVER_STD_CHAR_DESC

这类型用来发现标准的特征描述,uuid参数必须是符合下表的参数。GATT层会发送ATT_READ_BY_TYPE_REQ。其中Attribute Type配置为uuid_t。starting Handle等于startHdl,ending Handle等于endHdl。

UUID description UUID description
0x2900 Characteristic Extended Properties 0x2908 Report Reference
0x2901 Characteristic User Description 0x2909 Number of Digitals
0x2902 Client Characteristic Configuration 0x290A Value Trigger Setting
0x2903 Server Characteristic Configuration 0x290B Environmental Sensing Configuration
0x2904 Characteristic Presentation Format 0x290C Environmental Sensing Measurement
0x2905 Characteristic Aggregate Format 0x290D Environmental Sensing Trigger Setting
0x2906 Valid Range 0x290E Time Trigger Setting
0x2907 External Report Reference 0x290F Complete BR-EDR Transport Block Data

GAP Server

待补充

GAP Client

GAP client主要是针对GATT client已经封装的API,对一些场景进行的功能的封装。实现了write Attribute Value、read Attribute Value、SDP discovery(针对已知service uuid和characteristic uuid做服务发现)、reconnect service(同一连接和sdp discovery不能同时进行)。

(1) blc_gapc_writeAttributeValue

该API主要是实现了write Attribute value的功能,与GATT层的API接口相比,user需要关心的参数更少,也不需要定义全局变量。

ble_sts_t blc_gapc_writeAttributeValue(u16 connHandle, gapc_write_cfg_t *pGapWrCfg);

输入参数:

connHandle: ACL连接句柄。

pGapWrCfg: 想要写入的GAP参数。

返回类型ble_sts_t可能返回的结构如下表所示:

ble_sts_t Value ERR Reason
BLE_SUCCESS 0 操作成功
GAP_ERR_WRITE_BUSY 194 GAP层有操作未完成
GAP_ERR_INVALID_PARAMETER 192 handle等于0
other 其他值 操作错误

gapc_write_cfg_t结构体定义以及参数含义。

/** @brief GAP Write procedure parameters configuration */
typedef struct gapc_write_cfg {
    /** Response callback */
    gapc_write_func_t func;
    /** Attribute handle */
    u16 handle;
    /** Data to be written if length > MTU-3, data must global variables*/
    void *data;
    /** Length of the data */
    u16 length;
    /** If true use Write command procedure. */
    bool withoutRsp;
    /** response callback input data*/
    void* cbData;
} gapc_write_cfg_t;

func: 操作成功的回调函数。

handle: 写属性句柄,不能为0。

data: 写数据指针。

length: 写数据长度。

withoutRsp: 是否需要回复。true: 使用ATT_WRITE_CMD指令发送,回调无作用。false: 使用ATT_WRITE_REQ指针发送,操作成功会有回调。

cbData: 回调中希望返回的数据,可以指示调用的标记。非必须。

写属性数据的回调函数原型。

typedef void (*gapc_write_func_t)(u16 connHandle, u8 err, void* data);

输入参数:

connHandle: ACL连接句柄。

err: ATT层的错误,ATT_SUCCESS表示成功。

data: GAP下发写指令带的cbData数据。

(2) blc_gapc_readAttributeValue

该API主要是实现了read Attribute value的功能,与GATT client层的API接口相对,user需要关系的参数更少,也不需要定义全局变量。

ble_sts_t blc_gapc_readAttributeValue(u16 connHandle, gapc_read_cfg_t *pGapReCfg);

输入参数:

connHandle: 连接句柄。

pGapReCfg: 想要写入的GAP参数。

返回类型ble_sts_t可能返回的结构如下表所示:

ble_sts_t Value ERR Reason
BLE_SUCCESS 0 操作成功
GAP_ERR_WRITE_BUSY 194 GAP层有操作未完成
GAP_ERR_INVALID_PARAMETER 192 handle等于0
other 其他值 操作错误

pGapReCfg结构体定义和参数说明。

/** @brief GAP read procedure parameters configuration */
typedef struct gapc_read_cfg {
    /** Response callback */
    gapc_read_func_t func;
    /** Attribute handle */
    u16 handle;
    /** Read data pointer*/
    u8 *wBuff;
    /** Read data length pointer*/
    u16 *wBuffLen;
    /** read data maximum length*/
    u16 maxLen;
} gapc_read_cfg_t;

func: 操作成功的回调函数。

handle: 读属性句柄,不能为0。

wBuff: 读数据后想要存储的指针,为空数据不存储。

wBuffLen: 读完数据后,存储数据长度的指针,为空长度不存储。

maxLen: 读到的数据允许存储的最大长度,超过的会丢掉。

读属性数据的回调函数原型。

typedef void (*gapc_read_func_t)(u16 connHandle, u8 err, gattc_read_cfg_t *pRdCfg);

输入参数:

connHandle: ACL连接句柄。

err: ATT层的错误,ATT_SUCCESS表示成功。

pRdCfg: GATTC层返回的read配置,参考GATT章节blc_gattc_readAttributeValue的说明。唯一区别GAP层的user不能停止读取,只会读取完成才会回调。

(3) blc_gapc_registerDiscoveryService

这个API是GAP实现的针对某一个service uuid的服务发现功能。这个API集合了服务发现的所有功能。这个接口的特点。

  • 支持4个相同的服务UUID。

  • 查询特定特征UUID的句柄,属性信息。

  • 上报未定义的特征信息,包括UUID,句柄,特征属性。

  • 针对特征UUID的操作,读取属性值,开启订阅notify通知,开启订阅indicate通知,查询描述符。

  • 支持3组不同的include UUID,最大允许有8个include UUID。

  • 支持2个ACL连接同时进行,消息最大数量40。

ble_sts_t blc_gapc_registerDiscoveryService(u16 connHandle, const blc_gapc_discList_t *list);

输入参数:

connHandle: ACL连接句柄。

list: 服务发现的列表指针。

返回类型ble_sts_t可能返回的结构如下表所示:

ble_sts_t Value ERR Reason
BLE_SUCCESS 0 操作成功
GAP_ERR_STATE_NO_IDLE 193 当前连接有服务发现未完成

blc_gapc_discList_t结构体涉及的回调函数和结构体数量比较多,主要包含service uuid、characteristic uuid、include uuid三个部分。先从小的结构体开始介绍。

(a) 服务UUID相关的结构体参数说明以及回调函数原型

typedef struct{
    //service uuid.
    uuid_t uuid;
    //found service uuid callback function.
    gapc_foundService_func_t sfun;
} blc_gapc_discService_t;

uuid: 想要发现的服务uuid。

sfun: 服务uuid相关操作的回调函数。包括发现服务,发现完成等。

typedef void (*gapc_foundService_func_t)(u16 connHandle, u8 count, u16 startHandle, u16 endHandle);

输入参数:

connHandle: ACL连接句柄。

count: 不同参数的含义参考下表。

count description
0x00 服务发现完成
0x01-0xFE 发现服务计数
0xFF 没有发现该服务

startHandle: 当前服务的起始句柄。

endHandle: 当前服务的结束句柄。

(b) 特征UUID相关的结构体参数说明以及回调函数原型

typedef struct{
    union {
        u8 setting; //blc_gapc_char_setting_enum
        struct {
            //Automatically subscribe to the notify property, if had.
            bool subscribeNtf   :1;
            //Automatically subscribe to the indicate property, if had.
            bool subscribeInd   :1;
            //found Descriptors, if had.
            bool findDecs       :1;
            //Automatically read characteristic value, if had read property.
            bool readValue      :1;
        };
    };
    //characteristic uuid.
    uuid_t uuid;
    //found characteristic uuid callback function.
    gapc_foundChar_fun_t cfun;
    //found characteristic Descriptors uuid callback function.
    gapc_foundCharDesc_func_t dfun;
    //subscribe client characteristic configuration callback function.
    gapc_subscribeCcc_func_t scfun;
    //want read characteristic value callback function.
    gapc_startReadChar_func_t rfun;
} blc_gapc_discChar_t;

uuid: 想要查询的特征UUID。

cfun: 查询到特征UUID的回调,如果有多个特征会触发多次。

readValue/rfun: 读特征属性值标记以及回调函数。GAP层会判断该特征有没有可读属性,不需要user关心。

findDesc/dfun: 查询特征描述符标记以及回调。GAP层会判断该特征有没有描述符,如果不存在则不会查询。

subscribeNtf/subscribeInd/scfun: 配置CCC notify/indicate订阅以及结果回调。GAP层会判断该特征有没有notify/indicate属性,以及描述符CCC,如果不存在则不会配置以及回调。这个两个配置同时会触发dfun回调。

查询到特征UUID回调函数原型。

typedef void (*gapc_foundChar_fun_t)(u16 connHandle, u8 serviceCount, u8 properties, u16 valueHandle);

输入参数:

connHandle: ACL连接句柄。

serviceCount: 未使用,常数0。

properties: 特征的属性。

valueHandle: 特征数据句柄。

查询特征描述符回调函数原型。

typedef void (*gapc_foundCharDesc_func_t)(u16 connHandle, uuid_t* uuid, u16 attrHandle);

输入参数:

connHandle: ACL连接句柄。

uuid: 描述符的UUID。

attrHandle: 描述符的属性句柄。

配置特征CCC通知的回调函数原型。

typedef void (*gapc_subscribeCcc_func_t)(u16 connHandle, u16 cccHandle, u8 result);

输入参数:

connHandle: ACL连接句柄。

cccHandle: CCC描述符的属性句柄。

result: 配置的结果。ATT_SUCCESS表示成功。其他的参考蓝牙规范。

读特征属性值回调函数原型。

typedef void (*gapc_startReadChar_func_t)(u16 connHandle, u16 attrHandle, u8** read, u16** readLen, u16* readMaxSize, gapc_read_func_t *rdCbFunc);

输入参数:

connHandle: ACL连接句柄。

attrHandle: 读取特征的属性句柄。

输出参数:

read: 读取特征属性数据存放的指针。为空指针表示丢弃数据内容。

readLen: 读取特征属性数据长度存放的指针。为空指针表示丢弃长度。

readMaxSize: 读取特征属性数据允许的最大长度。通常等于read指针的大小。

rdCbFunc: 读特征属性结束或者错误的回调函数。通常默认未空。

(c) 多条特征UUID服务发现的结构体以及参数说明

typedef struct{
    //supported characteristic uuid size.
    u8 size;
    //discovery characteristic uuid table.
    const blc_gapc_discChar_t *characteristic;
    //found unknown characteristic uuuid callback function.
    gapc_foundUnknownChar_func_t ufun;
} blc_gapc_discCharTable_t;

size: 需要查找的特征UUID的数量。

characteristic: 上个小节介绍的特征UUID数组的起始指针。

ufun: 未定义的特征UUID发现后的回调函数。

未定义的特征UUID发现后的回调函数原型。

typedef void (*gapc_foundUnknownChar_func_t)(u16 connHandle, uuid_t* uuid, u8 properties, u16 valueHandle);

输入参数:

connHandle: ACL连接句柄。

uuid: 特征UUID。

properties: 特征UUID属性。

valueHandle: 特征UUID数据句柄。

(d) include UUID服务发现的结构体以及参数说明

typedef struct{
    //discovery include service uuid.
    uuid_t uuid;
    //include characteristic uuid table.
    blc_gapc_discCharTable_t characteristic;
    //found include service uuid callback function.
    gapc_foundInclude_func_t ifun;
} blc_gapc_discInclude_t;
typedef struct{
    //supported discovery include uuid size.
    u8 size;
    //include discovery information table.
    const blc_gapc_discInclude_t *include[GAPC_DISC_MAX_INCLUDE_INFO];
    //found unknown include uuid callback function.
    gapc_foundUnknownInclude_func_t uifun;
} blc_gapc_discIncludeTable_t;

uuid: include UUID。

characteristic: include UUID包含的特征UUID列表。

ifun: 发现该include UUID的回调。

size: include UUID的数量。

include: include UUID结构体的指针。

uifun: 未定义的include UUID的回调。

ifun的函数原型。

typedef bool (*gapc_foundInclude_func_t)(u16 connHandle, u16 startHandle, u16 endHandle);

输入参数:

connHandle: ACL连接句柄。

startHandle: include UUID起始句柄。

endHandle: include UUID结束句柄。

返回值:

true: 继续该include uuid特征UUID的服务发现。false: 结束include服务发现。

uifun的函数原型。

typedef void (*gapc_foundUnknownInclude_func_t)(u16 connHandle, uuid_t* uuid, u16 startHandle, u16 endHandle);

输入参数:

connHandle: ACL连接句柄。

uuid: include UUID。

startHandle: include UUID起始句柄。

endHandle: include UUID结束句柄。

(e) service UUID服务发现的结构体以及参数说明

typedef struct{
    //discovery service uuid maximum count.
    u8 maxServiceCount;
    const blc_gapc_discService_t *service;
    const blc_gapc_discIncludeTable_t includeTable;
    const blc_gapc_discCharTable_t characteristicTable;
} blc_gapc_discList_t;

maxServiceCount: 支持最大Service UUID的数量。如果server端的数量小于这个值,会一直查询到触发Not Found Error Code。

service: service UUID指针。含义参考前面的内容。

includeTable: include UUID列表。含义参考前面小节。

characteristicTable: Service包含的特征UUID列表。含义参考前面小节。

(4) blc_gapc_registerReconnectService

这个API是GAP实现的针对某一个service uuid的回连功能。需要和blc_gapc_registerDiscoveryService配合使用。回连会跳过characteristic UUID的句柄发现过程,根据输入的参数直接read attribute和Subscribe CCC。

ble_sts_t blc_gapc_registerReconnectService(u16 connHandle, const blc_gapc_reconnList_t *list);

输入参数:

connHandle: ACL连接句柄。

list: 回连服务的列表指针。

返回类型ble_sts_t可能返回的结构如下表所示:

ble_sts_t Value ERR Reason
BLE_SUCCESS 0 操作成功
GAP_ERR_STATE_NO_IDLE 193 当前连接有服务发现未完成

blc_gapc_reconnList_t结构体涉及的回调函数和结构体数量比较多,主要包含service uuid、characteristic uuid、include uuid三个部分。先从小的结构体开始介绍。

(a) 特征UUID回连结构体以及参数说明

typedef struct{
    //get characteristic information callback function.
    gapc_getCharInfo_fun_t ifun;
    //want read characteristic value callback function.
    gapc_startReadChar_func_t rfun;
} blc_gapc_reconnChar_t;

typedef struct{
    //supported reconnect characteristic size.
    u8 size;
    //reconnect characteristic list.
    const blc_gapc_reconnChar_t *characteristic;
} blc_gapc_reconnCharTable_t;

ifun: 获取特征UUID的信息的回调。

rfun: 读取特征UUID属性值前的回调函数。具体功能参数前面的介绍,函数原型是相同的。

size: 特征UUID的数量。

characteristic: 特征UUID回连的指针。

ifun函数原型。

typedef struct{
    u8 properties;  //supported CHAR_PROP_READ, CHAR_PROP_NOTIFY, CHAR_PROP_INDICATE
    u16 valueHandle;
    u16 cccHandle;
} blc_gapc_charInfo_t;
typedef int (*gapc_getCharInfo_fun_t)(u16 connHandle, blc_gapc_charInfo_t* charInfo);

输入参数:

connHandle: ACL连接句柄。

输出参数:

charInfo: 特征UUID的基本信息,包括属性、数据句柄,CCC句柄(如果有)。

返回值: 特征UUID的数量。不能超过40个。

properties: 特征UUID的属性,目前只支持CHAR_PROP_READ、CHAR_PROP_NOTIFY、CHAR_PROP_INDICATE。

valueHandle: 数据句柄。属性中包含CHAR_PROP_READ,且句柄不为0。GAP层会读特征的属性值。

cccHandle: CCC句柄。属性中包含CHAR_PROP_NOTIFY或CHAR_PROP_INDICATE,且句柄不为0。GAP层会打开CCC通知。

(b) Include UUID回连的结构体以及参数说明

typedef struct{
    //gapc reconnect include characteristic callback function. if not set, default 1.
    gapc_reconnIncl_fun_t reifun;
    // reconnect include characteristic table list.
    const blc_gapc_reconnCharTable_t charTb;
} blc_gapc_reconnInclTable_t;

reifun: 开始include UUID回连的回调函数。

charTb: include UUID包含的特征UUID列表。含义参考上个小节。

reifun函数原型。

typedef bool (*gapc_reconnIncl_fun_t)(u16 connHandle, int count);

输入参数:

connHandle: ACL连接句柄。

count: 当前回连的include UUID次数。从1开始记数。

返回值:

true: 允许include UUID回连。false: 停止include UUID回连。

(c) Service UUID回连的结构体以及参数说明

typedef struct{
    const uuid_t serviceUuid;
    //gapc reconnect service characteristic callback function. if not set, default 1.
    gapc_reconnService_fun_t resfun;

    // reconnect service characteristic table list.
    const blc_gapc_reconnCharTable_t charTb;

    //supported discovery include uuid size.
    u8 inclSize;
    //include reconnect information table.
    const blc_gapc_reconnInclTable_t *includeCharTb[GAPC_DISC_MAX_INCLUDE_INFO];

} blc_gapc_reconnList_t;

serviceUuid: 回连的service UUID。

resfun: 回连service UUID的回调函数。

charTb: service UUID包含的特征UUID列表。

inclSize: include UUID Size。

includeCharTb: include UUID的列表。

resfun函数原型。

typedef bool (*gapc_reconnService_fun_t)(u16 connHandle, int count);

输入参数:

connHandle: ACL连接句柄。

count: 当前回连的Service UUID次数。从1开始记数。

返回值:

true: 允许Service UUID回连。false: 停止Service UUID回连。

(5) blc_gapc_discoveryOrReconnectService_loop

下面API主要是user有需要GAP来实现服务发现(blc_gapc_registerDiscoveryService)、回连服务(blc_gapc_registerReconnectService)才需要调用,main_loop中循环调用。

void blc_gapc_discoveryOrReconnectService_loop(void);

(6) blc_gapc_getDiscoveryServiceUUID

该API的作用主要是为了获取当前在服务发现或者回连的service UUID。作用是为了一些特征UUID可能被不同的Service UUID包含,可以通过这个函数了解到当前具体的service UUID触发的回调。

const uuid_t* blc_gapc_getDiscoveryServiceUUID(u16 connHandle);

输入参数:

connHandle: ACL连接句柄。

返回值:服务发现或者回连的Service UUID。

Enhanced ATT

待补充。

OTA

Telink LE Audio SDK是基于Telink BLE Multiple Connection SDK开发的,所以OTA的限制一样。

user只需要初始化后调用blc_svc_addOtaGroup即可。

void blc_svc_addOtaGroup(void);

注意:

目前LE Audio的代码量比较大,所以需要将OTA的boot地址设置为0x80000。函数必须在 sys_init 或者 cpu_wakeup_init 之前调用。

blc_ota_setFirmwareSizeAndBootAddress(450, MULTI_BOOT_ADDR_0x80000);

LE Audio Profile

蓝牙LE音频框架

与之前的所有其他蓝牙规范一样,蓝牙 LE 音频架构是分层构建的,如下图所示。在主机中,有一个称为通用音频框架 (Generic Audio Framework,简称 GAF) 的新结构。这是一个音频中间件,它包含可能被多个音频应用程序使用的通用组件。Core 和 GAF 是蓝牙 LE 音频的核心

LE 音频架构

LE配置文件和服务模型

GAF 中的所有规范都使用下图中描述的标准蓝牙 LE GATT 模型归类为配置文件 (Profile) 或服务 (Service)。在低功耗蓝牙中,配置文件和服务可以被视为客户端 (Client) 和服务器 (Server)。

蓝牙®LE配置文件和服务模型

服务规范定义了一个或多个特性,这些特性可以表示单个特性或状态机的状态。它们也可以是控制点,导致状态机状态之间的转换。配置文件作用于这些特征,读取或写入它们并在任何时候收到通知。多个设备,每个设备都充当客户端,可以操作在一个服务器上。

在LE音频中,多对一拓扑是更加常见的情况,尤其是在音量控制和广播源选择功能中,用户可能有多个设备实现 Profile 规范并充当客户端。在大多数情况下,这些都是先到先得原则

LE音频规范

有关详细的 LE 音频规格文档可以从 SIG 官网下载 (LE Audio Specifications) 。

LE音频规范

注意:

本文档中如果出现首字母缩写词以 "P" 结尾,则表示 Profile,如果以 "S" 结尾,则表示服务,如果以 "PS" 结尾,则通常指单独的 "Profile" 和 "Service" 文档的组合,后续不再赘述。

GAF 的功能分组

在 GAF 内部各种规范之间存在大量交互,因此很难在他们之间绘制清晰的层次结构或关系集,但它们可以大致分为如下图示的四个功能组。

通用音频框架内规范的功能分组

这种分组主要是为了解释,在 LE 音频的实际实现中,这些规范中的大多数都或多或少地相互影响。完全有可能只用其中的几个来实现产品,但要设计功能丰富、可互操作的产品,则需要其中的大部分。

SDK Profile 文件结构

在 IDE 中导入 telink_b91m_ble_audio_sdk 后,展开 stack/ble/profile, 显示的 profile 文件组织结构如下图所示。顶层文件夹有2个:audio,services。

SDK Profile 文件结构

audio profile 目录划分按照 LE 音频规范功能分组实现,包含如下几个部分:

common: 提供 audio profile 通用组件,如SDP存储、profile 基础事件、主循环入口等。

content: 提供媒体控制、电话呼叫控制等功能。

render_cap: 提供麦克风控制、音量控制等功能,服务于音频渲染和音频捕获。

stream: 提供音频流控制、公开音频能力、广播扫描等功能,并封装了 BAP 组件,是音频流管理的核心组成部分。

trans_coord: 提供协调设备服务、公共音频接口等功能。

user_case: 提供最顶层的音频用例,如电话和多媒体用例、助听器用例、公共广播等。

其中 services 目录包含所有 LE 音频服务相关的 ATT 表定义,并提供 GATT 服务注册机制 (可参考 ATTGATT Server 章节)。

Audio Common

音频配置

(1) 音频Event ID

音频事件回调函数是需要用户重点关注的,它是整个 Audio profile 跟用户交互的核心。 Audio 事件 ID 类型值请参考 audio_event_enum 枚举类型定义,其中不同 Profile 或 Server 对应的 Event ID 类型定义将在各自的功能分组中单独介绍。

#define AUDIO_EVENT_TYPE_SIZE               0x0100

typedef enum{
    AUDIO_EVT_START = 0x00,
    AUDIO_EVT_SVR_ROLE_FAIL,    //refer to 'blc_audio_svrGapRoleErrorEvt_t'

    /******** Event for Client SDP *************/
    AUDIO_EVT_TYPE_SDP = AUDIO_EVT_START + AUDIO_EVENT_TYPE_SIZE,
    AUDIO_EVT_CLIENT_SDP_FAIL,  //refer to 'blc_audio_sdpFailEvt_t'
    AUDIO_EVT_CLIENT_SDP_FOUND, //refer to 'blc_audio_sdpFoundEvt_t'
    AUDIO_EVT_CLIENT_SDP_END,   //refer to 'blc_audio_sdpEndEvt_t'
    AUDIO_EVT_CLIENT_ALL_SDP_OVER, //refer to 'blc_audio_sdpOverEvt_t'

    /*********** Event for Client *************/
    AUDIO_EVT_TYPE_PRF_CLIENT_START = 0x1000, //refer to each XXXP modules XXXS.h
    AUDIO_EVT_TYPE_CSISC = AUDIO_EVT_TYPE_PRF_CLIENT_START,
    ......
    AUDIO_EVT_TYPE_HASC  = AUDIO_EVT_TYPE_TMASC + AUDIO_EVENT_TYPE_SIZE,

    /*********** Event for Server *************/
    AUDIO_EVT_TYPE_PRF_SERVER_START = 0x4000, //refer to each XXXP modules XXXS.h
    AUDIO_EVT_TYPE_CSISS = AUDIO_EVT_TYPE_PRF_SERVER_START,
    ......
    AUDIO_EVT_TYPE_HASS  = AUDIO_EVT_TYPE_TMASS + AUDIO_EVENT_TYPE_SIZE,

    /*********** Event for controller *************/
    AUDIO_EVT_TYPE_CONTROLLER_START = 0x5000,
    AUDIO_EVT_ACL_CONNECT,          //refer to 'blc_audio_aclConnEvt_t'
    AUDIO_EVT_ACL_DISCONNECT,       //refer to 'blc_audio_aclDisconnEvt_t'
    AUDIO_EVT_CIS_CONNECT,          //refer to 'blc_audio_cisConnEvt_t'
    AUDIO_EVT_CIS_DISCONNECT,       //refer to 'blc_audio_cisDisconnEvt_t'
    AUDIO_EVT_CIS_REQUEST,          //refer to 'blc_audio_cisReqEvt_t'

    /*********** Event for host *************/
    AUDIO_EVT_TYPE_HOST_START = 0x5100,
    AUDIO_EVT_SMP_SECURITY_DONE,    //refer to 'blc_audio_securityDoneEvt_t'

} audio_event_enum;

(a) AUDIO_EVT_SVR_ROLE_FAIL

用户注册的服务器或者客户端的角色不满足要求时会产生该事件, 该事件附带的参数信息请参考 blc_audio_svrGapRoleErrorEvt_t 定义。

typedef enum{
    AUDIO_CLIENT_MIN = 0x00,
    AUDIO_CSIS_CLIENT,      /* Coordinated Set Identification Service Client */
    AUDIO_PACS_CLIENT,      /* Published Audio Capabilities Service Client */
    AUDIO_ASCS_CLIENT,      /* Audio Stream Control Service Client */
    AUDIO_BASS_CLIENT,      /* Broadcast Audio Scan Service Client */
    AUDIO_GMCS_CLIENT,      /* Generic Media Control Service Client */
    AUDIO_GTBS_CLIENT,      /* Generic Telephone Bearer Service Client */
    AUDIO_VCP_CLIENT,       /* Volume Controller Profile Client, include VOCS+AISC*/
    AUDIO_MICS_CLIENT,      /* Microphone Control Service Client include AICS*/
    AUDIO_TMAS_CLIENT,      /* Telephony And Media Audio Service Client */
    AUDIO_HAS_CLIENT,       /* Hearing Access Service Client */
    AUDIO_TEST_PRF_CLIENT,  /* Test Profile(SPP Service) Client */
    AUDIO_CLIENT_MAX = 0x80,

    AUDIO_SERVER_MIN = AUDIO_CLIENT_MAX,
    AUDIO_CSIS_SERVER,      /* Coordinated Set Identification Service Server */
    AUDIO_PACS_SERVER,      /* Published Audio Capabilities Service Server */
    AUDIO_ASCS_SERVER,      /* Audio Stream Control Service Server */
    AUDIO_BASS_SERVER,      /* Broadcast Audio Scan Service Server */
    AUDIO_GMCS_SERVER,      /* Generic Media Control Service Server */
    AUDIO_GTBS_SERVER,      /* Generic Telephone Bearer Service Server */
    AUDIO_VCP_SERVER,       /* Volume Controller Profile Server, include VOCS+AISC*/
    AUDIO_MICS_SERVER,      /* Microphone Control Service Server include AICS*/
    AUDIO_TMAS_SERVER,      /* Telephony And Media Audio Service Server */
    AUDIO_HAS_SERVER,       /* Hearing Access Service Server */
    AUDIO_TEST_PRF_SERVER,  /* Test Profile(SPP Service) Server */
    AUDIO_SERVER_MAX,
} audio_service_role_enum;

typedef enum {
    ACL_ROLE_CENTRAL        = 0,
    ACL_ROLE_PERIPHERAL     = 1,
} acl_conection_role_t;

typedef struct{ //Event ID: AUDIO_EVT_SVR_ROLE_FAIL
    u16 connHandle;
    audio_service_role_enum svcId;
    acl_conection_role_t currAclRole;
} blc_audio_svrGapRoleErrorEvt_t;

事件参数:

connHandle: ACL 连接句柄。

svcId: 表示当前客户端或者服务器注册的 服务角色 ID 值,协议底层按照 ID 值递增顺序轮训处理注册的服务的数据和事件。

currAclRole: 表示当前客户端或者服务器应该在 ACL_ROLE_CENTRAL 或 ACL_ROLE_PERIPHERAL 角色下使用。

(b) AUDIO_EVT_CLIENT_SDP_FAIL

作为客户端在做服务发现(Service Discovery)时遇到失败情况会上报该事件,事件附带的参数信息结构体 blc_audio_sdpFailEvt_t。

typedef struct{ //Event ID: AUDIO_EVT_CLIENT_SDP_FAIL
    u16 aclHandle;
    audio_service_role_enum svcId;
} blc_audio_sdpFailEvt_t;

事件参数:

connHandle: ACL 连接句柄。

svcId: 表示当前用户注册的服务角色 ID 对应的客户端做服务发现时遇到失败,比如没有发现该服务。

(c) AUDIO_EVT_CLIENT_SDP_FOUND

当注册的客户端在做服务发现(Service Discovery)时发现关联的服务后会产生该事件,事件附带的参数参考 blc_audio_sdpFoundEvt_t。

typedef struct{ //Event ID: AUDIO_EVT_CLIENT_SDP_FOUND
    u16 aclHandle;
    audio_service_role_enum svcId;
    u16 startHdl;
    u16 endHdl;
} blc_audio_sdpFoundEvt_t

事件参数:

connHandle: 当前 ACL 连接句柄。

svcId: 表示当前用户注册的服务角色 ID 对应的客户端。

startHdl: 表示当前客户端发现的服务起始句柄。

endHdl: 表示当前客户端发现的服务结束句柄。

(d) AUDIO_EVT_CLIENT_SDP_END

作为客户端在做服务发现(Service Discovery)时完成服务发现时上报该事件,事件附带的参数信息结构体 blc_audio_sdpEndEvt_t。

typedef struct{ //Event ID: AUDIO_EVT_CLIENT_SDP_END
    u16 aclHandle;
    audio_service_role_enum svcId;
} blc_audio_sdpEndEvt_t;

事件参数:

connHandle: ACL 连接句柄。

svcId: 表示当前用户注册的服务角色 ID 对应的客户端做服务发现完成。

(e) AUDIO_EVT_CLIENT_ALL_SDP_OVER

当所有注册的客户端所有服务都发现完成后 (包括首要服务、次要服务、特性发现、特性读、CCC 订阅等),会产生该事件。事件附带的参数参考 blc_audio_sdpOverEvt_t。

typedef struct{ //Event ID: AUDIO_EVT_CLIENT_SDP_OVER
    u16 aclHandle;
} blc_audio_sdpOverEvt_t;

事件参数:

connHandle: ACL 连接句柄。

(f) AUDIO_EVT_ACL_CONNECT

ACL 连接建立完成后,只要控制器设置了相关事件掩码就会产生该事件。(blc_hci_le_setEventMask_cmd 必须使能 HCI_LE_EVT_MASK_CONNECTION_COMPLETE 或 HCI_LE_EVT_MASK_ENHANCED_CONNECTION_COMPLETE)。

typedef struct{ //Event ID: AUDIO_EVT_ACL_CONNECT
    u16  aclHandle;
    u16  connInterval;
    u8   PeerAddrType;
    u8   PeerAddr[6];
} blc_audio_aclConnEvt_t;

事件参数:

connHandle: ACL连接句柄。

(g) AUDIO_EVT_ACL_DISCONNECT

ACL 连接断开后,只要控制器设置了相关事件掩码就会产生该事件。(blc_hci_setEventMask_cmd 必须使能 HCI_EVT_MASK_DISCONNECTION_COMPLETE)

typedef struct{ //Event ID: AUDIO_EVT_ACL_DISCONNECT
    u16  aclHandle;
    u8   reason;
} blc_audio_aclDisconnEvt_t;

事件参数:

connHandle: ACL连接句柄。

reason: ACL 连接断开原因。

(h) AUDIO_EVT_CIS_CONNECT

CIS 连接建立完成后,只要控制器设置了相关事件掩码就会产生该事件。(blc_hci_le_setEventMask_cmd 必须使能 HCI_LE_EVT_MASK_CIS_ESTABLISHED)。

typedef struct{ //Event ID: AUDIO_EVT_CIS_CONNECT
    u16 cisHandle;
    u8  cigSyncDly[3];
    u8  cisSyncDly[3];
    u8  transLaty_m2s[3];
    u8  transLaty_s2m[3];
    u8  phy_m2s; //le_phy_type_t: 0x01/0x02/0x03
    u8  phy_s2m; //le_phy_type_t: 0x01/0x02/0x03
    u8  nse;
    u8  bn_m2s;
    u8  bn_s2m;
    u8  ft_m2s;
    u8  ft_s2m;
    u16 maxPDU_m2s;
    u16 maxPDU_s2m;
    u16 isoIntvl;
} blc_audio_cisConnEvt_t;

事件参数:细节参考《Core_V5.4》, Vol 4, Part E 7.7.65.25 LE CIS Established event。

(i) AUDIO_EVT_CIS_DISCONNECT

CIS 连接断开后,只要控制器设置了相关事件掩码就会产生该事件。(blc_hci_setEventMask_cmd 必须使能 HCI_EVT_MASK_DISCONNECTION_COMPLETE)

typedef struct{ //Event ID: AUDIO_EVT_ACL_DISCONNECT
    u16 cisHandle;
    u8  reason;
} blc_audio_cisDisconnEvt_t

事件参数:

cisHandle: 当前 CIS 连接句柄。

reason: CIS 连接断开原因。

(j) AUDIO_EVT_CIS_REQUEST

Peripheral 收到 central 发送的 CIS Request (LL Control PDU) 后,只要控制器设置了相关事件掩码就会产生该事件。(blc_hci_setEventMask_cmd 必须使能 HCI_LE_EVT_MASK_CIS_REQUESTED)。

typedef struct{ //Event ID: AUDIO_EVT_CIS_REQUEST
    u16 aclHandle;
    u16 cisHandle;
    u8  cigId;
    u8  cisId;
} blc_audio_cisReqEvt_t;

事件参数:细节参考《Core_V5.4》, Vol 4, Part E 7.7.65.26 LE CIS Request event。

(2) Common API

(a) blc_audio_main_loop

用于处理 Audio profile 协议相关的数据和事件入口 API:

void blc_audio_main_loop(void);

(b) blc_audio_registerProfileEventCallback

用于注册 Audio profile 协议处理相关的事件回调 API:

void blc_audio_registerProfileEventCallback(audio_evt_cb_t evtCb);

回调函数原型:

typedef int(*audio_evt_cb_t)(u16 connHandle, int evtID, u8 *pData, u16 dataLen);

输入参数:

connHandle: ACL 连接句柄。

evtID: 发生的音频事件 ID, 参考音频 Event ID 和后续各个功能分组中的 XXXX Event 介绍 章节。

pData: 发生当前音频事件时附带的事件参数信息的头指针,当无附带信息时为 NULL。

dataLen: 发生当前音频事件时附带的事件参数信息数据长度,当无附带信息时为 0。

参考如下代码进行初始化,当前 SDK 没有做事件掩码屏蔽机制*,所有的音频事件都会无条件上报用户层,用户可以选择对感兴趣的事件进行相关处理:

备注: LE Controller 和 Host 有部分事件是被 Audio profile 底层接管的,对于这些被接管的事件是否上报依赖于 LE Controller (对于 API: blc_hci_le_setEventMask_cmd) 和 Host (对应 API:blc_gap_setEventMask) 的事件掩码是否开启。相关细节可以参考单连接或多连接对应的章节。

static int app_audio_prfEvtCb(u16 connHandle, int evtID, u8 *pData, u16 dataLen)
{
    switch(evtID){
    case AUDIO_EVT_ACL_CONNECT:
    {
        //Process TODO: 
    }
    break;

    //...... Omit audio event 

    default:
        BLT_APP_LOG("unprocessed audio event:0x%x", evtID);
        break;
    }
    return 0;
}
//Audio profile event register
blc_audio_registerProfileEventCallback(app_audio_prfEvtCb);

(c) blc_audio_registerServiceModule

用于注册客户端或者服务器模块的 API。

void blc_audio_registerServiceModule(audio_acl_role_enum usedAclRole, blc_audio_proc_t *pSvc, void *param);

输入参数:

usedAclRole: 表示注册的客户端或者服务器 GAP 角色,参考 audio_acl_role_enum 枚举类型定义:

typedef enum{
    AUDIO_ACL_CENTRAL = BIT(0),
    AUDIO_ACL_PERIPHERAL = BIT(1),
    AUDIO_ACL_UNSPECIF = BITS(0, 1),
} audio_acl_role_enum;

pSvc: 表示注册服务组件的控制块,主要包括初始化函数指针、连接、断开函数指针、服务发现函数指针、loop 函数指针。 参考结构体 blc_audio_proc_t 定义:

typedef struct blc_audio_proc{
    struct blc_audio_proc *pNext;
    u8 id;//Refer to 'audio_service_role_enum'
    u8 usedAclRole; //Refer to 'audio_acl_role_enum'
    u8 resverd1[2];
    int (* init)(u8 initType, void *initParam);
    int (* connect)(u16 connHandle, audio_state_enum connState);
    int (* discov)(u16 connHandle);
    int (* loop)(u16 connHandle);
} blc_audio_proc_t;

参数信息:

pNext: 指针指向下一个注册的服务组件控制块的首地址。

id: 当前注册服务组件的 ID 编号,参考枚举类型 audio_service_role_enum, 服务组件队列根据 ID 编号从小到大顺序排列,目前 Client 服务组件的 ID范围 AUDIO_CLIENT_MIN = 0x00 ~ AUDIO_CLIENT_MAX = 0x80, Server ID 从 0x81 开始递增。

usedAclRole: 记录当前服务组件可以在 GAP Central or Peripheral 角色下运行。

init: 初始化函数指针,initTyp: : 可以为 AUDIO_PROC_INIT 或 AUDIO_PROC_DEINIT,可以参考枚举类型 audio_proc_type_enum 中的定义, initPar: m:表示输入初始化参数指针,传递一些服务组件初始化的配置信息。

connect: ACL 连接建立或者断开函数指针,connState 表示连接状态,可以参考枚举类型 audio_state_enum 定义。

discov: 服务发现指针函数,服务组件作为 Client 的时候,需要初始化用来做服务发现 (SDP),作为 Server 时,需要初始化为 NULL。

loop: 服务组件main_loop 轮训函数指针,用于注册组件的服务相关状态机、数据、事件等处理。

注意:

用户如果想要在 Audio Profile 组件上增加自己的服务器或者客户端,只需要新增相关服务 ID 并且实现上面4个函数指针对应的函数功能。具体的方法参考添加用户服务组件.

添加用户服务组件

本章节主要介绍如何添加用户服务组件,在实际产品中,在标准的LE Audio服务以外,用户想要添加自定义的服务。本章主要介绍在该SDK框架下添加用户服务组件,服务器更新特征属性值,客户端读取特征属性值,客户端写特征属性值,客户端如何完成服务发现等功能。

本章以Telink SPP服务为例,其中服务和特征表如下。

标签 UUID 属性
service 添加⽤⼾服务组件-0809-0a0b-0c0d-1910 -
sppOut 00010203-0405-0607-0809-0a0b-0c0d-2B10 read,notify
sppIn 00010203-0405-0607-0809-0a0b-0c0d-2B11 read,write,writeWithouthResponse

其中SPP服务表定义在stack/ble/profile/services/svc_telink文件夹中,用手机进行连接后,可以看到默认是SPP服务如下图所示。user也可以根据需要,自行修改属性,用来测试协议栈的功能,这个服务只用来测试使用,不要添加到量产固件

spp_service

SPP服务的规范代码定义在stack/ble/profile/audio/common/test_prf文件夹中,所有的代码开源。user如果想要添加新的服务组件,可以在这个目录代码中修改,也可以自行创建新的文件夹。下面依次介绍如何添加服务器和客户端用户服务组件。

(1) 添加服务器用户服务组件

(a) 服务器组件结构体

服务器用户组件,首先需要先定义一个结构体,将SDK服务组件必须的blc_audio_proc_t结构包含进行。服务器通常只需要拥有特征的句柄,也可以有一些特殊的参数也可以在自行定义,如VCS中的volStep。

typedef struct {
    u16 ser2cliHandle;  //service to client attribute handle
    u16 cli2serHandle;  //client to service attribute handle
} blc_test_prf_server_t;

typedef struct blc_test_prf_server_ctrl{
    blc_audio_proc_t process;
    blc_test_prf_server_t testPrfServer;
} blc_test_prf_server_ctrl_t;

服务器通常只需要定义一份server实体,在LE Audio中绝大多数都是一份server实体的。特征的属性值,推荐定义在服务表文件中,通过API来获取指针进行读写操作。如SPP中的属性值是定义在svc_spp.c中。

u8 sppOutData[100];
u16 sppOutDataLen = 0;
u8 sppInData[100];
u16 sppInDataLen = 0;

(b) 服务器组件实例化

对于服务器组件,需要进行实例化。通常服务组件的discov函数为空,该函数指针主要用户服务发现操作,客户端组件中需要定义。

blc_test_prf_server_ctrl_t test_prf_server_ctrl = {
    .process = {
        .pNext = NULL,
        .id = AUDIO_TEST_PRF_SERVER,
        .usedAclRole = 0,
        .init = blt_test_prf_server_init,
        .connect = blt_test_prf_server_connect,
        .discov = NULL,
        .loop = blt_test_prf_server_loop,
    },
};

pNext: 必须为NULL。在调用blc_audio_registerServiceModule函数后,添加到队列中。

id: 该服务器组件的ID,必须要定义在AUDIO_TEST_PRF_SERVER之后。该ID也可以充当规范的优先级,初始化等函数回调的先后顺序。

usedAclRole: 通常设置为0,在调用blc_audio_registerServiceModule函数,传入参数。

(c) 服务器注册函数

该API在所有的服务器文件中都存在,主要是用来将服务器组件注册到表里。

void blc_audio_registerTestProfileControlServer(const blc_test_prf_regParam_t *param)
{
blc_audio_registerServiceModule(AUDIO_ACL_UNSPECIF, (blc_audio_proc_t*)&test_prf_server_ctrl, (void*)param);
}

测试规范服务器组件,默认支持ACL主从都能运行。可以设置只支持ACL主设备或者ACL从设备。

param:测试规范服务器默认初始化的参数。该函数最终会触发test_prf_server_ctrl.init函数指针指向的函数。

测试规范初始化函数,需要将SPP服务表添加到协议栈中,注册读写函数回调,初始化参数到SPP服务表中。

int blt_test_prf_server_init(u8 initType, void *param)
{
    if(initType == AUDIO_PROC_INIT) {
        BLC_TEST_PRF_LOG("server init");
        blc_svc_addSppGroup();
        blc_svc_sppCbackRegister(blt_test_prf_server_readCback, blt_test_prf_server_writeCback);
        blt_test_prf_serviceInit(param);
    }
    return 0;
}

初始化使用的函数,都可以在第二章中找到说明,这里不再重复介绍。

(d) 服务器ACL连接/断连回调

测试规范给了一些简单的示例,ACL连接/断开会打印一些日志。

int blt_test_prf_server_connect(u16 connHandle, audio_state_enum connState)
{
    if(connState == AUDIO_STATE_DISCONN) {
        BLC_TEST_PRF_LOG("Disconnect:0x%x", connHandle);
    } else {
        BLC_TEST_PRF_LOG("Connect:0x%x", connHandle);
    }

    return 0;
}

(e) 服务器轮询回调

测试规范给了一些简单的示例,ACL连接的情况下,定时打印一些日志。

int blt_test_prf_server_loop(u16 connHandle)
{
    static u32 tick = 1;
    if(clock_time_exceed(tick, 10*1000*1000)){
        BLC_TEST_PRF_LOG("server connHandle:0x%02x", connHandle);
        tick = clock_time();
    }
    return 0;
}

(f) 服务器更新特征属性值

后续标准的LE Audio规范的文件中,都会有类似的 blc_xxxxs_updateYyyy(u16 connHandle, ...) 的函数定义。xxxx 是规范的名称,Yyyy 是特征属性的名称。如 blc_vcss_updateVolFlags/blc_micss_updateMute 等 API。

这类API的功能,就是服务器对某个连接的客户端发送 notify,来更新特征属性值,后续关于这类 API 不在重复介绍。测试规范中也有一个 API,用来更新特征属性值。

ble_sts_t   blc_test_prf_updateVal(u16 connHandle, char* val)
{
    blt_test_prf_setSer2CliData(val);
    return blc_gatts_notifyAttr(connHandle, TEST_PRF_SER2CLI_HANDLE);
}

注意:

服务器的测试可以使用手机来快速验证基本功能。

(2) 添加客户端用户服务组件

(a) 客户端组件结构体

客户端用户组件,首先需要先定义一个结构体,将SDK服务组件必须的blc_audio_proc_t结构包含进行。测试规范中客户端包括了所有characteristic attribute handle、Characteristic Properties、Characteristic attribute value、Characteristic attribute value length。

ttypedef struct {
    gattc_sub_ccc_msg_t ntfInput;
    /* Characteristic value handle */
    u8 ser2cliProperties;       //service to client attribute properties
    u16 ser2cliHandle;          //service to client attribute handle
    u8 cli2serProperties;       //client to service attribute properties
    u16 cli2serHandle;          //client to service attribute handle

    u16 ser2cliDataLen;         //service to client attribute value length
    u8 ser2cliData[100];        //service to client attribute value

    u16 cli2serDataLen;         //client to service attribute value length
    u8 cli2serData[100];        //client to service attribute value length

} blc_test_prf_client_t;

typedef struct blc_test_prf_client_ctrl{
    blc_audio_proc_t process;
    blc_test_prf_client_t* pTestPrfClient[STACK_AUDIO_ACL_CONN_MAX_NUM];
} blc_test_prf_client_ctrl_t;

typedef struct{

} blc_test_prf_client_regParam_t;

通常客户端需要定义多份client实体,数量和需要支持的ACL角色和数量相关。测试规范中默认支持ACL主从角色,所以client实体数量是'ACL_CENTRAL_MAX_NUM + ACL_PERIPHR_MAX_NUM'。

测试代码中,将结构体中client的数量定义为'STACK_AUDIO_ACL_CONN_MAX_NUM',主要是为了生成库后能修改数量。user开发可以不这样实现。

(b) 客户端组件实例化

对于客户端组件,需要进行实例化。

blc_test_prf_client_ctrl_t test_prf_client_ctrl = {
    .process = {
        .pNext = NULL,
        .id = AUDIO_TEST_PRF_CLIENT,
        .usedAclRole = 0,
        .init = blt_test_prf_client_init,
        .connect = blt_test_prf_client_connect,
        .discov = blt_test_prf_client_discovery,
        .loop = blt_test_prf_client_loop,
    },
};

客户端操作组件实例化。

blc_test_prf_client_t gTestPrfClient[ACL_CENTRAL_MAX_NUM + ACL_PERIPHR_MAX_NUM];

blc_test_prf_client_t *blt_test_prf_getClientControlBuffer(u8 instIdx)
{
    assert(instIdx < gAppAudioAclMaxNum);

    return &gTestPrfClient[instIdx];
}

(c) 客户端注册函数

该API在所有的客户端文件中都存在,主要是用来将客户端组件注册到表里。

void blc_audio_registerTestProfileControlClient(const blc_test_prf_client_regParam_t *param)
{
    blc_audio_registerServiceModule(AUDIO_ACL_UNSPECIF, (blc_audio_proc_t*)&test_prf_client_ctrl, (void*)param);
}

测试规范客户端组件,默认支持ACL主从都能运行。可以设置只支持ACL主设备或者ACL从设备。

param:测试规范客户端默认初始化的参数。该函数最终会触发test_prf_client_ctrl.init函数指针指向的函数。

测试规范初始化函数,注意将客户端组件数据清零。

int blt_test_prf_client_init(u8 initType, void *param)
{
    if(initType == AUDIO_PROC_INIT) {

        for (int i = 0; i < gAppAudioAclMaxNum; i++) {
            blc_test_prf_client_t *testPrfClient = blt_test_prf_getClientControlBuffer(i);
            test_prf_client_ctrl.pTestPrfClient[i] = testPrfClient;
            memset(testPrfClient, 0, sizeof(blc_test_prf_client_t));
        }
        BLC_TEST_PRF_LOG("client init");
    }
    return 0;
}

(d) 客户端ACL连接/断连回调

测试规范给了一些简单的示例,ACL连接/断开会打印一些日志。ACL断开时需要清零内存。

int blt_test_prf_client_connect(u16 connHandle, audio_state_enum connState)
{
    blc_test_prf_client_t *client = blt_test_prf_client_getClientInst(connHandle);
    if(connState == AUDIO_STATE_DISCONN) {
        BLC_TEST_PRF_LOG("Disconnect:0x%x", connHandle);
        memset(client, 0, sizeof(blc_test_prf_client_t));
    } else {
        BLC_TEST_PRF_LOG("Connect", connHandle);
    }

    return 0;
}

(e) 客户端轮询回调

测试规范给了一些简单的示例,ACL连接的情况下,定时打印一些日志。

int blt_test_prf_client_loop(u16 connHandle)
{
    static u32 tick = 1;
    if(clock_time_exceed(tick, 10*1000*1000)){
        BLC_TEST_PRF_LOG("Client connHandle:0x%02x", connHandle);
        tick = clock_time();
    }
    return 0;
}

(f) 客户端服务发现回调

目前存储未开放给user使用,测试规范只是给了一些简单的示例代码。

int blt_test_prf_client_discovery(u16 connHandle)
{
    if(blc_audio_checkDiscoveryBusy(connHandle))
        return 0;
    if(blc_audio_checkReconnectFlag(connHandle))
    {
        blc_test_prf_client_t* client = blt_test_prf_client_getClientInst(connHandle);

        //Test code,
        client->ntfInput.startHdl = SPP_START_HDL;
        client->ntfInput.endHdl = SPP_END_HDL;
        client->ntfInput.ntfOrIndFunc = blt_test_prf_client_dataInput;
        client->ser2cliHandle = SPP_SER2CLI_DP_HDL;
        client->ser2cliProperties = CHAR_PROP_READ | CHAR_PROP_NOTIFY;
        client->cli2serHandle = SPP_CLI2SER_DP_HDL;
        client->cli2serProperties = CHAR_PROP_READ | CHAR_PROP_WRITE_WITHOUT_RSP | CHAR_PROP_WRITE;

        if(client->ntfInput.startHdl)
        {
            if(BLC_SPP_START_RECONN(connHandle) == BLE_SUCCESS)
            {
                blc_audio_sendServiceDiscoveryFoundEvent(connHandle, AUDIO_TEST_PRF_CLIENT, client->ntfInput.startHdl, client->ntfInput.endHdl);
                blc_audio_setDiscoveryStatusBusy(connHandle);
                BLC_TEST_PRF_LOG("reconnect handle: 0x%x", connHandle);
            }
        }
        else
        {
            blc_audio_sendServiceDiscoveryFailEvent(connHandle, AUDIO_TEST_PRF_CLIENT);
            blc_audio_setDiscoveryStatusFinish(connHandle);
        }
        return 0;
    }

    if(BLC_SPP_START_SDP(connHandle) == BLE_SUCCESS)
    {
        blc_audio_setDiscoveryStatusBusy(connHandle);
        BLC_TEST_PRF_LOG("start discovery 0x%x", connHandle);
    }
    return 0;
}

blc_audio_checkDiscoveryBusy: 检查是否在进行服务发现。

blc_audio_setDiscoveryStatusBusy: 成功开始服务发现。

blc_audio_sendServiceDiscoveryFailEvent: 上报服务发现失败。

blc_audio_sendServiceDiscoveryFoundEvent: 上报服务发现成功。

blc_audio_setDiscoveryStatusFinish: 服务发现完成,profile层需要依赖这个进行下一个客户端的服务发现。

blc_audio_checkReconnectFlag: 检查此次是否是回连。目前user不能使用存储,回连不能简单实现。

其中真正的服务发现和回连的API可以参考GAPC章节blc_gapc_registerDiscoveryServiceblc_gapc_register ReconnectService

(g) 客户端读特征属性值

后续标准的LE Audio规范的文件中,都会有类似的blc_xxxxc_readYyyy(u16 connHandle, audio_prf_read_cb_t readCb)的函数定义。xxxx的规范的名称,Yyyy是特征属性的名称。如blc_vcsc_readVolState/blc_micsc_readMute等API。

这类API的功能,就是客户端对某个连接的服务器发送read,来读取特征属性值,后续关于这类API不在重复介绍。测试规范中也有API,用来读取特征属性值。

读取SPP In特征属性值为例。

ble_sts_t blc_test_prf_client_readSppIn(u16 connHandle, audio_prf_read_cb_t readCb);

profile调用的读取特征值,在GAPC API: blc_gapc_readAttributeValue上在进行了封装,增加一个回调函数给user。用法参考blc_gapc_readAttributeValue

ble_sts_t blc_audio_readAttributeValue(u16 connHandle, gapc_read_cfg_t *pGapReCfg, audio_prf_read_cb_t readCb);

void blc_audio_readAttributeValueCallback(u16 connHandle, att_err_t err);

GAP层操作成功后才会有一个回调函数,通过函数指针readCb。

(h) 客户端获取特征属性值

后续标准的LE Audio规范的文件中,都会有类似的blc_xxxxc_getYyyy(u16 connHandle, ...)的函数定义。xxxx的规范的名称,Yyyy是特征属性的名称。如blc_vcsc_getVolState/blc_micsc_getMute等API。

这类API的功能,就是客户端直接从缓存中,获取特征属性值,后续关于这类API不在重复介绍。测试规范中也有API,用来获取特征属性值。

获取SPP In特征属性值为例。

audio_error_enum blc_test_prf_client_getSppIn(u16 connHandle, char* val);

(i) 客户端写特征属性值

后续标准的LE Audio规范的文件中,都会有类似的blc_xxxxc_writeYyyy(u16 connHandle, ..., audio_prf_write_cb_t writeCb)的函数定义。xxxx的规范的名称,Yyyy是特征属性的名称。如blc_vcsc_writeVolState/blc_micsc_writeMute等API。

这类API的功能,就是客户端对某个连接的服务器发送write req/prepare write req,来写特征属性值,后续关于这类API不在重复介绍。测试规范中也有API,用来写特征属性值。

写SPP In特征属性值为例。

ble_sts_t blc_test_prf_client_writeSppIn(u16 connHandle, char* val, audio_prf_write_cb_t writeCb);

profile调用的写特征值,在GAPC API: blc_gapc_writeAttributeValue上在进行了封装,增加一个回调函数给user。用法参考blc_gapc_writeAttributeValue

ble_sts_t blc_audio_writeAttributeValue(u16 connHandle, gapc_write_cfg_t *pGapWrCfg, audio_prf_write_cb_t writeCb);
void blc_audio_writeAttributeValueCallback(u16 connHandle, att_err_t err);

GAP层操作成功后会才有一个回调函数,通过函数指针writeCb。

(j) 客户端写特征属性值不带回复的方式

后续标准的LE Audio规范的文件中,都会有类似的blc_xxxxc_writeYyyyWithoutRsp(u16 connHandle, ...)的函数定义。xxxx的规范的名称,Yyyy是特征属性的名称。如blc_bassc_writeBcstScanCtrlPointWithoutRsp等API。

这类API的功能,就是客户端对某个连接的服务器发送write command,来写特征属性值,后续关于这类API不在重复介绍。测试规范中也有API,用来写特征属性值。

写SPP In特征属性值为例。

ble_sts_t blc_test_prf_client_writeSppInWithoutRsp(u16 connHandle, char* val);

这个接口和带回复的只是GATT层发送的ATT指令不同,不带回复的数据发送不能超过MTU-3。

(3) 日志打印规则

profile的日志打印,建议使用以下方法定义打印函数。该函数和printf库函数功能一样,支持最小的log打印系统,可以在uart/usb/模拟uart三套方式输出。

#ifndef PRF_DBG_TEST_PRF_EN
#define PRF_DBG_TEST_PRF_EN                                     1
#endif

const u8 DBG_PRF_MASK_TEST_PRF_LOG = IS_ENABLED(PRF_DBG_TEST_PRF_EN);

#define BLC_TEST_PRF_LOG(fmt, ...)  BLC_AUDIO_PRF_DBG(DBG_PRF_MASK_TEST_PRF_LOG, "[TEST]"fmt, ##__VA_ARGS__)

音频存储

(1) 音频存储区域

在 Flash 中⽤于存储 Audio 客户端信息的区域称为音频存储区域。对于 telink_b91m_ble_audio_sdk, 音频存储区域起始位置由宏 FLASH_SDP_ATT_ADRRESS 指定 (1MB Flash 默认为 0xF6000, 512KB Flash 默认为 0x7D000)。 音频存储区域分为 2 个区,分别称为 A 区、B 区,占⽤的空间相等,由宏 FLASH_SDP_ATT_MAX_SIZE 指定(默认为 0x2000,即 8KB,所以总音频存储区域⼤⼩为 16KB)。每个区的 (FLASH_SDP_ATT_MAX_SIZE-0x10) 偏移量(默认为 0x1FF0)位置为 “区有效 Flag”, 0x3C 代表有效, 0xFF 代表未⽣效。

(2) 存储相关API

blc_audio_initPairingInfoStoreModule

用于注册 音频服务发现后的句柄信息是否存储的 API:

void blc_audio_initPairingInfoStoreModule(void);

用户如果初始化的时候没有调用该 API, 则将⾳频不会将客户端服务发现的句柄信息存储到 FLASH 中。

流管理 (Stream Management)

一组定义音频设备基本互操作性的新规范,包括单播和广播音频流的功能发现和配置。

流控制

这四个规范构成了通用音频框架的基础,它们统称为 BAPS 规范。它们的核心是 BAP 基本音频配置文件,用于设置和管理单播和广播音频流。

服务 描述
PACS 已发布的音频能力服务,它公开了设备支持的音频能力
BASS ⼴播⾳频扫描服务,它定义了发现和连接到⼴播⾳频流以及分发⼴播加密密钥的流程
ASCS ⾳频流控制服务,它公开设备的⾳频流端点,并允许客⼾端发现,配置,建⽴和控制⾳频流端点以及与之相关的单播⾳频流
BAP ⼀组定义⾳频设备基本互操作性的新规范,包括单播和⼴播⾳频流的功能发现和配置

BAPS规范负责设置承载音频数据的底层同步通道的格式,同时为 LC3 定义了一组标准的编解码器配置 (参考附录LC3 编解码器 章节),以及用于广播和单播应用程序的相应服务质量 (QoS) 设置范围。

每个单独的同步通道的状态机都为单播和广播定义,两者都将音频流从配置状态移动到流状态,简化的同步通道状态机: IDLE <=> Configured <=> Streaming。对于单播,状态机在 ASCS 规范中定义,状态驻留在服务器中的各个音频端点中,客户端控件在 BAP 中定义。对于广播,发送器和接收器之间没有连接,客户端与服务器模型的概念变得有点脆弱。因此,状态机仅为发送器端定义,并且完全受其本地应用程序的控制。使用广播,接收者需要检测流的存在然后接收它,但无法影响其状态。多个单播或广播同步通道以组的形式绑定在一起,BAP 定义了如何将这些组及其组成的同步通道放在一起以用于广播和单播流 (参考BIS 和 CIS 同步通信 章节)。

PACP/PACS: 已发布的音频能力

PACS:已发布的音频能力服务,它公开了设备支持的音频能力。PACS在LE Audio profile里负责发布设备支持的能力,包括支持的编解码器(LC3或者厂商自定义的编解码器),支持的编解码参数(包括采样率,帧间隔,压缩数据比特率等),元数据参数。支持音频位置,支持播放的音频上下文。

PACSS是user后面开发中最常修改的参数,需要了解PAC(Published Audio Capability)、Audio Location、Audio Contexts三个参数的含义。

(1) PAC(Published Audio Capability)

PAC包含Codec ID(声明编解码器编号),Codec Specific Capabilities(编解码器特定参数),metadata(元数据)三块组成。格式如下表所示:

参数 ⼤⼩ (字节) 描述
Codec ID 5 LC3: 0600000000
Byte0: 编解码器格式
Byte1-Byte2: company ID
Byte3-Byte4: 供应商指定codec_ID
编解码器特定参数⻓度 1
编解码器特定参数 可变 根据需求填写
元数据⻓度 1 -
元数据 可变 根据需求填写

其中编码器特定参数和元数据都是LTV格式,具体的可以参考Bluetooth Assigned Number,附录编码器特定能力LTV结构中也有介绍。下面小节介绍PACS server初始化的API时会介绍参数的含义。

(2) Audio Locations

音频位置是4Byte的数据,目前定义参考附录音频位置定义,最新的定义需要参考Bluetooth Assigned Number。音频位置主要作用是让Client端在选择音频时,将合适位置的音频发送给特定设备。如TWS耳机,左耳音频位置只支持Front Left,右耳音频位置只支持Front Right,这样的话手机就能准确立体声音频发送给左右耳耳机。

SDK中关于音频位置的宏定义,user可以直接使用。

// Audio Support Location (for codec parameter)
enum{
    BLC_AUDIO_LOCATION_FLAG_FL                = BIT(0),  // Front Left
    BLC_AUDIO_LOCATION_FLAG_FR                = BIT(1),  // Front Right
    BLC_AUDIO_LOCATION_FLAG_FC                = BIT(2),  // Front Center
    BLC_AUDIO_LOCATION_FLAG_LFE1              = BIT(3),  // Low Frequency Effects 1
    BLC_AUDIO_LOCATION_FLAG_BL                = BIT(4),  // Back Left
    BLC_AUDIO_LOCATION_FLAG_BR                = BIT(5),  // Back Right
    BLC_AUDIO_LOCATION_FLAG_FLc               = BIT(6),  // Front Left of Center
    BLC_AUDIO_LOCATION_FLAG_FRc               = BIT(7),  // Front Right of Center
    BLC_AUDIO_LOCATION_FLAG_BC                = BIT(8),  // Back Center
    BLC_AUDIO_LOCATION_FLAG_LFE2              = BIT(9),  // Low Frequency Effects 2
    BLC_AUDIO_LOCATION_FLAG_SiL               = BIT(10), // Side Left
    BLC_AUDIO_LOCATION_FLAG_SiR               = BIT(11), // Side Right
    BLC_AUDIO_LOCATION_FLAG_TpFL              = BIT(12), // Top Front Left
    BLC_AUDIO_LOCATION_FLAG_TpFR              = BIT(13), // Top Front Right
    BLC_AUDIO_LOCATION_FLAG_TpFC              = BIT(14), // Top Front Center
    BLC_AUDIO_LOCATION_FLAG_TpC               = BIT(15), // Top Center
    BLC_AUDIO_LOCATION_FLAG_TpBL              = BIT(16), // Top Back Left
    BLC_AUDIO_LOCATION_FLAG_TpBR              = BIT(17), // Top Back Right
    BLC_AUDIO_LOCATION_FLAG_TpSiL             = BIT(18), // Top Side Left
    BLC_AUDIO_LOCATION_FLAG_TpSiR             = BIT(19), // Top Side Right
    BLC_AUDIO_LOCATION_FLAG_TpBC              = BIT(20), // Top Back Center
    BLC_AUDIO_LOCATION_FLAG_BtFC              = BIT(21), // Bottom Front Center
    BLC_AUDIO_LOCATION_FLAG_BtFL              = BIT(22), // Bottom Front Left
    BLC_AUDIO_LOCATION_FLAG_BtFR              = BIT(23), // Bottom Front Right
    BLC_AUDIO_LOCATION_FLAG_FLw               = BIT(24), // Front Left Wide
    BLC_AUDIO_LOCATION_FLAG_FRw               = BIT(25), // Front Right Wide
    BLC_AUDIO_LOCATION_FLAG_LS                = BIT(26), // Left Surround
    BLC_AUDIO_LOCATION_FLAG_RS                = BIT(27), // Right Surround
    BLC_AUDIO_LOCATION_FLAG_RFU               = BITS(28,29,30,31) // bit28 ~ bit29
};
#define BLC_AUDIO_CHANNEL_ALLOCATION_RFU(param)         (param&BLC_AUDIO_LOCATION_FLAG_RFU)

(3) 音频内容

音频内容(Audio Context)是2字节的内容,每个bit表示能接受的音频内容。目前最新定义参考附录中的“音频内容”小节,最新的定义需要参考Bluetooth Assigned Number

SDK中关于音频内容的宏定义,user可以直接使用。

// Context Type
enum{
    BLC_AUDIO_CONTEXT_TYPE_PROHIBITED          = 0x0000, 
    BLC_AUDIO_CONTEXT_TYPE_UNSPECIFIED         = BIT(0),
    BLC_AUDIO_CONTEXT_TYPE_CONVERSATIONAL      = BIT(1), 
    BLC_AUDIO_CONTEXT_TYPE_MEDIA               = BIT(2), 
    BLC_AUDIO_CONTEXT_TYPE_GAME                = BIT(3), 
    BLC_AUDIO_CONTEXT_TYPE_INSTRUCTIONAL       = BIT(4), 
    BLC_AUDIO_CONTEXT_TYPE_VOICE_ASSISTANTS    = BIT(5), 
    BLC_AUDIO_CONTEXT_TYPE_LIVE                = BIT(6), 
    BLC_AUDIO_CONTEXT_TYPE_SOUND_EFFECTS       = BIT(7), 
    BLC_AUDIO_CONTEXT_TYPE_NOTIFICATIONS       = BIT(8),
    BLC_AUDIO_CONTEXT_TYPE_RINGTONE            = BIT(9), 
    BLC_AUDIO_CONTEXT_TYPE_ALERT               = BIT(10), 
    BLC_AUDIO_CONTEXT_TYPE_EMERGENCY_ALARM     = BIT(11),
};
#define BLC_AUDIO_CONTEXT_TYPE_VALID_BITS(param)   (param&(~BITS(12, 13, 14, 15)))      //bit12 ~ bit15
#define BLC_AUDIO_CONTEXT_TYPE_CHECK_RFU(param)    (param == 0 || param&BITS(12, 13, 14, 15))

(4) PACSC/ Event介绍

TBD

(5) PACSC API: blc_audio_registerPACSControlClient

该 API 用来注册 PACS Client 模块,只有 GAP Central 角色下生效。

void blc_audio_registerPACSControlClient(const blc_pacsc_regParam_t *param);

输入:param: 未使用,传入空指针即可。

注意:

BAP 相关 API 会封装更高一层 API 供用户使用 (参考BAP相关 API介绍),一般不建议用户单独调用。

(6) PACSS API: blc_audio_registerPACSControlServer

该 API 用来注册 PACS Server 模块,只有 GAP Peripheral 角色下生效。

void blc_audio_registerPACSControlServer(const blc_pacss_regParam_t *param);

输入:param:希望初始化的 PACS 参数。

注意:

BAP相关API会封装更高一层 API 供用户使用 (参考BAP相关 API介绍),一般不建议用户单独调用。

blc_pacss_regParam_t 结构体的定义以及参数含义。

typedef struct{
    u8 sinkPacNum;                  //number of Sink PAC records
    const blc_audio_pacParam_t* sinkPac;    //Sink PAC
    u32 sinkAudioLocations;         //Sink Audio Location, BLC_AUDIO_LOCATION_FLAG_FL

    u8 sourcePacNum;                    //number of Source PAC records
    const blc_audio_pacParam_t* sourcePac;  //Source PAC
    u32 sourceAudioLocations;       //Source Audio Location, BLC_AUDIO_LOCATION_FLAG_FL

    u16 availableSinkContexts;      //Available Sink Contexts
    u16 availableSourceContexts;    //Available Source Contexts
    u16 supportedSinkContexts;      //Supported Sink Contexts
    u16 supportedSourceContexts;    //Supported Sink Contexts
} blc_pacss_regParam_t;

sinkPacNum: Sink PAC的数量。

sinkPac: Sink PAC的能力。

sinkAudioLocations: Sink支持音频位置。

sourcePacNum: Source PAC的数量。

sourcePac: source PAC的能力。

sourceAudioLocations: source支持音频位置。

availableSinkContexts: 可用的Sink音频内容。

availableSourceContexts: 可用的source音频内容。

supportedSinkContexts: 支持的Sink音频内容。

supportedSourceContexts: 支持的source音频内容。

PAC参数前面小节PAC(Published Audio Capability)有做介绍,主要有编解码器ID,音频特定能力,元数据三部分组成。结构体定义如下。

typedef struct{
    blc_audio_codec_id_t  codecId; //Codec ID, 06 0000 0000 mean LC3 codec
    blc_audio_codecSpecCapParam_t codecSpec;
    blc_audio_metadataParam_t metadata;
} blc_audio_pacParam_t;

codecId: 编解码器ID。LC3是06 0000 0000,其他数据自行查询。

codecSpec: 音频特定能力,主要包括采样率,帧间隔,压缩后比特率等参数。

metadata: 元数据。

该SDK将编解码器ID和音频特定能力合并定义了宏定义,将常用的LC3编解码器的参数,都进行了封装。LC3的名称参考附录音频支持能力要求。user在初始化时,只需要设置支持的音频声道,每个SDU支持的LC3编解码块数量。

#define LC3_CAP_8_1(counts, perSdu)             ...
#define LC3_CAP_8_2(counts, perSdu)             ...
#define LC3_CAP_16_1(counts, perSdu)            ...
#define LC3_CAP_16_2(counts, perSdu)            ...
#define LC3_CAP_24_1(counts, perSdu)            ...
#define LC3_CAP_24_2(counts, perSdu)            ...
#define LC3_CAP_32_1(counts, perSdu)            ...
#define LC3_CAP_32_2(counts, perSdu)            ...
#define LC3_CAP_441_1(counts, perSdu)           ...
#define LC3_CAP_441_2(counts, perSdu)           ...
#define LC3_CAP_48_1(counts, perSdu)            ...
#define LC3_CAP_48_2(counts, perSdu)            ...
#define LC3_CAP_48_3(counts, perSdu)            ...
#define LC3_CAP_48_4(counts, perSdu)            ...
#define LC3_CAP_48_5(counts, perSdu)            ...
#define LC3_CAP_48_6(counts, perSdu)            ...

PAC中关心的元数据类型只有preferredContexts和StreamingContexts,该SDK对于这两个参数,也封装了一下宏定义。方便用户初始化PACS server模块。

#define METADATA_CONTEXTS(contexts)         .metadata = \
                                            {   \
                                                .preferredContexts = contexts,  \
                                                .StreamingContexts = contexts,  \
                                            }

#define METADATA_CONTEXTS_UNSPECIFIED           ...
#define METADATA_CONTEXTS_CONVERSATIONAL        ...
#define METADATA_CONTEXTS_MEDIA                 ...
#define METADATA_CONTEXTS_GAME                  ...
#define METADATA_CONTEXTS_INSTRUCTIONAL         ...
#define METADATA_CONTEXTS_VOICE_ASSISTANTS      ...
#define METADATA_CONTEXTS_LIVE                  ...
#define METADATA_CONTEXTS_SOUND_EFFECTS         ...
#define METADATA_CONTEXTS_NOTIFICATIONS         ...
#define METADATA_CONTEXTS_RINGTONE              ...
#define METADATA_CONTEXTS_ALERT                 ...
#define METADATA_CONTEXTS_EMERGENCY_ALARM       ...

pacs_server_buf.c文件中也简单的声明了支持LC3_16_2和LC3_24_2两组参数,左声道,一个LC3编解码块;支持media类型的音频的PAC初始化参数。user可以参考这个初始化,自行定义预期的PAC参数。

const blc_audio_pacParam_t defaultPac[] = {
    {
        LC3_CAP_16_2(BLC_AUDIO_CHANNEL_COUNTS_1, 1),
        METADATA_CONTEXTS_MEDIA,
    },
    {
        LC3_CAP_24_2(BLC_AUDIO_CHANNEL_COUNTS_1, 1),
        METADATA_CONTEXTS_MEDIA,
    },
};

const blc_pacss_regParam_t defaultPacsParam = {
    .sinkPacNum = 1,
    .sinkPac = defaultPac,
    .sinkAudioLocations = BLC_AUDIO_LOCATION_FLAG_FL,
    .sourcePacNum = 1,
    .sourcePac = defaultPac,
    .sourceAudioLocations = BLC_AUDIO_LOCATION_FLAG_FL,
    .availableSinkContexts = BLC_AUDIO_CONTEXT_TYPE_UNSPECIFIED|BLC_AUDIO_CONTEXT_TYPE_CONVERSATIONAL|BLC_AUDIO_CONTEXT_TYPE_MEDIA,
    .availableSourceContexts = BLC_AUDIO_CONTEXT_TYPE_UNSPECIFIED|BLC_AUDIO_CONTEXT_TYPE_CONVERSATIONAL|BLC_AUDIO_CONTEXT_TYPE_MEDIA,
    .supportedSinkContexts = BLC_AUDIO_CONTEXT_TYPE_UNSPECIFIED|BLC_AUDIO_CONTEXT_TYPE_CONVERSATIONAL|BLC_AUDIO_CONTEXT_TYPE_MEDIA,
    .supportedSourceContexts = BLC_AUDIO_CONTEXT_TYPE_UNSPECIFIED|BLC_AUDIO_CONTEXT_TYPE_CONVERSATIONAL|BLC_AUDIO_CONTEXT_TYPE_MEDIA,
};

ASCP/ASCS:音频流控制

ASCS: 音频流控制服务,它公开设备的音频流端点,并允许客户端发现,配置,建立和控制音频流端点以及与之相关的单播音频流。

ASCS服务主要由Audio Stream Endpoint(简称ASE)承载,ASE分两种:

  • Source ASE: 音频流出的方向

  • Sink ASE: 音频流入的方向

Source ASE 状态机

Source ASE有7个状态,分别为

  • Idle:待机态,初始化默认处于Idle状态,可以通过Release操作从Releasing状态进入。

  • Codec Configured:音频参数,如采样率,位宽,帧长等配置完成。可以通过Config Codec操作从Idle状态进入,通过Release操作从Rleaseing状态进入,通过Config Codec操作从QoS Configured状态进入。

  • QoS Configurded:连接参数,如PHY,Retransmit Num,Max Transport Latency等配置完成,可以通过Config QoS操作从Codec Configured状态进入,通过Receive Stop Ready操作从Disabling状态进入。

  • Enabling:使能态,client与server之间的CIS开始建立或者已经建立完成。只能通过Enable操作从QoS Configured状态进入。在Enabling状态下可以更新metedata。

  • Streaming:流状态,音频流建立完成,音频数据可以在client与server之间传递。此状态只能通过Receive start ready操作从Enabling状态进入。在streaming状态下可以更新metadata。

  • Disabling:失效态,音频流停止,该状态是一个中间态,可以通过Receive start stop操作退回到QoS Configured状态。需要注意:该状态只有Source ASE才拥有。可以通过Disable操作从Enabling或者Streaming状态进入。

  • Releasing:释放态,音频资源释放,该状态是一个中间态,可以从除Idle态的任何一个状态进入,可以通过Released操作Release到Codec Configured态(缓存,减少从释放音频流到再次建立音频流所用的时间)或者Idle态(完全释放)。

Sink ASE 状态机

Sink ASE共有6个状态,其所有状态和操作均可参考Source ASE。相比于Source ASE,Sink ASE少了Disabling状态,所以从Enabling或Streaming状态Disable时,将直接退回到QoS Configured状态。Sink ASE相比于Source ASE少了Disabling状态的原因请用户结合ASCS V1.0理解。

(1) ASCSC Event 介绍

ASCS Client 相关的事件都已被 BAP Unicast Client 事件接管,用户可以参考BAP Unicast Client Event 介绍 章节。

(2) ASCSS Event 介绍

ASCS Server 相关的事件都已被 BAP Unicast Server 事件接管,用户可以参考BAP Unicast Server Event 介绍 章节。

(3) ASCSC API: blc_audio_registerASCSControlClient

该 API 用来注册 ASCS Client 模块,只有 GAP Central 角色下生效。

void blc_audio_registerASCSControlClient(const blc_ascsc_regParam_t *param);

输入:param未使用,传入空指针即可。

注意:

ASCSC 相关 API 会封装更高一层 API 供用户使用 (参考Unicast Client API介绍章节),一般不建议用户单独调用。

(4) ASCSS API: blc_audio_registerASCSControlServer

该 API 用来注册 ASCS Server 模块,只有 GAP Peripheral 角色下生效。

void blc_audio_registerASCSControlServer(const blc_ascss_regParam_t *param);

输入:param 未使用,传入空指针即可。

注意:

ASCSS 相关 API 会封装更高一层 API 供用户使用 (参考Unicast Server API介绍章节),一般不建议用户单独调用。

BASP/BASS: 广播音频扫描

BASS:广播音频扫描服务,它定义了发现和连接到广播音频流以及分发广播加密密钥的流程。

(1) BASSC Event介绍

//BASS Client Event ID
typedef enum{
    AUDIO_EVT_BASSC_START = AUDIO_EVT_TYPE_BASSC,
    AUDIO_EVT_BASSC_RECV_SYNCINFO_REQ,
    AUDIO_EVT_BASSC_RECV_SINK_STATE,    
    AUDIO_EVT_BASSC_BROADCAST_CODE_REQ,
    AUDIO_EVT_BASSC_BAD_BROADCAST_CODE, 
} audio_bassc_evt_enum;

BASSC 事件主要在收到broadcast receive state时,触发的事件。

AUDIO_EVT_BASSC_RECV_SYNCINFO_REQ: 收到syncinfo 请求事件,assistant层会自动处理(PAST流程)。

AUDIO_EVT_BASSC_RECV_SINK_STATE: 收到Sink状态,主要是周期性广播和BIS同步状态。该事件会有两种情况参数。

  • data为空指针,表示收到broadcast receive state为空数据。
  • data不为空指针,数据可以用结构体blc_bassc_recvSinkStateEvt_t解析。

AUDIO_EVT_BASSC_BROADCAST_CODE_REQ: sink正在同步的source是加密的,会有这个事件上报。user收到这个事件后,需要通过blc_bassc_writeSetBroadcastCode/blc_bapba_writeSetBroadcastCode发送broadcast code给sink。

AUDIO_EVT_BASSC_BAD_BROADCAST_CODE: 设置给sink的broadcast code错误,sink返回错误的broadcast code事件。只能通过移除source操作后,重新设置源信息。

(a) blc_bassc_recvSinkStateEvt_t结构体参数说明

typedef struct{ //Event ID: AUDIO_EVT_RECV_SINK_STATE
    u8 sourceID;
    bool paState;
    u8 numSubgroups;
    u32 bisSyncState;
    u8 metadataLen;
    u8* metadata;
} blc_bassc_recvSinkStateEvt_t;

sourceID: 源ID号。

paState: 周期性广播同步状态。true表示正在同步;false表示同步失败/未同步。

numSubgroups: BIG的数量,通常为1。不为1时,表示bisSyncState/metadataLen/metadata有多份,可以继续查询。

bisSyncState: BIS同步状态。BIT(n)=1表示,BIS_index(n+1)正在同步。

metadataLen: 元数据长度。

metadata: 元数据指向的指针位置。

(b) blc_bassc_bcstCodeReq_t结构体参数说明

typedef struct{ //Event ID: AUDIO_EVT_BASSC_BROADCAST_CODE_REQ
    u8 sourceID;
} blc_bassc_bcstCodeReq_t;

sourceID: 源ID号。

(c) blc_bassc_badBroadcastCodeEvt_t结构体参数说明

typedef struct{ //Event ID: AUDIO_EVT_BASSC_BAD_BROADCAST_CODE
    u8 sourceID;
    bool paState;
    u16 broadcastCode[16];
} blc_bassc_badBroadcastCodeEvt_t;

sourceID: 源ID号。

paState: 周期性广播同步状态。true表示正在同步;false表示同步失败/未同步。

broadcastCode: 错误的broadcast code。

(2) BASSS Event介绍

TBD

(3) BASSC API: blc_audio_registerBASSControlClient

该 API 用来注册 BASS Client 模块,只有 GAP Central 角色下生效。

void blc_audio_registerBASSControlClient(const blc_bassc_regParam_t *param);

输入: param: 未使用,传入空指针即可。

注意:

BASSC 相关 API 会封装更高一层 API 供用户使用 (参考Broadcast Assistant API 介绍章节),一般不建议用户单独调用。

(4) BASSS API: blc_audio_registerBASSControServer

该 API 用来注册 BASS Server 模块,只有 GAP Peripheral 角色下生效。

void blc_audio_registerBASSControServer(const blc_basss_regParam_t *param);

输入: param: 未使用,传入空指针即可。

注意:

BASSS 相关 API 会封装更高一层 API 供用户使用 (参考Broadcast Sink API介绍章节),一般不建议用户单独调用。

BAP角色和服务

BAP 角色和服务支持要求如下:

BAP 角色和服务支持要求

在 telink_b91m_ble_audio_sdk 的 profile BAP 层实现如下五个角色,分别是 Unicast Client、 Unicast Server、 Broadcast Source、 Broadcast Assistant、 Broadcast Sink (和 Scan Delegator 共置)。

BAP 角色*

注意:

目前 SDK 将 Broadcast Source 的实现直接放在了应用层中,用户可以参考 vendor 目录下的音频 Demo 实现 (SDK/vendor/audio_broadcast_source) 去开发自己的广播音频项目。

备注:* BAP Broadcast Sink 和 Scan Delegator 是共置的,SDK 将其作为 BAP Broadcast Sink 角色。

(1) Unicast Client Event

//BAP Unicast Client Event ID
typedef enum{
    AUDIO_EVT_BAPUC_START = AUDIO_EVT_TYPE_BAPUC,
    AUDIO_EVT_BAPUC_SET_CIG_PARAMS, 
    AUDIO_EVT_BAPUC_CODEC_CONFIGURED, 
    AUDIO_EVT_BAPUC_QOS_CONFIGURED,    
    AUDIO_EVT_BAPUC_ENABLING,           
    AUDIO_EVT_BAPUC_DISABLING,          
    AUDIO_EVT_BAPUC_UPDATE_METADATA,    
    AUDIO_EVT_BAPUC_RELEASING,          
    AUDIO_EVT_BAPUC_SEND_STREAMING,     
    AUDIO_EVT_BAPUC_RECEIVE_STREAMING,  
} audio_bapuc_evt_enum;

(a) AUDIO_EVT_BAPUC_SET_CIG_PARAMS

CIG参数设置回调,无附加参数信息。定制CIG参数场景,可以通过该事件设置CIG参数。

(b) AUDIO_EVT_BAPUC_CODEC_CONFIGURED

Codec Configured事件回调。Server端进入Codec Configured状态后会将自己偏好的QoS参数发给Client,Client收到后通过该事件上报给APP层,由APP层结合自身偏好决定最终的QoS参数。事件附带的参数信息结构体:

typedef struct{ //Event ID: AUDIO_EVT_BAPUC_CODEC_CONFIGURED
    u16 aclHandle;
    audio_dir_enum  aseDir;
    u8  aseID;
    u8  framing;
    u8  PreferredRetransmitNum;
    u16 maxTransportLatency;
}blc_bapuc_codecConfiguredEvt_t;

参数信息:

aclHandle: Client端ACL连接句柄

aseDir: Server端ASE方向,sink或source,具体定义参考枚举’audio_dir_enum’

aseID: Server端ASE ID

framing: Server端偏好的音频格式,framed或unframed,具体定义参考枚举'blc_audio_framingType_e'

PreferredRetransmitNum: Server端偏好的重传次数

maxTransportLatency: Server端最大传输延时,单位毫秒

注意:

Client端在Service Discovery过程中通过查询Server端的PAC来获得Server支持的音频能力。

(c) AUDIO_EVT_BAPUC_QOS_CONFIGURED

QoS Configured事件回调。Server端收到Client发送的Config QoS指令后,确定最终的QoS参数,然后通知Client端,Client端收到通知后,将参数通过该事件上报给APP层。事件附带的参数信息结构体:

typedef struct{ //Event ID: AUDIO_EVT_BAPUC_QOS_CONFIGURED
    u16 aclHandle;
    audio_dir_enum  aseDir;
    u8  aseID;
    u8  framing;
    u8  PHY;
    u8  retransNum;
    u16 maxSdu;
    u16 maxTransLatency;
    u32 sduInterval;
    u32 presentationDelay;
}blc_bapuc_qosConfiguredEvt_t;

参数信息:

aclHandle: Client端ACL连接句柄。

aseDir: Server端ASE方向,sink或source,具体定义参考枚举’audio_dir_enum’。

aseID: Server端ASE ID。

framing: Server与Client协商确定的最终音频格式,framed或unframed,具体定义参考枚举'blc_audio_framingType_e'。

PHY: Server与Client确定的连接PHY,1M或2M或coded,位有效,具体参考枚举'blc_audio_phyFlag_e'。

retransNum: Server和Client协商确定的重传次数。

maxSdu: Server和Client协商确定的SDU最⼤⻓度。

maxTransLatency: Server与Client协商确定的最大传输延时,单位毫秒。

presentationDelay: server端演示延时,单位微妙。

(d) AUDIO_EVT_BAPUC_ENABLING

Enabling事件回调,Server处于Enabling状态时,会通知Client端。Client端收到Server的通知后,通过该事件上报给应用层。事件附带的参数信息结构体:

typedef struct{ //Event ID: AUDIO_EVT_BAPUC_ENABLING
    u16 aclHandle;
    audio_dir_enum  aseDir;
    u8  aseID;
    blc_audio_metadata_parsed_t metaParam;
} blc_bapuc_enablingEvt_t;

参数信息:

aclHandle:Client端ACL连接句柄。

aseDir:Server端ASE方向,sink或source,具体定义参考枚举’audio_dir_enum’。

aseID:ASE ID。

metaParam:应用于该ASE的metadata信息。

(e) AUDIO_EVT_BAPUC_DISABLING

Disabling事件回调。只有Source ASE有Disabling状态,Server处于Disabling状态时会通知Client,Client收到通知后,上报给APP层。Client端APP层收到该事件回调时,只停止接收数据而不释放音频资源。事件附带的参数信息结构体:

typedef struct{ //Event ID: AUDIO_EVT_BAPUC_DISABLING 
    u16 aclHandle;
    audio_dir_enum  aseDir;
    u8  aseID;
} blc_bapuc_disablingEvt_t;

参数信息:

aclHandle: Client端ACL连接句柄

aseDir: Server端ASE方向,sink或source,具体定义参考枚举’audio_dir_enum’

aseID: ASE ID

(f) AUDIO_EVT_BAPUC_UPDATE_METADATA

Update事件回调。ASE处于Enabling或者Streaming状态时,client和server可以更新metadata。metadata更新时,client需要通知APP层。事件附带的参数信息结构体:

typedef struct{ //Event ID: AUDIO_EVT_BAPUC_UPDATE_METADATA
    u16 aclHandle;
    audio_dir_enum  aseDir;
    u8  aseID;
    blc_audio_metadata_parsed_t metaParam;
} blc_bapuc_updateMetadataEvt_t;

参数信息:

aclHandle: Client端ACL连接句柄

aseDir: Server端ASE方向,sink或source,具体定义参考枚举’audio_dir_enum’

aseID: ASE ID

metaParam: 更新后的metadata

(g) AUDIO_EVT_BAPUC_RELEASING

Releasing事件回调。除Idle状态外,ASE处于任何状态都可以由Server或者Client执行Release操作。Server处于Releasing状态时,需要通知Client,Client收到通知后,上报给APP层。APP层收到通知后,Release所有与该ASE相关的音频资源。事件附带的参数信息结构体:

typedef struct{ //Event ID: AUDIO_EVT_BAPUC_RELEASING
    u16 aclHandle;
    audio_dir_enum  aseDir;
    u8  aseID;
}blc_bapuc_releasingEvt_t;

参数信息:

aclHandle: Client端ACL连接句柄

aseDir: Server端ASE方向,sink或source,具体定义参考枚举’audio_dir_enum’

aseID: ASE ID

(h) AUDIO_EVT_BAPUC_SEND_STREAMING

Streaming事件回调。当Server端的Sink ASE处于streaming状态时,会通知Client。此时Server是音频流的接收端,Client是音频流的发送端。Client收到通知后,会上报给APP层。APP层收到该事件后,可以开始发送数据。事件附带的参数信息结构体:

typedef struct{ //Event ID: AUDIO_EVT_BAPUC_SEND_STREAMING  
    u16 aclHandle;
    u8  aseID;
} blc_bapuc_sendStreamingEvt_t;

参数信息:

aclHandle: Client端ACL连接句柄

aseID: ASE ID

(i)AUDIO_EVT_BAPUC_RECEIVE_STREAMING

Streaming事件回调。当Server的Source ASE处于streaming状态时,会通知Client。此时Server是音频的发送端,Client是音频的接收端。Client收到通知后,会上报给APP层。APP层收到该事件后,可以开始接收数据。事件附带的参数信息结构体:

typedef struct{ //Event ID:AUDIO_EVT_BAPUC_RECEIVE_STREAMING
    u16 aclHandle;
    u8  aseID;
} blc_bapuc_receiveStreamingEvt_t;

参数信息:

aclHandle: Client端ACL连接句柄

aseID: ASE ID

(2) Unicast Server Event

Client会根据Server的音频能力和偏好配置双方都接受的音频通路。音频通路配置流程启动后,用户需要根据Profile层的事件回调,执行APP层相关操作,例如配置codec,配置lc3等。

//BAP Unicast Server Event ID
typedef enum{
    AUDIO_EVT_BAPUS_START = AUDIO_EVT_TYPE_BAPUS,
    AUDIO_EVT_BAPUS_CODEC_CONFIGURED,   
    AUDIO_EVT_BAPUS_QOS_CONFIGURED,   
    AUDIO_EVT_BAPUS_ENABLING,           
    AUDIO_EVT_BAPUS_UPDATE_METADATA,            
    AUDIO_EVT_BAPUS_RELEASING,          
    AUDIO_EVT_BAPUS_DISABLING,           
    AUDIO_EVT_BAPUS_RECEIVE_STREAMING,
    AUDIO_EVT_BAPUS_SEND_STREAMING,
} audio_bapus_evt_enum;

(a) AUDIO_EVT_BAPUS_CODEC_CONFIGURED

Codec Configured事件回调。Client端根据Server端在PAC里面暴露的音频能力,选择一组codec参数下发给Server。Server收到校验完成后将codec参数整理上报给APP层。APP层根据该事件上报的codec参数,配置本地的硬件codec以及lc3编解码器。事件附带的参数信息结构体:

typedef struct{ //Event ID: AUDIO_EVT_BAPUS_CODEC_CONFIGURED
    audio_dir_enum dir;
    u8  audioEpId;
    blc_audio_codec_id_t  codecid;
    u8  frequency;
    u8  duration;
    u16 frameOcts;
    u32 location;
    u8  codecfrmBlksPerSDU;
} blc_bapus_codecConfiguredEvt_t;

参数信息:

dir: ASE方向,sink或source,具体定义参考枚举'audio_dir_enum'

audioEpId: endpoint ID

codecid: 包含codec ID(codec种类,例如lc3,sbc),company ID,vender ID,具体定义参考'blc_audio_codec_id_t'

frequency: 音频参数-采样率,一般可以是16kHz, 24kkHz, 32kkHz, 48kkHz,值有效,具体可参考枚举'blc_audio_freqCfg_e'

duration: 音频参数-帧时间,一般为7.5ms或10ms,值有效,具体可参考枚举'blc_audio_duraCfg_e'

frameOcts: 音频参数-帧长,byte为单位

location: 音频参数-位置,left,right或其他,位有效,具体可参考枚举'blc_audio_locationFlag_e'

codecfrmBlksPerSDU: 每个sdu中包含的blocks,一般在多路复用场景中使用(一个SDU包含多个channel的音频数据)

(b) AUDIO_EVT_BAPUS_QOS_CONFIGURED

QoS Configured事件回调。Server端收到Configue Codec指令后,将自己偏好的QoS信息发送给Client,Client收到信息后,结合自身需求确定最终的QoS参数发送给Server。Server收到后整理上报给APP层。事件附带的参数信息结构体:

typedef struct{ //Event ID: AUDIO_EVT_BAPUS_QOS_CONFIGURED
    audio_dir_enum dir;
    u8  audioEpId;
    u8  cigID;
    u8  cisID;
    u8  framing;
    u8  PHY;
    u8  retransNum;
    u16 maxSdu;
    u16 maxTransLatency;
    u32 sduInterval;
    u32 presentationDelay;
} blc_bapus_qosConfiguredEvt_t;

参数信息:

dir: ASE方向,sink或source,具体定义参考枚举'audio_dir_enum'

audioEpId: endpoint ID

cigID: ASE关联的CIG标识

cisID: ASE关联的CIS标识,CIG ID和CIS ID唯一确定了该ASE关联的同步音频流CIS。

framing: 音频格式,framed(0x00)或unframed(0x01),具体参考枚举'blc_audio_framingType_e'

PHY: 1M,2M或coded。位有效,具体参考枚举'blc_audio_phyFlag_e'

retransNum: Client和Server协商确定的重传次数

maxSdu: Client和Server协商确定的SDU最大长度

maxTransLatency: Client和Server协商确定的最大传输延时,单位毫秒

sduInterval: SDU产生间隔,单位微秒

presentationDelay: 演示延迟。单位微秒。

注意:

QoS参数具体含义和用途请参考《Core_V5.4》, Vol 4 Part E 7.8.97 LE Set CIG Parameters command。

(c) AUDIO_EVT_BAPUS_ENABLING

Enabling事件回调。Client与Server之间的Codec参数和QoS参数完全确定之后,Client端可以使能Server端的ASE,并在此过程中确保CIS建立完成。Client端将应用于该ASE的metadata通过Enable事件发送给Server。Server收到后整理上报给APP层。事件附带的参数信息结构体:

typedef struct{ //Event ID: AUDIO_EVT_BAPUS_ENABLING 
    audio_dir_enum dir;
    u8  audioEpId;
    u8  metaLen;
    u8  meta[255];
} blc_bapus_enablingEvt_t;

参数信息:

dir: ASE方向,sink或source,具体定义参考枚举'audio_dir_enum'

audioEpId: endpoint ID

metaLen: 应用于ASE的metadata长度

meta: 应用于ASE的metadata,metadata的具体内容和格式请参考元数据LTV结构

(d) AUDIO_EVT_BAPUS_UPDATE_METADATA

Update事件回调。当Server处于Enabling或Streaming状态时,可以由Server自身或Client更新metadata。当Client更新metadata时,Server会通过该事件将更新的metadata上报给APP层。事件附带的参数信息结构体:

typedef struct{ //Event ID: AUDIO_EVT_BAPUS_UPDATE_METADATA
    audio_dir_enum dir;
    u8  audioEpId;
    u8  metaLen;
    u8  meta[255];
}blc_bapus_updateMetadataEvt_t;

参数信息:

dir:ASE方向,sink或source,具体定义参考枚举'audio_dir_enum'。

audioEpId:endpoint ID。

metaLen:应用于ASE的metadata长度。

meta:应用于ASE的metadata,metadata的具体内容和格式请参考元数据LTV结构

(e) AUDIO_EVT_BAPUS_RELEASING

Releasing事件回调,除IDLE状态外,ASE处于任何状态都可以由Server自己或Client执行Release操作。Release操作执行成功后,ASE将处于Releasing状态,此状态下,Server应该释放该ASE相关的所有音频资源。事件附带的参数信息结构体:

typedef struct{ //Event ID: AUDIO_EVT_BAPUS_RELEASING
    audio_dir_enum dir;
    u8  audioEpId;
}blc_bapus_releasingEvt_t;

参数信息:

dir:ASE方向,sink或source,具体定义参考枚举'audio_dir_enum'。

audioEpId:endpoint ID

(f) AUDIO_EVT_BAPUS_DISABLING

Disabling事件回调。当Source ASE处于Enabling或Streaming状态时,可以由Client或Server执行Disable操作,Disable操作执行成功后,Source ASE将处于Disabling状态,需要注意:Source ASE处于Disabling状态时,Server不应该停止发送音频数据。Client下发Receive Stop Ready指令后,Server切换Disabling状态到QoS Configured状态 ,音频发送停止。事件附带的参数信息结构体:

typedef struct{ //Event ID: AUDIO_EVT_BAPUS_DISABLING
    audio_dir_enum dir;
    u8  audioEpId;
}blc_bapus_disablingEvt_t;

参数信息:

dir:ASE方向,sink或source,具体定义参考枚举'audio_dir_enum'。

audioEpId:endpoint ID

(g) AUDIO_EVT_BAPUS_RECEIVE_STREAMING

Streaming事件回调。当Server端Sink ASE处于Streaming状态时,会通知APP层。此时Server是音频的接受端,Client是音频的发送端。APP层收到该事件后,可以开始接收数据。事件附带的参数信息结构体:

typedef struct{ //Event ID: AUDIO_EVT_BAPUS_RECEIVE_STREAMING
    audio_dir_enum dir;
    u8  audioEpId;
}blc_bapus_receiveStreamingEvt_t;

参数信息:

dir:ASE方向,sink或source,具体定义参考枚举'audio_dir_enum'。

audioEpId:endpoint ID

(h) AUDIO_EVT_BAPUS_SEND_STREAMING

Streaming事件回调。当Server端Source ASE处于Streaming状态时,会通知APP层。此时Server是音频的发送端,Client是音频的接受端。APP层收到该事件后,可以开始接收数据。事件附带的参数信息结构体:

typedef struct{ //Event ID: AUDIO_EVT_BAPUS_SEND_STREAMING
    audio_dir_enum dir;
    u8  audioEpId;
}blc_bapus_sendStreamingEvt_t;

参数信息:

dir:ASE方向,sink或source,具体定义参考枚举'audio_dir_enum'。

audioEpId:endpoint ID。

(3) Broadcast Assistant Event

广播辅助器相关事件。

//BAP Broadcast Assistant Event ID
typedef enum{
    AUDIO_EVT_BAPBA_START = AUDIO_EVT_TYPE_BAPBA,
    AUDIO_EVT_BAPBA_FOUND_SINK, 
    AUDIO_EVT_BAPBA_START_SYNC_PA,
    AUDIO_EVT_BAPBA_FOUDN_SOURCE_INFO, 
    AUDIO_EVT_BAPBA_SOURCE_ENC_STATE,   
    AUDIO_EVT_BAPBA_PAST_STARTED_READY, 
} audio_bapba_evt_enum;

(a) AUDIO_EVT_BAPBA_FOUND_SINK

assistant发现sink设备, 事件附带的参数信息结构体 blc_bapba_foundSinkEvt_t

typedef struct{ //Event ID: AUDIO_EVT_BAPBA_FOUND_SINK
    u8 addrType;
    u8 address[6];
    u8 completeNameLen;
    u8 completeName[50];
} blc_bapba_foundSinkEvt_t;

addrType: sink的地址类型。00表示public address;01表示random address。

address: Sink的地址。小端模式。

completeNameLen: sink的设备名长度。

completeName: sink的设备名。

(b) AUDIO_EVT_BAPBA_START_SYNC_PA

assistant准备同步周期性广播的事件,在开始扫描source时会上报。该事件返回0表示对这个周期性广播进行同步,返回1表示不进行同步。同步成功后会上报AUDIO_EVT_BAPBA_FOUDN_SOURCE_INFO事件。事件附带的参数信息结构体 blc_bapba_startSyncPaEvt_t。

typedef struct{ //Event ID: AUDIO_EVT_BAPBA_START_SYNC_PA
    u8 sid;
    u8 addrType;
    u8 address[6];
    u8 broadcastId[3];
    u8 completeNameLen;
    u8 completeName[50];
    u8 broadcastNameLen;
    u8 broadcastName[50];
} blc_bapba_startSyncPaEvt_t;

sid: source广播的SID,参考扩展广播的内容。

addrType: source地址类型。00表示public address;01表示random address。

address: source地址。

broadcastId: broadcast ID

completeNameLen: source的设备名长度。

completeName: source的设备名。

broadcastNameLen: source广播名称长度。PBP层规定的ADType。

broadcastName: source广播名称。

(c) AUDIO_EVT_BAPBA_FOUDN_SOURCE_INFO

assistant成功发现source后,上报的事件。事件附带的参数信息结构体 blc_bapba_foundSourceInfoEvt_t。

typedef struct{ //Event ID: AUDIO_EVT_BAPBA_FOUDN_SOURCE_INFO
    u8 sid;
    u8 addrType;
    u8 address[6];
    u8 bisIndex;
    u32 presentationDelay;
    bisSyncInfo_t bisInfo[1];
} blc_bapba_foundSourceInfoEvt_t;

sid: source广播的SID,参考扩展广播的内容。

addrType: source地址类型。00表示public address;01表示random address。

address: source地址。

bisIndex: source信息的BIS index。

presentationDelay: 演示延迟。0xFFFFFFFF表示之前已经上报过该事件,只是source存在多组BIS,需要上报多次。

bisInfo: BIS的信息。包括编码器ID,编码参数以及元数据等。

(d) AUDIO_EVT_BAPBA_SOURCE_ENC_STATE

assistant成功发现source后,上报source是否加密的事件。

事件附带的参数信息结构体 blc_bapba_sourceEncStateEvt_t。

typedef struct{ //Event ID: AUDIO_EVT_BAPBA_SOURCE_ENC_STATE
    u8 enc;
} blc_bapba_sourceEncStateEvt_t;

enc: source加密的标记。1表示source是加密的;0表示source是不加密的。

(e) AUDIO_EVT_BAPBA_PAST_STARTED_READY

assistant关于PAST事件。目前未实现。

(4) Broadcast Sink Event

广播Sink事件。

//BAP Broadcast Sink Event ID
typedef enum{
    AUDIO_EVT_BAPBS_START = AUDIO_EVT_TYPE_BAPBS,
    AUDIO_EVT_BAPBS_REMOTE_SCAN_STOPPED,    
    AUDIO_EVT_BAPBS_REMOTE_SCAN_STARTED,    
    AUDIO_EVT_BAPBS_BIS_SINK_INIT_CODEC,    
    AUIOD_EVT_BAPBS_BIS_SINK_SYNC_BIG,      
    AUDIO_EVT_BAPBS_PDA_SYNC_STATE,         
} audio_bapbs_evt_enum;

(a) AUDIO_EVT_BAPBS_REMOTE_SCAN_STOPPED

sink收到remote scan stopped指令触发的事件,事件不携带数据。

(b) AUDIO_EVT_BAPBS_REMOTE_SCAN_STARTED

sink收到remote scan started指令触发的事件,事件不携带数据。

(c) AUDIO_EVT_BAPBS_BIS_SINK_INIT_CODEC

sink在准备和BIS进行同步时,上报的事件。主要通知user进行解码器初始化,audio模块初始化等操作。事件附带的参数信息结构体 blc_bapbs_bisSinkInitCodecEvt_t。

typedef struct{ //Event ID: AUDIO_EVT_BAPBS_BIS_SINK_INIT_CODEC
    u32 presentationDelay;
    u8 bisNum;
    bisSyncInfo_t bisInfo[0];
} blc_bapbs_bisSinkInitCodecEvt_t;

presentationDelay: 演示延迟。

bisNum: 准备同步的BIS数量。

bisInfo: BIS的信息。包括解码器ID,解码参数以及元数据等。

(d) AUIOD_EVT_BAPBS_BIS_SINK_SYNC_BIG

sink完成BIG同步后,上报的事件。事件附带的参数信息结构体 blc_bapbs_BisSinkSyncBigEvt_t。

typedef enum{
    BIG_SYNCED_FAILED,
    BIG_LOSTED,
    BIG_SYNCED,
} blc_audio_bigSyncState_enum;

typedef struct{ //Event ID: AUIOD_EVT_BAPBS_BIS_SINK_SYNC_BIG
    blc_audio_bigSyncState_enum state;
    u8 bigHandle;
    union{
        struct{
        u8 numBis;
        u16 isoInterval;
        u16 bisHandles[0];
        };
        u8 lostReason;
    };
} blc_bapbs_BisSinkSyncBigEvt_t;

state: BIG同步状态,主要是同步失败、同步丢失、同步成功三种状态。

bigHandle: 同步成功状态下,BIG句柄。

lostReason: 同步失败/同步丢失状态下,丢失的原因。

numBis: 同步的BIS数量。

isoInterval: ISO间隔。

bisHandles: 同步的BIS句柄,依赖于numBis。

(e) AUDIO_EVT_BAPBS_PDA_SYNC_STATE

sink完成周期性广播同步后,上报的事件。事件附带的参数信息结构体 blc_bapbs_pdaSyncStateEvt_t。

typedef enum{
    PDA_SYNCED,
    PDA_SYNCED_FAILED,
    PDA_LOSTED,
} blc_audio_pdaSyncState_enum;

typedef struct{ //Event ID: AUDIO_EVT_BAPBS_PDA_SYNC_STATE
    blc_audio_pdaSyncState_enum  state;
    u16 syncHandle;
    u8  advSID;
    u8  advAddrType;
    u8  advAddr[6];
    u8  advPHY;
    u16 perdAdvItvl;
    u8  advClkAccuracy;
} blc_bapbs_pdaSyncStateEvt_t;

state: 周期性广播同步状态,主要是同步失败、同步丢失、同步成功三种状态。

syncHandle: 周期性广播同步句柄。

advSID: 扩展广播SID。

advAddrType: 周期性广播地址类型。

advAddr: 周期性广播地址。

advPHY: 广播使用的PHY。

perdAdvItvl: 周期性广播间隔。

advClkAccuracy: 广播时钟精度。

注意:

BAP Broadcast Source 实现代码在应用层,所有相关事件都在应用层,参考 vendor/audio_broadcast_source 中的 app_controller_event_callback。

(5) Unicast Client API

(a) blc_audio_registerBapUnicastClient

void blc_audio_registerBapUnicastClient(const blc_bapuc_regParam_t *param);

输入参数'param'传入NULL即可(预留扩展用)。Unicast Client角色需要支持ASCS Client和PACS Client,调用该API相当于同时注册了ASCS Client和PACS Client。

(b) blc_bapuc_checkAudioConfigures

audio_error_enum blc_bapuc_checkAudioConfigures(u16 aclHandle, blc_audio_std_aud_cfg_enum audCfgIdx, blc_audio_ase_cfg_info_t *outChnInfo);

aclHandle: 输入参数 - ACL连接句柄

audCfgIdx: 输入参数 - Client端的偏好的音频场景配置,例如Sink ASE和Source ASE的数量,CIS的数量,音频流的数量等。具体定义参考枚举'std_unicast_aud_cfg_enum'

outChnInfo: 输出参数 - Client偏好的音频场景对应具体配置参数。参数如下:

typedef struct{
    //PAC record audio location
    u32 srcAudLoc;
    u32 sinkAudLoc;
    //Codec Configuration allocated location
    u32 sinkAudLocAlloc[2]; //max 2 channels
    u32 srcAudLocAlloc[2];  //max 2 channels

    u8 sinkCodecFrameBlksPerSDU;
    u8 srcCodecFrameBlksPerSDU;
    u8 sinkASEsPerSvr;
    u8 sinkASEId[2];//max 2 ASE
    u8 srcASEsPerSvr;
    u8 srcASEId[2];//max 2 ASE
} blc_audio_ase_cfg_info_t;
srcAudLoc: Server在Source PAC里面暴露支持的音频位置。

sinkAudLoc: Server在Sink PAC里面暴露支持的音频位置。

sinkAudLocAlloc[]: Sink Channel可使用的音频位置,最多两个(最大支持两个channel)。

srcAudLocAlloc[]: Source Channel可使用的音频位置,最多两个(最大支持两个channel)。

sinkASEsPerSvr: 每个Server使用的Sink ASE数量。

sinkASEId:可使用Sink ASE对应的ASE ID。

srcASEsPerSvr: 每个Server使用的Source ASE数量。

srcASEId:可使用Source ASE对应的ASE ID。

返回值: 具体定义参考'audio_error_enum'。

API使用说明: Client端根据Server在PAC里面暴露的音频能力以及Client端偏好的音频场景,判断Server的能力是否满足Client的要求,如果满足则输出可使用的音频场景对应的配置参数。

(c) blc_bapuc_sduPacketPush

audio_error_enum blc_bapuc_sduPacketPush(u16 aclHandle, u8 idx, u8 *pPkt, u16 pktLen);
aclHandle: 输入参数 - ACL连接句柄

idx: 输入参数 - 索引号,从 0 开始。 如果音频接收设备上有两个 Sink ASE, 它们的索引号对应 0 和 1,如果向第一个 Sink ASE 发送音频数据,那么 idx 为 0。索引值的引入是为了简化上层代码,免去复杂的ID判断流程。

pPkt: 输入参数 - 待发送 ISO SDU 音频数据的首地址。

pktLen: 输入参数 - 待发送 ISO SDU 音频数据的长度。

返回值: 具体定义参考'audio_error_enum'

API使用说明: Client端作为Source端,向Sink端(Server)发送同步音频数据。

(d) blc_bapuc_sduPacketPop

sdu_packet_t *blc_bapuc_sduPacketPop(u16 aclHandle,u8 idx);

aclHandle: 输入参数 - ACL连接句柄

idx: 输入参数 - 索引号,从 0 开始。 如果音频接收设备上有两个 Source ASE, 它们的索引号对应 0 和 1,如果从第一个 Source ASE 端接收音频数据,那么 idx 为 0。索引值的引入是为了简化上层代码,免去复杂的ID判断流程。

返回值: 具体定义参考下述'sdu_packet_t'

typedef struct{
    /*
     * Tx: receiving HCI PDU from host and then tx to rf pdu
     * Rx: rx RF PDU then packet to SDU buffer
     */
    u16 pkt_seq_num;  //rx: pkt_seq_num, Tx: pkt_seq_num
    u16 iso_sdu_len;  //rx: data len, Tx: data len

    u32 timestamp;     // rx: timestamp, Tx: timestamp ISO Test Mode in receive: mark payloadNum
    u16 sduOffset;     // rx: unused, Tx: mark offert of sdu data when hci ISO data copy to SDU buffer
    u8 numHciPkt;      // rx:unused, Tx: mark number of HciPkt in the SDU data

    u8 pkt_st       :2; //refer to "iso_ps_flag_t"
    u8 pb           :2;   
    u8 ts           :1;
    u8 numOfCmplt_en:1; 
    u8 rsvd         :2;

    u8 isoHandle;      // mark bisHandle OR cisHandle value
    u8 data[1];        // ISO_SDU_Fragment
}sdu_packet_t;

返回值为 NULL: 获取音频数据包失败;

其他值: 获取音频数据包成功,其中 pkt_st: ISO SDU 包状态标志枚举类型如下:

typedef enum{
    HCI_ISO_VALID_DATA =    0x00, //Valid data. The complete ISO_SDU was received correctly
    HCI_ISO_POSSIBLE_INVALID_DATA = 0x01, //Possibly invalid data
    HCI_ISO_LOST_DATA = 0x02, //Part(s) of the ISO_SDU were not received correctly. This is reported as "lost data"
} iso_ps_flag_t;

只有 pkt_st 值为 HCI_ISO_VALID_DATA, 才表示 ISO SDU数据是完整有效的。当收到无效 SDU 时,APP层需要使用PLC 丢包隐藏算法,一般代码中通过检查 iso_sdu_len 值是否合法来判断是否收到无效 ISO SDU。具体参考demo介绍。

API使用说明: Client端作为Sink端,从Source端(Server)接收同步音频数据。

(e) blc_bapuc_setASEOperationConfigCodec

audio_error_enum blc_bapuc_setASEOperationConfigCodec(u16 aclHandle, u8 aseID, blc_audio_std_codec_settings_enum codecCfgIdx, blc_audio_ase_cfg_info_t *pAseCfgInfo);

API使用说明: Client根据Server的音频能力,选择自己偏好的音频参数和音频场景下发给Server。请结合ASE状态机Unicast Client Event一同理解。

(f) blc_bapuc_setAseConfigCodec

audio_error_enum blc_bapuc_setAseConfigCodec(u16 aclHandle, u8 aseID, blc_audio_std_qos_settings_e qosCfgIdx);

API使用说明: Client根据Server的音频能力,确定最终的连接参数。请结合ASE状态机Unicast Client Event一同理解。

(g) blc_bapuc_setAseReceiverStartReady

audio_error_enum blc_bapuc_setAseReceiverStartReady(u16 aclHandle, u8 aseID);

API使用说明: Client作为Sink,Server作为Source,Client通知Server已准备好接收数据。只能对Server端的Source ASE操作。请结合ASE状态机Unicast Client Event一同理解。

(h) blc_bapuc_setAseDisable

audio_error_enum blc_bapuc_setAseDisable(u16 aclHandle, u8 aseID);

API使用说明: ASE Disable。请结合ASE状态机Unicast Client Event一同理解。

(i) blc_bapuc_setAseReceiverStopReady

audio_error_enum blc_bapuc_setAseReceiverStopReady(u16 aclHandle, u8 aseID);

API使用说明: Client作为Source,Server作为Sink,Client通知Server已准备好停止接收数据。只能对Server端的Source ASE操作。请结合ASE状态机Unicast Client Event一同理解。

(j) blc_bapuc_setASEMetadata

audio_error_enum blc_bapuc_setASEMetadata(u16 aclHandle, u8 aseID, u8 *pMetadata, u8 metadataLen);

API使用说明: 用于初始化设置该ASE相关的metadata

(k) blc_bapuc_setAseUpdateMetadata

audio_error_enum blc_bapuc_setAseUpdateMetadata(u16 aclHandle, u8 aseID, u8 *pMetadata, u8 metadataLen);

API使用说明: 更新metadata,Server端的ASE处于Enabling或Streaming时,Client可以使用该API更新metadata。请结合ASE状态机Unicast Client Event一同理解。

(l) blc_bapuc_setAseRelease

audio_error_enum blc_bapuc_setAseRelease(u16 aclHandle, u8 aseID);

API使用说明: ASE Release。释放所有该ASE相关的音频资源。请结合ASE状态机Unicast Client Event一同理解。

(6) Unicast Server API

(a) blc_audio_registerBapUnicastServer

void blc_audio_registerBapUnicastServer(const blc_bapus_regParam_t *param);

API使用说明: Unicast Server ⻆⾊需要⽀持 ASCS Server 和 PACS Server,调⽤该 API 相当于同时注册了 ASCS Server 和 PACS Server。

用户可以使用 SDK 提供的默认参数 defaultUnicastSvrParam (参考 audio/stream/pacp/pacs_server_buf.c) 注册 Unicast Server,例如:

blc_audio_registerBapUnicastServer(&defaultUnicastSvrParam); 

用户也可以定制参数。

(b) blc_bapus_aseConfigCodec

audio_error_enum blc_bapus_aseConfigCodec(u16 aclHandle,u8 epId);

API使用说明:Server端可以自己执行config codec操作。请结合ASE状态机Unicast Server Event一同理解。

(c) blc_bapus_aseDisable

audio_error_enum blc_bapus_aseDisable(u16 aclHandle,u8 epId);

API使用说明:Disable操作。请结合ASE状态机Unicast Server Event一同理解。

(d) blc_bapus_aseUpdateMetadata

audio_error_enum blc_bapus_aseUpdateMetadata(u16 aclHandle,u8 epId,u8 meta[],u8 metaLen);

API使用说明:metadata更新。请结合ASE状态机Unicast Server Event一同理解。

(e) blc_bapus_aseReceiverStartReady

audio_error_enum blc_bapus_aseReceiverStartReady(u16 aclHandle,u8 epId);

API使用说明:作为Sink端,如果Server准备好了接收数据,可以调用该API(只对Sink ASE有效),该API调用成功后,ASE自动切换到Streaming状态并通知Client,Client收到通知后将开始发送数据。请结合ASE状态机Unicast Server Event一同理解。

(f) blc_bapus_aseRelease

audio_error_enum blc_bapus_aseRelease(u16 aclHandle,u8 epId);

API使用说明:Release操作,执行该指令后,Server开始释放所有与该ASE相关的音频资源。请结合ASE状态机Unicast Server Event一同理解。

(g) blc_bapus_aseReleasedByCache

audio_error_enum blc_bapus_aseReleasedByCache(u16 aclHandle, u8 epId, bool cache);

API使用说明:Released操作,音频资源释放完成后,执行该指令,Server可以选择退回到IDLE或Codec Configured状态。请结合ASE状态机Unicast Server Event一同理解。

(h) blc_bapus_sduPacketPush

audio_error_enum blc_bapus_sduPacketPush(u16 aclHandle,u8 aseID, u8* pPkt, u16 pktLen);

aclHandle: 输入参数 - ACL连接句柄

idx: 输入参数 - 索引号,从 0 开始。 如果音频接收设备上有两个 Sink ASE, 它们的索引号对应 0 和 1,如果向第一个 Sink ASE 发送音频数据,那么 idx 为 0。索引值的引入是为了简化上层代码,免去复杂的ID判断流程。

pPkt: 输入参数 - 待发送 ISO SDU 音频数据的首地址。

pktLen: 输入参数 - 待发送 ISO SDU 音频数据的长度。

返回值: 具体定义参考'audio_error_enum'

API使用说明: Server端作为Source端,向Sink端(Client)发送同步音频数据。

(i) blc_bapus_sduPacketPop

sdu_packet_t* blc_bapus_sduPacketPop(u16 aclHandle,u8 aseID);
aclHandle: 输入参数 - ACL连接句柄

idx: 输入参数 - 索引号,从 0 开始。 如果音频接收设备上有两个 Source ASE, 它们的索引号对应 0 和 1,如果从第一个 Source ASE 端接收音频数据,那么 idx 为 0。索引值的引入是为了简化上层代码,免去复杂的ID判断流程。

返回值: 具体定义参考下述'sdu_packet_t'

API使用说明: Server端作为Sink端,从Source端(Client)接收同步音频数据。

(7) Broadcast Sink API

blc_audio_registerBapBroadcastSink

用于注册 BAP Broadcast Sink 功能的 API:

void blc_audio_registerBapBroadcastSink(const blc_bapbs_regParam_t *param);

输入参数参考PACS 和 BASS章节初始化对参数的定义。

另外用户调用该 API 等同于注册了 PACS Server 和 BASS Server, Profile 协议层接管了 BLE 的控制器和主机的蓝牙事件回调, 协议底层处理这部分逻辑,用户不需要关心。

(8) Broadcast Assistant API

(a) blc_audio_registerBroadcastAssistant

用于注册 BAP Broadcast Assistant 功能的 API:

void blc_audio_regBcstAssistant(const blc_bapba_regParam_t *param);

输入参数参考PACS 和 BASS章节初始化对参数的定义。

目前该 API 的传参没有用到 (预留扩展用), 输入 NULL 就可以了。另外用户调用该 API 等同于注册了 PACS Client 和 BASS Client, Profile 协议层接管了 BLE 的控制器和主机的蓝牙事件回调, 协议底层处理这部分逻辑,用户不需要关心。

(b) blc_bapba_writeRemoteScanStarted

user向特定的ACL连接,BASS层发送Remote Scan Started指令,并且BAP层开始扫描source。

void blc_bapba_writeRemoteScanStarted(u16 connHandle);

输入参数含义分别如下:

connHandle: ACL连接句柄;

(c) blc_bapba_writeRemoteScanStopped

user向特定的ACL连接,BASS层发送Remote Scan Stopped指令,并且BAP层停止扫描source。

void blc_bapba_writeRemoteScanStopped(u16 connHandle);

输入参数含义分别如下:

connHandle: ACL连接句柄;

(d) blc_bapba_writeAddSourceXXXX

user向特定的ACL连接,BASS层发送Add Source指令,BAP层将Add Source指令做了一些简化。只关注PA_Sync, BIS_Sync两个参数。BAP对不同的PA_sync参数,分装了三个接口,分别不同步周期广播,使用PAST进行周期性广播同步,不使用PAST进行周期性广播同步。标准的Add Source指令,可以通过blc_bassc_writeAddSource指令发送指令。

void blc_bapba_writeAddSourceNotSyncPA(u16 connHandle, blc_audio_source_head_t *head, u32 bisSync);
void blc_bapba_writeAddSourcePast(u16 connHandle, blc_audio_source_head_t *head, u32 bisSync);
void blc_bapba_writeAddSourceNoPast(u16 connHandle, blc_audio_source_head_t *head, u32 bisSync);

输入参数含义分别如下:

connHandle: ACL连接句柄;

head: 广播源头信息。包括地址类型,地址,广播集SID,Broadcast_ID等。

bisSync: BIS同步信息。0xFFFF表示不指定具体的同步信息。BIT(n)=1表示同步BIS_index=n+1参数。

(e) blc_bapba_writeModifySourceXXXX

user向特定的ACL连接,BASS层发送Modify Source指令,BAP层将Modify Source指令做了一些简化。只关注PA_Sync, BIS_Sync两个参数。BAP对不同的PA_sync参数,分装了三个接口,分别不同步周期广播,使用PAST进行周期性广播同步,不使用PAST进行周期性广播同步。标准的Modify Source指令,可以通过blc_bassc_writeModifySource指令发送指令。

void blc_bapba_writeModifySourceNotSyncPA(u16 connHandle, u8 sourceID, u32 bisSync);
void blc_bapba_writeModifySourcePast(u16 connHandle, u8 sourceID, u32 bisSync);
void blc_bapba_writeModifySourceNoPast(u16 connHandle, u8 sourceID, u32 bisSync);

输入参数含义分别如下:

connHandle: ACL连接句柄;

sourceID: 源ID。可以通过AUDIO_EVT_BASSC_RECV_SINK_STATE事件获取。

bisSync: BIS同步信息。0xFFFF表示不指定具体的同步信息。BIT(n)=1表示同步BIS_index=n+1参数。

(f) blc_bapba_writeSetBroadcastCode

user向特定的ACL连接,BASS层发送Set Broadcast Code指令。

void blc_bapba_writeSetBroadcastCode(u16 connHandle, u8 sourceID, u8 bcstCode[16]);

输入参数含义分别如下:

connHandle: ACL连接句柄;

sourceID: 源ID。可以通过AUDIO_EVT_BASSC_RECV_SINK_STATE事件获取。

bcstCode: 广播源的broadcast Code。加密的广播源才需要。收到AUDIO_EVT_BASSC_BROADCAST_CODE_REQ事件后,才能发送。

(g) blc_bapba_writeRemoveSource

user向特定的ACL连接,BASS层发送Remove Source指令。

void blc_bapba_writeRemoveSource(u16 connHandle, u8 sourceID);

输入参数含义分别如下:

connHandle: ACL连接句柄;

sourceID: 源ID。可以通过AUDIO_EVT_BASSC_RECV_SINK_STATE事件获取。

(9) Broadcast Source API

在介绍Broadcast Source API之前,首先介绍下基本音频公告。

基本音频公告的数据结构定义为广播音频源端点(Broadcast Audio Source Endpoint(BASE))结构,下面都简称BASE。BASE字段数据包含三个级别:

  • 级别1: 组级别,只能有一个BIG组。

  • 级别2:BIS 子组级别。一个BIG组中包含一个或多个BIS的集合。

  • 级别3: BIS 级别。

下图展示了一个典型的BASE结构数据的内容,下图引用于 BAP_v1.0.1, Figure 3.1: Example BASE logical structure

BASE逻辑结构示例

上图显示的了一共有4路 BIS (其中每2路 BIS 组成一个 BIS 子组) ,分属于1路BIG。

第一路 BIS 子组 (包含2路BIS) 采用的是 LC3_48_2 编解码参数,两路 BIS 分别传输左、右声道音频,音频的语言是西班牙语,音频是音乐类型;

第二路 BIS 子组 (包含2路BIS) 采用的是 LC3_48_2 编解码参数,两路 BIS 分别传输左、右声道音频,音频的语言是英语,音频是音乐类型;

更多的内容可以参考 BAP_v1.0.1, 3.7.2.2 Basic Audio Announcements

SDK针对BASE结构体,封装了两个API用来计算BASE数据长度和生成BASE数据。

注意:

SDK分支的API不能兼容多组BIG的情况下,每个BIG包含的BIS数量不一样的情况。这类特殊的场景,可以直接手动实现BASE数据的生成。

(a) BIG_BCST_NUMBER和BIS_NUM_IN_PER_BIG_BCST

BIG_BCST_NUMBER: 声明广播音频BIG的数量,默认是0。需要用户重新定义。

BIS_NUM_IN_PER_BIG_BCST: 声明广播音频每个BIG中BIS数量,默认是0.需要用户重新定义。

(b) blc_bap_calculateBASELength

该API用来计算BASE字段数据长度。

int blc_bap_calculateBASELength(void* base);

输入参数:

base: BASE字段结构体的指针,数据类型参考blc_bcstAudioAnnouncements_param_t。

返回值: 计算得到的BASE字段数据长度,包括AD Length和AD Type等。

(c)blc_bap_setBASEToAddress

该API用来生成BASE字段数据。

u8* blc_bap_setBASEToAddress(void* base, u8* dst);

输入参数:

base: BASE字段结构体的指针,数据类型参考blc_bcstAudioAnnouncements_param_t。

dst: BASE字段生成的数据需要存储的指针。

返回值: BASE数据最后的写指针位置。

(d)blc_bcstAudioAnnouncements_param_t

该数据结构主要是对BASE字段的描述。

//Broadcast Source
typedef struct{
    u8 BIS_index;
    blc_audio_codec_spec_cfg_param_t codecCfg;
}blc_BASE_BIS_param_t;

typedef struct{
    u8 BIS_num;
    blc_audio_codec_id_t  codecId; //Codec ID, 06 0000 0000 mean LC3 codec
    blc_audio_codec_spec_cfg_param_t codecCfg;
    blc_audio_metadata_param_t metadata;
    blc_BASE_BIS_param_t BIS_param[BIS_NUM_IN_PER_BIG_BCST];
} blc_BASE_BIG_param_t;

typedef struct{
    u32 presentation_delay; //Range:0x000000-0xFFFFFF  Units: us
    u8 subGroupNum;
    blc_BASE_BIG_param_t BIG_param[BIG_BCST_NUMBER];
} blc_bcstAudioAnnouncements_param_t;

presentation_delay: 演示延迟,数据是u24格式,单位us。

subGroupNum: BIG数量。

BIS_num: 每个BIG下BIS数量。

codecId: 编解码器ID。LC3是06 0000 0000,其他数据自行查询。

codecCfg: 编解码器配置参数。主要包括采样率,帧间隔,帧长度等信息。参考附录编码器特定配置LTV结构

metadata: 元数据内容。参考附录元数据LTV结构

BIS_index: BIS索引。范围1-32

编解码器配置参数可以使用下面的宏定义来初始化。

#define LC3_CFG_8_1                     ...
#define LC3_CFG_8_2                     ...
#define LC3_CFG_16_1                    ...
#define LC3_CFG_16_2                    ...
#define LC3_CFG_24_1                    ...
#define LC3_CFG_24_2                    ...
#define LC3_CFG_32_1                    ...
#define LC3_CFG_32_2                    ...
#define LC3_CFG_441_1                   ...
#define LC3_CFG_441_2                   ...
#define LC3_CFG_48_1                    ...
#define LC3_CFG_48_2                    ...
#define LC3_CFG_48_3                    ...
#define LC3_CFG_48_4                    ...
#define LC3_CFG_48_5                    ...
#define LC3_CFG_48_6                    ...

渲染和捕获控制 (Rendering and Capture Control)

渲染和捕获控制相关的配置文件和服务

渲染和捕获控制主要包括MICP和VCP两个规范,在两个规范下又包括MICS、VCS、VOCS、AICS四个服务规范文档。

服务 描述
MICS 麦克风输入控制服务。只针对麦克风输入的描述,只有静音控制,可以包含AICS服务
VCS 音量控制服务。对所有音频输出的音量控制描述,包含AICS服务和VOCS服务
VOCS 音量偏移服务。主要对特定位置音量的描述,控制音量大小。设置输出通道,输出描述
AICS 音频输入控制服务。主要对音频输入的描述,控制静音,输入增益,增益模式,设置输入的类型、状态,输入描述

MICP/MICS麦克风控制

一组定义麦克风控制的新规范。麦克风控制服务和配置文件 (MICS 和 MICP) 的存在是为了提供对麦克风的设备范围控制。MICS 可能是所有 LE 音频规范中最简单的服务,它包含一个特性,可为麦克风提供设备范围 (device wide) 的静音 (对应下图用例组合 1)。

MICS 典型 4 种组合用例

如果需要控制麦克风的增益,MICS 应该与 AICS 的实例结合使用 (对应上图用例组合 2)。

当存在多个麦克风时, AICS 和 MICS 的组合更有意义,因为 MICS 为麦克风提供了设备范围内的静音(对应上图用例组合 3)。

另外, 用例组合3 还可以作为音量控制方案的一部分 (对应上图用例组合 4),这展示了 LE 音频中音量和麦克风控制服务的灵活性。

(1) MICSC Event 介绍

//MICS Client Event ID
typedef enum{
    AUDIO_EVT_MICSC_START = AUDIO_EVT_TYPE_MICSC,
    AUDIO_EVT_MICSC_CHANGE_MUTE,    //refer to 'blc_micsc_muteChangeEvt_t'
} audio_micsc_evt_enum;

AUDIO_EVT_MICSC_CHANGE_MUTE

MICS客户端收到服务器上报麦克风静音状态后,上报的事件。事件附带的参数信息结构体 blc_micsc_muteChangeEvt_t。

typedef struct{ //Event ID: AUDIO_EVT_MICSC_CHANGE_MUTE
    u8 mute; //blc_mics_mute_value_enum
} blc_micsc_muteChangeEvt_t;

mute:麦克风静音状态。状态参考blc_mics_mute_value_enum枚举。

(2) MICSS Event介绍

//MICS Server Event ID
typedef enum{
    AUDIO_EVT_MICSS_START = AUDIO_EVT_TYPE_MICSS,
    AUDIO_EVT_MICSS_CHANGE_MUTE, 
} audio_micss_evt_enum;

AUDIO_EVT_MICSS_CHANGE_MUTE

MICS服务器修改麦克风静音状态后,上报的事件。事件附带的参数信息结构体 blc_micss_muteChangeEvt_t。

typedef struct{ //Event ID: AUDIO_EVT_MICSS_CHANGE_MUTE
    u8 mute; //blc_mics_mute_value_enum
} blc_micss_muteChangeEvt_t;

mute:麦克风静音状态。状态参考blc_mics_mute_value_enum枚举。

(3) MICSC API: blc_audio_registerMICSControlClient

用于注册麦克风控制客户端功能的API。

void blc_audio_registerMICSControlClient(const blc_micsc_regParam_t *param);

param:未使用,传入空指针即可。

(4) MICSS API: blc_audio_registerMICSControlServer

用于注册麦克风控制服务器功能的API。

void blc_audio_registerMICSControlServer(const blc_micss_regParam_t *param);

param: 需要初始化的麦克风状态。空指针时,默认配置为defaultMicpParam定义的参数。麦克风状态的定义如下。

typedef enum{
    MICS_MUTE_VALUE_NOT_MUTED = 0x00,
    MICS_MUTE_VALUE_MUTED,
    MICS_MUTE_VALUE_DISABLED,
    MICS_MUTE_VALUE_RFU,
} blc_mics_mute_value_enum;

const blc_micss_regParam_t defaultMicpParam =
{
    .mute = MICS_MUTE_VALUE_NOT_MUTED,
};

typedef struct{
    blc_mics_mute_value_enum mute;
} blc_micss_regParam_t;

VCP/VCS音量控制

一组定义音量控制和平衡的新规范。

(1) VCSC Event介绍

//VCS Client Event ID
typedef enum{
    AUDIO_EVT_VCSC_START = AUDIO_EVT_TYPE_VCSC,
    AUDIO_EVT_VCSC_CHANGED_VOLUME_STATE, 
} audio_vcsc_evt_enum;

AUDIO_EVT_VCSC_CHANGED_VOLUME_STATE

VCS服务器修改音量输出状态后,客户端上报的事件。

事件附带的参数信息结构体 blc_vcsc_volumeStateChangeEvt_t。

typedef struct{ // Event ID: AUDIO_EVT_VCSC_CHANGED_VOLUME_STATE
    u8   volumeSetting;
    bool mute;
} blc_vcsc_volumeStateChangeEvt_t;

volumeSetting: 音量状态。参数定义见下表。

数值 描述
0x00 最小值
0xFF 最大值

mute: 静音状态。参数定义见下表。

数值 描述
true 输出静音
false 输出取消静音

(2) VCSS Event介绍

//VCS Server Event ID
typedef enum{
    AUDIO_EVT_VCSS_START = AUDIO_EVT_TYPE_VCSS,
    AUDIO_EVT_VCSS_CHANGED_VOLUME_STATE, 
} audio_vcss_evt_enum;

AUDIO_EVT_VCSS_CHANGED_VOLUME_STATE

VCS客户端被服务端修改音量输出状态后,上报的事件。

事件附带的参数信息结构体 blc_vcss_volumeStateChangeEvt_t。

typedef struct{ // Event ID: AUDIO_EVT_VCSS_CHANGED_VOLUME_STATE
    u8   volumeSetting;
    bool mute;
} blc_vcss_volumeStateChangeEvt_t;

volumeSetting: 音量状态。参数定义见下表。

数值 描述
0x00 最小值
0xFF 最大值

mute: 静音状态。参数定义见下表。

数值 描述
true 输出静音
false 输出取消静音

(3) VCSC API: blc_audio_registerVCSControlClient

用于注册音量控制服务器功能的API。

void blc_audio_registerVCSControlClient(const blc_vcsc_regParam_t *param);

param: 未使用,传入空指针即可。

(4) VCSS API: blc_audio_registerVCSControlServer

用于注册音量控制客户端功能的API。

void blc_audio_registerVCSControlServer(const blc_vcss_regParam_t *param);

param: 需要初始化的VCP的参数。sdk设计时,AICS可能被MICS和VCS同时包含的情况,AICS的参数初始化在VCS初始化时完成。并且SDK默认的AICS不能只被MICS包含,不被VCS包括,AICS的控制只能全部走VCS。

blc_vcss_regParam_t结构体参数以及说明,结构体主要包含了VCS参数,AICS参数,VOCS参数,下面依次介绍。

  • VCS参数

blc_vcsc_regParam_t结构体以及说明。

typedef struct{
    u8 step;        //Volume Setting Change Step;1-255

    /* Volume State */
    u8 volume;      //Volume Setting
    bool mute;      //mute

} blc_vcsc_regParam_t;

step: 音量控制的步进。这是由于VCS规范中有音量加、音量减这类定性操作,profile在执行这类操作时,需要有一个步进来控制。

volume: 输出音量值。范围0-255.

mute: 输出静音。

  • AICS参数

blc_aicss_regParam_t结构体以及说明。

typedef struct{
    /* Audio Input State */
    s8 gainSetting;         // Gain_Setting range(-128 to 127)
    u8 mute;        // mute blc_aics_mute_value_enum
    u8 gainMode;    //blc_aics_gain_mode_value_enum

    /* Gain Setting Properties */
    u8 units;       //Gain Setting Units, 0.1dB
    s8 minGain;     //Gain Setting Minimum >= -128
    s8 maxGain;     //Gain Setting Maximum <= 127, -128<=minGain<maxGain<=127

    /* Audio Input Type */
    u8 inputType;   //blc_aics_audio_input_type_def_enum

    /* Audio Input Status */
    u8 inputStatus; //blc_aics_audio_input_status_enum

    /* Audio Input Description */
    char *desc;
} blc_aicss_regParam_t;

gainSetting: 输入增益。范围[-128, 127]。

mute: 输入静音。参考如下定义。

typedef enum{
    AICS_MUTE_VALUE_NOT_MUTED = 0x00,
    AICS_MUTE_VALUE_MUTED,
    AICS_MUTE_VALUE_DISABLED,
    AICS_MUTE_VALUE_RFU,
} blc_aics_mute_value_enum;

gainMode: 输入增益模式。参考如下定义。

typedef enum{
    AICS_GAIN_MODE_VALUE_MANUAL_ONLY    = 0x00,
    AICS_GAIN_MODE_VALUE_AUTOMATIC_ONLY,
    AICS_GAIN_MODE_VALUE_MANUAL,
    AICS_GAIN_MODE_VALUE_AUTOMATIC,
} blc_aics_gain_mode_value_enum;

units: 输入增益步进,单位0.1dB。

minGainmaxGain: 输入增益最大最小值。

inputType: 输入类型。参考附录音频输入类型定义,SDK中定义如下。

typedef enum{
    AICS_INPUT_TYPE_UNSPECIFIED = 0x00, //Unspecified Input
    AICS_INPUT_TYPE_BLUETOOTH,          //Bluetooth Audio Stream
    AICS_INPUT_TYPE_MICROPHONE,         //Microphone
    AICS_INPUT_TYPE_ANALOG,             //Analog Interface
    AICS_INPUT_TYPE_DIGITAL,            //Digital Interface
    AICS_INPUT_TYPE_RADIO,              //AM/FM/XM/etc
    AICS_INPUT_TYPE_STREAMING,          //Streaming Audio Source
    AICS_INPUT_TYPE_RFU,
} blc_aics_audio_input_type_def_enum;

inputStatus: 输入状态。参考如下定义。

typedef enum{
    AICS_INPUT_STATUS_INACTIVE = 0x00,
    AICS_INPUT_STATUS_ACTIVE,
    AICS_INPUT_STATUS_RFU,
} blc_aics_audio_input_status_enum;

desc: 输入描述,通常是utf-8字符串。

  • VOCS参数

blc_vocss_regParam_t结构体以及含义。

typedef struct{
    /* Volume Offset State */
    s16 volumeOffset;   //-255 to 255 MIN_VOLUME_OFFSET

    /* Audio Location */
    u32 location;   //BLC_AUDIO_CHANNEL_ALLOCATION_RFU

    /* Audio Output Description */
    char *desc;

} blc_vocss_regParam_t;

volumeOffset: 输出音量偏移值。范围是[-255, 255]。

location: 音频输出位置。参考附录音频位置定义

desc: 输出描述,通常是utf-8字符串。

VOCS音量偏移控制

一个设备可以有多个音量偏移控制服务,主要用来实例化音频输出(如扬声器)的偏移电平和位置。VOCS通常被VCS给包含。

VOCSS可以通过宏定义APP_AUDIO_VCS_INCLUDE_VOCS_INSTANCE_NUM来修改设备支持的最大VOCS数量。

VOCSC可以通过宏定义APP_AUDIO_VCS_CLIENT_INCLUDE_VOCS_INSTANCE_NUM来修改查询对端设备的最大VOCS数量。

(1) VOCSC Event介绍

//VOCS Client Event ID
typedef enum{
    AUDIO_EVT_VOCSC_START = AUDIO_EVT_TYPE_VOCSC,
    AUDIO_EVT_VOCSC_CHANGED_VOLUME_OFFSET, 
    AUDIO_EVT_VOCSC_CHANGED_LOCATION, 
    AUDIO_EVT_VOCSC_CHANGED_OUTPUT_DESCROPTION,
} audio_vocsc_evt_enum;

(a) AUDIO_EVT_VOCSC_CHANGED_VOLUME_OFFSET

VOCS客户端收到服务器上报音量偏移状态后,上报的事件。

事件附带的参数信息结构体 blc_vocsc_volumeOffsetStateChangeEvt_t。

typedef struct{ //Event ID: AUDIO_EVT_VOCSC_CHANGED_VOLUME_OFFSET
    u8 vocsIndex;
    s16 volumeOffset;
} blc_vocsc_volumeOffsetStateChangeEvt_t;

vocsIndex: Vocs被VCP包含的索引号。

volumeOffset: 音量偏移状态值。范围是[-255, 255]。

(b) AUDIO_EVT_VOCSC_CHANGED_LOCATION

VOCS客户端收到服务器上报音频位置后,上报的事件。

事件附带的参数信息结构体 blc_vocsc_locationChangeEvt_t。

typedef struct{ //Event ID: AUDIO_EVT_VOCSC_CHANGED_LOCATION
    u8 vocsIndex;
    u32 location;
} blc_vocsc_locationChangeEvt_t;

vocsIndex: Vocs被VCP包含的索引号。

location: 音频输出位置。参数定义参数附录音频位置定义

(c) AUDIO_EVT_VOCSC_CHANGED_OUTPUT_DESCROPTION

VOCS客户端收到服务器上报音频描述后,上报的事件。

事件附带的参数信息结构体 blc_vocsc_outputDescChangeEvt_t。

typedef struct{ //Event ID: AUDIO_EVT_VOCSC_CHANGED_OUTPUT_DESCROPTION
    u8 vocsIndex;
    u16 descLen;
    u8* desc;
} blc_vocsc_outputDescChangeEvt_t;

vocsIndex: Vocs被VCP包含的索引号。

descLen: 音频输出描述长度。

desc: 音频输出描述。通常是utf-8格式。

(2) VOCSS Event介绍

//VOCS Server Event ID
typedef enum{
    AUDIO_EVT_VOCSS_START = AUDIO_EVT_TYPE_VOCSS,
    AUDIO_EVT_VOCSS_CHANGED_VOLUME_OFFSET, 
    AUDIO_EVT_VOCSS_CHANGED_LOCATION,
    AUDIO_EVT_VOCSS_CHANGED_OUTPUT_DESCROPTION,
} audio_vocss_evt_enum;

(a) AUDIO_EVT_VOCSS_CHANGED_VOLUME_OFFSET

VOCS服务器收到客户端修改音量偏移状态后,上报的事件。

事件附带的参数信息结构体 blc_vocss_volumeOffsetStateChangeEvt_t。

typedef struct{ //Event ID: AUDIO_EVT_VOCSS_CHANGED_VOLUME_OFFSET
    u8 vocsIndex;
    s16 volumeOffset;
} blc_vocss_volumeOffsetStateChangeEvt_t;

vocsIndex: Vocs被VCP包含的索引号。

volumeOffset: 音量偏移状态值。范围是[-255, 255]。

(b) AUDIO_EVT_VOCSS_CHANGED_LOCATION

VOCS服务器收到客户端修改音频位置后,上报的事件。

事件附带的参数信息结构体 blc_vocss_locationChangeEvt_t。

typedef struct{ //Event ID: AUDIO_EVT_VOCSS_CHANGED_LOCATION
    u8 vocsIndex;
    u32 location;
} blc_vocss_locationChangeEvt_t;

vocsIndex: Vocs被VCP包含的索引号。

location: 音频输出位置。参数定义参数附录音频位置定义

(c) AUDIO_EVT_VOCSS_CHANGED_OUTPUT_DESCROPTION

VOCS服务器收到客户端修改音频输出描述后,上报的事件。

事件附带的参数信息结构体 blc_vocss_outputDescChangeEvt_t。

typedef struct{ //Event ID: AUDIO_EVT_VOCSS_CHANGED_OUTPUT_DESCROPTION
    u8 vocsIndex;
    u16 descLen;
    u8* desc;
} blc_vocss_outputDescChangeEvt_t;

vocsIndex: Vocs被VCP包含的索引号。

descLen: 音频输出描述长度。

desc: 音频输出描述。通常是utf-8格式。

AICS:音频输入控制

一个设备可以有多个音频输入控制服务,主要用来实例化音频输入(如蓝牙音频流、麦克风等)的设置。多个音频输入可以成为音频混合功能的一部分。AICS通常被VCS或者MICS给包含,被MICS包含的AICS的音频输入类型只能是麦克风。SDK在server部分做了一定的简化,认为AICS被MICS包含的同时,肯定被VCS包含,没有考虑只有麦克风输入无音频输出的设备。client端是完整的服务,AICS能单独被MICS或VCS包含,也能同时被MICS和VCS包含的情况。

macro definition role description
APP_AUDIO_VCS_INCLUDE_AICS_INSTANCE_NUM vcs server vcs include aics number, less than 4
APP_AUDIO_MICS_INCLUDE_AICS_INSTANCE_NUM mics server mics include aics number, less than vcs number
APP_AUDIO_VCS_CLIENT_INCLUDE_AICS_INSTANCE_NUM vcs client remote device vcs include aics number
APP_AUDIO_MICS_CLIENT_INCLUDE_AICS_INSTANCE_NUM mics client remote device mics include aics number

(1) AICSC Event介绍

//AICS Client Event ID
typedef enum{
    AUDIO_EVT_AICSC_START = AUDIO_EVT_TYPE_AICSC,
    AUDIO_EVT_AICSC_CHANGED_INPUT_STATE,    
} audio_aicsc_evt_enum;

AUDIO_EVT_AICSC_CHANGED_INPUT_STATE

AICS客户端收到服务器上报音频输入状态后,上报的事件。

事件附带的参数信息结构体blc_aicsc_inStateChangeEvt_t。

typedef struct{ //Event ID: AUDIO_EVT_AICSC_CHANGED_INPUT_STATE
    u8 vcpInclIdx;
    u8 micpInclIdx;
    s8 gainSetting;
    u8 mute; //blc_aics_mute_value_enum
    u8 gainMode;    //blc_aics_gain_mode_value_enum
} blc_aicsc_inStateChangeEvt_t;

vcpInclIdx: Aics被VCP包含的索引号。

micpInclIdx: Aics被MICP包含的索引号。

gainSetting: 输入增益。单位dB。

mute: 输入静音,参考枚举值blc_aics_mute_value_enum。

typedef enum{
    AICS_MUTE_VALUE_NOT_MUTED = 0x00,
    AICS_MUTE_VALUE_MUTED,
    AICS_MUTE_VALUE_DISABLED,
    AICS_MUTE_VALUE_RFU,
} blc_aics_mute_value_enum;

gainMode: 输入增益模式。参考枚举值blc_aics_gain_mode_value_enum

typedef enum{
    AICS_GAIN_MODE_VALUE_MANUAL_ONLY    = 0x00,
    AICS_GAIN_MODE_VALUE_AUTOMATIC_ONLY,
    AICS_GAIN_MODE_VALUE_MANUAL,
    AICS_GAIN_MODE_VALUE_AUTOMATIC,
} blc_aics_gain_mode_value_enum;

(2) AICSS Event介绍

//AICS Server Event ID
typedef enum{
    AUDIO_EVT_AICSS_START = AUDIO_EVT_TYPE_AICSS,
    AUDIO_EVT_AICSS_CHANGED_INPUT_STATE,    
} audio_aicss_evt_enum;

AUDIO_EVT_AICSS_CHANGED_INPUT_STATE

AICS服务器收到客户端修改音频输入状态后,上报的事件。

事件附带的参数信息结构体blc_aicss_inStateChangeEvt_t。

typedef struct{ //Event ID: AUDIO_EVT_AICSS_CHANGED_INPUT_STATE
    u8 vcpInclIdx;
    s8 gainSetting;
    u8 mute; //blc_aics_mute_value_enum
    u8 gainMode; //blc_aics_gain_mode_value_enum
} blc_aicss_inStateChangeEvt_t;

vcpInclIdx: Aics被VCP包含的索引号。

gainSetting: 输入增益。单位dB。

mute: 输入静音,参考枚举值blc_aics_mute_value_enum。

gainMode: 输入增益模式。参考枚举值blc_aics_gain_mode_value_enum

内容控制 (Content Control)

我们收听的内容是在蓝牙规范之外生成的,它可能是流媒体音乐、直播电视、电话或视频会议。内容控制规范所做的是允许在开始、停止、应答、暂停和选择流方面进行控制。在 LE 音频中,它们分为两组规范:一组用于所有形式的电话 (CCP/TBS),另一组用于媒体 (MCP/MCS)。

内容控制相关的配置文件和服务

服务 描述
CCP 呼叫控制配置⽂件,它是作用于TBS的客户端
(G)TBS (通用)电话承载服务,提供了电话服务承载的状态和控制接口
MCP 媒体控制配置⽂件,它是作⽤于MCS的客户端
(G)MCS (通用)媒体控制服务,提供了媒体播放状态与控制接口

MCP/MCS 媒体控制

(G)MCS –(通用)媒体控制服务,公开提供媒体播放状态和控制的特征,用于描述媒体播放器信息,包括媒体播放器类别,图标和当前渲染目标、当前曲目和相关曲目信息,下一曲目,播放速度、当前组(多个轨道的组),当前组内轨道的播放顺序;(G)MCP – 媒体控制配置文件,定义了角色和规程,用于控制实现MCS或GMCS的客户端。

(1) GMCSC Event介绍

//GMCS Client Event ID
typedef enum{
    AUDIO_EVT_GMCSC_START = AUDIO_EVT_TYPE_GMCSC,
    AUDIO_EVT_MCSC_MEDIA_PLAYER_NAME,              
    AUDIO_EVT_MCSC_MEDIA_TRACK_CHANGED,            
    AUDIO_EVT_MCSC_MEDIA_TRACK_TITLE,              
    AUDIO_EVT_MCSC_MEDIA_TRACK_DURATION,           
    AUDIO_EVT_MCSC_MEDIA_TRACK_POSITION,          
    AUDIO_EVT_MCSC_MEDIA_PLAYBACK_SPEED,           
    AUDIO_EVT_MCSC_MEDIA_SEEKING_SPEED,            
    AUDIO_EVT_MCSC_MEDIA_CURRENT_TRACK_OBJECT_ID,  
    AUDIO_EVT_MCSC_MEDIA_NEXT_TRACK_OBJECT_ID,     
    AUDIO_EVT_MCSC_MEDIA_PARENT_GROUP_OBJECT_ID,   
    AUDIO_EVT_MCSC_MEDIA_CURRENT_GROUP_OBJECT_ID,  
    AUDIO_EVT_MCSC_MEDIA_PLAYING_ORDER,            
    AUDIO_EVT_MCSC_MEDIA_STATE,                    
    AUDIO_EVT_MCSC_MEDIA_CTRL_RESULT,              
    AUDIO_EVT_MCSC_MEDIA_CTRL_OPCODE_SUPPORT,      
    AUDIO_EVT_MCSC_SEARCH_CTRL_RESULT,             
    AUDIO_EVT_MCSC_SEARCH_RESULT_OBJECT_ID,        
} audio_gmcsc_evt_enum;

(a) AUDIO_EVT_MCSC_MEDIA_PLAYER_NAME

当MCS Server端的媒体播放器名称发生变化时,应该通知MCS Client端。MCS Client端收到通知后通过该事件上报给APP层。事件附带的参数信息结构体:

typedef struct{ //Event ID: AUDIO_EVT_MCSC_MEDIA_PLAYER_NAME
    u16 connHandle;
    u8  mediaNameLen;
    u8  mediaName[50];
} blc_mcsc_mediaPlayerNameEvt_t;

参数信息:

connHandle:ACL连接句柄。

mediaNameLen:媒体播放器名称长度。

mediaName:媒体播放器名称。

(b) AUDIO_EVT_MCSC_MEDIA_TRACK_CHANGED

当MCS Server端的媒体播放器当前播放曲目发生变化时,应该通知MCS Client端。MCS Client端收到通知后通过该事件上报给APP层。事件附带的参数信息结构体:

typedef struct{ //Event ID: AUDIO_EVT_MCSC_MEDIA_TRACK_CHANGED
    u16 connHandle;
} blc_mcsc_mediaTrackChangedEvt_t;

参数信息:

connHandle:ACL连接句柄。

(c) AUDIO_EVT_MCSC_MEDIA_TRACK_TITLE

当MCS Server端的媒体播放器当前曲目名称发生变化时,应该通知MCS Client端。MCS Client端收到通知后通过该事件上报给APP层。事件附带的参数信息结构体:

typedef struct{ //Event ID: AUDIO_EVT_MCSC_MEDIA_TRACK_TITLE
    u16 connHandle;
    u8  trackTitleLen;
    u8  trackTitle[50];
} blc_mcsc_mediaTrackTitleEvt_t;

参数信息:

connHandle:ACL连接句柄。

trackTitleLen:曲目名称长度。

trackTitle:曲目名称。

(d) AUDIO_EVT_MCSC_MEDIA_TRACK_DURATION

当MCS Server端的媒体播放器当前曲目时长发生变化时,应该通知MCS Client端。MCS Client端收到通知后通过该事件上报给APP层。事件附带的参数信息结构体:

typedef struct{ //Event ID: AUDIO_EVT_MCSC_MEDIA_TRACK_DURATION
    u16 connHandle;
    s32 trackDuration;
} blc_mcsc_mediaTrackDurationEvt_t;

参数信息:

connHandle:ACL连接句柄。

trackDuration:曲目时长,32位有符号数。分辨率0.01s。

(e) AUDIO_EVT_MCSC_MEDIA_TRACK_POSITION

当MCS Server端的媒体播放器当前曲目位置发生变化时,应该通知MCS Client端。MCS Client端收到通知后通过该事件上报给APP层。事件附带的参数信息结构体:

typedef struct{ //Event ID: AUDIO_EVT_MCSC_MEDIA_TRACK_POSITION
    u16 connHandle;
    s32 trackPosition;
} blc_mcsc_mediaTrackPositionEvt_t;

参数信息:

connHandle:ACL连接句柄。

trackPosition:当前曲目播放位置,32位有符号数,分辨率0.01s。

(f) AUDIO_EVT_MCSC_MEDIA_PLAYBACK_SPEED

当MCS Server端的媒体播放器播放速度发生变化时,应该通知MCS Client端。MCS Client端收到通知后通过该事件上报给APP层。事件附带的参数信息结构体:

typedef struct{ //Event ID: AUDIO_EVT_MCSC_MEDIA_PLAYBACK_SPEED
    u16 connHandle;
    s8  playbackSpeed;
} blc_mcsc_mediaPlaybackSpeedEvt_t;

参数信息:

connHandle:ACL连接句柄。

playbackSpeed:媒体播放器播放速度,计算方式为:

Palyback Speed

例如:

Palyback Speed Calculate

(g) AUDIO_EVT_MCSC_MEDIA_SEEKING_SPEED

当MCS Server端的媒体播放器搜寻发生变化时,应该通知MCS Client端。MCS Client端收到通知后通过该事件上报给APP层。事件附带的参数信息结构体:

typedef struct{ //Event ID: AUDIO_EVT_MCSC_MEDIA_SEEKING_SPEED
    u16 connHandle;
    s8  seekingSpeed;
} blc_mcsc_mediaSeekingSpeedEvt_t;

参数信息:

connHandle:ACL连接句柄。

seekingSpeed:搜寻速度,如快进n倍,快退n倍。

(h) AUDIO_EVT_MCSC_MEDIA_CURRENT_TRACK_OBJECT_ID

当MCS Server端的媒体播放器当前曲目对象ID发生变化时,应该通知MCS Client端。MCS Client端收到通知后通过该事件上报给APP层。事件附带的参数信息结构体:

typedef struct{ //Event ID: AUDIO_EVT_MCSC_MEDIA_CURRENT_TRACK_OBJECT_ID
    u16 connHandle;
    blt_object_id_t  object;
} blc_mcsc_mediaCurrentTrackObjectIdEvt_t;

参数信息:

connHandle:ACL连接句柄。

object:对象ID,6个字节区分。

(i) AUDIO_EVT_MCSC_MEDIA_NEXT_TRACK_OBJECT_ID

当MCS Server端的媒体播放器下一曲目ID发生变化时,应该通知MCS Client端。MCS Client端收到通知后通过该事件上报给APP层。事件附带的参数信息结构体:

typedef struct{ //Event ID: AUDIO_EVT_MCSC_MEDIA_NEXT_TRACK_OBJECT_ID
    u16 connHandle;
    blt_object_id_t  object;
} blc_mcsc_mediaNextTrackObjectIdEvt_t;

参数信息:

connHandle:ACL连接句柄。

object:对象ID,6个字节区分。

(j) AUDIO_EVT_MCSC_MEDIA_PARENT_GROUP_OBJECT_ID

当MCS Server端的媒体播放器上级列表ID发生变化时,应该通知MCS Client端。MCS Client端收到通知后通过该事件上报给APP层。事件附带的参数信息结构体:

typedef struct{ //Event ID: AUDIO_EVT_MCSC_MEDIA_PARENT_GROUP_OBJECT_ID
    u16 connHandle;
    blt_object_id_t  object;
} blc_mcsc_mediaParentGroupObjectIdEvt_t;

参数信息:

connHandle:ACL连接句柄。

object:对象ID,6个字节区分。

(k) AUDIO_EVT_MCSC_MEDIA_CURRENT_GROUP_OBJECT_ID

当MCS Server端的媒体播放器当前列表ID发生变化时,应该通知MCS Client端。MCS Client端收到通知后通过该事件上报给APP层。事件附带的参数信息结构体:

typedef struct{ //Event ID: AUDIO_EVT_MCSC_MEDIA_CURRENT_GROUP_OBJECT_ID
    u16 connHandle;
    blt_object_id_t  object;
} blc_mcsc_mediaCurrentGroupObjectIdEvt_t;

参数信息:

connHandle:ACL连接句柄。

object:对象ID,6个字节区分。

(l) AUDIO_EVT_MCSC_SEARCH_RESULT_OBJECT_ID

当MCS Server端的媒体播放器搜索对象ID发生变化时,应该通知MCS Client端。MCS Client端收到通知后通过该事件上报给APP层。事件附带的参数信息结构体:

typedef struct{ //Event ID: AUDIO_EVT_MCSC_SEARCH_RESULT_OBJECT_ID
    u16 connHandle;
    blc_object_id_t  object;
} blc_mcsc_searchResultObjectIdEvt_t;

参数信息:

connHandle:ACL连接句柄。

object:对象ID,6个字节区分。

(m) AUDIO_EVT_MCSC_MEDIA_PLAYING_ORDER

当MCS Server端的媒体播放器播放顺序发生变化时,应该通知MCS Client端。MCS Client端收到通知后通过该事件上报给APP层。事件附带的参数信息结构体:

typedef struct{ //Event ID: AUDIO_EVT_MCSC_MEDIA_PLAYING_ORDER
    u16 connHandle;
    blc_mcs_playingOrder_enum order;
}blc_mcsc_mediaPlayingOrderEvt_t;

参数信息:

connHandle:ACL连接句柄。

order:媒体播放器播放顺序,具体内容请参考结构体'blc_mcs_playingOrder_enum'。

typedef enum{
    BLC_MCS_PLAYING_ORDER_SINGLE_ONCE = 0x01,
    BLC_MCS_PLAYING_ORDER_SINGLE_REPEAT,
    BLC_MCS_PLAYING_ORDER_IN_ORDER_ONCE,
    BLC_MCS_PLAYING_ORDER_IN_ORDER_REPEAT,
    BLC_MCS_PLAYING_ORDER_OLDEST_ONCE,
    BLC_MCS_PLAYING_ORDER_OLDEST_REPEAT,
    BLC_MCS_PLAYING_ORDER_NEWEST_ONCE,
    BLC_MCS_PLAYING_ORDER_NEWEST_REPEAT,
    BLC_MCS_PLAYING_ORDER_SHUFFLE_ONCE,
    BLC_MCS_PLAYING_ORDER_SHUFFLE_REPEAT,
    BLC_MCS_PLAYING_ORDER_RFU = 0XFF,
}blc_mcs_playingOrder_enum;

(n) AUDIO_EVT_MCSC_MEDIA_STATE

当MCS Server端的媒体播放器播放状态发生变化时,应该通知MCS Client端。MCS Client端收到通知后通过该事件上报给APP层。事件附带的参数信息结构体:

typedef struct{ //Event ID: AUDIO_EVT_MCSC_MEDIA_STATE
    u16 connHandle;
    blc_mcs_mediaState_enum state;
}blc_mcsc_mediaStateEvt_t;

参数信息:

connHandle:ACL连接句柄。

state:媒体播放器播放状态,具体内容请参考结构体'blc_mcs_mediaState_enum'。

/* Media State values */
typedef enum{
    GMCS_MEDIA_STATE_INACTIVE           = 0x00,
    GMCS_MEDIA_STATE_PLAYLING           = 0x01,
    GMCS_MEDIA_STATE_PAUSED             = 0x02,
    GMCS_MEDIA_STATE_SEEKING            = 0x03,
    GMCS_MEDIA_STATE_RFU                = 0xff,
}blc_mcs_mediaState_enum;

(o) AUDIO_EVT_MCSC_MEDIA_CTRL_RESULT

MCS Server端对MCS Client每个媒体控制操作都会回复成功或失败,MCS Client端收到回复后通过该事件上报给APP层。事件附带的参数信息结构体:

typedef struct{ //Event ID: AUDIO_EVT_MCSC_MEDIA_CTRL_RESULT
    u16 connHandle;
    blc_mcs_mediaCtrlPointOpcode_enum op;
    blc_mcs_mediaCtrlPointResult_enum result;
} blc_mcsc_mediaCtrlResultEvt_t;

参数信息:

connHandle:ACL连接句柄。

op:控制请求指令,具体内容请参考结构体'blc_mcs_mediaCtrlPointOpcode_enum'。

typedef enum{
    BLC_MCS_OPCODE_NONE                 = 0x00,
    BLC_MCS_OPCODE_PLAY                 = 0x01,
    BLC_MCS_OPCODE_PAUSE                = 0x02,
    BLC_MCS_OPCODE_FAST_REWIND          = 0x03,
    BLC_MCS_OPCODE_FAST_FORWARD         = 0x04,
    BLC_MCS_OPCODE_STOP                 = 0x05,
    BLC_MCS_OPCODE_MOVE_RELATIVE        = 0x10,
    BLC_MCS_OPCODE_PREVIOUS_SEGMENT     = 0x20,
    BLC_MCS_OPCODE_NEXT_SEGMENT         = 0x21,
    BLC_MCS_OPCODE_FIRST_SEGMENT        = 0x22,
    BLC_MCS_OPCODE_LAST_SEGMENT         = 0x23,
    BLC_MCS_OPCODE_GOTO_SEGMENT         = 0x24,
    BLC_MCS_OPCODE_PREVIOUS_TRACK       = 0x30,
    BLC_MCS_OPCODE_NEXT_TRACK           = 0x31,
    BLC_MCS_OPCODE_FIRST_TRACK          = 0x32,
    BLC_MCS_OPCODE_LAST_TRACK           = 0x33,
    BLC_MCS_OPCODE_GOTO_TRACK           = 0x34,
    BLC_MCS_OPCODE_PREVIOUS_GROUP       = 0x40,
    BLC_MCS_OPCODE_NEXT_GROUP           = 0x41,
    BLC_MCS_OPCODE_FIRST_GROUP          = 0x42,
    BLC_MCS_OPCODE_LAST_GROUP           = 0x43,
    BLC_MCS_OPCODE_GOTO_GROUP           = 0x44,
} blc_mcs_mediaCtrlPointOpcode_enum;

result:控制操作结果返回指令,具体内容请参考结构体'blc_mcs_mediaCtrlPointResult_enum'。

typedef enum{
    BLC_MCS_MEDIA_CTRL_RESULT_SUCCESS   = 0x01,
    BLC_MCS_MEDIA_CTRL_RESULT_OP_NOT_SUPP,
    BLC_MCS_MEDIA_CTRL_RESULT_MEDIA_PLAYER_INACTIVE,
    BLC_MCS_MEDIA_CTRL_RESULT_CMD_CANNOT_BE_COMPLETED
} blc_mcs_mediaCtrlPointResult_enum;

(p) AUDIO_EVT_MCSC_MEDIA_CTRL_OPCODE_SUPPORT

当MCS Server端的媒体播放器支持的媒体控制指令发生变化时,应该通知MCS Client端。MCS Client端收到通知后通过该事件上报给APP层。事件附带的参数信息结构体:

typedef struct{ //Event ID: AUDIO_EVT_MCSC_MEDIA_CTRL_OPCODE_SUPPORT
    u16 connHandle;
    u32 supportOpcode;
}blc_mcsc_mediaCtrlOpcodeSupportEvt_t;

参数信息:

connHandle:ACL连接句柄。

supportOpcode:媒体播放器支持的操作指令,位有效,具体内容请参考结构体。

'blc_mcs_mediaCtrlPointOpcodeSupport_enum'

typedef enum{
    BLC_MCS_OPCODE_SUPPORT_PLAY                 = BIT(0),
    BLC_MCS_OPCODE_SUPPORT_PAUSE                = BIT(1),
    BLC_MCS_OPCODE_SUPPORT_FAST_REWIND          = BIT(2),
    BLC_MCS_OPCODE_SUPPORT_FAST_FORWARD         = BIT(3),
    BLC_MCS_OPCODE_SUPPORT_STOP                 = BIT(4),
    BLC_MCS_OPCODE_SUPPORT_MOVE_RELATIVE        = BIT(5),
    BLC_MCS_OPCODE_SUPPORT_PREVIOUS_SEGMENT     = BIT(6),
    BLC_MCS_OPCODE_SUPPORT_NEXT_SEGMENT         = BIT(7),
    BLC_MCS_OPCODE_SUPPORT_FIRST_SEGMENT        = BIT(8),
    BLC_MCS_OPCODE_SUPPORT_LAST_SEGMENT         = BIT(9),
    BLC_MCS_OPCODE_SUPPORT_GOTO_SEGMENT         = BIT(10),
    BLC_MCS_OPCODE_SUPPORT_PREVIOUS_TRACK       = BIT(11),
    BLC_MCS_OPCODE_SUPPORT_NEXT_TRACK           = BIT(12),
    BLC_MCS_OPCODE_SUPPORT_FIRST_TRACK          = BIT(13),
    BLC_MCS_OPCODE_SUPPORT_LAST_TRACK           = BIT(14),
    BLC_MCS_OPCODE_SUPPORT_GOTO_TRACK           = BIT(15),
    BLC_MCS_OPCODE_SUPPORT_PREVIOUS_GROUP       = BIT(16),
    BLC_MCS_OPCODE_SUPPORT_NEXT_GROUP           = BIT(17),
    BLC_MCS_OPCODE_SUPPORT_FIRST_GROUP          = BIT(18),
    BLC_MCS_OPCODE_SUPPORT_LAST_GROUP           = BIT(19),
    BLC_MCS_OPCODE_SUPPORT_GOTO_GROUP           = BIT(20),
    BLC_MCS_OPCODE_SUPPORT_RFU                  = 0xFFFFFFFF,
} blc_mcs_mediaCtrlPointOpcodeSupport_enum;

(q) AUDIO_EVT_MCSC_SEARCH_CTRL_RESULT

MCS Server端对MCS Client每个搜索操作都会回复成功或失败,MCS Client端收到回复后通过该事件上报给APP层。事件附带的参数信息结构体:

typedef struct{ //Event ID: AUDIO_EVT_MCSC_SEARCH_CTRL_RESULT
    u16 connHandle;
    blc_mcs_searchCtrlPointResult_enum result;
} blc_mcsc_searhCtrlResultEvt_t;

参数信息:

connHandle:ACL连接句柄。

result:操作请求指令,具体内容请参考结构体'blc_mcs_searchCtrlPointResult_enum'。

typedef enum{
    BLC_MCS_SEARCH_CTRL_RESULT_SUCCESS,
    BLC_MCS_SEARCH_CTRL_RESULT_FAILURE,
} blc_mcs_searchCtrlPointResult_enum;

(2) GMCSS Event

TBD

(3) GMCSC相关API 介绍

TBD

(4) GMCSS相关API介绍

TBD

CCP/(G)TBS呼叫控制

TBS和通用TBS为可以拨打和接听电话的设备上的承载提供电话呼叫控制接口和状态,该服务公开了承载信息包括服务提供商、技术类型(3G,4G,VoIP等)、信号强度等。通过这些接口和状态,可以获取每个活动呼叫的状态,并使用相应的方法进行控制,例如活动、本地保持、远程保持、警报等。此外,也可以通过相应地控制来实现多路呼叫(例如,将一个保持为保留状态,将另一个保持活动状态,或者将两个都处于活动状态以进行三向呼叫)。CCP定义了用于控制实现TBS或GTBS的远程设备的角色和规程。

(1) GTBSC Event介绍

//GTBS Client Event ID
typedef enum{
    AUDIO_EVT_GTBSC_START = AUDIO_EVT_TYPE_GTBSC,
    AUDIO_EVT_GTBS_BEARER_PROVIDER_NAME,       
    AUDIO_EVT_GTBS_BEARER_TECHNOLOGY,           
    AUDIO_EVT_GTBS_BEARER_URI_SCHEMES_SUPP_LIST,
    AUDIO_EVT_GTBS_BEARER_SIGNAL_STRENGTH,     
    AUDIO_EVT_GTBS_BEARER_LIST_CURRENT_CALL,   
    AUDIO_EVT_GTBS_STATUS_FLAGS,              
    AUDIO_EVT_GTBS_INCOMING_CALL_TGT_URI,     
    AUDIO_EVT_GTBS_CALL_STATE,                 
    AUDIO_EVT_GTBS_TERM_REASON,              
    AUDIO_EVT_GTBS_INCOMING_CALL,            
    AUDIO_EVT_GTBS_CALL_FRIENDLY_NAME,      
    AUDIO_EVT_GTBS_CCP_NTF_RESULT_CODE,       
} audio_gtbsc_evt_enum;
(a) AUDIO_EVT_GTBS_BEARER_PROVIDER_NAME

当GTBS Server端的通话服务商名称(Bearer Provider Name)发生变化时,需要通知GTBS Client端,GTBS Client端收到后通过该事件上报给APP层。事件附带的参数信息结构体:

typedef struct{//Event ID: AUDIO_EVT_GTBS_BEARER_PROVIDER_NAME
    u8 nameLen;
    u8 providerName[30];//e.g. "CMCC"
} blc_gtbsc_bearerProviderName_t;

参数信息:

nameLen:名称长度。

providerName:通话服务商名称,最长30个字节,超过30个字节将只显示30个字节。

(b) AUDIO_EVT_GTBS_BEARER_TECHNOLOGY

当GTBS Server端的通话服务采用的技术(Bearer Technology)发生变化时,需要通知GTBS Client端,GTBS Client端收到后通过该事件上报给APP层。事件附带的参数信息结构体:

typedef struct{//Event ID: AUDIO_EVT_GTBS_BEARER_TECHNOLOGY
    blc_gtbs_technology_enum technology;
} blc_gtbsc_technology_t;

参数信息:

technology:通话服务采用的技术,具体类别参考枚举'blc_gtbs_technology_enum'。

/* Bearer Technology */
typedef enum
{
    GTBS_TECHNOLOGY_3G                         =0x01,
    GTBS_TECHNOLOGY_4G                         =0x02,
    GTBS_TECHNOLOGY_LTE                        =0x03,
    GTBS_TECHNOLOGY_WIFI                       =0x04,
    GTBS_TECHNOLOGY_5G                         =0x05,
    GTBS_TECHNOLOGY_GSM                        =0x06,
    GTBS_TECHNOLOGY_CDMA                       =0x07,
    GTBS_TECHNOLOGY_2G                         =0x08,
    GTBS_TECHNOLOGY_WCDMA                      =0x09,
    GTBS_TECHNOLOGY_IP                         =0x0a,
}blc_gtbs_technology_enum;

(c) AUDIO_EVT_GTBS_BEARER_URI_SCHEMES_SUPP_LIST

当GTBS Server端的通话服务商支持的URI方案(Bearer URI Schemes Supported List)发生变化时,需要通知GTBS Client端,GTBS Client端收到后通过该事件上报给APP层。事件附带的参数信息结构体:

typedef struct{//Event ID: AUDIO_EVT_GTBS_BEARER_URI_SCHEMES_SUPP_LIST
    u8 suppLen;
    u8 uriSchemeSuppList[30];//e.g.: "tel,sip,skype"
} blc_gtbsc_uriSchemeSuppList_t;

参数信息:

suppLen:支持的列表长度。

uriSchemeSuppList:通话服务商支持的URI方案,例如支持通话和skype,列表为“tel,skype”。最长30个字节,超过30个字节将只显示30个字节。URI方案由国际互联网工程任务组(IETF)负责分配、发布和维护。

(d) AUDIO_EVT_GTBS_BEARER_SIGNAL_STRENGTH

当GTBS Server端的通讯服务信号强度(Bearer Signal Strength)发生变化时,需要通知GTBS Client端,GTBS Client端收到后通过该事件上报给APP层。事件附带的参数信息结构体:

typedef struct{//Event ID: AUDIO_EVT_GTBS_BEARER_SIGNAL_STRENGTH
    u8 signalStrenth;
} blc_gtbsc_signalStrength_t;

参数信息:

signalStrenth:通讯服务讯号强度。取值范围为0-100,以及255。0代表无服务。100代表信号最强。255代表信号强度不可用。

(e) AUDIO_EVT_GTBS_BEARER_LIST_CURRENT_CALL

当GTBS Server端的当前通话服务列表(Bearer List Current Call)发生变化时,需要通知GTBS Client端,GTBS Client端收到后通过该事件上报给APP层。事件附带的参数信息结构体:

typedef struct{ //Event ID: AUDIO_EVT_GTBS_BEARER_LIST_CURRENT_CALL
    u8 listLen;
    u8 currentListCall[STACK_AUDIO_CALL_MEMBERS_MAX_NUM*40];//
} blc_gtbsc_listCurrentCallsEvt_t;

参数信息:

listLen:列表总长度。

currentListCall:当前通话服务列表,格式为:

Bear List Current Calls

对应的解析用结构体为'blc_gtbsc_list_curr_call_t'

typedef struct{
    u8 listItemLen;
    u8 callIndex;
    u8 state;
    u8 callFlags;
    u8 *pCallUri;
} blc_gtbsc_list_curr_call_t;

(f) AUDIO_EVT_GTBS_STATUS_FLAGS

当GTBS Server端的状态标志(Status Flags)发生变化时,需要通知GTBS Client端,GTBS Client端收到后通过该事件上报给APP层。事件附带的参数信息结构体:

typedef struct{ //Event ID: AUDIO_EVT_GTBS_STATUS_FLAGS
    u16  statusFlags;
} blc_gtbsc_statusFlagsEvt_t;

参数信息:

statusFlags:状态标志,位有效,具体含义为:

Call State Flags

(g) AUDIO_EVT_GTBS_INCOMING_CALL_TGT_URI

当GTBS Server端的来电目标服务商(Incoming Call Target URI)发生变化时,需要通知GTBS Client端,GTBS Client端收到后通过该事件上报给APP层。事件附带的参数信息结构体:

typedef struct{ //Event ID: AUDIO_EVT_GTBS_INCOMING_CALL_TGT_URI
    blt_tbs_incoming_call_target_bearer_uri_t uri;
} blc_gtbsc_incomingCallTgtUriEvt_t;

参数信息:

uri:格式为Call_Index + URI,具体内容参考:

typedef struct {
    u8 callIndex;
    u8 infoLen;
    u8 info[30];
} blt_tbs_incoming_call_target_bearer_uri_t

注:URI长度最大30个字节,超过30个字节将只取30个字节。

(h) AUDIO_EVT_GTBS_CALL_STATE

当GTBS Server端的通话状态(Call State)发生变化时,需要通知GTBS Client端,GTBS Client端收到后通过该事件上报给APP层。事件附带的参数信息结构体:

typedef struct{ //Event ID: AUDIO_EVT_GTBS_CALL_STATE
    u8  stateLen;
    blc_gtbs_call_state_t state[STACK_AUDIO_CALL_MEMBERS_MAX_NUM];
} blc_gtbsc_listCallStateEvt_t;

参数信息:

stateLen:通话状态列表总长度。

state:通话状态列表,最多支持'STACK_AUDIO_CALL_MEMBERS_MAX_NUM'个通话。通话状态的格式为:

typedef struct{
    u8 callIndex;
    u8 state;
    u8 callFlags;
} blc_gtbs_call_state_t;

(i) AUDIO_EVT_GTBS_TERM_REASON

当GTBS Server端的通话结束原因(Terminate Reason)发生变化时,需要通知GTBS Client端,GTBS Client端收到后通过该事件上报给APP层。事件附带的参数信息结构体:

typedef struct{ //Event ID: AUDIO_EVT_GTBS_TERM_REASON
    u8  callIndex;
    u8  termRsn;
} blc_gtbsc_termRsnEvt_t

参数信息:

callIndex:Call Index

termRsn:通话断连原因,具体原因请参考枚举'blc_gtbs_termReason_enum'。

/* Termination Reason Codes */
typedef enum
{
    GTBS_TERM_REASON_URI_ERROR                 = 0x00,
    GTBS_TERM_REASON_CALL_FAILED               = 0x01,
    GTBS_TERM_REASON_REMOTE_ENDED_CALL         = 0x02,
    GTBS_TERM_REASON_SERVER_ENDED_CALL         = 0x03,
    GTBS_TERM_REASON_LINE_BUSY                 = 0x04,
    GTBS_TERM_REASON_NETWORK_CONGESTION        = 0x05,
    GTBS_TERM_REASON_CLIENT_TERM_CALL          = 0x06,
    GTBS_TERM_REASON_NO_SERVICE                = 0x07,
    GTBS_TERM_REASON_NO_ANSWER                 = 0x08,
    GTBS_TERM_REASON_UNSPECIFIED               = 0x09,
}blc_gtbs_termReason_enum;

(j) AUDIO_EVT_GTBS_INCOMING_CALL

当GTBS Server端的来电(Incoming Call)发生变化时,需要通知GTBS Client端,GTBS Client端收到后通过该事件上报给APP层。事件附带的参数信息结构体:

typedef struct{ //Event ID: AUDIO_EVT_GTBS_INCOMING_CALL
    blt_tbs_incoming_call_t call;
} blc_gtbsc_incomingCallEvt_t;

参数信息:

call:来电,格式为Call Index + URI,具体内容请参考结构体'blt_tbs_incoming_call_t'。

typedef struct {
    u8 callIndex;
    u8 infoLen;
    u8 info[30];
}blt_tbs_incoming_call_t;
注:URI最大支持30个字节,超过30个字节将只取30个字节。

(k) AUDIO_EVT_GTBS_CALL_FRIENDLY_NAME

当GTBS Server端的通话好友名称(Call Friendly Name)发生变化时,需要通知GTBS Client端,GTBS Client端收到后通过该事件上报给APP层。事件附带的参数信息结构体:

typedef struct{ //Event ID: AUDIO_EVT_GTBS_CALL_FRIENDLY_NAME
    blt_tbs_call_friendly_name_t name;
} blc_gtbsc_friendlyNameEvt_t;

参数信息:

name:通话好友名称,格式为Call Index + Name,具体内容请参考结构体'blt_tbs_call_friendly_name_t'。

typedef struct {
    u8 callIndex;
    u8 infoLen;
    u8 info[30];
}blt_tbs_call_friendly_name_t;

注:Friendly Name最大支持30个字节,超过30个字节将只取30个字节。

(l) AUDIO_EVT_GTBS_CCP_NTF_RESULT_CODE

GTBS Server端对GTBS Client端的每个操作都会回复成功或失败,GTBS Client端收到后通过该事件上报给APP层。事件附带的参数信息结构体:

typedef struct{ //Event ID: AUDIO_EVT_GTBS_CCP_NTF_RESULT_CODE
    u8 reqOpcode;
    u8 callIndex;
    u8 resultCode;
} blc_gtbsc_ccpNtfResultCodesEvt_t,

参数信息:

reqOpcode:操作请求指令,具体内容参考枚举'blt_gtbs_opcode_enum'。

/* Call Control Point characteristic opcodes */
typedef enum {
    GTBS_OPCODE_ACCEPT,
    GTBS_OPCODE_TERMINATE,
    GTBS_OPCODE_LOCAL_HOLD,
    GTBS_OPCODE_LOCAL_RETRIEVE,
    GTBS_OPCODE_ORIGINATE,
    GTBS_OPCODE_JOIN,
    GTBS_OPCODE_MAX,
} blt_gtbs_opcode_enum;

callIndex:Call Index

resultCode:操作返回指令,具体内容请参考枚举'blc_gtbs_ntfResultcode_enum'。

/* Call Control Point Notification Result Codes */
typedef enum
{
    GTBS_NTF_RESULTCODE_SUCCESS                = 0x00,
    GTBS_NTF_RESULTCODE_OPCODE_NOT_SUPP        = 0x01,
    GTBS_NTF_RESULTCODE_OPERATION_NOT_POSSIBLE = 0x02,
    GTBS_NTF_RESULTCODE_INVALID_CALL_INDEX     = 0x03,
    GTBS_NTF_RESULTCODE_STATE_MISMATCH         = 0x04,
    GTBS_NTF_RESULTCODE_LACK_OF_RESOURCES      = 0x05,
    GTBS_NTF_RESULTCODE_INVALID_OUTGOING_URI   = 0x06,
}blc_gtbs_ntfResultcode_enum;

(2) GTBSS Event

TBD

(3) GTBSC相关API介绍

blc_audio_registerCallControlClient

用于注册 CCP Call Control 功能的 API:

void blc_audio_registerCallControlClient(const blc_ccpc_regParam_t *param);

目前该 API 的传参没有用到 (预留扩展用), 输入 NULL 就可以了。相当于注册了 GTBS 的客户端功能,连接建立后会主动 SDP 发现 GTBS 服务,并处理 CCP 相关的规程。

(4) GTBSS相关API介绍

TBD

过渡和协调控制 (Transition and Coordination Control)

过渡和协调控制相关的配置文件和服务

CAP通⽤⾳频配置⽂件,定义了⼀组程序来建⽴通⽤⽅式来执⾏在发起者和⼀个或多个接受者之间设置⾳频流所需的所有日常操作,CAP 为流程带来的另⼀件事是协调。CAP 制定了管理流的规则,使⽤协调集识别配置⽂件和服务将它们绑定在⼀起。

服务 描述
CAP 通用音频规范
CAS 通用音频服务
CSIP 协调集规范
CSIS 协调集服务

CSIP/CSIS协调设备

一组新规范,定义如何将设备识别和处理为协调集的一部分。(例如,真正的无线耳塞、家用立体声扬声器)。

(1) CSISC/CSISS Event介绍

目前 CSIS Client/Server 没有定义相关事件。

(2) CSISC API:

(a) blc_audio_registerCSISControlClient

用于注册 CSIP Set Coordinator (即 CSIS Client) 功能的 API:

void blc_audio_registerCSISControlClient(const blc_csisc_regParam_t *param);

目前该 API 的传参没有用到 (预留扩展用), 输入 NULL 就可以了。相当于注册了 CSIS 的客户端功能,连接建立后会主动 SDP 发现 CSIS 服务,并处理 CSIP 相关的规程。

(b) blc_csisc_getSetMemberRank

用于获取当前连接上的服务器上的协调集秩 (Rank) 的 API:

int blc_csisc_getSetMemberRank(u16 connHandle, u8 outRank[1]);

输入参数:

connHandle: ACL连接句柄。

输出参数:

outRank: 输出 Rank 值。

返回值:0 表示成功,其他值 表示失败。

属于同一协调集的服务器暴露的《Set Member Rank》特征的值应为从 0x01 到协调集大小的连续整数。

(c) blc_csisc_getSetMemberLock

用于获取当前连接上的服务器上的协调集锁状态的 API:

int blc_csisc_getSetMemberLock(u16 connHandle, u8 outLock[1]);

输入参数:

connHandle: ACL连接句柄。

输出参数:

outLock: 输出 Lock 值。

返回值:0 表示成功,其他值 表示失败。

《Set Member Lock》 特征允许客户端对服务器进行独占访问,以最大限度地减少当多个客户端同时访问同一协调集的多个服务器时可能出现的竞争条件。

(d) blc_csisc_getCoordinatedSetSize

用于获取属于同一调集的设备数量的 API:

int blc_csisc_getCoordinatedSetSize(u16 connHandle, u8 outSetSize[1]);

输入参数:

connHandle: ACL连接句柄。

输出参数:

outSetSize: SetSize 值。

返回值:0 表示成功,其他值 表示失败。

《Coordinated Set Size》 特性公开了组成 Coordinated Set 的设备数量,范围允许值:0x01 ~ 0xFF 范围内的整数。

(e) blc_csisc_getSetIdentityResolvingKey

用于获取协调集的设置身份解析密钥 (Set Identity Resolution Key, 简称SIRK) 的 API:

int blc_csisc_getSetIdentityResolvingKey(u16 connHandle, u8 outSIRK[16]);

输入参数:

connHandle: ACL连接句柄。

输出参数:

outSIRK: 小端字节序输出 SIRK。

返回值:0 表示成功,其他值 表示失败。

服务器端 《Set Identity Resolution Key》 特性公开与协调集合相关联的 SIRK, 属于同一协调集合的所有服务器应使用相同的SIRK。SIRK 应为 128 位长的随机数。用于生成 SIRK 的方法应符合核心规范 第2卷 H部分 第2节 中定义的随机数生成标准。协调集合由SIRK唯一标识。

注意:

SDK 音频 Demo 中没有使用随机数,只是为了演示,用户在实际开发产品时需使用随机数替代。

(f) blc_audio_registerCSISControlServer

用于注册 CSIP Set Member (即 CSIS Server *) 功能的 API:

void blc_audio_registerCSISControlServer(const blc_csiss_regParam_t *param);

*:包括 CAS (Common Audio Service) Include 服务注册, CAS 没有定义任何特性。

输入:param:希望初始化的 CSIS 参数。

blc_csiss_regParam_t 结构体的定义以及参数含义。

typedef struct{
    u8 setSize;     //Coordinated Set Size:1-255
    u8 setRank;     //Set Member Rank, must less than or equal to setSize
    u8 SIRK_type;   //exposes SIRK type, 0:plain text, 1:Encrypted, 2:only OOB
    u8 SIRK[16];    //Set Identity Resolving Key
    u8 lockedTimeout; //Unit time:1s, Parameter suggestion range:10-200
} blc_csiss_regParam_t;

setSize: Coordinated Set 的设备数量,范围允许值:0x01 ~ 0xFF 范围内的整数。

setRank: 协调集秩 (Rank) , 范围从 0x01 到协调集大小的连续整数,表示当前设备属于协调集组中的第几个设备。

SIRK_type: 表示 SIRK 的类型, 0:表示 SIRK 的值是明文; 1:表示SIRK 的值是密文。

SIRK: 表示 SIRK 的值, 小端类型,长度 16 字节。

lockedTimeout: 如果定时器T_CSIS (lock_timeout) 到期,则服务器应设置 《set Member Lock 》 特性的值为 Unlocked。计时器T_CSIS (lock_timeout) 的值应为 60 秒,也可以为由更高层规范定义。该值过高或过低都可能导致非预期的行为。

用户可以使用 SDK 提供的默认参数 defaultCsipSetMemberParam (参考 audio/stream/csip/csis_server_buf.c) 注册 CSIP Set Member:

blc_audio_registerCSISControlServer(&defaultCsipSetMemberParam); 

用户也可以自定义相关参数,参考 vendor/audio_unicast_server/app_att.c。

注意:

  • 对于 Headset 耳机,如果存在 CSIS 服务,那么 setSize 和 setRank 的值必须设置为 1。
  • 对于 TWS 耳机,需要确保 setSize = 2, setRank 的值为 1 或者 2 标识不同耳机,且所有耳机端的 SIRK 值相同 (明文相同,或者密文解密后的明文相同)。

(g) blc_csiss_getResolvableSetIdentifier

用于获取 RSI (Resolvable Set Identifier) 的值的 API:

void blc_csiss_getResolvableSetIdentifier(u8 outRSI[6]);
输出参数:outRSI,小端输出 RSI。

该 API 在使用前必须要先调用 blc_audio_registerCSISControlServer,初始化协调集参数。扩展广播包中暴露 RSI AD 类型声明,将广播设备标识为属于协调集,允许客户端设备识别它是协调集的成员。这意味着必须有两个或更多的 Acceptor 需要被找到。 RSI 是一个随机的六字节标识符,它随时间而变化。这降低了有人跟踪蓝牙产品的风险。

blc_audio_registerCSISControlServer(&defaultCsipSetMemberParam); //must used before call API:blc_csiss_getResolvableSetIdentifier
blc_csiss_getResolvableSetIdentifier(appCtrl.rsi); //get RSI value
const u8 audioAnnouncementAdvData[] = //RSI AD type announcement
{
    ......
    7,  DT_CSIP_RSI, 
    appCtrl.rsi[0], appCtrl.rsi[1], appCtrl.rsi[2],  appCtrl.rsi[3], appCtrl.rsi[4], appCtrl.rsi[5],
    ......
};
blc_ll_setExtAdvData(ADV_HANDLE0, sizeof(audioAnnouncementAdvData), (u8 *)audioAnnouncementAdvData);

一旦客户端设备 (可以是 Initiator 或 Commander) 与暴露 RSI AD 类型的设备建立连接,它就会与它发现的集合成员配对并绑 定,并读取其 SIRK 特性值。SIRK 是一个 128 位随机数,对协调集的所有成员都是通用的 (在设备生命周期内不会改变),客户端可以使用它寻找暴露 RSI AD 类型的其他接受器。

(h) blc_csis_cryptoGenerateRSI

根据 SIRK 生成 RSI (resolvableSetIdentifier) 的 API:

void blc_csis_cryptoGenerateRSI(u8 SIRK[16], u8 outRSI[6]);

输入参数:

SIRK: 小端字节序输入 SIRK。

输出参数:

outRSI: 小端字节序输出 RSI。

这里的 SIRK 和 RSI的关系 本质上跟 RPA 和 IRK 的关系是一样的, RSI 是一个可解析集标识符,格式如下:

RSI 的格式

使用 RSI 哈希函数 sih 生成哈希,输入参数 k 设置为设备的 SIRK,输入参数 r 设置为 prand:

  • hash = sih(SIRK, prand)

prand 和 hash 被连接起来以下列方式生成 RSI resolvableSetIdentifier:

  • resolvableSetIdentifier = hash || prand

(i) blc_csis_resolveRSI

用于判断 RSI 的是否可以被给定的 SIRK 解析的 API:

bool blc_csis_resolveRSI(u8 sirk[16], u8 rsi[6]);

输入参数:

sirk: 小端字节序输入 SIRK。

rsi: 小端字节序输入 RSI。

返回值:0,表示解析失败; 1,表示解析成功。

输入参数 k 设置为已知设备的 SIRK,输入参数 r 设置为从 RSI 中提取的 prand 值:

  • localHash = sih(SIRK, prand)

然后将 localHash 值与从 RSI 中提取的哈希值进行比较。 如果 localHash 值与提取的哈希值匹配,则 RSI 已被解析。

如果两个或以上的设备使用相应的 SIRK, 那么这些设备属于同一个协调集,它们可以被认为是属于同一个组内的设备,具有相同的音频捕获或渲染点,确保了多设备的音频采样或播放的同步。典型的例子是 TWS 耳机, 左右耳分属不同的设备,但是具有相同的 SIRK, 手机等 Initiator 设备会将这一对 TWS 的左右耳机当做同一个设备组。

CAP通用音频

一组新规范,定义了多服务情况下多设备同步音频流的应用,以及媒体和音量控制的集成。CAP 角色\/Profile-Service 关系 如下图所示,分为3个角色: Acceptor、 Initiator、 Commander。

CAP 角色\/Profile-Service 关系

  • Acceptor:要求是 Peripheral \/ Observer 设备,比如耳机,接收单播或广播音频流。

  • Initiator:要求是 Central \/ Broadcaster 设备,比如智能手机、平板等,用于发送单播或者广播音频流。

  • Commander:主要用于辅助设备 (既可以是 Central 和 Observer 也可以是 Peripheral 设备),需要注意的是当 Commander 和 Initiator 连接时,Commander 作为 Peripheral 设备,实现 Content Control 客户端功能;当 Commander 和 Acceptor 连接时,Commander 作为 Central 和 Observer 的设备,实现捕获和渲染控制的客户端功能或者音频流传输的客户端功能。

注意:

当一个设备支持多个并置 (multiple collocated ) 角色时,它应该同时支持多个 GAP 角色 (GAP Central、 Peripheral、 Broadcaster、 Observer 中的一种或几种角色的组合)。

配置文件支持 CAP 角色要求如下,一个设备可以同时实现多个角色。

配置文件支持 CAP 角色要求

  • C.1:Initiator必须支持至少一个 BAP Unicast Client 或 BAP Broadcast Source。

  • C.2:Acceptor必须支持至少一个 BAP Unicast Server 或 BAP Broadcast Sink。

  • C.3:如果支持 BAP Broadcast Sink,则为强制性,否则排除。

  • C.4:如果支持 BAP Scan Delegator,则为强制性,否则为可选。

  • C.5:如果支持 BAP Unicast Client,则为强制性,否则排除。

  • C.6:Commander必须至少支持一个 BAP Broadcast Assistant、BAP Scan Delegator、VCP Volume Controller、MICP Microphone Controller、CCP Call Control Client 或 MCP Media Control Client。

  • C.7:如果是协调集的一部分,则为强制性,否则排除。

  • C.8:如果Commander至少支持一个 BAP Broadcast Assistant、BAP Scan Delegator、VCP Volume Controller 或 MICP Microphone Controller,则为强制性,否则排除。

不实现 BAP Unicast Server 的外设将不会发送 BAP 公告 (BAP Announcements)。 这包括仅实现 Broadcast Sink 角色和 BAP Scan Delegator 角色的接受器,或者 Commanders。

引入 CAP 公告是为了使这些设备能够通过如下两种模式被 Initiator 或 Commander 建立 ACL 连接:

  • 迫切需要音频相关外设 (Immediate Need for Audio related Peripheral,简称 INAP) 模式
  • 准备好音频相关外设 (Ready for Audio related Peripheral,简称 RAP) 模式

为通知未连接的 Central 该 Peripheral 是可连接的,Peripheral 应传输包含服务数据 AD 数据类型的可连接扩展广播包,附加服务数据参考如下 CAP 公告格式。

CAP 公告格式

CAP 目标公告(公告类型 = 0x01)表示外围设备可连接并正在请求来自中央设备的连接。

CAP 一般公告(公告类型 = 0x00)表示外围设备可连接但未请求连接。

以 BAP Unicast Server 角色为例, 初始化 CAP 公告广播字段参考如下:

const u8 audioAnnouncementAdvData[] =
{
    ......
    4,  DT_SERVICE_DATA,
    //------------ CAS UUID ------------
    U16_LO(SERVICE_UUID_COMMON_AUDIO),U16_HI(SERVICE_UUID_COMMON_AUDIO),
    //------------ Announcement Type ------------
    BLC_AUDIO_TARGETED_ANNOUNCEMENT,
    ......
}
blc_ll_setExtAdvData(ADV_HANDLE0, sizeof(audioAnnouncementAdvData), (u8 *)audioAnnouncementAdvData);

(1) CAP Event

目前CAP没有定义相关事件。

(2) CAP相关API介绍

audio/trans_coord/cap/cap.c 封装了CAP 不同角色可以使用的所有 GAF 功能分组组合, 用户可以参考组合方式实现自己的应用。其中的组合规则按照 CAP 角色\/Profile-Service 关系来实现。

(3) Initiator API

TBD

用例配置文件 (Use Case Profiles)

一组新规范,定义了对基于LE音频的现有和新音频用例的互操作支持。用例配置文件也可以称为顶层配置文件,他们为特定音频用例提供了额外的要求。目前包括如下三个音频用例:

  • 电话和媒体音频配置文件 (TMAP),它指定使用更高质量的编解码器设置和更复杂的媒体和电话控制;

  • 听力访问配置文件和服务 (HAP 和 HAS),涵盖助听器生态系统的应用;

  • 公共广播配置文件 (PBP),它帮助用户选择全球可互操作的广播流。公共广播配置文件没有附带服务,因为它不存在客户端‑服务器交互的连接。

顶层配置文件

TMAP/TMAS 电话和媒体音频

TMAP 配置文件和依赖项:

TMAP配置文件和依赖项

TMAP 的角色分为 (注意:这些角色允许组合使用):

  • 电话网关 Call Gateway (CG)

  • 电话终端 Call Terminal (CT)

  • 单播媒体发送端 Unicast Media Sender (UMS)

  • 单播媒体接收端 Unicast Media Receiver (UMR)

  • 广播媒体发送端 Broadcast Media Sender (BMS)

  • 广播媒体接收端 Broadcast Media Receiver (BMR)

使用这些角色的一些示例实现如下:

智能手机和耳机 (Headset) 的示例 (CG\/CT) (参考下图示例 1)。

电视和立体声耳机示例 (BMS\/BMR) (参考下图示例 2)。

媒体播放器和立体声耳机示例 (UMS\/UMR) (参考下图示例 3)。

TMAP 角色典型示例实现

TMAP 用于单播音频流电话终端角色时,强制要求的上下行 LC3 Codec 配置: 16_1、32_1、32_2。

TMAP 用于单播音频流媒体接收角色时,强制要求的下行 LC3 Codec 配置: 48_1 ~ 48_6。

TMAP 用于广播音频流媒体接收角色时,强制要求的下行 LC3 Codec 配置: 48_1 ~ 48_6。

TMAP 媒体控制需要强制支持:播放和暂停

(1) TMASC/TMASS Event介绍

目前 TMAS Client/Server 没有定义相关事件。

(2) TMASC API: blc_audio_registerTMASControlClient

用于注册 TMAS Client 功能的 API:

void blc_audio_registerTMASControlClient(const blc_tmasc_regParam_t *param);

目前该 API 的传参没有用到 (预留扩展用), 输入 NULL 就可以了。主要跟 BAP Unicast Client / Broadcast Assistant 组合使用。

(3) TMASC API: blc_audio_registerTMASControlServer

用于注册 TMAS Server 功能的 API:

void blc_audio_registerTMASControlServer(const blc_tmass_regParam_t *param);

其中输入参数 param 参考结构体类型 blc_tmass_regParam_t 定义如下:

typedef struct{
    u16 role;       //blc_tamp_role_enum
} blc_tmass_regParam_t;

role 的枚举值类型为: blc_tamp_role_enum。

在 audio_unicast_server Demo 中我们使用了 CT + UMR 的角色组合,初始化代码如下:

const blc_tmass_regParam_t tmasParam = {
    .role = BLC_TMAP_ROLE_CALL_TERMINAL | BLC_TMAP_ROLE_UNICAST_MEDIA_RECEIVER,
};
blc_audio_registerTMASControlServer(&tmasParam);

主要跟 BAP Unicast Server/Broadcast Sink 组合使用,另外需要注意的是充当 TMAS 服务器的设备需要:

  • 扩展广播应包括外观 (Appearance ) 广播类型 (见核心规范补充 (Core Specification Supplement,简称 CSS))。外观值应反映外部设备的外观;

  • 扩展广播应包括服务数据 AD 类型,以及下图中显示的服务数据。

TMAP Role 特性值广播格式

#ifndef DEFAULT_TMAP_ROLE
#define DEFAULT_TMAP_ROLE           BLC_TMAP_ROLE_CALL_GATEWAY
#endif

const blc_adv_tmapRole_t advDefTmapRole = {
    .ltv.len = sizeof(blc_adv_tmapRole_t) - 1,
    .ltv.type = DT_SERVICE_DATA,
    .tamsUuid = SERVICE_UUID_TELEPHONY_AND_MEDIA_AUDIO,
    .tmapRole = DEFAULT_TMAP_ROLE,
};

注意:

目前我们的 SDK 没有做 UMS/UMR 跟 BMS/BMR的 组合,这需要设备同时支持 BIS 和 CIS,这种情况带宽会及其紧张。

HAP/HAS 助听

(1) HASC/HASS Event介绍

TBD

(2) HASC/HASS相关API介绍

TBD

PBP 公共广播

公共广播配置文件层次结构:

公共广播配置文件层次结构

PBP 的角色分为 (注意:这些角色允许组合使用):

  • 公共广播源 Public Broadcast Source (PBS)

  • 公共广播接收 Public Broadcast Sink (PBK)

  • 公共广播辅助器 Public Broadcast Assistant (PBA)

PBS 的实现都是在应用层实施的,相关的实现可以参考音频用例:vendor/audio_broadcast_source。

缩略语

Acronym/Abbreviation Meaning
ASCP/ASCS Audio Stream Control Profile / Service
ASCSC/ASCSS Audio Stream Control Service Client / Server
AICS Audio Input Control Profile / Service
AICSC/AICSS Audio Input Control Service Client / Server
BASP/BASS Broadcast Audio Scan Profile / Service
BASSC/BASSS Broadcast Audio Scan Service Client / Server
CSIP/CSIS Coordinated Set Identification Profile / Service
CSISC/CSISS Coordinated Set Identification Service Client / Server
CCP/TBS Call Control Profile / Telephone Bearer Service
TBSC/TBSS Telephone Bearer Service Client / Server
MCP/MCS Media Control Profile / Service
MCSC/MCSS Media Control Service Client / Server
MICP/MICS Microphone Control Profile / Service
MICSC/MICSS Microphone Control Service Client / Server
PACP/PACS Published Audio Capabilities Profile / Service
PACSC/PACSS Published Audio Capabilities Service Client / Server
VCP/VCS Volume Control Profile / Service
VCSC/VCSS Volume Control Service Client / Server
VOCS Volume Offset Control Profile / Service
VOCSC/VOCSS Volume Offset Control Service Client / Server

LE Audio Demo

概述

telink_b91m_ble_audio_sdk提供音频LE集成和示例,本章节将重点介绍 sdk/vendor/audio_xxx各个audio demo的使用方法,用户可以参考代码实现开发自己的LE音频产品。

特点:

  • LC3编解码器库

  • 集成LE音频配置文件

  • 支持同步通道的蓝牙控制器

  • 真正的无线立体声 (TWS) 解决方案

  • 示例应用程序

  • LE 音频 USB Dongle

  • 广播发射器、接收器、扫描代理器

  • TWS耳塞

  • Headset耳机

LE Audio按照功能分可以分为音频发射器和音频接收器两个角色。其中音频发射器包括Unicast Client(CIS)和Broadcast Source/Auracast^TM^ Transmitter(BIS),音频接收器包括Unicast Server(CIS)和Broadcast Sink/Auracast^TM^ Receiver(BIS)。

音频发射器SDK中实现的流程图如下所示。主要分为音频采集、音频算法、LC3编码、IAL层接收SDU、控制器拆分SDU、RF发射器。

LE_Audio_Transmitter

音频采集: 音频采集模块主要将音频信号采集为数字信号。音频的输入方式主要有USB、Line-in、AMIC、IIS等多种方式。

音频算法: 音频算法模块主要负责将原始的音频数据,进行一些处理,如ASRC/消噪等。这部分SDK中目前没有实现,SDK收发的都是原始PCM数据。

LC3编码: LC3编码模块主要负责将原始PCM数据,按照Profile交互的LC3编码参数,编码为LC3数据。

音频采集、音频算法、LC3编码三个模块都属于应用层,user开发主要也是在这三个模块上进行开发。

IAL层接收SDU: LC3编码后的数据,通过控制器提供的接口,发送到IAL层,作为一个SDU数据。SDU是音频发射器和音频接收器之间通讯的最小数据包,一个SDU可能包含多个声道数据。

控制器拆分SDU: 由于SDU的数据量可能会很大(SDU可以包含多个声道),控制器之间会协商PDU参数,PDU是控制器之间通讯的最小数据包。音频发射器会将SDU拆分成一个或多个PDU,依次发送到RF发射器。音频接收器会将接收到的一个或多个PDU,重组为SDU后上报到更高层。

RF发射器: RF发射器主要负责将PDU数据,按照特定的时序/编码方式,转化为无线信号发射出来。

IAL层接收SDU、控制器拆分SDU、RF发射器三个模块都属于Stack的部分,user开发不需要关心这部分内容。只需要关心IAL提供的发送SDU数据接口即可。

音频接收器SDK中实现的流程图如下所示。主要分为音频播放、音频算法、LC3解码、IAL层上报SDU、控制器重组SDU、RF接收器。

LE_Audio_Receiver

RF接收器: RF接收器主要负责将空中的无线信号,按照特定的时序/编码方式,转化为PDU数据。

控制器重组SDU: RF接收器接收到的一个或多个PDU数据,需要在控制中重组为SDU。

IAL层上报SDU: IAL层会将SDU数据上报给更高层。

RF接收器、控制器重组SDU、IAL层上报SDU三个模块都属于Stack的部分,user开发不需要关心这部分内容。只需要关心IAL提供的查询有无上报的SDU数据接口即可。

LC3解码: LC3解码模块主要负责收到SDU数据,按照Profile交互的LC3解码参数,解码为音频数据。

音频算法: 音频算法模块主要负责将LC3解码后的音频数据,进行一些处理,如ASRC/消噪等。这部分SDK中目前没有实现,SDK收发的都是原始PCM数据。

音频播放: 音频播放模块主要将接收到的PCM数据通过芯片Code/USB输出。音频的输出方式主要有USB、Line-out、IIS等多种方式。

LC3解码、音频算法、音频播放三个模块都属于应用层,user开发主要也是在这三个模块上进行开发。

注意:

BIS/CIS只是一个数据传输的通道,数据的格式具体是什么含义,BIS是通过在周期性广播中携带BASE字段来解析数据格式。CIS是在ASCS层交互的Config Codec Operation操作来解释数据格式。SDK默认BIS/CIS传输LC3编解码后的音频数据。

硬件清单

TLSR9518ADK80D通用EVK

TLSR9518ADK80D EVK

TLSR9518ADK80D通用EVK是TLSR95xx系列芯片的开发板,开发板集成了Mini USB, RF SMA, 4个可编程按键,4个LED灯,差分音频输入,差分音频输出,数字MIC,模拟MIC。PCB资料可以参考Wiki: B91 Development Board。硬件手册可以参考Wiki: B91 Generic Starter Kit

下面对TLSR9518ADK80D通用EVK简称9518 EVK。

TLSR9517CDK56D音频专用板

TLSR9517CDK56DAudioDB

TLSR9517CDK56D音频专用板是TLSR95xx系列芯片针对音频开发定制的评估板。开发板集成了Mini USB, RF SMA, 3个可编程按键,4个LED灯,模拟MIC,单端音频输入,单端音频输出等模块。用户手册和PCB资料可以参考Wiki: B91 Audio Starter Kit

下面对TLSR9517CDK56D音频专用板简称9517音频板。

TLSR9518 USB Dongle

TLSR9518 Dongle

TLSR9518 USB Dongle是TLSR95xx系列芯片开发的USB dongle的评估板。开发板集成了5个LED,Mini USB,2个可编程按键,差分音频输出,模拟MIC等模块。PCB资料可以参考Wiki: B91 Dongle

下面对TLSR9518 USB Dongle简称9518 Dongle。

  • 9518 EVK是一个TLSR9518通用开发平台,LE Audio Demo所有的代码都能在这个平台上运行。

  • 9517音频板主要将音频输入和输出接口,从9518 EVK上的差分输入/输出改成单端输入/输出,普通的3.5mm接口耳机都能正常使用。

  • 9518 Dongle是针对USB dongle应用环境的简化,实现和USB-A设备之间直连,省去Mini USB 转USB-A线的连接。

应用模式

LE Audio Demo集成了连接同步流(CIS)模式和广播同步流(BIS)模式的所有场景,具体的取决于使用哪个demo测试。

Application

Unicast Audio Demo分为两个角色,分别为Unicast Client和Unicast Server。

SDK提供CIS demo主要应用于TWS耳机和Headset场景,Unicast Client充当音频源设备:模拟手机或电脑端,Unicast Server充当音频接收设备:模拟TWS耳机或Headset端。TWS场景的Unicast Audio Demo的搭建需要三块开发板,一块烧录Unicast Client固件,两块烧录Unicast Server固件,如下图所示。

TWS_Soluntion

Broadcast Audio Demo分为三个角色,分别是Broadcast Source、Broadcast Sink和Broadcast Assistant。广播音频的测试环境如下图所示。

Broadcast_Soluntion

audio_unicast_client

  • 功能:单播音频场景模拟手机电脑端
  • 主要硬件:TLSR9518ADK80D EVK开发板 (或者TLSR9518ADK80D Dongle)

(1) Unicast Client Demo文件结构

Unicast Client Demo位于sdk vender目录下

Unicast_Client

Telink Unicast Client工程提供了两个demo:使用USB处理音频数据的demo和使用内置Codec处理音频数据的demo。两个demo除音源不同(涉及音频数据获取,音频播放方式),其余流程完全相同。本文以从Codec获取音频数据为例进行介绍。

Unicast_Client_Codec

app_audio.c: 定义了核心音频流处理流程,通过Profile层事件回调,结合APP层用户偏好,启动,配置,释放音频流。

app_codec.c: 定义了codec硬件处理,音频发送,接收,播放,lc3编解码,PLC补包等流程。

其他文件定义及作用请参考Telink_BLE_Multi_Connection_SDK_Developer_Handbook

Telink Unicast Client支持TWS模式和Headset模式。

(2) Unicast Client音频流处理

参考app_audio.c与app_audio.h

static int app_audio_prfEvtCb(u16 connHandle, int evtID, u8 *pData, u16 dataLen)

Profile层事件回调是所有音频流处理的入口。用户需要通过Profile层事件回调获取Unicast Server的状态,并正确的配置音频流。Unicast Client需要使用的Profile层事件如下:

(a) ACL Connnect & ACL Disconnect

AUDIO_EVT_ACL_CONNECT,
AUDIO_EVT_ACL_DISCONNECT,

用户通过该事件,获取当前ACL连接数量,ACL状态。并可以根据需要,在该事件回调中开启/关闭扩展扫描。

(b) CIS Connnect & CIS Disconnect

AUDIO_EVT_CIS_CONNECT,
AUDIO_EVT_CIS_DISCONNECT,

用户通过该事件,获取CIS连接状态。CIS相关操作由Profile层接管,该事件的设立是为了让用户更好的理解音频流建立过程,用户无需在该事件回调中执行任何操作。

(c) SDP Found & SDP Fail

AUDIO_EVT_CLIENT_SDP_FOUND,
AUDIO_EVT_CLIENT_SDP_FAIL,

Profile注册后,第一次ACL连接,Unicast Client会对Unicast Server进行注册Profile的Service Discovery,如果服务发现成功,Profile层会启动'AUDIO_EVT_CLIENT_SDP_FOUND'回调。如果服务发现失败,会启动'AUDIO_EVT_CLIENT_SDP_FAIL'回调。用户可以清晰的得知自己注册Profile在Unicast Server端的存在情况。每个Profile事件对应的ID参考'audio_service_role_enum'。

(d) ALL SDP OVER

AUDIO_EVT_CLIENT_ALL_SDP_OVER

所有注册Profile Service Discovery完成。用户可以选择在该回调中,启动音频流。

启动音频流之前,用户需要首先确定自己偏好的音频场景。

typedef enum{
    BLC_AUDIO_1_SVR_1_SINK_1_CHN_1_SRC_N_CHN_N_CISES_1_STREAMS_1,
    BLC_AUDIO_2_SVR_1_SINK_N_CHN_N_SRC_1_CHN_1_CISES_1_STREAMS_1,
    BLC_AUDIO_3_SVR_1_SINK_1_CHN_1_SRC_1_CHN_1_CISES_1_STREAMS_2,
    BLC_AUDIO_4_SVR_1_SINK_1_CHN_2_SRC_N_CHN_N_CISES_1_STREAMS_1,
    BLC_AUDIO_5_SVR_1_SINK_1_CHN_2_SRC_1_CHN_1_CISES_1_STREAMS_2,
    BLC_AUDIO_6I_SVR_1_SINK_2_CHN_1_SRC_N_CHN_N_CISES_2_STREAMS_2,
    BLC_AUDIO_6II_SVR_2_SINK_2_CHN_1_SRC_N_CHN_N_CISES_2_STREAMS_2,
    BLC_AUDIO_7I_SVR_1_SINK_1_CHN_1_SRC_1_CHN_1_CISES_2_STREAMS_2,
    BLC_AUDIO_7II_SVR_2_SINK_1_CHN_1_SRC_1_CHN_1_CISES_2_STREAMS_2,
    BLC_AUDIO_8I_SVR_1_SINK_2_CHN_1_SRC_1_CHN_1_CISES_2_STREAMS_3,
    BLC_AUDIO_8II_SVR_2_SINK_2_CHN_1_SRC_1_CHN_1_CISES_2_STREAMS_3,
    BLC_AUDIO_9I_SVR_1_SINK_N_CHN_N_SRC_2_CHN_1_CISES_2_STREAMS_2,
    BLC_AUDIO_9II_SVR_2_SINK_N_CHN_N_SRC_2_CHN_1_CISES_2_STREAMS_2,
    BLC_AUDIO_10_SVR_1_SINK_N_CHN_N_SRC_1_CHN_2_CISES_1_STREAMS_1,
    BLC_AUDIO_11I_SVR_1_SINK_2_CHN_1_SRC_2_CHN_1_CISES_2_STREAMS_4,
    BLC_AUDIO_11II_SVR_2_SINK_2_CHN_1_SRC_2_CHN_1_CISES_2_STREAMS_4,
    BLC_AUDIO_STD_AUDIO_CONFIGURATIONS_E_MAX,
} std_unicast_aud_cfg_enum;

关于音频场景的具体描述请参考Bluetooth SIG官方文档BAP_v1.0.1, 4.4 Multiple-channel LC3 unicast audio

audio_error_enum blc_bapuc_checkAudioConfigures(u16 aclHandle, std_unicast_aud_cfg_enum audCfgIdx, blc_audio_ase_cfg_info_t *outChnInfo);

用户确认需要使用的具体音频场景后,调用API 'blc_bapuc_checkAudioConfigures'获得该音频场景对应的参数,例如有几个Unicast Server,每个Unicast Server需要配置几个Endpoint,每个Endpoint可以使用的Audio Location等。

audio_error_enum blc_bapuc_setASEOperationConfigCodec(u16 aclHandle, audio_dir_enum dir, u8 idx, blc_audio_std_codec_settings_e codecCfgIdx, blc_audio_ase_cfg_info_t *pAseCfgInfo)

获取用户偏好的音频场景对应的参数后,用户通过调用'blc_bapuc_setASEOperationConfigCodec'启动音频流(Configure Codec操作)。

(e) Codec Configured&QoS Configured & Enabling & streaming & Disabling & Releasing

    AUDIO_EVT_UNICAST_CLIENT_CODEC_CONFIGURED,
    AUDIO_EVT_UNICAST_CLIENT_QOS_CONFIGURED,  
    AUDIO_EVT_UNICAST_CLIENT_ENABLING,
    AUDIO_EVT_UNICAST_CLIENT_DISABLING,
    AUDIO_EVT_UNICAST_CLIENT_UPDATE,
    AUDIO_EVT_UNICAST_CLIENT_RELEASING,
    AUDIO_EVT_UNICAST_CLIENT_RECEIVE_STREAMING,
    AUDIO_EVT_UNICAST_CLIENT_SEND_STREAMING,

Unicast Server端ASE状态回调。Unicast Client对Unicast Server的操作造成Unicast Server端ASE状态转变时,Unicast Server会把状态告诉Unicast Client。Unicast Client收到后,上报给APP层,APP层收到后结合自身偏好,执行后续动作。

Unicast Client端ASE状态回调请结合Unicast Client EventUnicast Client API理解。

(3) Unicast Client Codec处理

SDK将硬件codec处理,如codec初始化,codec打开关闭,音频数据获取,播放等操作,封装成一个子模块,请用户直接参考SDK目录

  • vender/common/tlk_codec.c

  • vender/common/tlk_codec.h

(a) codec初始化

Unicast Client在音频流配置中掌握完全主动的地位,所以Telink LE Audio SDK默认Unicast Client的音频场景是确定的,用户可以直接在初始化中配置硬件codec。

(b) codec输入

Unicast Client端codec输入对应音频数据的发送。Client与Server端的音频流完全建立之后(如果下行存在),Client可以开始音频数据的采集,编码,发送流程。该流程在'app_audio_send_process'中完成。

static void app_audio_send_process(void);

TWS场景和Headset场景对应的codec输入流程有所不同。

  • TWS场景对应的codec输入:Client将双声道音频数据编码后分别发送给两个Unicast Server。

  • Headset场景对应的codec输入:Client将双声道音频数据编码拼接成一个SDU发送给一个Unicast Server(多路复用技术)。

(c) codec输出

codec输出对应音频数据的接收。Client与Server端的音频流完全建立之后(如果上行存在),Client可以开始音频接收,解码,播放流程。

LE Audio协议规定,Unicast Client端每一包音频数据(SDU)在接收时都附带一个timestamp(参考《Core_V5.4》, Vol 6, Part G 3.2, SDU synchronization reference),是这一包数据对应的发送时间(过去的时间),该时间加Client与Server端确定的传输延时(Transport Latency)和演示延时(Presentation Delay)就是这一包对应的Render Ponit。Client需要保证接收到的每一包音频数据在对应的Render Ponit播放出来。

为了确保Client端收到的每一个SDU在固定时间点(Render Point)播放,Client引入了Timer0中断。音频数据包附带的Render Point作为Timer0的capture时间点。

为了兼容性和APP层代码的高效简洁,Telink LE Audio SDK在音频数据播放时引入了动态内存分配机制。具体的应用原理为:

Client端在收到音频数据后,经过解码得到原始的PCM音频数据,但此时还没有到达Render Ponit,此时Client使用malloc将这一笔音频数据存到链表中。经过连续操作,Client会得到Render Point有序的链表。链表中Render Point由近到远排列,链表头(空)的下一个节点需要在最近的时间点播放,链表尾需要在最远的时间点播放。Render Point有序链表配合Timer中断,即可使音频数据源源不断的播放。

动态内存分配子模块定义与使用请参考SDK目录:

  • common/buf_pool0/

TWS场景和Headset场景对应的codec输出流程有所不同。

  • TWS场景对应的codec输出:Client接收两个server端传来的两个声道数据,根据SDU附带的timestamp(同一个CIG Event如果有多个CIS Event,每个CIS Event接收到的SDU其timestamp是一样的,参考CIS同步处理),将其合并成双声道数据通过codec播放。

  • Headset场景对应的codec输出:Client接收一个Server端传来的单声道数据,解码播放。

(4) Unicast Client LC3编解码处理

(a) 初始化

Unicast Client的SDP流程结束之后,用户确定自己需要使用的音频场景,然后根据该音频场景对应的音频参数,即可启动配置Unicast Server音频流的第一步Configure Codec。此时Unicast Client可以确定LC3初始化所需要的参数并启动LC3初始化流程。

(b) PLC补包

Client端接收数据时,Controller会按照固定Interval均匀的上报固定长度的SDU。如果SDU的长度不对,就可以判定底层的Controller有丢包情况,此时APP层可启动LC3 PLC补包流程。PLC流程能够增加音频链路的抗干扰性,在不影响音频体验的情况下增加音频传输距离。

(5) Unicast Client Demo使用流程

(a) 固件编译

在app_config.h选择应用场景(TWS或Headset)

#define APP_SCENE_TWS                               0
#define APP_SCENE_HEADSET                           1

#define APP_AUDIO_SCENE                             APP_SCENE_HEADSET

在app_audio.h中确定音频场景,偏好的音频参数,偏好的连接参数

#define APP_AUDIO_CONFIGURATION_PREFER   
#define APP_AUDIO_CODEC_INPUT_PARAMETER_PREFER   
#define APP_AUDIO_CODEC_OUTPUT_PARAMETER_PREFER  

#define APP_AUDIO_QOS_INPUT_PARAMETER_PREFER     
#define APP_AUDIO_QOS_OUTPUT_PARAMETER_PREFER    

在app_audio.c中配置音频场景和音频参数对应的硬件codec

tlk_codec_config(TLK_CODEC_INPUT.....)
tlk_codec_config(TLK_CODEC_OUTPUT.....)

所有配置完成后,编译固件,下载到对应硬件中。

(b) 连接/绑定

Telink LE Audio SDK提供了一套基本的连接配对流程,用户可以参考'app.c'与'app_ui.c',具体原理为:

Unicast Client与Unicast Server在第一次配对时,需要使用按键手动连接。Unicast Client与Unicast Server距离需要足够近,Rssi强度满足要求,Unicast Client就会启动连接配对流程。

  • 第一次连接后,Unicast Client会存储连接信息和Unicast Server端的音频信息,信息存储之后,Unicast Client与对应的Unicast Server后续连接会走回连流程,无需按键。音频信息的绑定请参考音频存储

  • 如果是TWS场景,一个Unicast Client对两个Unicast Server的情况,Unicast Client连接到一个Unicast Server后,会通过CSIP协议自动连接另一个Unicast Server。CSIP协议具体内容请参考CSIP/CSIS 协调设备

audio_unicast_server

  • 功能:单播音频场景,模拟TWS耳机或Headset端
  • 主要硬件:TLSR9518ADK80D EVK开发板 (或者TLSR9517CDK56D音频专用板)

注:Headset场景只需要一块开发板。TWS场景需要两块开发板。

Unicast Server Demo文件结构

Unicast Server Demo位于sdk vender目录下

Unicast_Client

Telink Unicast Server工程提供了Unicast Server基础版本,用户可以在此基础上进行完善补充以及二次开发。

app_audio_init.c:定义了Unicast Server初始化流程,如扩展广播初始化,音频能力初始化等。

app_audio_call.c:定义了APP层通话控制流程,包括接听,挂断,保持,以及通话状态上报等内容。

app_audio_media.c:定义了APP层媒体控制流程,包括媒体状态上报,媒体控制(上一首,下一首,暂停,播放)等。

app_audio_volume.c:定义了APP层音量控制流程,如音量控制,mute/unmute等内容。

app_audio_ctrl.h:APP层Audio总体控制,包含了通话控制,音量控制,媒体控制。

app_audio.c:定义了核心音频流控制流程,包括音频流配置,启动,释放等。

app_codec.c:定义了codec处理流程,如音频数据接收,发送,lc3编解码等。

Telink Unicast Server支持Headset模式和TWS模式。

(1) Unicast Server音频处理

参考app_audio.c和app_audio.h

static int  app_audio_prfEvtCb(u16 connHandle, int evtID, u8 *pData, u16 dataLen);

Profile层事件回调是所有音频流处理的入口。用户需要通过 Profile层事件回调获取ASE的状态,并根据ASE状态配置执行APP层相应操作。Unicast Server需要使用的Profile层事件如下:

(a) ACL Connnect & ACL Disconnect

AUDIO_EVT_ACL_CONNECT,
AUDIO_EVT_ACL_DISCONNECT,

用户通过该事件,获取当前ACL状态。并可以根据需要,在该事件回调中开启/关闭扩展广播。

(b) CIS Connnect & CIS Disconnect & CIS REQUEST

AUDIO_EVT_CIS_CONNECT,
AUDIO_EVT_CIS_DISCONNECT,
AUDIO_EVT_CIS_REQUEST,

用户通过该类事件,获取CIS连接状态。CIS相关操作由Profile层接管,该事件的设立是为了让用户更好的理解音频流建立过程,用户无需在该事件回调中执行任何操作。

(c) SDP FOUND & SDP FAIL & SDP OVER

AUDIO_EVT_CLIENT_SDP_FOUND,
AUDIO_EVT_CLIENT_SDP_FAIL,
AUDIO_EVT_CLIENT_ALL_SDP_OVER,

Profile注册后,第一次ACL连接时,Unicast Server会对Unicast Client进行注册Profile的Service Discovery,如果服务发现成功,Profile层会启动'AUDIO_EVT_CLIENT_SDP_FOUND'回调。如果服务发现失败,会启动'AUDIO_EVT_CLIENT_SDP_FAIL'回调。用户可以清晰的得知自己注册Profile在Unicast Server端的存在情况。每个Profile事件对应的ID参考'audio_service_role_enum'。

(d) Codec Configured&QoS Configured & Enabling & streaming & Disabling & Releasing

    AUDIO_EVT_BAPUS_CODEC_CONFIGURED,
    AUDIO_EVT_BAPUS_QOS_CONFIGURED,  
    AUDIO_EVT_BAPUS_ENABLING,
    AUDIO_EVT_BAPUS_UPDATE_METADATA,
    AUDIO_EVT_BAPUS_RELEASING,
    AUDIO_EVT_BAPUS_DISABLING,
    AUDIO_EVT_BAPUS_RECEIVE_STREAMING,
    AUDIO_EVT_BAPUS_SEND_STREAMING,

ASE状态回调,Unicast Client的操作造成Unicast Server端的ASE状态发生改变时,Unicast Server端Profile层会通过事件回调通知APP层,APP层收到通知后,可以配合Profile层执行相应操作,如配置硬件codec,开始接收/发送音频数据。

Unicast Server端ASE状态回调请结合Unicast Server EventUnicast Server API理解。

(3) Unicast Server Codec处理

SDK将硬件codec处理,如codec输入输出初始化,codec打开关闭,音频数据的获取,输出等操作,封装成了一个子模块,请用户直接参考SDK目录。

  • vender/common/tlk_codec.c

  • vender/common/tlk_codec.h

(a) codec初始化

在音频流的配置中,Unicast Server将自己的音频能力通过PAC暴露给Unicast Client,Unicast Client获取Unicast Server的音频能力后结合自身偏好选择一组音频参数配置Unicast Server,启动音频流。在这个过程中,Unicast Server是被动的,需要根据Unicast Client配置的参数,实时更新自己的codec配置。

Unicast Server通过ASE状态回调'codec configured'获取音频端点参数,获取所有音频端点参数后(qos configured状态上报即可认为所有codec参数配置完成),Unicast Server在本地配置lc3和codec。

(b) codec输入

static void app_audio_send_process(void);

codec输入对应音频数据的发送。Client与Server端的音频流完全建立之后(如果音频上行存在),Client可以开始音频数据的采集,编码,发送流程。该流程在'app_audio_send_process'中完成。

TWS场景和Headset场景对应的codec输入流程相同,都是把单声道音频数据发送给Unicast Client。

(c) codec输出

static void app_codec_receive_process(void);

codec输出对应音频数据的接收。Client与Server端的音频流完全建立之后(如果音频下行存在),Server可以开始音频接收,解码,播放流程。

LE Audio协议规定,Unicast Server端每一包音频数据(SDU)在接收时都附带一个timestamp(参考《Core_V5.4》, Vol 6, Part G 3.2, SDU synchronization reference),是这一包数据对应的渲染时间(未来的时间),该时间加Client与Server端确定的演示延时(Presentation Delay)就是这一包对应的Render Ponit。Server需要保证接收到的每一包音频数据在对应的Render Ponit播放出来。

为了确保Client端收到的每一个SDU在固定时间点(Render Point)播放,Server引入了Timer0中断。音频数据包附带的Render Point作为Timer0的capture时间点。

为了兼容性和APP层代码的高效简洁,Telink LE Audio SDK在音频数据播放时引入了动态内存分配机制。具体的应用原理为:

Server端在收到音频数据后,经过解码得到原始的PCM音频数据,但此时还没有到达Render Ponit,此时Server端使用malloc将这一笔音频数据存到链表中。经过连续操作,Server会得到Render Point有序的链表。链表中Render Point由近到远排列,链表头(空)的下一个节点需要在最近的时间点播放,链表尾需要在最远的时间点播放。Render Point有序链表配合Timer中断,即可使音频数据源源不断的播放。

动态内存分配子模块定义与使用请参考SDK目录:

common/buf_pool0/

Unicast Server端TWS场景和Headset场景对应的codec输出流程有所不同。

  • TWS场景对应的codec输出:Server端接收Client端传输的单声道音频数据解码并播放。

  • Headset场景对应的codec输出:Server端接收Client端传输的双声道音频数据(多路复用,一个SDU包含两个声道的数据),解码后合并成双声道数据播放。

(4) Unicast Server LC3编解码处理

(a) 初始化

Unicast Server获取Unicast CLient配置的所有音频端点参数后,将codec和lc3同时初始化。请参考Unicast Server codec初始化

(b) PLC补包

Unicast Server端接收数据时,Controller会按照固定Interval均匀的上报固定长度的SDU。如果SDU的长度不对,就可以判定底层Controller有丢包情况,此时APP层可启动LC3 PLC补包流程。PLC流程能够增加音频链路的抗干扰性,在不影响音频体验的情况下增加音频传输距离。

(5) Unicast Server 媒体控制

参考'app_audio_media.c'。

Telink LE Audio SDK提供标准的媒体控制服务。用户可以在初始化中注册MCP模块。

void blc_audio_regMcpMediaCtrlClt(const blc_mcpc_regParam_t *param)

MCP模块注册后,Unicast Client连接Unicast Server后,Unicast Server会查询Unicast Client端的MCS服务,如果查询成功,Profile层会通过事件'AUDIO_EVT_CLIENT_SDP_FOUND'通知APP层。如果查询失败,Profile层会通过'AUDIO_EVT_CLIENT_SDP_FAIL'通知APP层。

MCS查询成功,用户即可通过MCP层事件回调实时获取Unicast Client端的各种媒体相关信息。

void app_media_event_callback(u16 connHandle, int evtID, u8 *pData, u16 dataLen)

用户也可以调用MCP层API。主动去读取Unicast Client端各种媒体相关信息。请参考MCP/MCS媒体控制

(6) Unicast Server通话控制

参考'app_audio_call.c'

Telink LE Audio SDK提供标准的通话控制服务。用户可以在初始化中注册CCP模块。

void blc_audio_registerCallControlClient(const blc_ccpc_regParam_t *param)

CCP模块注册后,Unicast Client连接Unicast Server后,Unicast Server会查询Unicast Client端的TBS服务,如果查询成功,Profile层会通过事件'AUDIO_EVT_CLIENT_SDP_FOUND'通知APP层。如果查询失败,Profile层会通过'AUDIO_EVT_CLIENT_SDP_FAIL'通知APP层。

TBS服务查询成功,用户即可通过CCP层事件回调实时获取Unicast Client端通话相关信息。用户也可以通过调用CCP层API,主动获取Unicast Client端通话状态。请参考CCP/(G)TBS呼叫控制

(7) Unicast Server音量控制

参考'app_audio_volume.c'

Telink LE Audio SDK提供标准的音量控制服务。用户可以在初始化中注册音量控制服务。

void blc_audio_registerVCSControlServer(const blc_vcss_regParam_t *param)

Unicast Client端对音量进行调节时,Unicast Server会通过Profile层事件回调实时通知APP层。

void app_volume_event_callback(u16 connHandle, int evtID, u8 *pData, u16 dataLen)

用户可以在音量控制事件回调中实时更新音量。

用户也可以本地调节音量,相关API请参考VCP/VCS音量控制

(8) Unicast Server Demo使用流程

在app_config.h中选择硬件类型,Telink Unicast Server支持TLSR9518ADK80D EVK开发板或者TLSR9517CDK56D音频专用板。

#define TLSR9517CDK56D            1
#define TLSR9518ADK80D            2

#define HW_VERSION               TLSR9518ADK80D

在app_config.h中选择应用场景,Telink Unicast Server支持TWS场景或Headset场景。

#define APP_SCENE_TWS                               0
#define APP_SCENE_HEADSET                           1

#define APP_AUDIO_SCENE                             APP_SCENE_HEADSET

在app_audio_init.c中选择自己支持的音频场景,注意,如果是TWS场景,需要对应修改audio location,请参考demo示例。

const blc_pacss_regParam_t pacsParam = {
    .sinkPacNum = ARRAY_SIZE(sinkPac),
    .sinkPac = &sinkPac[0],
    .sinkAudioLocations = AUDIO_LOCATION_FLAG_USED,
    .sourcePacNum = ARRAY_SIZE(sourcePac),
    .sourcePac = &sourcePac[0],
    .sourceAudioLocations = AUDIO_LOCATION_FLAG_USED,
    .availableSinkContexts = AUDIO_UNICAST_SERVER_DEFAULT_CONTEXT,
    .availableSourceContexts = AUDIO_UNICAST_SERVER_DEFAULT_CONTEXT,
    .supportedSinkContexts = AUDIO_UNICAST_SERVER_DEFAULT_CONTEXT,
    .supportedSourceContexts = AUDIO_UNICAST_SERVER_DEFAULT_CONTEXT,
};

用户也可以在扩展广播中增加自己需要的定制广播类型。

    blc_adv_ltv_t *adv_ltvs[] = {
            (blc_adv_ltv_t *) &advDefFlags,
            (blc_adv_ltv_t *) &advDefCompleteName,
            (blc_adv_ltv_t *) &advDefAppearance,
            (blc_adv_ltv_t *) &advIncompleteList,
            (blc_adv_ltv_t *) &advCsisRsi,
            (blc_adv_ltv_t *) &capTargetAnnouncement,
            (blc_adv_ltv_t *) &bapTargetDefAnnouncement,
            (blc_adv_ltv_t *) &advTmapRole,
            };
    u8 advData[255];
    u8 adv_ext_len = blc_adv_buildAdvData(adv_ltvs,ARRAY_SIZE(adv_ltvs),advData);

所有配置完成之后,编译固件,下载到对应的硬件中。

audio_broadcast_source

  • 功能: Broadcast Source主要是负责将采集到的音频,通过BIS通道广播出去。
  • 主要硬件:9518 Dongle x 2

(1) 广播音频流和广播包

下图展示了一个典型的广播源如果传输BIS数据,一个BIS数据广播,必须包含扩展广播、周期性广播、BIS三个部分。

广播源传输EA、PA和BIS数据包的关系

BIS传输可以分成以下几个流程。

  • 在主要广播通道(37/38/39)上发送ADV_EXT_IND广播包。该广播包中携带AdvA, ADI, AuxPtr字段。

  • AuxPtr字段能引导出AUX_ADV_IND广播包。该广播包中携带ADI,SyncInfo,ADVData(Broadcast Audio Announcement Service UUID, Broadcast_ID)。包括可选的AuxPtr。

  • 如果AUX_ADV_IND广播包的ADVData数据在不能一包完成传输,需要AuxPtr字段引导出AUX_CHAIN_IND广播包。(可选,非必须)

  • SyncInfo字段引导出AUX_SYNC_IND广播包。该广播包中写道ACAD(BIGInfo), ADVData(Basic Audio Announcement Service UUID, BASE)。以及可选的AuxPtr。

  • 如果AUX_SYNC_IND广播包的ADVData数据不能再一包完成传输,需要AuxPtr字段引导出AUX_CHAIN_IND广播包。(可选,非必须)

  • BIGInfo字段引导出BIS广播包,实现广播音频流的建立。

每个广播包的内容可以参《Core_V5.4》, 4.4 Non-connected states章节。

user开发者不需要关心扩展广播、周期性广播、BIS的实现细节。只需要关心几个API参数即可。

(2) 创建扩展广播

关于扩展广播的理论,可以参考《Core_V5.4》, Vol 6, 4.4.2 Advertising state 章节的描述。本小节主要介绍Broadcast source demo中user开发中可能使用到的部分。

创建扩展广播的API是blc_ll_setExtAdvParam,用户需要修改的是广播间隔、SID两个参数。API更多的参数可以参考《Core_V5.4》, Vol 3, 7.8.53 LE Set Extended Advertising Parameters command章节的描述。

    u32  my_adv_interval_min = ADV_INTERVAL_100MS;
    u32  my_adv_interval_max = ADV_INTERVAL_100MS;
    // Extended, None_Connectable_None_Scannable undirected, with auxiliary packet
    blc_ll_setExtAdvParam( ADV_HANDLE0,         ADV_EVT_PROP_EXTENDED_NON_CONNECTABLE_NON_SCANNABLE_UNDIRECTED, my_adv_interval_min,    my_adv_interval_max,
                           BLT_ENABLE_ADV_ALL,  OWN_ADDRESS_PUBLIC,                                             BLE_ADDR_PUBLIC,        NULL,
                           ADV_FP_NONE,         TX_POWER_3dBm,                                                  BLE_PHY_1M,             0,
                           BLE_PHY_2M,          PRIVATE_EXT_FILTER_SPECIFIC_SID,                                0);

设置扩展广播数据必须要包含Broadcast_ID,PBP Feature,Broadcast Name。

Broadcast_ID: 是BAP规范定义的,广播源必须要携带的参数,Broadcast_ID是第一次上电生成的随机数。具体的参考BAP_v1.0.1, 3.7.2.1 Broadcast Audio Announcements章节。SDK中默认用DEFAULT_BROADCAST_ID宏来替代。

PBP FeatureBroadcast Name: 是PBP规范定义的,广播源需要携带的参数。 SDK中默认用DEFAULT_BROADCAST_NAME宏来设置Broadcast Source。PBP Feature默认不支持元数据设置。

厂商自定义数据: 这个数据是测试的数据,方便Broadcast Sink快速的扫描到Source准备的。

    u8 advData[255];
    blc_adv_ltv_t *adv_ltvs[] = {
            (blc_adv_ltv_t *) &advDefFlags,
            (blc_adv_ltv_t *) &advDefCompleteName,
            (blc_adv_ltv_t *) &advDefBroadcastId,
            (blc_adv_ltv_t *) &advDefPbpFeature,
            (blc_adv_ltv_t *) &advDefBcastName,
            (blc_adv_ltv_t *) &manDataField,
            };
    u8 adv_ext_len = blc_adv_buildAdvData(adv_ltvs, ARRAY_SIZE(adv_ltvs), advData);

(3) 创建周期性广播

关于周期性广播的理论,可以参考《Core_V5.4》, Vol 6, 4.4.2.12 Periodic advertising章节的描述。本小节主要介绍Broadcast source demo中user开发中可能使用到的部分。

创建周期性广播的API是blc_ll_setPeriodicAdvParam,用户需要修改的是周期性广播间隔参数。API更多的参数可以参考《Core_V5.4》, Vol 6, 7.8.61 LE Set Periodic Advertising Parameters command章节的描述。

u32  my_per_adv_itvl_min = PERADV_INTERVAL_100MS;
u32  my_per_adv_itvl_max = PERADV_INTERVAL_100MS;
blc_ll_setPeriodicAdvParam( ADV_HANDLE0, my_per_adv_itvl_min, my_per_adv_itvl_max, PERD_ADV_PROP_MASK_TX_POWER_INCLUDE);

周期性广播数据主要是BASE数据,SDK中这个数据是通过blc_bap_setBASEToAddress来生成的,可以参考Broadcast Source API

u8 pdaAdvData[256];
blc_bap_setBASEToAddress(&bisSource.BASE, pdaAdvData);
blc_ll_setPeriodicAdvData( ADV_HANDLE0, blc_bap_calculateBASELength(&bisSource.BASE), &pdaAdvData[0]);

(4) 创建BIS广播

关于BIS广播的理论,可以参考《Core_V5.4》, Vol 6, 4.4.6 Isochronous Broadcasting state 章节的描述。本小节主要介绍Broadcast source demo中user开发中可能使用到的部分。

创建BIG广播有两个接口blc_hci_le_createBigParams和blc_hci_le_createBigParamsTest。两者侧重的参数不同,前者更高层只设置总的重传次数,由控制器根据带宽情况计算BIGInfo后,创建BIG广播;后者是指定具体的BIGInfo参数,控制器只负责创建BIG广播。关于这两个API的输入参数说明参考《Core_V5.4》, Vol 4, Part E, 7.8.103 LE Create BIG command和7.8.104 LE Create BIG Test command。

SDK出于演示的考虑使用的是blc_hci_le_createBigParamsTest接口来创建BIG广播,user可以修改BIG的参数NSE、BN、IRC、PTO等参数。BIG广播创建后会上报HCI_SUB_EVT_LE_CREATE_BIG_COMPLETE事件,user需要保存对应的BIG句柄和BIS句柄,后面往ISO层发送数据,需要依据BIS句柄索引。

(5) 广播源常修改的参数

SDK将Broadcast Source需要修改的参数,都定义在bisSource参数中。首先介绍下app_bisSource_param_t结构体的参数以及函数。

typedef struct {
    blc_adv_ltv_t ltv;
    u16 company_id;
    u8 configuration;
} adv_ext_manufacturer_data_field;

typedef struct{
    u8 numBis;               //only supported 1/2
    u8 nse;
    u8 bn;
    u8 irc;
    u8 pto;
    u8 enc;
    char broadcastCode[16];
    u32 presentationDelay;  //unit us
}app_big_param_t;

typedef struct{
    app_audio_brodcast_state_enum state;
    u8 dataInMode;
    app_big_param_t bigParam;
    blc_bcstAudioAnnouncements_param_t BASE;
} app_bisSource_param_t;

state: BIS Source的状态,主要作用是简单的维护状态,需要开关时需要。

默认配置为APP_AUDIO_BRODCAST_SOURCE_STATE_IDLE

dataInMode: BIS source数据输入的方式。目前支持AMIC、Line-in、IIS、USB-MIC、测试数据几种方式。其中USB-MIC只支持48KHz采样率。宏定义如下:

#define APP_AUDIO_INPUT_AMIC                       1
#define APP_AUDIO_INPUT_LINEIN                     2
#define APP_AUDIO_INPUT_IISIN                      4
#define APP_AUDIO_INPUT_USB_MIC                    5
#define APP_AUDIO_INPUT_NONE                       6

bigParam: BIG广播的参数。主要包含NSE、BN、IRC、PTO、Enc等。这样参数的定义可以参考API: blc_hci_le_createBigParamsTest介绍或者《Core_V5.4》 7.8.104 Le Create BIG Test command里面介绍。

BASE: BASE字段数据结构。结构体的含义参考Broadcast Source API

audio_broadcast_sink

  • 功能: Broadcast Sink主要是将空中的BIS广播数据,接收后解码后通过音频输出口输出。
  • 主要硬件: 9517 音频板 x 2

如何选择BIS,SDK提供了三种方式,user可以通过以下三个宏定义来选择。

#define SINK_SELECT_SOURCE_WITH_BASS                1
#define SINK_SELECT_SOURCE_WITH_UART                0
#define SINK_SELECT_SOURCE_WITH_KEY                 0

(a) SINK_SELECT_SOURCE_WITH_BASS

该宏打开后,Broadcast sink选择BIS需要assistant建立ACL连接,通过BASS服务来配置选择。assistant如何选择Source的方式,可以参考章节audio_broadcast_assistant。关于BASS和sink的流程,可以参考profile中的相关章节,主要是BASSC事件和BAP Broadcast Sink事件。

(b) SINK_SELECT_SOURCE_WITH_UART

该宏打开后,Broadcast sink是集成了assistant的功能,可以通过assistant模块进行source的扫描,扫描后通过sink接口直接进行source的同步。目前这个功能,同步source后不能停止同步,并开启另一个source同步,不推荐使用。

(c) SINK_SELECT_SOURCE_WITH_KEY

该宏打开后,Broadcast sink同样是集成了assistant的功能,不过demo对其进行了简化,只对Telink Source进行扫描,下面会具体介绍如何识别Telink Source。assistant会将扫描到的source信息,存储到内容中,user可以通过按键进行source的切换。

下面所有的sink的介绍,都是基于SINK_SELECT_SOURCE_WITH_KEY的demo讲解,另外两个demo是基于LE Audio profile的规定操作,将扩展广播扫描、周期性广播同步、BIS同步等操作集成到sdk中。

demo会将ADType: 0xFF, ADValue: 0x11 0x02(Telink Company ID) 0xXX识别为Telink Source设备。并且Source的类型会按照configurations中定义的格式去解析,目前仅支持LC3_48_2, 两路BIS分别传输左右声道音频参数,user可以按照需要自行添加更多的参数。

static const telink_big_configuration_t configurations[] = {
    {
        .enc = 0,
        .mse = 4,
        .big_sync_timeout = 10,
        .frequency = BLC_AUDIO_FREQ_CFG_48000,
        .duration = BLC_AUDIO_DURATION_CFG_10,
        .num_bis = 2,
        .bis = {
            {
                .index = 1,
                .allocation = BLC_AUDIO_LOCATION_FLAG_FL,
                .frameOcts = 100,
                .frameBlocks = 1,
            },
            {
                .index = 2,
                .allocation = BLC_AUDIO_LOCATION_FLAG_FR,
                .frameOcts = 100,
                .frameBlocks = 1,
            },
        },
    }
};

audio_broadcast_source章节,有讲述了如何创建一个BIS广播,Broadcast Sink对于开启BIS同步流程与创建相同。

(1) 开启扩展扫描

上电初始化会开启扩展扫描,扩展扫描的参数可以参数多连接handbook的描述。

    blc_ll_setExtScanParam( OWN_ADDRESS_PUBLIC, SCAN_FP_ALLOW_ADV_ANY, SCAN_PHY_1M,
                            SCAN_TYPE_PASSIVE,  SCAN_INTERVAL_100MS,   SCAN_INTERVAL_100MS,
                            0,                  0,                     0);

    blc_ll_setExtScanEnable( BLC_SCAN_ENABLE, DUP_FILTER_DISABLE, SCAN_DURATION_CONTINUOUS, SCAN_WINDOW_CONTINUOUS);

开始扩展扫描后,source的信息会通过HCI_SUB_EVT_LE_EXTENDED_ADVERTISING_REPORT事件上报,相应的mask也必须要打开。

demo中会将扫描到的Telink Source信息,存储在telinkBcastSources内存中,后续切换BIS同步,会直接从中加载Source信息。

(2) 创建周期性广播同步

创建周期性广播同步时,需要传入广播的地址、地址类型、广播集ID,这些在扩展广播中都能拿到。

status = blc_ll_periodicAdvertisingCreateSync(SYNC_ADV_SPECIFY | REPORTING_INITIALLY_EN,
                                        currectSource_Idx->advSid,
                                        currectSource_Idx->addrType,
                                        currectSource_Idx->addr,
                                        0, SYNC_TIMEOUT_2S, 0);

创建周期性广播同步后,会上报 HCI_SUB_EVT_LE_PERIODIC_ADVERTISING_SYNC_ESTABLISHED 或者 HCI_SUB_EVT_LE_PERIODIC_ADVERTISING_SYNC_LOST 事件,来表示同步结果。并且有 HCI_SUB_EVT_LE_ BIGINFO_ADVERTISING_REPORT 描述其BIG的状态,HCI_SUB_EVT_LE_PERIODIC_ADVERTISING_REPORT 会携带BASE字段来描述BIG中的音频信息。

sink demo对其做了简化处理,默认已知了BIG的所以信息,SDK的assistant和sink是完整的解析这些事件。user如果需要了解更多的信息,可以参考《Core_V5.4》中的描述。

(3) 创建BIG同步

创建BIG同步,需要传入BIG句柄,周期性广播同步句柄,是否加密等信息,参数的含义参考《Core_V5.4》中的描述。

u8 bigSyncParamBuf[sizeof(hci_le_bigCreateSyncParams_t) + 32] = {0};
hci_le_bigCreateSyncParams_t *pBigCreateSyncParam = (hci_le_bigCreateSyncParams_t*)bigSyncParamBuf;
const telink_big_configuration_t *config = &configurations[source->configuration];

pBigCreateSyncParam->big_handle = BIG_HANDLE_0;
pBigCreateSyncParam->sync_handle = pa_sync_h;
pBigCreateSyncParam->enc = config->enc;
pBigCreateSyncParam->mse = config->mse;
pBigCreateSyncParam->big_sync_timeout = config->big_sync_timeout;
pBigCreateSyncParam->num_bis = config->num_bis;
memcpy(pBigCreateSyncParam->broadcast_code, config->broadcast_code, 16);

for (int i = 0; i < config->num_bis; i++) {
    pBigCreateSyncParam->bis[i] = config->bis[i].index;
}

return blc_hci_le_bigCreateSync(pBigCreateSyncParam);

创建BIG同步后,会上报HCI_SUB_EVT_LE_BIG_SYNC_ESTABLISHED或者HCI_SUB_EVT_LE_BIG_SYNC_LOST事件,来表示同步结果。

如果user准备好,需要通过API:blc_ll_setupIsoDataPath将controller的数据上报上来。

(4) 停止BIG同步

user如果需要停止BIG的同步,可以调用下面的API主动停止BIG同步。

ble_sts_t   blc_ll_bigTerminateSync(u8 bigHandle);

BIG同步结束时,会上报HCI_SUB_EVT_LE_TERMINATE_BIG_COMPLETE事件来指示。

(5) 停止周期性广播同步

user如果需要停止周期性广播同步,可以调用下面的API主动停止周期性广播同步。该SDK的立即生效的,控制器接收到这个命令后,会清除周期性同步的信息,不会上报事件。

ble_sts_t   blc_ll_periodicAdvertisingTerminateSync (u16 sync_handle);

(6) 获取BIS SDU数据

user可以通过该API获取对应的BIS Handle的SDU数据,sdu的数据结构可以参考前面章节的描述。

sdu_packet_t* blc_ll_popBisSyncRxSduData(u16 bis_connHandle);

(7) Sink UI功能

user可以打开UI_9517C宏定义,支持9518EVK和9517音频板上同时运行。

app_ui.c中定义了可编程按键的功能。

9518EVK上4个可编程按键的功能,如下表所示。

key num BASS KEY
SW2 K1 音量加 同步下一个source
SW3 K2 音量减 同步下一个source
SW4 K3 静音 同步上一个source
SW5 K4 取消静音 同步上一个source

9517音频板上3个可编程按键的功能,如下表所示。

key num BASS KEY
SW8 音量减 同步下一个source
SW9 音量加 同步上一个source
SW10 静音/取消静音 NA

audio_broadcast_assistant

  • 功能:Broadcast Assistant在广播音频中作为辅助器的角色,首先需要扫描BIS sink设备,并建立ACL连接,发现Sink的PACS、BASS两个描述音频能力的服务。之后会进行本地广播扫描,发现符合Sink能力的广播源信息,并通过BASS服务对Sink进行增/删/改/查的源操作,如果源是加密的,也可以通过设置Broadcast code指令进行解密。
  • 主要硬件: 9518EVK x 2

SDK中对于广播辅助器的操作,全部进行了封装,对于user来说广播辅助器就是一个工具,对广播音频进行一些操作。广播辅助器提供了uart/usb-cdc两种方式控制,user可以通过以下宏定义进行切换。

#define APP_AUDIO_UI_UART                           1
#define APP_AUDIO_UI_USB_CDC                        2
#define APP_AUDIO_UI_IFACE                          APP_AUDIO_UI_UART

广播辅助器所有的控制指令,都需要以\r\n(ASCII: 0x0d 0x0A)结尾,下文介绍指令时,不再重复。

(1) 扫描sink

扫描sink的指令格式: scan-sink <start|stop|clear> \r\n

(a) scan-sink start

开启扫描sink功能,辅助器首先会回复assistant start scan sink;

之后如果扫描到符合的Sink设备,会按照以下格式上报

[dev_idx] <public/random> xx:xx:xx:xx:xx:xx name: complete name

如下是扫描到的示例。

[1] public 12:34:56:00:00:00 name:Telink-BIS-SINK

其中dev_idx后面连接时候,需要填入。

(b) scan-sink stop

停止扫描sink功能,辅助器首先会回复assistant stop scan sink;

(c) scan-sink clear

停止扫描sink功能,并清空已经扫描到的sink信息,辅助器首先会回复assistant clear sink info;

(2) 连接特定sink设备

连接特定sink的指令格式: conn-sink <dev_idx> \r \n

其中dev_idx是在scan-sink start指令后上报的索引值。

conn-sink 1

如果索引值不存在,辅助器会回复

connect sink index error
show sink to view sink index.

如果索引值存在,辅助器会和对应是sink设备创建ACL连接。并回复assistant start connect sink

连接过程中,辅助器会根据profile的状态,上报一些中间状态。主要是ACL连接设备的MAC地址,连接的ACL句柄,BASS、PACS、VCP三个服务的状态,SDP结束的标记。

acl connected Handle:80, Addr 12:34:56:00:00:00
ConnHandle:80, PACS Found Start.
ConnHandle:80, PACS Found End.
ConnHandle:80, BASS Found Start.
Sink no any Sync Source Info
ConnHandle:80, BASS Found End.
ConnHandle:80, VCP Volume Controller Found Start.
ConnHandle:80, VCP Volume Controller Found End.
ConnHandle:80, SDP over

(3) 为已连接的Sink扫描源信息

为sink扫描源信息的指令格式: scan-bcast <start|stop|clear> <conn_idx> \r\n

其中conn_idx目前默认是1,user可以根据show conn指令查询索引。

(a) scan-bcast start <conn_idx>

开始扫描源信息操作.

如果conn_idx索引不正常,辅助器会回复index error.You can run the "show conn" command to view connection information.

如果辅助器为完成SDP的流程,辅助器会回复wait sdp discovery ending.

如果连接的Sink设备,没有BASS服务,辅助器会回复connect device not supported BASS Server.

如果连接的Sink设备,没有PACS服务,辅助器会回复connect device not supported PACS Server.

如果一切正常,辅助器会回复assistant start scan broadcast source.

当辅助器扫描到符合要求的源信息时,会上报信息,如下是一个示例。其中[1]中1是source_idx,在后续源的添加中会使用到。LC3编解码器ID是0600000000,源加密与否,每个BIS音频采样率和声道信息等。

Found Source Info[1]
    public Address is 12:34:56:00:00:00
    Device Name:Telink-BIS-SOURCE
    Broadcast Name:Broad-source
        BIS Index: 1
        Codec ID: 0600000000
        Sampling Frequency: 48kHz
        Front Left: Supported
        BIS Index: 2
        Codec ID: 0600000000
        Sampling Frequency: 48kHz
        Front Right: Supported
    Source is Unencrypted

(b) scan-bcast stop <conn_idx>

停止扫描源信息操作,辅助器回复assistant stop scan broadcast source.

(c) scan-bcast clear <conn_idx>

停止扫描源信息操作,并且清空已经扫描到的所有源信息,辅助器回复assistant clear broadcast source info.

(4) 对已连接的sink添加源

为已连接的sink添加源的指令格式: add-source <conn_idx> <source_idx> <bis_sync> [broadcast_key]\r\n

其中conn_idx目前默认是1,user可以根据show conn指令查询索引。

source_idx是在扫描源操作时上报的,user也可以根据show source指令查询索引。

bis_sync是表示源信息的BIS同步信息,这些是在扫描源操作时上报的。如想要同步BIS index 1,bis_sync=BIT(0);想要同步BIS index 2, bis_sync=BIT(1);想要同步BIS index1和2,bis_sync=BITS(0, 1)即可。

该指令如果对方已经有同步的源信息,会先进行移除源信息后重新添加新的源信息。

添加源的示例指令如: add-source 1 1 3

辅助器会上报,sink的周期性广播同步和BIS同步状态都会上报。

started add source
sink PA state is Sync
BIS Synced state is 0x00000000
sink PA state is Sync
BIS Synced state is 0x00000003

(5) 查询信息指令集合

查询信息指令格式: show <conn|sink|source|vcp> [conn_dev]\r\n

(a) show conn

查询已连接sink信息,辅助器回复connect sink info.打印已连接sink的信息。

[1]Connect:public 12:34:56:00:00:00 name:Telink-BIS-SINK

(b) show sink

查询已扫描到的sink信息,辅助器回复scan sink info.打印已扫描到的sink信息。

[1] public 12:34:56:00:00:00 name:Telink-BIS-SINK

(c) show source

查询已扫描到的source信息,辅助器回复并且打印source信息。

Connected Index:1 is scanning source info
Found Source Info[1]
    public Address is A4:C1:38:10:00:1F
    Device Name:Telink-BIS-SOURCE
    Broadcast Name:Broad-source
        BIS Index: 1
        Codec ID: 0600000000
        Sampling Frequency: 48Hz
        Front Left: Supported
        BIS Index: 2
        Codec ID: 0600000000
        Sampling Frequency: 48Hz
        Front Right: Supported
    Source is Unencrypted

(d) show vcp [conn_dev]

查询已连接的sink设备的音量控制协议信息,包括VCS和VOCS信息。如果设备有VCS信息,可以控制音量,如果有VOCS信息,可以控制每个输出的音量偏移。

Remote Volume Control State, connHandle: 0x80
volume(min:0, max:255) is 20, muteSate:Unmute
VOCS index is 0 
Location:Front Left 
Audio Description is Telink BIS Left Output
Volume Offset(min:-255, max:255) is 0
VOCS index is 1 
Location:Front Right 
Audio Description is Telink BIS Rigth Output
Volume Offset(min:-255, max:255) is 0

(6) 音量控制指令

音量控制指令,分为静音/取消静音、音量加、音量减、设置绝对音量四个操作。

所有的音量控制指令,操作完成/sink端自身音量变量,都会打印sink的音量值、静音标记信息。

Conn:80, volume(min:0, max:255) is 40, muteSate:Unmute

(a) 静音/取消静音

指令格式: mute <conn_idx>

指令默认会静音和取消静音翻转设置。

(b)音量加

指令格式: vol+ <conn_idx>

默认会发送音量加,具体sink端音量增加多少,需要看sink端的设置。

(c)音量减

指令格式: vol- <conn_idx>

默认会发送音量减,具体sink端音量减少多少,需要看sink端的设置。

(d)设置绝对音量

指令格式: set-vol <conn_idx> <volume value>

默认会将volume value值,设置为sink的音量,范围是0-255.

(7) 音量偏移控制

如果对端Sink设备拥有VOCS服务,可以设置单独设置输出音量偏移值。指令格式: set-vol-offset <conn_idx> <vocs_idx> <vol_offset>

vocs_idx可以使用show vcp <conn_idx>查询。

vol_offset偏移值返回为-255到255.

设置音量偏移或者sink端自行修改音量偏移值,都会打印。

Conn:80, vocs index is 1 volume offset(min:-255, max:255) is -60

(8) 示例

本小节以一个assistant,两个sink,两个source为例,讲述如何为两个sink配置不同的source信息。

sink的基本信息:

sink 1, public地址,12:34:56:00:00:00 设备名:Telink-BIS-SINK

sink 2, public地址,12:34:56:00:00:01 设备名:Telink-BIS-SINK

source的基本信息:

source 1, public地址,12:34:56:00:01:00 设备名:Telink-BIS-SOURCE

source 2, public地址,12:34:56:00:01:01 设备名:Telink-BIS-SOURCE

(a) 扫描空中的sink设备

下面显示了sink扫描的流程,首先发送scan-sink start指令,当扫描到期望的sink设备时,发送scan-sink stop指令停止当前扫描,可以通过show sink指令打印当前扫描到的sink信息。

[10:22:41.371]send: scan-sink start
[10:22:41.372]recv: assistant start scan sink
[1] public 12:34:56:00:00:00 name:Telink-BIS-SINK
[2] public 12:34:56:00:00:01 name:Telink-BIS-SINK

[10:22:44.061]send: scan-sink stop
[10:22:44.063]recv: assistant stop scan sink

[10:22:50.587]send: show sink
[10:22:50.589]recv: scan sink info
Now Stop Scan new Sink
[1] public 12:34:56:00:00:00 name:Telink-BIS-SINK
[2] public 12:34:56:00:00:01 name:Telink-BIS-SINK

(b) 与sink 1创建ACL连接

根据上面扫描到的sink信息,可以看到sink 1的dev_idx为1,所以发现conn-sink 1指令,可以与sink 1建立ACL连接。

[10:27:22.053]send: conn-sink 1
[10:27:22.054]recv: assistant start connect sink
acl connected Handle:80, Addr 12:34:56:00:00:00
ConnHandle:80, PACS Found Start.
ConnHandle:80, PACS Found End.
ConnHandle:80, BASS Found Start.
sink PA state is Loss
BIS Synced state is 0x00000000
ConnHandle:80, BASS Found End.
ConnHandle:80, VCP Volume Controller Found Start.
Sink no any Sync Source Info
ConnHandle:80, VCP Volume Controller Found End.
ConnHandle:80, SDP over

[10:31:37.373]send: show conn
[10:31:37.377]recv: connect sink info
[1]Connect:public 12:34:56:00:00:00 name:Telink-BIS-SINK

在创建ACL连接后,会打印sink支持的服务信息,包括PACS、BASS、VCP信息。如果包括BASS服务,会打印周期性广播和BIS广播同步情况。上面示例sink没有任何同步信息。

当assistant上报SDP over时,表示完成了服务发现的流程,可以进行其他操作。回连和配对的服务发现时间差异较大,user可以通过抓包自行查看。

(c) 为sink 1扫描source信息

上述日志能看到sink 1连接后的conn_idx为1,所以发送scan-bcast start 1命令扫描空中符合的source信息,当扫描到符合要求的source信息后,发送scan-bcast stop1命令,停止扫描source。

[10:32:39.822]send: scan-bcast start 1
[10:32:39.824]recv: assistant start scan broadcast source
[10:32:39.959]recv: Found Source Info[1]
    public Address is 12:34:56:00:01:00
    Device Name:Telink-BIS-SOURCE
    Broadcast Name:Broad-source
        BIS Index: 1
        Codec ID: 0600000000
        Sampling Frequency: 48Hz
        Front Left: Supported
        BIS Index: 2
        Codec ID: 0600000000
        Sampling Frequency: 48Hz
        Front Right: Supported
    Source is Unencrypted
[10:32:40.199]recv: Found Source Info[2]
    public Address is 12:34:56:00:01:01
    Device Name:Telink-BIS-SOURCE
    Broadcast Name:Broad-source
        BIS Index: 1
        Codec ID: 0600000000
        Sampling Frequency: 48Hz
        Front Left: Supported
        BIS Index: 2
        Codec ID: 0600000000
        Sampling Frequency: 48Hz
        Front Right: Supported
    Source is Unencrypted

[10:33:59.365]send: scan-bcast stop 1
[10:33:59.375]recv: assistant stop scan broadcast source

上述日志显示了空中符合要求的source信息。

(d) 为sink 1添加/切换source信息

根据前面的信息,source 1的source_idx为1,source 2的source_idx为2,并且两者都是不加密的。

下面日志的操作步骤,user可以根据sink端听到的声音来判断有无操作成功。

  • 同步source 1左右声道音频;

  • 同步source 2左右声道音频;

  • 同步source 1左声道音频;

  • 同步source 2左声道音频;

  • 同步source 1右声道音频;

  • 同步source 2右声道音频;

//同步source 1左右声道音频;
[10:38:46.205]send: add-source 1 1 3
[10:38:46.208]recv: started add source
[10:38:46.849]recv: sink PA state is Sync
BIS Synced state is 0x00000000
[10:38:46.909]recv: sink PA state is Sync
BIS Synced state is 0x00000003
//同步source 2左右声道音频;
[10:38:50.197]send: add-source 1 2 3
[10:38:50.202]recv: started add source
[10:38:50.330]recv: sink PA state is Loss
BIS Synced state is 0x00000003
sink PA state is Loss
BIS Synced state is 0x00000000
[10:38:50.449]recv: Sink no any Sync Source Info
[10:38:50.751]recv: sink PA state is Sync
BIS Synced state is 0x00000000
[10:38:50.930]recv: sink PA state is Sync
BIS Synced state is 0x00000003
//同步source 1左声道音频;
[10:38:59.580]send: add-source 1 1 1
[10:38:59.585]recv: started add source
[10:38:59.690]recv: sink PA state is Loss
BIS Synced state is 0x00000003
sink PA state is Loss
BIS Synced state is 0x00000000
[10:38:59.810]recv: Sink no any Sync Source Info
[10:39:00.231]recv: sink PA state is Sync
BIS Synced state is 0x00000000
[10:39:00.291]recv: sink PA state is Sync
BIS Synced state is 0x00000001
//同步source 2左声道音频;
[10:39:03.709]send: add-source 1 2 1
[10:39:03.714]recv: started add source
[10:39:03.830]recv: sink PA state is Loss
BIS Synced state is 0x00000001
sink PA state is Loss
BIS Synced state is 0x00000000
[10:39:03.950]recv: Sink no any Sync Source Info
[10:39:04.430]recv: sink PA state is Sync
BIS Synced state is 0x00000000
[10:39:04.551]recv: sink PA state is Sync
BIS Synced state is 0x00000001
//同步source 1右声道音频;
[10:39:08.837]send: add-source 1 1 2
[10:39:08.841]recv: started add source
[10:39:08.990]recv: sink PA state is Loss
BIS Synced state is 0x00000001
sink PA state is Loss
BIS Synced state is 0x00000000
[10:39:09.110]recv: Sink no any Sync Source Info
[10:39:09.531]recv: sink PA state is Sync
BIS Synced state is 0x00000000
[10:39:09.650]recv: sink PA state is Sync
BIS Synced state is 0x00000002
////同步source 2右声道音频;
[10:39:12.597]send: add-source 1 2 2
[10:39:12.602]recv: started add source
[10:39:12.711]recv: sink PA state is Loss
BIS Synced state is 0x00000002
sink PA state is Loss
BIS Synced state is 0x00000000
[10:39:12.890]recv: Sink no any Sync Source Info
[10:39:13.250]recv: sink PA state is Sync
BIS Synced state is 0x00000000
[10:39:13.430]recv: sink PA state is Sync
BIS Synced state is 0x00000002

(e) 音量控制的操作

当完成服务发现流程后,可以发送show vcp 1指令,查询sink支持的音量控制协议。连接的sink是支持音量控制、静音、左声道音量增益(sink端应用层未实现),右声道音量增益(sink端应用层未实现)。

[10:41:59.686]send: show vcp 1
[10:41:59.689]recv: Remote Volume Control State, connHandle: 0x80
volume(min:0, max:255) is 20, muteSate:Unmute
VOCS index is 0 
Location:Front Left 
Audio Description is Telink BIS Left Output
Volume Offset(min:-255, max:255) is 0
VOCS index is 1 
Location:Front Right 
Audio Description is Telink BIS Rigth Output
Volume Offset(min:-255, max:255) is 0

当sink端按键跳转音量时,assistant端会打印当前的音量信息。

Conn:80, volume(min:0, max:255) is 40, muteSate:Unmute

assistant也可以发送音量加、音量减,静音等指令。

//音量加
[10:45:10.741]send: vol+ 1
[10:45:10.857]recv: Conn:80, volume(min:0, max:255) is 60, muteSate:Unmute
[10:45:11.541]send: vol+ 1
[10:45:11.637]recv: Conn:80, volume(min:0, max:255) is 80, muteSate:Unmute
//音量减
[10:45:13.382]send: vol- 1
[10:45:13.497]recv: Conn:80, volume(min:0, max:255) is 80, muteSate:Unmute
[10:45:14.141]send: vol- 1
[10:45:14.217]recv: Conn:80, volume(min:0, max:255) is 60, muteSate:Unmute
//静音/取消静音
[10:45:17.541]send: mute 1
[10:45:17.637]recv: Conn:80, volume(min:0, max:255) is 60, muteSate:Mute
[10:45:18.212]send: mute 1
[10:45:18.296]recv: Conn:80, volume(min:0, max:255) is 60, muteSate:Unmute
//设置音量
[10:47:38.849]send: set-vol 1 75
[10:47:38.940]recv: Conn:80, volume(min:0, max:255) is 75, muteSate:Unmute
[10:47:43.293]send: set-vol 1 160
[10:47:43.380]recv: Conn:80, volume(min:0, max:255) is 160, muteSate:Unmute
[10:47:53.501]send: set-vol 1 8
[10:47:53.580]recv: Conn:80, volume(min:0, max:255) is 8, muteSate:Unmute

sink端支持VOCS服务,所以也可以设置VOCS值。

//设置左声道音量偏移值
[10:49:12.158]send: set-vol-offset 1 0 27
[10:49:12.242]recv: Conn:80, vocs index is 0 volume offset(min:-255, max:255) is 27,
write volume offset value is success
[10:49:18.621]send: set-vol-offset 1 0 -7
[10:49:18.722]recv: Conn:80, vocs index is 0 volume offset(min:-255, max:255) is -7,
write volume offset value is success
//设置右声道音量偏移值
[10:49:31.772]send: set-vol-offset 1 1 -60
[10:49:31.862]recv: Conn:80, vocs index is 1 volume offset(min:-255, max:255) is -60
write volume offset value is success
[10:49:36.269]send: set-vol-offset 1 1 120
[10:49:36.362]recv: Conn:80, vocs index is 1 volume offset(min:-255, max:255) is 120
write volume offset value is success
////查询音量参数
[10:49:40.133]send: show vcp 1
[10:49:40.137]recv: Remote Volume Control State, connHandle: 0x80
volume(min:0, max:255) is 8, muteSate:Unmute
VOCS index is 0 
Location:Front Left 
Audio Description is Telink BIS Left Output
Volume Offset(min:-255, max:255) is -7
VOCS index is 1 
Location:Front Right 
Audio Description is Telink BIS Rigth Output
Volume Offset(min:-255, max:255) is 120

打印日志

开发过程中常用的调试方法可以参考《Telink BLE Multi Connection SDK Developer Handbook》,调试⽅法章节。本章主要介绍打印日志相关的功能,打印日志的实现可以参考vendor/common/tlkapi_debug.c和tlkapi_debug.h。

#define TLKAPI_DEBUG_CHANNEL_UDB                    1   //USB Dump
#define TLKAPI_DEBUG_CHANNEL_GSUART                 2   //GPIO simulate UART
#define TLKAPI_DEBUG_CHANNEL_UART                   3   //hardware UART

#ifndef TLKAPI_DEBUG_ENABLE
#define TLKAPI_DEBUG_ENABLE                         0
#endif

#ifndef TLKAPI_DEBUG_CHANNEL
#define TLKAPI_DEBUG_CHANNEL                        TLKAPI_DEBUG_CHANNEL_UDB
#endif

void tlkapi_send_str_data (char *str, u8 *ph, u32 n);
void tlkapi_send_str_u8s (char *str, u8 d0, u8 d1, u8 d2, u8 d3);
void tlkapi_send_str_u32s (char *str, u32 d0, u32 d1, u32 d2, u32 d3);
int  my_printf(const char *fmt, ...);

#define tlkapi_send_string_data(en,s,p,n)               if(en){tlkapi_send_str_data(s,(u8*)(p),n);}
#define tlkapi_send_string_u8s(en,s,d0,d1,d2,d3)        if(en){tlkapi_send_str_u8s(s,(u8)(d0),(u8)(d1),(u8)(d2),(u8)(d3));}
#define tlkapi_send_string_u32s(en,s,d0,d1,d2,d3)       if(en){tlkapi_send_str_u32s(s,(u32)(d0),(u32)(d1),(u32)(d2),(u32)(d3));}
#define tlkapi_printf(en,fmt, ...)                      if(en){my_printf(fmt, ##__VA_ARGS__);}

通过修改用户工程目录下app_config.h文件中如下两个宏定义,可以实现打印模块的开启和输出通道选择(如USB Dump、GPIO模拟UART,硬件UART),例如选择USB输入打印日志:

#define TLKAPI_DEBUG_ENABLE                  1
#define TLKAPI_DEBUG_CHANNEL                 TLKAPI_DEBUG_CHANNEL_UDB

USB Dump打印

在 Telink BLE Multiple Connection SDK 中可以看到有多处 my_dump_str_data 这个API的调用,该功能是B91上借助USB接口将调试信息输出的一种实现,不是官方推荐的调试信息输出方法,仅作为一种参考,目的是解决在中断中不能通过GPIO模拟UART进行输出的问题。该功能默认不开启,需要定义 DUMP_STR_EN 为 1 才能开启。

GPIO模拟UART打印

为了方便用户进行调试,telink_b91m_ble_audio_sdk提供了一种通过GPIO模拟UART串口输出调试信息的实现方法。这种方法仅供参考,不是官方推荐的调试信息输出方式。将例程中 app_config.h 中的宏TLKAPI_DEBUG_ENABLE设置为1,并且TLKAPI_DEBUG_CHANNEL配置为TLKAPI_DEBUG_CHANNEL_GSUART,就可以在代码中直接使用与 C 语言语法规则一致的 my_printf 接口进行串口输出,也可以使用API:tlkapi_send_str_xxx进行日志输入。

一般而言,只修改波特率与GPIO_PIN。

#if (TLKAPI_DEBUG_CHANNEL == TLKAPI_DEBUG_CHANNEL_GSUART)
    #define TLKAPI_DEBUG_GPIO_PIN               GPIO_PD2
    #define TLKAPI_DEBUG_GSUART_BAUDRATE        1000000
#endif

注意:

  • 波特率目前最高支持1Mbps。
  • 由于GPIO模拟UART串口的打印会被中断打断,致使模拟的UART的时序不准确,因此在实际使用过程中时而会有打印乱码的情况发生。
  • 由于GPIO模拟UART串口的打印会占用CPU,所以不建议在中断中加入打印,会影响对时序要求较高的中断任务。

硬件UART打印

硬件UART打印调试日志可以避免GPIO模拟打印的不足,由于使用了DMA机制,在中断中也可以使用,不足之处在于需要额外硬件外设支持。

Audio Profile打印日志开启和关闭

用户可以通过修改stack/ble/profile/audio/audio_cfg.h中如下宏的使能或失能去开启或者屏蔽Audio Profile不同层的打印日志功能。

#ifndef DBG_PRF_AUD_LOG
#define DBG_PRF_AUD_LOG                                         1
#endif

#ifndef PRF_DBG_AUDIO_EN
#define PRF_DBG_AUDIO_EN                                        1
#endif

#ifndef PRF_DBG_ASCS_EN
#define PRF_DBG_ASCS_EN                                         1
#endif

#ifndef PRF_DBG_BASS_EN
#define PRF_DBG_BASS_EN                                         1
#endif

#ifndef PRF_DBG_PACS_EN
#define PRF_DBG_PACS_EN                                         1
#endif

#ifndef PRF_DBG_BAP_EN
#define PRF_DBG_BAP_EN                                          1
#endif

#ifndef PRF_DBG_BCST_EN
#define PRF_DBG_BCST_EN                                         1
#endif

#ifndef PRF_DBG_CSIS_EN
#define PRF_DBG_CSIS_EN                                         1
#endif

#ifndef PRF_DBG_OTS_EN
#define PRF_DBG_OTS_EN                                          1
#endif

#ifndef PRF_DBG_MCS_EN
#define PRF_DBG_MCS_EN                                          1
#endif

#ifndef PRF_DBG_TBS_EN
#define PRF_DBG_TBS_EN                                          1
#endif

#ifndef PRF_DBG_VCS_EN
#define PRF_DBG_VCS_EN                                          1
#endif

#ifndef PRF_DBG_VOCS_EN
#define PRF_DBG_VOCS_EN                                         1
#endif

#ifndef PRF_DBG_MICS_EN
#define PRF_DBG_MICS_EN                                         1
#endif

#ifndef PRF_DBG_AICS_EN
#define PRF_DBG_AICS_EN                                         1
#endif

#ifndef PRF_DBG_CAP_EN
#define PRF_DBG_CAP_EN                                          1
#endif

#ifndef PRF_DBG_PBP_EN
#define PRF_DBG_PBP_EN                                          1
#endif

#ifndef PRF_DBG_TMAS_EN
#define PRF_DBG_TMAS_EN                                         1
#endif

#ifndef PRF_DBG_HAS_EN
#define PRF_DBG_HAS_EN                                          1
#endif

#ifndef PRF_DBG_UCP_EN
#define PRF_DBG_UCP_EN                                          1
#endif

#ifndef PRF_DBG_STORE_EN
#define PRF_DBG_STORE_EN                                        1
#endif

附录

附录1:LC3编解码器

LC3编解码器通道数量配置

单个LC3编解码器只支持一路音频通道,这也就意味着立体声音频需要两个LC3编解码器才能工作。在该SDK中,LC3编解码器相关的API代码文件:algorithm/audio_alg/lc3/lc3.h。

使用LC3编解码器,需要先重新定义以下宏定义,确定LC3编码通道数量、LC3解码通道数量。

#ifndef LC3_ENCODE_CHANNAL_COUNT
#define LC3_ENCODE_CHANNAL_COUNT        0
#endif

#ifndef LC3_DECODE_CHANNAL_COUNT
#define LC3_DECODE_CHANNAL_COUNT        0
#endif

如BIS Source Demo中,只需要将采集到的立体声音频,编码后广播出去。所以需要LC3编码器数量等于2。

#define LC3_ENCODE_CHANNAL_COUNT        2

如CIS Client Demo中,需要将采集到的立体声音频,编码后发送给两个耳机,同时需要将两个耳机上报的数据解码为立体声音频。所以需要LC3编码器数量等于2,LC3解码器数量等于2。

#define LC3_ENCODE_CHANNAL_COUNT        2
#define LC3_DECODE_CHANNAL_COUNT        2

其他的场景,用户根据需求,自行配置编解码器数量。注:需要注意RAM size。

LC3编码器API

(1) lc3enc_encode_init

调用该API可以初始化LC3编码器,针对了解LC3编码器的用户使用。

int lc3enc_encode_init(u8 index, u32 nSamplerate, u32 nBitrate, u16 nMs_mode);

输入参数:

index: LC3编码器索引,范围为[0, LC3_ENCODE_CHANNAL_COUNT-1].

nSamplerate: 音频的采样率,单位HZ。8kHz对于8000

nBitrate: 编码后的比特率。

nMs_mode: 音频采样周期。1表示7.5ms、0表示10ms

返回值:0表示初始化成功。其他值表示失败。

(2) lc3enc_encode_init_bap

调用该API可以初始化LC3编码器,输入是BAP规范的参数。

int lc3enc_encode_init_bap(u8 index, u8 samplingFreq, u8 frameDuration, u16 perCodecFrame);

输入参数:

index: LC3编码器索引,范围为[0, LC3_ENCODE_CHANNAL_COUNT-1].

samplingFreq: 音频的采样率,参考附录2

frameDuration: 音频采样周期,参考附录2

perCodecFrame: 编码后的帧长度,参考附录2

返回值:0表示初始化成功。其他值表示失败。

(3) lc3enc_encode_pkt

调用该API可以真正编码音频数据,需要先初始化编码模块。

int lc3enc_encode_pkt(u8 index, u8* rawData, u8* encData);

输入参数:

index: LC3编码器索引,范围为[0, LC3_ENCODE_CHANNAL_COUNT-1].

rawData: 原始数据指针。

encData: 编码后的数据指针。

返回值:0表示初始化成功。其他值表示失败。

(4) lc3enc_free_init

该API目前没有任何作用。

int lc3enc_free_init(u8 index);

输入参数:

index: LC3编码器索引,范围为[0, LC3_ENCODE_CHANNAL_COUNT-1].

返回值:0表示初始化成功。其他值表示失败。

LC3解码器API

(1) lc3dec_decode_init

调用该API可以初始化LC3解码器,针对LC3解码器了解的用户使用。

int lc3dec_decode_init(u8 index, u32 nSamplerate, u32 nBitrate, u16 nMs_mode);

输入参数:

index: LC3解码器索引,范围为[0, LC3_DECODE_CHANNAL_COUNT-1].

nSamplerate: 音频的采样率,单位HZ。8kHz对于8000

nBitrate: 编码后的比特率。

nMs_mode: 音频采样周期。1表示7.5ms、0表示10ms

返回值:0表示初始化成功。其他值表示失败。

(2) lc3dec_decode_init_bap

调用该API可以初始化LC3解码器,输入是BAP规范的参数。

int lc3dec_decode_init_bap(u8 index, u8 samplingFreq, u8 frameDuration, u16 perCodecFrame);

输入参数:

index: LC3解码器索引,范围为[0, LC3_DECODE_CHANNAL_COUNT-1].

samplingFreq: 音频的采样率,参考附录2

frameDuration: 音频采样周期,参考附录2

perCodecFrame: 编码后的帧长度,参考附录2

返回值:0表示初始化成功。其他值表示失败。

(3) lc3dec_decode_pkt

调用该API可以真正解码出音频数据,需要先初始化解码模块。

int lc3dec_decode_pkt(u8 index, u8* encData, u16 encDataLen, u8* rawData);

输入参数:

index: LC3解码器索引,范围为[0, LC3_DECODE_CHANNAL_COUNT-1].

encData: 编码数据指针。

encDataLen: 解码数据长度。

rawData: 解码后的音频数据指针。

返回值:0表示初始化成功。其他值表示失败。

(4) lc3dec_set_parameter

调用该API可以修改LC3解码器参数,目前只用来PLC补包功能。需要在解码数据前调用。

int lc3dec_set_parameter(u8 index, LC3_PARAMETER para, u32* val);

输入参数:

index: LC3解码器索引,范围为[0, LC3_DECODE_CHANNAL_COUNT-1].

para: 固定参数LC3_PARA_BEC_DETECT

val: 1表示没有这块数据,需要解码器补包。

返回值:0表示初始化成功。其他值表示失败。

(5) lc3dec_free_init

该API目前没有任何作用。

int lc3dec_free_init(u8 index);

输入参数:

index: LC3解码器索引,范围为[0, LC3_DECODE_CHANNAL_COUNT-1].

返回值:0表示初始化成功。其他值表示失败。

附录1:音频支持能力要求

BAP中对于单播音频和广播音频,LC3的参数配置做了一些定义。下表定义的LC3参数对PACS、ASCS、BASS通用。44.1KHz在传输过程中需要使用Framed模式传输,该SDK目前不支持。

编解码器设置 采样率(kHz) 帧周期(ms) 帧大小(octets) 比特率(kbps)
LC3_8_1 8 7.5 26 27.734
LC3_8_2 8 10 30 24
LC3_16_1 16 7.5 30 32
LC3_16_2 16 10 40 32
LC3_24_1 24 7.5 45 48
LC3_24_2 24 10 60 48
LC3_32_1 32 7.5 60 64
LC3_32_3 32 10 80 64
LC3_441_1 44.1 8.163 97 95.06
LC3_441_2 44.1 10.884 130 95.55
LC3_48_1 48 7.5 75 80
LC3_48_2 48 10 100 80
LC3_48_3 48 7.5 90 96
LC3_48_4 48 10 120 96
LC3_48_5 48 7.5 117 124.8
LC3_48_6 48 10 155 124

附录3:通用音频定义

下表都是参考SIG发布的《Generic Audio》文档。

音频位置定义

Audio Location Bit Num 数值(bitmap)
Front Left 0 0x00000001
Front Right 1 0x00000002
Front Center 2 0x00000004
Low Freqyency Effects 1 3 0x00000008
Back Left 4 0x00000010
Back Right 5 0x00000020
Front Left of Center 6 0x00000040
Front Right of Center 7 0x00000080
Back Center 8 0x00000100
Low Freqyency Effects 2 9 0x00000200
Side Left 10 0x00000400
Side Right 11 0x00000800
Top Front Left 12 0x00001000
Top Front Right 13 0x00002000
Top Front Center 14 0x00004000
Top Center 15 0x00008000
Top Back Left 16 0x00010000
Top Back Right 17 0x00020000
Top Side Left 18 0x00040000
Top Side Right 19 0x00080000
Top Back Center 20 0x00100000
Buttom Front Center 21 0x00200000
Buttom Front Left 22 0x00400000
Buttom Front Right 23 0x00800000
Front Left Wide 24 0x01000000
Front Right Wide 25 0x02000000
Left Surround 26 0x04000000
Right Surround 27 0x08000000
RFU 28 0x10000000
RFU 29 0x20000000
RFU 30 0x40000000
RFU 31 0x80000000

音频输入类型定义

标签 描述 数值
Unspecified Unspecified Input 0x00
Bluetooth Bluetooth Audio Stream 0x01
Microphone Microphone 0x02
Analog Analog Interface 0x03
Digital Digital Interface 0x04
Radio AM/FM/XM/etc. 0x05
Streaming Streaming Audio Source 0x06

音频内容

标签 Bit Num 数值(bitmap) 描述
Prohibited NA 0x0000 Prohibited
Unspecified 0 0x0001 Unspecified
Conversational 1 0x0002 Conversation between humans, for example, in telephony or video calls, including traditional cellular as well as VoIP and Push-to-Talk
Media 2 0x0004 Media, for example, music playback, radio, podcast or movie soundtrack, or tv audio
Game 3 0x0008 Audio associated with video gaming, for example gaming media; gaming effects; music and in-game voice chat between participants; or a mix of all the above
Instructional 4 0x0010 Instructional audio, for example, in navigation, announcements, or user guidance
Voice Assistants 5 0x0020 Man-machine communication, for example, with voice recognition or virtual assistants
Live 6 0x0040 Live audio, for example, from a microphone where audio is perceived both through a direct acoustic path and through an LE Audio Stream
Sound Effects 7 0x0080 Sound effects including keyboard and touch feedback; menu and user interface sounds; and other system sounds
Notifications 8 0x0100 Notification and reminder sounds; attention-seeking audio, for example, in beeps signaling the arrival of a message
Ringtone 9 0x0200 Alerts the user to an incoming call, for example, an incoming telephony or video call, including traditional cellular as well as VoIP and Push-to-Talk
Alerts 10 0x0400 Alarms and timers; immediate alerts, for example, in a critical battery alarm, timer expiry or alarm clock, toaster, cooker, kettle, microwave, etc.
Emergency Alarm 11 0x0800 Emergency alarm Emergency sounds, for example, fire alarms or other urgent alerts
RFU other other Reserved for Futrue Use

编码器特定能力LTV结构

支持采样率(Supported_Sampling_Frequencies)的参数表。

参数 大小 数值
长度 1 0x03
类型 1 0x01
数值 2 0b1 = supported, 0b0 = not supported
Bit 0: 8,000 Hz
Bit 1: 11,025 Hz
Bit 2: 16,000 Hz
Bit 3: 22,050 Hz
Bit 4: 24,000 Hz
Bit 5: 32,000 Hz
Bit 6: 44,100 Hz
Bit 7: 48,000 Hz
Bit 8: 88,200 Hz
Bit 9: 96,000 Hz
Bit 10: 176,400 Hz
Bit 11: 192,000 Hz
Bit 12: 384,000 Hz

支持帧周期(Supported_Frame_Durations)的参数表。

参数 大小 数值
长度 1 0x02
类型 1 0x02
数值 1 Bit 0: 7.5 ms frame duration. 0b1 = supported, 0b0 = not supported.
Bit 1: 10 ms frame duration. 0b1 = supported, 0b0 = not supported.
Bit 2: RFU
Bit 3: RFU
Bit 4: 7.5 ms preferred. Valid only when 7.5 ms is supported and 10 ms is supported. Shall not be set to 0b1 if bit 5 is set to 0b1.
Bit 5: 10 ms preferred. Valid only when 7.5 ms is supported and 10 ms is supported. Shall not be set to 0b1 if bit 4 is set to 0b1.
Bit 6: RFU
Bit 7: RFU

支持音频声道数量(Supported_Audio_Channel_Counts)的参数表。

参数 大小 数值
长度 1 0x02
类型 1 0x03
数值 1 0b0 = Channel count not supported
0b1 = Channel count supported
Bit 0: Channel count: 1
Bit 1: Channel count: 2
Bit 2: Channel count: 3
Bit 3: Channel count: 4
Bit 4: Channel count: 5
Bit 5: Channel count: 6
Bit 6: Channel count: 7
Bit 7: Channel count: 8
Bit position
0x00: RFU

每个编解码器帧支持的字节数(Supported_Octets_Per_Codec_Frame)的参数表。

参数 大小 数值
长度 1 0x05
类型 1 0x04
数值 4 Octet 0–1: Minimum number of octets supported per codec frame
Octet 2–3: Maximum number of octets supported per codec frame

每个SDU支持的最大编解码帧(Supported_Max_Codec_Frames_Per_SDU)的参数表。

参数 大小 数值
长度 1 0x02
类型 1 0x05
数值 1 Maximum number of codec frames per SDU supported by this device

编码器特定配置LTV结构

采样率(Sampling_Frequency)参数表。

参数 大小 数值
长度 1 0x02
类型 1 0x01
数值 1 0x01: 8,000 Hz
0x02: 11,025 Hz
0x03: 16,000 Hz
0x04: 22,050 Hz
0x05: 24,000 Hz
0x06: 32,000 Hz
0x07: 44,100 Hz
0x08: 48,000 Hz
0x09: 88,200 Hz
0x0A: 96,000 Hz
0x0B: 176,400 Hz
0x0C: 192,000 Hz
0x0D: 384,000 Hz

帧间隔(Frame_Duration)参数表。

参数 大小 数值
长度 1 0x02
类型 1 0x02
数值 1 0x00: Use 7.5 ms codec frames
0x01: Use 10 ms codec frames
All other values: RFU

音频声道分配(Audio_Channel_Allocation)参数表。

参数 大小 数值
长度 1 0x05
类型 1 0x03
数值 4 4-octet bitfield of Audio Location values

每个编解码帧大小(Octets_Per_Codec_Frame)参数表。

参数 大小 数值
长度 1 0x03
类型 1 0x04
数值 2 Number of octets used per codec frame

每个SDU编解码帧页数(Codec_Frame_Blocks_Per_SDU)参数表。

参数 大小 数值
长度 1 0x02
类型 1 0x05
数值 1 Number of blocks of codec frames per SDU

元数据LTV结构

首选音频内容(Preferred_Audio_Contexts)参数表。

参数 大小 数值
长度 1 0x03
类型 1 0x01
数值 2 Bitfield of Context Type values
See Context Type values defined in Table X.Y.
0b0 = Context Type is not a preferred use case for this codec configuration.
0b1 = Context Type is a preferred use case for this codec configuration.

音频流内容(Streaming_Audio_Contexts)参数表。

参数 大小 数值
长度 1 0x03
类型 1 0x02
数值 2 Bitfield of Context Type values
See Context Type values defined in Table X.Y.
0b0 = Context Type is not an intended use case for the Audio Stream.
0b1 = Context Type is an intended use case for the Audio Stream.

项目信息(Program_Info)参数表。

参数 大小 数值
长度 1 varies
类型 1 0x03
数值 varies Title and/or summary of Audio Stream content: UTF-8 format

语言(Language)参数表。

参数 大小 数值
长度 1 0x04
类型 1 0x04
数值 3 3-byte, lower case language code as defined in ISO 639-3

CCID表(CCID_List)参数表。

参数 大小 数值
长度 1 varies
类型 1 0x05
数值 varies Array of CCID values

分级(Parental_Rating)参数表。

参数 大小 数值
长度 1 0x02
类型 1 0x06
数值 1 Bits 0 – 3 Value representing the parental rating:
0x00 – no rating 0x01 – recommended for listeners of any age
Other values – recommended for listeners of age Y years, where
Y = value + 3 years. e.g. 0x05 = recommended for listeners of 8 years or older
Bits 4 – 7 RFU
The numbering scheme aligns with Annex F of EN 300 707 v1.2.1 which defines parental rating for viewing.
https://www.etsi.org
ETSI EN 300 707 V1.2.1 (2002-12)

URI项目信息(Program_Info_URI)参数表。

参数 大小 数值
长度 1 varies
类型 1 0x07
数值 varies A UTF-8 formatted URL link used to present more information about Program_Info

音频有效状态(Audio_Active_State)

参数 大小 数值
长度 1 0x02
类型 1 0x08
数值 1 0x00: No audio data is being transmitted
0x01: Audio data is being transmitted
0x02–0xFF: RFU

广播音频立即演示标志(Broadcast_Audio_Immediate_Rendering_Flag)参数表。

参数 大小 数值
长度 1 0x01
类型 1 0x09

该LTV没有任何数据段。

扩展元数据(Extended Metadata)参数表。

参数 大小 数值
长度 1 varies
类型 1 0xFE
数值 varies Octet 0-1 = Extended Metadata Type
Octet 2-254… = Extended Metadata

厂商特殊元数据(Vendor_Specific)参数表。

参数 大小 数值
长度 1 varies
类型 1 0xFF
数值 varies Octet 0-1 = Company_ID
Company ID values are defined in Bluetooth Assigned Numbers.
Octet 2-254… = Vendor-Specific Metadata