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. ~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 / 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, "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:

  • WithMemoryLimit has 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. WithTimeout does still apply, because Run imposes it above the backend seam.
  • Engine progress goes quiet. Frame, OutTime and Speed stay zero and Fraction falls back to the byte-observed source, because /dev/afmpeg-progress is 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.