[WIP] gallium/va: add termux-va bridge for hardware VA-API decode via the Termux MediaCodec daemon - #100
Draft
lfdevs wants to merge 20 commits into
Draft
[WIP] gallium/va: add termux-va bridge for hardware VA-API decode via the Termux MediaCodec daemon#100lfdevs wants to merge 20 commits into
lfdevs wants to merge 20 commits into
Conversation
tva_protocol.h is the cmp-verified mirror of the daemon's
common/tva_protocol.h (repository lfdevs/termux-va); the two copies must
stay byte-identical (scripts/check-mirror.sh in the daemon repo).
tva_client.{h,c} is a port of droidspaces-media-decode's
vaapi-driver/src/dmd_client.{c,h} (Apache-2.0, relicensed GPL-3.0 with
the modification notice in the file headers). Changes vs upstream:
Unix-socket-only transport (TCP removed, wire values unchanged),
protocol constants from the mirror header, symbols renamed dmd_* ->
tva_*, and the default endpoint resolves in container view
(TERMUX_VA_SOCKET > TERMUX_VA_SOCKET_DIR > shared-tmp paths, i.e.
/tmp/termux-va/termux-va.sock) through Mesa's os_get_option so Android
system properties work as a fallback.
Kept faithful: the v3 handshake with the endpoint dev/ino reconciliation,
the v2 downgrade retry, the SHM pickup through the abstract socket with
SCM_RIGHTS, the format-block parsing with CAP_FRAME_PTS, non-blocking I/O
with bounded waits, MSG_NOSIGNAL and CLOEXEC everywhere.
The bridge delegates VA-API bitstream decode to the termux-va daemon (MediaCodec hardware decode in Termux) over the Unix socket described by tva_client.h, and runs entirely on the application thread - no bridge-owned threads touch pipe_context. - tva_screen wraps the real screen: get_video_param / is_video_format_supported advertise the bridge codec set (H.264 Baseline/Main/High, HEVC Main, VP9 Profile0, NV12 only, 96x96..8192x4320, progressive) while everything else forwards. - tva_pipe wraps the multimedia context: create_video_codec hands decode to the bridge codec, and create_video_buffer(_with_modifiers) falls back to the generic vl_video_buffer_create helper because the underlying drivers have no video path. - The codec accumulates the frontend's slice data (which already carries H.264/HEVC start codes; parameter sets arrive as slice data buffers), splits it into Annex B units in end_frame (parameter sets excluded from the unit index exactly like the daemon), and pairs the returned frame with the picture by the daemon's unit index through a pending ring whose depth is capped by the daemon's SHM slot pool. - fence_wait pumps frames into staging buffers and copies the visible (cropped) region of the NV12 buffer into the surface plane resources on the caller's thread, honoring the decoder stride/slice_height geometry from the format block. Derived in part from droidspaces-media-decode's vaapi-driver (Apache-2.0): the capability table, pipeline-depth model and unit classification; the modification notice per GPL-3.0 section 5 is in the file header.
- VA_DRIVER_INIT_FUNC wraps the multimedia context with the bridge right after it is created (activation is runtime-gated by the TERMUX_VA_* environment variables) and appends a marker to the vendor string so vainfo shows when the bridge is active. - New meson feature option 'termux-va-bridge' (default auto): the bridge sources are compiled into libva_st unless disabled, and the option also relaxes the VA state tracker's gallium-driver whitelist so a freedreno-only build can enable gallium-va for the bridge. - targets/dri exposes the megadriver as termuxva_drv_video.so so libva can dlopen it via LIBVA_DRIVER_NAME=termuxva. Runtime activation summary: TERMUX_VA_BRIDGE=1 forces the bridge on, =0 forces it off; with the variable unset the bridge activates when TERMUX_VA_SOCKET/TERMUX_VA_SOCKET_DIR is set or the default endpoint /tmp/termux-va/termux-va.sock exists. A failed bridge activation returns a VA error so applications fall back to software decoding instead of crashing.
build-check.yml: the debian-trixie job now runs on pushes to test/add-va-bridge and workflow_dispatch (the other distro jobs stay pull_request-only; their clone step only knows the PR head ref), installs libva-dev, and enables the VA bridge in the packaged tar.gz: -Dgallium-va=enabled -Dtermux-va-bridge=enabled -Dvideo-codecs=all. The clone step falls back to the pushed branch when no PR ref exists. docs: add docs/termux-va.rst (building, activation, socket location, data path, troubleshooting) to the documentation index and document the bridge's environment variables (TERMUX_VA_BRIDGE, TERMUX_VA_SOCKET, TERMUX_VA_SOCKET_DIR, DMD_WANT_SHM, DMD_VA_LOG, LIBVA_DRIVER_NAME) in docs/envvars.rst.
lfdevs
force-pushed
the
test/add-va-bridge
branch
from
August 29, 2026 12:21
dc31abe to
893122f
Compare
…ction The stock loader path cannot create a screen on the kgsl stack: Xiaomi/ DroidSpaces kernels report the display controller's DRM node as "msm_drm" (no pipe_loader descriptor matches, so kmsro/zink fallbacks engage and fail - "ZINK: failed to choose pdev" - because the kgsl stack has no usable Vulkan device), and even a node reported as "msm" drives no GPU there. The VA frontend's vscreen creation failed before the bridge's codec ever came into play. tva_bridge_vscreen_create() now creates the underlying screen with a TERMUX_VA_GPU_BACKEND selection: - auto (default): stock loader selection, then the fork's "kgsl" freedreno alias, then llvmpipe over the null sw winsys. - kgsl: force the "kgsl" alias - pipe_loader re-points driver_name at the fork-registered descriptor whose device layer redirects GPU submission to /dev/kgsl-3d0 while keeping the handed fd as the control/identity fd (freedreno_device.c); FD_FORCE_KGSL=1 is set when unset, matching the EGL path (MESA_LOADER_DRIVER_OVERRIDE=kgsl). - drm: stock selection only. sw: llvmpipe only. The frontend's DRM/Wayland vscreen creation calls the bridge when it is active; the fd stays owned by libva (pipe_loader dups internally).
Install Termux VA capability and codec hooks directly on the real Gallium screen and multimedia context instead of creating memcpy-based wrapper objects that can be misinterpreted by native drivers. Move frame reception to a dedicated reader thread, track pending frames by input-unit identity, and keep surface copies on the application thread. Add reversible drain and explicit frame-release handling for inline and SHM transport. Support TERMUX_VA_GPU_BACKEND=sw through a null winsys and llvmpipe so the bridge can operate without KGSL, DRM, or Vulkan access. Initialize and release the GLSL type singleton in the Freedreno screen lifetime to prevent shader compilation from using a null GLSL linear allocation context.
Align the Termux VA documentation and bridge comments with the tested backend selection. The auto mode uses the stock DRM loader and falls back to llvmpipe, while KGSL is selected explicitly and sw forces llvmpipe. Document that the current Mesa bridge advertises H.264 and VP9 Profile 0 only, while HEVC parsing and CSD synthesis remain incomplete.
Synchronize the bridge reader and fence lifecycle, retain pending surface resources, and make teardown cancel the socket worker safely. Export KGSL bridge surfaces as linear DMA-BUF-backed resources with complete object metadata. The bridge now owns video capability queries, initializes KGSL DMA-BUF allocation before screen creation, and avoids advertising unsupported external timeline semaphore FDs to Vulkan clients on KGSL. This lets FFmpeg and ffplay consume exported VA surfaces while preserving the existing Unix-socket protocol.
lfdevs
force-pushed
the
test/add-va-bridge
branch
from
September 1, 2026 05:36
bb69993 to
3f29ee1
Compare
Keep complete H.264/HEVC access units together, retain pending resources until frame copies complete, and make CPU/Gallium uploads explicit for KGSL. Synchronize imported DMA-BUFs before Turnip use, add cache and barrier diagnostics, and document the pipeline and copy controls.
Synchronize imported dma-bufs across KGSL producer and consumer contexts, invalidate shared NV12 texture state, and retain pending VA frames until MediaCodec output arrives. Improve the lowered NV12 import path and synthesize H.264 parameter sets without an incorrect zero-reorder restriction while restoring reference defaults from the decoder DPB.
Preserve the VA H.264 profile when rebuilding SPS headers and emit the High-profile SPS and PPS fields required by MediaCodec. Keep zero PPS reference defaults for High streams while retaining the DPB fallback for the existing Main-profile path.
Use the active reference-list sizes reported by VA slice parameters when synthesizing Main-profile PPS headers. Keep the largest observed values in the codec so CSD updates converge during the first GOP instead of being regenerated for every frame.
Advertise HEVC Main through the termux-va bridge and synthesize the VPS, SPS, and PPS parameter sets required by MediaCodec from VA-API picture descriptors. Forward HEVC slice data unchanged and cache parameter sets so Chrome and FFmpeg can use the hardware decoder. Reject parameter sets whose reference-picture-set contents are not exposed by VA-API instead of sending malformed CSD.
Keep the short-term reference picture set count when synthesizing HEVC SPS headers. Qualcomm's decoder requires a non-zero SPS RPS count even when FFmpeg supplies each slice's RPS inline, while VA-API does not expose the set contents, so emit empty placeholders and reject slices that refer to unavailable SPS RPS data.
Advertise AV1 Main and route it through the termux-va codec. Reconstruct complete AV1 temporal units from VA-API picture and tile descriptors, including sequence headers, frame OBUs, tile groups, hidden reference frames, and refresh masks required by MediaCodec. Add shared H.264/HEVC bitstream helpers and AV1 OBU writers, and include the new sources in the VA frontend build.
Handle hidden AV1 pictures as output frames or synthesize show_existing_frame OBUs so MediaCodec returns reference surfaces needed by the VA bridge. Track per-picture pending entries and wait for recycled fences before detaching them, preventing late frames from overwriting surfaces reused by Chromium. Copy decoded linear resources directly into exported DMA-BUFs from the reader thread when possible, retain the Gallium transfer fallback, and avoid unnecessary flushes for CPU copies. Add configurable AV1 output modes, pending limits, fence wait timing, and tracing for diagnostics.
VAAPI does not expose H.264 PPS default reference-list counts. Infer them from effective slice parameters when synthesizing CSD, while keeping learned High-profile defaults stable across subsequent slices. This prevents Qualcomm MediaCodec from stalling on High@L4 streams after the initial IDR frames.
Open /dev/kgsl-3d0 when a VA display has no DRM fd and route screen creation through the KGSL Freedreno alias. Automatically allocate linear NV12 surfaces from a single dma-buf in KGSL-only containers, while preserving per-plane offsets for PRIME exports. Treat shared plane resources as one exported object when PRoot cannot compare file descriptions. Document the PRoot XWayland requirements and the contiguous dma-buf override.
Support Chromium hardware video decode in PRoot containers that expose /dev/kgsl-3d0 but no usable DRM render node. - Synchronize KGSL dma-buf cache ownership and imported resource handling. - Stabilize VA bridge staging, fence retirement, surface teardown, and VPP synchronization. - Support linear NV12 exports and planar DRI/GBM imports used by Chromium. - Make Freedreno compositor paths compatible with KGSL. - Propagate TERMUX_VA_GPU_BACKEND=kgsl through the loader, X11 DRI3, EGL, and GLX paths. - Document the DRM-less PRoot configuration and add bridge diagnostics.
Add the optional termux-va-wayland-shim Meson feature and build libtva_drm_shim_wayland.so for Chromium's native Wayland backend in DRM-less KGSL PRoot containers. The shim redirects Chromium's DRM discovery and version probes to /dev/kgsl-3d0 without replacing Mesa's KGSL backend. Enable the target in build-check workflows and document the required LD_PRELOAD launch configuration.
lfdevs
force-pushed
the
test/add-va-bridge
branch
from
September 9, 2026 03:51
76697c0 to
a5e3b8f
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Based on DroidSpaces Media Decode Daemon by Re-s, this branch adds a termux-va bridge to the VA frontend: VA-API video decoding is forwarded over a Unix socket to the
termux-vadaemon running in Termux, which decodes with the Android MediaCodec API in hardware and returns NV12 frames. Applications inside a Linux container that shares Termux's tmp directory (proot-distro ... --shared-tmp) get hardware decoding through the standard VA-API without any modification — ffmpeg, Firefox and Chrome work unmodified.This follows the porting model established by anland-termux: a Termux daemon, a Unix socket placed in the shared tmp directory, and a bridge on the container side. The wire protocol is byte-compatible with droidspaces-media-decode protocol v3 (
HELLO_MAGIC 0x444D4400), which lets us reuse its protocol regression tooling.The daemon itself lives in the companion repository
lfdevs/termux-va(NDK r29, 29.0.14206865, same pin as anland-termux); it listens on$TMPDIR/termux-va/termux-va.sock, which appears as/tmp/termux-va/termux-va.sockinside the container.How it works
The 26.x VA frontend already parses VA buffers and prepends H.264/HEVC start codes to slice data, so the bridge is thin:
tva_bridge.c):get_video_param/is_video_format_supportedadvertise the bridge codec set; everything else forwards to the real screen.create_video_codechands bitstream decode to the bridge;create_video_buffer(_with_modifiers)falls back to the genericvl_video_buffer_createhelper, because the underlying drivers have no video path (freedreno/llvmpipe).decode_bitstreamaccumulates the frontend's slice data;end_framesplits it into Annex B units (one NALU per daemon length prefix, parameter sets excluded from the unit index exactly like the daemon), sends them and returns — the pending pipeline depth is capped at 6 to stay within the daemon's 8-slot SHM pool.fence_wait(called byvaSyncSurface): frames are pumped into staging buffers, paired with the picture by the daemon's unit index, and the visible (cropped) region is copied into the surface's plane resources on the caller's thread, honoring the decoder's stride/slice-height geometry (Venus aligns buffers to 128x32).Everything runs on the application thread — no bridge-owned threads touch
pipe_context. Frames come back inline on the socket or zero-copy through a memfd slot pool handed over viaSCM_RIGHTS(the daemon-side copy from the gralloc output buffer remains, as upstream).Codec support and known limitations
vaDeriveImageis not available (plane resources are separate textures,SUPPORTS_CONTIGUOUS_PLANES_MAPis not claimed); CPU consumers can usevaGetImage.vaExportSurfaceHandledepends on the underlying screen's dma-buf export; decode itself never touches the GPU.Activation (runtime-gated)
A build with the bridge behaves exactly like an unmodified one until activated:
TERMUX_VA_BRIDGE=1forces it on,0forces it off.TERMUX_VA_SOCKET/TERMUX_VA_SOCKET_DIRis set, or when the default endpoint exists as a socket.TERMUX_VA_SOCKET(full path) >TERMUX_VA_SOCKET_DIR(directory) >/tmp/termux-va/termux-va.sock, read throughos_get_optionso Android system properties work as a fallback. The same variables are understood by the daemon.LIBVA_DRIVER_NAME=termuxva(the megadriver is additionally exposed astermuxva_drv_video.so).Build & CI
termux-va-bridge(defaultauto). The bridge sources are compiled intolibva_stunless disabled; the option also relaxes the VA state tracker's gallium-driver whitelist so a freedreno-only build can enablegallium-vafor the bridge.h264dec/h265dec/vp9decinvideo-codecs(e.g.-Dvideo-codecs=all) for the frontend's parsers.docs/termux-va.rst(index-linked) and the bridge's environment variables indocs/envvars.rst.