一、问题现象

由于设备支持双模蓝牙,设备的BLE需求中,既需要支持作为从机被手机等设备连接,也支持作为主机连接蓝牙手柄等外设,即在播放音频时,允许同时进行低功耗蓝牙相关的功能。实际开发过程中发现在播放音频时,如果发起BLE扫描、连接了蓝牙手柄或其他设备之后,蓝牙音频会变的十分卡顿。

二、问题分析

设备使用的蓝牙模组是单天线模组,性能较弱。实测播放蓝牙音频时,手机BLE连接设备时,并不会造成音频卡顿,因此怀疑是手机BLE连接设备时,连接间隔(连接间隔就是通信间隔)较大,因此不会频繁占用天线,天线资源能合理地分配给蓝牙音频。反过来说,会造成卡顿的情况,必定是占住了天线资源,因此优化的方向就是在满足使用需求的前提下,尽可能地少占用天线。需要按照不同的BLE功能场景,逐点优化。

三、扫描优化

在内核net/bluetooth/hci_request.c中如下代码:

static int active_scan(struct hci_request *req, unsigned long opt)
{......memset(&param_cp, 0, sizeof(param_cp));param_cp.type = LE_SCAN_ACTIVE;param_cp.interval = cpu_to_le16(interval);param_cp.window = cpu_to_le16(DISCOV_LE_SCAN_WIN);param_cp.own_address_type = own_addr_type;hci_req_add(req, HCI_OP_LE_SET_SCAN_PARAM, sizeof(param_cp), &param_cp);......
}static void start_discovery(struct hci_dev *hdev, u8 *status)
{   ......hci_req_sync(hdev, active_scan, DISCOV_LE_SCAN_INT, HCI_CMD_TIMEOUT, status);......
}   // DISCOV_LE_SCAN_WIN和DISCOV_LE_SCAN_INT定义如下:
#define DISCOV_LE_SCAN_WIN              0x12
#define DISCOV_LE_SCAN_INT              0x12

从上述代码分析可知,开启BLE扫描后,内核使用的扫描间隔和扫描窗口都是0x12,而扫描的占空比等于扫描窗口 / 扫描间隔,此时扫描占空比为100%,所以严重占用了天线,修改代码如下。实际上hdev中携带了扫描间隔和扫描窗口这两个参数,直接使用这两个参数即可,内核中默认的扫描间隔为0x60,扫描窗口为0x30,这两个值也可通过上层API进行修改。不知为何,内核直接使用了上面的两个宏,是个不小的BUG。

static int active_scan(struct hci_request *req, unsigned long opt)
{......memset(&param_cp, 0, sizeof(param_cp));param_cp.type = LE_SCAN_ACTIVE;param_cp.interval = cpu_to_le16(interval);param_cp.window = cpu_to_le16(req->hdev->le_scan_window);param_cp.own_address_type = own_addr_type;hci_req_add(req, HCI_OP_LE_SET_SCAN_PARAM, sizeof(param_cp), &param_cp);......
}static void start_discovery(struct hci_dev *hdev, u8 *status)
{   ......hci_req_sync(hdev, active_scan, hdev->le_scan_interval, HCI_CMD_TIMEOUT, status);......
}

至于为什么会查到内核的这段代码,是因为事先使用hcidump工具抓到了扫描参数配置:

四、BLE主机优化

当与手柄配对完成后,手柄会主动发起连接参数请求更新,请求将连接间隔改到0x0C以提高操作实时性,且设备选择了接收该参数并更新,如下图所示。

手柄操纵的实时性固然重要,但也要分场景,如果手柄是用来玩游戏,这对实时性的要求会比较高,而在我的设备上,手柄只是用来远程控制设备,对实时性的要求相对来说没有那么高。因此我们可以考虑拒绝手柄发起的连接参数更新请求,即使用设备在连接时指定的连接参数。

在内核net/bluetooth/l2cap_core.c中有如下函数:

