v1.3.1: 修断电隔夜重启后"又没声音"(物理输出从存档恢复成旧值, 桥只监视变化不兜底)
根因: 菜单音量桥的约定是「虚拟声卡=音量真源, 物理输出=执行器」, 但桥只监视虚拟声卡 的变化(v != ref 才折算写物理)。断电重启后物理输出从 WirePlumber 存档恢复成旧值 (实测 0.35, 而虚拟声卡 1.0 = 小 9 dB), 虚拟声卡没变 -> 桥永远不动物理 -> 声音就小着。 而看门狗没起来(非自启), 整晚没人兜底。 - web/webui.py: 桥启动时(BRIDGE_REF is None 那次)把物理输出对齐一次 - 音频守护.py: 新增第 9 项检查「物理输出音量 ≠ 虚拟声卡」(容差 0.03; 虚拟声卡自身 <0.1 时不判 —— 那是真被调小了) -> 自动纠回, 破坏测试实测 22 秒纠回 - config/组件配置单.json: 登记「音量应与虚拟声卡一致」期望项 - web/启动.sh: 起来时顺带拉起看门狗(已在跑不重复起) —— 不违背"不自启"的习惯, 但重启后一用面板就有人守; 看门狗 setsid 脱离, 挂在 systemd --user 下 - README: 版本记录 1.3.1
This commit is contained in:
@@ -486,6 +486,10 @@ python3 web/webui.py --port 8788 --bind 127.0.0.1 # 或直接运行后
|
||||
|
||||
## 11. 版本记录
|
||||
|
||||
### 1.3.1
|
||||
- 修复:**断电隔夜重启后"又没声音"** —— 物理输出的音量会从 WirePlumber 存档恢复成旧值(实测 0.35,而虚拟声卡是 1.0),而菜单音量桥只"监视虚拟声卡变化"、虚拟没变就永远不动物理 → 声音凭空小 9 dB。**两处兜底**:① 桥启动时把物理输出对齐一次;② 看门狗新增第 9 项检查「物理输出音量 ≠ 虚拟声卡」并自动纠回(实测 22 秒纠回)
|
||||
- 修复:**隔夜无人看守** —— 看门狗不做开机自启(既有约定),重启后链路就没有守护。现在 `web/启动.sh` 起来时**顺带把看门狗拉起**(已在跑则不重复起);看门狗用 `setsid` 脱离会话,挂在 `systemd --user` 下,Hermes 关了它照样跑
|
||||
|
||||
### 1.3.0
|
||||
- **离线渲染**(`离线渲染.py`):音频 / 视频 → 空间音频 FLAC(96 kHz 双耳)。链路与实时虚拟声卡同参数:`pan` 上混 5.1 → `sofalizer`(同一个 H4 SOFA)→ 房间 IR 卷积(`afir`)→ `loudnorm`。视频自动取音轨;已是 5.1 的片源不重上混
|
||||
- **两道锁**:渲染锁(一轮任务期间**不接受新任务**,直接拒绝而非排队堆积);文件就绪检查(相隔 1.2 秒两次 stat 的大小/时间不变 + ffprobe 能读出音轨,防止把还在下载 / 写入的文件扔进渲染)
|
||||
|
||||
+4
-1
@@ -58,7 +58,10 @@
|
||||
"物理输出": {
|
||||
"匹配": "alsa_output.usb-EDIFIER_Technology_EDIFIER_Fit900NB_4250315939393214-00.iec958-stereo",
|
||||
"_匹配说明": "USB 耳机重启后 profile 可能在 iec958-stereo / analog-stereo 之间变, 名字会变; 所以按 EDIFIER 前缀匹配即可",
|
||||
"_路由": "数字(S/PDIF) 优先, 96kHz/24bit(S24LE); 实测音质优于模拟"
|
||||
"_路由": "数字(S/PDIF) 优先, 96kHz/24bit(S24LE); 实测音质优于模拟",
|
||||
"音量应与虚拟声卡一致": true,
|
||||
"容差": 0.03,
|
||||
"_音量说明": "★2026-09-14 教训: 断电隔夜重启后本级会从 WirePlumber 存档恢复成旧值(实测 0.35, 而虚拟声卡是 1.0), 链上每一项都对却声音凭空小 9 dB。约定 = 虚拟声卡是**音量真源**、这里是**执行器**, 两者应一致; 虚拟声卡自身 <0.1 时不判(那是真被调小了, 别乱纠)"
|
||||
},
|
||||
|
||||
"上混链": {
|
||||
|
||||
@@ -722,6 +722,16 @@ def vol_bridge_tick() -> str | None:
|
||||
# ★ 不能采信 VOL_FILE(存档): 重建/别处改动之后实况可能与存档不一致,
|
||||
# 那桥会把这个差值当成"菜单音量"折算一次 → 音量无故跳一下(2026-09-13 教训)。
|
||||
BRIDGE_REF = v if v > 0.01 else 0.35
|
||||
# ★★ 启动时顺手把**物理输出对齐一次**(2026-09-14 教训): 断电隔夜重启后物理输出会
|
||||
# 从 WirePlumber 存档恢复成旧值(实测 0.35 而虚拟声卡是 1.0) —— 桥只"监视变化",
|
||||
# 虚拟没变它就永远不动物理, 于是整晚/整天的声音都小 9 dB。启动时对齐一次即可。
|
||||
p0, pm0 = vol_of(BRIDGE_PID, timeout=4)
|
||||
if pm0:
|
||||
sh("wpctl", "set-mute", str(BRIDGE_PID), "0", timeout=4)
|
||||
return "启动同步: 物理输出原本静音 → 已解除"
|
||||
if p0 >= 0.0 and abs(p0 - BRIDGE_REF) > 0.02:
|
||||
msg = set_speaker(BRIDGE_REF)
|
||||
return "启动同步: 物理输出 %.3f → %.3f (对齐虚拟声卡) — %s" % (p0, BRIDGE_REF, msg)
|
||||
return None
|
||||
ref = BRIDGE_REF
|
||||
p, _pm = vol_of(BRIDGE_PID, timeout=4)
|
||||
|
||||
@@ -16,3 +16,26 @@ else
|
||||
cat /tmp/cinema-webui.log
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# ---------------------------------------------------------------- 看门狗
|
||||
# ★ 2026-09-14: 隔夜断电重启后"又没声音"的真因之一 = 看门狗没起来(它刻意不做开机自启,
|
||||
# 这是老板的习惯), 于是重启后没人看着链路(那次是物理输出被恢复成旧值 0.35)。
|
||||
# 面板是日常入口, 由它顺手把看门狗拉起最自然 —— 不算"自启", 但重启后一用面板就有人守。
|
||||
# 判在不在跑一律读 /proc/<pid>/cmdline: grep -f 会匹配到自己的命令行把自己算进去。
|
||||
n=0
|
||||
for d in /proc/[0-9]*; do
|
||||
[ -r "$d/cmdline" ] || continue
|
||||
if tr '\0' ' ' < "$d/cmdline" 2>/dev/null | grep -q '音频守护\.py'; then
|
||||
n=$((n + 1))
|
||||
fi
|
||||
done
|
||||
if [ "$n" -eq 0 ]; then
|
||||
GD="$(dirname "$(readlink -f "$0")")/.."
|
||||
mkdir -p "$HOME/.local/state/cinema-spatial"
|
||||
( cd "$GD" && setsid -f python3 音频守护.py \
|
||||
> "$HOME/.local/state/cinema-spatial/guardian-console.log" 2>&1 )
|
||||
sleep 1
|
||||
echo "看门狗: 已拉起(30 秒一轮, 扫到问题自动修)"
|
||||
else
|
||||
echo "看门狗: 已在跑($n 个)"
|
||||
fi
|
||||
|
||||
@@ -334,6 +334,23 @@ def check_all(cfg: dict[str, Any]) -> list[str]:
|
||||
if not panel_ok():
|
||||
probs.append("🟢 面板 8788 无响应")
|
||||
|
||||
# 9) 物理输出音量 vs 虚拟声卡 —— 2026-09-14 教训: 断电重启后物理输出会从
|
||||
# WirePlumber 存档恢复成旧值(实测 0.35), 而虚拟声卡是 1.0, 链上全对
|
||||
# 却声音凭空小 9 dB。本项目约定「虚拟声卡 = 音量真源, 物理输出 = 执行器」,
|
||||
# 两者该一致。虚拟声卡自己很低(<0.1)时不判 —— 那是真被调小了, 别乱纠。
|
||||
vs = node_ids(want_sink)
|
||||
pn = phys_name()
|
||||
if vs and pn:
|
||||
vv, vm = vol_of(vs[0])
|
||||
for pid in node_ids(pn):
|
||||
pv, pm = vol_of(pid)
|
||||
if pm:
|
||||
probs.append("🔴 物理输出 %s 被静音 —— 链上全对却没声音" % pn)
|
||||
elif not vm and vv > 0.1 and pv >= 0.0 and abs(pv - vv) > 0.03:
|
||||
probs.append("🟡 物理输出音量 %.3f ≠ 虚拟声卡 %.3f "
|
||||
"—— 多半是重启后从存档恢复的旧值, 声音会小一大截" % (pv, vv))
|
||||
break
|
||||
|
||||
return probs
|
||||
|
||||
|
||||
@@ -385,6 +402,8 @@ def _pick_fix(desc: str) -> tuple[str, str]:
|
||||
return ("dup", "重建链路(生成器会清理同名 conf)")
|
||||
if "应为 1.000" in desc:
|
||||
return ("unitgain", "把中间级音量纠回 1.000")
|
||||
if "物理输出" in desc:
|
||||
return ("physvol", "把物理输出对齐到虚拟声卡(音量真源)")
|
||||
if "面板" in desc:
|
||||
return ("panel", "拉起面板")
|
||||
return ("misc", "重建链路")
|
||||
@@ -418,9 +437,41 @@ def _do_fix(key: str, cfg: dict[str, Any]) -> str:
|
||||
return "; ".join(done) if done else "无需纠正"
|
||||
if key == "panel":
|
||||
return fix_panel()
|
||||
if key == "physvol":
|
||||
return fix_phys_volume(cfg)
|
||||
return fix_rebuild()
|
||||
|
||||
|
||||
def fix_phys_volume(cfg: dict[str, Any]) -> str:
|
||||
"""把物理输出对齐到虚拟声卡。
|
||||
|
||||
本项目的音量真源是**虚拟声卡**(面板滑块、菜单媒体键、系统设置都写它), 物理输出
|
||||
只是执行器。断电重启后物理输出可能从 WirePlumber 存档恢复成旧值(2026-09-14 实测
|
||||
0.35 vs 虚拟声卡 1.0), 于是链上每一项都对却声音小 9 dB —— 就是这一项在兜底。
|
||||
"""
|
||||
want = str((cfg.get("默认输出") or {}).get("必须是", "cinema_spatial_up_sink"))
|
||||
vs = node_ids(want)
|
||||
if not vs:
|
||||
return "! 找不到虚拟声卡 %s" % want
|
||||
vv, vm = vol_of(vs[0])
|
||||
if vm or vv <= 0.01:
|
||||
return "! 虚拟声卡本身就不对(音量 %.3f, 静音=%s), 该先修它" % (vv, vm)
|
||||
pn = phys_name()
|
||||
if not pn:
|
||||
return "! 找不到物理输出"
|
||||
pids = node_ids(pn)
|
||||
if not pids:
|
||||
return "! 物理输出节点不存在"
|
||||
pid = pids[0]
|
||||
before, bm = vol_of(pid)
|
||||
sh("wpctl", "set-mute", str(pid), "0")
|
||||
sh("wpctl", "set-volume", str(pid), "%.3f" % vv)
|
||||
after, am = vol_of(pid)
|
||||
if am or abs(after - vv) > 0.03:
|
||||
return "! 物理输出写不进去(仍 %.3f, 目标 %.3f)" % (after, vv)
|
||||
return "物理输出 %.3f%s -> %.3f (对齐虚拟声卡)" % (before, "(原本静音)" if bm else "", after)
|
||||
|
||||
|
||||
def main() -> None:
|
||||
ap = argparse.ArgumentParser(description="电影院空间音频看门狗")
|
||||
ap.add_argument("--once", action="store_true", help="只跑一轮")
|
||||
|
||||
Reference in New Issue
Block a user