Files
SDR-Rover/tools/video002_radio_budget.py

85 lines
3.4 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Расчётная длительность пакета 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)