# firefox-fourier — V4L2 Stateless Decoder Patch Plan Plan to extend Firefox 149's V4L2 hardware decode path to cover Rockchip mainline kernel boards (RK3399 rkvdec, RK3566/RK3588 hantro, RK3588 rkvdec2) by routing stateless `S264`/`S265`/`VP9F` fourccs through libavcodec's `v4l2_request` hwaccel, which mainline FFmpeg surfaces via `AV_HWDEVICE_TYPE_DRM` (no dedicated `_V4L2REQUEST` enum exists upstream — confirmed against `libavutil/hwcontext.h`). ## 1. Files touched (in order) | # | Path | Change | Lines | |---|------|--------|-------| | 1 | `widget/gtk/GfxInfo.cpp` | `V4L2ProbeDevice` (~L1030–1110): add `S264`/`S265`/`VP9F` matches alongside existing `H264`/`HEVC`/`VP90`. Set `mIsV4L2Supported = Some(true)` and OR the same `CODEC_HW_DEC_*` bits. Tag a new bool `mV4L2IsStateless` so downstream can branch. | +35 / -2 | | 2 | `dom/media/platforms/ffmpeg/FFmpegLibWrapper.h` | Add wrappers for `av_hwdevice_ctx_create` (currently only `_alloc`/`_init` per L173–174) and `av_hwdevice_find_type_by_name`. Needed because stateless wants the *device-path-aware* `_create` form to bind `/dev/dri/renderD128`. | +4 / 0 | | 3 | `dom/media/platforms/ffmpeg/FFmpegLibWrapper.cpp` | `dlsym` the two new pointers; gate behind `LIBAVUTIL_VERSION_MAJOR >= 56`. | +6 / 0 | | 4 | `dom/media/platforms/ffmpeg/FFmpegVideoDecoder.h` | Add `AVBufferRef* mDRMDeviceContext = nullptr;` and `bool mUsingV4L2Request = false;` next to existing `mUsingV4L2`. | +3 / 0 | | 5 | `dom/media/platforms/ffmpeg/FFmpegVideoDecoder.cpp` | New `CreateV4L2RequestDeviceContext()` modelled on `CreateVAAPIDeviceContext` (current uses `av_hwdevice_ctx_alloc(AV_HWDEVICE_TYPE_VAAPI)`). Wire it in alongside the existing V4L2 init branch (the one feeding `ChooseV4L2PixelFormat` at L~258). `FindVideoHardwareAVCodec` call at L708 stays unchanged for stateless — we want the *generic* `h264`/`hevc`/`vp9` decoder + a DRM hw_device_ctx, not a `_v4l2m2m` codec. | +90 / -5 | | 6 | `modules/libpref/init/StaticPrefList.yaml` | New pref `media.ffmpeg.v4l2-request.enabled` mirroring `media.ffmpeg.vaapi.enabled` (default `@IS_LINUX@`). | +6 / 0 | | 7 | `dom/media/ipc/RDDProcessHost.cpp` + sandbox policy file | Whitelist `/dev/media*`, `/dev/dri/renderD*`, `/dev/video*` for the RDD process (already partly done for VAAPI — verify `policy/linux/SandboxBrokerPolicyFactory.cpp`). | +6 / 0 | Total: roughly **+150 / -10** across 7 files. ## 2. Probe extension — `GfxInfo.cpp::V4L2ProbeDevice` Existing pattern (~L1075): ```cpp if (outFormats.Contains("H264")) { mIsV4L2Supported = Some(true); mV4L2SupportedCodecs |= CODEC_HW_DEC_H264; ... } ``` Add three siblings, each setting an additional `mV4L2IsStateless = true`: ```cpp if (outFormats.Contains("S264")) { /* CODEC_HW_DEC_H264 | stateless */ } if (outFormats.Contains("S265")) { /* CODEC_HW_DEC_HEVC | stateless */ } if (outFormats.Contains("VP9F")) { /* CODEC_HW_DEC_VP9 | stateless */ } ``` Decision: do **not** introduce a separate `mIsV4L2StatelessSupported`. Collapse under `mIsV4L2Supported` so the existing feature-gate plumbing (`MediaCodecsSupport`, `gfxFeature::HW_DECODE_VIDEO`) flips identically — only `mV4L2IsStateless` distinguishes the routing in step 3. Stateful + stateless on the same SoC (rare, but RK3588 has both rkvdec2 + hantro VEPU) gracefully degrades to whichever codec wins enumeration order. The capture-format gate (`YV12`/`NV12`) needs widening: stateless decoders frequently expose only `NV12` or `NV15` (10-bit, RK3588 HEVC). Add `NV15` and `NM12` (multiplanar NV12, hantro). Without this the prober rejects an otherwise-good device. ## 3. Decoder routing — `FFmpegVideoDecoder.cpp` Codec selection happens at **L651** (`AV_HWDEVICE_TYPE_VAAPI` → `h264_vaapi`) and **L708** (the V4L2 fallback path → `h264_v4l2m2m` via `FindVideoHardwareAVCodec(mLib, mCodecID)` resolving by suffix). The stateless route diverges from both: the *codec* must remain the generic `h264`/`hevc`/`vp9` decoder (libavcodec auto-binds `v4l2_request` from its `hw_configs` when a DRM hw_device_ctx is attached). Pseudo-patch: ```cpp if (gfxInfo.mUsingV4L2 && gfxInfo.mV4L2IsStateless) { AVCodec* codec = mLib->avcodec_find_decoder(mCodecID); // generic mCodecContext = mLib->avcodec_alloc_context3(codec); if (!CreateV4L2RequestDeviceContext()) return false; mCodecContext->get_format = ChooseV4L2PixelFormat; // already returns DRM_PRIME mUsingV4L2Request = true; } ``` `CreateV4L2RequestDeviceContext()` body: ```cpp const char* drm = "/dev/dri/renderD128"; if (mLib->av_hwdevice_ctx_create(&mDRMDeviceContext, AV_HWDEVICE_TYPE_DRM, drm, nullptr, 0) < 0) return false; mCodecContext->hw_device_ctx = mLib->av_buffer_ref(mDRMDeviceContext); ``` `av_hwdevice_ctx_create` — not currently wrapped — is the entry point. The codec's internal hwaccel selector then walks `avcodec_get_hw_config()` and picks the entry whose `device_type == AV_HWDEVICE_TYPE_DRM` and `pix_fmt == AV_PIX_FMT_DRM_PRIME`, which is the v4l2_request hwaccel registered in `libavcodec/v4l2_request_*.c`. No `av_hwdevice_find_type_by_name("v4l2_request")` needed — stays an internal libavcodec name. ## 4. Dmabuf / DRM_PRIME reuse `ChooseV4L2PixelFormat` at L~258–270 already returns `AV_PIX_FMT_DRM_PRIME` and is the *only* format the v4l2_request hwaccel produces. The downstream consumer (DMABufSurfaceYUV import in `FFmpegVideoFramePool.cpp`) is already DRM_PRIME-aware for the stateful path — same code reads `AVDRMFrameDescriptor` from `frame->data[0]`. **No new output handling required for NV12/YV12.** 10-bit caveat: RK3588 HEVC outputs `DRM_FORMAT_NV15` / `NV20` (Mali-tile). Existing `WaylandDMABufSurface::CreateYUVSurface` modifier list does not include `DRM_FORMAT_MOD_ARM_AFBC` or NV15 fourcc. Either reject 10-bit at probe (capture format gate above) or extend `gfx/layers/DMABUFSurfaceImage.cpp` — out of scope for v1; gate to NV12 only. SAND format pollution Turner mentioned in bug 1969297 c#3 is **Pi5-specific**; rkvdec/hantro do not produce SAND. Safe to ignore for the Rockchip target. ## 5. Configuration New pref: ```yaml - name: media.ffmpeg.v4l2-request.enabled type: RelaxedAtomicBool value: @IS_LINUX@ mirror: always ``` No new env var. No `MOZ_X11_EGL`-style kludge. The existing `MOZ_LOG=PlatformDecoderModule:5` covers diagnostics. Default-on matches `media.ffmpeg.vaapi.enabled` shape; users get fallback to software via existing failure paths if `av_hwdevice_ctx_create` fails (e.g., missing `/dev/media0`). ## 6. Test plan (fresnel — RK3399, KDE Wayland) 1. `ls /dev/video* /dev/media*` — confirm `/dev/video0` (rkvdec output) and `/dev/media0` exist. 2. `v4l2-ctl -d /dev/video0 --list-formats-out` — expect `S264`/`S265`/`VP9F`. 3. Start: `MOZ_LOG="PlatformDecoderModule:5,FFmpegVideo:5" firefox-fourier 2>&1 | tee fx.log`. 4. Open `https://test-videos.co.uk/bigbuckbunny/mp4-h264` 1080p clip. 5. Success markers in `fx.log`: - `V4L2ProbeDevice: /dev/video0 supports S264 (stateless)` - `Choosing FFmpeg pixel format for V4L2 video decoding.` - `Requesting pixel format DRM PRIME` - `av_hwdevice_ctx_create(DRM, /dev/dri/renderD128) ok` - **No** `Using preferred software codec h264`. 6. `cat /sys/kernel/debug/clk/clk_summary | grep vdec` — clock should be active during playback. 7. `top` — CPU < 40% on a single A72 core for 1080p H.264 (stock = 100% on all 6 cores). ## 7. Build + ship — `firefox-fourier` PKGBUILD Mirror `chromium-fourier` shape exactly (sibling). ```bash pkgname=firefox-fourier pkgver=149.0 arch=('aarch64' 'x86_64') makedepends=(rust clang lld nodejs python cbindgen nasm yasm wasi-libc-bin gtk3 mesa libva ffmpeg) # ffmpeg only for headers via system libs source=( "https://archive.mozilla.org/pub/firefox/releases/${pkgver}/source/firefox-${pkgver}.source.tar.xz" patches/0001-gfxinfo-v4l2-stateless-fourccs.patch patches/0002-libwrapper-hwdevice-ctx-create.patch patches/0003-ffmpegvideo-v4l2-request-route.patch patches/0004-prefs-v4l2-request.patch mozconfig ) ``` `prepare()`: `cd firefox-${pkgver}` → apply patches with `patch -Np1`. `build()`: `MOZ_NOSPAM=1 ./mach build`. `package()`: `./mach install DESTDIR=${pkgdir}`. `mozconfig` enables `--enable-default-toolkit=cairo-gtk3-wayland`, `--with-system-ffmpeg`, `ac_add_options --disable-tests`. **No** `--enable-media-gpu-process` — let it default. Tarball is the official Mozilla source release (not gecko-dev). Extra makedepends vs. stock firefox PKGBUILD: none — this only modifies existing C++. ## 8. Risk register (ranked) 1. **libavcodec ABI mismatch.** ALARM ships ffmpeg 7.x; Firefox dlopens whatever's at `libavcodec.so.61`. If the v4l2_request hwaccel was compiled out (Arch's ffmpeg has it; ALARM rebuild may not), `av_hwdevice_ctx_create(DRM, ...)` succeeds but no codec binds — silent fallback. Mitigation: `ffmpeg -hwaccels` should list `drm`. 2. **Renderer-process sandbox** blocks `/dev/dri/renderD128` open. VAAPI already brokered this for RDD process; verify `SandboxBrokerPolicyFactory.cpp` covers `/dev/media*` too — likely doesn't. 3. **glxtest probe runs in stripped env.** `v4l2test` (the FireTestProcess child) needs `cap_sys_admin` for `VIDIOC_S_EXT_CTRLS` request API ioctls? No — request API just needs `O_RDWR` on `/dev/media*`. Should be fine. 4. **Regression of stateful path.** Adding new fourccs is additive; the routing branch is gated on `mV4L2IsStateless`. Stateful boards (Pi4) untouched. 5. **NV15/10-bit on RK3588** — explicitly out-of-scope v1; gate-rejected. 6. **rkvdec2 driver maturity.** Linux 6.12 mainline rkvdec2 H.264 works; HEVC/VP9 still upstream-pending on some boards. Probe will skip what kernel doesn't expose. 7. **DMA-BUF modifier negotiation** with panfrost/panthor on Wayland — already shaken out by chromium-fourier on RK3566; same code path. ## 9. Upstream path (bug 1969297) Split into 4 reviewable commits matching files 1, 2+3, 5, 6 from the table. Add a gtest exercising `V4L2ProbeDevice` against a synthetic v4l2test stdout containing `S264` (no kernel needed). Reach out to skyevg (D252119 author) for review continuity. r? jya for the FFmpegVideoDecoder change. The `av_hwdevice_ctx_create` wrapper addition is a self-contained 6-liner that should land independently. The 10-bit/SAND concerns Turner raised remain valid for Pi5 — explicitly scope this series to **stateless DRM_PRIME NV12 only**, leaving SAND for a follow-up bug.