中控(Central)
1 功能概述
本工程用于手机、拨号盘等主设备场景,主要功能有
1.作为a2dp source向对端音响传输音乐数据2.接受对端的控制(播放暂停)3.ble gatt server/gatt client
1.1 软件规格
- a2dp:
avdtp source
- avrcp:
tg
ct
- ble:
gap
gatt server
gatt client
smp legacy pair/secure connection pair
1.2 代码路径及编译命令
Demo路径:./projects/bluetooth/central
编译命令:
make bk7258 PROJECT=bluetooth/central
2 cmd命令简介
2.1 a2dp source
a2dp_player connect <xx:xx:xx:xx:xx:xx>
连接音响
a2dp_player disconnect <xx:xx:xx:xx:xx:xx>
断开连接
a2dp_player play <xxx.mp3>
播放mp3
a2dp_player stop
停止播放
a2dp_player pause
暂停
a2dp_player resume
恢复
a2dp_player abs_vol <xxx>
设置音响绝对音量 0 ~ 127 (是否有效视对端而定)
2.2 ble gatt
ble_gatt_demo -h
显示详细命令
ble_gatt_demo <gatts|gattc> init
gatts/gattc初始化
ble_gatt_demo gatts disconnect <xx:xx:xx:xx:xx:xx>
gatts断开连接
ble_gatt_demo gattc connect <xx:xx:xx:xx:xx:xx> <0|1>
连接gatts,0为public,1为random
ble_gatt_demo gattc disconnect <xx:xx:xx:xx:xx:xx>
gattc断开连接
ble_gatt_demo gattc connect_cancel
取消连接
ble_gatt_demo update_param <xx:xx:xx:xx:xx:xx> <interval> <timeout>
更新连接参数
3 a2dp source 测试
3.1 测试过程
1.准备一张sd卡,格式化成exfat,将project/bluetooth/central/1_qcs.mp3放入根目录。(必须是16bits的mp3)2.插入sd卡,开机。3.令音响进入配对模式4.输入a2dp_player connect xx:xx:xx:xx:xx:xx,其中xx为音响地址。等待连接成功5.如果连接成功,会提示”a2dp connect complete”,如果连接失败,会提示”Unsuccessful Connection Complete”之类的log。6.输入a2dp_player play xxx.mp3。7.如果sd卡正常,会提示”f_mount OK”,如果音乐文件存在,会提示”mp3 file open successfully”8.此时可听到播出声音9.正在播放的情况下,可以stop pause,停止播放的情况下可以play。(尽量连接后立刻play,不要stop,参见本章节兼容性说明)10.如果音响支持avrcp,可以通过音响控制播放暂停。(存在兼容性问题,参见本章节兼容性说明)
3.2 兼容性说明
1.仅播放歌曲场景,某些音响如果长时间处于stop(本地a2dp_player stop 或对端avdtp suspend)状态,会主动断开连接。log会提示bt_api_event_cb:Disconnected from xx:xx:xx:xx:xx:xx2.某些音响不会向本地注册avrcp playback notify,会导致两端操作播放暂停时状态不一致的现象。3.某些音响不会向本地报告avrcp volume change,此时音响调节音量,central无法得知。4.某些音响不支持设置绝对音量。
3.3 其他注意事项
1.如果sdcard有问题,会出现 f_mount failed 或 read data crc error 的提示
4 ble gatt 测试
4.1 单板作为client与手机连接测试
1.手机使用nrf connect开启广播2.开发板a输入ble_gatt_demo gattc init3.开发板a输入ble_gatt_demo gattc connect <手机adv地址> 13.连接成功会有log打印BK_BLE_GAP_CONNECT_COMPLETE_EVT4.discover完成会有打印BK_GATTC_DIS_SRVC_CMPL_EVT
4.2 双板对测
1.开发板a输入ble_gatt_demo gatts init2.开发板b输入ble_gatt_demo gattc init3.开发板b输入ble_gatt_demo gattc connect <a板地址> 14.连接成功会有log打印BK_BLE_GAP_CONNECT_COMPLETE_EVT5.b板discover完成会有打印BK_GATTC_DIS_SRVC_CMPL_EVT6.后续a板会定时给b板notify
5 结构图
5.1 运行时
Figure 1. software module architecture
5.2 流程图
Figure 2. flow chart
6 重要流程说明
6.1 连接音响
int bt_a2dp_source_demo_connect(uint8_t *addr)
{
...
//连接对端
err = bk_bt_a2dp_source_connect(a2dp_env.peer_addr.addr);
if (err)
{
a2dp_loge("connect a2dp err %d", err);
goto error;
}
a2dp_logi("start wait a2dp connect cb");
//等待连接成功
err = rtos_get_semaphore(&s_bt_api_event_cb_sema, 12 * 1000);
if (err)
{
a2dp_loge("get sem for connect err");
goto error;
}
a2dp_logi("start wait a2dp cap report cb");
//等待获取a2dp cap
err = rtos_get_semaphore(&s_bt_api_event_cb_sema, 6 * 1000);
if (err)
{
a2dp_loge("get sem for cap select err");
goto error;
}
a2dp_logi("a2dp connect complete");
...
}
6.2 mp3解码
static void bt_a2dp_source_decode_task(void *arg)
{
...
while (*task_ctrl)
{
...
if (0 != mp3_read_end_ptr - current_mp3_read_ptr - bytesleft)
{
//读取文件
fr = f_read(&mp3file, current_mp3_read_ptr + bytesleft, mp3_read_end_ptr - current_mp3_read_ptr - bytesleft, &num_rd);
if (fr != FR_OK)
{
a2dp_loge("read %d %s failed!", num_rd, full_path);
goto error;
}
if (!num_rd)
{
//回到文件头
a2dp_logi("file end, return to begin");
os_memmove(mp3_read_start_ptr, current_mp3_read_ptr, bytesleft);
current_mp3_read_ptr = mp3_read_start_ptr;
f_lseek(&mp3file, frame_start_offset);
continue;
}
...
}
a2dp_logv("bytesleft %d %p", bytesleft, current_mp3_read_ptr);
do
{
...
//判断ring buffer是否满
while (ring_buffer_particle_len(&s_rb_ctx) > s_decode_trigger_size)
{
if (!*task_ctrl)
{
goto error;
}
a2dp_logd("ring buffer not read too much, wait %d", ring_buffer_particle_len(&s_rb_ctx));
rtos_get_semaphore(&s_source_need_decode_sema, BEKEN_WAIT_FOREVER);
}
//解码
ret = MP3Decode(s_mp3_decoder, ¤t_mp3_read_ptr, &bytesleft, (int16_t *)pcm_write_ptr, 0);
//异常处理
if (ret != ERR_MP3_NONE)
{
if (ERR_MP3_INDATA_UNDERFLOW == ret)
{
bytesleft = last_success_bytesleft;
current_mp3_read_ptr = last_success_read_ptr;
break;
}
goto error;
}
//写ring buffer
if (ring_buffer_particle_write(&s_rb_ctx, pcm_write_ptr, tmp_mp3_frame_info.outputSamps * tmp_mp3_frame_info.bitsPerSample / 8))
{
a2dp_logd("ring_buffer full %d", ring_buffer_particle_len(&s_rb_ctx));
...
continue;
}
else
{
a2dp_logv("write %d, %d", tmp_mp3_frame_info.outputSamps * tmp_mp3_frame_info.bitsPerSample / 8, ring_buffer_particle_len(&s_rb_ctx));
}
pcm_decode_size += tmp_mp3_frame_info.outputSamps * tmp_mp3_frame_info.bitsPerSample / 8;
}
while (tmp_mp3_frame_info.outputSamps && *task_ctrl);
}
...
}
6.3 host a2dp 取数回调(pcm)
static int32_t a2dp_source_data_cb(uint8_t *buf, int32_t len)
{
uint32_t read_len = 0;
...
//从ring buffer读取
if (ring_buffer_particle_len(&s_rb_ctx) < len)
{
a2dp_loge("ring buffer not enough data %d < %d ", ring_buffer_particle_len(&s_rb_ctx), len);
}
else
{
ring_buffer_particle_read(&s_rb_ctx, buf, len, &read_len);
}
//通知mp3继续解码
if (s_source_need_decode_sema)
{
rtos_set_semaphore(&s_source_need_decode_sema);
}
return read_len;
}
6.3 host a2dp 重采样回调
static int32_t a2dp_source_pcm_resample_cb(uint8_t *in_addr, uint32_t *in_len, uint8_t *out_addr, uint32_t *out_len)
{
...
bt_audio_resample_req_t rsp_req;
os_memset(&rsp_req, 0, sizeof(rsp_req));
input_len = *in_len;
output_len = *out_len;
rsp_req.in_addr = in_addr;
rsp_req.out_addr = out_addr;
rsp_req.in_bytes_ptr = &input_len;
rsp_req.out_bytes_ptr = &output_len;
//发送给cpu1重采样
ret = media_send_msg_sync(EVENT_BT_PCM_RESAMPLE_REQ, (uint32_t)&rsp_req);
if (ret)
{
a2dp_loge("EVENT_BT_PCM_RESAMPLE_REQ err %d !!", ret);
ret = -1;
}
...
return ret;
}
6.4 host a2dp sbc编码回调
static int32_t a2dp_source_pcm_encode_cb(uint8_t type, uint8_t *in_addr, uint32_t *in_len, uint8_t *out_addr, uint32_t *out_len)
{
...
bt_audio_encode_req_t rsp_req;
int ret = 0;
os_memset(&rsp_req, 0, sizeof(rsp_req));
rsp_req.handle = &s_sbc_software_encoder_ctx;
rsp_req.in_addr = in_addr;
rsp_req.type = type;
rsp_req.out_len_ptr = (typeof(rsp_req.out_len_ptr))&encode_len;
//发送给cpu1编码
ret = media_send_msg_sync(EVENT_BT_PCM_ENCODE_REQ, (uint32_t)&rsp_req);
if (ret)
{
a2dp_loge("EVENT_BT_PCM_ENCODE_REQ err %d !!", ret);
return -1;
}
...
return 0;
}
6.5 ble gap初始化
int dm_gatt_main(cli_gatt_param_t *param)
{
...
//注册gap回调
bk_ble_gap_register_callback(dm_ble_gap_private_cb);
dm_gatt_add_gap_callback(dm_ble_gap_common_cb);
//读取本地ir/er
bluetooth_storage_read_local_key(&s_dm_gap_local_key);
//向host设置ir/er
ret = bk_ble_gap_set_security_param(BK_BLE_SM_SET_ER, (void *)s_dm_gap_local_key.er, sizeof(s_dm_gap_local_key.er));
...
ret = bk_ble_gap_set_security_param(BK_BLE_SM_SET_IR, (void *)s_dm_gap_local_key.ir, sizeof(s_dm_gap_local_key.ir));
//生成rpa地址
...
ret = bk_ble_gap_generate_rpa(NULL);
...
//设置privacy
ret = bk_ble_gap_config_local_privacy(s_dm_gatt_privacy_enable);
//循环添加已配对设备
for()
{
ret = bk_ble_gap_bond_dev_list_operation(BK_GAP_BOND_DEV_LIST_OPERATION_ADD, &bond_dev);
}
//设置iocap
ret = bk_ble_gap_set_security_param(BK_BLE_SM_IOCAP_MODE, (void *)&s_dm_gatt_iocap, sizeof(s_dm_gatt_iocap));
//设置配对请求auth参数
ret = bk_ble_gap_set_security_param(BK_BLE_SM_AUTHEN_REQ_MODE, (void *)&s_dm_gatt_auth_req, sizeof(s_dm_gatt_auth_req));
//设置密钥分发种类
ret = bk_ble_gap_set_security_param(BK_BLE_SM_SET_INIT_KEY, (void *)&s_dm_gatt_init_key_distr, sizeof(s_dm_gatt_init_key_distr));
ret = bk_ble_gap_set_security_param(BK_BLE_SM_SET_RSP_KEY, (void *)&s_dm_gatt_rsp_key_distr, sizeof(s_dm_gatt_rsp_key_distr));
//设置gatt mtu
bk_ble_gatt_set_local_mtu(517);
}
6.6 ble gatts初始化
int dm_gatts_main(cli_gatt_param_t *param)
{
//初始化gap
dm_gatt_main(NULL);
//注册gatts回调,注册gatts app
bk_ble_gatts_register_callback(bk_gatts_cb);
ret = bk_ble_gatts_app_register(0);
//设置service
ret = bk_ble_gatts_create_attr_tab(s_gatts_attr_db_service_1, s_gatts_if, sizeof(s_gatts_attr_db_service_1) / sizeof(s_gatts_attr_db_service_1[0]), 30);
ret = bk_ble_gatts_create_attr_tab(s_gatts_attr_db_service_2, s_gatts_if, sizeof(s_gatts_attr_db_service_2) / sizeof(s_gatts_attr_db_service_2[0]), 30);
//开启service
bk_ble_gatts_start_service(s_service_attr_handle);
//注册gap回调(仅gatts关心)
dm_gatt_add_gap_callback(dm_ble_gap_cb);
//设置adv参数
ret = dm_gatts_set_adv_param(s_dm_gatts_local_addr_is_public);
//设置adv random addr(仅rpa开启或自定义addr)
if (need_set_random_addr)
{
ret = bk_ble_gap_set_adv_rand_addr(ADV_HANDLE, current_addr);
if (ret)
{
gatt_loge("bk_ble_gap_set_adv_rand_addr err %d", ret);
goto error;
}
ret = rtos_get_semaphore(&s_ble_sema, SYNC_CMD_TIMEOUT_MS);
if (ret != kNoErr)
{
gatt_loge("wait set adv rand addr err %d", ret);
goto error;
}
}
//设置adv data
ret = bk_ble_gap_set_adv_data((bk_ble_adv_data_t *)&adv_data);
//开启adv
ret = bk_ble_gap_adv_start(1, &ext_adv);
...
}
6.7 ble gattc初始化
int dm_gattc_main(cli_gatt_param_t *param)
{
...
//注册gap回调(仅gattc关心)
dm_gatt_add_gap_callback(dm_ble_gap_cb);
...
//注册gattc回调
bk_ble_gattc_register_callback(bk_gattc_cb);
...
//注册gattc app
ret = bk_ble_gattc_app_register(0);
...
//设置connect/scan rand addr(仅rpa开启或自定义addr)
if (need_set_random_addr)
{
ret = bk_ble_gap_set_rand_addr(current_addr);
if (ret)
{
gatt_loge("bk_ble_gap_set_rand_addr err %d", ret);
goto error;
}
ret = rtos_get_semaphore(&s_ble_sema, SYNC_CMD_TIMEOUT_MS);
if (ret != kNoErr)
{
gatt_loge("wait set rand addr err %d", ret);
goto error;
}
}
...
}
6.8 ble gattc 连接
int32_t dm_gattc_connect(uint8_t *addr, uint32_t addr_type)
{
...
//设置连接参数
bk_gap_create_conn_params_t param = {0};
bk_bd_addr_t peer_id_addr = {0};
bk_ble_addr_type_t peer_id_addr_type = BLE_ADDR_TYPE_PUBLIC;
param.scan_interval = 800;
param.scan_window = param.scan_interval / 2;
param.initiator_filter_policy = 0;
//决定是否用rpa连接
if (g_dm_gap_use_rpa && 0 == dm_gatt_find_id_info_by_nominal_info(addr, addr_type, peer_id_addr, &peer_id_addr_type))
{
gatt_logi("local use rpa");
param.local_addr_type = (s_dm_gattc_local_addr_is_public ? BLE_ADDR_TYPE_RPA_PUBLIC : BLE_ADDR_TYPE_RPA_RANDOM);
os_memcpy(param.peer_addr, addr, sizeof(param.peer_addr));
param.peer_addr_type = addr_type;
}
else
{
param.local_addr_type = (s_dm_gattc_local_addr_is_public ? BLE_ADDR_TYPE_PUBLIC : BLE_ADDR_TYPE_RANDOM);
os_memcpy(param.peer_addr, addr, sizeof(param.peer_addr));
param.peer_addr_type = addr_type;
}
//设置连接参数
param.conn_interval_min = 16;
param.conn_interval_max = 16;
param.conn_latency = 0;
param.supervision_timeout = 500;
param.min_ce = 0;
param.max_ce = 0;
err = bk_ble_gap_connect(¶m);
...
return err;
}
7 常见问题FAQ
7.1 ble连接、断连evt是怎样的顺序?
1. BK_BLE_GAP_CONNECT_COMPLETE_EVT -> dm_gatts.c dm_gattc.c BK_GATTS_CONNECT_EVT|BK_GATTC_CONNECT_EVT -> 各个上层profile的 BK_GATTS_CONNECT_EVT|BK_GATTC_CONNECT_EVT2. 各个上层profile的 BK_GATTS_DISCONNECT_EVT|BK_GATTC_DISCONNECT_EVT -> dm_gatts.c dm_gattc.c BK_GATTS_DISCONNECT_EVT|BK_GATTC_DISCONNECT_EVT -> BK_BLE_GAP_CONNECT_COMPLETE_EVT
7.2 蓝牙多模(bt/ble)链接的有哪些库?
libbluetooth_host_dm_dual.a 和 libbluetooth_controller_dual.a
7.3 对于一个已存在的ble链路,有没有handle之类的以便于控制?
BK_GATTS_CONNECT_EVT和 BK_GATTC_CONNECT_EVT中有conn_id,所有gatts/gattc接口都以此作为handle。这些evt也包含对端addr可供上层作映射关系。BK_BLE_GAP_CONNECT_COMPLETE_EVT也有hci_handle,但不能在gatt直接使用,需要通过转换接口。
7.4 如何创建gatts db,如何将其和attribute handle对应起来?
1.请参考dm_gatts.c的表结构。2.表reg成功后会上报BK_GATTS_CREAT_ATTR_TAB_EVT,handles对应表每一行的attribute handle(其中char decl/char value只有后者有效)
7.5 bk_ble_gap_register_callback能不能复用,比如多个profile都想收到evt?
不能,但在dm_gatt.c里有个dm_gatt_add_gap_callback通过bk_ble_gap_register_callback封装了这个功能,更多细节请参考dm_gatts.c dm_gattc.c及现有profile的实现
7.6 attribute handle的evt能不能分别给到各个profile?
请参考dm_gatts_reg_db的实现
7.7 各个profile能否收到gatts的evt?
请参考dm_gatts_reg_db的实现,目前已添加了几个evt,可以根据需要修改
7.8 配对相关的evt有哪些流程?
BK_BLE_GAP_SEC_REQ_EVT 配对请求BK_BLE_GAP_AUTH_CMPL_EVT 配对结果BK_BLE_GAP_BOND_KEY_GENERATE_EVT key生成BK_BLE_GAP_PASSKEY_NOTIF_EVT passkey生成通知BK_BLE_GAP_PASSKEY_REQ_EVT 本地输入passkey req evtBK_BLE_GAP_NC_REQ_EVT 本地number compare请求大致流程是BK_BLE_GAP_SEC_REQ_EVT -> passkey、number compare(如果有) -> BK_BLE_GAP_BOND_KEY_GENERATE_EVT -> BK_BLE_GAP_AUTH_CMPL_EVT
7.9 app应该存储key吗?
需要,BK_BLE_GAP_BOND_KEY_GENERATE_EVT需要存入flash
7.10 绑定(bond)和解绑流程?
1.绑定:BK_BLE_GAP_BOND_KEY_GENERATE_EVT后需要存入flash2.解绑:通过bk_ble_gap_bond_dev_list_operation 删除(有限制)
7.11 绑定相关还有哪些注意事项?
1.绑定涉及到的存储分app部分和sdk部分2.配对成功后,sdk上报BK_BLE_GAP_BOND_KEY_GENERATE_EVT,app需要将key存入flash中以备下次开机使用。sdk也会存储这部分key但只是在ram里。3.配对成功后,如果断连并第二次连接,如果对端请求加密、鉴权,结果也会在BK_BLE_GAP_AUTH_CMPL_EVT体现4.配对失败(或者加密、鉴权失败),auth_cmpl.success为0,则app需要将对应key从flash删除5.一旦app决定要删除key,则同时还需要调用bk_ble_gap_bond_dev_list_operation删除sdk存储的key6.使用bk_ble_gap_bond_dev_list_operation时,要保证当前ble不在广播、扫描状态。7.使用bk_ble_gap_bond_dev_list_operation删除或清空key时,sdk不会断开涉及的链路8.开机蓝牙初始化后,开广播、扫描前,需要用bk_ble_gap_bond_dev_list_operation将先前BK_BLE_GAP_BOND_KEY_GENERATE_EVT的key添加给sdk
7.12 如何修改ble地址?
1.如果是public,则通过bk_get_mac获取,可以通过bk_set_base_mac设置(具体请参考对应文档)。bk_ble_gap_ext_adv_params_t.own_addr_type = BLE_ADDR_TYPE_PUBLIC; bk_gap_create_conn_params_t.local_addr_type = BLE_ADDR_TYPE_PUBLIC2.如果是random,对于发adv:bk_ble_gap_set_adv_rand_addr设置地址且bk_ble_gap_ext_adv_params_t.own_addr_type = BLE_ADDR_TYPE_RANDOM; 对于master或扫描:bk_ble_gap_set_rand_addr设置地址且bk_gap_create_conn_params_t.local_addr_type = BLE_ADDR_TYPE_RANDOM
7.13 bt、ble public addr能否不一样?
目前只能一样
7.14 rpa是什么意思?
RPA(resolvable private address): ble一种防止跟踪的随机地址生成、使用机制,基于random addr,开启后adv、scan、connect的local addr由特殊算法生成,在空口上看是随机的。
7.15 ble如何使用rpa?
dm_gatt.c dm_gatts.c dm_gattc.c里,g_dm_gap_use_rpa = 1; s_dm_gattc_local_addr_is_public = 0; s_dm_gatts_local_addr_is_public = 0;
7.16 ble如果配对使用,推荐用哪种地址?
只推荐public或rpa,单纯random addr会在与手机交互的场景里出现兼容性问题。