mcpField
W
wjs-syncing-multicam

wjs-syncing-multicam

github.com/jianshuo创意与媒体137181 次安装今天更新
代码仓库

关于 wjs-syncing-multicam

wjs-syncing-multicam 是用于对齐同一事件的多机位录音素材的技能,适合后续需要剪辑或合成的多源视频工作流。它通过音频包络互相关计算时间偏移,并写入旁车 JSON,不会修改、复制或重新编码原始文件。

主要功能

  1. 01通过音频互相关计算时间偏移
  2. 02自动选择响度最高的音轨
  3. 03检测时钟漂移并估算重叠区间
  4. 04写入 .sync.json 旁车文件
  5. 05支持覆盖不完整的素材

适用场景

  1. 01对齐多台摄像机拍摄的同一场活动
  2. 02将只覆盖部分时段的录音与主机位同步
  3. 03为多机位剪辑提供可验证的同步元数据

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/jianshuo/claude-skills --skill wjs-syncing-multicam

安装后在 Claude Code 中输入/wjs-syncing-multicam确认已加载。

详细介绍

wjs-syncing-multicam

使用音频互相关,为同一事件的每个多源录制计算一个时间偏移量,并在每个原始文件旁生成 .sync.json 辅助文件。**绝不会修改、复制或重新编码原始文件。**下游工具会在使用时通过 -itsoffset 应用偏移量。

安装与命令

