VIDEO-005: add full offline QPSK IQ video receiver
This commit is contained in:
60
protocol/video005_iq_waveform.py
Normal file
60
protocol/video005_iq_waveform.py
Normal file
@@ -0,0 +1,60 @@
|
||||
"""Формирование комплексного IQ для программного видеотракта VIDEO-005."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from functools import lru_cache
|
||||
from typing import Sequence
|
||||
|
||||
import numpy as np
|
||||
from scipy.signal import upfirdn
|
||||
|
||||
from protocol.bpsk_radio import root_raised_cosine_taps
|
||||
from protocol.video003_modem import (
|
||||
RRC_ROLLOFF,
|
||||
RRC_SPAN_SYMBOLS,
|
||||
SAMPLE_RATE_HZ,
|
||||
OfflineFrame,
|
||||
samples_per_symbol,
|
||||
)
|
||||
|
||||
|
||||
SYMBOL_RATE = 500_000
|
||||
NOMINAL_CARRIER_HZ = 125_000.0
|
||||
GUARD_SAMPLES = 64
|
||||
|
||||
|
||||
@lru_cache(maxsize=2)
|
||||
def rrc_taps(symbol_rate: int = SYMBOL_RATE) -> np.ndarray:
|
||||
"""Возвращает неизменяемые коэффициенты уже применяемого RRC-фильтра."""
|
||||
taps = root_raised_cosine_taps(
|
||||
RRC_ROLLOFF, samples_per_symbol(symbol_rate), RRC_SPAN_SYMBOLS
|
||||
)
|
||||
taps.flags.writeable = False
|
||||
return taps
|
||||
|
||||
|
||||
def shape_packet(
|
||||
frame: OfflineFrame,
|
||||
symbol_rate: int = SYMBOL_RATE,
|
||||
carrier_hz: float = NOMINAL_CARRIER_HZ,
|
||||
) -> np.ndarray:
|
||||
"""Формирует один IQ-пакет с защитными паузами, без канальных нарушений."""
|
||||
if not np.isfinite(carrier_hz) or abs(carrier_hz) >= SAMPLE_RATE_HZ / 2:
|
||||
raise ValueError("несущая должна лежать внутри полосы дискретизации")
|
||||
if frame.modulation != "QPSK" or frame.rate != "2/3":
|
||||
raise ValueError("VIDEO-005 использует QPSK и внутренний FEC 2/3")
|
||||
shaped = upfirdn(rrc_taps(symbol_rate), frame.symbols, up=samples_per_symbol(symbol_rate))
|
||||
envelope = np.pad(shaped, (GUARD_SAMPLES, GUARD_SAMPLES))
|
||||
phase = 2 * np.pi * carrier_hz * np.arange(envelope.size) / SAMPLE_RATE_HZ
|
||||
return (envelope * np.exp(1j * phase)).astype(np.complex64)
|
||||
|
||||
|
||||
def shape_stream(
|
||||
frames: Sequence[OfflineFrame],
|
||||
symbol_rate: int = SYMBOL_RATE,
|
||||
carrier_hz: float = NOMINAL_CARRIER_HZ,
|
||||
) -> np.ndarray:
|
||||
"""Соединяет IQ-пакеты; приёмнику их границы не передаются."""
|
||||
if not frames:
|
||||
raise ValueError("пустой поток")
|
||||
return np.concatenate(tuple(shape_packet(frame, symbol_rate, carrier_hz) for frame in frames))
|
||||
Reference in New Issue
Block a user