Files
NODEDC_MISSION_CORE/tests/test_perception_streaming_decoder.py
T

248 lines
8.2 KiB
Python

"""Synthetic boxes and fake codec only; real decoding belongs on Worker 006."""
import subprocess
import sys
from types import SimpleNamespace
import pytest
from k1link import media_fragments as mp4
from k1link.perception import streaming_decoder as decoder_module
def box(kind, data=b""):
return (len(data) + 8).to_bytes(4, "big") + kind + data
def u32(value):
return value.to_bytes(4, "big")
def fixture(*, dts=0, sync=True, size=5, offset_delta=0, count=1, trun_flags=1):
tkhd = box(b"tkhd", bytes(12) + u32(1))
mdhd = box(b"mdhd", bytes(12) + u32(1000))
hdlr = box(b"hdlr", bytes(8) + b"vide")
init = box(b"moov", box(b"trak", tkhd + box(b"mdia", mdhd + hdlr)))
tfhd = box(
b"tfhd", b"\x00\x02\x00\x38" + u32(1) + u32(100) + u32(size) + u32(0 if sync else 0x10000)
)
tfdt = box(b"tfdt", bytes(4) + u32(dts))
mfhd = box(b"mfhd", bytes(8))
def moof(offset):
trun = box(b"trun", u32(trun_flags) + u32(count) + u32(offset))
return box(b"moof", mfhd + box(b"traf", tfhd + tfdt + trun))
return init, moof(len(moof(0)) + 8 + offset_delta) + box(b"mdat", b"frame")
def test_shared_parser_and_direct_sample_are_source_neutral():
init, fragment = fixture()
timing = mp4.video_timing(init, mp4.ParseBudget(128, 1))
frame = mp4.video_fragment_timing(fragment, timing, mp4.ParseBudget(128, 1))
assert (timing.track_id, timing.timescale) == (1, 1000)
assert (frame.base_decode_time, frame.duration_units, frame.random_access) == (0, 100, True)
sample = mp4.single_sample_payload(fragment, timing)
assert sample == b"frame" and sample.obj is fragment
script = (
"import sys; import k1link.perception.streaming_decoder; "
"assert 'k1link.sessions' not in sys.modules; "
"assert 'k1link.compute' not in sys.modules; assert 'av' not in sys.modules"
)
subprocess.run([sys.executable, "-c", script], check=True, timeout=5)
@pytest.mark.parametrize(
"changes,match",
[
({"size": 4}, "size"),
({"offset_delta": 1}, "offset"),
({"count": 2}, "one sample"),
({"trun_flags": 0x801}, "composition"),
],
)
def test_direct_sample_rejects_guessed_offsets_or_sizes(changes, match):
init, fragment = fixture(**changes)
with pytest.raises(mp4.Mp4IntegrityError, match=match):
mp4.single_sample_payload(fragment, mp4.video_timing(init, mp4.ParseBudget()))
def test_parser_bounds_and_truncation():
init, fragment = fixture(count=2)
timing = mp4.video_timing(init, mp4.ParseBudget())
with pytest.raises(mp4.Mp4IntegrityError, match="sample budget"):
mp4.video_fragment_timing(fragment, timing, mp4.ParseBudget(128, 1))
with pytest.raises(mp4.Mp4IntegrityError, match="box budget"):
mp4.video_timing(init, mp4.ParseBudget(1, 1))
with pytest.raises(mp4.Mp4IntegrityError, match="box size"):
mp4.single_sample_payload(fragment[:-1], timing)
with pytest.raises(mp4.Mp4IntegrityError, match="moof then mdat"):
mp4.single_sample_payload(fragment + box(b"free"), timing)
@pytest.fixture
def fake_av(monkeypatch):
class Image:
shape, nbytes = (600, 800, 3), 1_440_000
def setflags(self, *, write):
assert write is False
class Codec:
has_b_frames = False
def open(self):
pass
def decode(self, packet):
self.calls.append(packet)
return (
[]
if self.future
else [
SimpleNamespace(
width=self.width,
height=600,
pts=packet.pts + self.pts_delta,
key_frame=self.key_frame,
to_ndarray=lambda **kwargs: Image(),
)
]
)
codec = Codec()
codec.calls, codec.future, codec.width, codec.pts_delta, codec.key_frame = (
[],
False,
800,
0,
True,
)
class Streams(list):
@property
def video(self):
return self
class Container:
def __enter__(self):
return SimpleNamespace(
streams=Streams(
[
SimpleNamespace(
codec_context=SimpleNamespace(
name="h264", width=800, height=600, extradata=b"config"
)
)
]
)
)
def __exit__(self, *args):
pass
opens = []
def open_container(stream, **kwargs):
opens.append(stream.getvalue())
return Container()
av = SimpleNamespace(
__version__="18.0.0",
open=open_container,
CodecContext=SimpleNamespace(create=lambda *args: codec),
Packet=lambda raw: SimpleNamespace(raw=bytes(raw)),
)
original = decoder_module.importlib.import_module
monkeypatch.setattr(
decoder_module.importlib,
"import_module",
lambda name: av if name == "av" else original(name),
)
return codec, av, opens
def test_epoch_reset_discards_predictive_state_but_keeps_decoder_object(fake_av):
decoder = decoder_module.FragmentDecoder()
init, frame = fixture()
decoder.configure(init)
decoder.decode(frame)
decoder.reset(init)
assert decoder.frames == 0 and decoder._next_dts is None
decoder.decode(fixture(dts=500)[1])
assert decoder.frames == 1 and decoder._next_dts == 600
decoder.reset(init)
with pytest.raises(decoder_module.StreamingDecodeError, match="random-access"):
decoder.decode(fixture(dts=600, sync=False)[1])
with pytest.raises(decoder_module.StreamingDecodeError, match="failed decoder"):
decoder.reset(init)
def test_persistent_codec_emits_current_frame_without_demux_or_flush(fake_av):
codec, _, opens = fake_av
init, fragment = fixture()
decoder = decoder_module.FragmentDecoder()
decoder.configure(init)
for sequence in range(3):
decoder.decode(fixture(dts=sequence * 100)[1])
assert len(opens) == 1 and len(codec.calls) == decoder.frames == 3
assert codec.thread_count == 1 and codec.thread_type == "SLICE"
assert all(p.raw == b"frame" and p.pts == p.dts for p in codec.calls)
decoder.close()
with pytest.raises(decoder_module.StreamingDecodeError, match="failed"):
decoder.decode(fragment)
@pytest.mark.parametrize(
"mutation,match",
[
(("future", True), "future"),
(("has_b_frames", True), "reordering"),
(("width", 801), "raster"),
(("pts_delta", 1), "identity"),
(("key_frame", False), "keyframe"),
],
)
def test_ambiguous_native_output_poisons_decoder(fake_av, mutation, match):
codec, _, _ = fake_av
setattr(codec, *mutation)
decoder = decoder_module.FragmentDecoder()
init, fragment = fixture()
decoder.configure(init)
with pytest.raises(decoder_module.StreamingDecodeError, match=match):
decoder.decode(fragment)
assert decoder.failed and decoder.frames == 0
def test_codec_gap_does_not_use_later_frame_as_current(fake_av):
codec, _, _ = fake_av
decoder = decoder_module.FragmentDecoder()
decoder.configure(fixture()[0])
decoder.decode(fixture()[1])
with pytest.raises(decoder_module.StreamingDecodeError, match="discontinuous"):
decoder.decode(fixture(dts=200)[1])
assert len(codec.calls) == 1 and decoder.failed
@pytest.mark.parametrize("case", ["no-init", "reinit", "non-keyframe", "oversized", "version"])
def test_invalid_stream_setup_fails_closed(fake_av, case):
_, av, _ = fake_av
if case == "version":
av.__version__ = "other"
with pytest.raises(decoder_module.StreamingDecodeError, match="version"):
decoder_module.FragmentDecoder()
return
decoder = decoder_module.FragmentDecoder()
if case != "no-init":
decoder.configure(fixture()[0])
with pytest.raises(decoder_module.StreamingDecodeError):
if case == "reinit":
decoder.configure(fixture()[0])
else:
decoder.decode(
b"x" * (1024 * 1024 + 1)
if case == "oversized"
else fixture(sync=case != "non-keyframe")[1]
)
assert decoder.failed