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,130 @@
"""Проверки целостности и ограничения возраста видеокадра."""
from __future__ import annotations
from dataclasses import replace
import pytest
from protocol.video_stream_transport import (
BoundedFrameQueue, FrameReassembler, FrameType, VideoFrame,
VideoTransportError, decode_fragment, encode_fragment, is_newer,
split_frame,
)
from tools.video002_radio_budget import estimate, margin_fits
def frame(number: int = 1, capture_ms: int = 0, size: int = 600) -> VideoFrame:
return VideoFrame(7, number, FrameType.KEYFRAME, capture_ms, bytes(range(256)) *
(size // 256) + bytes(range(size % 256)))
def test_header_roundtrip_and_corruption() -> None:
part = split_frame(frame(), 256)[0]
wire = encode_fragment(part, 256)
assert len(wire) == 256
assert decode_fragment(wire, 256) == part
with pytest.raises(VideoTransportError):
decode_fragment(wire[:-1], 256)
with pytest.raises(VideoTransportError):
decode_fragment(bytes([0x21]) + wire[1:], 256)
with pytest.raises(VideoTransportError):
encode_fragment(replace(part, fragment_index=part.fragment_count), 256)
def test_out_of_order_reassembly_no_partial_or_replay() -> None:
source = frame()
parts = [encode_fragment(item, 256) for item in split_frame(source, 256)]
receiver = FrameReassembler(7, 256)
assert receiver.offer(parts[2], 20) is None
assert receiver.offer(parts[0], 21) is None
assert receiver.offer(parts[0], 22) is None
complete = receiver.offer(parts[1], 23)
assert complete is not None and complete.data == source.data
assert receiver.offer(parts[1], 24) is None
assert receiver.rejected_fragments == 1
def test_incomplete_frame_expires_and_new_frame_advances() -> None:
receiver = FrameReassembler(7, 256, max_age_ms=100)
first = encode_fragment(split_frame(frame(1), 256)[0], 256)
assert receiver.offer(first, 0) is None
assert receiver.expire(100) == (1,)
assert receiver.offer(first, 101) is None
next_frame = frame(2, 101, 25)
complete = receiver.offer(encode_fragment(split_frame(next_frame, 256)[0], 256), 102)
assert complete is not None and complete.data == next_frame.data
def test_sender_bounded_queue_discards_old_frame() -> None:
sender = BoundedFrameQueue(256, max_frames=2, max_age_ms=100)
sender.enqueue(frame(1, 0), 0)
sender.enqueue(frame(2, 20), 20)
sender.enqueue(frame(3, 40), 40)
assert sender.depth == 2 and sender.capacity_frame_drops == 1
part = sender.next_fragment(40, 50)
assert part is not None and part.frame_id == 2
sender.expire(120)
assert sender.stale_frame_drops == 1
assert sender.next_fragment(120, 130).frame_id == 3
sender.expire(140)
assert sender.depth == 0 and sender.stale_frame_drops == 2
def test_fractional_packet_end_drops_before_receiver_age_rounding() -> None:
sender = BoundedFrameQueue(256, max_age_ms=300)
sender.enqueue(frame(1, 0, 25), 0)
assert sender.next_fragment(299, 299.1) is None
assert sender.stale_frame_drops == 1
def test_conflicting_fragment_closes_frame() -> None:
receiver = FrameReassembler(7, 256)
original = split_frame(frame(), 256)[0]
receiver.offer(encode_fragment(original, 256), 0)
bad = replace(original, data=b"X" + original.data[1:])
with pytest.raises(VideoTransportError):
receiver.offer(encode_fragment(bad, 256), 1)
assert receiver.offer(encode_fragment(original, 256), 2) is None
def test_wrong_stream_and_wraparound() -> None:
receiver = FrameReassembler(7, 256)
other = encode_fragment(split_frame(replace(frame(size=1), stream_id=8), 256)[0], 256)
assert receiver.offer(other, 0) is None
assert is_newer(0, 0xFFFFFFFF)
assert not is_newer(0xFFFFFFFE, 1)
def test_expired_frame_cannot_replay_after_closed_cache_rollover() -> None:
receiver = FrameReassembler(7, 256, max_age_ms=10)
for frame_id in range(1025):
part = split_frame(frame(frame_id, frame_id * 20, 300), 256)[0]
assert receiver.offer(encode_fragment(part, 256), frame_id * 20) is None
receiver.expire(frame_id * 20 + 10)
assert 0 not in receiver._closed
old_parts = [encode_fragment(part, 256) for part in split_frame(frame(0, 0, 300), 256)]
assert receiver.offer(old_parts[0], 20_501) is None
assert receiver.offer(old_parts[1], 20_502) is None
assert receiver.rejected_fragments >= 2
def test_two_new_frames_can_start_out_of_order() -> None:
receiver = FrameReassembler(7, 256)
newer_parts = [encode_fragment(item, 256) for item in split_frame(frame(11, 10, 300), 256)]
older = encode_fragment(split_frame(frame(10, 0, 25), 256)[0], 256)
assert receiver.offer(newer_parts[0], 20) is None
assert receiver.offer(older, 21).frame_id == 10
assert receiver.offer(newer_parts[1], 22).frame_id == 11
def test_radio_formula_and_margin() -> None:
current = estimate("BPSK", 500_000, 256, 5)
assert current.coded_body_bits == 4300
assert current.airtime_ms == pytest.approx(11.532)
assert current.video_bytes_per_packet == 239
assert current.useful_video_bps == pytest.approx(115_654.488)
future = estimate("QPSK", 500_000, 1024, 0)
assert not future.within_current_packet_limit
assert not future.implemented_in_current_phy
assert not margin_fits(future.useful_video_bps, 376_000, 0.2)