Anthias board / display / codec knowledge
Durable engineering findings from field debugging across the board fleet. Each bullet carries the mechanism plus the fixing PR / CalVer release where known; OPEN items are called out. Boards map to a rendering stack by userspace arch, so read the display-stack section first.
Display stack fundamentals (QPA: linuxfb / eglfs / wayland-cage)
- Board → QPA plugin mapping. Pi 1/2/3-32 (armhf/Qt5) run
QT_QPA_PLATFORM=linuxfb(needs/dev/fb0); Pi 3-64/Pi 4 (arm64/Qt6) runeglfs(DRM/GBM, holds DRM master, no fb0); Pi 5/x86/Rock Pi 4 (arm64/Qt6) run cage/wlroots wayland. Pi 4 switched linuxfb→eglfs in #2904 for HW video. Stalestart_viewer.shcomments claiming "Pi 4 runs linuxfb" were corrected in #2962. - DRM master ownership determines what can blank/rotate/recover. On eglfs/linuxfb the Qt app (or drm_fb_helper) owns DRM master; on wayland the cage compositor owns the connector. This single fact drives blanking, hotplug recovery, and rotation mechanism per stack.
- Board selection is by userspace arch (
dpkg --print-architecture), NOTuname -mor model string. 32-bit Raspberry Pi OS ships a 64-bit kernel by default on Pi 3 (arm_64bit=1), souname -m=aarch64while docker/apt are armhf. Gating on kernel arch mispickedpi3-64and pulledlinux/arm/v8(no matching manifest). Fixed in PR #3076 (2026-06-16) to gate on userspace arch.arm64catch-all branch still usesuname -m(no armhf image for generic SBCs). No in-place arch switch — only reflash changes streams.
Display blanking (per-platform power-off)
- Wayland boards can true-power-off; eglfs/linuxfb cannot.
wlr-randr --output <NAME> --offDPMS-powers-off the monitor on wayland (run asvieweruser withXDG_RUNTIME_DIR=/run/user/1000+WAYLAND_DISPLAY=wayland-0). On eglfs/linuxfb the Qt app holds DRM master so external blank is rejected (echo 3 > /sys/class/drm/cardN-HDMI-A-1/dpms→ EBUSY under atomic KMS;vcgencmd display_power 0no-op under vc4-KMS) — the only blank without stopping the viewer is to paint the screen black. PR #3065 added first-classblank/unblankviewer commands (Redisanthias.viewerchannel) that do wlr-randr off on wayland, paintBLACK_SCREEN(img/black.png) on eglfs/linuxfb.
Headless-boot / HDMI-hotplug recovery (differs sharply by stack)
- linuxfb (32-bit Pi 1/2/3): headless boot = no
/dev/fb0= crash-loop. Full KMS (dtoverlay=vc4-kms-v3d) only creates/dev/fb0when a display is connected at boot; headless → connectordisconnected→ no fb0 → linuxfb hard-requires it →Unable to figure out framebuffer device→no screens available→ Qt heap-corrupts on the no-screen path (malloc(): unaligned tcache). Firmwarehdmi_force_hotplug/hdmi_*knobs are a DEAD END (full KMS ignores them); only kernel cmdlinevideo=<connector>:...forces a fb, unsafe for a fleet. Fix = graceful wait-and-retry in viewer (#2959 fb0-wait), self-heals when display hotplugs./dev/dri/card0+renderD128 exist even headless; only fbdev emulation is absent. - eglfs (Pi 3-64/Pi 4): does NOT need the linuxfb stale-
/devexit-1 guard. Verified by clean power-cycle:/dev/dri/cardNis created at vc4 driver bind (kernel boot), present headless;open()routes by (major,minor)=226,1 to the live device even with a stale-timestamp node; connector status is read from sysfs (truthful). eglfs opens container card1 and modesets HDMI-A-1 after headless-boot+connect. But eglfs boards refuse to start the viewer while headless (wait_for_eglfs_displayin start_viewer.sh from #2962 polls/sys/class/drm/card*-*/statusfor non-disconnected) — correct signage behavior but means a headless testbed renders nothing. - eglfs headless gap was also a crash-loop before #2962. Pi 4 eglfs headless →
no screens available→ binary exits before D-Bus handshake → container crash-loop. #2962 (wait_for_eglfs_display) mirrors the linuxfb fb0 wait. Separately #2969 added capped-backoff retry of the AnthiasViewer spawn (BROWSER_SPAWN_MAX_ATTEMPTS=30,BROWSER_SPAWN_BACKOFF_CAP_SECONDS=15). - wayland-cage (Pi 5/x86): headless boot binds ZERO wl_outputs and never recovers on hotplug (#3155, refactor #3161, shipped 2026-07-08). cage enumerates DRM connectors at startup; if none connected+enabled it binds 0 wl_outputs and runs forever (no exit → no restart → permanent black). It can't recover on hotplug because the viewer container has no udevd / no
/run/udev, so wlroots' libudev hotplug monitor gets no uevent. Contrast: eglfs Qt exits non-zero on no-fb andrestart: alwaysself-heals. Symptom: viewer log spamswlr-randr could not apply rotation N;grim→no wl_output; scheduler runs normally. Fix =_wayland_output_watchdog()in__init__.pyper asset_loop tick:sys.exit(1)if no bindable output pastWAYLAND_OUTPUT_GRACE_S=60→ container restart → cage re-enumerates.- Gate on BINDABLE, not just
connected. A marginally-seated HDMI asserts HPD (status=connected) but EDID never read →/sys/class/drm/<c>/modesEMPTY → cage has no mode to bind → restart-loops forever._kernel_has_bindable_display()requiresconnectedAND non-emptymodes. - Second bug: restart recovery is racy. The restart-policy relaunch is nearly instantaneous; if the old cage's DRM-master release + vc4 connector reset haven't drained, the fresh cage races a half-reset connector → EDID not re-read → empty modes → headless again (~50% wedge).
docker restartrecovers reliably (its stop→start gap lets vc4 reset). Fix instart_viewer.shcage branch: on aconnectedconnector,sleep 4then forceecho detect > status+ bounded-wait formodesto repopulate before launching cage.
- Gate on BINDABLE, not just
- HDMI hotplug resolution drop to 1024×768 (issue #3052, fixed PR #3075).
1024×768is the kerneldrm_fb_helperhard-coded fallback mode, triggered when avc4-kms-v3dre-probe races the sink's EDID return (slow TVs). linuxfb boards vulnerable (no process holds DRM master; Qt reads/dev/fb0once, can't follow a later mode change). eglfs boards immune (Qt holds DRM master, keeps its modeset across hotplug). Recover in-container without fbset:echo "U:1920x1080p-0" > /sys/class/graphics/fb0/mode(the-0refresh field is CORRECT — vc4-kms-v3d fbdev emulation registers modes asU:WxHp-0). True active geometry =fbset -i;virtual_sizeis the static allocated buffer,/modeshead is a sticky unreliable list. - Container
/devis a boot-time snapshot. If the privileged viewer container started headless and a display is plugged in later, the host gets/dev/fb0but the container does NOT (only/sys/class/graphics/fb0shows through) →wait_for_framebufferparks forever.docker restartre-snapshots/dev. Only bites hotplug-after-boot. The celery container can NEVER do display recovery (not privileged, no/dev/fb0or/dev/dri,/sysread-only) — only the privileged viewer container.
Video decode (HW vs SW) by chip
- Pi 4 (bcm2835, stateful v4l2m2m): ≤1080p H.264 HW-decodes; >1080p falls to SW.
h264_v4l2m2m(the+rpt1ffmpeg build) IS engaged by QtMultimedia for ≤1080p H.264 —/dev/video10fd held open whole clip, ~120-140% CPU, ~0 drops. 4K30 / High10 / 4:2:2 / level>4.x → not opened, cores pegged, ~36% drops.QT_FFMPEG_DECODING_HW_DEVICE_TYPESonly accepts AVHWDeviceType names (cuda/drm/vaapi), NOT v4l2m2m (a standalone AVCodec) — Qt reaches it empirically anyway on+rpt1. Probe =/dev/video10open + CPU-delta. - Pi 5 (Hantro G2, v4l2-request): HEVC HW-decodes through QtMultimedia libavcodec. Verified via
/proc/<pid>/fd:/dev/video19(rpi-hevc-dec),/dev/dma_heap/linux,cma,/dev/media2,anon_inode:request,/dmabuf:frames; 4K30/4K60 HEVC both play at 1.0×. The chain QtMultimedia→libavcodec61→v4l2-request→/dev/video19is load-bearing; don't strip device passthrough or the+rpt1pin. Pi 5 has no H.264 HW decode block (upload gate rejects H.264, supported set ={hevc}). - Pi 3-64 (VideoCore IV,
+rpt1gate): was SW-only until #3110 addedpi3-64to the board tuple.docker/_rpt1-ffmpeg-pin.j2gated the RPi+rpt1ffmpeg onboard in ('pi4-64','pi5','arm64')— pi3-64 was MISSING, so it shipped stock Debian ffmpeg and SW-decoded (~0.7× realtime). PR #3110 (shipped, HW-validated) →/dev/video10held, 30fps realtime,dropped=0. H.264-only HW decode on VC IV (_HW_DECODE_VIDEO_CODECS['pi3-64'] = {'h264'}). - Rock Pi 4 (RK3399, stateless v4l2_request): both H.264 and HEVC SW-decode in practice (OPEN). No-hwaccel picks native SW — what QtMultimedia's libavcodec does.
-hwaccel drmengages rkvdec for HEVC but producesDecode failat 0.77× (broken on Armbian 6.18 kernel); H.264 has NO v4l2_request hwaccel binding in the+rpt17.1.3 build.h264_v4l2m2m/hevc_v4l2m2m= "Could not find a valid device" (stateful M2M wrappers don't bind the stateless RK3399 nodes). Real arm64 HW decode needs QtMultimedia to request-hwaccel drmagainst v4l2_request, and even then only HEVC works (with errors) on this kernel. - x86 (Intel UHD 610, iHD/VAAPI): HEVC HW decode fails inside cage's GL context (OPEN, worked around).
vainfolists HEVC VLD, container ffmpeg + an offscreen AnthiasViewer both VAAPI-decode HEVC cleanly, but the real display-up viewer emitsFormatError code=2/ 0 frames on HEVC RTSP (H.264 in the same context is fine). VAAPI-HW × GL-context × HEVC interaction;QT_DISABLE_HW_TEXTURES_CONVERSION=1did NOT fix it. Workaround:QT_FFMPEG_DECODING_HW_DEVICE_TYPES=,(empty → disable HW decode) → SW decode plays. Tradeoff: disables HW decode for ALL codecs on x86; a board-conditional list is better than blanket-off.
Video presentation bottleneck (Qt6 boards — decode ≠ what reaches screen)
- Qt6 boards present video at ~8-12fps despite perfect HW decode (issue #2967). Root cause:
QGraphicsVideoItem::paint → QVideoFrame::toImage → qImageFromVideoFrame— Qt 6.8 converts YUV→RGB on the GPU then reads it back to a QImage, CPU-blits into raster backing store, re-uploads to GPU. Two GPU/CPU crossings per frame. Measured: Pi 4 1080p30 H.264 8.3fps presented; Pi 5 11.7fps. PR #2975 fix = QMLVideoOutputin a QQuickWidget (player->setVideoSink(item.videoSink)) → Pi 4 30.0fps/66% CPU. A QOpenGLWidget viewport is NOT sufficient (toImage stays); QVideoWidget = second native window, fails the eglfs single-window constraint. - #2975 fix does NOT hold at 1080p (OPEN, resolution-scaling). RC QA (forced 1080p60 headless): 720p H.264
28fps, but 1080p H.264 ~5.8fps (40% pacer-dropped). Not a regression (Qt6 video byte-identical across releases). #3006 added a pacer that DROPS frames to match scene-render capacity. CONFOUND: measured on a no-EDID headless display; confirm on a real Pi 4 + monitor. Tracked in issue #2987. - Measure PRESENTED fps via the kernel DRM vblank tracepoint, never
playback-stats.log. Countdrm_vblank_event_deliveredevents over 10s (/sys/kernel/debug/tracing,count/10 = presented fps,crtc=Ndisambiguates outputs) — zero observer effect.playback-stats.logcounts sink deliveries (full decode rate) and is blind to paints coalescing.frames-rendered(=QQuickWindow::afterRendering) is scene-render into an offscreen FBO, NOT scanout. - Architecture note (misnomer): Qt6 boards (pi4/pi5/x86/rockpi4) use
MPVMediaPlayer— a misnomer, it drives IN-PROCESS QtMultimedia in the C++ webview over D-Bus, NO mpv binary anywhere post-#2904. pi1/2/3-32 useGstFbdevMediaPlayer(GStreamer HW → fbdevsink). pi3-64/pi4-64 routed viaforce_mpvenv override inmedia_player.py.
issue #2987 video regressions (2026.6.1, four distinct defects)
- 1. pi3-32 portrait stretch — #2972's
_video_sink()forced fb-sized caps; fix = CAPS-event pad probe → aspect-fit caps withpixel-aspect-ratio=1/1(PR #3004, shipped 2026.06.3). - 2. pi3-32 freeze/cut clips — bash
while true; gst-launchloop rebuilt the pipeline per EOS. Fix: playbinabout-to-finishgapless loop ingst_fbdev_player.py(PR #3004). Helper MUST be spawned by path, not-m(package__init__imports Django). - 3. Pi 4 (Qt6) 1080p60 collapse — 22.6fps presented, clips killed by slot duration. Fix (PR #3006): intermediate QVideoSink gating on
afterRendering+ single-slot latest-frame mailbox. CEILING ~23fps for 60fps sources; full 60fps needs the bigger single-window rework.dropped=counter reads 0 in fast-forward mode — don't use it to detect this. - 4. Backup timeout — sync tar gzip-9 = 98s for 355MB on Pi 4, browsers abort byte-less requests ~5min. Fix: StreamingHttpResponse over a pipe + compresslevel 1 (PR #3005). Pre-existing.
Pi 3-64 video BLACK on VideoCore IV (issue #3084 / forum 6720/6730) — RESOLVED via DRM overlay plane
- Root cause: vc4 (VideoCore IV) GLES2 can composite RGB but CANNOT render QtMultimedia's
VideoOutputscene-graph video node — for HW OR SW frames. Video plays fine on Pi 4 (V3D 4.2) and Pi 5 (V3D 7.x) but is pure black on Pi 3-64, while still images + webview composite fine. Decode is healthy. Pinned by a red-backdrop diagnostic (Rectangle{color:RED; VideoOutput{}}→ JUST RED). The #2967 substrate swap (toImageCPU-raster →VideoOutputRHI-texture) traded slow-but-visible for fast-but-black on VC IV. - CPU-raster fallback works (unblacks) but caps ~1.5fps —
QVideoFrame::toImage()on a 1080p HW SAND/DMABUF frame = ~600-800ms/frame on VC IV. SW decode makes toImage cheap but SW-decoding 1080p30 REBOOTS the 1GB Pi 3. - RESOLVED PR #3164 (merged 2026-07-09): in-process GStreamer HW decode → kmssink on a vc4 DRM OVERLAY plane, HW-composited alongside eglfs's primary plane, 30fps zero ongoing drops. Qt eglfs exposes its DRM master fd (
libQt6EglFsKmsSupport.sonative-resource keysdri_fd,dri_crtcid,dri_connectorid,dri_atomic_request). Winning pipeline:filesrc ! qtdemux ! h264parse ! v4l2h264dec capture-io-mode=mmap ! queue max-size-buffers=4 ! kmssink fd=<dri_fd> connector-id=<> plane-id=<overlay> force-modesetting=false can-scale=true skip-vsync=true qos=true.- Two keys: (1) decoder-direct, NO v4l2convert — the bcm2835 ISP I420→NV12 convert caps
15-18fps at 1080p; decoder alone does 30fps+. (2)frame 15). mmap forces kmssink to COPY each frame (~3MB, cheap) and release the decoder buffer immediately.capture-io-mode=mmap— default (exported dmabuf) lets kmssink zero-copy and PIN the decoder DPB buffers → pool starves → DEADLOCK ( - Audio runs a fully independent GStreamer pipeline →
alsasink device=sysdefault:CARD=vc4hdmiDIRECT to HDMI (not pulsesink; pulse defaults to 3.5mm). Every shared-pipeline attempt froze video at frame 1. Addedgstreamer1.0-libavfor AC3/AAC/MP3. - In-process kmssink on eglfs's SHARED master fd is fragile: a crashed/SIGKILLed kmssink leaves the overlay plane bound → cascading DRM wedge until reboot — a supervised DRM-lease child would be more robust.
- Two keys: (1) decoder-direct, NO v4l2convert — the bcm2835 ISP I420→NV12 convert caps
- OPEN follow-up: presented fps << decode fps under memory pressure. On the ~787MB Pi 3-64 the merged build decodes 30fps but kmssink QoS-drops ~11fps as "late" → screen presents ~19-21fps under swap thrash. NOT a code regression. Next lever untried:
kmssink qos=false.
Rotation (screen_rotation convention + per-stack mechanism)
screen_rotationis CLOCKWISE uniformly on every stack as of 2026.07.3 (PR #3202, 2026-07-16). Before 2026.07.3 linuxfb turned CW but eglfs/wayland turned CCW for the same setting. Canonical = clockwise (matches linuxfb paint path + GStreamer videoflip).- linuxfb (Pi 1/2/3-32): QPA ignores
rotation=N, so content is turned by hand — images/GIFs inView::paintEvent(rotation.cpplinuxfbRotationOverride), web pages via injected CSS (webpageRotationScript), video viavideoflip(90→CW). - eglfs (Pi 3-64/Pi 4, Rock Pi 4):
QT_QPA_EGLFS_ROTATIONNEGATED —{90:-90, 180:180, 270:90}. A literal270rotates content but skips Qt's width/height swap (viewport stays 1920×1080 → stretched); emit-90instead (PR #2973, #2970). - wayland (Pi 5/x86):
_wlr_transform_value—{90:'270', 180:'180', 270:'90'}(wlr transforms turn CCW). - pi3-64 video: vc4 overlay plane rotate-180 for 0/180 (HW, #3189); 90/270 drop the overlay → eglfs raster fallback inherits the compositor turn.
- linuxfb (Pi 1/2/3-32): QPA ignores
_is_wayland_board()rotation dispatch bug (RESOLVED #3047, issue #3044). ReturnedDEVICE_TYPE == 'x86'only, so pi5/arm64 never reached the wlr-randr transform path. Fixed toos.environ.get('QT_QPA_PLATFORM','').startswith('wayland').- Pi 4 rotation was a no-op on eglfs (RESOLVED). #2882 appended
rotation=NtoQT_QPA_PLATFORM, but that's a linuxfb-plugin option eglfs ignores. Fix: setQT_QPA_EGLFS_ROTATION=<deg>in_build_webview_envfor eglfs AND remove the app-levelvideo-rotatebranch inmedia_player.py(else video double-rotates). Firmwaredisplay_rotateis ignored under vc4-kms-v3d + eglfs_kms. - pi2/pi3-32 linuxfb: only video rotated pre-fix, images + web pages upright (RESOLVED PR #3197, 2026.07.3). Fix: images/GIFs rotated by hand in
View::paintEvent(linuxfbRotationOverride()); web pages via injected CSS through imperativerunJavaScript. Gated linuxfb-only (eglfs/wayland rotate the whole compositor output). - qt5pi declarative
QWebEngineScriptsilently never fires — even withApplicationWorld+DocumentReady. Use imperativepage->runJavaScript(js, QWebEngineScript::ApplicationWorld)from a PERSISTENTloadFinishedhandler (re-applies on every auto-refresh reload; keep a MutationObserver for SPA rewrites). Debug without rebuild:QTWEBENGINE_REMOTE_DEBUGGING=9222+ a stdlib CDP WebSocket client. - Rotated video is ~1-2fps on Pi 1/2/3 linuxfb (issue #3198). Software
videofliprotates full-res frames single-threaded on A7/A53 → 100% of one core, ~1fps (vs 26fps unrotated). Pre-existing from #3004. Split verdict (PR #3207): 180° fixable for FREE via the bcm2835 ISP —v4l2convert extra-controls=c,horizontal_flip=1,vertical_flip=1runs the flip in HW → full baseline fps, pixel-exact. 90/270 INFEASIBLE on VideoCore IV — norotatecontrol on any bcm2835 v4l2 device (only hflip/vflip). Scale-before-flip is a dead end. Measure fps withfpsdisplaysink, NOT fb0 mmap sampling.
GPU / WebGL
- WebGL 1+2 is hardware-accelerated out of the box on every Qt6 board (verified 2026-06-07): Pi 5 = V3D 7.1.7.0 (wayland), Pi 4 = V3D 4.2.14.0 (eglfs), Rock Pi 4 = Mali-T860/Panfrost (wayland). No
QTWEBENGINE_CHROMIUM_FLAGSset;WebGLEnableddefault true. Qt5 boards (Pi 1-3 32-bit, linuxfb) have NO GL — Qt built without-opengl es2(enabling it breaks QtWebEngine); WebGL cannot work there. If a WebGL asset misbehaves on a Qt6 board, suspect page JS or memory pressure, not the GPU context.
Webpage transition stale-buffer flash (issue #2954) — RESOLVED
- Webpage→webpage transition flashed the (n-2 mod N) page for ~1 frame. The 2-buffer ping-pong revealed a buffer whose last-composited GPU surface still held the page from two rotations ago.
loadFinished≠ first-paint, and an occluded QWebEngineView does NOT composite (Chromium frame-callback-throttles occluded WebContents). Fix (2026-07-08, live-verified Pi 5): collapse to the single-QWebEngineView path for ALL boards — an in-placeload()on the visible view keeps the old page composited until the new one paints. Removed theANTHIAS_LOW_RAMviewer flag. Residual: x86 wayland logs periodicAtomic commit failed: Device or resource busy(benign).
Boot splash (Plymouth)
- x86: Plymouth boot splash holds DRM master → cage wedges (RESOLVED #2955, shipped 2026.05.2). balenaOS plymouthd on x86 opens
/dev/dri/card0as DRM MASTER (efifb→i915 early KMS). cage then runs as non-master and every atomic commit is rejected — screen frozen on splash whileSwapchain for output 'DP-1' failed testspams. Confirm via/sys/kernel/debug/dri/0/clients. Fix =start_viewer.shasks host systemd (over the host system bus) to runplymouth-quit.servicebefore launching cage. x86-specific: Pi plymouthd opens/dev/fb0, never becomes DRM master. - "Splash broken" on a fresh Pi is usually a pending-reboot artifact, NOT a bug. The running kernel still holds the stock rpi-imager cmdline (no
splash); Ansible writes the Anthias cmdline.txt but it only takes effect next reboot. First diagnostic: diffcat /proc/cmdlinevscat /boot/firmware/cmdline.txt. Usedpkg-query -W(notdpkg -l | grep) to check the package. - Pi 2 splash rendered the TEXT theme, not Anthias (RESOLVED PR #3199, 2026.07.3). plymouthd waits
DeviceTimeoutfor a graphics device (distro default 8s); on a Pi 2 (A7 @900MHz) vc4's/dev/dri/card0only appears at ~14.3s → plymouthd commits to text at 12.65s. Fix:DeviceTimeout=30in the plymouthd default + a Regenerate-initramfs handler. plymouthd runs from the initramfs and reads its config from the copy baked there — a config-only on-disk change is inert; verify withunmkinitramfs. AdisconnectedHDMI fakes this exact bug.
Audio
- Qt6 Multimedia (Debian trixie) has ONLY a PulseAudio backend — links libpulse, zero ALSA code, no
QT_AUDIO_BACKENDescape. No pulse server →QMediaDevices::audioOutputs()empty → silent video (issue #3000, all Qt6 boards after the #2905 mpv→QtMultimedia migration). Fix (PR #3001):start_pulseaudio()instart_viewer.shruns a per-container pulse daemon as the viewer user. Sink-naming trick keeps routing:load-module module-alsa-card ... name=<alsa card id>→ sink names embed the card name (alsa_output.vc4hdmi0.hdmi-stereo). Verify via/proc/asound/cardN/pcm0p/sub0/status=RUNNING. - HDMI/3.5mm
audio_outputselector is a no-op on x86 + arm64 (RESOLVED issue 3208 / PR 3209).get_alsa_audio_devicecollapsed to'default'on non-Pi boards. Fix: non-Pi boards discover sinks at runtime viapactl list short sinksand pick by the profile token in the sink name (hdmi-*vsanalog-*), not the card name; return the bare sink name, matched againstQAudioDevice::id(); fall back todefaultif pulse unreachable. Pi paths unchanged. A headless x86 has no HDMI pulse sink, so it correctly falls back to default.
Pi 2 blank / Qt5 armhf toolchain (release blocker) — RESOLVED
- Pi 2 (and 32-bit Pi 3/armhf) blanks: deterministic ARMv7 alignment SIGBUS inside libQt5WebEngineCore (forum thread 6732). AnthiasViewer handshakes on D-Bus then SIGBUSes post-handshake:
Alignment trap ... f9400adf. A 64-bit NEON doubleword store (vst1.8 {q8},[rN:64]) stamped:64alignment lands on a 4-byte-aligned Chromium address; Cortex-A7 faults, kernel can't fix up NEON → SIGBUS. 100% deterministic even with empty playlist. - Root cause is the Debian gcc-14 armhf TOOLCHAIN, not the Qt version. Bisection on real Pi 2: 5.15.19 crashes; prebuilt 5.15.14 (built with Linaro gcc-7) is clean; 5.15.18 AND 5.15.14 rebuilt with gcc-14 BOTH crash. The culprit is the toolchain switch (~PR #3070 "replace retired Linaro armhf toolchain with Debian's"). The faulting store comes from gcc's ARM backend RTL block-move expander (
arm_block_set_aligned_vect), NOT the SLP vectorizer (so-fno-tree-vectorizedoes nothing). GCC WONTFIX by policy; only Linaro gcc-7 doesn't emit it. - Fix VALIDATED on real Pi 2 (2026-07-10), 0 alignment traps — requires BOTH: (1)
arm_use_neon=falsein qtwebenginesrc/core/config/common.pri; (2) global-mno-unaligned-accessvia a compiler wrapper shim on/usr/bin/arm-linux-gnueabihf-{gcc,g++}. HW SIMD video decode preserved (libvpx/Skia re-add-mfpu=neonper-file). Metric truth = runtimedmesg | grep -c "alignment exception", NOT thevst1:64count in the .so and NOT the launch harness (post-handshake, reports OK while the screen still blanks). - Qt5 toolchain rebuild is broken on trixie (Python 3.13 removed stdlib modules Chromium 87's build needs). Fixes:
imp/pipes/cgishims viaPYTHONPATH, overwrite Chromium's vendored six with distro six 1.17; the re2 API break — droplibre2-devsouse_system_re2=false(also clearconfig.cache'swebengine-re2.result=true). QtWebEngine ninja OOMs the build box regardless ofMAKE_CORES(bundled ninja self-detects nproc); fix:export NINJAFLAGS="-j${MAKE_CORES}". - Qt5 5.15.14→5.15.19 toolchain bump: MERGED PR #3121, WebView-v2026.07.0. Qt6 boards untouched (track trixie apt 6.8.2;
qt_version='6.4.2'inutils.pyis dead, only names the Qt5 tarball). The viewer image downloadsqt5-<ver>-trixie-<board>.tar.gzfrom the WebView release, so tarballs must be built + uploaded BEFORE the utils.py bump merges or pi2/pi3 image builds 404 on the sha256.
Pi 2/3-32 armhf webview crash-loop (headless AND display-present)
- Two distinct malloc-unaligned crashes on arm32 — don't conflate. (a)
malloc(): unaligned tcacheon the NO-SCREEN/headless path (see headless-hotplug). (b) On a device WITH a valid/dev/fb0, the Qt5 viewer heap-corrupts while building the font database (deterministically at the synthetic-obliqueMonospacestyle) → crash-loop. arm32-only. The real corruptor is Qt5 QtWebEngine/Chromium init on armv7 (only surfaces with--no-sandbox, which the fleet uses); the font DB is just the victim. Stop-gap = #2969 capped-backoff spawn retry. Real fix = the arm64/Qt6pi3-64migration (Qt6 WebEngine inits clean on Pi 3 silicon).
Board / fleet mapping facts
- pi3-64 (arm64/Qt6/eglfs_kms) added alongside legacy 32-bit armhf/Qt5
pi3; mirrors the pi4-64 path (in-process QtMultimedia, no cage, no mpv); H.264-only HW decode on VC IV.get_device_type()still returns'pi3'; routing relies onforce_mpv.docker/eglfs-kms.jsonshared pi4-64/pi3-64, rewritten at runtime by start_viewer.sh DRM-card detection. - Rock Pi 4 (
rockpi-4b-rk3399) runs the GENERIC arm64 images (no rockpi4 board build; deploy scripts rewrite BOARD→arm64 and strip/dev/vchiq).board.get_board_subtypefalls back to/proc/device-tree/modelin-container when redis is empty. Rock Pi 4 is 1GB and OOM-wedges even idle on the arm64 viewer. - Pi 5 runs a 16KB-page kernel (
getconf PAGESIZE=16384). 32-bit armhf userspace (built for 4KB pages) fails to load (ELF load command ... not page-aligned) even under--platform linux/arm/v7; no qemu binfmt on these boards. For native armv7/armhf validation use the Pi 4 (arm64 kernel, 4KB pages). Pi 5 outputs 4K@30 → presented-fps ceiling is 30, not 60.
Diagnostic / harness techniques
- Webview viewport probe via D-Bus (rotation/geometry without a screen capture): read
DBUS_SESSION_BUS_ADDRESSfrom/proc/<AnthiasViewer pid>/environ,docker exec -u viewerpydbusbus.get('anthias.viewer','/Anthias').loadPage(url)(root gets "connection is closed"), load adata:page thatfetch()esinnerWidth/innerHeight/screen.widthto the server, read dims from the uvicorn access log. - Offscreen D-Bus repro harness (validate AnthiasWebview on the dev host, no testbed): x86 viewer image + build
src/anthias_webviewwithqmake6 && makeinside, run underdbus-run-sessionwithQT_QPA_PLATFORM=offscreen+QTWEBENGINE_CHROMIUM_FLAGS="--no-sandbox --disable-gpu", drive viagdbus call. D-Bus interface casing differs Qt5 vs Qt6: Qt6 =local.anthiasviewer.MainWindow(lowercase), Qt5 (armhf) =local.AnthiasViewer.MainWindow(CamelCase); introspect/Anthiasrather than hard-coding. C++ stderr is NOT indocker logs(bounded deque, issue-3138) — the playback-stats file is the visible signal. - pi3-64 eglfs capture/fps (fb0 is dead/all-zero on eglfs — DRM/GBM not fbdev): DRM plane structure via
sudo modetest -M vc4 -p(active = crtc!=0 && fb!=0; overlay path = 2 planes;rotation:value 1=rotate-0, 4=rotate-180, vc4 has no 90/270). Pixel grab viaffmpeg -f kmsgrab(primary plane is T-tiled → scrambled but colors intact; SAND-tiled video plane can't be grabbed). Watchvcgencmd measure_temp/get_throttled(SW rotate heats the passively-cooled Pi 3 into throttle). - Content-diff fps measurement needs a full-frame-motion source (
ffmpeg -f lavfi -i mandelbrot); a color-bar clip with a thin moving sweep under-counts to ~1fps even when playing fine. Preferfpsdisplaysinkover fb0 mmap sampling.