117 lines
7.4 KiB
Markdown
117 lines
7.4 KiB
Markdown
# 响度怪 · 根因与修复(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 |
|
||
|
||
## 六、现在的行为
|
||
|
||
```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-bit(S24LE)** | (待下次在模拟路上量) |
|
||
| 硬件增益基数 | 1.00000(0 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 拉回(就是上面第二节那个坑)。这条命令把这一串都包住了,且**已经在目标路上时不会重启任何东西**。
|
||
|