fix(engine): support AMD AMF GPU encoding

This commit is contained in:
Miguel Ángel
2026-05-26 13:35:55 -04:00
parent 9a7b3efa94
commit 0e052e42d2
12 changed files with 293 additions and 24 deletions
@@ -256,6 +256,28 @@ describe("buildEncoderArgs GPU preset mapping", () => {
);
expect(presetArg(args)).toBe("medium");
});
it("uses AMD AMF encoder names and quality flags when selected", () => {
const h264Args = buildEncoderArgs(
{ ...baseOptions, codec: "h264", preset: "medium", quality: 23, useGpu: true },
inputArgs,
"out.mp4",
"amf",
);
expect(h264Args[h264Args.indexOf("-c:v") + 1]).toBe("h264_amf");
expect(h264Args[h264Args.indexOf("-qp_i") + 1]).toBe("23");
expect(h264Args).toContain("-bf");
expect(h264Args[h264Args.indexOf("-bf") + 1]).toBe("0");
const h265Args = buildEncoderArgs(
{ ...baseOptions, codec: "h265", preset: "medium", quality: 23, useGpu: true },
inputArgs,
"out.mp4",
"amf",
);
expect(h265Args[h265Args.indexOf("-c:v") + 1]).toBe("hevc_amf");
expect(h265Args[h265Args.indexOf("-qp_i") + 1]).toBe("23");
});
});
describe("buildEncoderArgs color space", () => {
@@ -529,7 +551,7 @@ describe("buildEncoderArgs lockGopForChunkConcat", () => {
it("true is a no-op on GPU encoders", () => {
// GPU encoders take a separate code path; lockGopForChunkConcat does not
// wire `-g` / `-keyint_min` into nvenc/qsv/vaapi.
// wire `-g` / `-keyint_min` into nvenc/amf/qsv/vaapi.
const args = buildEncoderArgs(
{
...baseOptions,
@@ -816,7 +838,7 @@ describe("buildEncoderArgs HDR color space", () => {
});
it("tags BT.2020 + transfer for HDR GPU H.265 (no mastering metadata via -x265-params)", () => {
// GPU encoders (nvenc, videotoolbox, qsv, vaapi) still emit the BT.2020
// GPU encoders (nvenc, videotoolbox, amf, qsv, vaapi) still emit the BT.2020
// color tags via the codec-level -colorspace/-color_primaries/-color_trc
// flags, but cannot accept x265-params, so HDR static mastering metadata
// (master-display, max-cll) is not embedded. Acceptable for previews,
+9 -2
View File
@@ -140,9 +140,13 @@ export function buildEncoderArgs(
if (bitrate) args.push("-b:v", bitrate);
else args.push("-global_quality", String(quality));
break;
case "amf":
if (bitrate) args.push("-b:v", bitrate);
else args.push("-rc", "cqp", "-qp_i", String(quality), "-qp_p", String(quality));
break;
}
// Same B-frame story as the SW branch below — nvenc emits B-frames
// Same B-frame story as the SW branch below — nvenc/amf emit B-frames
// by default (qsv via b_strategy, vaapi too), and the negative-DTS
// freeze hits the same downstream players. The unconditional
// `-avoid_negative_ts make_zero` near the bottom of this function
@@ -153,7 +157,10 @@ export function buildEncoderArgs(
// negative DTS in practice on macOS Sonoma+.
if (
codec === "h264" &&
(gpuEncoder === "nvenc" || gpuEncoder === "qsv" || gpuEncoder === "vaapi")
(gpuEncoder === "nvenc" ||
gpuEncoder === "qsv" ||
gpuEncoder === "vaapi" ||
gpuEncoder === "amf")
) {
args.push("-bf", "0");
if (gpuEncoder === "qsv") {
@@ -265,6 +265,26 @@ describe("buildStreamingArgs", () => {
const args = buildStreamingArgs({ ...baseGpu, preset: "medium" }, "/tmp/out.mp4", "qsv");
expect(presetArg(args)).toBe("medium");
});
it("uses AMD AMF encoder names and quality flags when selected", () => {
const h264Args = buildStreamingArgs(
{ ...baseGpu, preset: "medium", quality: 23 },
"/tmp/out.mp4",
"amf",
);
expect(h264Args[h264Args.indexOf("-c:v") + 1]).toBe("h264_amf");
expect(h264Args[h264Args.indexOf("-qp_i") + 1]).toBe("23");
expect(h264Args).toContain("-bf");
expect(h264Args[h264Args.indexOf("-bf") + 1]).toBe("0");
const h265Args = buildStreamingArgs(
{ ...baseGpu, codec: "h265", preset: "medium", quality: 23 },
"/tmp/out.mp4",
"amf",
);
expect(h265Args[h265Args.indexOf("-c:v") + 1]).toBe("hevc_amf");
expect(h265Args[h265Args.indexOf("-qp_i") + 1]).toBe("23");
});
});
});
@@ -231,14 +231,21 @@ export function buildStreamingArgs(
if (bitrate) args.push("-b:v", bitrate);
else args.push("-global_quality", String(quality));
break;
case "amf":
if (bitrate) args.push("-b:v", bitrate);
else args.push("-rc", "cqp", "-qp_i", String(quality), "-qp_p", String(quality));
break;
}
// Mirror SW branch: GPU h264 paths emit B-frames by default (nvenc, qsv,
// vaapi) and produce the same negative-DTS freeze for downstream players.
// Mirror SW branch: GPU h264 paths emit B-frames by default (nvenc, amf,
// qsv, vaapi) and produce the same negative-DTS freeze for downstream players.
// See chunkEncoder.buildEncoderArgs for the full explanation.
if (
codec === "h264" &&
(gpuEncoder === "nvenc" || gpuEncoder === "qsv" || gpuEncoder === "vaapi")
(gpuEncoder === "nvenc" ||
gpuEncoder === "qsv" ||
gpuEncoder === "vaapi" ||
gpuEncoder === "amf")
) {
args.push("-bf", "0");
if (gpuEncoder === "qsv") {