mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-05 00:56:23 +00:00
feat(engine): closed-GOP VP9 encoder args + concat-copy smoke test (#950)
## Description PR 1 of 4 in the WebM (VP9) distributed-rendering series. A gating experiment that proves closed-GOP libvpx-vp9 chunks survive `ffmpeg -f concat -c copy` losslessly, so the rest of the stack can ship Path A (concat-copy) rather than the slower re-encode-in-assemble fallback. Two changes: 1. **Closed-GOP VP9 encoder args.** `buildEncoderArgs` now lays `-g <chunkSize>`, `-keyint_min <chunkSize>`, `-auto-alt-ref 0`, and `-cpu-used 2` on libvpx-vp9 when `lockGopForChunkConcat=true`. Mirrors the existing libx264/libx265 branches. The alt-ref disable is load-bearing — libvpx-vp9's default non-displayable alt-ref frames can reach across chunk seams and break concat-copy. `-cpu-used 2` pins the speed/quality tradeoff so chunks encoded on workers with different libvpx-vp9 defaults produce visually consistent output across seams. Default (`lockGopForChunkConcat` unset) preserves the existing in-process VP9 path unchanged. 2. **Concat-copy smoke test** at `packages/producer/tests/distributed/_smoke/webm-concat-copy.test.ts`. Generates 60 PNGs via lavfi `testsrc2`, encodes them as 4 VP9 chunks of 15 frames using `buildEncoderArgs` with `lockGopForChunkConcat=true`, concat-copies via `ffmpeg -f concat -c copy`, then runs three independent verifications: `ffprobe -show_streams`, `ffmpeg -f null -` decode test, and `ffprobe -count_frames`. Each verification surfaces its failure fingerprint in the error message. Smoke test passes 6/6 locally → Path A works; the rest of the stack takes it. Also exports `buildEncoderArgs` from `@hyperframes/engine` so adapters / tests can construct args without re-implementing the contract. ## Testing - [x] `bunx vitest run --root packages/engine src/services/chunkEncoder.test.ts` — 62/62 pass (new VP9 closed-GOP tests included) - [x] `bun test packages/producer/tests/distributed/_smoke/webm-concat-copy.test.ts` — passes - [x] `bunx oxlint` + `bunx oxfmt --check` on all changed files — clean - [x] `bunx tsc --noEmit -p packages/engine/tsconfig.json` — clean 🤖 Generated with [Claude Code](https://claude.com/claude-code)
This commit is contained in:
@@ -254,8 +254,58 @@ export function buildEncoderArgs(
|
||||
args.push("-c:v", "libvpx-vp9", "-b:v", bitrate || "0", "-crf", String(quality));
|
||||
args.push("-deadline", preset === "ultrafast" ? "realtime" : "good");
|
||||
args.push("-row-mt", "1");
|
||||
|
||||
// Closed-GOP args for distributed chunk concat-copy. Mirrors the
|
||||
// libx264/libx265 branch above: `lockGopForChunkConcat=true` lays a
|
||||
// keyframe at every chunk boundary so `ffmpeg -f concat -c copy` can
|
||||
// stitch sibling chunks losslessly.
|
||||
//
|
||||
// VP9-specific: `-auto-alt-ref 0` is mandatory. Alt-ref (a.k.a.
|
||||
// "ARNR") frames are non-displayable references libvpx-vp9 inserts
|
||||
// anywhere in the GOP for compression; they break concat-copy at
|
||||
// chunk seams because the boundary frame is no longer the first
|
||||
// displayable reference. The alpha branch below already disables
|
||||
// alt-ref for an unrelated reason (alpha + alt-ref is unsupported);
|
||||
// closed-GOP extends that to every pixel format.
|
||||
//
|
||||
// `-cpu-used 2` pins the libvpx-vp9 speed/quality tradeoff so chunks
|
||||
// encoded on workers with different default cpu-used values still
|
||||
// produce visually consistent output across seams. libvpx-vp9's
|
||||
// default with `-deadline good` has drifted across versions
|
||||
// historically — locking it makes the planHash round-trip
|
||||
// deterministic.
|
||||
const lockGopVp9 = options.lockGopForChunkConcat === true;
|
||||
if (lockGopVp9) {
|
||||
if (
|
||||
typeof options.gopSize !== "number" ||
|
||||
!Number.isFinite(options.gopSize) ||
|
||||
options.gopSize <= 0
|
||||
) {
|
||||
throw new Error(
|
||||
`[chunkEncoder] lockGopForChunkConcat=true requires a positive integer gopSize (received ${String(options.gopSize)})`,
|
||||
);
|
||||
}
|
||||
const gop = Math.floor(options.gopSize);
|
||||
args.push(
|
||||
"-g",
|
||||
String(gop),
|
||||
"-keyint_min",
|
||||
String(gop),
|
||||
"-auto-alt-ref",
|
||||
"0",
|
||||
"-cpu-used",
|
||||
"2",
|
||||
);
|
||||
}
|
||||
if (pixelFormat === "yuva420p") {
|
||||
args.push("-auto-alt-ref", "0");
|
||||
// Alpha + alt-ref is unsupported by libvpx-vp9. The closed-GOP
|
||||
// branch above already disables alt-ref; only push the flag for
|
||||
// the non-locked alpha case to keep the args list clean (a second
|
||||
// `-auto-alt-ref 0` is harmless but noisier in `ffmpeg -loglevel`
|
||||
// diagnostics).
|
||||
if (!lockGopVp9) {
|
||||
args.push("-auto-alt-ref", "0");
|
||||
}
|
||||
args.push("-metadata:s:v:0", "alpha_mode=1");
|
||||
}
|
||||
} else if (codec === "prores") {
|
||||
|
||||
Reference in New Issue
Block a user