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

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

原理

多声道源 ──┐
           ├─> [矩阵上混 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 直通版
空间音频 关        # 切回物理设备
空间音频 状态      # 查看两个虚拟声卡和当前默认

mpv-影院 电影.mkv  # 只给 mpv 用, 不依赖全局设置(适合 A/B 对比)

也可以直接在桌面环境的"声音设置"里选 电影院空间音频

Web 控制台

浏览器里看状态、切模式、调音量、一键修复"失声"(设备名失效时自动重建)。

bash web/启动.sh            # 启动(默认端口 8788,重复执行不会起第二个)
python3 web/webui.py --port 8788 --bind 127.0.0.1   # 也可以直接跑

打开 http://127.0.0.1:8788/。菜单里也有「空间音频控制台」快捷方式。

界面会实时显示:默认输出、链路采样率、HRTF 模型、配置输出设备 vs 当前物理输出(不一致会标红)、 输出端实际连到哪个设备、以及两个虚拟声卡和设备的音量。检测到设备名失效时直接点修复即可。

文件说明

文件 作用
空间音频 开关命令
mpv-影院 mpv 专用的 HRTF 播放包装
生成配置.py 生成 PipeWire filter-chain 配置
换HRTF.sh 从 SOFA 提取脉冲响应(可换任意 HRTF 模型)
提取HRIR.sh 早期版本(已被 换HRTF.sh 取代)
打包deb.sh 打包 collaplex-cinema-spatial
验收测试.sh 安装后自动验证
web/ 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. 判定设备是否存在不能用 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%