use super::ofdm::{EqualizerMethod, OfdmEqualizer};
use super::ofdm_frame::decode_chain;
use crate::core::Block;
use crate::dsp::Rotator;
use crate::fec::{CrcKind, DecodeRule, InterleaverKind, ScramblerKind, ScramblerPos};
use crate::modulate::ofdm_frame::{CodecCache, block_plan};
use crate::multicarrier::{CyclicPrefixRemove, FftBlock};
use crate::sync::{dvb_t_gi_sync, dvb_t_integer_cfo};
use crate::waveform::dvb_t::{
DVB_T_DATA_CARRIERS, DVB_T_FRAME_OUTER, DVB_T_FRAME_OUTER_IL, DVB_T_N_FFT, DvbTFrameParams,
ScatteredPilotExtractor, dvb_t_soft_llr, tps_carrier_bins,
};
use crate::waveform::dvb_t_tps::{TPS_SYMBOLS_PER_FRAME, TpsDecoder, TpsWord};
use crate::waveform::dvb_t_ts::{TS_PACKET_LEN, ts_depacketize, ts_energy_disperse};
use num_complex::Complex32 as C32;
const INTEGER_CFO_ACCUM_SYMBOLS: usize = 8;
const INTEGER_CFO_MAX_BINS: i32 = 32;
#[derive(Debug, Clone, PartialEq)]
pub struct DvbTRxFrame {
pub payload: Vec<u8>,
pub tps: TpsWord,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
pub enum DvbTRxError {
#[error("guard-interval acquisition failed (buffer too short or no CP lock)")]
Acquisition,
#[error("too few samples for the expected frame")]
Incomplete,
#[error("TPS word failed to decode (BCH uncorrectable)")]
TpsDecode,
#[error("payload FEC/CRC decode failed")]
PayloadDecode,
}
#[derive(Debug, Clone)]
pub struct DvbTFrameDemod {
params: DvbTFrameParams,
integer_cfo: bool,
}
impl DvbTFrameDemod {
pub fn new(params: DvbTFrameParams) -> Self {
Self {
params,
integer_cfo: false,
}
}
pub fn with_integer_cfo_correction(mut self, on: bool) -> Self {
self.integer_cfo = on;
self
}
pub fn params(&self) -> DvbTFrameParams {
self.params
}
pub fn integer_cfo_correction(&self) -> bool {
self.integer_cfo
}
fn integer_cfo_correct(
&self,
iq: &[C32],
n_fft: usize,
cp_len: usize,
fs: f32,
) -> Option<Vec<C32>> {
if !self.integer_cfo {
return None;
}
let sps = n_fft + cp_len;
let acq = dvb_t_gi_sync(iq, n_fft, cp_len, fs, sps)?;
let mut cpr = CyclicPrefixRemove::new(n_fft, cp_len);
let mut fft = FftBlock::new(n_fft);
let mut time = vec![C32::default(); n_fft];
let mut freq = vec![C32::default(); n_fft];
let mut accum = vec![C32::default(); n_fft];
for s in 0..INTEGER_CFO_ACCUM_SYMBOLS {
let off = acq.start_sample + s * sps;
if off + n_fft > iq.len() {
break;
}
cpr.process(&iq[off..], &mut time);
fft.process(&time, &mut freq);
for (a, &x) in accum.iter_mut().zip(freq.iter()) {
*a += C32::new(x.norm_sqr(), 0.0);
}
}
let k = dvb_t_integer_cfo(&accum, n_fft, INTEGER_CFO_MAX_BINS).map(|e| e.bins)?;
if k == 0 {
return None;
}
let mut corrected = vec![C32::default(); iq.len()];
Rotator::new(-(k as f32) * fs / n_fft as f32, fs).rotate_block(iq, &mut corrected);
Some(corrected)
}
pub fn decode(
&self,
iq: &[C32],
n_symbols: usize,
payload_len: usize,
) -> Result<DvbTRxFrame, DvbTRxError> {
let params = self.params;
let cache = CodecCache::new();
let base = params.config();
let n_fft = DVB_T_N_FFT;
let cp_len = base.carrier_plan.cp_len();
let sps = n_fft + cp_len;
let vbits = params.constellation().bits_per_symbol();
let corrected = self.integer_cfo_correct(iq, n_fft, cp_len, base.fs);
let iq: &[C32] = corrected.as_deref().unwrap_or(iq);
let acq = dvb_t_gi_sync(iq, n_fft, cp_len, base.fs, sps).ok_or(DvbTRxError::Acquisition)?;
let start = acq.start_sample;
if iq.len() < start + n_symbols * sps {
return Err(DvbTRxError::Incomplete);
}
let mut extractor = ScatteredPilotExtractor::new(params.guard());
let mut eq = OfdmEqualizer::new(&base, EqualizerMethod::PerSymbolPilotInterp);
let mut cp_remove = CyclicPrefixRemove::new(n_fft, cp_len);
let mut fft = FftBlock::new(n_fft);
let mut tps_dec = TpsDecoder::new();
let tps_bins = tps_carrier_bins();
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 data_syms = vec![C32::default(); DVB_T_DATA_CARRIERS];
let bits_per_sym = DVB_T_DATA_CARRIERS * vbits;
let mut llrs = vec![0.0f32; n_symbols * bits_per_sym];
let mut tps_word: Option<TpsWord> = None;
for s in 0..n_symbols {
let off = start + s * sps;
if cp_remove.process(&iq[off..], &mut time).out_written != n_fft {
return Err(DvbTRxError::Incomplete);
}
fft.process(&time, &mut freq);
let cells: Vec<C32> = tps_bins.iter().map(|&b| freq[b]).collect();
tps_dec.feed_symbol(&cells);
if (s + 1) % TPS_SYMBOLS_PER_FRAME == 0 && tps_word.is_none() {
tps_word = tps_dec.word();
tps_dec.reset();
}
let pilots = extractor.current_pilot_bins().to_vec();
let data_bins = extractor.data_bins().to_vec();
eq.set_pilot_bins(&pilots, &data_bins);
eq.process(&freq, &mut equalized);
extractor.extract_symbol(&equalized, &mut data_syms);
let sym_llrs = &mut llrs[s * bits_per_sym..(s + 1) * bits_per_sym];
for (c, &sym) in data_syms.iter().enumerate() {
let l = dvb_t_soft_llr(sym, vbits).expect("DVB-T order");
sym_llrs[c * vbits..(c + 1) * vbits].copy_from_slice(&l);
}
}
let tps = tps_word.ok_or(DvbTRxError::TpsDecode)?;
let n_ts_packets = payload_len.div_ceil(TS_PACKET_LEN - 1).max(1);
let ts_bytes_len = n_ts_packets * TS_PACKET_LEN;
let plan = block_plan(
ts_bytes_len,
CrcKind::None,
DVB_T_FRAME_OUTER,
params.inner(),
DVB_T_FRAME_OUTER_IL,
InterleaverKind::None,
&cache,
);
let (mut ts, ok) = decode_chain(
&llrs,
&plan,
CrcKind::None,
DVB_T_FRAME_OUTER,
params.inner(),
DVB_T_FRAME_OUTER_IL,
InterleaverKind::None,
ScramblerKind::None,
ScramblerPos::BeforeOuterFec,
0,
&cache,
DecodeRule::SumProduct,
)
.map_err(|_| DvbTRxError::PayloadDecode)?;
if !ok {
return Err(DvbTRxError::PayloadDecode);
}
if ts.len() < ts_bytes_len {
return Err(DvbTRxError::PayloadDecode);
}
ts.truncate(ts_bytes_len);
ts_energy_disperse(&mut ts);
let payload = ts_depacketize(&ts).ok_or(DvbTRxError::PayloadDecode)?;
let payload = payload
.get(..payload_len)
.map(|s| s.to_vec())
.unwrap_or(payload);
Ok(DvbTRxFrame { payload, tps })
}
}