Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 18 additions & 13 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,25 @@ categories = ["multimedia::encoding", "hardware-support", "external-ffi-bindings
edition = "2021"
rust-version = "1.85"

# Dev-only tooling; not needed to build the crate from the published tarball.
exclude = ["flake.nix", "flake.lock", "justfile", "deny.toml", "rust-toolchain.toml", ".rustfmt.toml"]
# Dev-only tooling and the headers `just bindgen` reads; not needed to build the
# crate from the published tarball.
exclude = [
"flake.nix",
"flake.lock",
"justfile",
"deny.toml",
"rust-toolchain.toml",
".rustfmt.toml",
"libva/",
"libva-wrapper.h",
]

# bindgen reads the vendored libva headers checked into `libva/` (refreshed by
# `just vendor`, pinned to a known VA-API version so a system libva bump can't
# drift the bindings). libva is not linked: bindgen's dynamic-loading mode emits a
# `Va` struct that dlopen's libva.so at runtime (see bindgen_gen.rs), so the build
# needs only libclang + the vendored headers, the binary carries no NEEDED libva
# (a libva-less host loads and lets the caller fall back), and the crate builds on
# any OS (macOS, docs.rs) without libva installed.
# The libva bindings are generated from the vendored headers in `libva/` (pinned
# to a known VA-API version, so a system libva bump can't drift them) by `just
# bindgen`, and checked in: the build needs no libclang, no libva, and no build
# script. libva is not linked either: the bindings' `Va` struct dlopen's it at
# runtime, so the binary carries no NEEDED libva (a libva-less host loads and
# lets the caller fall back), and the crate builds on any OS (macOS, docs.rs).

[dependencies]
anyhow = "1"
Expand All @@ -29,7 +38,3 @@ bitflags = "2.5"
log = "0"
# Runtime dlopen of libva; the bindgen `Va` struct is built on libloading::Library.
libloading = "0.8"

[build-dependencies]
bindgen = "0.70.1"
regex = "1.11.1"
23 changes: 15 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,15 @@ pulled in as a git dependency.
Meteor Lake (iHD 26.1.5) for constrained baseline, main with B-frames, high at
720p, and a cropped non-macroblock-aligned size. The main-with-B-frames case
runs as a test wherever ffmpeg and a VA-API device are both present.
- **Pinned, vendored headers.** libva's headers are vendored into
[`libva/`](./libva) (see `just vendor`) and fed to bindgen, so generating the
bindings needs **no system libva-dev** — only `libclang` — and the pinned
version means a system libva bump can't drift the bindings.
- **Checked-in bindings from pinned headers.** libva's headers are vendored
into [`libva/`](./libva) (see `just vendor`), and `just bindgen` generates
[`src/bindings/libva.rs`](./src/bindings/libva.rs) from them. The output is
checked in, so building the crate needs **no libclang, no libva-dev, and no
build script**, and the pinned version means a system libva bump can't drift
the bindings. The file is portable: fixed-width C types, no layout tests, and
the two VPP structs whose padding libva sizes by pointer width carry both
layouts under a `cfg`. `just ci` fails if the file is not what the headers
generate.
- **Loads system libva at runtime on Linux.** The generated `Va` function table
loads `libva` / `libva-drm` with `libloading`. Building needs no system libva,
and a binary can start without it; opening a display reports a missing driver
Expand All @@ -70,7 +75,7 @@ This run checked all five cros-libva commits and the one cros-codecs commit:
| --- | --- |
| cros-libva [`838808ce`](https://github.com/chromeos/cros-libva/commit/838808cebfeff64f5c3543c873dbc604857b3702), protected-content headers | Not needed: no protected-content consumer or Android build in this crate. |
| cros-libva [`89454d66`](https://github.com/chromeos/cros-libva/commit/89454d660411627a4a463116fdea00ac1ac888bc), explicit image lifetimes | Already present in `Image::create_from` and `Picture::{derive_image,create_image}`. |
| cros-libva [`9f3c0d92`](https://github.com/chromeos/cros-libva/commit/9f3c0d9264ec04b4fa792764178b879341ce33b1), packed-header bindings | Already present in `bindgen_gen.rs`, `buffer.rs`, and the H.264 encoder. |
| cros-libva [`9f3c0d92`](https://github.com/chromeos/cros-libva/commit/9f3c0d9264ec04b4fa792764178b879341ce33b1), packed-header bindings | Already present in the `bindgen` allowlist, `buffer.rs`, and the H.264 encoder. |
| cros-libva [`ed9c63b7`](https://github.com/chromeos/cros-libva/commit/ed9c63b743ce9490e1079a8b0c1a24b8e879324f), borrowed input buffers | Optional copy avoidance, not a correctness fix. Keep the existing owned-buffer API; no new public API or optimization without measurement. |
| cros-libva [`59384456`](https://github.com/chromeos/cros-libva/commit/59384456ac2ae78c0c3e5515f41ef1efd9b802cf), libva 2.23 VP9 fields | Build compatibility already covered by `..Default::default()` in the retained VP9 wrapper against pinned 2.23 headers. The codec drivers support H.264 only, so no VP9 segmentation control is needed. |
| cros-codecs [`a050c6cd`](https://github.com/chromeos/cros-codecs/commit/a050c6cde530e563197ee9e3e72ab01f48a0c41f), release log filtering | Ported: remove `log/release_max_level_debug`, which conflicts with a consumer selecting another release level. `just ci` checks feature unification with `log/release_max_level_trace`. |
Expand All @@ -91,16 +96,18 @@ advisories checked, not a guarantee that no undisclosed vulnerabilities exist.
and decode drivers (`encode`, `decode`), DMA-BUF import (`dmabuf`), video
post-processing (`vpp`), and the color space both share (`color`).
- `libva/` — vendored libva headers (checked in; refreshed by `just vendor`).
- `build.rs` + `bindgen_gen.rs` + `libva-wrapper.h` — bindgen setup.
- `libva-wrapper.h` + the `bindgen` recipe in `justfile` — what generates
`src/bindings/libva.rs`.

## Development

Recipes run inside the nix devShell:

```sh
nix develop --command just check # clippy + fmt
nix develop --command just ci # check + cargo-deny
nix develop --command just vendor # refresh libva/ headers from upstream
nix develop --command just ci # check + tests + bindings drift + cargo-deny
nix develop --command just vendor # refresh libva/ headers from upstream, then the bindings
nix develop --command just bindgen # regenerate src/bindings/libva.rs
```

The crate compiles on macOS for development, with no link-time dependency on
Expand Down
25 changes: 0 additions & 25 deletions bindgen_gen.rs

This file was deleted.

140 changes: 0 additions & 140 deletions build.rs

This file was deleted.

11 changes: 3 additions & 8 deletions flake.nix
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
description = "moq-vaapi - VA-API H.264 encoder (vendored libva)";
description = "moq-vaapi - VA-API hardware video codecs (vendored libva)";

inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
Expand Down Expand Up @@ -44,19 +44,14 @@
just
git
cargo-deny
# bindgen needs libclang to parse the vendored libva headers.
clang
# `just bindgen` regenerates the checked-in bindings; builds need neither it nor libclang.
rust-bindgen
]
++ pkgs.lib.optionals (!pkgs.stdenv.isDarwin) [
# libva is dlopen'd at runtime (not linked at build), so this is only
# needed to actually run vaapi encode in the devShell. macOS has no VAAPI.
libva
];

# bindgen locates libclang via this.
shellHook = ''
export LIBCLANG_PATH="${pkgs.libclang.lib}/lib"
'';
};

formatter = pkgs.nixfmt-tree;
Expand Down
69 changes: 65 additions & 4 deletions justfile
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,12 @@ check:
cargo clippy --all-targets -- -D warnings
cargo fmt --all --check

# Full CI: check, unit tests, and dependency hygiene.
# Full CI: check, unit tests, bindings drift, and dependency hygiene.
ci:
just check
# The checked-in bindings must be what the vendored headers generate.
just bindgen
git diff --exit-code src/bindings/libva.rs
cargo test --lib
# A consumer must be able to choose its own release logging level.
cargo check --lib --features log/release_max_level_trace
Expand All @@ -28,9 +31,8 @@ fix:
cargo clippy --fix --allow-staged --allow-dirty --all-targets
cargo fmt --all

# Refresh the vendored libva headers in `libva/` from upstream at {{ LIBVA_TAG }}.
# We check the headers in (rather than a submodule) so the crate is a
# self-contained, crates.io-publishable tarball. Only the files bindgen needs are
# Refresh the vendored libva headers in `libva/` from upstream at {{ LIBVA_TAG }},
# then regenerate the bindings from them. Only the files bindgen needs are
# copied: meson.build (version), va/*.h{,.in}, and va/drm/va_drm.h.
vendor:
#!/usr/bin/env bash
Expand All @@ -45,3 +47,62 @@ vendor:
cp "$tmp/va/drm/va_drm.h" libva/va/drm/va_drm.h
cp "$tmp/COPYING" LICENSE.libva
echo "vendored libva {{ LIBVA_TAG }} into libva/ ($(find libva -type f | wc -l | tr -d ' ') files)"
just bindgen

# Regenerate src/bindings/libva.rs from the vendored headers in `libva/`.
# Builds need neither this nor libclang: the output is checked in, and the C
# types it uses are fixed-width or symbolic (`c_char`), so one file serves every
# target. The exception is libva's own: two VPP structs pad to a constant size
# with a reserved array whose length depends on the pointer width, so both
# layouts are generated and the lines that differ are kept under a `cfg`.
bindgen:
#!/usr/bin/env bash
set -euo pipefail
work=$(mktemp -d)
trap 'rm -rf "$work"' EXIT
# libva generates va_version.h from a template at build time, and installs
# va_drm.h beside va.h; stage both where `#include <va/...>` finds them.
mkdir -p "$work/va"
version() { sed -nE "s/^$1\s*=\s*([0-9]+).*/\1/p" libva/meson.build; }
major=$(version va_api_major_version)
minor=$(version va_api_minor_version)
micro=$(version va_api_micro_version)
sed -e "s/@VA_API_MAJOR_VERSION@/$major/" -e "s/@VA_API_MINOR_VERSION@/$minor/" \
-e "s/@VA_API_MICRO_VERSION@/$micro/" -e "s/@VA_API_VERSION@/$major.$minor.$micro/" \
libva/va/va_version.h.in > "$work/va/va_version.h"
cp libva/va/drm/va_drm.h "$work/va/va_drm.h"
# `--dynamic-loading` emits a `Va` struct of function pointers that
# libloading resolves at runtime, rather than link-time externs, so libva is
# dlopen'd. A symbol a system libva older than these headers lacks errors
# only when called, not when the library loads. Freestanding, so the host's
# C library cannot change the output.
generate() {
bindgen libva-wrapper.h \
--rust-target 1.85 \
--rust-edition 2021 \
--no-layout-tests \
--with-derive-default \
--with-derive-eq \
--constified-enum-module 'VA.*' \
--allowlist-var 'VA.*' \
--allowlist-function 'va.*' \
--allowlist-type '.*ExternalBuffers.*|.*PRIME.*|.*MPEG2.*|.*VP8.*|.*VP9.*|.*H264.*|.*HEVC.*|.*JPEG.*|VACodedBufferSegment|.*AV1.*|VAEncMisc.*|VASurfaceDecodeMBErrors|VADecodeErrorType|.*VAProc.*|VAEncPacked.*' \
--dynamic-loading Va \
--output "$2" \
-- -Ilibva -I"$work" -ffreestanding --target="$1"
}
generate x86_64-unknown-linux-gnu "$work/wide.rs"
generate i686-unknown-linux-gnu "$work/narrow.rs"
# Only the reserved padding may differ; anything else needs a look before
# it is papered over with a cfg.
unexpected=$(diff "$work/wide.rs" "$work/narrow.rs" | grep -E '^[<>]' | grep -vE '^[<>]\s*pub va_reserved: \[u(16|32); [0-9]+usize\],$' || true)
if [ -n "$unexpected" ]; then
printf 'the bindings differ by pointer width in more than libva'"'"'s reserved padding:\n%s\n' "$unexpected" >&2
exit 1
fi
# The condition libva's headers pad on: x86_64, or LP64.
wide='any(target_arch = "x86_64", all(target_pointer_width = "64", not(windows)))'
diff --unchanged-line-format='%L' \
--old-line-format=$'\t#[cfg('"$wide"$')]\n%L' \
--new-line-format=$'\t#[cfg(not('"$wide"$'))]\n%L' \
"$work/wide.rs" "$work/narrow.rs" > src/bindings/libva.rs || [ $? -eq 1 ]
4 changes: 3 additions & 1 deletion src/bindings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@
// bare URLs and other rustdoc-unfriendly markup. Don't lint generated docs.
#![allow(rustdoc::all)]

include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
// Generated from the vendored headers by `just bindgen` and checked in, so a
// build needs neither bindgen nor libclang.
include!("bindings/libva.rs");

use std::sync::OnceLock;

Expand Down
Loading
Loading