use crate::core::Block;
use crate::demodulate::ofdm::{
EqualizerMethod, OfdmDemod, OfdmEqualizer, OfdmRxFrame, OfdmSoftDemod,
};
use crate::dsp::Rotator;
use crate::fec::{
BlockInterleaver, CrcKind, DecodeRule, FrameMetadata, FramePacket, HeaderFormat, InnerFec,
InterleaverKind, OuterFec, RxError, ScramblerKind, ScramblerPos, viterbi_decode_soft,
};
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,
symbol_config, symbols_for_coded_bits,
};
use crate::multicarrier::{CarrierGrid, CyclicPrefixRemove, FftBlock, GridExtract};
use crate::sync::{OfdmPreamble, 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>,
) -> 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;
}
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 cp_remove = CyclicPrefixRemove::new(n_fft, cp_len);
let mut fft = FftBlock::new(n_fft);
let mut grid_extract = GridExtract::new(grid);
let mut time = vec![C32::default(); n_fft];
let mut freq = vec![C32::default(); n_fft];
let mut equalized = vec![C32::default(); n_fft];
let mut in_off = 0;
let mut out_off = 0;
for _ in 0..n_symbols {
if cp_remove.process(&iq[in_off..], &mut time).out_written != n_fft {
return None;
}
if fft.process(&time, &mut freq).out_written != n_fft {
return None;
}
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;
}
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(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
}
}
}
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
}
}
}
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 } => {
let info = viterbi_decode_soft(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)
}
}
}
#[allow(clippy::too_many_arguments)]
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<(Vec<u8>, bool), 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 (mut outer_il_bits, inner_ok) =
inner_decode(inner, inner_de, plan.outer_il_bits, cache, ldpc_rule);
outer_il_bits.truncate(plan.outer_il_bits);
let outer_de = deinterleave_bits(outer_il, &outer_il_bits);
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
&& let Some(ref s) = sc
{
s.scramble(&mut framed);
}
let (bytes, crc_ok) = check_and_strip_crc(crc, &framed).ok_or(RxError::MalformedHeader)?;
Ok((bytes, crc_ok && inner_ok && outer_ok))
}
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 decode_frame_body(
cfg: &OfdmConfig,
mcs_table: &McsTable,
iq: &[C32],
channel_estimate: Option<&[C32]>,
cache: &CodecCache,
) -> Result<(FramePacket, usize), BodyError> {
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 (metadata, per_frame_seed, payload_len) = if cfg.header_format == HeaderFormat::OrionSdr {
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 = soft_demap(cfg, HEADER_CONSTELLATION, &iq[cursor..], n_sym, eq.as_mut())
.ok_or(BodyError::Incomplete)?;
let (fields, ok) = 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 !ok {
return Err(BodyError::Failed(RxError::HeaderCrcMismatch));
}
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 llrs = soft_demap(cfg, mcs.constellation, &iq[cursor..], n_sym, eq.as_mut())
.ok_or(BodyError::Incomplete)?;
let (bytes, ok) = 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,
)
.map_err(BodyError::Failed)?;
if !ok {
return Err(BodyError::Failed(RxError::CrcMismatch));
}
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((FramePacket { metadata, payload }, cursor))
}
pub fn demodulate_frame(
cfg: &OfdmConfig,
mcs_table: &McsTable,
iq: &[C32],
cache: Option<&CodecCache>,
) -> Result<FramePacket, RxError> {
let owned;
let cache = match cache {
Some(c) => c,
None => {
owned = CodecCache::new();
&owned
}
};
decode_frame_body(cfg, mcs_table, iq, None, cache)
.map(|(frame, _)| frame)
.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,
cache: Arc<CodecCache>,
}
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 {
let fs = cfg.fs;
Self {
cfg,
mcs_table,
preamble,
fs,
buf: Vec::new(),
score_threshold: 0.5,
cache,
}
}
pub fn with_score_threshold(mut self, t: f32) -> Self {
self.score_threshold = t;
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()
}
pub fn flush(&mut self) -> Vec<Result<RxFrame, RxError>> {
self.drain()
}
fn drain(&mut self) -> Vec<Result<RxFrame, RxError>> {
let mut out = Vec::new();
while let FrameStep::Decoded(result, consume_to) = self.try_one_frame() {
self.buf.drain(..consume_to);
out.push(result);
}
out
}
fn try_one_frame(&mut self) -> 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) = sync.into_iter().find(|r| r.score >= self.score_threshold) 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..];
match decode_frame_body(
&self.cfg,
&self.mcs_table,
body,
channel_estimate.as_deref(),
&self.cache,
) {
Ok((packet, body_samples)) => {
let diagnostics = OfdmRxFrame {
bits: Vec::new(),
num_symbols: 0,
evm_db: None,
cfo_hz: Some(total_cfo),
timing_offset_samples: Some(best.start_sample as i32),
channel_mse: None,
};
let consume_to = best.start_sample + pre_len + body_samples;
if consume_to > self.buf.len() {
return FrameStep::NeedMore;
}
FrameStep::Decoded(
Ok(RxFrame {
packet,
diagnostics,
}),
consume_to,
)
}
Err(BodyError::Incomplete) => 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 cp_remove = CyclicPrefixRemove::new(n_fft, cp_len);
let mut fft = FftBlock::new(n_fft);
let mut time = vec![C32::default(); n_fft];
if cp_remove
.process(&corrected[training_start..end], &mut time)
.out_written
!= n_fft
{
return None;
}
let mut freq = vec![C32::default(); n_fft];
if fft.process(&time, &mut freq).out_written != n_fft {
return None;
}
Some(freq)
}
}
enum FrameStep {
Decoded(Result<RxFrame, RxError>, usize),
NeedMore,
}