跳转到内容

时间、帧率和素材时间

AelionSDK 的公开时间单位是整数微秒,带时间含义的字段通常以 Us 结尾。1 秒是 1_000_000,不是 1,也不是浮点秒。

使用 SDK helper 会更清楚,也能统一检查范围:

import { frames, milliseconds, seconds } from '@aelionsdk/sdk';
const introStartUs = seconds(2.5); // 2_500_000
const fadeDurationUs = milliseconds(180); // 180_000
const frame100Us = frames(100, { numerator: 30_000, denominator: 1_001 });

时间必须是 JavaScript safe integer。开始时间允许为 0;大多数 duration 必须大于 0。小数微秒、NaN、Infinity 和负数都会被拒绝。

时间线会反复移动、裁剪、序列化和比较边界。浮点秒在这些操作中容易出现 2.9999999997 一类误差,最终影响吸附、相邻判断和导出帧数。

整数微秒适合媒体时间戳,也足以表达常见视频帧和音频采样边界。帧率仍然使用有理数,避免把 29.97 当作不精确小数。

显示名称Project 值
24 fps{ numerator: 24, denominator: 1 }
25 fps{ numerator: 25, denominator: 1 }
29.97 fps{ numerator: 30000, denominator: 1001 }
30 fps{ numerator: 30, denominator: 1 }
59.94 fps{ numerator: 60000, denominator: 1001 }

把播放头吸附到帧时,使用 frames(frameIndex, frameRate)。不要在多个组件里分别写 Math.round(frame / fps * 1e6),否则边界算法很容易不一致。

startUs + durationUs 得到结束边界,但结束时间本身不属于这个片段:

片段 A: [0, 1_000_000)
片段 B: [1_000_000, 2_000_000)

两段可以在 1 秒处无缝相接,不会同时拥有边界那一时刻。这条规则会用于片段命中、裁剪、转场和 affected range 计算。

判断播放头是否严格位于片段内部:

const startUs = item.range.startUs;
const endUs = startUs + item.range.durationUs;
const canSplit = playheadUs > startUs && playheadUs < endUs;

起点和终点都不能作为切分点,否则会产生 0 时长片段。

时间线时间和原素材时间不是一回事

Section titled “时间线时间和原素材时间不是一回事”

假设一个片段放在时间线 10 秒处,但从原视频 3 秒处开始读取:

timeline range: [10s, 15s)
source range: [ 3s, 8s)

1× 正向播放时:

sourceTime = sourceStart + (timelineTime - timelineStart)
  • 移动片段:timeline start 变化,source range 不变;
  • trim start:两者的开始通常一起变化;
  • slip:timeline range 不变,source range 前后移动;
  • 变速/反向:由 TimeMap 把两套时间关联起来。

音频和视频使用同一个映射,因此 edit、seek、preview 和 export 不应该分别发明一套换算。

普通线性 TimeMap 可以设置速度和方向。音频默认使用 varispeed:速度变快时音高也 升高,速度变慢时音高降低。需要保持音高时,在音频 Clip 上设置 pitchPolicy: 'preserve';引擎会使用确定性的 WSOLA overlap-add,并在任意 PCM 块边界之间保留连续状态。

非线性 TimeMap 无法由单一 stretch ratio 表达,因此 Project validation 会拒绝 pitchPolicy: 'preserve'。反向、曲线变速或广播级主观音质仍应在目标素材和设备上 单独验收,不能只根据策略名称推断音质。

SDK 会拒绝非法时间,但不会替你选择吸附规则。时间线通常收集:

  • Sequence 开头和结尾;
  • Item 入点和出点;
  • Marker;
  • 播放头;
  • 当前帧边界。

把鼠标像素换成初始微秒,再在屏幕容差内选择最近候选,最后把确定的整数 timeUs 交给命令。吸附辅助线、容差和优先级留在 UI state,不要写进 Project。

UI 预检查可以给用户更快的反馈:

  • 结果时间不能小于 0;
  • duration 不能为 0;
  • 切分点必须在片段内部;
  • source range 不能越过可用素材,除非 boundary 明确允许;
  • 固定时长 Sequence 不能违反 overflow 设置;
  • 转场需要左右片段都有足够 handle。

这些检查不能取代 SDK 校验。两个协作者可能同时修改工程,UI 看到合法的位置在提交时仍可能因 revision 或结构变化而失败。