Complete VIDEO-001 through VIDEO-004 software video studies
This commit is contained in:
85
tests/test_video004_erasure.py
Normal file
85
tests/test_video004_erasure.py
Normal file
@@ -0,0 +1,85 @@
|
||||
"""Восстановление целых видеопакетов и срок жизни фрагментов VIDEO-004."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import random
|
||||
|
||||
import pytest
|
||||
|
||||
from protocol.packet_erasure_fec import InsufficientSymbolsError
|
||||
from protocol.video_stream_transport import FrameType, VideoFragment
|
||||
from protocol.video004_erasure import (
|
||||
MAX_VIDEO_BYTES, VideoErasureCollector, VideoErasureError,
|
||||
encode_block, parse_symbol, recover_block,
|
||||
)
|
||||
|
||||
|
||||
def _fragments(count: int, size: int = MAX_VIDEO_BYTES) -> tuple[tuple[VideoFragment, int], ...]:
|
||||
return tuple((VideoFragment(19, index // 4, index % 4, 4,
|
||||
FrameType.KEYFRAME if index < 4 else FrameType.DELTA_FRAME,
|
||||
0, bytes([index]) * (size if index != count - 1 else 7)),
|
||||
(index // 4) * 100) for index in range(count))
|
||||
|
||||
|
||||
@pytest.mark.parametrize("source_count,parity_count", [
|
||||
(8, 1), (8, 2), (12, 2), (12, 3), (16, 2), (16, 4),
|
||||
])
|
||||
def test_recover_exact_source_from_one_and_up_to_r_losses(
|
||||
source_count: int, parity_count: int,
|
||||
) -> None:
|
||||
fragments = _fragments(source_count)
|
||||
wires = encode_block(fragments, 27, parity_count)
|
||||
assert len(wires) == source_count + parity_count
|
||||
assert all(len(wire) == 1012 for wire in wires)
|
||||
assert all(parse_symbol(wire).block_id == 27 for wire in wires)
|
||||
expected = recover_block(wires[:source_count])
|
||||
assert [item.fragment for item in expected] == [item[0] for item in fragments]
|
||||
assert [item.capture_ms for item in expected] == [item[1] for item in fragments]
|
||||
for erased_count in (1, parity_count):
|
||||
erased = set(random.Random(source_count * 100 + erased_count).sample(
|
||||
range(source_count), erased_count))
|
||||
available = tuple(wire for index, wire in enumerate(wires) if index not in erased)
|
||||
recovered = recover_block(available)
|
||||
assert [item.raw_video_wire for item in recovered] == [
|
||||
item.raw_video_wire for item in expected]
|
||||
assert {item.index for item in recovered if item.recovered} == erased
|
||||
with pytest.raises(InsufficientSymbolsError):
|
||||
recover_block(wires[parity_count + 1:])
|
||||
|
||||
|
||||
def test_collector_releases_systematic_immediately_and_rejects_late_recovery() -> None:
|
||||
fragments = _fragments(8)
|
||||
wires = encode_block(fragments, 5, 2)
|
||||
collector = VideoErasureCollector(19)
|
||||
first = collector.offer(wires[0], 15)
|
||||
assert len(first) == 1 and first[0].index == 0 and not first[0].recovered
|
||||
for index, wire in enumerate(wires[1:8], start=1):
|
||||
collector.offer(wire, 20 if index < 4 else 120)
|
||||
assert collector.parity_recoveries == 0
|
||||
missing = VideoErasureCollector(19)
|
||||
for index, wire in enumerate(wires[1:8], start=1):
|
||||
missing.offer(wire, 20 if index < 4 else 120)
|
||||
recovered = missing.offer(wires[8], 301)
|
||||
assert not recovered
|
||||
assert missing.stale_rejections >= 1
|
||||
timely = VideoErasureCollector(19)
|
||||
for index, wire in enumerate(wires[1:8], start=1):
|
||||
timely.offer(wire, 20 if index < 4 else 120)
|
||||
recovered = timely.offer(wires[8], 250)
|
||||
assert [item.index for item in recovered] == [0]
|
||||
assert timely.parity_recoveries == 1
|
||||
|
||||
|
||||
def test_invalid_metadata_and_mixed_stream_are_rejected() -> None:
|
||||
fragments = _fragments(8)
|
||||
wire = encode_block(fragments, 2, 1)[0]
|
||||
broken = bytearray(wire)
|
||||
broken[0] ^= 0xFF
|
||||
with pytest.raises(VideoErasureError):
|
||||
parse_symbol(bytes(broken))
|
||||
other = VideoErasureCollector(20)
|
||||
with pytest.raises(VideoErasureError):
|
||||
other.offer(wire, 0)
|
||||
collector = VideoErasureCollector(19)
|
||||
with pytest.raises(VideoErasureError, match="будущим"):
|
||||
collector.offer(encode_block(_fragments(8), 3, 1)[4], 10)
|
||||
Reference in New Issue
Block a user