Files
spoopyghosty0--frameport/tests/test_hevc_codec.py
T
spoopyghosty0 44bc689348 Shared video decoder (codec revision 8): hidden VP9 frames no longer end the EOS drain early
Iris returns an empty capture buffer (bytesused 0, no LAST flag, time 0) for every VP9 frame that isn't shown
(alt-ref frames split from superframes). FFmpeg's V4L2 wrapper takes any empty capture buffer during a drain
for the end of the stream, so the first hidden frame decoded after EOS ended it and the pictures still in the
driver were lost (two-pass VP9 4K: 573/600 and 617/640, always the last frames); outside a drain the empty
buffers were returned as pictures, which the component then guessed away by counting hidden frames.
build.py now patches v4l2_context.c to requeue empty capture buffers without LAST/ERROR; the drain ends at the
LAST buffer, or after a skipped empty buffer at one silent second, so it can't block. The component's
hidden-frame counting is removed. Dev Frame (Batman's container): two-pass VP9 4K 600/600 and 640/640 in
hardware with the same Y hashes as OMX.google.vp9.decoder (seek phase too), VP9 without hidden frames,
H.264 4K and HEVC 4K 600/600, VP9 surface output 600/600. Two builds (different dirs and NDK copies) give the
same libstagefrighthw.so 141de01f.
2026-10-10 02:22:48 -04:00

247 lines
12 KiB
Python

"""Codec packaging and per-game container isolation; no game assets needed."""
import hashlib
import importlib.util
import json
import os
from pathlib import Path
import pytest
ROOT = Path(__file__).resolve().parents[1]
def load_module(path, name):
spec = importlib.util.spec_from_file_location(name, path)
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
return module
def test_assets_have_matching_checksums():
directory = ROOT / "artifacts/hevc"
manifest = json.loads((directory / "manifest.json").read_text())
for name, expected in manifest["files"].items():
path = directory / (name + ".txt" if name == "podman.py" else name)
assert hashlib.sha256(path.read_bytes()).hexdigest() == expected
assert (directory / "podman.py.txt").read_bytes() == (ROOT / "native/hevc/podman.py").read_bytes()
def test_mounts_only_matching_game_and_runtime(tmp_path, monkeypatch):
wrapper = load_module(ROOT / "native/hevc/podman.py", "codec_wrapper")
runtime = tmp_path / "lepton/images/rootfs"
(runtime / "vendor/lib64").mkdir(parents=True)
(runtime / "vendor/etc").mkdir()
(runtime / "vendor/lib64/libstagefright_softomx.so").write_bytes(b"runtime ABI")
xml = runtime / "vendor/etc/media_codecs.xml"
xml.write_text('<MediaCodecs><Include href="stock.xml" /></MediaCodecs>')
directory = tmp_path / "game/frameport-codec"
directory.mkdir(parents=True)
for name in ("libstagefrighthw.so", "media_codecs_frameport.xml"):
(directory / name).write_bytes(b"codec asset")
config = {"lepton": str(tmp_path / "lepton/lepton"), "appid": "123",
"runtime_sha256": hashlib.sha256(b"runtime ABI").hexdigest()}
exists = Path.exists
monkeypatch.setattr(Path, "exists", lambda p: True if p.as_posix() == "/dev/video-dec0" else exists(p))
(tmp_path / "run").mkdir()
monkeypatch.setenv("XDG_RUNTIME_DIR", str(tmp_path / "run"))
args = ["run", "--name", "lepton-steamlaunch-123", "--rootfs", str(runtime) + ":O", "/init"]
extra = wrapper.mounts(directory, config, args)
assert [s.replace("\\", "/") for s in extra[:2]] == [
"--mount", "type=bind,source=/dev/video-dec0,destination=/dev/video-dec0,rw"]
# the merged codec list goes to the runtime dir: the verified version directory is never written
merged = next((tmp_path / "run/frameport-video").glob("media_codecs.*.xml"))
assert "media_codecs_frameport.xml" in merged.read_text()
assert sorted(p.name for p in directory.iterdir()) == ["libstagefrighthw.so", "media_codecs_frameport.xml"]
for spec in extra[1::2][1:]:
fields = dict(item.split("=", 1) for item in spec.split(",") if "=" in item)
assert Path(fields["source"]).is_file()
assert "frameport" not in xml.read_text() # shared runtime stays unchanged
assert wrapper.mounts(directory, config, ["kill", "lepton-steamlaunch-123"]) == []
assert wrapper.mounts(directory, config, [s.replace("123", "456") for s in args]) == []
config["runtime_sha256"] = "wrong ABI"
assert wrapper.mounts(directory, config, args) == []
# The shared wrapper serves any package with an unchanged APK: the launcher (frame.hw_video_decode in the game's
# recipe) puts it on PATH, the container identity limits it to that game's container.
app = tmp_path / "another-package/lepton-app"
app.mkdir(parents=True)
monkeypatch.setenv("SteamAppId", "123")
monkeypatch.setenv("STEAM_COMPAT_INSTALL_PATH", str(app))
shared = {"scope": "shared", "runtime_sha256": hashlib.sha256(b"runtime ABI").hexdigest()}
assert wrapper.mounts(directory, shared, args)
assert wrapper.mounts(directory, shared, [s.replace("123", "456") for s in args]) == []
assert wrapper.mounts(directory, shared, [s.replace(":O", "") for s in args]) == []
assert wrapper.mounts(directory, shared, ["kill", "lepton-steamlaunch-123"]) == []
monkeypatch.delenv("SteamAppId")
assert wrapper.mounts(directory, shared, args) == []
@pytest.mark.parametrize("configuration", [None, "{", "[]", "{}", '{"podman":null}',
'{"podman":"missing-podman"}', "self"])
def test_broken_wrapper_configuration_executes_stock_podman(tmp_path, monkeypatch, configuration):
wrapper = load_module(ROOT / "native/hevc/podman.py", "fallback_wrapper")
directory = tmp_path / "codec"
own = directory / "bin/podman"
own.parent.mkdir(parents=True)
own.write_text("wrapper")
own.chmod(0o755)
real = tmp_path / "system/podman"
real.parent.mkdir()
real.write_text("stock podman")
real.chmod(0o755)
monkeypatch.setattr(wrapper, "__file__", str(own))
monkeypatch.setenv("PATH", str(own.parent) + os.pathsep + str(real.parent))
# Avoid Windows' executable-extension rules: this launcher runs on Linux.
monkeypatch.setattr(wrapper.shutil, "which", lambda _, path: str(Path(path) / "podman"))
if configuration == "self":
configuration = json.dumps({"podman": str(own)})
if configuration is not None:
(directory / "deployment.json").write_text(configuration)
args = ["run", "--name", "lepton-steamlaunch-123", "/init"]
monkeypatch.setattr(wrapper.sys, "argv", [str(own), *args])
executed = []
class ExecSucceeded(BaseException):
pass
def execute(path, argv):
if Path(path) != real:
raise FileNotFoundError(path)
executed.append((path, argv))
raise ExecSucceeded
monkeypatch.setattr(wrapper.os, "execv", execute)
with pytest.raises(ExecSucceeded):
wrapper.main()
assert executed == [(str(real), [str(real), *args])]
def test_failed_codec_mount_or_exec_preserves_original_arguments(tmp_path, monkeypatch):
wrapper = load_module(ROOT / "native/hevc/podman.py", "mount_failure_wrapper")
directory = tmp_path / "codec"
directory.mkdir()
own = directory / "bin/podman"
monkeypatch.setattr(wrapper, "__file__", str(own))
monkeypatch.setattr(wrapper, "real_podman", lambda _: "/usr/bin/podman")
(directory / "deployment.json").write_text('{"podman":"/missing/podman"}')
args = ["run", "--name", "lepton-steamlaunch-123", "/init"]
monkeypatch.setattr(wrapper.sys, "argv", [str(own), *args])
executed = []
class ExecSucceeded(BaseException):
pass
def execute(path, argv):
if path != "/usr/bin/podman":
raise OSError("configured executable is unavailable")
executed.append(argv)
raise ExecSucceeded
monkeypatch.setattr(wrapper.os, "execv", execute)
monkeypatch.setattr(wrapper, "mounts", lambda *_: ["--mount", "private-codec"])
with pytest.raises(ExecSucceeded):
wrapper.main()
assert executed == [["/usr/bin/podman", *args]]
def malformed(*_):
raise KeyError("runtime_sha256")
monkeypatch.setattr(wrapper, "mounts", malformed)
with pytest.raises(ExecSucceeded):
wrapper.main()
assert executed == [["/usr/bin/podman", *args]] * 2
def test_wrapper_finds_podman_under_the_android_path(tmp_path, monkeypatch):
"""Lepton runs `podman exec` with the Android guest's PATH (/system/bin:...): the wrapper still finds the host's
Podman (it used to raise, which broke Lepton's logcat mirror and app-pid checks and stopped the container)."""
wrapper = load_module(ROOT / "native/hevc/podman.py", "android_path_wrapper")
monkeypatch.setattr(wrapper, "__file__", str(tmp_path / "codec/bin/podman"))
monkeypatch.setenv("PATH", "/product/bin:/system/bin:/vendor/bin")
monkeypatch.setattr(
wrapper.shutil, "which",
lambda _, path: path + "/podman" if Path(path).as_posix().endswith("/usr/bin") else None,
)
monkeypatch.setattr(wrapper.Path, "resolve", lambda self: self)
assert Path(wrapper.real_podman(tmp_path / "codec")).as_posix().endswith("/usr/bin/podman")
def test_codec_list_matches_the_plugin_table():
"""H.264, HEVC and VP9: the codec XML names exactly the components the plugin's table enumerates."""
from xml.etree import ElementTree as ET
xml = ET.parse(ROOT / "artifacts/hevc/media_codecs_frameport.xml").getroot()
entries = {c.get("name"): c.get("type") for c in xml.iter("MediaCodec")}
assert entries == {"OMX.frameport.avc.decoder": "video/avc", "OMX.frameport.hevc.decoder": "video/hevc",
"OMX.frameport.vp9.decoder": "video/x-vnd.on2.vp9"}
for codec in xml.iter("MediaCodec"):
limits = {limit.get("name"): limit for limit in codec.iter("Limit")}
# VP9 at 7680x3840 never returned a picture on the Frame (2026-10-09): it stops at 4K
expected = "4096x2304" if "vp9" in codec.get("name") else "8192x8192"
assert limits["concurrent-instances"].get("max") == "1" and limits["size"].get("max") == expected
source = (ROOT / "native/hevc/frameport_hevc.cpp").read_text()
for name, mime in entries.items():
assert f'"{name}"' in source and f'"{mime}"' in source
assert "4096,2304}" in source # the plugin's own VP9 limit (larger VP9 decodes in software)
build = (ROOT / "native/hevc/build.py").read_text()
for decoder in ("h264_v4l2m2m", "hevc_v4l2m2m", "vp9_v4l2m2m"):
assert f'"{decoder}"' in source and decoder in build
assert (ROOT / "artifacts/hevc/media_codecs_frameport.xml").read_bytes() == (
ROOT / "native/hevc/media_codecs_frameport.xml").read_bytes()
@pytest.mark.parametrize("how", ["env", "flag"])
def test_switched_off_wrapper_runs_stock_podman_untouched(tmp_path, monkeypatch, how):
"""FRAMEPORT_NO_HW_VIDEO=1 or FramePort's setting (video-codec/disabled): the wrapper adds nothing."""
wrapper = load_module(ROOT / "native/hevc/podman.py", f"off_wrapper_{how}")
directory = tmp_path / "video-codec/versions/abc"
own = directory / "bin/podman"
own.parent.mkdir(parents=True)
(directory / "deployment.json").write_text('{"scope": "shared", "runtime_sha256": "x"}')
monkeypatch.setattr(wrapper, "__file__", str(own))
monkeypatch.setattr(wrapper, "real_podman", lambda _: "/usr/bin/podman")
monkeypatch.delenv("FRAMEPORT_NO_HW_VIDEO", raising=False)
if how == "env":
monkeypatch.setenv("FRAMEPORT_NO_HW_VIDEO", "1")
else:
(tmp_path / "video-codec/disabled").write_text("off")
monkeypatch.setattr(wrapper, "mounts", lambda *_: pytest.fail("mounts while switched off"))
args = ["run", "--name", "lepton-steamlaunch-123", "/init"]
monkeypatch.setattr(wrapper.sys, "argv", [str(own), *args])
executed = []
class ExecSucceeded(BaseException):
pass
def execute(path, argv):
executed.append(argv)
raise ExecSucceeded
monkeypatch.setattr(wrapper.os, "execv", execute)
with pytest.raises(ExecSucceeded):
wrapper.main()
assert executed == [["/usr/bin/podman", *args]]
monkeypatch.setenv("FRAMEPORT_NO_HW_VIDEO", "0")
(tmp_path / "video-codec/disabled").unlink(missing_ok=True)
assert not wrapper.switched_off(directory)
def test_empty_capture_buffers_are_requeued_not_taken_for_the_end():
"""Iris returns an empty capture buffer (no LAST flag) for each hidden VP9 frame. FFmpeg's wrapper ended the EOS
drain at the first one and lost the pictures still in the driver (two-pass VP9 4K: 573 of 600, 2026-10-10)."""
build = load_module(ROOT / "native/hevc/build.py", "hevc_build")
stand_in = ("static V4L2Buffer* v4l2_dequeue_v4l2buf(V4L2Context *ctx, int timeout)\n{\n struct pollfd pfd = {\n"
+ build.EMPTY_DECLARE + "start:\n" + build.EMPTY_POLL + "dequeue:\n"
+ build.EMPTY_DRAIN + " = 0;\n }\n}\n")
patched = build.skip_empty_pictures(stand_in)
assert "int i, ret, skipped_empty = 0;" in patched
# empty buffers are skipped before the drain's "empty buffer = end" check, unless LAST/ERROR is set
assert patched.index("ff_v4l2_buffer_enqueue(&ctx->buffers[buf.index])") < patched.index(build.EMPTY_DRAIN)
assert "!(buf.flags & (V4L2_BUF_FLAG_LAST | V4L2_BUF_FLAG_ERROR))" in patched
# a drain can't block forever after a skipped empty buffer
assert "if (!ret && skipped_empty && ctx_to_m2mctx(ctx)->draining) {" in patched
with pytest.raises(RuntimeError):
build.skip_empty_pictures(stand_in.replace(build.EMPTY_POLL, ""))
# the component no longer guesses which pictures belong to hidden frames
assert "vp9Hidden" not in (ROOT / "native/hevc/frameport_hevc.cpp").read_text()