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

117
tools/video003_transport.py Normal file
View File

@@ -0,0 +1,117 @@
"""VIDEO-003: реальные H.264 кадры в модели очереди VIDEO-002 и нового PHY."""
from __future__ import annotations
import argparse
import json
from pathlib import Path
from protocol.video_stream_transport import FrameType, VideoFrame
from tools.video002_simulation import load_real_stream, simulate
from tools.video003_codec import BASELINE, SOURCE, encode_case, source_frames
from tools.video003_design import ROOT, estimate
DEFAULT_OUTPUT = ROOT / "data/processed/video003/transport_results.json"
CODEC_RESULTS = ROOT / "data/processed/video003/codec_results.json"
RESULT_160 = ROOT / "data/processed/video001/comparison_traffic_160k.json"
SCENARIOS = tuple(("random", loss) for loss in (0, 1, 2, 5, 10))
BURST_SCENARIOS = tuple(("burst", length) for length in (2, 5, 10))
LINKS = (
("CURRENT_BPSK_256_5MS", "BPSK", 500_000, "1/2", 256, 5.0),
("BPSK_500_R12_1012_0MS", "BPSK", 500_000, "1/2", 1012, 0.0),
("QPSK_250_R34_1012_0MS", "QPSK", 250_000, "3/4", 1012, 0.0),
("QPSK_500_R12_1012_0MS", "QPSK", 500_000, "1/2", 1012, 0.0),
("QPSK_500_R23_1012_0MS", "QPSK", 500_000, "2/3", 1012, 0.0),
("QPSK_500_R34_1012_0MS", "QPSK", 500_000, "3/4", 1012, 0.0),
("QPSK_500_R34_768_0P5MS", "QPSK", 500_000, "3/4", 768, 0.5),
)
def _frames(mode: str, width: int, height: int, fps: int,
window: dict, codec_results: dict, baseline_160: dict) -> tuple[list[VideoFrame], str]:
if mode == "ROBUST":
cases = [case for case in baseline_160["cases"]
if case["window"] == window["name"] and case["resolution"] == [width, height]
and case["fps"] == fps and case["codec"] == "H264_LIBX264"]
if len(cases) != 1:
raise ValueError("нет единственной исходной серии ROBUST")
return load_real_stream(SOURCE, window, width, height, fps, 160_000,
cases[0]["frame_sizes_bytes"]), "CURRENT_VIDEO001"
pixels = source_frames(SOURCE, width, height, fps,
window["start_seconds"], window["duration_seconds"])
frames: list[VideoFrame] = []
for profile, target in (("CURRENT", None), ("SHORT_GOP", 400_000)):
case = [item for item in codec_results["cases"]
if item["mode"] == mode and item["window"] == window["name"]
and item["profile"] == profile and item["target_bps"] == target]
if len(case) != 1:
raise ValueError("неоднозначный кодековый прогон")
result, packets = encode_case(pixels, fps, profile, target)
if (result["frame_sizes_bytes"] != case[0]["frame_sizes_bytes"]
or result["keyframe_positions"] != case[0]["keyframe_positions"]):
raise ValueError("повтор кодирования отличается от сохранённого результата")
keyframes = set(result["keyframe_positions"])
for index, packet in enumerate(packets):
frames.append(VideoFrame(
0x56303033 if profile == "CURRENT" else 0x56303334,
index, FrameType.KEYFRAME if index in keyframes else FrameType.DELTA_FRAME,
round(index * 1000 / fps), packet))
return frames, "CURRENT_AND_SHORT_GOP_400K"
def run() -> dict:
codec = json.loads(CODEC_RESULTS.read_text(encoding="utf-8"))
baseline_320 = json.loads(BASELINE.read_text(encoding="utf-8"))
baseline_160 = json.loads(RESULT_160.read_text(encoding="utf-8"))
if not (codec["source_sha256"] == baseline_320["source_sha256"]
== baseline_160["source_sha256"]):
raise ValueError("исходные ролики не совпадают")
modes = (("ROBUST", 320, 240, 5), ("NORMAL", 640, 360, 10),
("HIGH_QUALITY", 854, 480, 10), ("720P5", 1280, 720, 5))
results = []
for mode, width, height, fps in modes:
for window in baseline_320["windows"]:
combined, label = _frames(mode, width, height, fps, window, codec, baseline_160)
groups = [(label, combined)] if mode == "ROBUST" else [
("CURRENT", [frame for frame in combined if frame.stream_id == 0x56303033]),
("SHORT_GOP_400K", [frame for frame in combined if frame.stream_id == 0x56303334]),
]
for codec_profile, frames in groups:
for link_name, modulation, symbol_rate, fec_rate, payload, gap_ms in LINKS:
budget = estimate(modulation, symbol_rate, fec_rate, payload, gap_ms)
link = (link_name, modulation, symbol_rate, payload, gap_ms)
for scenario in SCENARIOS + BURST_SCENARIOS:
outcome = simulate(frames, fps, link, scenario,
airtime_override_ms=budget.packet_airtime_ms)
outcome.update({
"mode": mode, "codec_profile": codec_profile,
"window": window["name"], "resolution": [width, height],
"fps": fps, "modulation": modulation, "symbol_rate": symbol_rate,
"fec_rate": fec_rate, "phy_payload_bytes": payload,
"gap_ms": gap_ms, "calculated_useful_rate_bps": budget.useful_video_rate_bps,
})
results.append(outcome)
print(f"{mode}/{window['name']}: {len(results)} записей", flush=True)
return {
"source_sha256": codec["source_sha256"],
"random_loss_model": "independent Bernoulli per transmitted packet",
"burst_loss_model": "separate deterministic runs of 2/5/10 packet erasures",
"radio_model": "exact packet airtime from VIDEO-003 design table; full-packet conservative airtime",
"receiver": "VIDEO-002 bounded queue, max age 300 ms, actual H.264 decoder",
"cases": results, "rf_tx": 0,
}
def main() -> None:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--output", type=Path, default=DEFAULT_OUTPUT)
args = parser.parse_args()
result = run()
args.output.parent.mkdir(parents=True, exist_ok=True)
args.output.write_text(json.dumps(result, ensure_ascii=False, indent=2), encoding="utf-8")
print(f"VIDEO-003: {len(result['cases'])} прогонов транспорта; {args.output}")
if __name__ == "__main__":
main()