summaryrefslogtreecommitdiffstats
path: root/packages/meshbay-node/tests
diff options
context:
space:
mode:
Diffstat (limited to 'packages/meshbay-node/tests')
-rw-r--r--packages/meshbay-node/tests/test_hwaccel.py171
-rw-r--r--packages/meshbay-node/tests/test_stream_video_transcode.py48
2 files changed, 219 insertions, 0 deletions
diff --git a/packages/meshbay-node/tests/test_hwaccel.py b/packages/meshbay-node/tests/test_hwaccel.py
new file mode 100644
index 0000000..a79d784
--- /dev/null
+++ b/packages/meshbay-node/tests/test_hwaccel.py
@@ -0,0 +1,171 @@
+"""
+The GPU re-encoding plan, and the one thing that must stay true across both
+encoders.
+
+`hwaccel.py` exists so that a low-power node can re-encode a browser-hostile
+video at all — see its module docstring. What is tested here is the part that
+holds whether or not the machine running the tests has a GPU: the shape of the
+plan, what a demotion does, and the agreement between the codec string the node
+announces and the arguments each encoder is actually given.
+
+The end-to-end fallback — a hardware mode that fails, the stream playing anyway
+— is in `test_stream_video_transcode.py`, because it needs a real ffmpeg and a
+real file.
+"""
+
+import re
+import sys
+from pathlib import Path
+
+import pytest
+from meshbay_node import hwaccel
+from meshbay_node.transport import webrtc_server
+
+from conftest import needs_subprocess
+
+VAAPI = next(c for c in hwaccel.CANDIDATES if c.name == "vaapi")
+FAKE = hwaccel.Encoder(candidate=VAAPI, variant="standard", extra=(),
+ device="/dev/dri/renderD128")
+
+
+@pytest.fixture(autouse=True)
+def _clean():
+ hwaccel._reset_for_tests()
+ yield
+ hwaccel._reset_for_tests()
+ hwaccel.set_enabled(True)
+
+
+async def test_no_working_encoder_means_the_software_path_and_nothing_else(monkeypatch):
+ """
+ The guarantee this module owes every node that has no GPU, no driver, or a
+ driver that fails its test encode: one mode, the one that was always there.
+ A second mode would mean a second ffmpeg spawn and a second wait on a
+ machine that can never benefit from either.
+ """
+ monkeypatch.setattr(hwaccel, "encoder", _fixed(None))
+
+ assert await hwaccel.modes_for("hevc") == [hwaccel.SW]
+
+
+async def test_the_plan_always_ends_in_software(monkeypatch):
+ monkeypatch.setattr(hwaccel, "encoder", _fixed(FAKE))
+
+ assert await hwaccel.modes_for("hevc") == [hwaccel.HW, hwaccel.HWENC, hwaccel.SW]
+
+
+async def test_a_demotion_is_remembered_for_that_source_codec_only(monkeypatch):
+ """
+ iHD decodes HEVC and has no MPEG-4 Part 2 decoder at all, so "the GPU
+ cannot do this one" is a fact about the pair, not about the machine.
+ Demoting the machine would cost every HEVC film the acceleration that one
+ Xvid file proved was unavailable to *it*.
+ """
+ monkeypatch.setattr(hwaccel, "encoder", _fixed(FAKE))
+ hwaccel.demote("mpeg4", hwaccel.HW, "no decoder")
+
+ assert await hwaccel.modes_for("mpeg4") == [hwaccel.HWENC, hwaccel.SW]
+ assert await hwaccel.modes_for("hevc") == [hwaccel.HW, hwaccel.HWENC, hwaccel.SW]
+
+
+async def test_the_operator_switch_turns_off_the_probe_itself(monkeypatch):
+ """`hardware_video_encode = false` must cost no subprocess, not merely go
+ unused: the probe is a test encode, and a node whose operator said no
+ should never spawn it."""
+ spawned = []
+ monkeypatch.setattr(hwaccel, "_test_encode",
+ lambda *a, **k: spawned.append(a) or None)
+ hwaccel.set_enabled(False)
+
+ assert await hwaccel.encoder() is None
+ assert await hwaccel.modes_for("hevc") == [hwaccel.SW]
+ assert spawned == []
+
+
+def test_every_encoder_produces_what_the_node_announces():
+ """
+ `stream_init` carries `avc1.640029` and the client puts it through
+ MediaSource.isTypeSupported before it trusts a byte of the stream. The
+ string is not a label chosen next to libx264 — it is a claim about the
+ bytes, and it has to stay true for whichever encoder produced them.
+
+ So the claim is read out of the streaming code and decoded (profile_idc
+ 0x64 = 100 = High, level_idc 0x29 = 41 = 4.1) rather than restated here,
+ and every mode is checked against it. Changing the arguments of one encoder
+ without the other, or changing either without the announced string, fails.
+ """
+ source = Path(webrtc_server.__file__).read_text(encoding="utf-8")
+ announced = set(re.findall(r"avc1\.([0-9a-f]{6})", source))
+ assert announced == {"640029"}, (
+ "the streaming path announces a codec string this test does not know "
+ f"how to check: {announced}")
+
+ (code,) = announced
+ profile_idc, constraints, level_idc = (int(code[i:i + 2], 16) for i in (0, 2, 4))
+ assert (profile_idc, constraints, level_idc) == (100, 0, 41), (
+ "the announced string no longer says High/4.1, so the arguments below "
+ "are no longer what it claims")
+
+ for mode in (hwaccel.SW, hwaccel.HW, hwaccel.HWENC):
+ args = hwaccel.codec_args(mode, FAKE)
+ assert args[args.index("-profile:v") + 1] == "high", \
+ f"{mode} does not encode the High profile the node announces"
+ assert args[args.index("-level") + 1] == "4.1", \
+ f"{mode} does not encode at the level the node announces"
+
+
+def test_the_software_mode_never_touches_the_gpu():
+ """The fallback has to be a real fallback: if `sw` carried a VA-API
+ argument, the mode that exists for machines where the GPU failed would
+ fail with it."""
+ for args in (hwaccel.input_args(hwaccel.SW, FAKE),
+ hwaccel.codec_args(hwaccel.SW, FAKE)):
+ joined = " ".join(args)
+ assert "vaapi" not in joined and "hwupload" not in joined, joined
+ assert hwaccel.input_args(hwaccel.HW, None) == []
+ assert "libx264" in hwaccel.codec_args(hwaccel.HW, None)
+
+
+def _fixed(value):
+ async def _f():
+ return value
+ return _f
+
+
+@needs_subprocess
+async def test_the_probe_accepts_only_what_the_node_announces():
+ """The acceptance test is neither always true nor always false.
+
+ Both halves are run with libx264, which is on any machine that can run
+ these tests, so what is being checked is the judgement itself rather than
+ anybody's GPU: the same encoder is accepted when it writes High at level
+ 4.1 and rejected when it writes level 3.1. Without this, an encoder whose
+ arguments were spelled in a way ffmpeg accepts but misreads would pass the
+ probe and then announce a level it had not produced.
+ """
+ def _software(label: str, extra: tuple[str, ...]) -> hwaccel.Candidate:
+ return hwaccel.Candidate(
+ name=label, encoder="libx264", platforms=(sys.platform,),
+ hwaccel="none", hw_filter="null", sw_filter="format=yuv420p",
+ variants=((label, extra),))
+
+ right = _software("as announced", ("-preset", "veryfast", "-crf", "30"))
+ assert await hwaccel._test_encode(right, None, right.variants[0][1]) is None
+
+ wrong = _software("a level lower",
+ ("-preset", "veryfast", "-crf", "30", "-level", "3.1"))
+ why = await hwaccel._test_encode(wrong, None, wrong.variants[0][1])
+ assert why and "31" in why, (
+ f"an encoder that wrote another level must be rejected, and say so: {why!r}")
+
+
+@needs_subprocess
+async def test_a_missing_encoder_is_rejected_rather_than_raised():
+ """ffmpeg exits non-zero for an encoder it does not have; nothing here may
+ turn that into an exception, because it happens on every machine that lacks
+ one of the three candidates — which is most of them."""
+ absent = hwaccel.Candidate(
+ name="not a thing", encoder="h264_definitely_not", platforms=(sys.platform,),
+ hwaccel="none", hw_filter="null", sw_filter="null", variants=(("x", ()),))
+
+ assert await hwaccel._test_encode(absent, None, ()) is not None
diff --git a/packages/meshbay-node/tests/test_stream_video_transcode.py b/packages/meshbay-node/tests/test_stream_video_transcode.py
index b165af4..0fd12c6 100644
--- a/packages/meshbay-node/tests/test_stream_video_transcode.py
+++ b/packages/meshbay-node/tests/test_stream_video_transcode.py
@@ -35,6 +35,7 @@ from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
from meshbay_common.crypto import generate_gek
from meshbay_common.webcrypto import chunk_key_aes, decrypt_chunk_aes
+from meshbay_node import hwaccel
from meshbay_node.indexer.group_index import GroupIndex
from meshbay_node.transport.webrtc_server import WebRTCPeerSession, _probe_video
@@ -271,3 +272,50 @@ async def test_the_operator_can_refuse_the_re_encode_and_says_so(tmp_path):
f"the refusal must point at the setting that caused it: {detail!r}")
assert not [m for m in session.sent if m.get("type") == "stream_init"], (
"a stream_init went out with no codec string the client could check")
+
+
+@needs_mp3
+async def test_a_hardware_encoder_that_does_not_work_costs_the_viewer_nothing(
+ tmp_path, monkeypatch):
+ """A VA-API mode that fails falls through to libx264 and the film plays.
+
+ This is the failure that cannot be prevented by probing: `hwaccel.py`
+ proves the *encoder* with a test encode at startup, and nothing proves the
+ GPU can decode a particular source until it is asked to — iHD has no
+ MPEG-4 Part 2 decoder at all, which is exactly the file used here.
+
+ The encoder is faked onto `/dev/null` rather than skipped for want of a
+ GPU, so this runs identically on a machine with one and on a machine
+ without: both hardware modes fail instantly, and what is tested is that
+ the viewer sees one working stream and not an error. The demotions are
+ checked too — a library of Xvid files must not pay for the first one's two
+ dead spawns again and again.
+ """
+ clip = tmp_path / "clip.avi"
+ _make_mpeg4_clip(clip)
+ gek = generate_gek()
+ session, file_id = _session(clip, gek)
+
+ hwaccel._reset_for_tests()
+
+ async def _broken_encoder():
+ vaapi = next(c for c in hwaccel.CANDIDATES if c.name == "vaapi")
+ return hwaccel.Encoder(candidate=vaapi, variant="standard", extra=(),
+ device="/dev/null")
+
+ monkeypatch.setattr(hwaccel, "encoder", _broken_encoder)
+ try:
+ await session._stream_video_inner({"file_id": file_id, "start": 0, "credits": 0})
+
+ assert not [m for m in session.sent if m.get("type") == "error"], (
+ "a hardware encoder that does not work must cost the viewer nothing")
+ remuxed = _reassemble(session.sent, gek, file_id)
+ out_path = tmp_path / "out.mp4"
+ out_path.write_bytes(remuxed)
+ assert _output_video_codec(out_path) == "h264", \
+ "the software fallback must still deliver a playable H264 stream"
+
+ assert ("mpeg4", hwaccel.HW) in hwaccel._demoted
+ assert ("mpeg4", hwaccel.HWENC) in hwaccel._demoted
+ finally:
+ hwaccel._reset_for_tests()