Skip to content

feat(moq-video): native V4L2 capture, NVENC via dlopen, drop VAAPI + nokhwa(Linux)#1704

Merged
kixelated merged 8 commits into
devfrom
claude/strange-cohen-f7dbba
Jun 13, 2026
Merged

feat(moq-video): native V4L2 capture, NVENC via dlopen, drop VAAPI + nokhwa(Linux)#1704
kixelated merged 8 commits into
devfrom
claude/strange-cohen-f7dbba

Conversation

@kixelated

@kixelated kixelated commented Jun 13, 2026

Copy link
Copy Markdown
Collaborator

moq-video native-capture/codec overhaul, continuing the drop-the-abstraction-crates effort (ffmpeg already gone). Compile-verified on Linux via podman; runtime-validated where possible without a GPU.

Changes

NVENC via dlopen — portable + shippable. NVENC now dlopens everything at runtime (cudarc/fallback-dynamic-loading for libcuda; nvidia-video-codec-sdk/dynamic-loading for libnvidia-encode — a small fork feature, see root [patch.crates-io]). So an --features nvenc binary links on a GPU-less builder and loads on machines without the NVIDIA driver, falling back to software. Verified: readelf -d shows no libnvidia-encode/libcuda DT_NEEDED, and cargo test --features nvenc passes on a GPU-less box. cudarc bumped 0.16 → 0.19. This is the prerequisite for shipping capture/encode in a portable moq-cli.

Native V4L2 capture, replacing nokhwa on Linux. capture/v4l2.rs streams MMAP buffers via the v4l crate, negotiates YUYV or MJPEG, and converts to CPU I420 (MJPEG decoded with pure-Rust zune-jpeg — no IJG/libjpeg). nokhwa is now Windows-only (until a native Media Foundation capture lands); macOS already uses AVFoundation/ScreenCaptureKit.

Drop VAAPI (cros-codecs). cros-codecs 0.0.6 / cros-libva 0.0.12 don't compile against libva ≥ 2.23 (modern distros + nixpkgs). Removed the backend + its dmabuf capture; parked until cros-codecs supports cros-libva ≥ 0.0.13. Intel/AMD Linux uses openh264 meanwhile.