此功能由开源的 polysync pip 包实现(https://pypi.org/project/polysync/ · https://github.com/jianshuo/polysync)——此技能不再自带脚本。请确保已安装该包,然后通过其 CLI 运行:

python3 -m pip install -U polysync      # needs ffmpeg/ffprobe on PATH

polysync sync   REFERENCE SOURCE        # align SOURCE to REFERENCE, write sidecars
polysync sync   REFERENCE SOURCE --partial   # source covers only part of the session
polysync verify REFERENCE SOURCE SOURCE.sync.json   # independent residual check

每个非参考机位都运行一次 polysync sync(先指定参考机位,每次使用相同的参考机位)。下文介绍算法、辅助文件架构,以及该包内置的注意事项;请阅读这些内容,以便解读输出并选择标志。

设计原则——使用辅助文件,而非重新编码

此技能的早期版本会通过裁剪并重新编码生成 *_synced.MOV 文件,将偏移量写入文件。我们已移除该做法:

  • 磁盘空间——3 台摄像机拍摄 75 分钟的 4K 素材会占用 60+ GB。重新编码生成同步副本只会让占用翻倍,不会增加任何信息。
  • 质量——每次重新编码都会造成有损压缩。原始文件才是事实来源;辅助文件则是可逆元数据。
  • 速度——在 Apple Silicon 上,每个文件生成 _synced.MOV 需要 10+ 分钟;生成辅助文件只需几秒。
  • 可组合性——任何下游工具(polysync edit、NLE 导入、ffmpeg 单行命令)都能读取辅助文件并自行应用偏移量。不会被特定工具的文件格式绑定。

不适用场景

  • 单机位素材——没有可与之同步的对象。若要将单个源文件拆分为片段,请使用 video-segmentation。
  • 在 NLE 时间线上已对齐的素材——不要与剪辑软件对着干。
  • 若要进行同步后的自动剪辑、剪切或画中画渲染,请使用 wjs-editing-multicam(该工具会读取这些辅助文件)。

为何使用基于包络的方法,而非原始波形

当两个麦克风的增益或房间响应不同时,原始 PCM 互相关会产生较弱的峰值和错误匹配——使用辅助摄像机时几乎总是如此。对数能量包络能捕捉对话和音乐的动态变化,无论频率响应如何,两个麦克风都能记录到这些变化。不要跳过包络步骤——这正是此技能在低信噪比下仍然可靠的关键。

算法

  1. 从每个输入中提取 8 kHz、16 位的单声道 PCM。音频流会按响度自动选择(loudest_audio_stream):在文件中段选取 60 秒窗口,探测每个 0:a:N,并选择平均音量最高的流。多轨专业摄像机若简单地使用 0:a:0 就会出问题——Sony FX6 MXF 片段包含 4 条单声道 PCM 音轨,而 a:0 / a:1 通常没有信号(约 -90 dB),环境麦克风则位于 a:2 / a:3;与静音音轨做互相关会导致同步失败。单音轨输入(大多数 MP4 摄像机)会直接使用 a:0。
  2. 以 100 Hz 计算对数能量包络(10 ms 步长、50 ms 窗口)。使用截止频率为 0.05 Hz 的二阶 Butterworth 高通滤波器,并通过 filtfilt 滤波,以消除缓慢漂移和增益偏移。
  3. 对包络进行端到端 FFT 互相关,得到粗略偏移量(约 10 ms)。
  4. 在样本级进行精细校准:在粗略对齐位置附近,从 A 中取 60 秒片段,并在 ±2 秒搜索窗口内搜索,使用抛物线进行峰值插值。
  5. 多探测点漂移检查——每隔约 3 分钟重复步骤 4。线性拟合 delta(t) = slope·t + intercept 可揭示真实时钟漂移(通常为 5–50 ppm)。使用以中点为基准的偏移量(slope · midpoint + intercept),使残余误差围绕零对称分布。
  6. 在参考时间线上计算重叠窗口:overlap = [max(0, delta), min(ref_dur, delta + src_dur)]。
  7. 在每个非参考输入旁生成 .sync.json 辅助文件。不会复制、裁剪或重新编码任何文件。参考输入也会获得辅助文件(其中 delta_seconds: 0),以便下游代码统一处理所有输入。

polysync sync 即为此功能的实现。它只会生成 .sync.json 辅助文件——不会生成 _synced.MOV,也不会重新编码。

Sidecar 架构(<input>.sync.json)

每个原始输入对应一个 sidecar,写入其旁边。纯 JSON,文件内不含注释——以下字段参考为权威说明。

{
  "_about": "Sync metadata for cam_b.MOV. Apply via ffmpeg -itsoffset. See wjs-syncing-multicam SKILL.md for full schema.",
  "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
  }
}
字段参考
字段类型含义
_aboutstring易于理解的一句话说明。包含指向此 SKILL.md 的引用。始终存在。
schema_versionint此架构发生任何不兼容变更时递增。当前:1。
sourcestring此 sidecar 所描述的原始文件名。相对于 sidecar 所在目录。绝不指向重新编码后的文件。
referencestring时间线对齐所依据的输入文件。该参考文件自己的 sidecar 会在此处列出它自身。
delta_secondsfloatsource 的 t=0 在参考时间线中的位置。**若为正值,表示 source 晚于参考文件开始;传给 ffmpeg 时使用 -itsoffset <delta>。**也可以为负值(例如,source 来自提前开始录制的摄像机)。
drift_slopefloat线性时钟漂移斜率(无量纲,约为 10⁻⁵)。0.0 表示未测得漂移。下游仅在同步声音/长时长唇形同步时对 source 应用 atempo = 1 + drift_slope——摄像机切换剪辑时忽略。
overlap_in_reference[start, end](秒)source 和参考文件均有覆盖的时间窗口,以参考时间线表示。用它将输出裁剪到双方均有效的时间范围。
overlap_in_source[start, end](秒)同一时间窗口,以 source 的本地时间线表示。overlap_in_reference[0] - delta_seconds = overlap_in_source[0]。
verificationobject运行 polysync verify 的输出——用于判定“同步是否收敛”。median_residual_ms 应为几毫秒;若 residual_spread_ms 大于交付帧率下的一帧时长,表示需要进行漂移校正,但校正被跳过。

下游如何使用 sidecar

-itsoffset 是 ffmpeg 的逐输入参数,且必须放在 -i 之前。始终从 sidecar 读取 source 的 delta_seconds:

# Play cam_b aligned to cam_a's timeline
ffmpeg -itsoffset $(jq -r .delta_seconds cam_b.MOV.sync.json) -i cam_b.MOV \
       -i cam_a.MOV \
       -filter_complex "[0:v][1:v]hstack" out.mp4

# Trim to mutual overlap window (read from cam_b.MOV.sync.json)
ffmpeg -ss <overlap_in_source[0]> -i cam_b.MOV -t <overlap_dur> ...

对于 wjs-editing-multicam,polysync edit 会自动导入每个 <input>.sync.json;无需手动组合这些标志。

部分覆盖的片段 — polysync sync --partial

