7.4 KiB
响度怪 · 根因与修复(2026-09-13)
你报的症状
先在 Ubuntu 面板上点模拟输出再点空间音频 → 正常响度 先点数字输出再点空间音频 → 响度很小
一、现场事实(全部实测,不是推断)
硬件:EDIFIER Fit900NB(USB 声卡),共 9 个 profile。相关两条:
| profile | 输出 route | 硬件音量 | 说明 |
|---|---|---|---|
1 output:analog-stereo+input:mono-fallback |
analog-output |
有(volumeBase = 1.11428) |
面板上的「模拟输出」 |
3 output:iec958-stereo+input:mono-fallback |
iec958-stereo-output |
无(只有 iec958Codecs) |
面板上的「数字输出」 |
两条 route 存的音量都是 1.0(default-routes 里 channelVolumes:[1.0,1.0]),但只有模拟那条带硬件音量控制。
音量现状(12 层逐项量出来):
| 层 | 值 | 相对 |
|---|---|---|
虚拟声卡(上混)cinema_spatial_up_sink |
0.330 | −9.6 dB |
filter 输出端 cinema_spatial_up_out |
1.120 | +1.0 dB |
| 物理耳机(模拟 route) | 1.000 | 0 dB |
| 整链 | 0.370 | −8.6 dB |
二、三处缺陷
🔴 1. node.target 是"某一刻的节点名快照",重建后会当场失效(推断,未复现)
- 配置里写死的是当前那个 profile 的节点名:
...-00.analog-stereo - 你在面板点「数字输出」→ 声卡切到 profile 3 → 那个名字的节点消失 → 目标失效
- 脚本走"重建"分支 → 写新配置 →
systemctl --user restart pipewire - 重启时 WirePlumber 用它存档的
default-profile(实测存的是 analog)把 profile 拉回 → 刚写进去的 target 又指向不存在的节点 - 而旧脚本不校验连线,静默继续 → 声音由 WirePlumber 的默认策略接管,响度就不再由你控制
证据:~/.local/state/wireplumber/default-profile 存的 analog;default-nodes 里同时有 analog-stereo 和 iec958-stereo 两个名字;设备 Profile 参数是 save:false。
🔴 2. 音量每次"开"都被硬写 0.25(已实测确认)
旧脚本 apply_volume() 无条件 wpctl set-volume ... 0.25。你在面板调好的值(现场 0.330)每次开都被抹掉,而且"先点数字输出"这条链恰好会触发重建 → 音量被重置 —— 两条叠加就是你感觉的"音量怪"。
🟡 3. 两条 profile 的硬件增益基数差约 0.94 dB(实测)
用 pw-dump 读活动 Route 的 volumeBase:模拟 1.11428(+0.94 dB)/ 数字(S/PDIF) 1.00000(0 dB)。
两条 route 都有音量控制、存档音量也都是 1.0,差别只是模拟那条的硬件增益基数高约 +0.94 dB。
★ 更正(同日):我最初读的是 EnumRoute(能力清单,不带 volumeBase),据此得出过
"数字 route 没有硬件音量"的错误结论;Route 参数才是当前/存档状态。已按活动 Route 改正。
另外:你说的"点数字输出后响度很小"我没能复现量级——复现要切档或放音,而现场有录音占用(见下节)。 若你实测数字那条确实小很多,那是硬件/DAC 路径的差异,用第六节的补偿旋钮兜。
三、为什么我没直接复现
复现要切 profile 或重启 PipeWire,而现场有一个 pw-record(控制台的子进程,PID 28599,已跑 7 分钟)正在采你的耳机麦克风 —— 切档会打断它。所以我只做只读诊断 + 改代码,没动任何设备/服务。
四、顺带挖出的两个真 bug
① 电平表一直在偷录麦克风。 控制台的电平表用 pw-record --target <物理设备>.monitor 取输出电平,但实测该 monitor 节点不存在(手测两条 --target 都录不出文件)→ pw-record 静默回退到默认录制源 = 耳机麦克风。已改成:monitor 不存在就不启动录制(宁可不显示,不乱采)。
② 音量读数的换算陷阱。 Props.volume 恒为 1.0(不是音量!),channelVolumes 是三次方值;用户看到的数才是线性音量 = wpctl get-volume。我先前读 0.042 就误当成 0.35(0.042^(1/3)=0.348),而真值是 0.330。已统一按 wpctl get-volume 报数。
五、改了什么
| 文件 | 改动 |
|---|---|
空间音频 |
① 音量持久化:记在 ~/.local/state/cinema-spatial/volume,开的时候先"记账"你手动调过的值再应用,重建不再丢 ② 重建时先记下物理卡当前 profile,重启后按回去(必要时 wpctl set-profile),保证配置里的 target 与 profile 一致 ③ 重建后校验实际连线(认当前生效的那条),失败明确报错 ④ 打印音量账(每层 + dB + 整链)⑤ 新增 诊断 |
音频状态.py(新增) |
状态/诊断/profile 助手:纯 stdlib,wpctl/pw-dump;子命令 json / check / phys-sink / card-of / profile / set-profile |
web/webui.py |
控制台不再自己实现一套:开/关/一键修复全部委托给 空间音频 脚本(单一实现);音量滑块写入持久化文件;monitor 不存在则不启动电平录制 |
打包deb.sh |
源改成项目目录(原来从 ~/.local/bin 拷旧版)、带上 音频状态.py、版本 1.0.2 |
六、现在的行为
空间音频 开 # 打印完整音量账;target 死了才重建,重建必保 profile 并验连线
空间音频 诊断 # 输出目标 / profile / 硬件增益基数(逐 route) / 音量账 / 连线状态
「数字输出」这条道:脚本会明确警告"硬件增益基数比模拟低约 0.9 dB、两条路电平不等价",要硬用可以给补偿:
export CINEMA_SPATIAL_VOLUME_DIGITAL=0.6 # 或你实测合适的值
七、要你验的三件事(我这边不出声)
- 面板点数字输出 → 点空间音频:应看到 profile 保活/警告,响度应仍正常(若仍偏小,说明就是硬件 route 差,用上面的补偿)
- 面板点模拟输出 → 点空间音频:响度应与以前一致
- 面板把音量调到你喜欢的值 → 关一次再开:音量应保持不变(这条是本次核心修复)
八、追加(同日):数字输出定为首选
你实测的主观音质:数字输出(S/PDIF) 比模拟输出好一大截 —— 所以上面"建议改用模拟输出"的说法作废, 数字才是首选。客观侧能佐证的实测数据:
| 数字输出 route | 模拟输出 route | |
|---|---|---|
| 协商格式 | 96 kHz / 24-bit(S24LE) | (待下次在模拟路上量) |
| 硬件增益基数 | 1.00000(0 dB) | 1.11428(+0.94 dB) |
数字这条路 96 kHz + 24-bit 全保住了,96k 链和 512-tap IR 没白费(这是关键——否则 512tap 会被当 256tap 用)。 模拟那条我暂时量不到协商格式(要切过去才有),下次你在模拟路上时我量一下,就能确认音质差是出在位深/采样率还是别的环节。
新增命令(比在 GNOME 面板点更稳)
空间音频 数字 # 切数字输出(S/PDIF):切完自动重建目标 + 把 profile 按回去 + 验连线 + 出音量账
空间音频 模拟 # 切模拟输出
为什么不让你直接在面板点:面板点会改 profile → 目标节点名随之变化 → 触发重建 → 重建里的 restart pipewire
又可能被 WirePlumber 的存档 profile 拉回(就是上面第二节那个坑)。这条命令把这一串都包住了,且已经在目标路上时不会重启任何东西。