2026-09-13 07:04:52 +08:00

Cinema Spatial —— 耳机上的电影院空间音频

把多声道音轨(5.1/7.1)用 HRTF 渲染成双耳,耳机上得到接近电影院 C 位的环绕包围感。 立体声源会先经过矩阵上混(杜比 Pro Logic 原理)再渲染,所以浏览器、音乐播放器同样有空间感。

★ 2026-09-13 修复「响度怪」(先点模拟/数字输出导致响度不同):根因、证据与改动见 响度怪-根因与修复.md。新增 空间音频 诊断,音量改为持久化,重建会保 profile 并校验连线。

原理

多声道源 ──┐
           ├─> [矩阵上混 5.1] ─> 6 路 ─> [每路与人头脉冲响应卷积] ─> 双耳混音 ─> 耳机
立体声源 ──┘                    (SADIE-II 真人 HRTF)
  • HRTFHead-Related Transfer Function,头相关传输函数):同一方向的声音到达两耳时, 会被人头、耳廓滤波,产生不同的时间差、强度差和频谱染色 —— 大脑靠这些线索判断方位。 把这些滤波做成脉冲响应(IR),与声道信号卷积,就能在耳机上"伪造"出方位。
  • 虚拟声卡:用 PipeWire 的 filter-chain 模块建两个 Audio/Sink,程序把声音送进来, 内部渲染完再输出到真实设备。全局生效,任何播放器都能用。

特性

  • 全链路 96kHz / 24bitHRTF 采用 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 + 验连线"都包住了。

★ 反复重建会搞崩 WirePlumber2026-09-13 血亏)

症状:面板「处理后电平」恒为 -120 dBFS,但 pw-link 连线对、设备 1.00 未静音、 pw-top 里设备 WAIT/BUSY 正常 —— 链路看着全对

真凶两条,都已治:

  1. wireplumber 连续快速重启 PipeWire 后会崩GLib 断言 → core-dump)。它一死,monitor 的新连接 建不起来 → 任何从 monitor 录电平的实现都读静音。判据 systemctl --user is-active wireplumbersystemctl --user restart wireplumber(只重启会话管理器,PipeWire 本体不动,~1s 恢复)。 已写进 CLI regen_chain 自愈:重建后查 is-active,不活就拉起。
  2. 录制进程认死旧节点 id:每次重建节点 id 都会漂(实测物理输出 84 → 166),内层永不退出的 pw-record 会一直从已消失的目标录静音。已改:电平 worker 每 15s 主动重开录制。

教训:重建别连发;重建后既要验连线,也要验会话管理器活着。

★ "重启后目标失效"的根因与修复(2026-09-13

现象(老板报):接单声道蓝牙音响后日志出现 ! 重启后目标失效(配置里是 bluez_output...), 回退到自动探测 → 之后蓝牙音响一直连不上

根因链:

  1. 改混响/参数/单声道/设备都要重建滤波链 → 滤波链是配置里的模块没有运行时热加载 → 必须重启 PipeWire
  2. WirePlumber 是 PipeWire 客户端 → 会跟着退出并被 systemd 拉起(避免不了
  3. 它一重启,蓝牙/USB 设备要 3~8 秒(蓝牙有时 10+ 秒)才重新出现
  4. 原代码 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>物理设备的 monitorpw-record 自己找 target 时:

  • 自动连线会挑错端口(实测 post 两个输入都从 output_FR 来 —— 双声道表其实只拿了右声道)
  • target 一旦解析不到 → 静默回退默认录制源 = 麦克风 ✗✗
  • 而且两个 pw-record 进程同名node.name 都是 pw-recordapplication.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,天然成对):

  • rawcinema_spatial_up_sink:monitor_FL/FR(被捕捉的原始音频)
  • postcinema_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 dBFS0dBFS 母带 + 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 都活着), 真凶在显示

  1. 两张表共用固定量程 0…-60 dBFS。处理后信号比原始低约 22 dB(虚拟音量 -10.5 + HRTF 约 -16), 落在 -48 上下 —— 幅度只剩 2~3 dB 抖动,肉眼看就是一条贴底死线。 → 改每表自动量程:按最近 2 秒的最大峰值向上取整到 6 dB 定顶线(显示 48 dB 动态范围, 轴标签写真实 dBFS)。★ 注意必须用"最近 2 秒"而不是整 12 秒:历史最响的一瞬 -10 dBFS 会把顶线 钉在 -6,安静段照样贴底。
  2. 顺带挖出潜伏 bughold.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 倍。 所以裁 HRIR1024 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.wav2.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 开发期排查工具