常见情况——主摄像机覆盖 75 分钟,Riverside/手机/领夹麦克风录音设备只覆盖中间的 30 分钟。运行 polysync sync REF.MOV NEW.mp4 --partial:

  1. 将新输入与参考文件进行互相关分析(使用与完整重叠模式相同的包络算法)。
  2. 找出新片段的 t=0 在参考时间线中的位置(delta_seconds 可能很大,例如 1842.5)。
  3. 仅写入 source sidecar——不添加黑色视频、不填充音频、不重新编码。overlap_in_reference 会准确告知使用者此输入的覆盖时段;在该时段之外,回退到主摄像机。

--partial 标志只改变失败处理方式:在探测结果较少时,以中位数 delta 进行宽松处理;没有探测结果时,则使用粗略 delta,而不是在少于 3 个有效探测结果时失败;同时跳过参考文件 sidecar(假定参考文件已属于一个既有同步集)。其他行为与默认模式完全相同。

何时跳过漂移校正

对于摄像机切换剪辑(最常见的情况),一小时内 ±25 ms 的残差低于人类感知阈值——设置 drift_slope: 0.0,只使用中点 delta_seconds。

对于长时长(>30 min)的同步声音/唇形同步,如果 verification.residual_spread_ms > 40,下游会对 source 应用 atempo = 1 + drift_slope。source 文件仍不会被修改——atempo 滤镜会在使用时运行。

验证(始终运行)

polysync verify REF.MOV SRC.MOV SRC.sync.json 会原生地从两个原始文件重新提取音频(选择音量最大的音轨,不使用 ffmpeg 偏移),并再次运行多探针相关性分析。它会将 sidecar 中的 delta_seconds(以及使用 --apply-drift 时的漂移斜率)作为 numpy 中的索引运算应用:在参考时间 bs 处的探针取自本地时间 bs - delta 对应的源文件位置,然后在参考索引 bs 附近进行搜索;峰值偏移即为残差。结果会写回 sidecar 的 verification 字段。

通过标准——median_residual_ms < 15 且 residual_spread_ms < 交付帧率下的 1 帧。未通过时,启用漂移校正后重试。

只有 spread 未通过而 median 接近零时,通常是噪声,并非不同步。 宽景/B-roll 摄像机上的远场麦克风(混响大、SNR 低、ncoef 约为 0.2)会产生少数离群探针,从而拉高 spread,但 median 仍只有几毫秒。对于镜头切换式剪辑,这已经对齐——median 才是真实情况;spread 门槛较为保守。只有长篇口型同步时才需要追查。

常见问题

  • 低 SNR 下,原始波形互相关会产生假峰值。 始终先提取包络——这不是可调参数,而是整个方法的前提。
  • -itsoffset 对音频和视频的语义不同——要确保同步正确,它必须是该输入的第一个标志。ffmpeg -i src -itsoffset X 是错误的;ffmpeg -itsoffset X -i src 才是正确的。
  • 将流复用为无头原始 PCM(-f s16le)时,-itsoffset 不起作用。 不存在可承载偏移的容器时间戳,因此 ffmpeg 会静默丢弃偏移,也不会插入前导静音。任何将音频提取为原始 PCM 的分析(例如 polysync verify)都必须在 numpy 中通过索引运算应用偏移,绝不能在提取时使用 -itsoffset。错误操作的表现:即使同步本身完全正确,验证残差仍会散布在数百毫秒的范围内,且 ncoef ~0。
  • 简单地提取 0:a:0 会在不报错的情况下与无声轨道同步。 多音轨摄像机(Sony FX6 MXF:a:0/a:1 通常为 -90 dB,麦克风在 a:2)需要根据响度选择音轨。polysync sync 和 polysync verify 都会自动选择同一个音量最大的音轨——如果你要重新实现,必须保持这一不变量,否则残差将毫无意义。
  • Sidecar 路径必须相对于 sidecar 文件所在目录,而不是使用它的进程的工作目录。应将 source/reference 路径相对于 Path(sidecar).parent 解析。
  • 不要将 drift_slope 合并进 sidecar 的 delta_seconds。 它们分设字段自有原因——简单的使用方可以忽略漂移,而支持同步声音的使用方可以应用它。混在一起会丢失信息。

评论

支持 Markdown