Use the native backend¶
By default afmpeg runs the ffmpeg-wasi engine as a
sandboxed WebAssembly module: portable, arch-independent, single-threaded. For consumers who
are encode- or throughput-bound, afmpeg also has an opt-in native backend (spec
0028, "Backend B"): the same
libav-direct engine compiled to a native ELF, driven over an in-memory IPC bridge. It is
CGO-free (a subprocess, not a linked library), so afmpeg's Go package stays permissively
licensed, and all I/O still crosses your afero.Fs, never the host disk.
Reach for it when you want:
- Native-speed software encode: threads + SIMD make H.264/VP9/etc. ~50× faster with openh264 and ~170× with libx264 than the single-threaded WASM build. The encoder matters more than the FFmpeg version does (measured).
- HEVC or AV1 encode: the heavy
libx265/libsvtav1encoders are impractical in WASM, so they ship only in the native driver's full profile.
Platform
The native driver is currently published for linux/amd64 only. NewFromRelease resolves
the driver for the host GOOS/GOARCH; on other platforms use the WASM module (or build the
driver yourself). Hardware-accelerated encoders (NVENC/VAAPI/…) are a future addition to the
full profile, gated on device availability.
Certified native driver (recommended)¶
native.NewFromRelease is the native equivalent of
WithModuleRelease: it fetches the signed native driver for (tag,
variant) from a published ffmpeg-wasi release, verifies it through the same trust chain
(the KMS/OpenPGP signature over checksums.txt against afmpeg's pinned key, the asset checksum,
the provenance match) before the binary is ever written or executed, caches it as a
content-addressed executable, and returns a Backend. Wire that into afmpeg.New with
WithBackend:
import (
"gitlab.com/phpboyscout/afmpeg/pkg/afmpeg"
"gitlab.com/phpboyscout/afmpeg/pkg/afmpeg/native"
)
backend, err := native.NewFromRelease(ctx, "n9.0.1-3", afmpeg.VariantLGPL)
if err != nil { /* ... */ }
rt, err := afmpeg.New(ctx, afmpeg.WithBackend(backend))
// rt.RunJob / rt.Probe / rt.Frames — the API is identical to the WASM path.
A consumer never executes an unverified binary: fetch-and-verify happens up front, exactly
as for a .wasm. All the WithModuleRelease options apply
(WithReleaseBaseURL for a mirror, WithReleaseBundleDir for air-gapped verification,
WithReleaseCacheDir, WithReleaseHTTPClient, WithReleaseWKDEmail).
Profiles: lean, intermediate, or full¶
The native driver ships in all three capability
profiles; select one with
WithReleaseProfile (default ProfileLean):
| Profile | Adds over the previous | Asset |
|---|---|---|
ProfileLean |
H.264 encode (openh264; libx264 in gpl) at native speed | ffmpeg-wasi-driver-linux-amd64-<variant> |
ProfileIntermediate |
the full software batch: Opus/MP3/Vorbis/WebP/VP8-9 + subtitles, and AV1 decode (libdav1d) |
…-driver-linux-amd64-intermediate-<variant> |
ProfileFull |
AV1 encode (libsvtav1, both variants), HEVC encode (libx265, gpl only) |
…-driver-linux-amd64-full-<variant> |
AV1 decode (via libdav1d) is in the intermediate profile on both runtimes. The WASM
module decodes AV1 too (a single-threaded dav1d build; correct but slower than the threaded native
driver). AV1 encode needs the full profile (native only).
ProfileFull is the only way to reach HEVC/AV1 encode. HEVC (libx265) is GPL, so it is present
only in the gpl variant: the lgpl full driver encodes AV1 but rejects libx265. See the
HEVC/AV1 licence & patent posture.
// The full/gpl driver: HEVC (x265) + AV1 (SVT-AV1) at native speed.
backend, err := native.NewFromRelease(ctx, "n9.0.1-3", afmpeg.VariantGPL,
afmpeg.WithReleaseProfile(afmpeg.ProfileFull))
rt, err := afmpeg.New(ctx, afmpeg.WithBackend(backend))
cmd := afmpeg.Command{
Inputs: []afmpeg.Input{{Path: "in.mp4"}},
FilterComplex: "[0:v]format=yuv420p[v]",
Outputs: []afmpeg.Output{{
Path: "out.mp4", Map: []string{"[v]"},
VideoCodec: "libx265",
Options: map[string]string{"preset": "medium"},
}},
}
res, err := rt.RunJob(ctx, fs, cmd) // encodes HEVC over the IPC bridge, into fs
From a local driver binary¶
If you have already built or downloaded the driver (e.g. via
ffmpeg-wasi's build/Dockerfile.native),
point the backend straight at it, with no fetch and no verification (you supply the bytes, you accept
them):
backend := native.New(native.WithNativeBinary("/path/to/driver"))
rt, err := afmpeg.New(ctx, afmpeg.WithBackend(backend))
This is the bring-your-own path, the analogue of WithModuleFile. For the project's own
releases prefer NewFromRelease, which verifies the binary before it runs.
How it works¶
WithBackend swaps afmpeg's execution seam: instead of instantiating the WASM module in
wazero, the native backend spawns the driver as a subprocess and serves your afero.Fs to it
over a Unix socket (a framed read/write/seek protocol, so even a muxer's backward seeks,
like an MP4 moov patch, round-trip through the filesystem you passed, never host disk). The
job spec, the Command/Probe/Frames API, and the results are identical to the WASM
path. Only the runtime underneath changes. See the
architecture overview and spec
0028 for the full design.
Trust boundary
The native driver is a native subprocess, not a WASM sandbox, so it runs with your
process's privileges. Load it only from a source you trust: NewFromRelease gives you the
signature-verified project artifact; WithNativeBinary trusts whatever path you supply.
What you give up by switching¶
Three things stop working when you swap the WASM backend out, and none of them is obvious from the call site:
WithMemoryLimithas no effect. The cap is a wazero setting and there is no wazero. The driver is bounded by the operating system, like any other subprocess.WithTimeoutdoes still apply, becauseRunimposes it above the backend seam.- Engine progress goes quiet.
Frame,OutTimeandSpeedstay zero andFractionfalls back to the byte-observed source, because/dev/afmpeg-progressis served by the WASM backend only. - Portability. Drivers are published for linux/amd64 only; there is no automatic fallback to WASM on another platform, just a missing asset.
The full list is in limitations.