40 Commits

Author SHA1 Message Date
Matteo Benedetto fe39312cfa perf: HUDTextCache — reuse text SDL textures, skip when HUD hidden 2026-03-24 12:24:42 +01:00
Matteo Benedetto 6ef8f4722c test: add section 9 HUD overhead benchmark (draw_playback timing) 2026-03-24 12:15:57 +01:00
Matteo Benedetto 9db1bfc0ba test: fix section 6 map unpack; warmup=30; add p95 to timing stats
- Section 6 _on_live_sample: buf.map() returns (ok, map_info) tuple;
  accessing .size on the tuple caused AttributeError for every frame
  (22 tracebacks in log). Fixed to unpack properly.

- Section 8 WARMUP raised from 5 to 30 frames (~1.25 s) so cold-DRM
  DMA setup, network buffer fill, and lazy texture init all complete
  before stats are recorded. Eliminates the 36ms first-upload spike
  from post-warmup measurements.

- _stat() now shows mean / p95 / max so isolated spikes are visible
  without inflating the headline figure.
2026-03-24 11:56:37 +01:00
Matteo Benedetto 790f001f4e fix: scale to 16:9 target box with add-borders to preserve source DAR
GStreamer caps fixation always picks the identity value for unconstrained
dimensions (width-only caps keeps source height unchanged, giving 640x1080
instead of 640x360 for a 1920x1080 source).

Fix: compute a 16:9 output box that fits inside the video area, use both
width and height in the capsfilter, and set add-borders=True so GStreamer
letterboxes or pillarboxes any non-16:9 source without distortion.

For the test device (720x720 KMSDRM, ~120px HUD):
  video area: ~720x600 → scale target: 720x404 (16:9)
For default viewport (640x480):
  video area: 640x480 → scale target: 640x360 (16:9)

Section 8 test updated to mirror the same 16:9+add-borders strategy.
2026-03-24 11:51:06 +01:00
Matteo Benedetto 761707b45a test: section 8 — lazy texture + width-only NV12 caps (matches app AR fix)
- Remove fixed SDL8_SCALE_H; capsfilter now uses width-only (same as app)
  so GStreamer derives height from source DAR.
- Texture created lazily on first frame with correct dimensions instead of
  a fixed 640x480 that would mismatch an AR-preserving 640x360 frame.
- SDL_RenderCopy now letterboxes the frame into the window (preserves AR)
  instead of stretching to fill, matching what _fit_frame_to_viewport does.
- [texture] log line reports actual w x h and AR ratio for verification.
2026-03-24 11:43:36 +01:00
Matteo Benedetto 0c080bc994 fix: omit height from NV12 capsfilter so GStreamer preserves source DAR
A height range like (int)[2,2160] includes the source height (1080),
so GStreamer's caps fixation picked identity for height (no scale) and
only scaled width 1920->720, giving a distorted 720x1080 frame.

Fix: omit height entirely from the capsfilter caps string.
GStreamer then derives the output height from the source's display
aspect ratio for the given width target.  NV12's even-dimension
requirement is satisfied automatically by caps fixation rounding.
2026-03-24 11:40:12 +01:00
Matteo Benedetto f1e7448aad fix: preserve AR by constraining only width in NV12 videoscale capsfilter
Constraining both width AND height caused GStreamer to stretch the video
to fill the target box, distorting the aspect ratio when the box was not
the same AR as the source (e.g. 720x600 vs a 16:9 source).

Fix: only constrain width in the capsfilter (height=(int)[2,2160]).
GStreamer then picks the height from the source's native DAR, naturally
preserving aspect ratio without relying on add-borders.
_fit_frame_to_viewport centres the resulting frame in the SDL viewport.
2026-03-24 11:28:50 +01:00
Matteo Benedetto 16d3ccaa8e fix: preserve aspect ratio in NV12 videoscale (add-borders, video area target)
- Scale target is now the actual video area (window minus HUD margins)
  instead of the full window size; dimensions rounded to even for NV12.
- Set add-borders=True so videoscale letterboxes/pillarboxes the source
  rather than stretching it, preserving the original aspect ratio.
- Add pixel-aspect-ratio=1/1 in capsfilter so downstream treats output
  pixels as square and _fit_frame_to_viewport works correctly.
