use crate::core::Block;
use crate::demodulate::ofdm::{
EqualizerMethod, OfdmDemod, OfdmEqualizer, OfdmRxFrame, OfdmSoftDemod,
};
use crate::demodulate::ofdm_probe::{OfdmRxProbe, ProbeMeta};
use crate::dsp::Rotator;
use crate::fec::{
BlockInterleaver, ConvDeinterleaver, ConvInterleaver, CrcKind, DecodeRule, FrameMetadata,
FramePacket, InnerFec, InterleaverKind, OuterFec, RxError, ScramblerKind, ScramblerPos,
viterbi_decode_soft_with,
};
use crate::modulate::ofdm::{ConstellationOrder, OfdmConfig};
use crate::modulate::ofdm_frame::{
BCH_INFO_BITS, BlockPlan, CodecCache, HEADER_CONSTELLATION, HEADER_FIELD_BYTES, HEADER_LDPC,
McsTable, bits_to_bytes, block_plan, build_scrambler, bytes_to_bits, check_and_strip_crc,
encode_chain_stages, inner_encode, interleave_bits, scramble_bits, scramble_bytes,
symbol_config, symbols_for_coded_bits,
};
use crate::multicarrier::{CarrierGrid, GridExtract, SymbolFft};
use crate::sync::{OfdmPreamble, earliest_accepted, ofdm_sync};
use num_complex::Complex32 as C32;
use std::sync::Arc;
fn soft_demap(
base: &OfdmConfig,
constellation: ConstellationOrder,
iq: &[C32],
n_symbols: usize,
equalizer: Option<&mut OfdmEqualizer>,
mut symbol_sink: Option<&mut Vec<C32>>,
) -> Option<Vec<f32>> {
let cfg = symbol_config(base, constellation);
let sps = cfg.samples_per_ofdm_symbol();
if iq.len() < n_symbols * sps {
return None;
}
let n_data = cfg.carrier_plan.data_carriers().len();
let bps = cfg.bits_per_ofdm_symbol();
let mut soft = OfdmSoftDemod::new(&cfg);
let mut symbols = vec![C32::default(); n_data];
let mut llrs = vec![0.0f32; n_symbols * bps];
match equalizer {
None => {
let mut demod = OfdmDemod::new(&cfg);
let mut in_off = 0;
let mut out_off = 0;
for _ in 0..n_symbols {
let dw = demod.process(&iq[in_off..], &mut symbols);
if dw.out_written != n_data {
return None;
}
if let Some(sink) = symbol_sink.as_deref_mut() {
sink.extend_from_slice(&symbols);
}
let sw = soft.process(&symbols, &mut llrs[out_off..out_off + bps]);
if sw.out_written != bps {
return None;
}
in_off += sps;
out_off += bps;
}
}
Some(eq) => {
let n_fft = cfg.carrier_plan.n_fft();
let cp_len = cfg.carrier_plan.cp_len();
let grid = CarrierGrid::from_plan(&cfg.carrier_plan);
let mut symbol_fft =
SymbolFft::new(n_fft, cp_len).with_window_backoff(base.rx_window_backoff);
let mut grid_extract = GridExtract::new(grid);
let mut equalized = vec![C32::default(); n_fft];
let mut all = vec![C32::default(); n_symbols * n_data];
let mut in_off = 0;
for k in 0..n_symbols {
let freq = symbol_fft.demod_symbol(&iq[in_off..])?;
if eq.process(freq, &mut equalized).out_written != n_fft {
return None;
}
if grid_extract.process(&equalized, &mut symbols).out_written != n_data {
return None;
}
all[k * n_data..(k + 1) * n_data].copy_from_slice(&symbols);
in_off += sps;
}
remove_common_phase_error(&cfg, &mut all, n_symbols);
let mut out_off = 0;
for k in 0..n_symbols {
let block = &all[k * n_data..(k + 1) * n_data];
if let Some(sink) = symbol_sink.as_deref_mut() {
sink.extend_from_slice(block);
}
let sw = soft.process(block, &mut llrs[out_off..out_off + bps]);
if sw.out_written != bps {
return None;
}
out_off += bps;
}
}
}
Some(llrs)
}
const CPE_MIN_SYMBOLS: usize = 4;
fn remove_common_phase_error(cfg: &OfdmConfig, symbols: &mut [C32], n_symbols: usize) {
let n_data = cfg.carrier_plan.data_carriers().len();
if n_symbols < CPE_MIN_SYMBOLS || n_data == 0 || symbols.len() < n_symbols * n_data {
return;
}
const ALPHA: f32 = 0.5;
const BETA: f32 = 0.05;
let mut soft = OfdmSoftDemod::new(cfg);
let mut mapper = crate::modulate::ofdm::ideal_symbol_mapper(cfg.constellation);
let bps = cfg.bits_per_ofdm_symbol();
let mut llrs = vec![0.0f32; bps];
let mut bits = vec![0u8; bps];
let mut ideal = vec![C32::default(); n_data];
let mut rotated = vec![C32::default(); n_data];
let mut measured = vec![0.0f32; n_symbols];
let (mut phase, mut freq) = (0.0f32, 0.0f32);
for k in 0..n_symbols {
let block = &symbols[k * n_data..(k + 1) * n_data];
let (sin, cos) = (-phase).sin_cos();
let p = C32::new(cos, sin);
for (dst, src) in rotated.iter_mut().zip(block) {
*dst = src * p;
}
if soft.process(&rotated, &mut llrs).out_written != bps {
return;
}
for (b, l) in bits.iter_mut().zip(llrs.iter()) {
*b = u8::from(*l <= 0.0);
}
if mapper.process(&bits, &mut ideal).out_written != n_data {
return;
}
let mut acc = C32::default();
for (y, r) in rotated.iter().zip(ideal.iter()) {
acc += y * r.conj();
}
let err = if acc.re == 0.0 && acc.im == 0.0 {
0.0
} else {
acc.im.atan2(acc.re)
};
measured[k] = phase + err;
phase += freq + ALPHA * err;
freq += BETA * err;
}
let n = n_symbols as f32;
let sum_k = n * (n - 1.0) / 2.0;
let sum_k2 = (n - 1.0) * n * (2.0 * n - 1.0) / 6.0;
let sum_y: f32 = measured.iter().sum();
let sum_ky: f32 = measured.iter().enumerate().map(|(k, y)| k as f32 * y).sum();
let denom = n * sum_k2 - sum_k * sum_k;
if denom.abs() <= f32::EPSILON {
return;
}
let slope = (n * sum_ky - sum_k * sum_y) / denom;
let intercept = (sum_y - slope * sum_k) / n;
for k in 0..n_symbols {
let (sin, cos) = (-(intercept + slope * k as f32)).sin_cos();
let p = C32::new(cos, sin);
for c in &mut symbols[k * n_data..(k + 1) * n_data] {
*c *= p;
}
}
}
fn soft_demap_scattered(
base: &OfdmConfig,
constellation: ConstellationOrder,
iq: &[C32],
n_symbols: usize,
extractor: &mut crate::waveform::dvb_t::ScatteredPilotExtractor,
) -> Option<Vec<f32>> {
let cfg = symbol_config(base, constellation);
let sps = cfg.samples_per_ofdm_symbol();
if iq.len() < n_symbols * sps {
return None;
}
let n_fft = cfg.carrier_plan.n_fft();
let cp_len = cfg.carrier_plan.cp_len();
let n_data = extractor.num_data_carriers();
let vbits = constellation.bits_per_symbol();
let bps = n_data * vbits;
let dvb_t_llr = crate::waveform::dvb_t::is_dvb_t_constellation(constellation);
let mut soft = OfdmSoftDemod::new(&cfg);
let mut eq = OfdmEqualizer::new(&cfg, EqualizerMethod::PerSymbolPilotInterp);
let mut symbol_fft = SymbolFft::new(n_fft, cp_len).with_window_backoff(cfg.rx_window_backoff);
let mut equalized = vec![C32::default(); n_fft];
let mut symbols = vec![C32::default(); n_data];
let mut llrs = vec![0.0f32; n_symbols * bps];
let mut in_off = 0;
let mut out_off = 0;
for _ in 0..n_symbols {
let freq = symbol_fft.demod_symbol(&iq[in_off..])?;
let pilots = extractor.current_pilot_bins().to_vec();
let data_bins: Vec<usize> = extractor.data_bins().to_vec();
eq.set_pilot_bins(&pilots, &data_bins);
if eq.process(freq, &mut equalized).out_written != n_fft {
return None;
}
extractor.extract_symbol(&equalized, &mut symbols);
let sym_llrs = &mut llrs[out_off..out_off + bps];
if dvb_t_llr {
for (c, &sym) in symbols.iter().enumerate() {
let l = crate::waveform::dvb_t::dvb_t_soft_llr(sym, vbits).expect("DVB-T order");
sym_llrs[c * vbits..(c + 1) * vbits].copy_from_slice(&l);
}
} else {
let sw = soft.process(&symbols, sym_llrs);
if sw.out_written != bps {
return None;
}
}
in_off += sps;
out_off += bps;
}
Some(llrs)
}
fn deinterleave_llrs(il: InterleaverKind, llrs: &[f32]) -> Vec<f32> {
match il {
InterleaverKind::None => llrs.to_vec(),
InterleaverKind::Block { rows, cols } => {
let block = rows * cols;
let bi = BlockInterleaver::new(rows, cols);
let mut out = Vec::with_capacity(llrs.len());
let mut restored = vec![0.0f32; block]; for chunk in llrs.chunks(block) {
if chunk.len() < block {
out.extend_from_slice(chunk);
continue;
}
bi.deinterleave(chunk, &mut restored);
out.extend_from_slice(&restored);
}
out
}
InterleaverKind::Convolutional { .. } => {
debug_assert!(false, "Convolutional interleaver is byte-domain only");
llrs.to_vec()
}
}
}
fn deinterleave_bits(il: InterleaverKind, bits: &[u8]) -> Vec<u8> {
match il {
InterleaverKind::None => bits.to_vec(),
InterleaverKind::Block { rows, cols } => {
let block = rows * cols;
let bi = BlockInterleaver::new(rows, cols);
let mut out = Vec::with_capacity(bits.len());
let mut restored = vec![0u8; block]; for chunk in bits.chunks(block) {
if chunk.len() < block {
out.extend_from_slice(chunk);
continue;
}
bi.deinterleave(chunk, &mut restored);
out.extend_from_slice(&restored);
}
out
}
InterleaverKind::Convolutional { branches, depth } => {
let d = ConvInterleaver::new(branches, depth).roundtrip_delay();
let total = bits.len() / 8;
if total <= d {
return Vec::new();
}
let n_padded = total - d;
let bytes = bits_to_bytes(&bits[..total * 8]);
let mut di = ConvDeinterleaver::new(branches, depth);
let deint = di.feed(&bytes);
bytes_to_bits(&deint[d..d + n_padded])
}
}
}
fn inner_decode(
inner: InnerFec,
coded_llrs: &[f32],
info_len: usize,
cache: &CodecCache,
ldpc_rule: DecodeRule,
) -> (Vec<u8>, bool) {
match inner {
InnerFec::None => {
(
coded_llrs.iter().map(|&l| u8::from(l <= 0.0)).collect(),
true,
)
}
InnerFec::Ldpc(code) => {
let ldpc = cache.ldpc(code);
let n = ldpc.n();
let mut info = Vec::new();
let mut all_ok = true;
for chunk in coded_llrs.chunks(n) {
if chunk.len() < n {
all_ok = false;
break;
}
let (msg, unsat) = ldpc.decode_soft_with(chunk, 50, ldpc_rule);
if unsat != 0 {
all_ok = false;
}
info.extend_from_slice(&msg);
}
(info, all_ok)
}
InnerFec::Convolutional { rate, code } => {
let info = viterbi_decode_soft_with(code, coded_llrs, info_len, rate);
(info, true)
}
}
}
fn outer_decode(outer: OuterFec, coded_bits: &[u8], cache: &CodecCache) -> (Vec<u8>, bool) {
match outer {
OuterFec::None => (coded_bits.to_vec(), true),
OuterFec::Bch { t } => {
let code = cache.bch(t, BCH_INFO_BITS);
let n = code.n();
let mut msg = Vec::new();
let mut all_ok = true;
for chunk in coded_bits.chunks(n) {
if chunk.len() < n {
all_ok = false;
break;
}
match code.decode(chunk) {
Ok(block) => msg.extend_from_slice(&block),
Err(_) => {
all_ok = false;
msg.extend_from_slice(&chunk[..code.k()]);
}
}
}
(msg, all_ok)
}
OuterFec::ReedSolomon { n, n_parity } => {
let rs = cache.rs(n, n_parity);
let coded_bytes = bits_to_bytes(coded_bits);
let mut msg_bytes = Vec::new();
let mut all_ok = true;
for chunk in coded_bytes.chunks(n) {
if chunk.len() < n {
all_ok = false;
break;
}
match rs.decode(chunk) {
Ok(block) => msg_bytes.extend_from_slice(&block),
Err(_) => {
all_ok = false;
msg_bytes.extend_from_slice(&chunk[..rs.k()]);
}
}
}
(bytes_to_bits(&msg_bytes), all_ok)
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ChainOutcome {
pub bytes: Vec<u8>,
pub inner_ok: bool,
pub outer_ok: bool,
pub crc_ok: bool,
pub crc_present: bool,
pub outer_present: bool,
pub inner_out_bits: Vec<u8>,
}
impl ChainOutcome {
pub fn is_valid(&self) -> bool {
if self.crc_present {
self.crc_ok
} else if self.outer_present {
self.outer_ok
} else {
self.inner_ok
}
}
}
#[allow(clippy::too_many_arguments)]
pub fn decode_chain(
coded_llrs: &[f32],
plan: &BlockPlan,
crc: CrcKind,
outer: OuterFec,
inner: InnerFec,
outer_il: InterleaverKind,
inner_il: InterleaverKind,
scrambler: ScramblerKind,
scrambler_pos: ScramblerPos,
per_frame_seed: u32,
cache: &CodecCache,
ldpc_rule: DecodeRule,
) -> Result<ChainOutcome, RxError> {
let mut llrs = coded_llrs.to_vec();
llrs.truncate(plan.coded_bits);
let sc = build_scrambler(scrambler, per_frame_seed);
if scrambler_pos == ScramblerPos::AfterInnerFec
&& let Some(ref s) = sc
{
apply_pn_to_llrs(s, &mut llrs);
}
let inner_de = deinterleave_llrs(inner_il, &llrs);
let inner_de = &inner_de[..plan.inner_coded_bits.min(inner_de.len())];
let (outer_il_bits, inner_ok) =
inner_decode(inner, inner_de, plan.outer_il_bits, cache, ldpc_rule);
let trimmed = &outer_il_bits[..plan.outer_il_bits.min(outer_il_bits.len())];
let outer_de = deinterleave_bits(outer_il, trimmed);
let outer_de = &outer_de[..plan.outer_coded_bits.min(outer_de.len())];
let (mut framed_bits, outer_ok) = outer_decode(outer, outer_de, cache);
framed_bits.truncate(plan.framed_bytes * 8);
if framed_bits.len() < plan.framed_bytes * 8 {
return Err(RxError::MalformedHeader);
}
let mut framed = bits_to_bytes(&framed_bits);
if scrambler_pos == ScramblerPos::BeforeOuterFec {
scramble_bytes(scrambler, per_frame_seed, &mut framed);
}
let (bytes, crc_ok) = check_and_strip_crc(crc, &framed).ok_or(RxError::MalformedHeader)?;
Ok(ChainOutcome {
bytes,
inner_ok,
outer_ok,
crc_ok,
crc_present: crc != CrcKind::None,
outer_present: outer != OuterFec::None,
inner_out_bits: outer_il_bits,
})
}
fn reencode_inner_output(
cfg: &OfdmConfig,
inner: InnerFec,
inner_out: &[u8],
coded_bits: usize,
per_frame_seed: u32,
cache: &CodecCache,
out: &mut Vec<u8>,
) {
let inner_bits = inner_encode(inner, inner_out, cache);
out.clear();
match cfg.inner_interleaver {
InterleaverKind::None => out.extend_from_slice(&inner_bits),
il => out.extend_from_slice(&interleave_bits(il, &inner_bits)),
}
if cfg.scrambler_pos == ScramblerPos::AfterInnerFec
&& let Some(ref s) = build_scrambler(cfg.scrambler, per_frame_seed)
{
scramble_bits(s, out);
}
out.truncate(coded_bits);
}
fn apply_pn_to_llrs(s: &crate::fec::PnScrambler, llrs: &mut [f32]) {
let n_bytes = llrs.len().div_ceil(8);
let mut pn = vec![0u8; n_bytes];
s.scramble(&mut pn);
let pn_bits = bytes_to_bits(&pn);
for (l, &p) in llrs.iter_mut().zip(pn_bits.iter()) {
if p != 0 {
*l = -*l;
}
}
}
enum BodyError {
Incomplete,
Failed(RxError),
}
fn bit_error_rate(a: &[u8], b: &[u8]) -> Option<f32> {
let n = a.len().min(b.len());
if n == 0 {
return None;
}
let errs = a[..n]
.iter()
.zip(b[..n].iter())
.filter(|(x, y)| x != y)
.count();
Some(errs as f32 / n as f32)
}
#[derive(Debug, Clone, Default)]
struct FrameScratch {
symbols: Vec<C32>,
hard: Vec<u8>,
}
struct DecodedBody {
packet: FramePacket,
consumed: usize,
evm_db: Option<f32>,
inner_ok: bool,
outer_ok: bool,
channel_ber: Option<f32>,
inner_ber: Option<f32>,
}
#[allow(clippy::too_many_arguments)]
fn decode_frame_body(
cfg: &OfdmConfig,
mcs_table: &McsTable,
iq: &[C32],
channel_estimate: Option<&[C32]>,
cache: &CodecCache,
measure_ber: bool,
scratch: &mut FrameScratch,
probe: Option<&mut OfdmRxProbe>,
) -> Result<DecodedBody, BodyError> {
let mut probe = probe.filter(|_| !cfg.dvb_t_scattered);
let mut cursor = 0usize;
let make_eq = |constellation: ConstellationOrder| -> Option<OfdmEqualizer> {
channel_estimate.map(|est| {
let symcfg = symbol_config(cfg, constellation);
let mut eq = OfdmEqualizer::new(&symcfg, EqualizerMethod::TrainingSymbolHold);
eq.estimate_from_training_symbol(est);
eq
})
};
let mut scattered = cfg.dvb_t_scattered.then(|| {
let guard =
crate::waveform::dvb_t::GuardInterval::from_cp_len_2k(cfg.carrier_plan.cp_len())
.expect("DVB-T scattered link requires a 2K guard interval");
crate::waveform::dvb_t::ScatteredPilotExtractor::new(guard)
});
let mut demap = |constellation: ConstellationOrder,
iq: &[C32],
off: usize,
n_sym: usize,
eq: Option<&mut OfdmEqualizer>,
sink: Option<&mut Vec<C32>>|
-> Option<Vec<f32>> {
match scattered.as_mut() {
Some(x) => soft_demap_scattered(cfg, constellation, &iq[off..], n_sym, x),
None => soft_demap(cfg, constellation, &iq[off..], n_sym, eq, sink),
}
};
let (metadata, per_frame_seed, payload_len) = if cfg.header_format.has_header_block() {
let hplan = block_plan(
HEADER_FIELD_BYTES,
cfg.header_crc,
OuterFec::None,
InnerFec::Ldpc(HEADER_LDPC),
InterleaverKind::None,
InterleaverKind::None,
cache,
);
let n_sym = symbols_for_coded_bits(cfg, HEADER_CONSTELLATION, hplan.coded_bits);
let mut eq = make_eq(HEADER_CONSTELLATION);
let llrs = demap(HEADER_CONSTELLATION, iq, cursor, n_sym, eq.as_mut(), None)
.ok_or(BodyError::Incomplete)?;
let header = decode_chain(
&llrs,
&hplan,
cfg.header_crc,
OuterFec::None,
InnerFec::Ldpc(HEADER_LDPC),
InterleaverKind::None,
InterleaverKind::None,
ScramblerKind::None,
ScramblerPos::BeforeOuterFec,
0,
cache,
DecodeRule::SumProduct,
)
.map_err(BodyError::Failed)?;
if !header.is_valid() {
return Err(BodyError::Failed(RxError::HeaderCrcMismatch));
}
let fields = header.bytes;
if fields.len() < HEADER_FIELD_BYTES {
return Err(BodyError::Failed(RxError::MalformedHeader));
}
let mcs_index = fields[0];
let payload_len = u32::from_be_bytes([fields[1], fields[2], fields[3], fields[4]]) as usize;
let sequence_num = u32::from_be_bytes([fields[5], fields[6], fields[7], fields[8]]);
let flags = fields[9];
let seed = u32::from_be_bytes([fields[10], fields[11], fields[12], fields[13]]);
let sps = symbol_config(cfg, HEADER_CONSTELLATION).samples_per_ofdm_symbol();
cursor += n_sym * sps;
(
FrameMetadata {
sequence_num,
mcs_index,
flags,
},
seed,
payload_len,
)
} else {
return Err(BodyError::Failed(RxError::MalformedHeader));
};
let mcs = mcs_table
.get(metadata.mcs_index)
.ok_or(BodyError::Failed(RxError::MalformedHeader))?;
let pplan = block_plan(
payload_len,
cfg.payload_crc,
mcs.outer_fec,
mcs.inner_fec,
cfg.outer_interleaver,
cfg.inner_interleaver,
cache,
);
let n_sym = symbols_for_coded_bits(cfg, mcs.constellation, pplan.coded_bits);
let mut eq = make_eq(mcs.constellation);
let sym_start = match probe.as_deref_mut() {
Some(p) => p.symbols.len(),
None => {
scratch.symbols.clear();
0
}
};
let llrs = {
let sink: &mut Vec<C32> = match probe.as_deref_mut() {
Some(p) => &mut p.symbols,
None => &mut scratch.symbols,
};
demap(
mcs.constellation,
iq,
cursor,
n_sym,
eq.as_mut(),
Some(sink),
)
.ok_or(BodyError::Incomplete)?
};
scratch.hard.clear();
scratch
.hard
.extend(llrs.iter().map(|&l| u8::from(l <= 0.0)));
let evm_db = {
let symbols: &[C32] = match probe.as_deref() {
Some(p) => &p.symbols[sym_start..],
None => &scratch.symbols,
};
crate::demodulate::ofdm::evm_db(
&symbol_config(cfg, mcs.constellation),
symbols,
&scratch.hard,
n_sym,
)
};
let payload_outcome = match decode_chain(
&llrs,
&pplan,
cfg.payload_crc,
mcs.outer_fec,
mcs.inner_fec,
cfg.outer_interleaver,
cfg.inner_interleaver,
cfg.scrambler,
cfg.scrambler_pos,
per_frame_seed,
cache,
cfg.ldpc_decode_rule,
) {
Ok(outcome) if outcome.is_valid() => outcome,
rest => {
if let Some(p) = probe.as_deref_mut() {
p.push_undecoded(sym_start, mcs.constellation, Some(metadata.sequence_num));
}
return Err(BodyError::Failed(match rest {
Err(e) => e,
Ok(_) => RxError::CrcMismatch,
}));
}
};
let (inner_ok, outer_ok) = (payload_outcome.inner_ok, payload_outcome.outer_ok);
let stages = (measure_ber || probe.is_some()).then(|| {
encode_chain_stages(
&payload_outcome.bytes,
cfg.payload_crc,
mcs.outer_fec,
mcs.inner_fec,
cfg.outer_interleaver,
cfg.inner_interleaver,
cfg.scrambler,
cfg.scrambler_pos,
per_frame_seed,
cache,
)
});
let coded_bits = pplan.coded_bits.min(scratch.hard.len());
let (channel_ber, inner_ber) = match stages.as_ref().filter(|_| measure_ber) {
Some(s) => (
bit_error_rate(&scratch.hard[..coded_bits], &s.coded),
bit_error_rate(&payload_outcome.inner_out_bits, &s.outer_il_bits),
),
None => (None, None),
};
if let (Some(p), Some(s)) = (&mut probe, stages.as_ref()) {
reencode_inner_output(
cfg,
mcs.inner_fec,
&payload_outcome.inner_out_bits,
pplan.coded_bits,
per_frame_seed,
cache,
&mut p.estimate,
);
debug_assert_eq!(
p.estimate.len(),
pplan.coded_bits,
"the re-encoded decoder estimate must span the whole coded block"
);
let (codeword_bits, codeword_info_bits) = match mcs.inner_fec {
InnerFec::Ldpc(code) => (code.n(), code.k()),
InnerFec::None | InnerFec::Convolutional { .. } => (0, 0),
};
p.push_decoded(
sym_start,
ProbeMeta {
sequence_num: Some(metadata.sequence_num),
constellation: mcs.constellation,
codeword_bits,
codeword_info_bits,
},
&s.coded[..coded_bits.min(s.coded.len())],
&scratch.hard[..coded_bits],
);
}
let bytes = payload_outcome.bytes;
let payload_sps = symbol_config(cfg, mcs.constellation).samples_per_ofdm_symbol();
cursor += n_sym * payload_sps;
let payload = bytes
.get(..payload_len)
.map(|s| s.to_vec())
.unwrap_or(bytes);
Ok(DecodedBody {
packet: FramePacket { metadata, payload },
consumed: cursor,
evm_db,
inner_ok,
outer_ok,
channel_ber,
inner_ber,
})
}
#[derive(Debug, Clone)]
pub struct OfdmFrameDemod {
cfg: OfdmConfig,
mcs_table: McsTable,
cache: Arc<CodecCache>,
}
impl OfdmFrameDemod {
pub fn new(cfg: OfdmConfig, mcs_table: McsTable) -> Self {
Self::with_cache(cfg, mcs_table, Arc::new(CodecCache::new()))
}
pub fn with_cache(cfg: OfdmConfig, mcs_table: McsTable, cache: Arc<CodecCache>) -> Self {
crate::modulate::ofdm_frame::assert_baseband(&cfg);
Self {
cfg,
mcs_table,
cache,
}
}
pub fn config(&self) -> &OfdmConfig {
&self.cfg
}
pub fn decode(&self, iq: &[C32]) -> Result<FramePacket, RxError> {
let mut scratch = FrameScratch::default();
decode_frame_body(
&self.cfg,
&self.mcs_table,
iq,
None,
&self.cache,
false,
&mut scratch,
None,
)
.map(|body| body.packet)
.map_err(|e| match e {
BodyError::Incomplete => RxError::MalformedHeader,
BodyError::Failed(err) => err,
})
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct RxFrame {
pub packet: FramePacket,
pub diagnostics: OfdmRxFrame,
}
pub struct OfdmFrameStreamDemod {
cfg: OfdmConfig,
mcs_table: McsTable,
preamble: OfdmPreamble,
fs: f32,
buf: Vec<C32>,
score_threshold: f32,
want_channel_estimate: bool,
want_error_rates: bool,
cache: Arc<CodecCache>,
scratch: FrameScratch,
}
impl OfdmFrameStreamDemod {
pub fn new(cfg: OfdmConfig, mcs_table: McsTable, preamble: OfdmPreamble) -> Self {
Self::with_cache(cfg, mcs_table, preamble, Arc::new(CodecCache::new()))
}
pub fn with_cache(
cfg: OfdmConfig,
mcs_table: McsTable,
preamble: OfdmPreamble,
cache: Arc<CodecCache>,
) -> Self {
crate::modulate::ofdm_frame::assert_baseband(&cfg);
let fs = cfg.fs;
Self {
cfg,
mcs_table,
preamble,
fs,
buf: Vec::new(),
score_threshold: 0.5,
want_channel_estimate: false,
want_error_rates: false,
cache,
scratch: FrameScratch::default(),
}
}
pub fn with_score_threshold(mut self, t: f32) -> Self {
self.score_threshold = t;
self
}
pub fn with_channel_estimate(mut self, on: bool) -> Self {
self.want_channel_estimate = on;
self
}
pub fn with_error_rates(mut self, on: bool) -> Self {
self.want_error_rates = on;
self
}
pub fn len(&self) -> usize {
self.buf.len()
}
pub fn is_empty(&self) -> bool {
self.buf.is_empty()
}
pub fn view_buf(&self) -> &[C32] {
&self.buf
}
pub fn clear(&mut self) {
self.buf.clear();
}
pub fn feed(&mut self, iq: &[C32]) -> Vec<Result<RxFrame, RxError>> {
self.buf.extend_from_slice(iq);
self.drain(None)
}
pub fn flush(&mut self) -> Vec<Result<RxFrame, RxError>> {
self.drain(None)
}
pub fn feed_probed(
&mut self,
iq: &[C32],
probe: &mut OfdmRxProbe,
) -> Vec<Result<RxFrame, RxError>> {
probe.clear();
self.buf.extend_from_slice(iq);
self.drain(Some(probe))
}
pub fn flush_probed(&mut self, probe: &mut OfdmRxProbe) -> Vec<Result<RxFrame, RxError>> {
probe.clear();
self.drain(Some(probe))
}
fn drain(&mut self, mut probe: Option<&mut OfdmRxProbe>) -> Vec<Result<RxFrame, RxError>> {
let mut out = Vec::new();
while let FrameStep::Decoded(result, consume_to) = self.try_one_frame(probe.as_deref_mut())
{
self.buf.drain(..consume_to);
out.push(result);
}
out
}
fn try_one_frame(&mut self, mut probe: Option<&mut OfdmRxProbe>) -> FrameStep {
let n_fft = self.cfg.carrier_plan.n_fft();
let cp_len = self.cfg.carrier_plan.cp_len();
let pre_len = self.preamble.total_len();
if self.buf.len() < pre_len + (n_fft + cp_len) {
return FrameStep::NeedMore;
}
let sync = ofdm_sync(&self.buf, self.fs, &self.preamble, 0, self.buf.len());
let Some(best) = earliest_accepted(sync, self.score_threshold, pre_len) else {
return FrameStep::NeedMore;
};
let subcarrier_spacing = self.fs / n_fft as f32;
let total_cfo = best.cfo_hz + best.integer_cfo_bins as f32 * subcarrier_spacing;
let region = &self.buf[best.start_sample..];
let mut corrected = vec![C32::default(); region.len()];
let mut rot = Rotator::new(-total_cfo, self.fs);
rot.rotate_block(region, &mut corrected);
let channel_estimate = self.estimate_channel(&corrected);
if corrected.len() < pre_len {
return FrameStep::NeedMore;
}
let body = &corrected[pre_len..];
let mark = probe.as_deref().map(|p| p.mark());
match decode_frame_body(
&self.cfg,
&self.mcs_table,
body,
channel_estimate.as_deref(),
&self.cache,
self.want_error_rates,
&mut self.scratch,
probe.as_deref_mut(),
) {
Ok(body) => {
let diagnostics = OfdmRxFrame {
bits: Vec::new(),
num_symbols: 0,
evm_db: body.evm_db,
cfo_hz: Some(total_cfo),
timing_offset_samples: Some(best.start_sample as i32),
channel_mse: None,
sync_score: Some(best.score),
channel_estimate: self
.want_channel_estimate
.then(|| channel_estimate.as_deref().map(channel_from_training))
.flatten(),
inner_fec_ok: Some(body.inner_ok),
outer_fec_ok: Some(body.outer_ok),
channel_ber: body.channel_ber,
inner_ber: body.inner_ber,
};
let consume_to = best.start_sample + pre_len + body.consumed;
if consume_to > self.buf.len() {
return FrameStep::NeedMore;
}
FrameStep::Decoded(
Ok(RxFrame {
packet: body.packet,
diagnostics,
}),
consume_to,
)
}
Err(BodyError::Incomplete) => {
if let (Some(p), Some(m)) = (&mut probe, mark) {
p.rollback(m);
}
FrameStep::NeedMore
}
Err(BodyError::Failed(e)) => {
let skip = (best.start_sample + pre_len).min(self.buf.len());
FrameStep::Decoded(Err(e), skip)
}
}
}
fn estimate_channel(&self, corrected: &[C32]) -> Option<Vec<C32>> {
let training = self.preamble.training_symbol?;
let n_fft = training.n_fft;
let cp_len = training.cp_len;
let training_start = self.preamble.num_repeats * self.preamble.repeat_len;
let end = training_start + n_fft + cp_len;
if corrected.len() < end {
return None;
}
let mut symbol_fft =
SymbolFft::new(n_fft, cp_len).with_window_backoff(self.cfg.rx_window_backoff);
let freq = symbol_fft.demod_symbol(&corrected[training_start..end])?;
Some(freq.to_vec())
}
}
fn channel_from_training(received_freq: &[C32]) -> Vec<C32> {
let known = crate::sync::ofdm_sync::training_symbol_freq_pattern(received_freq.len());
received_freq
.iter()
.zip(known.iter())
.map(|(&rx, &k)| rx / k)
.collect()
}
enum FrameStep {
Decoded(Result<RxFrame, RxError>, usize),
NeedMore,
}