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¶
- Build and install libvmaf (see libvmaf below).
- Provision the test clips with
scripts/test/fetch-test-yuvs.sh. -
Run the scoring example:
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:
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.
Option A — system install (recommended for development)¶
# 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:
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:
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.
Safe layer (use vmafx_sys::safe::*)¶
Thin RAII wrappers with four guarantees:
- Lifetimes:
VmafContextgets at most two native close attempts;VmafModeldestroys 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¶
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¶
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:
error: failed to run custom build command for vmafx-sys¶
bindgen failed to parse the header. Check that:
LIBVMAF_PREFIXpoints to a directory containinginclude/libvmaf/libvmaf.h.clang/libclang-devis 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.