CI. just rs ci builds + tests moq-video with real --all-features on Linux (dlopen needs no GPU/driver/flags); excluded from the workspace --all-features only because the NVENC SDK has no macOS bindings. IJG allowed in deny.toml (mozjpeg via nokhwa, Windows), plus the fork git source. Includes a merge of dev for the libmoq test fix (#1710).

Test plan

  • Linux (aarch64, podman): clippy -D warnings default + --all-features; cargo test -p moq-video --features nvenc (7 pass, NVENC falls back); readelf confirms no driver DT_NEEDED
  • macOS default-feature clippy; cargo fmt/sort/shear/doc/deny green
  • Real hardware (not in CI): V4L2 capture vs a real /dev/video*; NVENC encode on a Linux+GPU box

Follow-ups (DESIGN-native-codecs.md)

  • Upstream the nvidia-video-codec-sdk dynamic-loading feature; drop the fork patch.
  • Native Windows (Media Foundation) capture to finish removing nokhwa.
  • Re-introduce VAAPI once cros-codecs supports modern libva.

(Written by Claude)

kixelated and others added 2 commits June 12, 2026 20:12
…erage

Continues the native-codecs work from #1691 by writing the last missing
piece (the V4L2 dmabuf capture) and making CI actually compile the
Linux-only hardware backends so they can't silently rot.

Capture: new src/capture/v4l2.rs exports NV12 MMAP buffers as persistent
dmabufs (REQBUFS -> EXPBUF -> QBUF/STREAMON -> DQBUF) and hands them to the
VAAPI encoder zero-copy, re-queuing one frame late since the capture/encode
loop is synchronous. It is opt-in: used only when VAAPI is chosen by name
(backend::requires_dmabuf -> capture::open's want_dmabuf), never for Auto.
That matters because the generic Linux release binary enables both nvenc
and vaapi; making the vaapi feature unconditionally switch capture to
dmabuf would break an NVIDIA-only box, where neither NVENC nor openh264 can
consume a dmabuf frame. The CPU webcam + software/NVENC path is unchanged.

CI: cargo check --workspace --all-features (run by `just rs ci`) was broken
for these backends. nvidia-video-codec-sdk's build.rs panics without
libnvidia-encode.so, so a new `nvenc-ci` feature enables its `ci-check`
escape hatch (additive, so it also neutralizes the panic under
--all-features), and libva is added to the nix devShell so cros-libva's
build script can probe it. Both backends now type-check on Linux.

Compile-verified on Linux (cargo check + clippy -D warnings) via a podman
aarch64 VM; the V4L2 capture and the VAAPI encode path are not yet
runtime-tested (need a real /dev/video* + VAAPI device).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
cros-codecs 0.0.6 (the latest) caret-pins cros-libva 0.0.12, which does not
compile against libva >= 2.23: newer headers add seg_id_block_size /
va_reserved8 to VAEncPictureParameterBufferVP9 and cros-libva 0.0.12's struct
literal omits them. cros-libva 0.0.13 fixes it but cros-codecs 0.0.6 won't
accept it. Modern distros and the nix toolchain ship libva 2.23+, so the VAAPI
backend (added in #1691) could neither build nor ship, and CI's --all-features
could not be made green while the `vaapi` feature pulled this dependency. This
only surfaced now because wiring the hardware backends into CI is what first
compiled VAAPI against a real, modern libva.

cros-codecs is the only pure-Rust VAAPI H.264 encoder and reintroducing ffmpeg
for VAAPI defeats this effort, so VAAPI is parked until cros-codecs releases
against cros-libva >= 0.0.13. Intel/AMD Linux falls back to the openh264
software encoder; NVIDIA uses NVENC; macOS uses VideoToolbox.

Removed: backend/vaapi.rs, capture/v4l2.rs (the dmabuf capture that only fed
VAAPI), the `vaapi` feature and its cros-codecs/v4l/libc deps, Frame::DmaBuf and
the requires_dmabuf capture coupling, and libva from the nix devShell.

Kept from this PR's original scope: the `nvenc-ci` feature, which enables
nvidia-video-codec-sdk/ci-check so `cargo check --workspace --all-features`
type-checks the NVENC backend on Linux without an NVIDIA driver (its build.rs
otherwise panics looking for libnvidia-encode.so). NVENC remains
compile-verified only; runtime validation still needs a Linux + GPU box.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@kixelated kixelated changed the title feat(moq-video): V4L2 dmabuf capture for VAAPI + Linux backend CI coverage feat(moq-video): drop unbuildable VAAPI backend, add NVENC CI coverage Jun 13, 2026
`cargo deny` (run by `just rs ci`) rejected the IJG license carried by
mozjpeg-sys, which nokhwa pulls for MJPEG decode in moq-video. This was latent:
`just rs ci` runs `cargo check --workspace --all-features` before `cargo deny`,
and that check failed at compile time until now, so the deny step was never
reached. With the hardware backends compiling, deny runs and surfaces it. IJG
(Independent JPEG Group) is a standard permissive license; add it to the allow
list.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@kixelated
kixelated enabled auto-merge (squash) June 13, 2026 05:06
`just rs ci` ran `cargo check/test --workspace --all-features`, which
force-enables moq-video's `nvenc` feature. That can't work in a GPU-less CI:
the SDK's build.rs panics without libnvidia-encode.so (cargo check), and even
with the `ci-check` escape hatch a test/build still fails to link its NVENC
symbols. This is why --all-features has been red since NVENC landed (#1691).

Exclude moq-video from the workspace-wide --all-features check and test, and
cover it explicitly instead: a Linux-only check-only pass with `nvenc-ci`
(ci-check skips the link), plus `cargo test -p moq-video` on default features.
moq-video is still built (without nvenc) as moq-cli's dependency, so its
default path keeps full coverage; only the vendor-lib encoder feature is
carved out of the blanket runs.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@kixelated

Copy link
Copy Markdown
Collaborator Author

CI note: the remaining red Check is a pre-existing libmoq bug, not this PR.

--all-features in just rs ci has been broken since NVENC landed in #1691 (its own Check was also red when merged), so cargo deny and cargo test --all-features never actually ran. Fixing the moq-video/plumbing layers here got the pipeline through compile → deny → the full test suite for the first time, which surfaced a backlog of latent failures. The moq-video-related ones are fixed in this PR:

  1. NVENC build.rs panic (no SDK) → nvenc-ci feature (ci-check)
  2. cros-libva vs libva 2.23 → VAAPI removed
  3. IJG license (mozjpeg via nokhwa) → allowed in deny.toml
  4. NVENC link error (no GPU lib) → excluded from workspace --all-features, covered check-only

The last failure is a SIGSEGV in libmoq's tests (rs/libmoq/src/test.rs) — a callback use-after-free in the C bindings. It touches none of this PR's files, reproduces on a clean checkout, and is tracked separately. This PR leaves CI strictly better than dev.

(Written by Claude)

kixelated and others added 2 commits June 12, 2026 23:05
Replaces nokhwa's Linux webcam path with a native V4L2 capture
(src/capture/v4l2.rs) built on the `v4l` crate's MMAP streaming. It
negotiates YUYV or MJPEG (the formats essentially all UVC webcams expose)
and converts each frame to CPU I420 for the encoder: YUYV is resampled
4:2:2 -> 4:2:0 directly, MJPEG is decoded with the pure-Rust `zune-jpeg`
(MIT/Apache/Zlib, no libjpeg/IJG) then converted. The stream owns the
device's Arc<Handle>, so no self-referential borrow.

nokhwa is now Windows-only (Media Foundation), kept until a native MF
capture lands; macOS already uses AVFoundation/ScreenCaptureKit. This drops
nokhwa, mozjpeg, and image from the Linux and macOS builds (they remain in
the lockfile only for the Windows target).

frame.rs gains `from_rgb` (MJPEG path) and `from_yuyv` (raw path) alongside
the existing `from_rgba`, sharing a small `pack` helper; the two new ones
are Linux-gated since only the V4L2 capture uses them.

Capture is compile-verified on Linux (clippy -D warnings) but not
runtime-tested (no camera in CI / the sandbox); the V4L2 format negotiation
and YUYV/MJPEG conversions need a real /dev/video* device to confirm.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@kixelated kixelated changed the title feat(moq-video): drop unbuildable VAAPI backend, add NVENC CI coverage feat(moq-video): native V4L2 capture (drop nokhwa on Linux), drop VAAPI, NVENC CI coverage Jun 13, 2026
@kixelated

Copy link
Copy Markdown
Collaborator Author

CI update: the moq-video/plumbing work here now compiles and passes everything it should. With the libmoq fix from dev (#1710) merged in, CI got through compile → deny → and for the first time ever ran cargo test --workspace --all-features — which exposed one more pre-existing, non-moq-video issue:

  • moq-ffi dynamic_track_request / dynamic_track_request_can_abort deadlock (rs/moq-ffi/src/test.rs). The test awaits subscribe_track for a dynamic track before the producer's requested_track() accepts it → single-task deadlock. Introduced by b1580b48 ("Fix CI: await async subscribe_track"); never caught because moq-ffi's tests have never run in CI (the --all-features build died at the NVENC compile since feat(moq-video): native per-platform H.264 codecs + zero-copy capture, drop ffmpeg #1691).

This is the same category as the libmoq segfault and is being fixed separately on dev. Once that lands, merging dev in should make this Check green. CI isn't re-triggered here in the meantime since it would just hang on the same test.

(Written by Claude)

NVENC now dlopens everything at runtime, so an `--features nvenc` binary links
on a GPU-less builder and loads on machines without the NVIDIA driver, falling
back to software. This unblocks shipping capture/encode in a portable moq-cli,
and lets CI build/link/test NVENC normally instead of the previous ci-check
compile-only workaround.

- `nvidia-video-codec-sdk/dynamic-loading`: a small fork feature (root
  [patch.crates-io] -> kixelated/nvidia-video-codec-sdk) that dlopens
  libnvidia-encode for the two entry points the SDK uses to build its function
  table, and skips the link directives in build.rs. Upstream + drop the patch
  once merged.
- cudarc 0.16 -> 0.19 with `fallback-dynamic-loading` (dlopen libcuda) +
  `cuda-12020` (pin the CUDA API version so no toolkit is needed at build).
  DriverError lost its Display impl in 0.19, so format it with `{e:?}`.
- `just rs ci`: drop the ci-check incantation; build + test moq-video with real
  `--all-features` on Linux (still excluded from the workspace --all-features
  because the SDK has no macOS bindings).
- deny.toml allows the fork git source; DESIGN + the feature comment updated.

Verified on a GPU-less Linux box (podman): builds, links, `readelf -d` shows no
libnvidia-encode / libcuda DT_NEEDED, and `cargo test -p moq-video --features
nvenc` passes (falls back to openh264). Real-GPU encode still needs a Linux+GPU
box.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@kixelated kixelated changed the title feat(moq-video): native V4L2 capture (drop nokhwa on Linux), drop VAAPI, NVENC CI coverage feat(moq-video): native V4L2 capture, NVENC via dlopen, drop VAAPI + nokhwa(Linux) Jun 13, 2026
@kixelated
kixelated merged commit 9f8cdac into dev Jun 13, 2026
1 check passed
@kixelated
kixelated deleted the claude/strange-cohen-f7dbba branch June 13, 2026 23:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant