Skip to content

Rust development guide

This page covers getting started with the VMAFX Rust bindings.

Overview

The repository ships a Rust workspace at the repo root (Cargo.toml, edition 2024). Its members fall into two groups.

Bindings that call libvmaf from Rust:

Crate Path Description
vmafx-sys bindings/rust/vmafx-sys Auto-generated raw FFI bindings plus thin safe wrappers.
vmafx bindings/rust/vmafx Higher-level safe API (Context, Model, Picture, Score; ADR-0929) over vmafx-sys.

Rust code linked into libvmaf (-Denable_rust_features=true), a separate workspace (core/src/rust/Cargo.toml, offline build) described in Rust extractor framework:

Crate Path Description
vmafx-core-rs core/src/rust/staticlib The one Rust archive libvmaf links.
vmafx-fex core/src/rust/fex Framework of the Rust twins of C extractors (ADR-1713).
vmafx-fex-psnr, -speed, -motion, -adm, -cambi core/src/rust/feature/* One twin per crate (psnr is the reference twin).
vmafx-predict core/src/rust/predict Model prediction (RC4).
vmafx-tad core/src/feature/rust/tad Rust pilot of the Temporal Absolute Difference feature extractor (ADR-0707).

The crates are versioned independently (currently 0.1.0) and are not published to crates.io from the release pipeline (ADR-1127, ADR-1151). Depend on them by path.

Quick start

  1. Build and install libvmaf (see libvmaf below).
  2. Provision the test clips with scripts/test/fetch-test-yuvs.sh.
  3. Run the scoring example:

    VMAFX_REPO=$(git rev-parse --show-toplevel) \
        LD_LIBRARY_PATH=/usr/local/lib \
        cargo run --example score
    

The example scores the Netflix golden pair and asserts the result; see Running the smoke test for the expected output.

Prerequisites

Rust toolchain

Install the stable toolchain via rustup:

curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
rustup component add rustfmt clippy

The workspace uses Rust edition 2024, which needs Rust 1.85 or newer. Any recent stable toolchain is sufficient.

libvmaf

vmafx-sys links against libvmaf.so at runtime and uses its headers at build time. You must have a compatible install of libvmaf before running cargo build.

# From the repo root:
meson setup build core -Denable_cuda=false -Denable_sycl=false
ninja -C build
sudo ninja -C build install   # installs to /usr/local by default
sudo ldconfig                 # refresh the dynamic linker cache

Verify the install:

pkg-config --modversion libvmaf  # the C API version (3.x), not the product version — ADR-1235
ls /usr/local/lib/libvmaf.so     # should exist

Option B — non-standard prefix

If you install to a prefix other than /usr/local, set:

export LIBVMAF_PREFIX=$HOME/.local
export LD_LIBRARY_PATH=$LIBVMAF_PREFIX/lib:$LD_LIBRARY_PATH

build.rs reads LIBVMAF_PREFIX to locate both the headers (used by bindgen) and the shared library (used by the linker). LD_LIBRARY_PATH is for the dynamic loader at run time; build.rs does not read it. If you also use pkg-config to inspect the install, add PKG_CONFIG_PATH=$LIBVMAF_PREFIX/lib/pkgconfig.

Using vmafx-sys as a dependency

Add to your crate's Cargo.toml:

[dependencies]
vmafx-sys = { path = "<path-to-repo>/bindings/rust/vmafx-sys" }

The crate is not published to crates.io; depend on it by path (or by git URL).

The safe vs sys API split

vmafx-sys exposes two layers.

Raw layer (use vmafx_sys::*)

Auto-generated by bindgen from libvmaf.h. Every C type, constant, and function is available. Use this layer when you need access to functionality not yet wrapped by the safe layer.

use std::ffi::CStr;
use vmafx_sys::vmaf_version;
let v = unsafe { CStr::from_ptr(vmaf_version()) };

Safe layer (use vmafx_sys::safe::*)

Thin RAII wrappers with four guarantees:

  • Lifetimes: VmafContext gets at most two native close attempts; VmafModel destroys on drop.
  • Errors: C status codes become Result. Context close accepts exact zero only, keeps the first error and permits one retry.
  • unsafe: confined to the FFI call sites.
  • Threads: contexts and close-retry tokens are !Send, so teardown stays on the thread that owns the native pipeline.

Context close is explicitly retryable: a nonzero native close result becomes a teardown-only token rather than discarding ownership. A failed explicit retry exhausts the token; dropping it aborts without a third native close. See Rust context close and retry for the public contract and failure-handling example.

use vmafx_sys::safe::{VmafContext, VmafModel, alloc_yuv420p_8bit};

let mut ctx = VmafContext::new()?;
let model = VmafModel::from_path("/usr/local/share/model/vmaf_v0.6.1.json")?;
ctx.use_features_from_model(&model)?;

// Queue frames...
let mut ref_pic = alloc_yuv420p_8bit(576, 324)?;
let mut dist_pic = alloc_yuv420p_8bit(576, 324)?;
// ... fill pic.data[0..3] with YUV plane bytes ...
ctx.read_pictures(ref_pic, dist_pic, 0)?;

ctx.flush()?;
let score = ctx.score_pooled(&model, 0, 0)?;

Building

# From repo root:
cargo build -p vmafx-sys

Running the smoke test

VMAFX_REPO=$(git rev-parse --show-toplevel) \
    LD_LIBRARY_PATH=/usr/local/lib \
    cargo run --example score

Expected output:

vmafx-sys version: 1.0.0-rc.2
Reference:  .../python/test/resource/yuv/src01_hrc00_576x324.yuv
Distorted:  .../python/test/resource/yuv/src01_hrc01_576x324.yuv
Model:      .../model/vmaf_v0.6.1.json
Frames processed: 48
Mean VMAF score:  76.6678
Score assertion PASSED (expected 76.6690)

The first line is the product version that vmaf_version() returns (1.0.0-rc.2 at the time of writing), not the C API version that pkg-config reports. The example reads up to 240 frames and stops at the end of the file; the golden pair has 48. The assertion tolerance is 5e-3 (places=3).

Running tests

VMAFX_REPO=$(git rev-parse --show-toplevel) \
    LD_LIBRARY_PATH=/usr/local/lib \
    cargo test --workspace --all-features

--workspace runs every crate's tests, as the vmafx-sys CI job does (ADR-1528): vmafx-sys, the safe vmafx binding (unit tests and tests/smoke.rs) and vmafx-tad. Use -p <crate> to run one of them.

The integration test (tests/integration_test.rs) scores the Netflix golden YUV pair and asserts the mean VMAF equals 76.669 within 5e-3 (places=3; the Python golden gate uses places=2 for this sequence). If the YUV files are not present (e.g. a CI environment without test fixtures), the test skips gracefully.

Linting

cargo fmt --all --check
cargo clippy --workspace --all-targets -- -D warnings

Both cover every workspace member (vmafx-sys, vmafx, vmafx-tad, and any crate added to the root Cargo.toml). CI (.github/workflows/rust-ci.yml) runs these two gates, the build, the tests, the golden smoke example and cargo-deny on every PR touching bindings/rust/ or the Rust workspace. The required contexts are vmafx-sys CI and cargo-deny.

Environment variables reference

Variable Default Description
LIBVMAF_PREFIX /usr/local libvmaf install prefix. Used by build.rs.
VMAFX_REPO auto-detected from CARGO_MANIFEST_DIR Repo root; used to resolve test fixtures.
VMAFX_YUV_REF <VMAFX_REPO>/python/test/resource/yuv/src01_hrc00_576x324.yuv Reference YUV override.
VMAFX_YUV_DIST <VMAFX_REPO>/python/test/resource/yuv/src01_hrc01_576x324.yuv Distorted YUV override.
VMAFX_MODEL <VMAFX_REPO>/model/vmaf_v0.6.1.json Model path override.

Troubleshooting

error: could not find native library 'vmaf'

The linker cannot find libvmaf.so. Fix:

sudo ldconfig
# or
export LD_LIBRARY_PATH=/usr/local/lib:$LD_LIBRARY_PATH

error: failed to run custom build command for vmafx-sys

bindgen failed to parse the header. Check that:

  1. LIBVMAF_PREFIX points to a directory containing include/libvmaf/libvmaf.h.
  2. clang / libclang-dev is installed.

cargo: error[E0425]: cannot find function ...

The installed libvmaf version is older than expected. Build from source (Option A above) rather than using the distro package.