Skip to content

Commit 27a218a

Browse files
kixelatedclaude
andcommitted
Initial commit: moq-vaapi vendored VA-API H.264 encoder
A self-contained, crates.io-publishable VA-API H.264 encoder for moq-video, vendored and trimmed from cros-libva + discord/cros-codecs (BSD-3-Clause). Extracted from the moq monorepo (rs/moq-vaapi) into its own repo so the ~14k lines of vendored code + the libva headers live outside the main tree, and so it can be published to crates.io (which moq-video needs in order to publish too). The build is hermetic: bindgen reads libva's headers checked into libva/ (refreshed by `just vendor`, pinned to VA-API 1.23) and libva is dlopen'd at runtime, so the crate compiles on any OS with only libclang -- no system libva-dev, and the binding can't drift when a system libva bumps (the failure that broke the old cros-libva git dep). Project setup mirrors moq: nix devShell (flake.nix), justfile (check/ci/fix/ vendor), .rustfmt.toml (hard tabs), cargo-deny, and a Check workflow that runs `nix develop --command just ci`. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
0 parents  commit 27a218a

83 files changed

Lines changed: 32298 additions & 0 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/check.yml

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
name: Check
2+
3+
permissions:
4+
id-token: write
5+
contents: read
6+
7+
on:
8+
pull_request:
9+
push:
10+
branches: [main]
11+
12+
# Cancel in-flight runs when a new commit lands on the same ref.
13+
concurrency:
14+
group: check-${{ github.ref }}
15+
cancel-in-progress: true
16+
17+
jobs:
18+
check:
19+
name: Check
20+
runs-on: ubuntu-latest
21+
22+
steps:
23+
- name: Checkout
24+
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
25+
with:
26+
persist-credentials: false
27+
28+
- uses: DeterminateSystems/nix-installer-action@1d87d45818068401a10cf16bdc5f00b24994a83f # main
29+
with:
30+
determinate: false
31+
- uses: DeterminateSystems/magic-nix-cache-action@908b263ff629f4cc17666315b7fd3ec127c6244d # main
32+
33+
- name: Rust Cache
34+
uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
35+
with:
36+
cache-on-failure: true
37+
38+
- run: nix develop --command just ci

.gitignore

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# Rust build output
2+
/target
3+
Cargo.lock
4+
5+
# nix
6+
/result
7+
/.direnv
8+
9+
# editors / OS
10+
.DS_Store
11+
.idea
12+
*.local

.rustfmt.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# i die on this hill
2+
hard_tabs = true
3+
4+
max_width = 120

Cargo.toml

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
[package]
2+
name = "moq-vaapi"
3+
version = "0.0.1"
4+
description = "VA-API H.264 hardware encoder, vendored and trimmed from cros-libva + discord/cros-codecs"
5+
authors = ["The ChromiumOS Authors", "Discord", "Luke Curley <kixelated@gmail.com>"]
6+
repository = "https://github.com/moq-dev/vaapi"
7+
license = "BSD-3-Clause"
8+
edition = "2021"
9+
rust-version = "1.85"
10+
11+
# The build is hermetic: bindgen reads the vendored libva headers checked into
12+
# `libva/` (refreshed by `just vendor`, pinned to a known VA-API version so a
13+
# system libva bump can't drift the bindings), and libva is dlopen'd at runtime
14+
# rather than linked (so a built binary links on a libva-less host and starts on
15+
# machines without libva). There is no system-libva build path, so there are no
16+
# features to toggle: the build needs only libclang (for bindgen) on any OS.
17+
18+
[dependencies]
19+
anyhow = "1"
20+
thiserror = "1"
21+
bitflags = "2.5"
22+
log = { version = "0", features = ["release_max_level_debug"] }
23+
24+
[build-dependencies]
25+
bindgen = "0.70.1"
26+
regex = "1.11.1"

