归一化接在核心前面: 全局音频 -> 虚拟声卡 -> 响度归一化 -> HRTF(湿量0.3) -> 数字输出; 安装脚本单脚本可复现(含残留DSP清理)
This commit is contained in:
@@ -11,6 +11,6 @@ export LN_GATE_DB=-50
|
||||
export LN_MAX_BOOST=20
|
||||
export LN_MAX_CUT=20
|
||||
export LN_SLEW_DB_S=6
|
||||
export LN_RATE=48000
|
||||
export LN_RATE=96000
|
||||
export LN_IDLE_LIMIT=999999
|
||||
exec /usr/bin/python3 "__DSP__"
|
||||
|
||||
@@ -1,18 +1,38 @@
|
||||
# Collaplex 虚拟声卡(纯转发) —— 全局音频 -> 虚拟声卡 -> HRTF 独立实例 -> 数字输出
|
||||
# 占位符: __HRTF_IN__(接入 HRTF 的 sink 名), 由 脚本/安装.sh 渲染
|
||||
# Collaplex 虚拟声卡 + 响度归一化 —— 本文件由 脚本/安装.sh 生成(占位符: __HRTF_IN__ / __WRAPPER__)
|
||||
# 链路: 全局音频(应用输出) -> collaplex_vsink(虚拟声卡) -> 响度归一化(pipe DSP) -> 数字输出(S/PDIF)
|
||||
context.modules = [
|
||||
{ name = libpipewire-module-loopback
|
||||
{ name = libpipewire-module-filter-chain
|
||||
flags = [ nofail ]
|
||||
args = {
|
||||
node.description = "Collaplex 虚拟声卡"
|
||||
node.description = "Collaplex 虚拟声卡 (响度归一化)"
|
||||
media.name = "Collaplex Virtual Sink"
|
||||
filter.graph = {
|
||||
nodes = [
|
||||
{ type = builtin label = copy name = c1L }
|
||||
{ type = builtin label = copy name = c1R }
|
||||
{ type = builtin label = pipe name = lnL
|
||||
config = { command = "__WRAPPER__" } }
|
||||
{ type = builtin label = pipe name = lnR
|
||||
config = { command = "__WRAPPER__" } }
|
||||
{ type = builtin label = copy name = c2L }
|
||||
{ type = builtin label = copy name = c2R }
|
||||
]
|
||||
links = [
|
||||
{ output = "c1L:Out" input = "lnL:In" }
|
||||
{ output = "c1R:Out" input = "lnR:In" }
|
||||
{ output = "lnL:Out" input = "c2L:In" }
|
||||
{ output = "lnR:Out" input = "c2R:In" }
|
||||
]
|
||||
inputs = [ "c1L:In" "c1R:In" ]
|
||||
outputs = [ "c2L:Out" "c2R:Out" ]
|
||||
}
|
||||
capture.props = {
|
||||
media.class = Audio/Sink
|
||||
node.name = collaplex_vsink
|
||||
audio.channels = 2
|
||||
audio.position = [ FL FR ]
|
||||
}
|
||||
playback.props = {
|
||||
node.name = collaplex_vsink_out
|
||||
audio.channels = 2
|
||||
audio.position = [ FL FR ]
|
||||
node.target = "__HRTF_IN__"
|
||||
}
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
# Collaplex 虚拟声卡 + 响度归一化 —— 本文件由 脚本/安装.sh 生成(占位符: __TARGET__ / __WRAPPER__)
|
||||
# 链路: 全局音频(应用输出) -> collaplex_vsink(虚拟声卡) -> 响度归一化(pipe DSP) -> 数字输出(S/PDIF)
|
||||
context.modules = [
|
||||
{ name = libpipewire-module-filter-chain
|
||||
flags = [ nofail ]
|
||||
args = {
|
||||
node.description = "Collaplex 虚拟声卡 (响度归一化)"
|
||||
media.name = "Collaplex Virtual Sink"
|
||||
filter.graph = {
|
||||
nodes = [
|
||||
{ type = builtin label = copy name = c1L }
|
||||
{ type = builtin label = copy name = c1R }
|
||||
{ type = builtin label = pipe name = lnL
|
||||
config = { command = "__WRAPPER__" } }
|
||||
{ type = builtin label = pipe name = lnR
|
||||
config = { command = "__WRAPPER__" } }
|
||||
{ type = builtin label = copy name = c2L }
|
||||
{ type = builtin label = copy name = c2R }
|
||||
]
|
||||
links = [
|
||||
{ output = "c1L:Out" input = "lnL:In" }
|
||||
{ output = "c1R:Out" input = "lnR:In" }
|
||||
{ output = "lnL:Out" input = "c2L:In" }
|
||||
{ output = "lnR:Out" input = "c2R:In" }
|
||||
]
|
||||
inputs = [ "c1L:In" "c1R:In" ]
|
||||
outputs = [ "c2L:Out" "c2R:Out" ]
|
||||
}
|
||||
capture.props = {
|
||||
media.class = Audio/Sink
|
||||
node.name = collaplex_vsink
|
||||
audio.position = [ FL FR ]
|
||||
}
|
||||
playback.props = {
|
||||
node.name = collaplex_vsink_out
|
||||
audio.position = [ FL FR ]
|
||||
node.target = "__TARGET__"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
@@ -1,81 +1,74 @@
|
||||
#!/bin/bash
|
||||
# Collaplex 音效 — 把链路固化装到 PipeWire(用户级)
|
||||
#
|
||||
# 链路: 全局音频(应用输出) -> collaplex_vsink(虚拟声卡) -> collaplex_hrtf_in(双耳 HRTF + 房间混响, 湿量 $WET) -> 数字输出
|
||||
# 链路: 全局音频(应用输出) -> collaplex_vsink(虚拟声卡)
|
||||
# -> 响度归一化(pipe DSP) -> collaplex_hrtf_in(双耳 HRTF + 房间混响) -> 数字输出
|
||||
#
|
||||
# 装两个 filter/loopback 实例(各自独立, 靠 node.target 串成一条链):
|
||||
# ~/.config/pipewire/pipewire.conf.d/10-collaplex-vsink.conf 虚拟声卡(纯转发)
|
||||
# ~/.config/pipewire/pipewire.conf.d/20-collaplex-hrtf.conf HRTF + 混响(独立实例, 有接入/输出)
|
||||
# 用法: bash 脚本/安装.sh 湿量可覆盖: WET=0.15 bash 脚本/安装.sh
|
||||
# 装什么:
|
||||
# $HOME/.local/bin/collaplex-loudness-norm pipe 插件入口(指向本项目 dsp/)
|
||||
# $HOME/.config/pipewire/pipewire.conf.d/10-...conf 虚拟声卡 + 响度归一化
|
||||
# $HOME/.config/pipewire/pipewire.conf.d/20-...conf HRTF 核心(接入 + 输出)
|
||||
#
|
||||
# 湿量: WET=0.3 环境变量覆盖(默认 0.3)
|
||||
set -eu
|
||||
|
||||
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
CONF_DIR="$HOME/.config/pipewire/pipewire.conf.d"
|
||||
WRAPPER="$HOME/.local/bin/collaplex-loudness-norm"
|
||||
WET="${WET:-0.3}"
|
||||
HRIR="$ROOT/hrir"
|
||||
REVERB="$ROOT/reverb/房间混响IR-96k.wav"
|
||||
HRTF_IN="collaplex_hrtf_in"
|
||||
|
||||
echo "== Collaplex 音效 - 固化安装 =="
|
||||
echo "项目: $ROOT"
|
||||
|
||||
[ -f "$REVERB" ] || { echo "!! 缺房间 IR: $REVERB"; exit 1; }
|
||||
[ -f "$HRIR/FL_L.wav" ] || { echo "!! 缺 HRIR: $HRIR"; exit 1; }
|
||||
|
||||
# ---- 1. 探测数字输出(S/PDIF) ----
|
||||
TARGET="$(pw-dump | /usr/bin/python3 -c '
|
||||
import json,sys
|
||||
best=""
|
||||
# ---- 1. 探测数字输出 ----
|
||||
TARGET=$(pw-dump | /usr/bin/python3 -c '
|
||||
import json, sys
|
||||
for n in json.load(sys.stdin):
|
||||
if n.get("type")!="PipeWire:Interface:Node":
|
||||
if n.get("type") != "PipeWire:Interface:Node":
|
||||
continue
|
||||
p=(n.get("info") or {}).get("props") or {}
|
||||
nm=p.get("node.name") or ""
|
||||
if p.get("media.class")=="Audio/Sink" and ("iec958" in nm or "digital" in nm.lower()):
|
||||
best=nm
|
||||
print(best)
|
||||
')"
|
||||
[ -n "$TARGET" ] || { echo "!! 找不到数字输出设备(pw-dump 里没有 iec958/digital sink)"; exit 1; }
|
||||
p = (n.get("info") or {}).get("props") or {}
|
||||
if p.get("media.class") == "Audio/Sink":
|
||||
nm = p.get("node.name") or ""
|
||||
if "iec958" in nm or "digital" in nm.lower():
|
||||
print(nm)
|
||||
break
|
||||
')
|
||||
[ -n "$TARGET" ] || { echo "找不到数字输出设备(iec958)"; exit 1; }
|
||||
echo "数字输出: $TARGET"
|
||||
echo "HRTF 湿量: $WET"
|
||||
|
||||
# ---- 2. 渲染并安装两份配置 ----
|
||||
# ---- 2. 部署 pipe 插件入口(必须 ASCII 路径) ----
|
||||
mkdir -p "$(dirname "$WRAPPER")"
|
||||
sed -e "s|__DSP__|$ROOT/dsp/loudness_norm.py|" "$ROOT/bin/collaplex-loudness-norm" > "$WRAPPER"
|
||||
chmod 755 "$WRAPPER"
|
||||
echo "已装入口: $WRAPPER"
|
||||
|
||||
# ---- 3. 渲染两份配置 ----
|
||||
mkdir -p "$CONF_DIR"
|
||||
sed -e "s|__HRTF_IN__|$HRTF_IN|" \
|
||||
sed -e "s|__WRAPPER__|$WRAPPER|" -e "s|__HRTF_IN__|$HRTF_IN|" \
|
||||
"$ROOT/pipewire/10-collaplex-vsink.conf" > "$CONF_DIR/10-collaplex-vsink.conf"
|
||||
sed -e "s|__HRIR__|$HRIR|" -e "s|__REVERB__|$REVERB|" \
|
||||
sed -e "s|__HRIR__|$ROOT/hrir|" -e "s|__REVERB__|$ROOT/reverb/房间混响IR-96k.wav|" \
|
||||
-e "s|__TARGET__|$TARGET|" -e "s|__WET__|$WET|" \
|
||||
"$ROOT/pipewire/20-collaplex-hrtf.conf" > "$CONF_DIR/20-collaplex-hrtf.conf"
|
||||
echo "已装配置:"
|
||||
echo " $CONF_DIR/10-collaplex-vsink.conf"
|
||||
echo " $CONF_DIR/20-collaplex-hrtf.conf"
|
||||
echo "已装配置: 10-collaplex-vsink.conf(湿量不用) / 20-collaplex-hrtf.conf(湿量 $WET)"
|
||||
|
||||
# ---- 3. 重新加载 ----
|
||||
systemctl --user restart pipewire pipewire-pulse wireplumber
|
||||
sleep 7
|
||||
|
||||
# ---- 4. 自检 ----
|
||||
echo "---- 自检 ----"
|
||||
MISS=0
|
||||
for n in collaplex_vsink collaplex_vsink_out "$HRTF_IN" collaplex_hrtf_out; do
|
||||
if pw-dump | grep -q "\"node.name\": \"$n\""; then
|
||||
echo " ✓ $n"
|
||||
else
|
||||
echo " ✗ $n 缺失"; MISS=1
|
||||
fi
|
||||
# ---- 4. 重启前清掉残留 DSP(占住共享槽位会让新实例起不来) ----
|
||||
for p in /proc/[0-9]*; do
|
||||
c=$(tr '\0' ' ' < "$p/cmdline" 2>/dev/null) || continue
|
||||
case "$c" in
|
||||
"/usr/bin/python3 "*dsp/loudness_norm.py*) kill -9 "${p#/proc/}" 2>/dev/null || true;;
|
||||
esac
|
||||
done
|
||||
DEF="$(pw-dump | /usr/bin/python3 -c '
|
||||
import json,sys
|
||||
for n in json.load(sys.stdin):
|
||||
if n.get("type")=="PipeWire:Interface:Metadata":
|
||||
continue
|
||||
' 2>/dev/null; wpctl inspect @DEFAULT_SINK@ 2>/dev/null | grep -m1 "node.name" | cut -d= -f2 | tr -d " \"")"
|
||||
echo " 默认输出: ${DEF:-未知}"
|
||||
sleep 1
|
||||
|
||||
if [ "$MISS" = "0" ]; then
|
||||
echo "完成。桌面/浏览器的声音现在都会走: 虚拟声卡 -> HRTF -> 数字输出。"
|
||||
echo "单元测试: pw-play --target collaplex_hrtf_in 某个.wav (只听 HRTF 这一级)"
|
||||
else
|
||||
echo "!! 有节点没起来, 看: journalctl --user -u pipewire -n 50"
|
||||
exit 1
|
||||
fi
|
||||
# ---- 5. 重载 ----
|
||||
systemctl --user restart pipewire pipewire-pulse wireplumber
|
||||
sleep 8
|
||||
|
||||
# ---- 6. 自检 ----
|
||||
echo "=== 自检 ==="
|
||||
echo -n "节点: "; pw-dump | grep -o '"node.name": "collaplex[^"]*"' | sort -u | tr '\n' ' '; echo
|
||||
n=0
|
||||
for p in /proc/[0-9]*; do
|
||||
c=$(tr '\0' ' ' < "$p/cmdline" 2>/dev/null) || continue
|
||||
case "$c" in "/usr/bin/python3 "*dsp/loudness_norm.py*) n=$((n+1));; esac
|
||||
done
|
||||
echo "DSP 进程数: $n (应为 2)"
|
||||
echo -n "默认输出: "; wpctl inspect @DEFAULT_SINK@ 2>/dev/null | grep -m1 "node.name" | sed 's/.*= //'
|
||||
|
||||
Reference in New Issue
Block a user