Skip to content

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. 48–58× faster than the single-threaded WASM build.
  • HEVC or AV1 encode — the heavy libx265 / libsvtav1 encoders 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.

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, "n8.1.2-10", 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, "n8.1.2-10", 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 — no fetch, 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 — 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.