static inline int l2cap_conn_param_update_req(struct l2cap_conn *conn,struct l2cap_cmd_hdr *cmd,u16 cmd_len, u8 *data)
{......// 检查连接参数合法性err = hci_check_conn_params(min, max, latency, to_multiplier);if (err)  //参数不合法应答rejectrsp.result = cpu_to_le16(L2CAP_CONN_PARAM_REJECTED);    else        //参数合法应答acceptrsp.result = cpu_to_le16(L2CAP_CONN_PARAM_ACCEPTED); // 发送应答结果l2cap_send_cmd(conn, cmd->ident, L2CAP_CONN_PARAM_UPDATE_RSP,sizeof(rsp), &rsp);// 如果接受该连接参数更新请求,发起连接参数更新流程if (!err) {u8 store_hint;store_hint = hci_le_conn_update(hcon, min, max, latency,to_multiplier);mgmt_new_conn_param(hcon->hdev, &hcon->dst, hcon->dst_type,store_hint, min, max, latency,to_multiplier);}......
}

上面的函数会检查连接参数的合法性,如下所示,简单看一下就会发现hci_check_conn_params只是简单判断了连接参数是否在蓝牙spec规定的范围内,并不会根据自身设备的允许的最小、最大连接间隔进行判断。

static inline int hci_check_conn_params(u16 min, u16 max, u16 latency,u16 to_multiplier)
{u16 max_latency;if (min > max || min < 6 || max > 3200)return -EINVAL;if (to_multiplier < 10 || to_multiplier > 3200)return -EINVAL;if (max >= to_multiplier * 8)return -EINVAL;max_latency = (to_multiplier * 4 / max) - 1;if (latency > 499 || latency > max_latency)return -EINVAL;return 0;
}

我们修改一下l2cap_conn_param_update_req函数,判断从机发起的连接参数更新请求参数是否在设备允许的范围内,如果不在该范围内,我们应答拒绝!

static inline int l2cap_conn_param_update_req(struct l2cap_conn *conn,struct l2cap_cmd_hdr *cmd,u16 cmd_len, u8 *data)
{......// 检查从机请求的连接间隔是否在主机允许的范围内if(min < hcon->le_conn_min_interval || max > hcon->le_conn_max_interval){err = -EINVAL; }else  {// 检查连接参数合法性err = hci_check_conn_params(min, max, latency, to_multiplier);}if (err)   //参数不合法应答rejectrsp.result = cpu_to_le16(L2CAP_CONN_PARAM_REJECTED);    else        //参数合法应答acceptrsp.result = cpu_to_le16(L2CAP_CONN_PARAM_ACCEPTED); // 发送应答结果l2cap_send_cmd(conn, cmd->ident, L2CAP_CONN_PARAM_UPDATE_RSP,sizeof(rsp), &rsp);// 如果接受该连接参数更新请求,发起连接参数更新流程if (!err) {u8 store_hint;store_hint = hci_le_conn_update(hcon, min, max, latency,to_multiplier);mgmt_new_conn_param(hcon->hdev, &hcon->dst, hcon->dst_type,store_hint, min, max, latency,to_multiplier);}......
}

修改后,再次抓取hcilog,如下所示,设备拒绝了手柄发起的连接参数更细请求,后续的通信会保持设备在连接请求中指定的连接间隔,即45ms。

五、BLE从机优化

当设备作为从机时,会被对段主机设备连接。初始的连接参数由主机指定。

一些主机的初始连接连接和手柄一样,也是12(15ms),但设备马上会发起连接参数更新请求,将其改大。

我们的目的就是将连接间隔避免音频卡顿,但是这里这样做是有问题的,问题在于这个连接参数请求太早了,在BLE中,主机连接从机后,主机一般需要发起发现从机服务的流程,这个流程的快慢很大程度上取决于连接间隔。如果发现服务的过程较慢,从客观角度来看,主机与从机的连接过程是很慢的。即问题就在于主机还没有发现设备的服务,设备就已经发起了连接参数更新。因此需要延迟发起连接参数更新。

来看一下内核是如何处理这一流程的:

