技术博客:BK7258 蓝牙 A2DP 音频数据通路深度解析
基于
SCH3000_4302644_4.3_Inch/projects/bluetooth工程内部实现分析。
一、工程定位与模块划分
该工程包含三个蓝牙示例,分别对应不同角色:
| 目录 | 角色 | 核心功能 |
|---|---|---|
bluetooth/central |
A2DP Source | 设备发现、发起连接、MP3 解码、SBC 编码、推流 |
bluetooth/headset |
A2DP Sink | 可发现/可连接、接收 SBC/AAC 流、解码播放 |
bluetooth/polar |
基础示例 | 蓝牙基础功能验证 |
AP/CP 双核架构:AP 运行应用与协议栈上层,CP 运行蓝牙控制器。
二、headset 端:Sink 数据接收与播放管线
1. 连接管理与配对状态机
bt_manager.c 实现了一个完整的连接状态机,状态流转如下:
PAIRING → LINK_CONNECTED → PROFILE_CONNECTED
↑ ↓ ↓
└────── RECONNECTING ←────────┘
(1) 状态与可见性映射
c
case BT_MNG_MODE_PAIRING:
bk_bt_gap_set_visibility(BK_BT_CONNECTABLE, BK_BT_DISCOVERABLE);
break;
case BT_MNG_MODE_RECONNECTING:
bk_bt_gap_set_visibility(BK_BT_NON_CONNECTABLE, BK_BT_NON_DISCOVERABLE);
break;
(2) 断开连接后的回连策略
- 用户主动断开 / 远端主动断开且本地存有 LinkKey → 进入
CONNECTABLE,等待重连 - 链路超时 / 其他异常 → 启动单次定时器,延时后主动发起重连
- 认证失败 / PIN 丢失 → 删除存储的 LinkKey,回到配对模式
2. A2DP Sink 消息驱动架构
a2dp_sink_demo.c 采用消息队列 + 主循环模式,所有蓝牙事件通过消息投递到统一处理线程:
c
void bt_audio_sink_demo_main(void *arg)
{
while (1) {
bt_audio_sink_demo_msg_t msg;
rtos_pop_from_queue(&bt_audio_sink_demo_msg_que, &msg, BEKEN_WAIT_FOREVER);
switch (msg.type) {
case BT_AUDIO_D2DP_START_MSG:
// 初始化编解码器、ring buffer、扬声器线程
break;
case BT_AUDIO_D2DP_DATA_IND_MSG:
// 接收媒体数据并写入 ring buffer
break;
case BT_AUDIO_D2DP_STOP_MSG:
// 停止播放、回收资源
break;
// ...
}
}
}
(1) 数据包入口
蓝牙协议栈通过回调将 SBC/AAC 数据投递到应用层:
c
void bt_audio_sink_media_data_ind(const uint8_t *data, uint16_t data_len)
{
// 拷贝数据到堆,投递消息,无等待(队列满则丢弃并释放)
}
(2) 编解码器初始化
根据协商的 codec 类型(SBC/AAC)动态分配缓存与 ring buffer:
- SBC :根据
channel_mode、subbands、block_len、bitpool计算单帧长度,预分配(帧长+2) × 最大帧数的缓存。 - AAC:初始化 AAC 解码器,按单通道最大帧长分配缓存。
3. 扬声器播放线程
Sink 端在收到数据后,通过信号量唤醒专门的 a2dp_speaker_thread 进行解码和 DAC 输出:
- 使用
ring_buffer_node管理帧队列,节点固定大小,适合等长 SBC 帧。 - 对 AAC 帧,先做 LATM 解包,将长度前缀与负载写入节点。
- 调用
audio_play接口输出到 DAC,支持音量控制与静音。
三、central 端:Source 数据发送管线
1. 连接流程
a2dp_source_demo.c 使用同步等待方式完成连接:
- 初始化 A2DP Source 并注册回调
- 发起 ACL 连接
- 等待 A2DP 连接完成信号量
- 等待能力协商(
BK_A2DP_AUDIO_SOURCE_CFG_EVT) - 获取 SBC 编码参数与 MTU
2. MP3 解码线程
(1) 文件读取与 ID3 处理
- 支持 VFS 或 FatFS 两种文件系统接口。
- 读取 MP3 文件并跳过 ID3v1/ID3v2 标签,定位到首帧。
- 采用双缓冲滑动窗口,避免频繁文件读取。
(2) Ring Buffer 流控
- 解码线程与 A2DP 数据回调之间通过
ring_buffer_particle进行粒度化传输。 - 当 ring buffer 中数据量超过
s_decode_trigger_size时,解码线程阻塞等待,防止无限缓存。 - A2DP 数据回调从 ring buffer 读取 PCM 数据,成功读取后通过信号量唤醒解码线程继续生产。
c
// 解码线程的生产节流
while (ring_buffer_particle_len(&s_rb_ctx) > s_decode_trigger_size) {
rtos_get_semaphore(&s_source_need_decode_sema, BEKEN_WAIT_FOREVER);
}
// A2DP 数据回调的消费行为
if (ring_buffer_particle_len(&s_rb_ctx) >= len) {
ring_buffer_particle_read(&s_rb_ctx, buf, len, &read_len);
}
if (s_source_need_decode_sema) {
rtos_set_semaphore(&s_source_need_decode_sema);
}
3. PCM → SBC 编码与重采样
(1) 编码参数映射
从 A2DP 协商结果映射到软件 SBC 编码器参数:
- 分配方式:SNR / LOUDNESS
- Bitpool、Block Mode、Channel Mode、采样率索引、子带数
(2) 编码回调
A2DP 协议栈在需要发送数据时回调 a2dp_source_pcm_encode_cb:
- 根据
PCM_CALL_METHOD选择本地编码或通过 AMP 消息发送到协处理器编码。 - 编码完成后拷贝输出流到协议栈提供的缓冲区,并返回编码长度。
(3) 重采样处理
若 MP3 采样率与 SBC 协商采样率不一致,或位深不符,则调用 bk_aud_rsp 模块进行重采样/位深转换。
四、关键数据结构与同步机制
| 机制 | 用途 | 说明 |
|---|---|---|
| 消息队列 | 蓝牙事件解耦 | 回调上下文快速返回,处理线程串行化 |
| 信号量 | 同步等待 | 连接、断开、初始化等流程等待事件完成 |
| Ring Buffer | 音频数据缓冲 | 吸收编解码速率差异,提供流控 |
| 单次定时器 | 重连管理 | 延时后触发回连 |
| LinkKey 存储 | 免配对重连 | 保存远端设备密钥,支持快速回连 |
五、总结
该工程展示了 BK7258 双核蓝牙 SoC 上 A2DP 音频应用的典型实现:
- Sink 端:消息驱动的播放管线,支持 SBC/AAC 双解码,DAC 输出。
- Source 端:MP3 文件解码 + SBC 编码 + 重采样,通过 ring buffer 与协议栈无缝衔接。
- 连接管理:完善的状态机与 LinkKey 持久化,支持自动回连与手动配对。
代码实现中大量使用队列、信号量、ring buffer 等 RTOS 原语,体现了嵌入式蓝牙音频应用的经典设计模式。