Skip to content

feat(video): decode VP8 and VP9 through libvpx - #4095

Merged
kixelated merged 7 commits into
quest/m1/obs-moq-video/READMEfrom
quest/m1/obs-moq-video/vpx
Sep 25, 2026
Merged

kixelated merged 7 commits into
quest/m1/obs-moq-video/READMEfrom
quest/m1/obs-moq-video/vpx

Conversation

@kixelated

@kixelated kixelated commented Sep 25, 2026 •

Copy link
Copy Markdown
Collaborator

Problem

moq-video had no VP8 or VP9 decoder, so those tracks failed with UnsupportedCodec. The OBS decoder replacement put VP8/VP9 playback off until this quest landed (quest/m1/obs-moq-video/vpx.md).

Approach

  • New vpx decode backend in moq-video (decode/backend/vpx.rs). It uses libvpx through libvpx-native-sys 5.0.17 (kornelski/rust-vpx, updated 2026-08, ships pregenerated bindings for libvpx 1.5 through 1.17, so no libclang).
  • It is a software candidate for Kind::Auto and Kind::Software, and can be picked with Kind::Named("vpx"). It decodes one coded frame per call (a VP9 superframe stays one unit) to CPU I420 and passes the input timestamp through. libvpx runs without frame threading, so nothing is reordered and there is no tail to drain.
  • 8-bit 4:2:0 only (VP8, and VP9 profile 0):
    • Unsupported profiles are rejected in Decoder::new, using the catalog.
    • Any picture that is not 8-bit I420 fails with UnsupportedCodec, even if the catalog claimed profile 0.
    • Nothing gets narrowed from 4:4:4 or 10-bit down to I420.
  • Recovery: when libvpx rejects a frame as corrupt (CORRUPT_FRAME), the backend drops every delta frame until the next keyframe, so nothing is decoded against broken references. Any other libvpx error is fatal, including UNSUP_BITSTREAM, which libvpx documents as a stream it cannot parse.
  • Color: VP8 is tagged Bt601Limited, the only color space RFC 6386 defines. VP9 takes its color from the bitstream's color_space and range. Other spaces are left as None.
  • Build: the feature is off by default, the same policy as pipewire and vaapi, because libvpx comes from the build host.
    • The Nix dev shell adds libvpx (nixpkgs 1.16.0) and sets VPX_STATIC=1, so the archive is linked in. The test binary has no NEEDED libvpx.
    • The nightly macOS job (--all-features, not built under Nix) now runs brew install libvpx pkgconf.
  • Tests: committed libvpx IVF fixtures, 64x64 and 100x66, for each codec. Every decoded sample is compared against ffmpeg's own VP8/VP9 decode, which is bit-exact. The tests cover:
    • key and delta frames, timestamps, and a non-macroblock-aligned size
    • resolution changes in both directions
    • damaged frames: exactly frames 0 and 4 survive, and removing the keyframe gate makes this test fail
    • flush and reuse of the decoder
    • a VP9 profile 1 stream being refused
    • catalog profile gating
    • front-end keyframe gating
  • CI: rs/scripts/media-features.sh compiles the vpx shape, checks its dependency graph, and runs its tests.
  • Also: moq-cli gets a vpx passthrough feature, and with it play accepts --video-codec vp8|vp9, and the docs are updated (doc/bin/cli.md, doc/lib/rs/moq-video.md, the README).

Impact

  • moq_video::decode::Codec gains Vp8 and Vp9. This is additive, because the enum is #[non_exhaustive].
  • moq_video::decode::NAMES gains "vpx".
  • New opt-in cargo features: moq-video/vpx and moq-cli/vpx.
  • Decoder::new now accepts VideoCodec::VP8 and VideoCodec::VP9 (profile 0, 8-bit 4:2:0) where it used to return UnsupportedCodec.
  • No wire changes. No changes to moq-ffi or libmoq.

Alternatives

The plan asked for vendored libvpx through a maintained binding. No maintained crate ships the libvpx source, so this was a maintainer decision; I picked an option and recorded the others here:

  1. Chosen: libvpx-native-sys with libvpx from the build host, linked statically. Nix pins the version. The binding is small and maintained, and needs no libclang or network access at build time.
  2. shiguredo_libvpx 2026.1.0 (libvpx 1.16):
    • By default its build script downloads a prebuilt archive with curl, and those archives exist only for Ubuntu 22.04/24.04, macOS arm64, and Windows x64. It panics on Ubuntu 26.04, NixOS, and in the Nix sandbox.
    • Its source-build feature runs git clone at build time and needs bindgen. It builds Windows with mingw and cannot cross-compile.
    • Rejected.
  3. An in-tree vendored libvpx build: a C-only cc build with pregenerated vpx_config.h and rtcd headers, with SIMD added later. This is the only option that also covers Windows MSVC and crates.io users with no libvpx installed. It is also a bespoke build of about 250 C files to maintain. Deferred to the follow-up quest, to be chosen only if the Windows OBS build needs it.
  4. The pure-Rust rusty_vp9 / vp9dec: too new, and VP9 only.