2026-03-24 11:20:09 +01:00
Matteo Benedetto c334bfcc83 test,docs: fix section 6 vsink ref; update docs with SDL timing results and RenderPresent root cause 2026-03-24 11:02:54 +01:00
Matteo Benedetto ac7aa9146d test: add section 8 — end-to-end SDL NV12 render loop with per-phase timing (memmove+upload+render) 2026-03-24 11:00:52 +01:00
Matteo Benedetto 528c7f94e7 docs: update benchmark log with full videoscale optimization history and results 2026-03-24 10:56:53 +01:00
Matteo Benedetto 995830e3d2 player,bench: drop queue from vscale-bin (leaky=2 caused massive drops), keep nearest-neighbour 2026-03-24 10:54:02 +01:00
Matteo Benedetto 65665f4cff player,bench: add queue+nearest-neighbour before videoscale to prevent pipeline stalls 2026-03-24 10:49:14 +01:00
Matteo Benedetto 435bd51bbe bench: add videoscale GstBin path (mirrors _create_appsink, --noscale flag) 2026-03-24 10:42:15 +01:00
Matteo Benedetto 67224626a5 perf: insert videoscale before appsink to cut NV12 memmove 6.7×
When hardware decode (mppvideodec/NV12) is active, wrap the appsink in a
GstBin with a videoscale element so the VPU decodes at full stream
resolution but Python only receives a frame pre-scaled to the SDL display
size (default 640x480).

Effect:
  NV12 buffer per frame: 3,133,440 B (1080p) → 460,800 B (640x480)
  memmove per frame:     ~33 ms (80.5% budget) → ~5 ms (expected ~12%)

The videoscale bilinear step runs entirely in software on the A35 cores
but scales down 6.7×, so its cost is far lower than the avoided memmove.
SDL still handles final aspect-ratio fitting inside the viewport, so
visual quality is unchanged relative to what the 640x480 display can show.

Fallback: if videoscale is not available, unscaled NV12 is used as before.
2026-03-24 10:33:33 +01:00
Matteo Benedetto eaffa0026d docs: record NV12 benchmark results and next-step analysis
Add benchmark log table to development-status.md comparing:
  - a201594: extract_dup+from_buffer_copy (2 copies, 6MB/frame) → 36.5ms, 87.6% budget
  - da02e74: buffer.map+memmove into reusable ctypes array (1 copy, 3MB/frame) → 33.6ms, 80.5% budget

Note that the 3.1MB memmove is now the remaining bottleneck and further
reduction would require DMA-buf zero-copy via kernel VPU driver support.

Update next actions: profile SDL upload overhead, explore dmabuf fd path,
and consider 720p downscale option if stutter appears under combined load.
2026-03-24 01:14:04 +01:00
Matteo Benedetto da02e7446f perf: replace extract_dup+from_buffer_copy with buffer.map+memmove zero-copy
Instead of extract_dup (GLib alloc+memcpy → Python bytes) followed by
from_buffer_copy (Python bytes → ctypes array) — two 3MB copies per frame —
use Gst.Buffer.map(READ) to get a zero-allocation pointer to the decoded
frame memory, then memmove directly into a pre-allocated reusable ctypes
array (_raw_arr).

This reduces the per-frame copy path from 2 copies (6MB) to 1 memmove
(3MB), with no Python bytes object allocation at all.  The memmove happens
under _frame_lock so render() on the main thread never reads a partial frame.
_raw_arr is allocated once on the first frame (or on resolution change) and
reused for every subsequent frame.