LICENSE

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
Copyright 2022 The ChromiumOS Authors
2+
3+
Redistribution and use in source and binary forms, with or without modification,
4+
are permitted provided that the following conditions are met:
5+
6+
1. Redistributions of source code must retain the above copyright notice, this
7+
list of conditions and the following disclaimer.
8+
9+
2. Redistributions in binary form must reproduce the above copyright notice,
10+
this list of conditions and the following disclaimer in the documentation
11+
and/or other materials provided with the distribution.
12+
13+
3. Neither the name of the copyright holder nor the names of its contributors
14+
may be used to endorse or promote products derived from this software without
15+
specific prior written permission.
16+
17+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
18+
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19+
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
21+
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
22+
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
23+
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
24+
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25+
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
26+
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

LICENSE.cros-codecs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
Copyright 2022 The ChromiumOS Authors
2+
3+
Redistribution and use in source and binary forms, with or without modification,
4+
are permitted provided that the following conditions are met:
5+
6+
1. Redistributions of source code must retain the above copyright notice, this
7+
list of conditions and the following disclaimer.
8+
9+
2. Redistributions in binary form must reproduce the above copyright notice,
10+
this list of conditions and the following disclaimer in the documentation
11+
and/or other materials provided with the distribution.
12+
13+
3. Neither the name of the copyright holder nor the names of its contributors
14+
may be used to endorse or promote products derived from this software without
15+
specific prior written permission.
16+
17+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
18+
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19+
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
21+
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
22+
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
23+
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
24+
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25+
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
26+
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

LICENSE.libva

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
Permission is hereby granted, free of charge, to any person obtaining a
2+
copy of this software and associated documentation files (the
3+
"Software"), to deal in the Software without restriction, including
4+
without limitation the rights to use, copy, modify, merge, publish,
5+
distribute, sub license, and/or sell copies of the Software, and to
6+
permit persons to whom the Software is furnished to do so, subject to
7+
the following conditions:
8+
9+
The above copyright notice and this permission notice (including the
10+
next paragraph) shall be included in all copies or substantial portions
11+
of the Software.
12+
13+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
14+
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
15+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT.
16+
IN NO EVENT SHALL PRECISION INSIGHT AND/OR ITS SUPPLIERS BE LIABLE FOR
17+
ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
18+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
19+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

