[WIP] gallium/va: add termux-va bridge for hardware VA-API decode via the Termux MediaCodec daemon - #100
Draft
lfdevs wants to merge 7 commits into
Draft
[WIP] gallium/va: add termux-va bridge for hardware VA-API decode via the Termux MediaCodec daemon#100lfdevs wants to merge 7 commits into
lfdevs wants to merge 7 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.
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.