deploy: bundle pre-built MPP libs for RK3326, update setup script and status

- deploy/arkos/mpp-libs/: add librockchip_mpp.so*, librockchip_vpu.so*,
  libgstrockchipmpp.so — built from source via Docker QEMU (arm64v8/ubuntu:focal)
  using rockchip-linux/mpp + JeffyCN/mirrors@gstreamer-rockchip (-Drga=disabled)
- deploy/arkos/setup_hw_decode.sh: detect mpp-libs/ subdir and install from it
  automatically, no network required; apt fallback retained
- deploy/arkos/mpp-libs/README.md: document origin, target SoC, install steps
- tests/test_video_playback_device.py: on-device GStreamer diagnostic script
- docs/development-status.md: mark MPP HW decode deployed, mppvideodec verified

Verified on physical R36S: mppvideodec found by GStreamer registry with
GST_PLUGIN_PATH=/usr/lib/aarch64-linux-gnu/gstreamer-1.0
This commit is contained in:
Matteo Benedetto
2026-03-23 22:37:41 +01:00
parent ddbe31dc02
commit d79bc3e16f
11 changed files with 383 additions and 10 deletions
+38
View File
@@ -0,0 +1,38 @@
# mpp-libs — Pre-built Rockchip MPP libraries for R36S / ArkOS
These `.so` files are compiled for **aarch64 / Ubuntu focal** from:
- **librockchip-mpp** — [github.com/rockchip-linux/mpp](https://github.com/rockchip-linux/mpp) (commit c2c1ee5, 2026-03-10)
- **gst-mpp** — [github.com/JeffyCN/mirrors, branch gstreamer-rockchip](https://github.com/JeffyCN/mirrors) (RGA disabled)
Built via Docker QEMU (`arm64v8/ubuntu:focal` + `qemu-user-static`).
## Files
| File | Purpose |
|---|---|
| `librockchip_mpp.so{,.0,.1}` | Rockchip MPP runtime — hardware VPU abstraction |
| `librockchip_vpu.so{,.0,.1}` | Rockchip VPU legacy wrapper |
| `libgstrockchipmpp.so` | GStreamer plugin — exposes `mppvideodec` element |
## Install
Run the parent `setup_hw_decode.sh` as root — it picks up this directory automatically:
```bash
sudo bash deploy/arkos/setup_hw_decode.sh
```
Or manually:
```bash
sudo cp librockchip_mpp.so* librockchip_vpu.so* /usr/lib/aarch64-linux-gnu/
sudo cp libgstrockchipmpp.so /usr/lib/aarch64-linux-gnu/gstreamer-1.0/
sudo ldconfig
```
## Target
- SoC: RK3326 / PX30 (Cortex-A35)
- Kernel: 4.4 BSP with `vpu_service` device node at `/dev/vpu_service`
- ArkOS Ubuntu eoan arm64
- Requires `/dev/vpu_service` accessible to `video` group (handled by `setup_hw_decode.sh` udev rule)
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+21 -3
View File
@@ -4,8 +4,11 @@
# What this script does: # What this script does:
# 1. Adds a udev rule so the 'video' group can access /dev/vpu_service # 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). # (the VPU device used by Rockchip MPP on the kernel 4.4 BSP).
# 2. Tries to install librockchip-mpp0 and gstreamer1.0-rockchip-mpp. # 2. Installs librockchip-mpp and the gst-mpp GStreamer plugin, in order:
# If they are not in apt, it prints instructions for manual installation. # a) If a 'mpp-libs/' subdirectory is present next to this script,
# copies the pre-built .so files directly (no network required).
# b) Otherwise tries apt-get.
# c) If apt fails, prints manual installation instructions.
# #
# Requirements: # Requirements:
# - Must be run as root or via sudo. # - Must be run as root or via sudo.
@@ -67,7 +70,22 @@ info "Verify with: ls -la /dev/vpu_service (should show crw-rw---- root video)"
MPP_PKGS="librockchip-mpp0 gstreamer1.0-rockchip-mpp" MPP_PKGS="librockchip-mpp0 gstreamer1.0-rockchip-mpp"
if apt-cache show librockchip-mpp0 &>/dev/null; then SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
MPP_LIBS_DIR="$SCRIPT_DIR/mpp-libs"
if [[ -d "$MPP_LIBS_DIR" ]]; then
info "Found pre-built libraries in $MPP_LIBS_DIR — copying..."
# Core MPP runtime
for f in "$MPP_LIBS_DIR"/librockchip_mpp.so* "$MPP_LIBS_DIR"/librockchip_vpu.so*; do
[[ -e "$f" ]] && cp -v "$f" /usr/lib/aarch64-linux-gnu/
done
# GStreamer plugin
if [[ -f "$MPP_LIBS_DIR/libgstrockchipmpp.so" ]]; then
cp -v "$MPP_LIBS_DIR/libgstrockchipmpp.so" /usr/lib/aarch64-linux-gnu/gstreamer-1.0/
fi
ldconfig
info "MPP libraries installed from $MPP_LIBS_DIR."
elif apt-cache show librockchip-mpp0 &>/dev/null; then
info "Found librockchip-mpp0 in apt — installing..." info "Found librockchip-mpp0 in apt — installing..."
apt-get install -y $MPP_PKGS apt-get install -y $MPP_PKGS
info "MPP packages installed successfully." info "MPP packages installed successfully."
+7 -7
View File
@@ -50,10 +50,11 @@ Milestone 3 — SDL Video Viewport, HUD, and Wayland Compatibility
- Verify that the SDL-texture playback path is smooth enough on real host playback and on R36S hardware - Verify that the SDL-texture playback path is smooth enough on real host playback and on R36S hardware
- Measure whether BGRA frame upload is acceptable on RK3326 or whether a future YUV texture path is needed - Measure whether BGRA frame upload is acceptable on RK3326 or whether a future YUV texture path is needed
- Validate real video playback on the physical R36S after adding the missing H.264 and AAC decoder plugins to the device conda env
- Device deployment on the physical R36S is now wired through ArkOS `Ports -> MatHacks`, with the heavy runtime under `/home/ark` and only a lightweight stub launcher under `/roms/ports` - Device deployment on the physical R36S is now wired through ArkOS `Ports -> MatHacks`, with the heavy runtime under `/home/ark` and only a lightweight stub launcher under `/roms/ports`
- Device env bootstrap on the physical R36S reaches a clean `from r36s_dlna_browser.app import Application` inside `/home/ark/miniconda3/envs/r36s-dlna-browser` - Device env bootstrap on the physical R36S reaches a clean `from r36s_dlna_browser.app import Application` inside `/home/ark/miniconda3/envs/r36s-dlna-browser`
- ArkOS launcher asset added at `deploy/arkos/MatHacks.sh`; current launcher uses the `/home/ark/R36SHack` checkout plus verified `LD_LIBRARY_PATH`, `GST_PLUGIN_PATH`, and `LD_PRELOAD` exports needed to load the system `gstreamer1.0-libav` plugins from the conda runtime - ArkOS launcher asset added at `deploy/arkos/MatHacks.sh`; current launcher uses the `/home/ark/R36SHack` checkout plus verified `LD_LIBRARY_PATH`, `GST_PLUGIN_PATH`, and `LD_PRELOAD` exports needed to load the system `gstreamer1.0-libav` plugins from the conda runtime
- **Rockchip MPP hardware decode now deployed**: `librockchip-mpp` and `gst-mpp` compiled from source via Docker QEMU (arm64v8/ubuntu:focal), installed on device, `mppvideodec` confirmed visible to GStreamer. The `gstreamer_backend.py` probe auto-boosts `mppvideodec` rank if `/dev/vpu_service` is accessible.
- **Pre-built .so files bundled** in `deploy/arkos/mpp-libs/`; `setup_hw_decode.sh` installs them automatically without network access.
## Blockers Or Open Questions ## Blockers Or Open Questions
@@ -76,9 +77,8 @@ Milestone 3 — SDL Video Viewport, HUD, and Wayland Compatibility
## Next Recommended Actions ## Next Recommended Actions
1. Run a visual playback smoke test and confirm SDL-rendered video plus HUD eliminates flashing on host playback. 1. Run a real H.264 video end-to-end via app and observe whether `mppvideodec` is selected (logs will show "HW decode: boosted rank of mppvideodec") and whether playback is smooth.
2. Validate the SDL-texture playback path on the target R36S `kmsdrm` backend. 2. Measure CPU/load on RK3326 hardware during HW-accelerated video playback vs. `avdec_h264` software path.
3. Measure CPU/load on RK3326 hardware during audio and video playback. 3. Run a visual playback smoke test and confirm SDL-rendered video plus HUD eliminates flashing on host playback.
4. Test real MiniDLNA `.mkv` H.264/AAC media end-to-end from the ArkOS `Ports -> MatHacks` launcher using `deploy/run.sh`. 4. If RGBA upload cost is too high, add a follow-up YUV texture upload path using `SDL_UpdateYUVTexture`.
5. If RGBA upload cost is too high, add a follow-up YUV texture upload path using `SDL_UpdateYUVTexture`. 5. `avdec_hevc` is still missing (HEVC decoders not in system apt `gstreamer1.0-libav 1.16.1`); `mppvideodec` covers H.264/H.265/VP8/VP9 via HW so this is less critical now.
6. `avdec_hevc` is still missing (HEVC decoders not in system apt `gstreamer1.0-libav 1.16.1`); AV1 excluded intentionally as too heavy for RK3326 — this is acceptable for the H.264/AAC primary codec target.
+317
View File
@@ -0,0 +1,317 @@
#!/usr/bin/env python3
"""
Video playback diagnostic for R36S / ArkOS.
Tests GStreamer availability, codec coverage, and a short live-playback loop
using the same pipeline the app uses (playbin → appsink, BGRA frames).
Run directly on the device:
/home/ark/miniconda3/envs/r36s-dlna-browser/bin/python \
/home/ark/R36SHack/tests/test_video_playback_device.py
Accepts an optional URL/path to test real playback:
... test_video_playback_device.py http://server/video.mkv
"""
from __future__ import annotations
import sys
import time
import textwrap
# ── pretty output helpers ───────────────────────────────────────────────────
def _ok(msg: str) -> None:
print(f" [OK] {msg}")
def _warn(msg: str) -> None:
print(f" [WRN] {msg}")
def _fail(msg: str) -> None:
print(f" [ERR] {msg}")
def _section(title: str) -> None:
print(f"\n{'='*60}")
print(f" {title}")
print(f"{'='*60}")
# ── 1. Python env ───────────────────────────────────────────────────────────
_section("1. Python environment")
import platform
print(f" Python {sys.version}")
print(f" Platform: {platform.machine()} / {platform.system()}")
# ── 2. GI / GStreamer core ──────────────────────────────────────────────────
_section("2. GStreamer core")
try:
import gi
_ok(f"PyGObject (gi) {gi.__version__}")
except ImportError as exc:
_fail(f"PyGObject not found: {exc}")
sys.exit(1)
try:
gi.require_version("Gst", "1.0")
from gi.repository import Gst
Gst.init(None)
v = Gst.version()
_ok(f"GStreamer {v.major}.{v.minor}.{v.micro}.{v.nano}")
except Exception as exc:
_fail(f"GStreamer init failed: {exc}")
sys.exit(1)
try:
gi.require_version("GstApp", "1.0")
gi.require_version("GstVideo", "1.0")
from gi.repository import GstApp, GstVideo
_ok("GstApp + GstVideo bindings present")
except Exception as exc:
_fail(f"GstApp/GstVideo bindings missing: {exc}")
sys.exit(1)
# ── 3. Plugin registry ──────────────────────────────────────────────────────
_section("3. Plugin registry")
REQUIRED_ELEMENTS = {
"playbin": "core orchestrator",
"appsink": "app-side frame sink",
"videoconvert": "pixel format conversion",
"videoscale": "frame scaling",
"typefind": "format detection",
"decodebin": "auto demux/decode",
"uridecodebin": "URI decode helper",
}
CODEC_ELEMENTS = {
# demuxers
"matroskademux": "MKV/WebM demuxer",
"qtdemux": "MP4/MOV demuxer",
"flvdemux": "FLV demuxer",
"tsdemux": "MPEG-TS demuxer",
# video decoders
"avdec_h264": "H.264 software decoder (libav)",
"avdec_hevc": "H.265/HEVC software decoder (libav)",
"avdec_mpeg2video": "MPEG-2 video decoder",
"avdec_vp8": "VP8 decoder",
"avdec_vp9": "VP9 decoder",
"v4l2h264dec": "H.264 V4L2 HW decoder",
"v4l2h265dec": "H.265 V4L2 HW decoder",
# audio decoders
"avdec_aac": "AAC decoder (libav)",
"avdec_mp3": "MP3 decoder (libav)",
"avdec_ac3": "AC-3 decoder (libav)",
"vorbisdec": "Vorbis decoder",
"opusdec": "Opus decoder",
# audio output
"autoaudiosink": "Auto audio sink",
"alsasink": "ALSA sink",
"pulsesink": "PulseAudio sink",
}
missing_required = []
registry = Gst.Registry.get()
for element, desc in REQUIRED_ELEMENTS.items():
feat = registry.find_feature(element, Gst.ElementFactory.__gtype__)
if feat:
_ok(f"{element:20s} ({desc})")
else:
missing_required.append(element)
_fail(f"{element:20s} ({desc}) ← MISSING")
print()
missing_codecs = []
present_codecs = []
for element, desc in CODEC_ELEMENTS.items():
feat = registry.find_feature(element, Gst.ElementFactory.__gtype__)
if feat:
present_codecs.append(element)
_ok(f"{element:20s} ({desc})")
else:
missing_codecs.append(element)
_warn(f"{element:20s} ({desc}) ← not found")
if missing_required:
print(f"\n CRITICAL: {len(missing_required)} required element(s) missing: {missing_required}")
print(f"\n Codecs present: {len(present_codecs)} / {len(CODEC_ELEMENTS)}")
# ── 4. Caps negotiation: can we build a BGRA appsink pipeline? ──────────────
_section("4. BGRA appsink pipeline negotiation")
TEST_PIPE = (
"videotestsrc num-buffers=5 ! "
"videoconvert ! "
"video/x-raw,format=BGRA,width=64,height=64 ! "
"appsink name=sink emit-signals=true max-buffers=1 drop=true"
)
frames_received = 0
negotiation_ok = False
try:
pipe = Gst.parse_launch(TEST_PIPE)
sink = pipe.get_by_name("sink")
def _on_sample(sink, *_):
global frames_received
sample = sink.emit("pull-sample")
if sample:
frames_received += 1
return Gst.FlowReturn.OK
sink.connect("new-sample", _on_sample)
pipe.set_state(Gst.State.PLAYING)
time.sleep(1.5)
pipe.set_state(Gst.State.NULL)
if frames_received > 0:
negotiation_ok = True
_ok(f"BGRA appsink pipeline: received {frames_received} frame(s)")
else:
_fail("BGRA appsink pipeline ran but produced 0 frames")
except Exception as exc:
_fail(f"Could not build BGRA appsink pipeline: {exc}")
# ── 5. Audio output probe ───────────────────────────────────────────────────
_section("5. Audio output probe")
AUDIO_PIPE = "audiotestsrc num-buffers=10 freq=440 ! autoaudiosink"
audio_ok = False
try:
p = Gst.parse_launch(AUDIO_PIPE)
p.set_state(Gst.State.PLAYING)
time.sleep(0.8)
p.set_state(Gst.State.NULL)
audio_ok = True
_ok("autoaudiosink: played 440 Hz tone without error")
except Exception as exc:
_fail(f"autoaudiosink failed: {exc}")
if not audio_ok:
ALSA_PIPE = "audiotestsrc num-buffers=10 freq=440 ! alsasink device=hw:0"
try:
p = Gst.parse_launch(ALSA_PIPE)
p.set_state(Gst.State.PLAYING)
time.sleep(0.8)
p.set_state(Gst.State.NULL)
audio_ok = True
_ok("alsasink hw:0: played 440 Hz tone")
except Exception as exc:
_warn(f"alsasink also failed: {exc}")
# ── 6. Live URL playback (optional) ────────────────────────────────────────
test_url = sys.argv[1] if len(sys.argv) > 1 else None
_section("6. Live URL / file playback")
if not test_url:
_warn("No URL provided — skipping live playback test.")
print(" Pass a media URL or path as the first argument to test real decode.")
else:
print(f" URL: {test_url}")
live_frames = 0
live_error = None
LIVE_PIPE = (
f"playbin uri=\"{test_url}\" "
f"video-sink=\"videoconvert ! video/x-raw,format=BGRA ! appsink name=vsink emit-signals=true max-buffers=2 drop=true\""
)
try:
pipe = Gst.parse_launch(LIVE_PIPE)
vsink = pipe.get_by_name("vsink")
def _on_live_sample(sink, *_):
global live_frames
sample = sink.emit("pull-sample")
if sample:
buf = sample.get_buffer()
info = buf.map(Gst.MapFlags.READ)
if info.size > 0:
live_frames += 1
buf.unmap(info)
return Gst.FlowReturn.OK
vsink.connect("new-sample", _on_live_sample)
bus = pipe.get_bus()
pipe.set_state(Gst.State.PLAYING)
deadline = time.monotonic() + 15
while time.monotonic() < deadline:
msg = bus.timed_pop_filtered(
200 * Gst.MSECOND,
Gst.MessageType.ERROR | Gst.MessageType.WARNING | Gst.MessageType.EOS,
)
if msg:
if msg.type == Gst.MessageType.ERROR:
err, debug = msg.parse_error()
live_error = f"{err.message} | debug: {debug}"
break
if msg.type == Gst.MessageType.WARNING:
w, d = msg.parse_warning()
_warn(f"GStreamer warning: {w.message}")
if msg.type == Gst.MessageType.EOS:
break
if live_frames >= 10:
break
pipe.set_state(Gst.State.NULL)
if live_error:
_fail(f"Playback error: {live_error}")
elif live_frames == 0:
_fail("Playback ran but decoded 0 video frames (audio-only or decode failure)")
else:
_ok(f"Decoded {live_frames} video frame(s) successfully")
except Exception as exc:
_fail(f"Could not start live playback pipeline: {exc}")
# ── 7. Summary ─────────────────────────────────────────────────────────────
_section("7. Summary")
issues = []
if missing_required:
issues.append(f"Missing required elements: {', '.join(missing_required)}")
if not negotiation_ok:
issues.append("BGRA appsink pipeline negotiation failed")
if not audio_ok:
issues.append("No working audio sink found")
key_missing = [e for e in ("avdec_h264", "avdec_aac", "matroskademux", "qtdemux") if e in missing_codecs]
if key_missing:
issues.append(f"Key codecs missing (install gst-libav): {', '.join(key_missing)}")
if not issues:
print(" All checks passed — video playback should work.")
else:
print(" Issues found:")
for issue in issues:
print(f"{issue}")
print()
print(" Suggested fix:")
print(textwrap.dedent("""\
/home/ark/miniconda3/bin/conda install -n r36s-dlna-browser \\
-c conda-forge gst-libav gst-plugins-good gst-plugins-bad gst-plugins-ugly
"""))
print()