Wjs Syncing Multicam — Wjs 多摄像头同步
v0.1.0当用户拥有2+个相同事件的视频/音频录制,且这些录制由不同设备(相机、手机、独立音频录制器)捕获,并希望将它们对齐到单一的共同时间轴时使用。仅输出每个输入的轻量级`.sync.json`副文件 —— 原始文件永远不会被重新编码。触发器 —— “多机位同步”,“对齐这几个机位”,“match camera timelines”,“sync these angles”,“audio drift between cameras”,“separate audio recorder”,“Riverside / Zoom 录制需要对齐”。
运行时依赖
安装命令
点击复制技能文档
wjs-syncing-multicam 计算多源录制同一事件的单一时间偏移,使用音频互相关,并在每个原始文件旁边发出一个 .sync.json 副文件。原始文件永远不会被修改、复制或重新编码。下游工具使用 -itsoffset 在消费时应用偏移。
设计原则 —— 副文件优于重新编码 早期版本的此技能通过修剪和重新编码生成 *_synced.MOV 文件,以将偏移融入文件中。我们已删除此功能: 磁盘 —— 一个 75 分钟的 4K 录制,从 3 台摄像机拍摄,大小为 60+ GB。重新编码的同步副本将大小加倍,而没有任何信息增益。 质量 —— 每次重新编码都是有损的。原始文件是事实来源;副文件是可逆的元数据。 速度 —— _synced.MOV 文件的生成需要 10+ 分钟每个文件在 Apple Silicon 上;副文件的发出需要几秒钟。 组合性 —— 任何下游工具(autoedit.py、NLE 导入、ffmpeg 一行命令)都可以读取副文件并应用偏移本身。没有工具特定的文件格式锁定。
何时不使用 单摄像头录制 —— 没有需要同步的内容。 对于将一个源分割为片段,使用 video-segmentation。 已经在 NLE 时间轴中对齐的源 —— 不要与编辑器冲突。 对于自动编辑/剪辑/PIP 渲染步骤(在同步之后),使用 wjs-editing-multicam(使用这些副文件)。
为什么使用基于包络的方法,而不是原始波形 原始 PCM 互相关在两个麦克风具有不同的增益/房间响应时会产生弱峰值和错误匹配 —— 即几乎总是在次要摄像机中。对数能量包络捕获对话和音乐动态,这两个麦克风都可以听到,无论频率响应如何。不要跳过包络步骤 —— 这是此技能在低 SNR 下强大的原因。
算法 从每个输入中提取 8 kHz、16 位的单声道 PCM。 计算 100 Hz(10 ms 跳跃、50 ms 窗口)的对数能量包络。 使用 2 阶 Butterworth 高通滤波器(0.05 Hz 截止频率)和 filtfilt —— 去除缓慢漂移和增益偏移。 FFT 互相关包络端到端 → 粗略偏移(~10 ms)。 在 B 中的粗略对齐位置附近的 60 s 探测范围内,以 ±2 s 搜索窗口和抛物线峰值插值精化偏移。 多探测漂移检查 —— 每 ~3 分钟重复步骤 4。 线性拟合 delta(t) = slope·t + intercept 显示真实时钟漂移(5–50 ppm 典型)。 使用中点规范偏移(slope · 中点 + intercept),使残余误差对称围绕零。 计算参考时间轴中的重叠窗口:重叠 = [max(0, delta), min(ref_dur, delta + src_dur)]。 发出 .sync.json 副文件到每个非参考输入旁边。 没有文件被复制、修剪或重新编码。 参考输入也获得一个副文件(带有 delta_seconds: 0),以便下游代码可以统一处理所有输入。
脚本/sync.py 是实现。 注意:当前脚本仍然发出 _synced.MOV 文件与副文件 —— 这条路径已弃用;副文件是唯一权威输出。
副文件模式 (.sync.json) 每个原始输入一个副文件,写入其旁边。 纯 JSON,无文件内注释 —— 下面的字段参考是规范的。 { "_about": "Sync 元数据用于 cam_b.MOV。通过 ffmpeg -itsoffset 应用。请参阅 wjs-syncing-multicam SKILL.md 获取完整模式。", "schema_version": 1, "source": "cam_b.MOV", "reference": "cam_a.MOV", "delta_seconds": 12.345, "drift_slope": 1.8e-5, "overlap_in_reference": [12.345, 4512.180], "overlap_in_source": [0.000, 4499.835], "verification": { "median_residual_ms": 4.2, "residual_spread_ms": 11.8, "probe_count": 24 } }
字段参考 字段类型 含义 _about string 人类可读的一行。包括指向此 SKILL.md 的指针。始终存在。 schema_version int 在此模式的任何破坏性更改时增加。当前:1。 source string 原始文件的文件名,该副文件描述。相对于副文件的目录。永远不会指向重新编码的文件。 reference string 输入的时间轴我们正在对齐的。参考的自己的副文件在此列出。 delta_seconds float 源的 t=0 表示在参考的时间轴中。如果为正,源在参考之后开始;传递给 ffmpeg 作为 -itsoffset 。可以为负(源在参考之前开始,例如早期滚动摄像机)。 drift_slope float 线性时钟漂移斜率(无量纲,~10⁻⁵)。0.0 表示没有可测量的漂移。下游仅将其应用于源用于同步声音/长形式唇-sync —— 对于摄像机剪辑编辑,忽略。 overlap_in_reference [start, end](秒)源和参考都有覆盖的窗口,在参考的时间轴中表示。使用此窗口修剪输出到相互有效的时间范围。 overlap_in_source [start, end](秒)同一窗口在源的本地时间轴中表示。 overlap_in_reference[0] - delta_seconds = overlap_in_source[0]。 verification object verify.py 的输出 —— 驱动一个“是否同步收敛?”门。 median_residual_ms 应该是几毫秒;residual_spread_ms > 1 帧在交付 fps 意味着漂移纠正是必要的,但被跳过。