Files
cinema-spatial/响度怪-根因与修复.md
T

7.4 KiB
Raw Blame History

响度怪 · 根因与修复(2026-09-13

你报的症状

先在 Ubuntu 面板上点模拟输出再点空间音频 → 正常响度 先点数字输出再点空间音频 → 响度很小

一、现场事实(全部实测,不是推断)

硬件:EDIFIER Fit900NBUSB 声卡),共 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-routeschannelVolumes:[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 存的 analogdefault-nodes 里同时有 analog-stereoiec958-stereo 两个名字;设备 Profile 参数是 save:false

🔴 2. 音量每次"开"都被硬写 0.25(已实测确认)

旧脚本 apply_volume() 无条件 wpctl set-volume ... 0.25。你在面板调好的值(现场 0.330每次开都被抹掉,而且"先点数字输出"这条链恰好会触发重建 → 音量被重置 —— 两条叠加就是你感觉的"音量怪"。

🟡 3. 两条 profile 的硬件增益基数差约 0.94 dB(实测)

pw-dump活动 RoutevolumeBase:模拟 1.11428+0.94 dB/ 数字(S/PDIF) 1.000000 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 助手:纯 stdlibwpctl/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   # 或你实测合适的值

七、要你验的三件事(我这边不出声)

  1. 面板点数字输出 → 点空间音频:应看到 profile 保活/警告,响度应仍正常(若仍偏小,说明就是硬件 route 差,用上面的补偿)
  2. 面板点模拟输出 → 点空间音频:响度应与以前一致
  3. 面板把音量调到你喜欢的值 → 关一次再开:音量应保持不变(这条是本次核心修复)

八、追加(同日):数字输出定为首选

你实测的主观音质:数字输出(S/PDIF) 比模拟输出好一大截 —— 所以上面"建议改用模拟输出"的说法作废, 数字才是首选。客观侧能佐证的实测数据:

数字输出 route 模拟输出 route
协商格式 96 kHz / 24-bitS24LE (待下次在模拟路上量)
硬件增益基数 1.000000 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 拉回(就是上面第二节那个坑)。这条命令把这一串都包住了,且已经在目标路上时不会重启任何东西