前言

尽管在基于 Electron 的 NT QQ 推出之后,Linux QQ 体验得到了革新;但 3 年半过去了,时至今日体验依旧不算完整。尤其是涉及到语音的功能,似乎非常摆烂,到今天也不支持在 Linux QQ 上听别人发的气泡语音。

最近偶尔会在 Linux QQ 上接听语音电话,借此发现了另一个显著的问题:不正常的通话延迟。我说完一句话,对端似乎需要等上好几秒才能听到并做出回应。为了验证平时语音对话时感觉到的这一异常,我用 Linux QQ 向我的小号拨打了一通电话,证实了这一点:我的手机甚至需要等上个三四秒才能听到我向电脑发出的声音!(愤怒喵!

作为对比,Windows 和手机 QQ 的语音延迟基本是 <1.5s 的。借助 AI 神力,我找出了导致这个问题的原因。

环境

  • Fedora Workstation 44 (7.2.7-200.fc44.x86_64)
  • PipeWire 1.6.9
  • QQ 3.2.34-53644

分析

首先其他平台上的 QQ 同样是 NT 架构且此问题不可复现,因此先怀疑到 Linux 在音频处理上的差异。

1. 检查是否是 GNOME 环境本身就存在的问题

打开了 GNOME 设置面板,在 Sound - Input 中,发现麦克风音量条是实时显示的,不存在延迟,排除桌面环境本身故障。

2. 检查音频采集节点状态

排除了系统环境问题,就可以开始找 QQ 本身是否存在异常行为了。执行 pw-top,结果如下:

1
2
R  116   2048  48000  19.5us   1.2us  0.00  0.00    0    S16LE 1 48000 alsa_input.usb-USB_BlackShark_V2_Pro_Xbox_2.4-00.mono-fallback
R  109   6521  48000   7.1us   6.7us  0.00  0.00    0    S16LE 2 48000  + TRAE

啊虽然显示了一个 TRAE 容易让人联想到字节的那个 Trae AI IDE,但 毕竟我电脑上没安装 TRAE 实际上经过简单查阅可知这是腾讯自己的实时音频引擎 (Tencent Real-time Audio Engine)

那么 pw-dump 一下:

1
2
109 Node {"application.name": "TRAE", "application.process.binary": "qq", "media.name": "capture-stream", "node.name": "TRAE", "node.latency": "96000/48000", "node.rate": "1/48000", "media.class": "Stream/Input/Audio"}
126 Node {"application.name": "TRAE", "application.process.binary": "qq", "media.name": "render-stream", "node.name": "TRAE", "node.latency": "5040/48000", "node.rate": "1/48000", "media.class": "Stream/Output/Audio"}

注意到其 Input 赫然写着 "node.latency": "96000/48000"!吓!这采集流怎么用 2 秒的延迟啊(恼

3. 分析导致问题的原因

但这一定是某些默认值设置,毕竟 QQ 不太可能在语音环境可以用雷霆大缓冲来降低体验。实际上在 /usr/share/pipewire/pipewire-pulse.conf:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
pulse.properties = {
    # the addresses this server listens on
    ...
    #pulse.min.frag         = 256/48000     # 5.3ms
    #pulse.default.frag     = 96000/48000   # 2 seconds <- 在这捏
    #pulse.default.tlength  = 96000/48000   # 2 seconds
    #pulse.min.quantum      = 256/48000     # 5.3ms
    #pulse.idle.timeout     = 0             # don't pause after underruns
    #pulse.default.format   = F32
    #pulse.default.position = [ FL FR ]
}

对咯,那么在用户级的 pipewire-pulse.conf.d/ 中配置了一个覆写,覆写成 1920/48000 (40ms) 之后,确实就解决了这个问题。

不过这显然应该算是个 Bug。刚好有 AI 神力,让它加载了一个影子库,可以看到 TRAE 传给 libpulse 的原始参数。结果如下:

1
2
[TRAE] [INFO[CreateH264Encoder:487] [SHADOW] record-in pid=290300 rate=48000 ch=2 fmt=3 | max=0 tlen=0 prebuf=0 minreq=0 frag=0 ret=0
[SHADOW] record-out pid=290300 rate=48000 ch=2 fmt=3 | max=0 tlen=0 prebuf=0 minreq=0 frag=0 ret=0

可以看到 maxlength/tlength/prebuf/minreq/fragsize 全为 0,这几乎可以说明它并没有传递 attr,因此默认回落到 0,frag 配置也回落到默认的 96000/48000。

缓解

到这里缓解办法就很显然了。往 ~/.config/pipewire/pipewire-pulse.conf.d/ 写覆盖配置:

1
2
3
4
# 50-lowlatency-capture.conf
pulse.properties = {
    pulse.default.frag = 1920/48000    # 40ms
}

随后 systemctl --user restart pipewire-pulse 即可缓解。

这样做实际上修改了全局的默认配置,算是一种 workround。真正解决的办法应该是让 QQ 的 TRAE 正确传入 Attr 以避免此类情况。

想起来之前还在 Linux QQ 的群文件右键菜单见到 “在 Finder 中显示”… 怎么说都是 QQ 在适配 Linux 上不上心吧!(生气) (好像新版本修了这个文案

兔兔愤怒喵