diff --git a/91-clock.conf b/91-clock.conf new file mode 100644 index 0000000..665d435 --- /dev/null +++ b/91-clock.conf @@ -0,0 +1,11 @@ +# 96kHz 主时钟 —— 2026-09-12 换 SADIE-II H4 96K/512tap 脉冲响应 +# +# 为什么必须强制: PipeWire 的 convolver 会按"当前时钟率"重采样 IR。 +# 时钟若是 48k, 96k/512tap 的 IR 会被降采样成等效 256tap —— 等于白换。 +# +# 代价: 所有内容重采样到 96k。48k 内容(影片/多数音频)是整数比, 几乎无损; +# 44.1k 音乐略有精度损失。CPU 实测富余(48k 时 busy 仅 13us), 故强制。 +context.properties = { + default.clock.rate = 96000 + default.clock.allowed-rates = [ 96000 ] +} diff --git a/发布release.sh b/发布release.sh new file mode 100644 index 0000000..76b97bf --- /dev/null +++ b/发布release.sh @@ -0,0 +1,73 @@ +#!/bin/bash +# 打 release + 上传 deb 资产到 Gitea (2026-09-12) +set -eu +cd "/home/lou/桌面/工作区/实验/空间音频" +DEB="$HOME/桌面/collaplex-cinema-spatial_1.0.0_all.deb" +API="http://8.136.202.225:3000/api/v1/repos/edgevoid/cinema-spatial" +AUTH="edgevoid:147427" + +echo "=== 1. 重新打包(含最新脚本) ===" +rm -f "$DEB" +bash 打包deb.sh 2>&1 | tail -4 +LOCAL_SIZE=$(stat -c%s "$DEB") +LOCAL_MD5=$(md5sum "$DEB" | cut -d' ' -f1) +echo "本地: $LOCAL_SIZE 字节 md5=$LOCAL_MD5" + +echo +echo "=== 2. 创建 release v1.0.0 ===" +python3 - <<'PYEOF' > /tmp/rel_body.json +import json +body = """## collaplex-cinema-spatial 1.0.0 + +\u8033\u673a\u4e0a\u7684\u7535\u5f71\u9662\u7a7a\u95f4\u97f3\u9891 \u2014\u2014 PipeWire filter-chain \u865a\u62df\u58f0\u5361 + SADIE-II \u771f\u4eba HRTF\uff0c\u5168\u94fe\u8def 96kHz/24bit\u3002 + +### \u5b89\u88c5 + +``` +sudo dpkg -i collaplex-cinema-spatial_1.0.0_all.deb +systemctl --user restart pipewire pipewire-pulse wireplumber +``` + +\u4f9d\u8d56: pipewire libmysofa1 mpv + +### \u7528\u6cd5 + +``` +\u7a7a\u95f4\u97f3\u9891 \u5f00 # \u5168\u5c40\u5207\u5230\u7acb\u4f53\u58f0\u4e0a\u6df7\u7248(\u63a8\u8350) +\u7a7a\u95f4\u97f3\u9891 \u5f005.1 # \u5168\u5c40\u5207\u5230 5.1 \u76f4\u901a\u7248 +\u7a7a\u95f4\u97f3\u9891 \u5173 # \u5207\u56de\u7269\u7406\u8bbe\u5907 +\u7a7a\u95f4\u97f3\u9891 \u72b6\u6001 # \u67e5\u770b\u72b6\u6001 +mpv-\u5f71\u9662 \u7535\u5f71.mkv # \u53ea\u7ed9 mpv \u7528 +``` + +### \u8bf4\u660e + +- HRTF \u7528 SADIE-II \u771f\u4eba\u53d7\u8bd5\u8005 H4\uff0c96K/24bit/512tap +- \u7acb\u4f53\u58f0\u6e90\u81ea\u52a8\u77e9\u9635\u4e0a\u6df7\uff0c\u4e0d\u9700\u8981\u591a\u58f0\u9053\u7247\u6e90 +- \u82e5\u65e0\u58f0: python3 /usr/share/cinema-spatial/\u751f\u6210\u914d\u7f6e.py \u91cd\u65b0\u63a2\u6d4b\u8f93\u51fa\u8bbe\u5907 +""" +print(json.dumps({"tag_name": "v1.0.0", "name": "v1.0.0", "body": body}, ensure_ascii=False)) +PYEOF + +RELEASE_ID=$(curl -s -X POST "$API/releases" -u "$AUTH" -H "Content-Type: application/json" \ + --data-binary @/tmp/rel_body.json \ + | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('id','')); " 2>/dev/null) +echo "release id: $RELEASE_ID" + +echo +echo "=== 3. 上传 deb 资产 ===" +curl -s -X POST "$API/releases/$RELEASE_ID/assets?name=collaplex-cinema-spatial_1.0.0_all.deb" \ + -u "$AUTH" -H "Content-Type: application/octet-stream" \ + --data-binary "@$DEB" -o /tmp/upl.json -w "上传 HTTP %{http_code}\n" + +echo +echo "=== 4. ★ 逐字节核对 ===" +curl -s "$API/releases/$RELEASE_ID" -u "$AUTH" | python3 -c " +import sys, json +d = json.load(sys.stdin) +print('release:', d.get('tag_name'), '|', d.get('name')) +for a in d.get('assets', []): + print(' 资产:', a['name'], '|', a['size'], '字节') + print(' 下载:', a.get('browser_download_url','')) +" +echo "本地字节: $LOCAL_SIZE 本地md5: $LOCAL_MD5" diff --git a/打包deb.sh b/打包deb.sh index a859e91..2e46bb2 100644 --- a/打包deb.sh +++ b/打包deb.sh @@ -32,7 +32,7 @@ echo "=== 5. PipeWire 配置 (用安装路径生成) ===" CINEMA_SPATIAL_IR="${ROOT}/hrir" \ CINEMA_SPATIAL_OUT="$BUILD/usr/share/pipewire/pipewire.conf.d/90-cinema-spatial.conf" \ python3 "$SRC/生成配置.py" -cp "$HOME/.config/pipewire/pipewire.conf.d/91-clock.conf" \ +cp "$SRC/91-clock.conf" \ "$BUILD/usr/share/pipewire/pipewire.conf.d/" echo "=== 6. control ===" diff --git a/生成配置.py b/生成配置.py index ff793d0..e8228af 100644 --- a/生成配置.py +++ b/生成配置.py @@ -16,6 +16,7 @@ _ROOT = os.path.dirname(os.path.abspath(__file__)) # IR 目录: 环境变量优先(打包/安装场景指 /usr/share/cinema-spatial/hrir), # 否则用项目内的软链 hrir/current (开发时换 HRTF 只改软链)。 IR = os.environ.get("CINEMA_SPATIAL_IR") or os.path.join(_ROOT, "hrir", "current") + OUT = os.environ.get("CINEMA_SPATIAL_OUT") or os.path.expanduser( "~/.config/pipewire/pipewire.conf.d/90-cinema-spatial.conf" ) @@ -68,6 +69,15 @@ def default_sink_name() -> str: return first +# node.target —— filter-chain 的输出端必须送到"真实设备": +# - @DEFAULT_SINK@ 这类 pactl 占位符 PipeWire 不认(会当成设备名, 结果连到别的设备) +# - 完全不写则 WirePlumber 可能连错(实测连到 HDMI) +# 所以默认探测物理输出(优先 USB), 换设备后重跑本脚本即可。 +# 也可 export CINEMA_SPATIAL_TARGET= 强制指定。 +_TARGET = os.environ.get("CINEMA_SPATIAL_TARGET") or default_sink_name() +TARGET_PROP = f'node.target = "{_TARGET}" ' if _TARGET else "" + + def hrir_block(indent: str) -> list[str]: """12 个卷积器 + 6 个分发 + 左右耳混音。输入端口: cp:In""" L: list[str] = [] @@ -107,14 +117,14 @@ def sink_51() -> list[str]: ' outputs = [ "mixL:Out" "mixR:Out" ]', " }", " capture.props = { node.name = cinema_spatial_sink media.class = Audio/Sink " "audio.channels = 6 audio.position = [ FL FR FC LFE BL BR ] }", - f' playback.props = {{ node.name = cinema_spatial_out node.passive = true node.target = "{default_sink_name()}" ' + f' playback.props = {{ node.name = cinema_spatial_out {TARGET_PROP}' "audio.channels = 2 audio.position = [ FL FR ] }", " }", " }"] return L def sink_upmix() -> list[str]: - tgt = default_sink_name() + L = [" { name = libpipewire-module-filter-chain", " flags = [ nofail ]", " args = {", ' node.description = "电影院空间音频 (立体声上混)"', ' media.name = "Cinema Spatial Upmix"', " filter.graph = {", " nodes = [", @@ -157,7 +167,7 @@ def sink_upmix() -> list[str]: ' outputs = [ "mixL:Out" "mixR:Out" ]', " }", " capture.props = { node.name = cinema_spatial_up_sink media.class = Audio/Sink " "audio.channels = 2 audio.position = [ FL FR ] }", - f' playback.props = {{ node.name = cinema_spatial_up_out node.passive = true node.target = "{tgt}" ' + f' playback.props = {{ node.name = cinema_spatial_up_out {TARGET_PROP}' "audio.channels = 2 audio.position = [ FL FR ] }", " }", " }"] return L @@ -172,7 +182,7 @@ def main() -> int: with open(OUT, "w", encoding="utf-8") as fh: fh.write("\n".join(body)) print(f"已生成 {OUT} ({len(body)} 行)") - print(f"输出目标: {default_sink_name()}") + print(f"输出目标: {TARGET_PROP.replace('node.target = ', '').strip() or '自动(WirePlumber 路由到默认输出)'}") return 0