User Tools

Site Tools


fourier:brave_arm64_vaapi_wall

Brave 148 ARM64 + Mali-Bifrost / hantro VPU + libva-v4l2-request-fourier — definitive HW video decode wall

Verdict: Brave-bin on PineTab2 (and structurally on any RK3566/RK3568 Mali-Bifrost + hantro stack) cannot engage hardware video decode. The wall is architectural in chromium, not in our libva backend, not a flag-combination problem, not a “VAAPI not compiled in” problem. Brave is unfixable on this hardware without source rebuild — which Brave (closed source) does not permit. chromium-fourier (the open-source rebrand we maintain in marfrit-packages) IS fixable and DOES engage HW decode on the same hardware.

This document supersedes iter11/iter12/iter14 of the panvk-bifrost campaign, all of which produced contradictory and partially-incorrect theories about this wall.

Substrate this was measured on

  • Host: ohm (PineTab2, RK3566 Quad Cortex-A55, Mali-G52 r1 MC1)
  • OS: Arch Linux ARM, kernel linux-fresnel-fourier (hantro v4l2-stateless driver)
  • Brave: 148.1.90.124 (aarch64)
  • Mesa Vulkan ICD: mesa-panvk-bifrost r4-1 (/usr/lib/panvk-bifrost/libvulkan_panfrost.so)
  • libva: 2.23.0-1 + libva-v4l2-request-fourier 1:1.0.0.r390.c454618-1
  • /etc/profile.d/libva-v4l2-request.sh sets LIBVA_DRIVER_NAME=v4l2_request system-wide
  • Test file: /home/mfritsche/fourier-test/bbb_1080p30_h264.mp4 (725 MB, H.264 1080p30)
  • Measurement date: 2026-05-21

What was measured (verbatim, not inferred)

1. Brave's GPU process loads libva and successfully completes VAAPI initialization

LIBVA_TRACE=/tmp/brave_libva.log brave … (full launch command at the bottom of this doc) — the resulting trace files in /tmp/brave_libva.log.*.thd-* show:

[3485.434262] vaInitialize ret = VA_STATUS_SUCCESS
[3485.434538]	entrypoint = 1, VAEntrypointVLD  (H264Main + H264High + H264CB + VP8 + others)
[3490.281730] vaInitialize ret = VA_STATUS_SUCCESS  (second context, this time for actual decode)
[3490.281803]	entrypoint = 1, VAEntrypointVLD
[3490.719527] [ctx 0x02000000] profile = 6,VAProfileH264Main entrypoint = 1,VAEntrypointVLD

Every libva call up to and including decode-context creation returns SUCCESS.

This invalidates the iter14 memory claim “/proc/<gpu-pid>/maps: NO libva libraries loaded at all”. libva IS loaded; iter14's check (if it was even done) was wrong or measured a different state.

2. Brave's chromium emits the decoder-selection sequence

From the brave stderr log:

[VERBOSE2:media/gpu/chromeos/video_decoder_pipeline.cc:585] Initialize():
    config: codec: h264, profile: h264 main, level: not available,
    coded size: [1920,1080], visible rect: [0,0,1920,1080], natural size: [1920,1080] ...
[VERBOSE2:media/gpu/vaapi/vaapi_video_decoder.cc:136] VaapiVideoDecoder():
[VERBOSE2:media/gpu/vaapi/vaapi_video_decoder.cc:632] ApplyResolutionChange():
[VERBOSE2:media/gpu/vaapi/vaapi_video_decoder.cc:660] ApplyResolutionChangeWithScreenSizes():

VaapiVideoDecoder is fully constructed and active. The chromium decoder factory selects it for the H.264 stream. This invalidates iter11/12/14's various “VAAPI not in dispatch” / “VAAPI not compiled in” / “use_vaapi=false on ARM64” theories. VaapiVideoDecoder is in dispatch, is compiled in, and is selected.

3. The libva backend emits a non-fatal probe error

v4l2-request: Unable to set control(s): Invalid argument (error_idx=2/2 ioctl-level)

This is libva-v4l2-request-fourier’s src/context.c:587-588:

struct v4l2_ext_control dev_ctrls[2] = {
    { .id = V4L2_CID_STATELESS_H264_DECODE_MODE,
      .value = V4L2_STATELESS_H264_DECODE_MODE_FRAME_BASED, },
    { .id = V4L2_CID_STATELESS_H264_START_CODE,
      .value = V4L2_STATELESS_H264_START_CODE_ANNEX_B, },
};
(void)v4l2_set_controls(driver_data->video_fd, -1, dev_ctrls, 2);

Note: (void) cast — the return value is intentionally ignored. The comment immediately above the call says “Errors here are not fatal: not every backing driver supports both controls.” This is a startup probe, the message is informational, and brave proceeds past it. Confirmed by the next observation.

4. ffmpeg with the EXACT same backend on the EXACT same hardware decodes successfully

LIBVA_DRIVER_NAME=v4l2_request \
ffmpeg -hwaccel vaapi -hwaccel_device /dev/dri/renderD128 \
       -i /home/mfritsche/fourier-test/bbb_1080p30_h264.mp4 -t 3 -f null -

Result: decode at 1.56× realtime (frame=72 over 3s, speed=1.56x). The same probe error occurs in the ffmpeg trace (Unable to set control(s): Invalid argument (error_idx=2/2 ioctl-level)) but ffmpeg proceeds past it and completes the decode using the V4L2 stateless path through the same libva backend. This is the identical libva environment Brave just failed in.

This conclusively proves that the libva-v4l2-request EINVAL is NOT the wall — same backend, same file, same hardware, same libva environment, ffmpeg decodes, Brave doesn't. The difference is what each program does after libva.

5. The actual wall — measured in Brave's stderr

[VERBOSE2:media/gpu/chromeos/video_decoder_pipeline.cc:1263] PickDecoderOutputFormat(): Initializing ImageProcessor; max buffers: 16
[ERROR  :media/gpu/vaapi/vaapi_video_decoder.cc:1224] failed Initialize()ing the frame pool
[VERBOSE2:media/gpu/vaapi/vaapi_video_decoder.cc:144] ~VaapiVideoDecoder():

The chromium media/gpu/chromeos/video_decoder_pipeline.cc::PickDecoderOutputFormat path invokes ImageProcessor::Initialize() — a ChromeOS-specific V4L2 m2m chip block for color conversion / scaling. On a plain Linux + hantro system the ImageProcessor block does not exist, the init fails, and the frame pool that depends on it cannot be created. VaapiVideoDecoder destructs and the decoder factory falls back to FFmpegVideoDecoder (software).

ffmpeg does not use the chromeos pipeline; it goes through libavcodec's direct VAAPI integration without ImageProcessor. That's why ffmpeg succeeds where Brave fails on the identical libva backend.

Why this is unfixable in brave-bin

media/gpu/chromeos/video_decoder_pipeline.cc::PickDecoderOutputFormat is invoked by VaapiVideoDecoder unconditionally on Linux ARM64 because Brave's chromium build flags are:

use_vaapi=true
use_v4l2_codec=false

The behavior is selected at build time, not runtime. There is no chrome:// flag, no command-line switch, no --enable-features=* value that bypasses it.

chromium-fourier (our own rebuild) fixes this with the build-flag change use_v4l2_codec=true plus patch enable-v4l2-decoder-default.patch (the kAcceleratedVideoDecodeLinux master-gate fix at media/base/media_switches.cc:750). Those changes make V4L2VideoDecoder the selected decoder, which does NOT go through the chromeos pipeline and does NOT require ImageProcessor. chromium-fourier was operator-validated 2026-04-26 (see marfrit-packages/arch/chromium-fourier/NEXT.md) and plays 1080p30 H.264 from BBB on PineTab2 with fuser /dev/video1 confirming hantro engagement.

Brave is closed source. The Brave organization ships pre-compiled binaries; there is no brave-build-from-source PKGBUILD that mfritsche maintains. The build-time choice that engages the chromeos pipeline is therefore baked into every brave-bin release.

Recommendation

  1. For HW video decode in a chromium-family browser on PineTab2: use chromium-fourier instead of Brave. The package is published to packages.reauktion.de, installable via pacman -S chromium-fourier. It has the patches and DOES engage HW decode end-to-end.
  2. Do NOT pursue a “make brave HW-decode” campaign. This is structurally unfixable without rebuilding brave-bin from source, which the closed source license forbids and Brave's distribution model does not support.
  3. The project_brave_arm64_vaapi_wall memory is wrong. It needs replacement with this finding's text.
  4. iter11/12/14 closes are also wrong in their root-cause attribution. Don't read them as authoritative; this document is.

Future re-investigation triggers

This wall could change if:

  • Brave's upstream chromium drops use_v4l2_codec=false as a default and routes VaapiVideoDecoder without the chromeos pipeline on plain Linux. (No upstream signal of this as of 2026-05-21.)
  • Brave switches to a different chromium rebrand model that includes the source-modification capability we have for chromium-fourier. (Brave does not currently do this.)
  • chromium upstream provides a runtime --disable-chromeos-pipeline or equivalent flag. (No upstream signal.)
  • mfritsche pursues a build-from-Brave-source campaign (out of scope; closed source).

Until any of those land, this finding is the campaign result.

Full launch command used for the measurements

ssh ohm '
export XDG_RUNTIME_DIR=/run/user/$(id -u)
export WAYLAND_DISPLAY=wayland-0
XAUTHF=$(pgrep -fa Xwayland 2>/dev/null | grep -oE "/run/user/$(id -u)/xauth_[A-Za-z0-9]+" | head -1)
[ -n "$XAUTHF" ] && export XAUTHORITY="$XAUTHF"
export DISPLAY=:1
export LIBVA_DRIVER_NAME=v4l2_request
export LIBVA_V4L2_REQUEST_VIDEO_PATH=/dev/video1
export LIBVA_V4L2_REQUEST_MEDIA_PATH=/dev/media0
export LIBVA_TRACE=/tmp/brave_libva.log
export LIBVA_TRACE_BUFDATA=0
export VK_ICD_FILENAMES=/usr/lib/panvk-bifrost/icd.json
export PAN_I_WANT_A_BROKEN_VULKAN_DRIVER=1
export MESA_VK_VERSION_OVERRIDE=1.2
 
/opt/brave-bin/brave \
    --no-sandbox --disable-gpu-sandbox \
    --enable-logging=stderr --v=2 \
    --vmodule="*vaapi*=2,*media*=2,*v4l*=2,*video_decoder*=2,*gpu_video*=2,*libva*=2" \
    --enable-features=VaapiVideoDecoder,VaapiIgnoreDriverChecks,AcceleratedVideoDecoder \
    --use-gl=angle --use-angle=gles \
    --autoplay-policy=no-user-gesture-required \
    file:///home/mfritsche/fourier-test/bbb_1080p30_h264.mp4 \
    >> /tmp/brave_stderr.log 2>&1 &
'

— claude-noether, 2026-05-21

fourier/brave_arm64_vaapi_wall.txt · Last modified: by 127.0.0.1