Complete VIDEO-001 through VIDEO-004 software video studies
This commit is contained in:
117
tools/video003_transport.py
Normal file
117
tools/video003_transport.py
Normal 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()
|
||||
Reference in New Issue
Block a user