给自己的 PC 修了 6 个小时的 bug,观后感:
我的电脑连着一台蓝牙音箱,只要在浏览器里随手暂停一个视频,整个系统当场原地失聪。
这个弱智 bug 折磨了我整整 6 年。
昨晚终于把雷德蒙德这帮草台班子的裤衩给扒了。解法极其抽象:在后台常驻一个 0 音量无限循环音频,且必须向视窗注册系统媒体传输控件。
年初才骂完 微软边缘 这个咖喱浏览器,这次轮到他们 整套咖喱系统的左右脑在互搏。
下面是完整的坐牢破案过程。不想看哥们破防请直接跳解法 →
导火索:一只被冤枉了 6 年的透明水母
先介绍一下本案中最大的受害者:我桌上摆着一台尊贵的哈曼卡顿·声音棍·第四代HK SoundSticks 4,即「哈曼卡顿 水晶4」。
长得像未来水母的透明音响,底下那颗大低音炮跟个晶莹剔透的大果冻似的,灯效还会优雅地呼吸。当年咬牙两千多块买回来的,音质是真的顶未必,摆在桌上排面直接拉满。
昨晚在写我的 Godot 游戏,写累了想在后台开个脱口秀听听,给自己鼓鼓劲。很合理吧?熬大夜的傻子程序员都这么干。
打开浏览器,播放,听了两句不好笑,随手点了一下暂停。
切回游戏——
脱口秀没声、游戏没声、系统提示音没声、拖动音量条那个“噔”的一声也没了。显卡风扇呼呼转,两千多块的哈曼卡顿·声音棍·第四代宛如被物理拔掉了发声器官。
这个 bug,从 6 年前的视窗 10 时代就开始折磨我了。
6 年来,我换过蓝牙驱动、换过英特尔网卡、重装过系统,甚至一度怀疑我的这台哈曼卡顿·声音棍·第四代质量不行——两千多块钱的顶级水母啊!我一度怀疑是不是买到华强北假货了,天天在网上搜售后。
每次哑火,我就得像巴甫洛夫的狗一样,切去浏览器随便点个视频,播一秒再暂停——跟做法事一样,声音才会回来。
直到昨晚我才知道:我的哈曼卡顿·声音棍·第四代冰清玉洁,纯纯是微型软件(Microsoft)脑瘫。
晚上十点,哥们心态彻底爆炸,叫上「克劳德·编码」Claude Code,决定把视窗按在地上往死里解剖。
视窗:发生了某些事情,但我们认为一切正常
抓出来的第一组诊断数据就让我脑溢血:
在视窗的狗眼里,一切好得很。
- 音量合成器里,游戏进程的音量条在疯狂蹦迪
- 音频终结点Audio Endpoint,典中典微软机翻:正常、启用、未静音
- 蓝牙硬件状态:工作正常、已连接
- 底层追踪:数据流以 44100 帧/秒 的恒定速率向设备推进,一帧没丢
- 视窗自带的小故障侦测GlitchDetection:零错误,零告警
我把哑火状态与正常发声状态的十六进制内存对齐对比——连一个 bit 的差别都没有。
1 | [视窗操作系统报告] |
薛定谔的音频。
这就是它能潜伏 6 年的原因:因为在系统自己看来,它健康得像头牛。 日志干干净净,系统自我感觉良好,典中典之“全责装死,已为您解决”。
我那台两千多块的哈曼卡顿·声音棍·第四代,就这么替微型软件背了整整 6 年黑锅。
破案:送外卖的与关防盗门的
破案线索极其弱智。
哑火的时候,我随手按了一下哈曼卡顿·声音棍·第四代顶上的物理音量键——声音棍的灯亮了,视窗这边的音量条纹丝不动。反过来拖电脑音量条,声音棍也毫无反应。
平时这俩是联动的。现在:互不理睬,直接失联。
在蓝牙规范里,音频实际上跑在两条独立的管道上:
真相大白:
你在浏览器点暂停 → 视窗的系统媒体传送控件SMTC通过对讲机吼了一句“暂停!” → 哈曼卡顿·声音棍·第四代老老实实把大门焊死了 → 但开货车那哥们(视窗音频引擎)根本不知道这事,继续以每秒 44100 帧的速度,往焊死的大门上疯狂卸货。
送外卖的骑手在门口一秒扔 44100 份盒饭,客户早就出国旅游把防盗门锁死了。微型软件外卖后台显示:配送中,客户签收率 100%,骑手本月全勤。
雷德蒙德的两套状态机左右脑互搏,同部门俩同事上班谁也不跟谁说话。这就是万亿美元市值的工业奇迹。
18 个假说,全军覆没
从昨晚十点到凌晨四点,我和克劳德·编码提出了整整 18 个假说,然后看着它们一个接一个被抬进停尸房:
蓝牙低功耗模式、USB 选择性挂起、免提配置文件抢占、通信衰减、杜比全景声Dolby Atmos、音频增强、独占模式、硬件卸载、英伟达那一坨没有用的高清晰度多媒体接口HDMI音频终结点……
中间最癫狂的一段:我用视窗音频会话应用程序编程接口WASAPI,写了八种姿势往引擎里硬灌音频流:
轮询、事件驱动、换 AudioCategory、新建流、重用流、真波形、纯静音,最后甚至把铬Chromium的源码扒下来照着参数抄。
我把测试增益拉到终结点电平 0.41(平时听歌才 0.1),功放推到冒烟——依然一个屁都放不出来。
两千多块的哈曼卡顿·声音棍·第四代,被雷德蒙德这帮印度哥们直接干成了一个两千多块、只会发光的亚克力桌面摆件。造型确实顶级,就是他妈的不响。
那浏览器凭什么能响?
搞死我 18 个假说的关键在于:为什么浏览器一播视频就能把声音救回来?
我顺藤摸瓜测了一圈:网易云可以、罐子播放器PotPlayer可以、火狐Firefox也可以。
它们的唯一共同点不是“播放了声音”,而是:
它们都向系统注册了「系统媒体传送控件」(就是你按 Win+A 弹出的那个媒体播控面板)。
只有这帮正规军开始播放,视窗才会拿起对讲机喊一句“正在播放”,音箱收到后才会把大门重新打开。
声音根本不是关键,出入证才是。
我用注册了媒体控件的组件,播放了一段 0 分贝、全空的纯静音音频——
前面写底层 WASAPI 把功放推到冒烟全是在对着铁门骂街;放一段“什么都没有的虚无”,水母活了。
解法
既然音箱一收到“暂停”就会把门焊死,那永远别让它收到暂停不就完了?
写一个无 UI 控制台程序,后台常驻一个永远在播放、音量为 0、无限循环的幽灵媒体会话。只要这玩意一直播,视窗就永远不会向音箱发关闭指令。
核心代码(.NET 10 / WinRT):
1 | <!-- 必须 targeting windows10,否则调用不了 Windows.Media 命名空间 --> |
1 | using Windows.Media.Core; |
⚠️ 避坑 2:千万别想着“监听哑火再补播”。我试过:补播静音结束后,会话状态变回“已停止”,又触发了一次暂停指令,再次精准把音箱干哑——自己一脚踩在自己的狗尾巴上。必须常驻,永不停止。
完整源码我扔 GitHub 上了,WTFPL,你他妈想干啥就干啥:
README 里有英文版的完整说明,包括我实测过的所有失败方案对照表——万一有哪个倒霉蛋跟我一样被这玩意折磨,搜到了能少走 6 年弯路。
⚠️ 避坑 3 值得单独说两句,因为它离谱得有点艺术性。
我最早那版看门狗就是上面注释掉的天真写法:发现不在播放,就调 Play()。跑了 57 分钟之后,我发现控制中心里的「保持蓝牙链路」没了。
进程还活着,看门狗还在每秒兢兢业业地检查,日志刷了几百行「会话不在播放,重新 Play」——但那个 MediaPlayer 对象早就变成尸体了,PlaybackState 是 None,Play() 调下去没有任何反应,也不报错。
也就是说,我写的这个修复程序,得了跟它要修的 bug 一模一样的病:
- 进程活着、循环在跑、日志在刷 → 看起来一切正常
- 实际上早就什么都没在做了 → 保活功能已经死了 57 分钟
我他 🐴 属实是被雷德蒙德给传染了。
它现在像个幽灵一样挂在我的控制中心里:一个永远在播放虚无、永远 0 分贝的滚木播放器。
代价是快捷控制栏里常驻一个狗皮膏药。但比起两千多块的哈曼卡顿·声音棍·第四代随机失声 6 年,这笔账血赚。
后记:名场面集锦
- 排查中发现,英伟达的 HDMI 音频终结点在两个月里自己假死抖动了 1450 次。问题是:我的显示器压根没有扬声器。 它在空气里自嗨了 1450 次。
- 视窗自带的
Audio/Informational事件追踪每秒产生 20000+ 条日志,512MB 环形缓冲区只够存 3 分钟。 - 克劳德·编码中途写了个抓取脚本,启动耗时 90 秒,旨在抓取启动前 20 秒的日志。等它跑起来,想抓的日志早就被刷入虚空了。
- 360 把我们自己编译的修复程序当场报毒拦截。一个开机自启、0音量死循环、拦截不到任何声波的后台进程,确实挺像专业窃听木马的😅。
- 视窗终端Windows Terminal无法打开任何带中文路径的
file://超链接。斜杠、反斜杠、URL 转义全灭。微型软件自家终端打不开自家文件系统上的中文。 - 全程我那台两千多块的哈曼卡顿·声音棍·第四代就在旁边优雅地亮着呼吸灯,蓝牙未断,硬件未休眠,它从头到尾冰清玉洁,就是不出声。它比我还冤。
- 修复程序上线 57 分钟后自己悄悄死了,进程还在、循环还在、日志还在刷,就是不干活了。跟它要修的那个 bug 是同一种死法。 我现在怀疑这病会传染。
总结
这个 bug 必须集齐高级音频分发配置文件 + 音频/视频远程控制配置文件 + 系统媒体传送控件才会完美触发。
踩坑的用户大概率都和我一样:怀疑音箱、怀疑驱动、重连设备、玄学重启,一忍就是 6 年。
最讽刺的是:尊贵的哈曼卡顿·声音棍·第四代替微软背了 6 年黑锅。水晶4 对不起,是我错怪你了,你没有任何问题,是雷德蒙德的脑瘫工程师有问题。
我真的很想穿上我的大皮靴,狠狠地踢爆这帮印↗度↘工程师的屁股。不想好好做蓝牙音频可以不做,滚去给系统塞更多的 领航员Copilot 垃圾吧。
我再说一次:
