Complete VIDEO-001 through VIDEO-004 software video studies

This commit is contained in:
LittleSam129
2026-09-16 18:27:35 +03:00
parent af7c05f2da
commit 95f664076a
403 changed files with 392009 additions and 0 deletions

View 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)