踩过的坑(均为实测)

  1. 增益必须按"整链"算:矩阵上混出来的 6 路会在混音器里相加,实测整链净增益约 +10.5dB。 只按单路脉冲响应峰值校准,一听真实内容就削波爆音。
  2. 校准素材必须接近满刻度:自造测试信号(峰值 -12dB)与真实母带(≈0dBFS)差 10dB 以上, 用它校出的增益必然炸。
  3. 不同 SOFA 的电平差异极大:同一数据集内,D1(假头)IR 峰值 -11.6dB、H4(真人)-3.1dB。 换模型必须重新量峰值,目标压在 -12dB 左右。
  4. sofalizer 按 SOFA 自身的采样率输出:不管输入 48k 还是 96k,只要 SOFA 是 44.1k 输出就掉到 44.1k —— 这是"采样率偏低"的根因。要么在滤镜链尾 aresample 拉回, 要么用原生高采样率的数据集。
  5. convolver 按当前时钟率重采样脉冲响应:主时钟还是 48k 的话,96k/512tap 的 IR 会被降成等效 256tap,等于白换。必须把 default.clock.rate 一起提到 96k。
  6. node.passive = true 必须配 node.target:否则 filter-chain 的输出端不连任何设备, 声音凭空消失。
  7. ffmpeg 滤镜表达式里的 ,| 要用单引号包住,否则会被当分隔符解析。
  8. adelay + join 合成多声道素材不可靠:曾静默产出 0.25 秒的单声道文件。 改用 aevalsrc 直接合成。
  9. type=time 是时域卷积(保真);默认的 type=freq 走 FFT,会削高频。
  10. ★ 测试素材必须匹配真实内容的"峰值因子":用平稳的粉噪做压力测试,看着余量充足, 一放电影里爆炸/撞击那种高峰值因子瞬态就削 —— 同样听感响度下,瞬态峰值能再高出 10dB 以上。 校准要用真实片源,或至少混入冲击型素材。
  11. ★ 每一层音量都吃动态余量,改一处就要记账:虚拟声卡音量与设备(耳机)音量 串联在同一条链上。曾把设备音量从 0.66 提到 1.00(+3.6dB)却没算进余量账, 结果一有瞬态就削。设备自身的出厂音量是预留余量,不要顺手拉满。
  12. ★ USB 音频设备重启后 profile 会变iec958-stereoanalog-stereo), 设备 node.name 随之后缀改变 → 配置里记的 node.target 失效 → WirePlumber 回落到其它输出 (实测落到 HDMI,表现为"没声音"或"声音从显示器出来")。 本项目的 空间音频 开 已带自检:设备名失效时自动重新探测并重建配置。
  13. ★★ 虚拟声卡的音量不会被保留(2026-09-13,爆音反复的根因): filter-chain 每次重建(重载 PipeWire、开机、切设备)都是全新的节点 没有历史音量 → 回到默认 1.0 → 整链直接削波。 用户看到的现象是"我明明调低了,怎么又炸了"——而且调低的那一刻音量可能刚被重置修法两处:① capture.props 里写 node.volume = 0.25(重建时的默认值) ② 切换命令每次补写一遍(兜底)。
  14. 判定设备是否存在不能用 pw-cli info:它对不存在的名字也返回成功。要用 pw-dumpnode.name 精确比对。

数据来源

HRTF 采用 SADIE II University of YorkZenodo DOI 10.5281/zenodo.12092466), 使用其中真人受试者 H496K/24bit/512tap 版本。

引用方式见数据集页面。数据集不随本仓库分发,请从 Zenodo 自行下载。

License

MIT

S
Description
耳机上的电影院空间音频: PipeWire filter-chain 虚拟声卡 + SADIE-II 真人 HRTF, 全链路 96kHz/24bit, 立体声自动矩阵上混
Readme MIT 4 MiB
v1.3.6 Latest
2026-09-14 08:04:44 +08:00
Languages
Python 58.3%
Shell 21.7%
HTML 20%