diff --git a/.gitignore b/.gitignore index be6f7e0..a4b0e6d 100644 --- a/.gitignore +++ b/.gitignore @@ -14,3 +14,6 @@ dist/ # 本地状态与日志 *.log + +# 测试截图 +测试/*.png diff --git a/README.md b/README.md index 1f5b0d9..ff33bc3 100644 --- a/README.md +++ b/README.md @@ -89,6 +89,7 @@ collaplex-audio stop # 连链路一起 | **电平归一化** | 响度目标滑块 + **输入电平条** + **动态增益推子**(0 dB 在 60% 刻度线上, 量程 −18~+12 dB) | | **混响湿度** | 0 ~ 1, 实时生效 | | **总音量** | ±12 dB, 实时生效 | +| **输出设备** | 下拉选输出到哪台硬件设备(HDMI / 内建模拟 / USB 耳机 …) + 「应用」; 右侧显示是否已挂载 | | **32 段 EQ** | 1/3 倍频程(20 Hz ~ 25 kHz), 每段 ±15 dB,**10 个预设** | - 所有滑块: **滚轮调节 · Shift+滚轮微调 · 双击归零** @@ -270,6 +271,49 @@ collaplex-audio stop # 连链路一起 **注意**: 落盘是"最后一次改动的快照", 不是"出厂默认"。要回到出厂默认就删掉 `params.json` 并清掉共享内存(`rm /dev/shm/collaplex-eq*`), 再重启链路。 +## 输出设备选择与自动挂载(2026-09-24) + +**问题**: 链路末段的输出设备原来**写死**在 `30-collaplex-eq.conf` 的 `node.target`(安装时探测 +"第一个 iec958/digital")。开机时那台设备要是还没枚举出来, 会话管理器建链时目标不存在, +末段就连到别处或者干脆不连; 设备出现了也**不会**自己改回来。听感上就是"双重混响 / 声音 +不对", 只有 `collaplex-audio stop` + `start` 才恢复。 + +**实测的时序证据**(两次都是同一个模式 —— 链路先建好、设备后到): + +| 日期 | 开机 | 输出设备(EDIFIER USB)枚举 | 备注 | +|---|---|---|---| +| 2026-09-23 | 21:01:17 | **21:03:01**(+1 分 44 秒) | 那次 DSP 进程号也对得上: 归一化/湿量在 3997~4034, EQ 到 8924 才起 | +| 2026-09-24 | 08:57:07 | **09:19:43**(+22 分 36 秒) | `dmesg`: `usb 3-2.2: Product: EDIFIER Fit900NB` | + +**现在两件事一起做**: + +| 机制 | 说明 | +|---|---| +| **面板按钮** | 「输出设备」卡片: 下拉选硬件输出 + 「应用」→ 立刻重挂 + 落盘 | +| **选择落盘** | `~/.local/state/cinema-spatial/output.json`: `{"node_name": "...", "description": "..."}`; `脚本/安装.sh` 优先用它, 不再"猜第一个数字输出" | +| **自动挂载守护** | 面板进程里的守护线程(`web/output_route.py`), 每 3 秒核对"选中的设备在位 且 `collaplex_eq_out` **只**连到它"; 不对就拆掉错的、补上对的 —— **设备一插上就自动挂上, 不用重启链路** | + +**为什么设备名可以记住、挂载不能写死**: ALSA 节点名带 USB 序列号 +(`alsa_output.usb-EDIFIER_..._4250315939393214-00.iec958-stereo`), 跨重插/重开机稳定, +所以"选哪台"能记住; 不稳定的是**建链那一刻设备在不在**, 那正是守护要兜的事。 + +**实测**(2026-09-24, 全部真跑): + +| 场景 | 结果 | +|---|---| +| 面板真点「应用」切到内建模拟输出 | `routed` 立刻变成模拟输出 ✓ | +| 再选回耳机点「应用」 | `routed` 变回 EDIFIER, 提示行 `已挂到 EDIFIER Fit900NB 数字立体声(IEC958)` ✓ | +| 手动把末段连到错的设备, 等守护 | **2 秒**自动纠回 ✓ | +| 把末段连线全拆掉, 等守护 | **3 秒**自动挂回 ✓ | +| `collaplex-audio start` 自检 | 打印 `输出设备: … (EDIFIER Fit900NB 数字立体声(IEC958)) [在位]` ✓ | + +**接口**: `GET /api/devices`(设备清单 + 当前挂载 + 守护备注)、 +`POST /api/device {"name": "alsa_output.xxx"}`(落盘 + 立刻挂载)。 + +**注意**: 面板服务单元是 `enable --now` —— 已经在跑时 `--now` **不会**重启, 所以改完 +`web/*.py` 必须 `systemctl --user restart collaplex-audio-panel.service`(否则新代码只落在 +磁盘上, 内存里还是旧的: 实测 `GET /api/devices` 会 404)。 + ## 实测(2026-09-19) | 指标 | 数据 | @@ -435,6 +479,7 @@ uv run --with websocket-client python 测试/cdp_拖动验证.py # CDP 真鼠 | `pipewire/10,20,30-*.conf` | 虚拟声卡+归一化 / HRTF+混响+湿量级 / EQ | | `pipewire/91-collaplex-clock.conf` | 强制 96 kHz(不然 IR 会被按 48k 降采样) | | `web/server.py` `web/index.html` | 控制面板(纯标准库 + 单页, SSE 推送) | +| `web/output_route.py` | 输出设备选择(落盘)+ 自动挂载守护(设备晚到时把末段挂上去) | | `hrir/` `reverb/` | HRIR(12 条)与房间 IR | | `脚本/安装.sh` `脚本/面板.sh` | 部署 / 起面板 | | `打包deb.sh` | 打 deb 到 `发布/` | diff --git a/pipewire/30-collaplex-eq.conf b/pipewire/30-collaplex-eq.conf index b86dd69..45ad1b2 100644 --- a/pipewire/30-collaplex-eq.conf +++ b/pipewire/30-collaplex-eq.conf @@ -1,5 +1,10 @@ # Collaplex 32 段 EQ + 总音量 —— 本文件由 脚本/安装.sh 生成(占位符 __EQ_WRAPPER__ / __TARGET__) # 链路: ... -> HRTF 核心 -> [本图] 32 段 EQ + 总音量(pipe DSP) -> 数字输出 +# +# ★ __TARGET__ 是**用户选定的输出设备**(落盘在 ~/.local/state/cinema-spatial/output.json, +# 面板「输出设备」卡片可改; 没选过则由安装脚本探测第一个数字输出)。 +# 设备名跨重插是稳定的(含 USB 序列号), 所以设备晚到也不改这一行 —— 由面板守护 +# (web/output_route.py)在设备出现后自动把它挂上去。 context.modules = [ { name = libpipewire-module-filter-chain flags = [ nofail ] diff --git a/web/index.html b/web/index.html index fbf2667..eaa90d7 100644 --- a/web/index.html +++ b/web/index.html @@ -80,6 +80,9 @@ input[type=range]::-moz-range-thumb{width:14px;height:14px;border:none;border-ra button{background:var(--card-hi);color:var(--fg);border:1px solid var(--bd-hi);border-radius:6px; padding:5px 12px;font:inherit;font-size:11px;cursor:pointer} button:hover{border-color:var(--ok);color:var(--ok)} +select{background:var(--card-hi);color:var(--fg);border:1px solid var(--bd-hi);border-radius:6px; + padding:5px 8px;font:inherit;font-size:11px;flex:1;min-width:0;cursor:pointer} +select:hover{border-color:var(--ok)} @@ -128,6 +131,17 @@ button:hover{border-color:var(--ok);color:var(--ok)} +
+

输出设备

+
+ 输出到 + + + — +
+
换设备: 选一下再点「应用」。开机时设备晚到(USB 耳机常见)会由守护自动挂上, 不用重启链路。
+
+

32 段 EQ(1/3 倍频程 · ±15 dB)

@@ -437,8 +451,45 @@ function applyState(d) { if (!isBusy("loud") && d.loudness) { loud.value = d.loudness.target_db; el("loud-num").textContent = (+d.loudness.target_db).toFixed(1) + " dB"; } updateMeters(d.levels); updateLoud(d.loudness_levels); + updateOutput(d.output); state = d; } + +/* ---------- 输出设备 ---------- */ +/* 设备表只在变化时重建选项: SSE 是 60 Hz, 每帧重建