static void l2cap_le_conn_ready(struct l2cap_conn *conn)
{struct hci_conn *hcon = conn->hcon;....../* For LE slave connections, make sure the connection interval* is in the range of the minium and maximum interval that has* been configured for this connection. If not, then trigger* the connection update procedure.*/// 作为BLE从机时,如果主机指定的连接间隔不在从机允许的范围内时,发起连接参数更新请求if (hcon->role == HCI_ROLE_SLAVE &&(hcon->le_conn_interval < hcon->le_conn_min_interval ||hcon->le_conn_interval > hcon->le_conn_max_interval)) {struct l2cap_conn_param_update_req req;req.min = cpu_to_le16(hcon->le_conn_min_interval);req.max = cpu_to_le16(hcon->le_conn_max_interval);req.latency = cpu_to_le16(hcon->le_conn_latency);req.to_multiplier = cpu_to_le16(hcon->le_supv_timeout);l2cap_send_cmd(conn, l2cap_get_ident(conn),L2CAP_CONN_PARAM_UPDATE_REQ, sizeof(req), &req);}
}

从上面的函数可以看出,内核的处理是在建立L2CAP连接后立即判断主机指定的连接间隔是否在从机允许的范围内,如果不在范围内就会立即发起更新,和我们sniffer抓取的结果相符。

既然要延迟连接参数更新的流程,我们利用内核中的延迟工作队列来完成,一般来说,主机发现从机服务只需要2秒,我们预留一些余量,4秒后再发起连接参数更新请求流程,修改代码如下:

static void l2cap_le_conn_ready(struct l2cap_conn *conn)
{struct hci_conn *hcon = conn->hcon;....../* For LE slave connections, make sure the connection interval* is in the range of the minium and maximum interval that has* been configured for this connection. If not, then trigger* the connection update procedure.*/if (hcon->role == HCI_ROLE_SLAVE &&(hcon->le_conn_interval < hcon->le_conn_min_interval ||hcon->le_conn_interval > hcon->le_conn_max_interval)) {// 4秒后再发起连接schedule_delayed_work(&conn->update_timer, msecs_to_jiffies(4000));}
}static void l2cap_update_timeout(struct work_struct *work)
{struct l2cap_conn *conn = container_of(work, struct l2cap_conn,update_timer.work);struct hci_conn *hcon = conn->hcon;struct l2cap_conn_param_update_req req;req.min = cpu_to_le16(hcon->le_conn_min_interval);req.max = cpu_to_le16(hcon->le_conn_max_interval);req.latency = cpu_to_le16(hcon->le_conn_latency);req.to_multiplier = cpu_to_le16(hcon->le_supv_timeout);l2cap_send_cmd(conn, l2cap_get_ident(conn),L2CAP_CONN_PARAM_UPDATE_REQ, sizeof(req), &req);
}

BlueZ双模蓝牙音频卡顿问题优化相关推荐

  1. ViewPager -- Fragment 切换卡顿 性能优化

    当ViewPager切换到当前的Fragment时,Fragment会加载布局并显示内容,如果用户这时快速切换ViewPager,即 Fragment需要加载UI内容,而又频繁地切换Fragment, ...

  2. 解决WIN10 连接蓝牙音响卡顿的问题

    解决WIN10 连接蓝牙音响卡顿的问题 问题:在更新电脑驱动或者做一些其他事情. 解决方法:卸载蓝牙耳机驱动,重启电脑. 注:我这个是是win10 小新.如果以下步骤和你的不一样或许可以参考以下,却载 ...

  3. 漫步者W820蓝牙连接电脑后,音频卡顿解决方案

    如果你购买的耳机"漫步者W820NB"有蓝牙连接电脑卡顿的问题,可以尝试查看一下你电脑的声卡是不是最新版本 查看声卡方法,可以下载软件"驱动精灵",链接如下:h ...

  4. 解决使用 Bluetooth Audio Receiver 蓝牙传音卡顿问题

    很多朋友在使用 Bluetooth Audio Receiver 蓝牙投屏声音时发生了问题,我也查找了很久,终于自己尝试出了解决方案  好像查了个寂寞 解决方案: 1.打开控制面板 (Win+R运行输 ...

  5. Android 7.1 高德导航和蓝牙音乐卡顿问题 蓝牙电话和高德语音播报混音问题

    此文章主要解决三个问题 1.高德导航的时候打电话会出现混音问题. 2.蓝牙音乐在播放的时候导航界面语音播报蓝牙音乐会暂停,播报结束会恢复播放不能同时输出问题. 3.蓝牙音乐在播放的时候和导航界面的语音 ...

  6. 黑苹果音频卡顿_iPhone用户必升!苹果iOS 14.2正式发布

    苹果在今天凌晨发布了 iOS 14.2 和 iPadOS 14.2 正式版.本次更新修复了以下问题: 主屏幕上程序坞中的 App 可能排序混乱 相机取景器在启动时可能显示成黑色 锁定屏幕中的键盘在您尝 ...

  7. c# 多线程界面卡顿_优化electron客户端卡顿的几种方案

    背景 公司需要做一个同步盘的客户端,框架技术选型方面使用了支持跨平台的Electron框架,其中一些核心功能就是文件的上传,和下载,考虑到node操作文件比较方便,起初把文件的下载上传操作放到主进程, ...

  8. 苹果电脑win10蓝牙音响卡顿_Win10使用蓝牙鼠标老是卡顿的原因及解决方法

    随着科技的发展,很多用户开始倾向于在自己的win10电脑配对蓝牙耳机,在使用过程中蓝牙鼠标老是卡顿,影响正常使用.这个问题之前在win7系统就遇到过,到了win10系统依旧如此.如果蓝屏鼠标设备是正常 ...

  9. 西瓜卡顿 ANR 优化治理及监控体系建设

    背景 卡顿 & ANR 在各 APP 中都是非常影响用户体验的问题,关于其的分析和治理一直也是个老生常谈的话题.过去调查卡顿 & ANR 问题主要依赖上报的堆栈和 traceInfo ...

最新文章

  1. 使用onenote记HTML笔记,如何在Windows 10中使用OneNote做笔记
  2. oracle快照太旧含义,全解ORA-1555快照太旧错误原理及解决方案
  3. Kubernetes 1.6新特性
  4. git 回退到某个commit_it lesson2 版本回退
  5. jQuery 1.7.1 代码研究 extend
  6. 啊哈java_1.桶排序——啊哈算法java实现
  7. 消息称京东方通过苹果认证 本月开始向iPhone 12供货OLED面板
  8. win10+Ubuntu16.04双系统安装——史上最稳总结,排除一切花里花哨
  9. vs各个版本的编译器号
  10. 最全教程:微信小程序开发入门详解
  11. 中国天气网天气城市ID
  12. 如何将手机的网络代理给电脑 win10
  13. Cabbage教学(4)——面向对象编程
  14. 【GPS】在线经纬度距离计算
  15. 查找整数 本题要求从输入的N个整数中查找给定的X
  16. 5种常见的服务器种类是哪些
  17. Origin——全局垂直光标
  18. UESTC 1635 最大最小生成树
  19. PTA 7-2 USB接口的定义(接口、接口变量、接口数组)
  20. 如何彻底删除keil5

热门文章

  1. 如何使用阿里云虚拟主机搭建博客(三)设置篇
  2. 学习笔记 | STAR原则
  3. Redis 的 漏斗限流
  4. Android与iOS的区别
  5. 交换机与路由器与猫的区别与合作(带简单实例)
  6. 根据日期判断星期几(使用基姆拉尔森计算公式)
  7. 基于Java后台(Springboot框架)+前端小程序(MINA框架)+Mysql数据库的菜谱美食小程序系统设计与实现
  8. 2048小游戏及改进
  9. 拥抱区块链和金融科技,开启新金融时代
  10. 在Kettle里使用时间戳实现变化数据捕获(CDC)