_Frame no longer carries a pixels field.  Tests updated accordingly.
Benchmark updated to use the same buffer.map+memmove path as the app.
2026-03-24 01:07:35 +01:00
Matteo Benedetto 3e8661e2e5 fix(bench): del ctypes/bytes objects immediately in callback to prevent OOM on 1GB device 2026-03-24 00:53:03 +01:00
Matteo Benedetto a201594a90 perf: reduce NV12 per-frame copies from 5 to 2 via single from_buffer_copy + byref offset 2026-03-24 00:47:32 +01:00
Matteo Benedetto ecdbf5eb04 test: add NV12/mppvideodec decode benchmark with A/V sync and jitter metrics 2026-03-24 00:40:13 +01:00
Matteo Benedetto 5332ce9880 fix: use named c_ubyte arrays to prevent dangling pointer segfault in SDL_UpdateNVTexture 2026-03-24 00:31:44 +01:00
Matteo Benedetto bb0ac90c96 feat: add detailed logging for NV12 frames, GStreamer warnings, and render errors 2026-03-24 00:26:17 +01:00
Matteo Benedetto 0c8e2c2a11 player: fix SDL_UpdateNVTexture ctypes cast — cast c_char_Array to LP_c_ubyte 2026-03-24 00:03:34 +01:00
Matteo Benedetto fe2f25d4cf player: auto-enable HW decode when VPU accessible — remove R36S_HW_DECODE opt-in gate 2026-03-24 00:00:19 +01:00
Matteo Benedetto 9b8ddb3a05 ui: fix PAGE_UP/PAGE_DOWN crash — use self._layout.page_size not theme.PAGE_SIZE 2026-03-23 23:55:47 +01:00
Matteo Benedetto 6715f4b227 player: log last DLNA URL to /tmp/dlna_last_url.txt on play(); tests: auto-load it in diagnostic script 2026-03-23 23:50:11 +01:00
Matteo Benedetto 79a1c9a78c tests: fix identity handoff signal signature (*args for GStreamer 1.28 compat) 2026-03-23 23:38:44 +01:00
Matteo Benedetto 1a3549312e tests: fix benchmark (yuv420p fixture, identity probe frame counter) 2026-03-23 23:37:23 +01:00
Matteo Benedetto 9ab0ec4f44 tests: fix benchmark pipelines (decodebin + rank steering, LD_PRELOAD note) 2026-03-23 23:26:42 +01:00
Matteo Benedetto 4a0275d145 tests: add FHD H.264 benchmark fixture and decode benchmark script 2026-03-23 23:20:57 +01:00
Matteo Benedetto 6e15fcab5a player: NV12 zero-copy SDL upload path for Rockchip MPP hardware decode
- _Frame: add pixel_format ('BGRA'|'NV12'), uv_pixels, uv_pitch fields
- _create_appsink: accept NV12 caps when R36S_HW_DECODE=1 (NV12;BGRA fallback)
- render(): choose SDL_PIXELFORMAT_NV12 texture + SDL_UpdateNVTexture for NV12
  frames, avoiding any software colourspace conversion on the CPU
- _on_new_sample: detect format via VideoInfo.finfo.name, extract Y+UV planes
  separately from NV12 GStreamer buffers
- _destroy_texture: reset _texture_format to 'BGRA'
- deploy/arkos/MatHacks.sh: set R36S_HW_DECODE=1 to activate the path
- tests/test_player.py: add finfo mock, pixel_format to SimpleNamespace frame
2026-03-23 22:58:08 +01:00
Matteo Benedetto e23c57318f player: disable mppvideodec auto-rank-boost, add R36S_HW_DECODE=1 opt-in
mppvideodec outputs NV12 (hardware format) which GStreamer videoconvert
converts to BGRA in scalar software code — slower than avdec_h264 which
uses libav's NEON-optimised YUV→BGRA path.

Default behaviour: software decode (avdec_h264) at PRIMARY rank.
The MPP plugin is still detected and logged so the user knows it is
installed and operational.

Set R36S_HW_DECODE=1 to re-enable the rank boost once a zero-copy
NV12→SDL_UpdateNVTexture (or similar) upload path is implemented.
2026-03-23 22:49:16 +01:00
Matteo Benedetto d79bc3e16f 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
2026-03-23 22:37:41 +01:00
Matteo Benedetto ddbe31dc02 player: probe Rockchip MPP/V4L2 HW decoders at pipeline init, add setup_hw_decode.sh 2026-03-23 21:42:25 +01:00
Matteo Benedetto 544ed8bc6d ui: runtime-scaled layout for 640x480 and 720x720 displays 2026-03-22 16:57:24 +01:00
Matteo Benedetto 527e63a769 deploy: add arkos/MatHacks.sh launcher with system GST_PLUGIN_PATH+LD_PRELOAD 2026-03-22 16:38:39 +01:00
Matteo Benedetto 4b4c90fb0c deploy: add run.sh launch wrapper with GST_PLUGIN_PATH+LD_PRELOAD for R36S
On linux-aarch64 the conda gst-libav package has an unfixable ABI mismatch
(libdav1d.so.6 missing, libicuuc.so.78 via libxml2-16). Fix: use system
gstreamer1.0-libav installed via apt with GST_PLUGIN_PATH, and preload
system libgomp.so.1 to avoid static TLS block errors when dlopen loads
libgstlibav.so. avdec_h264 and avdec_aac now register correctly on device.

These vars are stored in conda activate.d/gst-env.sh and in deploy/run.sh.
2026-03-22 16:30:57 +01:00
Matteo Benedetto 193e914ffd Refresh env, add aiohttp dep, improve resource selection and GStreamer flags 2026-03-22 12:32:40 +01:00
Matteo Benedetto 6fc467127c Add publication metadata 2026-03-21 15:55:55 +01:00
Matteo Benedetto 1d89c7fdc7 Initial import 2026-03-21 15:55:32 +01:00