player: probe Rockchip MPP/V4L2 HW decoders at pipeline init, add setup_hw_decode.sh

This commit is contained in:
Matteo Benedetto
2026-03-23 21:42:25 +01:00
parent 544ed8bc6d
commit ddbe31dc02
2 changed files with 159 additions and 0 deletions
+89
View File
@@ -0,0 +1,89 @@
#!/bin/bash
# setup_hw_decode.sh — Enable Rockchip hardware video decoding on ArkOS / R36S
#
# What this script does:
# 1. Adds a udev rule so the 'video' group can access /dev/vpu_service
# (the VPU device used by Rockchip MPP on the kernel 4.4 BSP).
# 2. Tries to install librockchip-mpp0 and gstreamer1.0-rockchip-mpp.
# If they are not in apt, it prints instructions for manual installation.
#
# Requirements:
# - Must be run as root or via sudo.
# - Internet access (for apt-get).
# - Target: RK3326 / PX30 device running ArkOS with Ubuntu eoan arm64.
#
# After this script succeeds, restart the DLNA browser app. The GStreamer
# backend will detect mppvideodec and prefer it over software avdec_h264.
set -euo pipefail
info() { echo "[setup-hw-decode] $*"; }
warn() { echo "[setup-hw-decode] WARNING: $*" >&2; }
die() { echo "[setup-hw-decode] ERROR: $*" >&2; exit 1; }
[[ $EUID -eq 0 ]] || die "This script must be run as root (use sudo)."
# ---------------------------------------------------------------------------
# 1. udev rule — /dev/vpu_service accessible to 'video' group
# ---------------------------------------------------------------------------
UDEV_RULE="/etc/udev/rules.d/50-vpu-service.rules"
info "Writing udev rule: $UDEV_RULE"
cat > "$UDEV_RULE" <<'EOF'
# Rockchip VPU service — allow the 'video' group to access the hardware encoder/decoder.
KERNEL=="vpu_service", GROUP="video", MODE="0660"
KERNEL=="mpp_service", GROUP="video", MODE="0660"
EOF
udevadm control --reload-rules
udevadm trigger --name-match=vpu_service 2>/dev/null || true
udevadm trigger --name-match=mpp_service 2>/dev/null || true
info "/dev/vpu_service access granted to 'video' group."
info "Verify with: ls -la /dev/vpu_service (should show crw-rw---- root video)"
# ---------------------------------------------------------------------------
# 2. librockchip-mpp0 + gstreamer1.0-rockchip-mpp
# ---------------------------------------------------------------------------
#
# These packages are NOT in the standard Ubuntu eoan repos.
# Sources known to carry arm64 builds for RK3326/PX30:
#
# A) ODROID community apt mirror (requires adding the source manually):
# deb https://oph.mdrjr.net/meveric/ stretch main
# apt-key adv --keyserver keyserver.ubuntu.com --recv-keys A0A3B33F
#
# B) Pre-built .deb files from the OGA / ArkOS forum / Discord.
# Typical package names:
# librockchip-mpp0_1.x.x_arm64.deb
# gstreamer1.0-rockchip-mpp_1.x.x_arm64.deb
#
# C) Build from source (advanced):
# https://github.com/rockchip-linux/mpp
# https://github.com/JeffyCN/gst-mpp
#
# This script tries apt-get first; if the package is not found it falls back
# to printing the manual instructions and exits with success (non-blocking).
MPP_PKGS="librockchip-mpp0 gstreamer1.0-rockchip-mpp"
if apt-cache show librockchip-mpp0 &>/dev/null; then
info "Found librockchip-mpp0 in apt — installing..."
apt-get install -y $MPP_PKGS
info "MPP packages installed successfully."
else
warn "librockchip-mpp0 is not available in the current apt sources."
echo
echo " To enable hardware H.264/H.265/VP8/VP9 decoding, install these arm64 packages:"
echo " librockchip-mpp0 — Rockchip MPP runtime library"
echo " gstreamer1.0-rockchip-mpp — GStreamer plugin that exposes mppvideodec"
echo
echo " Option A — Add a community apt source with RK3326 support, then re-run this script."
echo " Option B — Download pre-built .deb files and run:"
echo " sudo dpkg -i librockchip-mpp0_*.deb gstreamer1.0-rockchip-mpp_*.deb"
echo
echo " The udev rule above has already been applied regardless."
echo " Once the packages are installed, restart the DLNA browser app."
fi
info "Done."
@@ -25,6 +25,72 @@ log = logging.getLogger(__name__)
Gst.init(None)
# GStreamer element names for hardware video decoders, in priority order.
# mppvideodec: Rockchip MPP plugin (gst-mpp), uses /dev/vpu_service on kernel 4.4 BSP.
# v4l2*dec: Linux V4L2 stateful decoders, available on kernel 5.4+ (rkvdec/hantro).
_HW_DECODER_ELEMENTS = [
"mppvideodec",
"v4l2h264dec",
"v4l2h265dec",
"v4l2vp8dec",
"v4l2vp9dec",
]
# VPU device nodes that must be accessible for hardware decode on RK3326.
_HW_VPU_DEVICES = [
"/dev/vpu_service", # kernel 4.4 BSP (RK3326 / PX30)
"/dev/mpp_service", # newer BSP variants
"/dev/video10", # V4L2 vcodec on mainline/5.10+ kernels
"/dev/video11",
]
def _vpu_device_accessible() -> bool:
"""Return True if at least one VPU device node can be opened by this process."""
for path in _HW_VPU_DEVICES:
try:
fd = os.open(path, os.O_RDWR | os.O_NONBLOCK)
os.close(fd)
log.info("HW decode: VPU device accessible: %s", path)
return True
except OSError:
pass
return False
def _probe_hw_decoders(gst_module) -> list:
"""
Probe for available hardware video decoder GStreamer elements and boost
their rank so that playbin's internal decodebin prefers them over software
decoders. Returns the list of element names that were found and boosted.
If no VPU device node is accessible the probe is skipped entirely so that
a half-initialised hardware element cannot stall the pipeline.
"""
if not _vpu_device_accessible():
log.info(
"HW decode: no accessible VPU device node — using software decode. "
"Run deploy/arkos/setup_hw_decode.sh as root to fix permissions."
)
return []
found = []
for name in _HW_DECODER_ELEMENTS:
factory = gst_module.ElementFactory.find(name)
if factory is not None:
try:
factory.set_rank(gst_module.Rank.PRIMARY + 1)
found.append(name)
log.info("HW decode: boosted rank of %s", name)
except Exception as exc: # pragma: no cover
log.warning("HW decode: could not boost rank of %s: %s", name, exc)
if not found:
log.info(
"HW decode: VPU device is accessible but no gst-mpp / V4L2 plugin found. "
"Run deploy/arkos/setup_hw_decode.sh to install gstreamer1.0-rockchip-mpp."
)
return found
def _wm_subsystem_name(subsystem: int) -> str:
mapping = {
@@ -116,6 +182,7 @@ class GStreamerBackend(PlayerBackend):
self._texture_renderer = None
self._texture_size = (0, 0)
self._resolution = ""
self._hw_decoders: list | None = None # None = not yet probed
def attach_window(self, window: object) -> None:
self._window = window
@@ -259,6 +326,9 @@ class GStreamerBackend(PlayerBackend):
if self._pipeline is not None:
return self._pipeline
if self._hw_decoders is None:
self._hw_decoders = _probe_hw_decoders(self._gst)
self._pipeline = self._playbin_factory()
self._configure_playbin_flags(self._pipeline)
self._video_sink = self._create_appsink()