Cinema Spatial —— 耳机上的电影院空间音频
把多声道音轨(5.1/7.1)用 HRTF 渲染成双耳,耳机上得到接近电影院 C 位的环绕包围感。 立体声源会先经过矩阵上混(杜比 Pro Logic 原理)再渲染,所以浏览器、音乐播放器同样有空间感。
★ 2026-09-13 修复「响度怪」(先点模拟/数字输出导致响度不同):根因、证据与改动见
响度怪-根因与修复.md。新增空间音频 诊断,音量改为持久化,重建会保 profile 并校验连线。
原理
多声道源 ──┐
├─> [矩阵上混 5.1] ─> 6 路 ─> [每路与人头脉冲响应卷积] ─> 双耳混音 ─> 耳机
立体声源 ──┘ (SADIE-II 真人 HRTF)
- HRTF(Head-Related Transfer Function,头相关传输函数):同一方向的声音到达两耳时, 会被人头、耳廓滤波,产生不同的时间差、强度差和频谱染色 —— 大脑靠这些线索判断方位。 把这些滤波做成脉冲响应(IR),与声道信号卷积,就能在耳机上"伪造"出方位。
- 虚拟声卡:用 PipeWire 的
filter-chain模块建两个Audio/Sink,程序把声音送进来, 内部渲染完再输出到真实设备。全局生效,任何播放器都能用。
特性
- 全链路 96kHz / 24bit(HRTF 采用 SADIE-II
96K/24bit/512tap真人数据) - 立体声源自动矩阵上混,不需要多声道片源
- 提供 deb 包,系统级安装
- 纯脚本 + 数据,无编译,
Architecture: all
安装
方式一:deb 包
sudo dpkg -i collaplex-cinema-spatial_1.0.0_all.deb
# 依赖: pipewire libmysofa1 mpv
配置装在 /usr/share/pipewire/pipewire.conf.d/,对所有用户生效。
生效:systemctl --user restart pipewire pipewire-pulse wireplumber(或注销重登)
方式二:从源码
# 1. 下载 HRTF 数据集(不随仓库分发)
mkdir -p sofa && cd sofa
curl -L -O https://zenodo.org/records/12092466/files/H4_HRIR_SOFA.zip
unzip H4_HRIR_SOFA.zip && cd ..
# 2. 从 SOFA 提取脉冲响应(12 个 = 6 方向 x 左右耳)
bash 换HRTF.sh "$PWD/sofa/H4_HRIR_SOFA/H4_HRIR_SOFA/H4_96K_24bit_512tap_FIR_SOFA.sofa" H4-96k -16
ln -sfn H4-96k hrir/current
# 3. 生成 PipeWire 配置
python3 生成配置.py
systemctl --user restart pipewire pipewire-pulse wireplumber
用法
空间音频 开 # 全局切到立体声上混版(推荐)
空间音频 开5.1 # 全局切到 5.1 直通版
空间音频 关 # 切回物理设备
空间音频 状态 # 查看两个虚拟声卡和当前默认
空间音频 诊断 # 输出目标 / profile / route 硬件增益基数 / 音量账 / 连线状态
空间音频 数字 # 切数字输出(S/PDIF) —— 实测音质更好, 推荐
空间音频 模拟 # 切模拟输出
mpv-影院 电影.mkv # 只给 mpv 用, 不依赖全局设置(适合 A/B 对比)
也可以直接在桌面环境的"声音设置"里选 电影院空间音频。
★ 输出 route 怎么选:实测耳机的「数字输出(S/PDIF)」音质明显优于「模拟输出」 (数字那条跑 96 kHz / 24-bit),推荐数字。用
空间音频 数字切比在 GNOME 面板点更稳—— 面板点会改 profile 从而让配置里的目标节点名失效,这条命令把"重建目标 + 保 profile + 验连线"都包住了。
★ 反复重建会搞崩 WirePlumber(2026-09-13 血亏)
症状:面板「处理后电平」恒为 -120 dBFS,但 pw-link 连线对、设备 1.00 未静音、
pw-top 里设备 WAIT/BUSY 正常 —— 链路看着全对。
真凶两条,都已治:
wireplumber连续快速重启 PipeWire 后会崩(GLib 断言 → core-dump)。它一死,monitor 的新连接 建不起来 → 任何从 monitor 录电平的实现都读静音。判据systemctl --user is-active wireplumber; 修systemctl --user restart wireplumber(只重启会话管理器,PipeWire 本体不动,~1s 恢复)。 已写进 CLIregen_chain自愈:重建后查 is-active,不活就拉起。- 录制进程认死旧节点 id:每次重建节点 id 都会漂(实测物理输出 84 → 166),内层永不退出的
pw-record会一直从已消失的目标录静音。已改:电平 worker 每 15s 主动重开录制。
教训:重建别连发;重建后既要验连线,也要验会话管理器活着。
★ "重启后目标失效"的根因与修复(2026-09-13)
现象(老板报):接单声道蓝牙音响后日志出现
! 重启后目标失效(配置里是 bluez_output...), 回退到自动探测 → 之后蓝牙音响一直连不上。
根因链:
- 改混响/参数/单声道/设备都要重建滤波链 → 滤波链是配置里的模块,没有运行时热加载 → 必须重启 PipeWire
- WirePlumber 是 PipeWire 客户端 → 会跟着退出并被 systemd 拉起(避免不了)
- 它一重启,蓝牙/USB 设备要 3~8 秒(蓝牙有时 10+ 秒)才重新出现
- 原代码
sleep 4就判target_alive→ 设备还没回来 → 判"失效" → 回退把目标换成别的设备 → 蓝牙音响再也连不上;而且回退还可能落到显卡pro-output(死输出)
修法:新增 wait_target <秒>(轮询等目标回来,最多 20 秒),三处重启后都用它 ✓
(设备在就立刻返回,放宽无代价);回退探测跳过 pro-output/HDMI ✓
实测:重建 2~7 秒、目标存活 ✓、连线正常 ✓
★ 已确认、别再试:运行时热加载不通 —— pw-cli load-module libpipewire-module-null-sink '{}'
返回 Error: "Could not load module"(无参/空对象/全路径都试过)→ 重建就得重启 PipeWire,省不掉。
下一步可选(未做):设备切换其实可以不重建——用 pw-link 运行期把链输出重连到新设备
(conf 里的 target 只影响下次重启),那样切蓝牙设备就不会掉线。2026-09-13 已实现(见下节)。
处理后电平:只读真实信号,绝不经过麦克风(2026-09-13 老板要求)
老板原话:「处理后电平的话不要用麦克风捕捉,要原始信号」。
病根(实测抓到):电平采集原来用 pw-record --target <数字 id> 录物理设备的 monitor,
而 pw-record 自己找 target 时:
- 自动连线会挑错端口(实测 post 两个输入都从
output_FR来 —— 双声道表其实只拿了右声道) - target 一旦解析不到 → 静默回退默认录制源 = 麦克风 ✗✗
- 而且两个
pw-record进程同名(node.name都是pw-record,application.process.id还是None) → 用pw-record:input_FL这种名字寻址会打到另一个进程的端口上(踩过:日志出现连 0/2+ 拆掉 2 条 ✗)
改法(三个要点):
--target 0 |
声明"不链接节点"→ 完全不自动连线,麦克风回退这条路被物理掐死 |
-P '{ node.name = cinema-meter-raw ... }' |
录制进程起唯一名字,不再有同名歧义 |
link_meter() 按端口 id 显式连线 |
pw-link <源端口id> <录制端口id>,源码里只连该表该看的信号 |
采集口语义(一 raw 一 post,天然成对):
raw←cinema_spatial_up_sink:monitor_FL/FR(被捕捉的原始音频)post←cinema_spatial_up_out:output_FL/FR(真正的处理后信号)
验证(硬证据):
电平[raw] 2/2 条 ← cinema_spatial_up_sink(只此信号, 无麦克风)
电平[post] 2/2 条 ← cinema_spatial_up_out(只此信号, 无麦克风)
cinema-meter-raw:input_FL ← cinema_spatial_up_sink:monitor_FL
cinema-meter-post:input_FL ← cinema_spatial_up_out:output_FL
麦克风(alsa_input)连线数: 0
★ 目标信号不在图里时宁可不录(空表),也不让 pw-record 去抓麦克风。
切换设备不再重启(运行期重连)—— 2026-09-13
空间音频 设备 <关键词> 与面板设备下拉现在优先走运行期重连(CLI 里的 relink_to):
链的 playback 节点本质是 PipeWire stream,输出端口可以运行期改连 → 切设备不必重建、
不必重启 PipeWire → 蓝牙设备不会被带掉 ✓
- 前提:新设备声道数 == 链当前输出声道数(单声道设备要改图 → 自动回落到重建)
- 顺序:拆旧连线 → 建新连线 → 校验;建不上 → 回落"重建 + wait_target 20"
- 同时把新目标写进 conf(供下次重启使用)
- 实测:切到临时 2ch null 声卡 1 秒完成、日志"运行期重连成功(没重启 PipeWire)"、 WirePlumber 启动时刻不变 ✓✓;切回内置模拟 0 秒 ✓
- 排查提示:
pw-link -l的格式是 来源在上一行、|-> 去向在下一行(反向连接显示|<-)
★ 已知遗留:链输出是 stream,目标设备消失时 WirePlumber 会按系统默认把它挪到别的设备
(实测一度被挪到显卡 pro-output-3 的 AUX0 = 死输出)。重建会按 conf 里的 target 拉回 ✓。
若想根治,可给 playback 节点加 node.autoconnect = false,只由本工具显式连线。
单声道设备(单声道蓝牙音响/耳机)—— 2026-09-13
现象(老板实测):接单声道蓝牙音响 → 直接断连 → 回退守卫滚回 → 之后一直连不上。
根因:链的 playback 节点硬编码 audio.channels = 2 audio.position = [ FL FR ],而单声道设备只有 1 个
MONO 端口 → 链接建不起来 → 设备掉线 → 回退。不是蓝牙的问题,是声道数不匹配。
三态开关(默认 自动):
| 操作 | 效果 |
|---|---|
空间音频 单声道 / 面板「单声道」按钮(点一下循环 自动→开→关) |
看/改当前 |
空间音频 单声道 开 |
链里加一级 monoOut 混音器(L/R 各 −6 dB 相加,避免相关信号 +6 dB 溢出),graph 输出单端口,playback 节点改 audio.channels = 1 audio.position = [ MONO ] |
空间音频 单声道 自动 |
pw-dump 读目标设备的 audio.channels,是 1 就自动合并 |
状态文件 ~/.local/state/cinema-spatial/mono.json({} = 自动)。
实测:开 → 两条链的运行节点都是 audio.channels=1 [ MONO ] ✓;自动 → 回到 2 [ FL FR ] ✓
★ 附带发现(解释"一提一点点就削波"的另一半):键盘媒体键调的是虚拟声卡音量 = 链的输入增益(它是应用默认输出), 不是"扬声器电平"。也就是说媒体键拧的就是削波余量旋钮 —— 面板的「输入增益」= 同一个量, 把它调到 0.24~0.30 就能给最响的瞬态留出余量。
★★ 削波真凶:混响 IR 只按峰值归一(2026-09-13)
老板报"扬声器音量开得很低,但一提一点点就直接削波"。测量(60 秒采最大峰值):
| 最大峰值 | 触顶(≥−0.5dBFS)窗数 | |
|---|---|---|
| 原始(虚拟声卡捕捉) | +1.02 dBFS | 4790 |
| 处理后 | +12.02 dBFS | 5759 |
超满刻度 12dB —— 这是硬削波。根因在我自己加的混响:
镜像法IR.py原先只做峰值归一(把峰值压到 −6 dBFS),没做能量归一。- 混响是"密集尾音",卷积的能量增益 ≈
sqrt(Σ ir²):2 秒 96k 的尾音这个值是 +11~13 dB。 - 于是湿路即使只给 0.05(−26 dB),也把整链顶高 +11 dB → 削波。
- 低音量时听不出(增益失真与电平无关),一拧大就暴露 —— 正是老板描述的症状。
修法:按能量归一(Σir²=1)。之后语义变干净:湿量 G 时湿路电平 = 干路 + 20log₁₀G。
控制变量验证(放已知电平信号,物理音量压到 0.02 听不见,读完换算回 1.000):
| 输入 | 输出 | 链净增益 | |
|---|---|---|---|
| 关混响 | −6.64 dBFS | −6.4 dBFS | +0.3 dB |
| 开混响 0.05(新 IR) | −6.64 dBFS | −6.2 dBFS | +0.4 dB(旧 IR 是 +11 dB ✗) |
| 0 dBFS 输入(单独测) | −1.04 dBFS | −1.17 dBFS | 余量 1.17 dB ✓ |
附带:新增「输入增益」滑块(= 链的输入电平 = 削波余量旋钮,/api/volume,改完立即生效不用重建)。
老板的音源本身峰值到 +1.02 dBFS(0dBFS 母带 + inter-sample peak),所以最响的瞬态还差 ~1 dB:
把输入增益从 0.30 降到 0.24 就干净(代价 = 整体低 2 dB)。
教训:任何卷积素材(混响 IR / HRIR / 房间响应)按峰值归一都是错的 —— 必须按能量归一, 而且必须用已知电平的信号做闭环验证(这次就是靠"放 −6dBFS 粉噪比 raw/post"一次定位的)。
老板报"这个原始音频信号调的是截获的音频信号不是扬声器电平"。原实现写的是虚拟声卡音量 (= 链的输入增益 → 它确实在调"被捕捉的信号"→ 报得对)。已改成写扬声器电平:
- 滑块 →
/api/speaker→ 物理输出音量(你听到的音量);工具提示改成「扬声器电平(物理输出 —— 你听到的音量)」 - 链的输入增益固定 0.300 不动:这样「原始电平」表是干净的声源监视(不受音量影响 = 老板要的语义), 也避免把链内部电平抬高 10.5 dB(12 路卷积叠加有削波风险)
- 实测:设 0.500 → 物理输出 0.500 ✓,而虚拟声卡捕捉电平 Δ0.000 ✓
- ★ 硬件增益基数不能写死表:实测这台数字路基数 1.003(我旧表写 1.0,差 10%), 且 Ryzen 卡会报一路 0.001 的假基数(照算会补 1114 倍)。→ 改成闭环自校正: 按实测比例(物理÷目标)算,写一次后读真实音量、量残差再修,最多 3 轮。现在"要多少就是多少"。
- 统一响度开着时写的是目标(物理 = 基数 × 目标),否则直接写该 route 的音量 —— 两种模式都不会被下次 apply 抹掉。
电平表"处理后不滚动"的根因与修复(2026-09-13)
老板报"处理后电平不滚动"。数据侧是活的(API 每 0.5s 都在变、120/120 窗有值、两个 pw-record 都活着), 真凶在显示:
- 两张表共用固定量程 0…-60 dBFS。处理后信号比原始低约 22 dB(虚拟音量 -10.5 + HRTF 约 -16), 落在 -48 上下 —— 幅度只剩 2~3 dB 抖动,肉眼看就是一条贴底死线。 → 改每表自动量程:按最近 2 秒的最大峰值向上取整到 6 dB 定顶线(显示 48 dB 动态范围, 轴标签写真实 dBFS)。★ 注意必须用"最近 2 秒"而不是整 12 秒:历史最响的一瞬 -10 dBFS 会把顶线 钉在 -6,安静段照样贴底。
- 顺带挖出潜伏 bug:
hold.raw = drawMeter(...)把**返回值(数字)**存回 hold, 下一帧peakHold.v就是undefined→ 峰值保持线一直画不出来。 → 改成传持久对象hold.raw = hold.raw || {v:-60},由 drawMeter 内部更新.v。
验证方式(只看像素,不看 DOM/日志):CDP 取 canvas 像素,隔 8 秒再取一次比像素和 + 墨点重心:
| 表 | 墨点 | 墨点重心 y | 像素和 |
|---|---|---|---|
| 原始 | 45353 → 44985 | 41.4 → 41.4 | 有变化 ✓ |
| 处理后 | 34950 → 36016 | 47.4 → 46.0 | 有变化 ✓ |
采样参数可编辑(2026-09-13)
- 面板「采样,各参数调整选项」里三个下拉直接改:采样率 / HRTF 模型 / HRIR 长度,改完自动重建(约 10s)
- 命令:
空间音频 参数(看现值 + 可选项)、空间音频 参数 hrtf H4-48k、参数 taps 1024、参数 rate 48000 - 状态文件
~/.local/state/cinema-spatial/params.json;生成配置.py按它选 IR 目录 (优先hrir/taps-<N>> 模型名 >hrir/current软链。模型与截断二选一,设一个会清掉另一个) - tap 截断:
work/裁HRIR.py <N>→hrir/taps-<N>/(末尾 3ms 淡出防咔嗒;512/1024/2048 随便裁) - 采样率:写用户级
~/.config/pipewire/pipewire.conf.d/91-clock.conf同名覆盖 deb 装在/usr/share/pipewire/pipewire.conf.d/的那份(不用 sudo) - ★ 三者都是生成期常量 → 改完必须重建链路;面板下拉已自动触发重建
- ★ 实测结论(控制变量):开着混响时 HRTF tap 数不是主成本 —— 2 秒的房间 IR 等效每声道 19.2 万 tap,比 HRIR 的 4800 tap 大 40 倍。 所以裁 HRIR(1024 tap)省不到多少 CPU,却会砍掉 HRTF 尾部让声音变干 → 默认保持 H4-96k / 4800 tap,除非以后 CPU 告急才考虑裁。
- 顶栏同时重排:标题 + 模式徽标 / 状态摘要(窄屏自动落到第二行整行)/
开 · 开 5.1 · 关分段控件(当前模式高亮)+ 主按钮
房间混响(真实房间脉冲响应卷积,2026-09-13)
用自己的房间模型算真实 IR,不用廉价算法混响:生成器 声学追踪引擎/src/镜像法IR.py
(房间 20×12×7m / 材质逐带吸声 / 空气吸收 / 双耳间距 0.175m 全来自 config/房间.json)。
- 方法:镜像法早期反射 + Sabine 尾音混合。纯镜像法阶数有限→时长有限(阶 18 只到 ~250ms), 所以在 80ms 交叉点把早期场淡出、按逐带 Sabine RT60 生成的指数衰减噪声尾音(两耳相干 0.4)淡入。
- 产物:
reverb/房间混响IR-96k.wav(2.00s / 96kHz / 双声道 / 峰值 -6dBFS / 29.1ms 前直达声已切) - 落地:
生成配置.py的混响级(revL/revR卷积 +wetL/wetR干湿混合,干=1.0、湿=可调); 关的时候整级不生成(不白吃 CPU)。湿量是生成期常量 → 改湿量必须重建。 - 命令:
空间音频 混响 [开|关|湿量](不带参数=翻转;约 10s 重建)、空间音频 重建 - 面板:混响开关 + 湿量滑块(湿量放在「采样参数」区,保持画稿那行三块结构不变)
- ★ 改湿量后必须重建:
生成配置.py只认生成期常量
输出设备选择(2026-09-13)
音频状态.py sinks→ 全部物理输出(USB → PCI → 显卡 Pro/HDMI,带当前目标标记)空间音频 设备 [关键词]→ 按 node.name / 描述匹配 → 重写 conf 的node.target→ 重建 → 校验 → 实装- 失败守卫:重启后目标若失效,自动滚回自动探测的设备(实测撞到过:pw-cli 造的 null sink 活不过重启)
- 面板「设备选择」下拉吃同一份数据
混响级实测(2026-09-13)
| 频段 | Sabine | 早期(镜像法) | 最终 IR 实测 |
|---|---|---|---|
| 125 Hz | 1.363 s | 1.542 s | 1.338 s |
| 250 Hz | 0.722 s | 0.779 s | 0.749 s |
| 500 Hz | 0.451 s | 0.639 s | 0.524 s |
| 1 kHz | 0.364 s | 0.871 s | 0.460 s |
| 2 kHz | 0.331 s | 1.153 s | 0.427 s |
| 4 kHz | 0.332 s | 0.809 s | 0.362 s |
- 运行中两个 filter-chain 模块里
revL/revR/wetL/wetR全部已加载(pw-cli info实测)✓ - 处理后峰值 -22.2 dBFS(没削波);设备噪声地板 -56 dBFS
- 测量工具:
work/测链路冲激响应.py(喂脉冲→录 monitor→算包络/T60;注意pw-link -l的来源在目标端口下一行、挑设备要读 conf 的 node.target 别按 pw-dump 顺序猜)
Collaplex 音效(Web 控制台 / :8788)—— 已按设计稿重做(2026-09-13)
布局照 ~/桌面/collaplex web设计稿.drawio 实现:顶栏 / 原始电平+处理后电平 / 三个信号开关(竖排) /
音量滑块 / 数字·模拟两条输出线 / 采样参数 / 响度统一开关+滑块 / 5.1直通·混响·设备选择 / 状态信息。
- 后端
web/webui.py:/api/state/api/meters/api/mode/api/volume/api/route/api/switch/api/loudness/api/fix/api/device - 电平表只认数字节点 ID ——
pw-record --target <数字ID>才接 monitor; 给名字不解析会静默回退麦克风,<名>.monitor本机根本不存在(详见技能 linux-audio-routing) - 已接线并实测:开/关/5.1、数字·模拟 route、原始音量、三个信号开关(虚拟声卡那路是真断开连线)、 响度统一(按硬件增益基数补 +0.94 dB)、按当前参数重建
- 待接线:混响开关(需定方案:房间 IR 卷积 / 轻量算法混响)、设备选择(要改 conf 的
node.target再重建)、 采样参数编辑(要重新生成配置) - 改设计稿之前的旧面板留档:
web/index.html.设计稿前备份
旧说明(保留)
浏览器里看状态、切模式、调音量、一键修复"失声"(设备名失效时自动重建)。
bash web/启动.sh # 启动(默认端口 8788,重复执行不会起第二个)
python3 web/webui.py --port 8788 --bind 127.0.0.1 # 也可以直接跑
打开 http://127.0.0.1:8788/。菜单里也有「Collaplex 音效」快捷方式。
界面会实时显示:默认输出、链路采样率、HRTF 模型、配置输出设备 vs 当前物理输出(不一致会标红)、 输出端实际连到哪个设备、以及两个虚拟声卡和设备的音量。检测到设备名失效时直接点修复即可。
文件说明
| 文件 | 作用 |
|---|---|
空间音频 |
开关命令 |
mpv-影院 |
mpv 专用的 HRTF 播放包装 |
生成配置.py |
生成 PipeWire filter-chain 配置 |
换HRTF.sh |
从 SOFA 提取脉冲响应(可换任意 HRTF 模型) |
提取HRIR.sh |
早期版本(已被 换HRTF.sh 取代) |
打包deb.sh |
打包 collaplex-cinema-spatial |
验收测试.sh |
安装后自动验证 |
web/ |
Collaplex 音效 Web 控制台(webui.py 后端 + index.html 前端 + 启动.sh),零依赖 |
demo.sh / 响度校准.sh / 方向测试.sh |
开发期验证工具 |
对比分析.py / 排查.sh |
开发期排查工具 |
踩过的坑(均为实测)
- 增益必须按"整链"算:矩阵上混出来的 6 路会在混音器里相加,实测整链净增益约 +10.5dB。 只按单路脉冲响应峰值校准,一听真实内容就削波爆音。
- 校准素材必须接近满刻度:自造测试信号(峰值 -12dB)与真实母带(≈0dBFS)差 10dB 以上, 用它校出的增益必然炸。
- 不同 SOFA 的电平差异极大:同一数据集内,D1(假头)IR 峰值 -11.6dB、H4(真人)-3.1dB。 换模型必须重新量峰值,目标压在 -12dB 左右。
sofalizer按 SOFA 自身的采样率输出:不管输入 48k 还是 96k,只要 SOFA 是 44.1k, 输出就掉到 44.1k —— 这是"采样率偏低"的根因。要么在滤镜链尾aresample拉回, 要么用原生高采样率的数据集。convolver按当前时钟率重采样脉冲响应:主时钟还是 48k 的话,96k/512tap 的 IR 会被降成等效 256tap,等于白换。必须把default.clock.rate一起提到 96k。node.passive = true必须配node.target:否则 filter-chain 的输出端不连任何设备, 声音凭空消失。- ffmpeg 滤镜表达式里的
,和|要用单引号包住,否则会被当分隔符解析。 adelay+join合成多声道素材不可靠:曾静默产出 0.25 秒的单声道文件。 改用aevalsrc直接合成。type=time是时域卷积(保真);默认的type=freq走 FFT,会削高频。- ★ 测试素材必须匹配真实内容的"峰值因子":用平稳的粉噪做压力测试,看着余量充足, 一放电影里爆炸/撞击那种高峰值因子瞬态就削 —— 同样听感响度下,瞬态峰值能再高出 10dB 以上。 校准要用真实片源,或至少混入冲击型素材。
- ★ 每一层音量都吃动态余量,改一处就要记账:虚拟声卡音量与设备(耳机)音量 串联在同一条链上。曾把设备音量从 0.66 提到 1.00(+3.6dB)却没算进余量账, 结果一有瞬态就削。设备自身的出厂音量是预留余量,不要顺手拉满。
- ★ USB 音频设备重启后 profile 会变(
iec958-stereo↔analog-stereo), 设备node.name随之后缀改变 → 配置里记的node.target失效 → WirePlumber 回落到其它输出 (实测落到 HDMI,表现为"没声音"或"声音从显示器出来")。 本项目的空间音频 开已带自检:设备名失效时自动重新探测并重建配置。 - ★★ 虚拟声卡的音量不会被保留(2026-09-13,爆音反复的根因):
filter-chain 每次重建(重载 PipeWire、开机、切设备)都是全新的节点,
没有历史音量 → 回到默认 1.0 → 整链直接削波。
用户看到的现象是"我明明调低了,怎么又炸了"——而且调低的那一刻音量可能刚被重置。
修法两处:①
capture.props里写node.volume = 0.25(重建时的默认值) ② 切换命令每次补写一遍(兜底)。 - 判定设备是否存在不能用
pw-cli info:它对不存在的名字也返回成功。要用pw-dump按node.name精确比对。
数据来源
HRTF 采用 SADIE II
(University of York,Zenodo DOI 10.5281/zenodo.12092466),
使用其中真人受试者 H4 的 96K/24bit/512tap 版本。
引用方式见数据集页面。数据集不随本仓库分发,请从 Zenodo 自行下载。
License
MIT