7.1 KiB
Collaplex 音效
耳机方向的空间音效链路(PipeWire)。
链路
全局音频(应用输出) → collaplex_vsink(虚拟声卡) → 响度归一化(pipe DSP)
→ collaplex_hrtf_in(双耳 HRTF + 房间混响, 湿量 0.3)
→ EQ(三段) + 总音量放大(+6 dB)
→ 数字输出(S/PDIF)
安装
bash 脚本/安装.sh # 默认: 混响湿量 0.3, 总音量 +6 dB
VOL_DB=9 bash 脚本/安装.sh # 总音量改成 +9 dB
WET=0.4 bash 脚本/安装.sh # 混响湿量改成 0.4
脚本:探测数字输出 → 部署 pipe 插件入口 → 渲染两份配置 → 清掉残留 DSP(占住共享槽位会让新实例起不来) → 重载 PipeWire → 自检(4 个节点 + 2 个 DSP 进程)。
调 EQ
pipewire/20-collaplex-hrtf.conf 里 param_eq 节点(左右各一个, 两处一起改):
filters1 = [ { type = bq_lowshelf freq = 120 gain = 0.0 } # 低频架
{ type = bq_peaking freq = 1000 gain = 0.0 q = 1.0 } ] # 中频峰
filters2 = [ { type = bq_highshelf freq = 6000 gain = 0.0 } ] # 高频架
gain 单位 dB(正=提升), 改完重跑 安装.sh。
★ 坑: param_eq 的端口名带编号 —— In 1 / Out 1(走 filters1)、In 2 / Out 2(走 filters2)。
写成 :In / :Out 会报 unknown input port 并让整张图加载失败(模块静默降级成直通, 节点都不建)。
控制面板
bash 脚本/面板.sh # http://127.0.0.1:8789
- 电平归一化(响度目标)、总音量、32 段 EQ 全部是滑块:滚轮调节、Shift+滚轮微调(0.1 dB)、双击归零
- EQ 是 1/3 倍频程 32 段(20 Hz … 25 kHz),每段 ±15 dB,实时生效(DSP 从共享内存读参数,不用重装)
- 参数落点:
/dev/shm/collaplex-eq(前端写、DSP 读,版本号保证不成半更新);响度目标写在~/.local/state/cinema-spatial/loudness.json(归一化 DSP 每秒重读)
关于"放大"与削波(2026-09-19 修)
总音量级的输出不能硬削(原实现是 np.clip(out, -1.0, 1.0)): 硬削把波形削成平顶,
听感就是"炸麦"; 而被削掉的峰值不增加响度, 所以出现"声音没变大、却削峰了"。
改用软限幅 dsp/eq.py: soft_limit(): 上限 -1 dBFS, 幅值在 (上限−拐点) 以内完全透明,
超出部分用 tanh 平滑渐近上限 —— 不产生平顶, 波形其余部分不动, 响度真实提升。
实测(峰值 -12 dBFS 素材, 总音量 +12 dB): 输出峰值 -1.01 dBFS, 贴顶样本自 64.6% → 0%。
第二处放大 bug(2026-09-19 修)
20-collaplex-hrtf.conf 里残留了一级固定 +6 dB(volL/volR mixer)和一段用不上的 param_eq
—— 它和面板的总音量叠了两级, 把归一化守住的 −3 dBFS 顶到 +3 dBFS 再送进 EQ,
软限幅被迫一直工作, 听感就是"响度上不去、动态还被压"。已删除, 音量只由面板控制。
逐级实测排除法: 归一化输出峰值 ≤ −3.17 dBFS(从不超 0, 清白); HRIR 峰值 −17 dB、房间混响 IR −16.5 dB, 都不可能推高峰值 → 元凶就是那句残留增益。
修后端到端(峰值 −12 dBFS 素材):
| 总音量 | 出 RMS | 提升 | 出峰值 |
|---|---|---|---|
| +0 dB | −22.97 dBFS | — | −12.28 dBFS |
| +6 dB | −16.98 dBFS | +5.99 dB | −6.35 dBFS |
| +12 dB | −11.77 dBFS | +11.20 dB | −1.11 dBFS |
中低频沙沙声的根因(2026-09-19 修)
reverb/房间混响IR-96k.wav 是未归一化的原始 IR: 它的频响在 100~200 Hz 比中高频高约 20 dB
(|H| = 8.9 / 10.6, 而 8 kHz 只有 0.26)。湿路径(×0.3)因此在中低频 +10 dB, 与干信号相干相加后
整个 HRTF 级 +19 dB —— 归一化守住的 −14 dBFS 被顶到 +5 dBFS, EQ 的软限幅于是一直在工作,
生成 3 次谐波与宽带失真, 听感就是中低频沙沙。
修法: 给房间混响的 convolver 加线性增益 gain = 0.126(−18 dB)。
★ 坑: convolver 的 gain 是线性倍数, 不是 dB —— 写 -18.0 会变成"反相 ×18", 电平反而更高。
修后实测: EQ 入峰值 +5.16 → −15.57 dBFS; 600 Hz 谐波 −9.9 → −28.9 dB; 软限幅不再触发。
已修(2026-09-19): 归一化的增益原来是逐块更新的, 块间台阶产生约 −29 dB 的谐波。 现已改为块内逐样本线性插值(上一块增益 → 本块增益的斜坡), 谐波(相对基频) +0/+6 dB 由 −12.1 / −15.9 dB 降到 −90.1 / −70.6 dB。
限幅阈值定太低的坑(2026-09-19 修)
EQ 输出级的软限幅原来设 ceiling = 0.891 / knee = 0.25, 介入点低到 −3.9 dBFS。
后果: 纯音峰值因子只有 3 dB, 峰值落在 −11 dBFS —— 测不出问题; 而音乐峰值因子 10~14 dB,
归一化到 −14 dBFS RMS 后峰值就在 −4 ~ 0 dBFS, 正好长期落在限幅区里被压 → 中低频谐波 → 沙沙。
所以才会"纯音测干净、一放音乐就犯"。
现改为 ceiling = 0.99 / knee = 0.06(介入点 −0.63 dBFS): 平时完全透明, 只在真要削波时介入。
实测(高峰值因子素材): 常规段增益精确 +6.00 dB; 仅瞬态尖峰(入 −2.21 dBFS)被压到 −0.09 dBFS。
★ 教训: 峰值相关的失真纯音测不出来, 必须用高峰值因子素材 —— 见 测试/test_limit.py。
空间数据来源
| 用途 | 数据 | 来源 |
|---|---|---|
| 双耳 HRTF | SADIE II 受试者 H4, 96 kHz / 24 bit / 512 tap, 每条截取 4800 tap(约 50 ms) | University of York 音频实验室, Zenodo DOI 10.5281/zenodo.12092466 |
| 房间混响 | 自家房间模型程序化生成的双耳 IR | 本项目 reverb/ |
hrir/的 12 条 = H4 的 6 个方向 × 左右耳(BL / BR / FC / FL / FR / LFE); 链路实际用前左 + 前右共 4 条(FL_L/FL_R/FR_L/FR_R)做双耳化。- SADIE II 为真人受试者测量: 2818 个方向, 仰角 ±90°, 测距 1.2 m。
- 房间混响 IR 是未归一化的原始响应 —— 所以混响 convolver 必须带
gain = 0.126标定 (见下文"中低频沙沙声的根因")。
实测(2026-09-19)
| 指标 | 数据 |
|---|---|
| 响度归一化 | 输入差 20.00 dB → 输出端差 0.00 dB(推子 −6.28 / +13.72) |
| HRTF 双耳化 | 出口左右相关系数 0.679(直通 = 1.000) |
| 混响 | 脉冲尾巴拖到 1.2 s(湿量 0.3) |
| 总音量 | 出口电平 −21.5 → −16.2 dBFS(+5.3 dB) |
试听
pw-play --target collaplex_hrtf_in 某个.wav # 只走 HRTF 核心(单曲)
全系统:默认输出就是 collaplex_vsink,Chrome/播放器/游戏直接出声即走整条链。
文件
| 路径 | 作用 |
|---|---|
dsp/loudness_norm.py |
响度归一化 DSP(pipe 插件载荷, 左右各一进程) |
dsp/状态.py |
读共享内存看实时推子/电平 |
bin/collaplex-loudness-norm |
pipe 插件入口模板(必须是 ASCII 路径) |
pipewire/10-collaplex-vsink.conf |
虚拟声卡 + 响度归一化 |
pipewire/20-collaplex-hrtf.conf |
HRTF 核心 + 混响 + EQ + 总音量 |
hrir/ reverb/ |
HRIR(12 个)与房间 IR |
脚本/安装.sh |
渲染 + 部署 + 重置 DSP + 自检 |