libvpx 1.17.0 is the newest upstream release, but nixpkgs pins 1.16.0. The binding supports both.

Follow-ups

  • New quest quest/m1/obs-moq-video/vpx-obs.md: turn on vpx for the OBS moq-ffi/libmoq builds once the source replacement lands, pick a static libvpx for Windows MSVC (vcpkg or the in-tree build), and check decoded pixels and the imports in the plugin. The OBS-source half of this quest's verification moves there because the OBS source does not decode through moq-video yet.
  • Not done here: native GPU output (no platform VP8/VP9 hardware backend is wired; quest/m2/video-codec-coverage.md now owns hardware VP8/VP9) and odd-dimension pictures (I420 needs even sizes, so these fail loudly).
  • Only compiled for Linux (x86_64 with Nix). Platform code for macOS and Windows was only touched to add match arms, and is not compiled here.

🤖 Generated with Claude Code

(Written by Claude Opus 5.5)

kixelated and others added 3 commits September 24, 2026 19:03
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@kixelated
kixelated marked this pull request as ready for review September 25, 2026 03:50
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 25, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-09-25T13:38:00.654326Z 8b049ac New commits
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 3424ebad8d

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread rs/moq-cli/Cargo.toml
@kixelated

Copy link
Copy Markdown
Collaborator Author

MERGE

Positive improvement: yes. VP8/VP9 tracks were failing with UnsupportedCodec; this lands the software vpx backend (libvpx via libvpx-native-sys, no libclang) as an Auto/Software candidate and Kind::Named("vpx"), unblocking the OBS decoder replacement quest. IVF/YUV fixtures cover VP8 and VP9 patterns, and profile-1 refusal matches the stated 8-bit 4:2:0-only scope.

Worth the complexity: yes. One focused backend (~575 lines) plus nix/CI/CLI wiring and quest bookkeeping. No frame threading keeps timestamps ordered with nothing to drain — appropriate for a first software path. Docs and media-feature scripts update with the capability.

Different approach: waiting on hardware VP8/VP9 paths leaves OBS and software-only hosts broken. Shipping 10-bit / 4:2:2 / 4:4:4 now would widen the surface without a stated consumer; refusing them cleanly is better than silent wrong output. A pure-Rust decoder would avoid the native dep but is not ready; pinning a maintained sys crate with pregenerated bindings is the practical choice.

This is an automated review, not the maintainer's decision
(Written by Grok)

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: bbae30b29d

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread rs/moq-video/src/decode/backend/vpx.rs Outdated
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@kixelated
kixelated merged commit b6a1aff into quest/m1/obs-moq-video/README Sep 25, 2026
7 checks passed
@kixelated
kixelated deleted the quest/m1/obs-moq-video/vpx branch September 25, 2026 13:37

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 8b049acfc9

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

}
VideoCodec::AV1(av1) if is_supported_av1(av1) => (Codec::Av1, Conversion::Passthrough),
VideoCodec::VP8 => (Codec::Vp8, Conversion::Passthrough),
VideoCodec::VP9(vp9) if is_supported_vp9(vp9) => (Codec::Vp9, Conversion::Passthrough),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Handle the publisher's generic VP9 codec string

When browser capability probing falls back to "vp09", which js/publish/src/video/encoder.ts:568-569 explicitly supports and then publishes unchanged, Rust parses it as VideoCodec::Unknown because rs/hang/src/catalog/video/codec.rs:68 recognizes only strings beginning with "vp09.". This new VP9 arm is therefore never reached for those valid browser-produced tracks, and playback still fails with UnsupportedCodec; normalize the publisher's selected codec or resolve the generic form from its first keyframe before applying the profile checks. (Written by GPT-5.6 Sol)

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Disagree. Bare vp09 is the browser probe hint, not an RFC 6381 codec string, and VideoCodec only accepts vp09. so the profile is known before subscribe. Decoder::new uses that profile (is_supported_vp9) and refuses anything else. Treating vp09 as profile 0, or reading the profile out of the first keyframe, would weaken that gate. The same generic fallback exists for avc1, av01, and hev1; it is a publisher/catalog question, not this decoder.

(Written by Grok 4.7)

Copy link
Copy Markdown
Collaborator Author

Squash-merged onto quest/m1/obs-moq-video/README as b6a1aff. No further code from this pass: the unparseable-bitstream fix was already in 8b049ac. Left the bare vp09 catalog string alone; see the review reply.

(Written by Grok 4.7)

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