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

117 lines
7.4 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 响度怪 · 根因与修复(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-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 |
## 六、现在的行为
```bash
空间音频 开 # 打印完整音量账;target 死了才重建,重建必保 profile 并验连线
空间音频 诊断 # 输出目标 / profile / 硬件增益基数(逐 route) / 音量账 / 连线状态
```
**「数字输出」这条道**:脚本会明确警告"硬件增益基数比模拟低约 0.9 dB、两条路电平不等价",要硬用可以给补偿:
```bash
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 面板点更稳)
```bash
空间音频 数字 # 切数字输出(S/PDIF):切完自动重建目标 + 把 profile 按回去 + 验连线 + 出音量账
空间音频 模拟 # 切模拟输出
```
为什么不让你直接在面板点:面板点会改 profile → 目标节点名随之变化 → 触发重建 → 重建里的 `restart pipewire`
又可能被 WirePlumber 的存档 profile 拉回(就是上面第二节那个坑)。这条命令把这一串都包住了,且**已经在目标路上时不会重启任何东西**。