跳转到内容

导出 MP4 和 WebM

视频导出使用当前 Sequence 的画布尺寸、帧率、采样率和声道。API 不单独接收 width/height;要导出不同分辨率,应创建或选择对应规格的 Sequence。

import { SeekableMemorySink } from '@aelionsdk/export';
const sink = new SeekableMemorySink();
const options = {
profile: 'mp4-h264-aac' as const,
execution: 'worker' as const,
sink: sink.writable,
videoBitrate: 8_000_000,
audioBitrate: 192_000,
onProgress: (progress: number) => {
progressBar.value = progress;
},
cleanupSink: () => sink.cleanup(),
};
const report = await session.export.preflightProfile(options);
if (!report.ok) {
sink.cleanup();
console.table(report.issues);
return;
}
const result = await session.export.startProfile(options);
const bytes = sink.finalize();
if ('encoderConfiguration' in result) {
console.log(result.mimeType);
console.log(result.encoderConfiguration);
console.log(result.videoFrames, result.audioFrames);
}

H.264 profile 根据当前画布与帧率,从满足 macroblock 约束的最小 AVC level 开始 尝试 High/Main/Baseline;例如参考运行时的 1080p30 选择 avc1.640028,4K30 选择 avc1.640033。实际选择写在 preflight 和 result 的 encoderConfiguration.videoCodecString 中。

AAC 不能只看 AudioEncoder.isConfigSupported()。SDK 还会做运行时 canary,避免浏览器声称支持但实际不能产出可用 AAC。

配置了 Export Worker 资源时,execution: 'worker' 会把 WebCodecs 编码、mux 和 Sink 背压调度移出页面主线程;帧生产仍由宿主回调完成。未显式选择时沿用当前默认 执行策略。

调用方式与 H.264 相同,把 profile 改为 mp4-av1-aacmp4-hevc-aac。这两个 路径会提交精确的 WebCodecs configuration,并且只有 preflight 通过才开始 mux:

const options = {
profile: 'mp4-av1-aac' as const,
sink: sink.writable,
videoBitrate: 6_000_000,
audioBitrate: 192_000,
};
const report = await session.export.preflightProfile(options);
if (report.ok) await session.export.startProfile(options);

不要把“profile 存在”解释为浏览器已带 encoder。unsupported 时可回退 H.264/WebM 或使用 Remote Export;HEVC/AV1 的解码支持也要对输入另行 probe。

调用方式相同,只替换 profile 和码率:

const sink = new SeekableMemorySink();
const options = {
profile: 'webm-vp9-opus' as const,
sink: sink.writable,
videoBitrate: 6_000_000,
audioBitrate: 160_000,
cleanupSink: () => sink.cleanup(),
};
const report = await session.export.preflightProfile(options);
if (!report.ok) {
sink.cleanup();
showIssues(report.issues);
return;
}
const result = await session.export.startProfile(options);
const bytes = sink.finalize();

WebM profile 使用 VP9 vp09.00.10.08 和 Opus。它在现代 Chromium/Firefox 上通常更容易满足,但最终交付平台未必接受 WebM,因此格式选择应由产品和目标渠道决定。

码率是 VBR 目标,不保证文件的实际平均码率。可以用下面数值建立第一批真实素材测试:

画布视频码率测试起点音频码率测试起点
720p3–5 Mbps128–160 kbps
1080p6–10 Mbps160–192 kbps
4K20–45 Mbps192–256 kbps

运动、噪点、帧率、文字细节和目标平台二次压缩都会影响结果。不要仅凭分辨率固定一个“最佳码率”;用实际业务素材比较清晰度、体积和编码耗时。

产品预设可以这样组织:

const presets = {
'1080p-standard': { videoBitrate: 8_000_000, audioBitrate: 192_000 },
'1080p-small': { videoBitrate: 5_000_000, audioBitrate: 160_000 },
'4k-standard': { videoBitrate: 30_000_000, audioBitrate: 256_000 },
} as const;

这些值仍需经过当前 Project preflight。

Project 可以设置 3840×2160。能否在本地完成由真实设备决定:

  • VideoEncoder 是否接受 H.264/VP9 的 4K 配置;
  • GPU 最大纹理尺寸和可用内存;
  • 原片 decoder、效果 pass 和轨道数量;
  • OPFS quota 和剩余空间;
  • 移动设备温控、后台和页面生命周期。

4K 不应只是一个永远可点的下拉选项。切换到 4K preset 后执行 preflight;失败时提供 1080p 或远程导出。

import { OpfsSeekableSink } from '@aelionsdk/export';
const sink = new OpfsSeekableSink('output.mp4');
const result = await session.export.startProfile({
profile: 'mp4-h264-aac',
sink: sink.writable,
videoBitrate: 12_000_000,
audioBitrate: 192_000,
cleanupSink: () => sink.cleanup(),
});
await sink.waitUntilFinalized();
const file = await sink.getFile();
console.log(result.mimeType, file.size);

waitUntilFinalized() 等待 transferred stream 真正关闭。不要在 Job 完成前读取,也不要复用已经关闭或失败的 Sink。

需要跨刷新或崩溃恢复时,直接使用 @aelionsdk/export 的持久分段 API:

import {
exportResumableMuxed,
IndexedDbResumableMuxedExportStore,
OpfsSeekableSink,
} from '@aelionsdk/export';
const store = new IndexedDbResumableMuxedExportStore({
databaseName: 'my-editor-export-checkpoints',
namespace: currentUserId,
});
const sink = new OpfsSeekableSink('delivery.mp4');
const result = await exportResumableMuxed({
key: exportJobId,
contentId: projectContentHash,
profile: 'mp4-h264-aac',
store,
segmentDurationUs: 2_000_000,
durationUs,
width,
height,
frameRate,
sampleRate,
channelCount,
videoBitrate: 12_000_000,
audioBitrate: 192_000,
renderFrame,
renderAudio,
sink: sink.writable,
});

每个完整 WebM cluster 或 fMP4 fragment 会和前缀 manifest 在同一个 IndexedDB 事务里提交。重启后用相同的 keycontentId 和编码配置再次调用;函数会校验 每个单元的 SHA-256,复用已提交前缀,只从第一个缺失单元继续。恢复调用必须提供 一个新的空 Sink;最终成片会从已验证单元顺序组装。

contentId 必须绑定工程内容、素材版本和影响像素/PCM 的外部输入。内容或编码配置 改变时,旧 checkpoint 会失效并重新开始。只有确认不再需要重试时才设置 deleteCheckpointOnSuccess: true

function downloadVideo(bytes: Uint8Array, mimeType: string, filename: string): void {
const url = URL.createObjectURL(new Blob([bytes], { type: mimeType }));
const anchor = Object.assign(document.createElement('a'), {
href: url,
download: filename,
});
anchor.click();
URL.revokeObjectURL(url);
}

只有 Job completed 后才能 finalize()。失败和取消时调用 cleanup,不要下载 0 字节或半成品容器。