README.md

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
# moq-vaapi
2+
3+
A small, self-contained **VA-API H.264 hardware encoder** for Linux (Intel / AMD),
4+
vendored and trimmed from [cros-libva](https://github.com/intel/cros-libva) and
5+
[discord/cros-codecs](https://github.com/discord/cros-codecs) (BSD-3-Clause).
6+
7+
It exists to give [moq](https://github.com/moq-dev/moq)'s `moq-video` a thin,
8+
crates.io-publishable VA-API encoder brick instead of a multi-backend framework
9+
pulled in as a git dependency.
10+
11+
## What it does
12+
13+
- **H.264 encode** over VA-API: tightly-packed NV12 in, an Annex-B elementary
14+
stream out (packed SPS/PPS + slice headers, low-latency IPPP, rate control).
15+
- **Hermetic build.** libva's headers are vendored into [`libva/`](./libva) (see
16+
`just vendor`) and fed to bindgen, so the build needs **no system libva-dev**
17+
only `libclang`. The header version is pinned, so a system libva bump can't
18+
drift the generated bindings.
19+
- **Runtime `dlopen`.** libva is loaded at runtime rather than linked, so a built
20+
binary links on a libva-less builder and starts on machines without libva
21+
(callers fall back to a software encoder).
22+
23+
## Layout
24+
25+
- `src/` — libva bindings (`bindings`, `display`, `surface`, `buffer`, ...), the
26+
H.264 bitstream layer (`bitstream_utils`, `codec::h264`), and the thin encode
27+
driver (`encode`).
28+
- `libva/` — vendored libva headers (checked in; refreshed by `just vendor`).
29+
- `build.rs` + `bindgen_gen.rs` + `libva-wrapper.h` — bindgen setup.
30+
31+
## Development
32+
33+
Recipes run inside the nix devShell:
34+
35+
```sh
36+
nix develop --command just check # clippy + fmt
37+
nix develop --command just ci # check + cargo-deny
38+
nix develop --command just vendor # refresh libva/ headers from upstream
39+
```
40+
41+
The crate compiles on any OS (macOS included) because the build is header-only +
42+
dlopen; runtime use is Linux-only.
43+
44+
## Licensing
45+
46+
BSD-3-Clause (see [`LICENSE`](./LICENSE)). Vendored upstreams keep their notices:
47+
[`LICENSE.cros-codecs`](./LICENSE.cros-codecs) and the vendored libva headers
48+
under [`LICENSE.libva`](./LICENSE.libva) (MIT).

bindgen_gen.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// Copyright 2022 The ChromiumOS Authors
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
/// The allow list of VA functions, structures and enum values.
6+
const ALLOW_LIST_TYPE : &str = ".*ExternalBuffers.*|.*PRIME.*|.*MPEG2.*|.*VP8.*|.*VP9.*|.*H264.*|.*HEVC.*|.*JPEG.*|VACodedBufferSegment|.*AV1.*|VAEncMisc.*|VASurfaceDecodeMBErrors|VADecodeErrorType|.*VAProc.*|VAEncPacked.*";
7+
8+
// The common bindgen builder for VA-API.
9+
pub fn vaapi_gen_builder(builder: bindgen::Builder) -> bindgen::Builder {
10+
builder
11+
.derive_default(true)
12+
.derive_eq(true)
13+
.layout_tests(false)
14+
.constified_enum_module("VA.*")
15+
.allowlist_var("VA.*")
16+
.allowlist_function("va.*")
17+
.allowlist_type(ALLOW_LIST_TYPE)
18+
}

build.rs

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
// Copyright 2022 The ChromiumOS Authors
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
// Vendored verbatim from cros-libva (BSD-3-Clause); not subject to strict lints.
6+
#![allow(clippy::all)]
7+
8+
use regex::Regex;
9+
use std::env::{self};
10+
use std::fs::read_to_string;
11+
use std::path::{Path, PathBuf};
12+
13+
mod bindgen_gen;
14+
use bindgen_gen::vaapi_gen_builder;
15+
16+
/// Wrapper file to use as input of bindgen.
17+
const WRAPPER_PATH: &str = "libva-wrapper.h";
18+
19+
/// Generate `va_version.h` from the vendored `va_version.h.in` template by
20+
/// extracting version numbers from `meson.build`. The `libva/` headers are
21+
/// checked into the repo and refreshed by `just vendor`.
22+
fn generate_vendored_version_header(out_dir: &Path) -> (u32, u32) {
23+
let meson_build =
24+
read_to_string("libva/meson.build").expect("failed to read libva/meson.build — run `just vendor`");
25+
26+
// Extract va_api_{major,minor,micro}_version from meson.build
27+
let extract = |var_name: &str| -> String {
28+
let re = Regex::new(&format!(r"{}\s*=\s*(\d+)", var_name)).unwrap();
29+
re.captures(&meson_build)
30+
.unwrap_or_else(|| panic!("{} not found in libva/meson.build", var_name))[1]
31+
.to_string()
32+
};
33+
34+
let major = extract("va_api_major_version");
35+
let minor = extract("va_api_minor_version");
36+
let micro = extract("va_api_micro_version");
37+
let version = format!("{}.{}.{}", major, minor, micro);
38+
39+
let template = read_to_string("libva/va/va_version.h.in").expect("failed to read libva/va/va_version.h.in");
40+
41+
let generated = template
42+
.replace("@VA_API_MAJOR_VERSION@", &major)
43+
.replace("@VA_API_MINOR_VERSION@", &minor)
44+
.replace("@VA_API_MICRO_VERSION@", &micro)
45+
.replace("@VA_API_VERSION@", &version);
46+
47+
let va_dir = out_dir.join("va");
48+
std::fs::create_dir_all(&va_dir).expect("failed to create va dir in OUT_DIR");
49+
std::fs::write(va_dir.join("va_version.h"), generated).expect("failed to write va_version.h");
50+
51+
// libva keeps va_drm.h under va/drm/ in its source tree, but installs it (and
52+
// the wrapper includes it) as <va/va_drm.h>. Stage it next to the generated
53+
// va_version.h so the OUT_DIR include path resolves <va/va_drm.h>.
54+
std::fs::copy("libva/va/drm/va_drm.h", va_dir.join("va_drm.h")).expect("failed to stage va/drm/va_drm.h");
55+
56+
(
57+
major.parse().expect("invalid major version"),
58+
minor.parse().expect("invalid minor version"),
59+
)
60+
}
61+
62+
fn main() {
63+
// Do not require dependencies when generating docs.
64+
if std::env::var("CARGO_DOC").is_ok() || std::env::var("DOCS_RS").is_ok() {
65+
return;
66+
}
67+
68+
let out_dir = PathBuf::from(env::var("OUT_DIR").expect("`OUT_DIR` is not set"));
69+
70+
// Always build against the vendored libva headers (checked into `libva/`,
71+
// pinned to a known VA-API version) and dlopen libva at runtime rather than
72+
// link it. There is no system-libva path, so the build needs only libclang for
73+
// bindgen and works on any OS.
74+
let (major, minor) = generate_vendored_version_header(&out_dir);
75+
println!("cargo:rerun-if-changed=libva/meson.build");
76+
println!("cargo:rerun-if-changed=libva/va/va_version.h.in");
77+
78+
// Two include paths: the vendored `libva/` root so `#include <va/va.h>`
79+
// resolves to `libva/va/va.h`, and OUT_DIR for the generated
80+
// `<va/va_version.h>` and the staged `<va/va_drm.h>`.
81+
let manifest_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").expect("`CARGO_MANIFEST_DIR` is not set"));
82+
let va_h_path = manifest_dir.join("libva");
83+
assert!(
84+
va_h_path.join("meson.build").exists(),
85+
"{} is missing the vendored libva headers — run `just vendor`",
86+
va_h_path.display()
87+
);
88+
89+
println!("libva {}.{} is used to generate bindings", major, minor);
90+
let va_check_version = |desired_major: u32, desired_minor: u32| {
91+
major > desired_major || (major == desired_major && minor >= desired_minor)
92+
};
93+
94+
// Declare the custom cfg flags to avoid warnings
95+
println!("cargo::rustc-check-cfg=cfg(libva_1_21_or_higher)");
96+
println!("cargo::rustc-check-cfg=cfg(libva_1_20_or_higher)");
97+
println!("cargo::rustc-check-cfg=cfg(libva_1_19_or_higher)");
98+
println!("cargo::rustc-check-cfg=cfg(libva_1_16_or_higher)");
99+
println!("cargo::rustc-check-cfg=cfg(libva_1_15_or_higher)");
100+
println!("cargo::rustc-check-cfg=cfg(libva_1_14_or_higher)");
101+
println!("cargo::rustc-check-cfg=cfg(libva_1_10_or_higher)");
102+
103+
// Set the cfg flags based on version
104+
if va_check_version(1, 21) {
105+
println!("cargo::rustc-cfg=libva_1_21_or_higher");
106+
}
107+
if va_check_version(1, 20) {
108+
println!("cargo::rustc-cfg=libva_1_20_or_higher")
109+
}
110+
if va_check_version(1, 19) {
111+
println!("cargo::rustc-cfg=libva_1_19_or_higher")
112+
}
113+
if va_check_version(1, 16) {
114+
println!("cargo::rustc-cfg=libva_1_16_or_higher")
115+
}
116+
if va_check_version(1, 15) {
117+
println!("cargo::rustc-cfg=libva_1_15_or_higher");
118+
}
119+
if va_check_version(1, 14) {
120+
println!("cargo::rustc-cfg=libva_1_14_or_higher");
121+
}
122+
if va_check_version(1, 10) {
123+
println!("cargo::rustc-cfg=libva_1_10_or_higher");
124+
}
125+
126+
let bindings = vaapi_gen_builder(bindgen::builder())
127+
.header(WRAPPER_PATH)
128+
.clang_arg(format!("-I{}", va_h_path.display()))
129+
.clang_arg(format!("-I{}", out_dir.display()))
130+
.generate()
131+
.expect("unable to generate bindings");
132+
133+
bindings
134+
.write_to_file(out_dir.join("bindings.rs"))
135+
.expect("Couldn't write bindings!");
136+
}

0 commit comments

Comments
 (0)