抓包屏幕挂灯,制作PC软件,最棒的拾音功能


cani-on-my-light.jpg

事情要从去年12月份的生日说起,朋友在我家观察许久:“我想到送你什么啦!” 是的,没有灯光的电脑桌,观感很昏暗,适合一个屏幕挂灯。

在谢谢朋友之后,这个屏幕挂灯是真不错,可软件却很烂。

来列举一下这个灯支持什么

  • 前置冷暖色调和亮度调整
  • 后置跑马灯
  • 蓝牙控制

蓝牙控制好呀,可是这个挂灯最大的败笔就在这里了,厂商自带的只有一个非常难看的安卓手机APP,作为一个为电脑设计的设备,实在是太不应该了。

那来解决这件事情吧,既然是蓝牙控制,必定是能破解的。


这里有一个最终效果实现,先来看看吧。

注意注意,视频有 54 MB,注意流量使用!打开声音食用更佳。


如果你觉得技术描述很无聊,请跳转到 拾音


蓝牙抓包

最开始是今年,也就是26年1月初,我开始寻找破解抓包之道,我对蓝牙协议只停留在大致理解工作方式。所以通过搜索知道了如何使用 Wireshark 查看蓝牙协议抓包 log。

第一步是 Log 从何处来

采用手上一部已经 Root 的安卓手机,通过开发者选项打开打开蓝牙数据包日志,该选项会在蓝牙开启的一刻开始抓取包,并在关闭蓝牙时结束本次的抓包。为什么需要 Root 呢,原因是小米手机将蓝牙日志放在了非 Root 不可访问的路径 /data/misc/bluetooth/logs/,在这里可以找到 btsnoop_hci_*.log。

在蓝牙开启后,打开自带的控制软件,将需要的功能操作后,关掉蓝牙。取出 btsnoop_hci_*.log 提取到电脑扔入 Wireshark。

具体是如何在 Wireshark 里筛选出该控制包的,已经忘了详细,毕竟我只是兴趣使然。不过大致过程是使用了安卓上的 nRF Connect 去连接设备,寻找到了控制该设备的特征值,并通过 Wireshark 筛选设备码与通讯类型还有该特征值。

一一对比不同控制的蓝牙传输报文,发现 开灯 关灯 仅在一处二进制里出现了差异。

很抱歉由于时间久远我这里没有保存着图片…

AI/LLM 狂欢

不过上面这些都不重要,自己抓包抓了一天,只抓出了如何控制前置灯光开关和亮度控制。使用小测试代码去发送该二进制,灯在我的操作下开开关关。

而接下来,LLM AI agent 的狂欢彻底打破一切软件开发,25 年年末出现的 AI/LLM 将原先25年年中写代码需要我给出完整实现方案才能写出我设想中可用的功能的 AI/LLM,变成了真正的人机协作。

现在 AI/LLM 将开发速度指数型上涨了,如这个抓包按照原先人工预期,由我来弄,需要大半个月的工期,现在直接震撼瘫坐得变成了 8 小时。

我的拖延症让我大半年都没有尝试去实现当时还处于设想里的 抓包屏幕挂灯,制作PC软件,最棒的拾音功能,去玩其它东西去了,时间一眨眼就来到9月。

采用全新 AI/LLM 抓包

还是需要一台 Root 的手机,将 Shell 给予 su 权限,不然 AI/LLM 无法调整系统设置与找到日志路径。通过 adb 连接手机后,告诉 AI/LLM 接下来我会配合它的工作进行安卓 APP 操作。它来进行抓包提取日志提取值后,将我的操作与日志比对。

日照渐弱,抓包工作接近尾声。我将前置灯的开关亮度色调与后置跑马灯的颜色动效速度指令全抓取一空。

这里展出部分指令集(让本文不全是段落,让文章不枯燥😉):

电源  7e 07 04  XX  CH  YY  02 01 ef
  关  XX=00  YY=00
  开  XX=ff  YY=01
  7e 07 04 00 05 00 02 01 ef   前置关
  7e 07 04 ff 05 01 02 01 ef   前置开
  7e 07 04 00 01 00 02 01 ef   后置关
  7e 07 04 ff 01 01 02 01 ef   后置开
-------------------------------------
亮度  7e 04 01  LV  CH  ff 02 01 ef
  LV = 0x01..0x64(约 1%..100%)
  前置 CH=05    后置 CH=01
  7e 04 01 01 05 ff 02 01 ef   前置最低
  7e 04 01 64 05 ff 02 01 ef   前置 100%
  7e 04 01 01 01 ff 02 01 ef   后置最低
  7e 04 01 64 01 ff 02 01 ef   后置 100%
-------------------------------------
前置色温  7e 07 05 02  WW  CW  02 01 ef
  WW+CW 始终 = 0x64
  3000K 64 00  4000K 4b 19  4500K 32 32
  5000K 19 4b  6000K  00 64

软件

在前期准备工作做好后,开始软件制作吧。

最性能的架构

后端必须最性能,为什么呢,因为在刷B站的时候,我可是看到过这个视频的:

现实中声音总是比画面慢,所以视频声音迟了可以接受。但是反过来,声音跑到前头就不对了,大脑在现实中没见过,立刻就会警觉。

来源:音画不同步!为什么视频口型总是怪怪的?点击播放

既然我们后续要做的拾音功能,按照物理规律怎么都是电脑先发出声音再激活光效,我们就要用最快的速度处理识别声音,为后面接下来要做的最重要的拾音功能做准备。

果断选择了神奇的 Rust。————不仅仅是后端,前端我也想用 Rust 写。

作为一个即时使用的软件,在后台运行的软件,想要点击即用的绿色软件,使用非 Web 技术是很重要的。

前端就来使用 GPUI 吧。这是一个还没长大的小屁孩 Rust 界面库,但是足够强大。

请看整体软件架构图:

architecture-diagram.png

请看软件界面:

HML-home.png light-color-intrigue.gif HML-back-color.png

拾音

拾音功能就是让跑马灯跟随我的音乐去闪烁。

这里我一共做了三次迭代。

1. 超过一定的音量激活

这里非常好理解,因为就是超过一定的音量激活光效,一般的歌曲,音量响度变高的时刻就是鼓点的时刻,而鼓点一般来说就是节拍。这个时候超过一定音量就能让灯光跟随节拍舞动。

如果事情就这么简单就好了。

这个音量阈值在哪呢?我们来定义一个灵敏度,指的是响度里超过系统音量里百分之多少就激活。

设置在 50% 就好了吗?

当然不是,每首歌的响度是不一致的。事实上由于音视频领域里从 2000 年代开始的响度战争(可见于响度战争-维基百科的描述),如今的歌曲与90年代的歌曲相差就有 3–9 dB。就在我的日常上,完全可见某个音符图标软件上的视频,需要我调整到手机音量的 20%,才能与我在 bilibili 上 70% 音量打平。

接下来就需要引入一个均方根了。

2. RMS 均方根

这是数学概念,用于在一组值里先平方→再平均→最后开根。数学概念说出来不好,不直观,那就直接看例子吧:

0.5, 0.5, 0.5, 0.5, 0.5       > 0.500
0.2, 0.2, 0.2, 0.2, 0.2       > 0.200
0.01, 0.01, 1, 0.01, 0.01     > 0.447
0.8, 0.3, 0.1, 0.05, 0.02     > 0.385
0.1, 0.1, 0.1, 0.1, 0.1       > 0.100
0.01, 0.01, 0.9, 0.01, 0.01   > 0.403

这下直观了,一组数字,也就是一段时间的音频需要的灵敏度就被我们算出来了。

当然在具体代码实现上,还有一些其他的参数,如削峰填谷。

到了这里,我们的拾音就非常完善了。完全可以实际上线了,但是总感觉还不够,有点问题。

再来看看这个情况。如果:

  • 鼓点密集
  • 重拍和人声响度一致

岂不是一直都在激发光效。这似乎没办法了,还有什么办法能在这些情况里找到节拍呢?

通过资料搜索,我们得引入 传统 AI 了。

3. BeatNet

是真没想到在 2026 这个时间节点,就能使用上传统 AI 这个词汇了,可以看见上文一直用的都是 AI/LLM ,大语言模型已经把 AI 这个词汇给占据了,只能将机器学习列为传统 AI了。

BeatNet 是一个从音频中识别节拍的模型,最关键是拥有实时模式。使用 PyTorch 就能跑起来啦。由于原本设计就是一个绿色软件,我不希望给环境塞入一个 Python 运行时,那么当然要做的是将 BeatNet 导出成 ONNX 通过 C++ 所编写的 ONNX Runtime 的 Rust 绑定 ort-rs 库去直接使用(长难句累死我了)。

一个晚上,只导出来 ONNX 正确跑起来。可里面的音频解析算法还没实现,就在寻找算法的 Rust 库时,发现就在两星期前,就有人和我想法相通,把轮子早一步做出来!beatnet-rs。

气得我连忙发了个 QQ 空间。

不过本着轮子就不要重复造了理论。果断使用该库做出了节拍功能。

beat-mode.png

根据我的实际感受,设计了一个暂缓和激活时间比例启动节拍。当然也可以在软件里一直启用节拍,但是使用体验有时候还不如 RMS 平衡灵敏。两者搭配才是最好的。这里有一个模型响应延迟,每台机器的性能当然也是不一样的,超过一定的响应时间就应该关掉这个节拍模式,不然体验更差。


AI/LLM 与人

现在是凌晨两点,很明显睡醒起床还去打工上班,但是现在写文章写入心流了,我需要结束这篇文章,来用什么结尾呢?

写下这个小标题,在这段话中删删减减,我在这场酣畅淋漓的 Vibe Coding 里做了什么?我提供了我的知识给 AI/LLM。如果不是我收集资料,知道一些小门道,我觉得 AI/LLM 会把技术引导向 Node.js + Python。

在与 AI/LLM 的比拼上,至少还有主观能动性在我手上嘛。

现在回到上面再看看成品吧 > 成品效果 <。或者来看看 > GitHub 项目链接 <


致谢