Complete VIDEO-001 through VIDEO-004 software video studies
This commit is contained in:
84
tools/video002_radio_budget.py
Normal file
84
tools/video002_radio_budget.py
Normal file
@@ -0,0 +1,84 @@
|
||||
"""Расчётная длительность пакета VIDEO-002; PHY и радиоприёмник не меняются."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import asdict, dataclass
|
||||
import math
|
||||
|
||||
from protocol.video_stream_transport import HEADER_SIZE
|
||||
|
||||
|
||||
PROTOCOL_OVERHEAD_BYTES = 12 # Заголовок protocol.packet и CRC-32.
|
||||
PREAMBLE_SYMBOLS = 1280
|
||||
PHY_HEADER_SYMBOLS = 144
|
||||
TRANSIENT_SYMBOLS = 42 # Два защитных интервала и хвост RRC.
|
||||
FEC_TAIL_BITS = 6
|
||||
RRC_ROLLOFF = 0.35
|
||||
CURRENT_MAX_PACKET_BYTES = 1024
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class RadioEstimate:
|
||||
modulation: str
|
||||
symbols_per_second: int
|
||||
phy_payload_bytes: int
|
||||
gap_ms: float
|
||||
video_bytes_per_packet: int
|
||||
packet_bytes_before_fec: int
|
||||
coded_body_bits: int
|
||||
airtime_ms: float
|
||||
useful_video_bps: float
|
||||
overhead_fraction: float
|
||||
raw_bps: int
|
||||
occupied_bandwidth_hz: float
|
||||
symbol_duration_us: float
|
||||
within_current_packet_limit: bool
|
||||
implemented_in_current_phy: bool
|
||||
|
||||
def as_dict(self) -> dict:
|
||||
return asdict(self)
|
||||
|
||||
|
||||
def estimate(modulation: str, symbols_per_second: int,
|
||||
phy_payload_bytes: int, gap_ms: float) -> RadioEstimate:
|
||||
"""QPSK: преамбула/PHY-заголовок BPSK, тело FEC по два бита на символ."""
|
||||
if modulation not in ("BPSK", "QPSK") or symbols_per_second <= 0:
|
||||
raise ValueError("Неизвестная модуляция или символьная скорость")
|
||||
if phy_payload_bytes <= HEADER_SIZE or gap_ms < 0:
|
||||
raise ValueError("Недопустимые нагрузка или пауза")
|
||||
packet_bytes = phy_payload_bytes + PROTOCOL_OVERHEAD_BYTES
|
||||
coded_bits = 2 * (packet_bytes * 8 + FEC_TAIL_BITS)
|
||||
bits_per_symbol = 1 if modulation == "BPSK" else 2
|
||||
body_symbols = math.ceil(coded_bits / bits_per_symbol)
|
||||
symbols = PREAMBLE_SYMBOLS + PHY_HEADER_SYMBOLS + body_symbols + TRANSIENT_SYMBOLS
|
||||
airtime_ms = 1000 * symbols / symbols_per_second
|
||||
video_bytes = phy_payload_bytes - HEADER_SIZE
|
||||
useful_bps = video_bytes * 8000 / (airtime_ms + gap_ms)
|
||||
# Вся ёмкость интервала, кроме байтов изображения, относится к накладным расходам.
|
||||
overhead = 1 - useful_bps / (symbols_per_second * bits_per_symbol)
|
||||
return RadioEstimate(
|
||||
modulation, symbols_per_second, phy_payload_bytes, gap_ms,
|
||||
video_bytes, packet_bytes, coded_bits, airtime_ms, useful_bps,
|
||||
overhead, symbols_per_second * bits_per_symbol,
|
||||
symbols_per_second * (1 + RRC_ROLLOFF),
|
||||
1_000_000 / symbols_per_second,
|
||||
packet_bytes <= CURRENT_MAX_PACKET_BYTES,
|
||||
modulation == "BPSK" and symbols_per_second == 500_000
|
||||
and phy_payload_bytes == 256 and gap_ms == 5,
|
||||
)
|
||||
|
||||
|
||||
def matrix() -> list[RadioEstimate]:
|
||||
return [estimate(modulation, speed, payload, gap)
|
||||
for modulation, speed in (("BPSK", 500_000),
|
||||
("QPSK", 250_000),
|
||||
("QPSK", 500_000))
|
||||
for payload in (256, 512, 1024)
|
||||
for gap in (5, 2, 1, 0)]
|
||||
|
||||
|
||||
def margin_fits(rate_bps: float, peak_bps: float, margin: float) -> bool:
|
||||
"""Запас 20%: предельная скорость >= пик * 1.2."""
|
||||
if not 0 <= margin <= 1:
|
||||
raise ValueError("Запас вне диапазона 0–1")
|
||||
return rate_bps >= peak_bps * (1 + margin)
|
||||
Reference in New Issue
Block a user