Skip to content
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
8a345ce
perf(viewer): pi4-64/pi5 use mpv --vo=gpu --gpu-context=drm
vpetersson May 13, 2026
6da684f
Merge remote-tracking branch 'origin/master' into perf/pi-vo-gpu-drm
vpetersson May 13, 2026
0a309df
perf(viewer): drop --drm-mode pin on Pi4-64/Pi5 under --gpu-context=drm
vpetersson May 13, 2026
0bf027a
Merge remote-tracking branch 'origin/master' into perf/pi-vo-gpu-drm
vpetersson May 13, 2026
4bac962
refactor(viewer): consolidate Qt6 boards onto cage + Wayland, pin Pi …
vpetersson May 13, 2026
2be4eb6
fix(ansible): propagate tags into boot.yml include_tasks
vpetersson May 13, 2026
5789cf8
fix(viewer): keep Pi 4 on linuxfb; only Pi 5 / x86 / arm64 go cage
vpetersson May 13, 2026
52ac3ce
fix(viewer): mirror host render-GID for all Qt 6 boards, not just cage
vpetersson May 13, 2026
2384851
fix(viewer): Pi 4 stays on --vo=drm (Qt linuxfb DRM master contention)
vpetersson May 13, 2026
81f3eba
fix(viewer): cage opens on the first connected connector, not HDMI-A-1
vpetersson May 13, 2026
2329388
feat(viewer): mpv from apt.raspberrypi.com on Pi 4 / Pi 5, hwdec auto…
vpetersson May 13, 2026
d86d43b
fix(viewer): use deb-packaged Pi keyring for archive.raspberrypi.com
vpetersson May 13, 2026
797a6f2
feat(viewer): per-codec hwdec on Pi via Lua hook
vpetersson May 13, 2026
bb27b18
fix(viewer,server): HW-decode everywhere on Pi 4 / Pi 5 / x86
vpetersson May 13, 2026
43c36fb
test(viewer,server): mock _probe_video_codec; fix mypy on Popen IO types
vpetersson May 13, 2026
6dc0504
feat(server): downscale 4K HEVC → 1080p on Pi 5 (CMA workaround)
vpetersson May 13, 2026
bc37d98
fix(viewer,system): Pi 5 4K HEVC HW + display-resampled VO sync
vpetersson May 13, 2026
0b6bea0
feat(server): introduce PlaybackEnvelope dataclass + matrix + cache
vpetersson May 14, 2026
3a7a118
feat(server): envelope-driven asset processor with sibling-original
vpetersson May 14, 2026
a5e5afd
feat(server): re-render walker + startup envelope reconciler
vpetersson May 14, 2026
5603ec0
fix(server): preserve `.original.<ext>` siblings during orphan sweep
vpetersson May 14, 2026
eb3940b
test(server): cover sibling-original storage across normalisation paths
vpetersson May 14, 2026
0af7e47
docs(board-enablement): replace codec policy table with playback enve…
vpetersson May 14, 2026
83ac4a5
fix(tests): mypy on `_make_video_asset` + boolean is_enabled
vpetersson May 14, 2026
36aee8b
fix(server,tests): envelope-aware container gate + startup hook safety
vpetersson May 14, 2026
bb4a5e9
fix(server,tests): drop PYTEST_CURRENT_TEST gate; align stale summaries
vpetersson May 14, 2026
7577705
feat(envelope): add generic-arm64 key for Rock Pi / Armbian SBCs
vpetersson May 14, 2026
f6bc491
fix(server): make celery_tasks.py top-level django.setup() reentrant-…
vpetersson May 14, 2026
b325b55
fix(server): commit `original_uri` to DB before transcode (crash safety)
vpetersson May 14, 2026
70997b0
docs(board-enablement): script-driven 1-minute sample pack
vpetersson May 14, 2026
dde1b20
feat(envelope,viewer): runtime Rock Pi 4 detection unlocks v4l2m2m HW…
vpetersson May 14, 2026
91ca8d0
refactor(envelope,viewer): publish board subtype via host_agent + Redis
vpetersson May 14, 2026
6517aa9
perf(celery): cap walker to --concurrency=1 so transcodes can't choke…
vpetersson May 14, 2026
c249ae1
fix(viewer): force MPV on legacy ``generic-arm64`` DEVICE_TYPE
vpetersson May 14, 2026
a817b45
Merge remote-tracking branch 'origin/master' into perf/pi-vo-gpu-drm
vpetersson May 14, 2026
37d21b8
fix(viewer): route ``generic-arm64`` through cage + ALSA-default like…
vpetersson May 14, 2026
09e1b49
fix(envelope): Rock Pi 4 stays on H.264 1080p30 -- stock ffmpeg has n…
vpetersson May 14, 2026
ba8d470
feat(viewer,envelope): extend +rpt1 ffmpeg to arm64; Rock Pi 4 = HEVC…
vpetersson May 14, 2026
06a5461
perf(celery): cgroup CPU hard cap (`cpus: 1.0`) so encodes never star…
vpetersson May 14, 2026
a9d893c
perf(celery): scale CFS quota with host cores (half of \$(nproc), min…
vpetersson May 14, 2026
76ef458
perf(walker): hardware-decode the source in the transcode pipeline
vpetersson May 14, 2026
4d0184f
feat(server-image): extend +rpt1 ffmpeg pin to anthias-server too
vpetersson May 14, 2026
0340b4f
perf(celery,viewer): four hardening fixes so the player survives an u…
vpetersson May 14, 2026
80b58cd
fix(tests): use sh.ErrorReturnCode_1 in hwaccel fallback test
vpetersson May 14, 2026
2870358
refactor(asset-processor): drop on-device video transcoding
vpetersson May 14, 2026
cc2572c
feat(asset-processor): gate uploads to hardware-decoded codecs only
vpetersson May 14, 2026
321e735
feat(dashboard): surface unsupported-codec failures with copyable recipe
vpetersson May 14, 2026
a35b38f
fix(processing): give recipe output a codec suffix so it doesn't over…
vpetersson May 14, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 43 additions & 27 deletions src/anthias_viewer/media_player.py
Original file line number Diff line number Diff line change
Expand Up @@ -172,43 +172,59 @@ def play(self) -> None:
# effect without a viewer restart, matching VLCMediaPlayer.
settings.load()

# Pin to 1080p on Pi4-64/Pi5: mpv's default --drm-mode=preferred
# reads the connector's EDID-preferred mode (4K on most modern
# TVs) and runs CPU zimg upscale, which drops below real-time
# on the A72. Software decode of 1080p H.264 fits 4 cores fine.
# Software-decode tuning for Pi4-64 (A72) / Pi5 (A76). mpv 0.40
# in Debian Trixie has v4l2m2m-copy but not v4l2request hwdec,
# so --hwdec=auto-safe falls through to software decode on
# H.264/HEVC; pinning 4 threads keeps 1080p decode well above
# real-time on both SoCs.
device_type = os.environ.get('DEVICE_TYPE', '')
extra_args: list[str] = []
if device_type in ('pi4-64', 'pi5'):
extra_args = [
'--drm-mode=1920x1080@60',
'--vd-lavc-threads=4',
]

# x86 runs under `cage` (a wlroots kiosk compositor — see
# bin/start_viewer.sh); cage holds DRM master, so --vo=drm is
# denied. Route mpv through the GL VO over a Wayland EGL
# context, which is the generic path mpv supports on every x86
# GPU with Mesa or vendor GL drivers. Paired with
# --hwdec=auto-safe, VAAPI-capable iGPUs (Intel iHD/i965, AMD
# radeonsi, …) decode in hardware and hand frames to the GL
# context as DMA-BUFs via dmabuf-interop-gl; software decode
# still works via the same VO for codecs without HW support.
# --vo=dmabuf-wayland would skip the GL upload entirely but
# segfaults under cage in the viewer's background-spawn path
# (mpv 0.40.0 + wlroots-0.18 + libplacebo dies between hwdec
# init and file open). Pi boards (pi4-64/pi5) keep --vo=drm —
# they own the framebuffer directly with no compositor.
extra_args = ['--vd-lavc-threads=4']

# Per-board VO selection:
#
# * x86 runs under `cage` (a wlroots kiosk compositor — see
# bin/start_viewer.sh); cage holds DRM master, so --vo=drm
# is denied. Route mpv through the GL VO over a Wayland EGL
# context, the generic path mpv supports on every x86 GPU
# with Mesa or vendor GL drivers. Paired with
# --hwdec=auto-safe, VAAPI-capable iGPUs (Intel iHD/i965,
# AMD radeonsi, …) decode in hardware and hand frames to
# the GL context as DMA-BUFs via dmabuf-interop-gl; software
# decode still works via the same VO. --vo=dmabuf-wayland
# would skip the GL upload entirely but segfaults under
# cage in the viewer's background-spawn path (mpv 0.40.0 +
# wlroots-0.18 + libplacebo dies between hwdec init and
# file open).
#
# * Pi4-64 / Pi5 own the framebuffer directly (no compositor)
# and go through the same GL VO with --gpu-context=drm.
# mpv talks to KMS via libgbm and runs scaling on the V3D
# at the connector's preferred mode (typically 4K on a
# modern TV). The previous --vo=drm path ran the same scale
# on CPU zimg and ran out of headroom on the A72; the
# previous --drm-mode=1920x1080@60 pin sidestepped that by
# forcing the output to 1080p. Under --gpu-context=drm
# neither workaround is needed — the V3D upscales for free
# and the --drm-mode flag actively *hurts* throughput here
# (verified on Pi4-64: 294 drops/30s with the pin vs 3-6
# without, on the same clip). So no pin, no extra mpv VO
# args beyond --vd-lavc-threads above.
if device_type == 'x86':
vo_args = ['--vo=gpu', '--gpu-context=wayland']
elif device_type in ('pi4-64', 'pi5'):
vo_args = ['--vo=gpu', '--gpu-context=drm']
else:
vo_args = ['--vo=drm']

# Issue #2856. On x86, cage/wlroots is rotated via wlr-randr and
# mpv's wayland VO inherits the compositor transform — passing
# --video-rotate here too would double-rotate. On Pi --vo=drm
# writes straight to the framebuffer with no compositor in the
# path, so the rotation has to happen inside mpv. Skip the arg
# at 0° so unrotated displays stay on the existing CLI.
# --video-rotate here too would double-rotate. On Pi mpv talks
# to KMS directly (no compositor in the path, whether via the
# legacy --vo=drm or today's --vo=gpu --gpu-context=drm), so
# the rotation has to happen inside mpv. Skip the arg at 0° so
# unrotated displays stay on the existing CLI.
rotation = _screen_rotation()
rotate_args: list[str] = []
if rotation and device_type != 'x86':
Expand Down
42 changes: 34 additions & 8 deletions tests/test_media_player.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,19 +47,21 @@ def mpv() -> Iterator[_MPVFixtures]:
return_value='sysdefault:CARD=vc4hdmi0',
)
@patch('anthias_viewer.media_player.subprocess.Popen')
def test_play_invokes_popen_with_expected_args(
def test_play_invokes_popen_with_expected_args_on_pi4_64(
mock_popen: Any, _mock_detect: Any, mpv: _MPVFixtures
) -> None:
mpv.player.set_asset('file:///test/video.mp4', 30)
with patch.dict('os.environ', {'DEVICE_TYPE': 'pi4'}):
with patch.dict('os.environ', {'DEVICE_TYPE': 'pi4-64'}):
mpv.player.play()

mock_popen.assert_called_once_with(
[
'mpv',
'--no-terminal',
'--vo=drm',
'--vo=gpu',
'--gpu-context=drm',
'--hwdec=auto-safe',
'--vd-lavc-threads=4',
'--audio-device=alsa/sysdefault:CARD=vc4hdmi0',
'--',
'file:///test/video.mp4',
Expand All @@ -70,35 +72,39 @@ def test_play_invokes_popen_with_expected_args(


@patch('anthias_viewer.media_player.subprocess.Popen')
def test_play_pins_1080p_mode_on_pi4_64(
def test_play_tunes_decoder_threads_on_pi4_64(
mock_popen: Any, mpv: _MPVFixtures
) -> None:
mpv.player.set_asset('file:///test/video.mp4', 30)
with patch.dict('os.environ', {'DEVICE_TYPE': 'pi4-64'}):
mpv.player.play()

args, _ = mock_popen.call_args
assert '--drm-mode=1920x1080@60' in args[0]
assert '--vd-lavc-threads=4' in args[0]
assert '--hwdec=auto-safe' in args[0]
assert '--hwdec=v4l2m2m-copy' not in args[0]
# --drm-mode pinning was used on the legacy --vo=drm path to dodge
# CPU zimg upscale at 4K; under --gpu-context=drm the V3D handles
# scaling, the pin is no longer needed, and it actively hurts
# throughput when combined with GBM (verified on Pi4 hardware).
assert '--drm-mode=1920x1080@60' not in args[0]


@patch('anthias_viewer.media_player.subprocess.Popen')
def test_play_pins_1080p_mode_on_pi5(
def test_play_tunes_decoder_threads_on_pi5(
mock_popen: Any, mpv: _MPVFixtures
) -> None:
mpv.player.set_asset('file:///test/video.mp4', 30)
with patch.dict('os.environ', {'DEVICE_TYPE': 'pi5'}):
mpv.player.play()

args, _ = mock_popen.call_args
assert '--drm-mode=1920x1080@60' in args[0]
assert '--vd-lavc-threads=4' in args[0]
assert '--drm-mode=1920x1080@60' not in args[0]


@patch('anthias_viewer.media_player.subprocess.Popen')
def test_play_does_not_pin_mode_on_x86(
def test_play_omits_pi_tuning_on_x86(
mock_popen: Any, mpv: _MPVFixtures
) -> None:
mpv.player.set_asset('file:///test/video.mp4', 30)
Expand All @@ -124,6 +130,26 @@ def test_play_uses_wayland_vo_on_x86(
assert '--vo=drm' not in args[0]


@pytest.mark.parametrize('device_type', ['pi4-64', 'pi5'])
@patch('anthias_viewer.media_player.subprocess.Popen')
def test_play_uses_drm_gpu_context_on_pi4_64_and_pi5(
mock_popen: Any, mpv: _MPVFixtures, device_type: str
) -> None:
# Pi4-64 and Pi5 own the framebuffer directly (no compositor)
# and use mpv's GL VO with --gpu-context=drm. This offloads the
# 1080p->4K upscale to the V3D instead of the A72/A76 CPU zimg
# path that --vo=drm took.
mpv.player.set_asset('file:///test/video.mp4', 30)
with patch.dict('os.environ', {'DEVICE_TYPE': device_type}):
mpv.player.play()

args, _ = mock_popen.call_args
assert '--vo=gpu' in args[0]
assert '--gpu-context=drm' in args[0]
assert '--vo=drm' not in args[0]
assert '--gpu-context=wayland' not in args[0]


@patch('anthias_viewer.media_player.subprocess.Popen')
def test_play_uses_local_audio_device_when_configured(
mock_popen: Any, mpv: _MPVFixtures
Expand Down