use super::ofdm::{ConstellationOrder, OfdmConfig, OfdmMod};
use crate::codec::{crc16, crc32};
use crate::core::Block;
use crate::fec::{
Bch, CrcKind, FramePacket, InnerFec, InterleaverKind, Ldpc, LdpcCode, OuterFec, PnScrambler,
ReedSolomon, ScramblerKind, ScramblerPos, SeedMode, conv_encode_punctured_with,
punctured_coded_len_with,
};
use crate::multicarrier::{CarrierPlan, SymbolWindow};
use crate::sync::{OfdmPreamble, generate_ofdm_preamble};
use num_complex::Complex32 as C32;
use std::sync::{Arc, Mutex};
type CodeMemo<K, V> = Mutex<Vec<(K, Arc<V>)>>;
#[derive(Debug, Default)]
pub struct CodecCache {
ldpc: CodeMemo<LdpcCode, Ldpc>,
bch: CodeMemo<(usize, usize), Bch>,
rs: CodeMemo<(usize, usize), ReedSolomon>,
}
impl Clone for CodecCache {
fn clone(&self) -> Self {
Self::default()
}
}
impl CodecCache {
pub fn new() -> Self {
Self::default()
}
pub fn ldpc(&self, code: LdpcCode) -> Arc<Ldpc> {
let mut table = self.ldpc.lock().unwrap();
if let Some((_, c)) = table.iter().find(|(k, _)| *k == code) {
return Arc::clone(c);
}
let built = Arc::new(Ldpc::new(code));
table.push((code, Arc::clone(&built)));
built
}
pub fn bch(&self, t: usize, msg_bits: usize) -> Arc<Bch> {
let key = (t, msg_bits);
let mut table = self.bch.lock().unwrap();
if let Some((_, c)) = table.iter().find(|(k, _)| *k == key) {
return Arc::clone(c);
}
let built = Arc::new(shortened_bch_for(t, msg_bits));
table.push((key, Arc::clone(&built)));
built
}
pub fn rs(&self, n: usize, n_parity: usize) -> Arc<ReedSolomon> {
let key = (n, n_parity);
let mut table = self.rs.lock().unwrap();
if let Some((_, c)) = table.iter().find(|(k, _)| *k == key) {
return Arc::clone(c);
}
let built = Arc::new(ReedSolomon::new(n, n_parity).expect("valid RS config"));
table.push((key, Arc::clone(&built)));
built
}
}
pub const HEADER_FIELD_BYTES: usize = 14;
pub const HEADER_CONSTELLATION: ConstellationOrder = ConstellationOrder::Bpsk;
pub const HEADER_LDPC: LdpcCode = LdpcCode::N512R12;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Mcs {
pub constellation: ConstellationOrder,
pub inner_fec: InnerFec,
pub outer_fec: OuterFec,
}
impl Mcs {
pub const fn new(
constellation: ConstellationOrder,
inner_fec: InnerFec,
outer_fec: OuterFec,
) -> Self {
Self {
constellation,
inner_fec,
outer_fec,
}
}
}
#[derive(Debug, Clone)]
pub struct McsTable {
entries: Vec<Mcs>,
}
impl McsTable {
pub fn new(entries: Vec<Mcs>) -> Self {
assert!(
!entries.is_empty(),
"MCS table must have at least one entry"
);
Self { entries }
}
pub fn default_ladder() -> Self {
let inner = InnerFec::Ldpc(LdpcCode::N512R12);
let outer = OuterFec::Bch { t: 8 };
Self::new(vec![
Mcs::new(ConstellationOrder::Bpsk, inner, outer),
Mcs::new(ConstellationOrder::Qpsk, inner, outer),
Mcs::new(ConstellationOrder::Qam16, inner, outer),
Mcs::new(ConstellationOrder::Qam64, inner, outer),
])
}
pub fn get(&self, mcs_index: u8) -> Option<Mcs> {
self.entries.get(mcs_index as usize).copied()
}
pub fn len(&self) -> usize {
self.entries.len()
}
pub fn is_empty(&self) -> bool {
self.entries.is_empty()
}
}
pub fn bytes_to_bits(bytes: &[u8]) -> Vec<u8> {
let mut bits = Vec::with_capacity(bytes.len() * 8);
for &b in bytes {
for i in (0..8).rev() {
bits.push((b >> i) & 1);
}
}
bits
}
pub fn bits_to_bytes(bits: &[u8]) -> Vec<u8> {
assert_eq!(
bits.len() % 8,
0,
"bit count must be a whole number of bytes"
);
let mut bytes = Vec::with_capacity(bits.len() / 8);
for chunk in bits.chunks(8) {
let mut b = 0u8;
for &bit in chunk {
b = (b << 1) | (bit & 1);
}
bytes.push(b);
}
bytes
}
pub fn append_crc(crc: CrcKind, data: &[u8]) -> Vec<u8> {
let mut out = data.to_vec();
match crc {
CrcKind::None => {}
CrcKind::Crc16 => out.extend_from_slice(&crc16(data).to_be_bytes()),
CrcKind::Crc32 => out.extend_from_slice(&crc32(data).to_be_bytes()),
}
out
}
pub fn check_and_strip_crc(crc: CrcKind, data: &[u8]) -> Option<(Vec<u8>, bool)> {
let clen = crc.len_bytes();
if data.len() < clen {
return None;
}
let (payload, tail) = data.split_at(data.len() - clen);
let ok = match crc {
CrcKind::None => true,
CrcKind::Crc16 => crc16(payload).to_be_bytes()[..] == *tail,
CrcKind::Crc32 => crc32(payload).to_be_bytes()[..] == *tail,
};
Some((payload.to_vec(), ok))
}
pub fn build_scrambler(kind: ScramblerKind, per_frame_seed: u32) -> Option<PnScrambler> {
match kind {
ScramblerKind::None | ScramblerKind::DvbTEnergyDispersal => None,
ScramblerKind::Additive { poly, width, seed } => {
let raw = match seed {
SeedMode::Fixed(v) => v,
SeedMode::PerFrameRandom => per_frame_seed,
};
let mask = if width >= 32 {
u32::MAX
} else {
(1u32 << width) - 1
};
let s = {
let m = raw & mask;
if m == 0 { 1 } else { m }
};
Some(PnScrambler::new(poly, width as u32, s))
}
}
}
pub fn scramble_bytes(kind: ScramblerKind, per_frame_seed: u32, bytes: &mut [u8]) {
match kind {
ScramblerKind::None => {}
ScramblerKind::DvbTEnergyDispersal => {
crate::waveform::dvb_t::DvbTEnergyDispersal::new().feed_in_place(bytes);
}
ScramblerKind::Additive { .. } => {
if let Some(s) = build_scrambler(kind, per_frame_seed) {
s.scramble(bytes);
}
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct BlockPlan {
pub info_bytes: usize,
pub framed_bytes: usize,
pub outer_coded_bits: usize,
pub outer_il_bits: usize,
pub inner_coded_bits: usize,
pub coded_bits: usize,
}
fn round_up(n: usize, block: usize) -> usize {
if block == 0 {
n
} else {
n.div_ceil(block) * block
}
}
fn conv_il_bits(n_bits: usize, branches: usize, depth: usize) -> usize {
let bytes =
round_up(n_bits.div_ceil(8), branches) + crate::fec::conv_roundtrip_delay(branches, depth);
bytes * 8
}
pub fn block_plan(
info_bytes: usize,
crc: CrcKind,
outer: OuterFec,
inner: InnerFec,
outer_il: InterleaverKind,
inner_il: InterleaverKind,
cache: &CodecCache,
) -> BlockPlan {
let framed_bytes = info_bytes + crc.len_bytes();
let framed_bits = framed_bytes * 8;
let outer_coded_bits = match outer {
OuterFec::None => framed_bits,
OuterFec::Bch { t } => {
let code = cache.bch(t, BCH_INFO_BITS);
let n_blocks = framed_bits.div_ceil(BCH_INFO_BITS);
n_blocks * code.n()
}
OuterFec::ReedSolomon { n, n_parity } => {
let rs = cache.rs(n, n_parity);
let n_blocks = framed_bytes.div_ceil(rs.k());
n_blocks * rs.n() * 8
}
};
let outer_il_bits = match outer_il {
InterleaverKind::None => outer_coded_bits,
InterleaverKind::Block { rows, cols } => round_up(outer_coded_bits, rows * cols),
InterleaverKind::Convolutional { branches, depth } => {
conv_il_bits(outer_coded_bits, branches, depth)
}
};
let inner_coded_bits = match inner {
InnerFec::None => outer_il_bits,
InnerFec::Ldpc(code) => {
let ldpc = cache.ldpc(code);
let n_blocks = outer_il_bits.div_ceil(ldpc.k());
n_blocks * ldpc.n()
}
InnerFec::Convolutional { rate, code } => {
punctured_coded_len_with(code, outer_il_bits, rate)
}
};
let coded_bits = match inner_il {
InterleaverKind::None => inner_coded_bits,
InterleaverKind::Block { rows, cols } => round_up(inner_coded_bits, rows * cols),
InterleaverKind::Convolutional { branches, depth } => {
conv_il_bits(inner_coded_bits, branches, depth)
}
};
BlockPlan {
info_bytes,
framed_bytes,
outer_coded_bits,
outer_il_bits,
inner_coded_bits,
coded_bits,
}
}
pub fn symbols_for_coded_bits(
base: &OfdmConfig,
constellation: ConstellationOrder,
bits: usize,
) -> usize {
let bps = base.carrier_plan.data_carriers().len() * constellation.bits_per_symbol();
bits.div_ceil(bps)
}
pub fn interleave_bits(il: InterleaverKind, bits: &[u8]) -> Vec<u8> {
match il {
InterleaverKind::None => bits.to_vec(),
InterleaverKind::Block { rows, cols } => {
let block = rows * cols;
let bi = crate::fec::BlockInterleaver::new(rows, cols);
let mut out = Vec::with_capacity(bits.len().div_ceil(block) * block);
let mut padded = vec![0u8; block];
let mut permuted = vec![0u8; block];
for chunk in bits.chunks(block) {
padded[..chunk.len()].copy_from_slice(chunk);
padded[chunk.len()..].fill(0);
bi.interleave(&padded, &mut permuted);
out.extend_from_slice(&permuted);
}
out
}
InterleaverKind::Convolutional { branches, depth } => {
let mut ci = crate::fec::ConvInterleaver::new(branches, depth);
let bytes = pack_bits_padded(bits);
let n = round_up(bytes.len(), branches);
let mut padded = bytes;
padded.resize(n, 0);
let mut out_bytes = ci.feed(&padded);
out_bytes.extend_from_slice(&ci.flush());
bytes_to_bits(&out_bytes)
}
}
}
pub const BCH_INFO_BITS: usize = 120;
pub fn outer_encode(outer: OuterFec, message_bytes: &[u8], cache: &CodecCache) -> Vec<u8> {
match outer {
OuterFec::None => bytes_to_bits(message_bytes),
OuterFec::Bch { t } => {
let msg_bits = bytes_to_bits(message_bytes);
let code = cache.bch(t, BCH_INFO_BITS);
let mut out = Vec::new();
for chunk in msg_bits.chunks(BCH_INFO_BITS) {
let mut block = chunk.to_vec();
block.resize(BCH_INFO_BITS, 0);
out.extend_from_slice(&code.encode(&block));
}
out
}
OuterFec::ReedSolomon { n, n_parity } => {
let rs = cache.rs(n, n_parity);
let k = rs.k();
let mut out_bytes = Vec::new();
for chunk in message_bytes.chunks(k) {
let mut block = chunk.to_vec();
block.resize(k, 0);
out_bytes.extend_from_slice(&rs.encode(&block));
}
bytes_to_bits(&out_bytes)
}
}
}
pub fn inner_encode(inner: InnerFec, info_bits: &[u8], cache: &CodecCache) -> Vec<u8> {
match inner {
InnerFec::None => info_bits.to_vec(),
InnerFec::Ldpc(code) => {
let ldpc = cache.ldpc(code);
let k = ldpc.k();
let mut out = Vec::new();
for chunk in info_bits.chunks(k) {
let mut msg = chunk.to_vec();
msg.resize(k, 0);
out.extend_from_slice(&ldpc.encode(&msg));
}
out
}
InnerFec::Convolutional { rate, code } => conv_encode_punctured_with(code, info_bits, rate),
}
}
pub fn shortened_bch_for(t: usize, msg_bits: usize) -> Bch {
let full = Bch::new(t).expect("valid BCH t");
let parity = full.parity_bits();
Bch::shortened(msg_bits + parity, t).expect("valid shortened BCH")
}
pub struct EncodedStages {
pub outer_il_bits: Vec<u8>,
pub coded: Vec<u8>,
}
#[allow(clippy::too_many_arguments)]
pub fn encode_chain_stages(
bytes: &[u8],
crc: CrcKind,
outer: OuterFec,
inner: InnerFec,
outer_il: InterleaverKind,
inner_il: InterleaverKind,
scrambler: ScramblerKind,
scrambler_pos: ScramblerPos,
per_frame_seed: u32,
cache: &CodecCache,
) -> EncodedStages {
let mut framed = append_crc(crc, bytes);
let sc = build_scrambler(scrambler, per_frame_seed);
if scrambler_pos == ScramblerPos::BeforeOuterFec {
scramble_bytes(scrambler, per_frame_seed, &mut framed);
}
let outer_bits = outer_encode(outer, &framed, cache);
let outer_il_bits = interleave_bits(outer_il, &outer_bits);
let inner_bits = inner_encode(inner, &outer_il_bits, cache);
let mut coded = interleave_bits(inner_il, &inner_bits);
if scrambler_pos == ScramblerPos::AfterInnerFec
&& let Some(ref s) = sc
{
scramble_bits(s, &mut coded);
}
EncodedStages {
outer_il_bits,
coded,
}
}
#[allow(clippy::too_many_arguments)]
pub fn encode_chain(
bytes: &[u8],
crc: CrcKind,
outer: OuterFec,
inner: InnerFec,
outer_il: InterleaverKind,
inner_il: InterleaverKind,
scrambler: ScramblerKind,
scrambler_pos: ScramblerPos,
per_frame_seed: u32,
cache: &CodecCache,
) -> Vec<u8> {
encode_chain_stages(
bytes,
crc,
outer,
inner,
outer_il,
inner_il,
scrambler,
scrambler_pos,
per_frame_seed,
cache,
)
.coded
}
pub fn scramble_bits(s: &PnScrambler, bits: &mut [u8]) {
let mut bytes = pack_bits_padded(bits);
s.scramble(&mut bytes);
let unpacked = bytes_to_bits(&bytes);
bits.copy_from_slice(&unpacked[..bits.len()]);
}
fn pack_bits_padded(bits: &[u8]) -> Vec<u8> {
let mut padded = bits.to_vec();
let rem = padded.len() % 8;
if rem != 0 {
padded.resize(padded.len() + (8 - rem), 0);
}
bits_to_bytes(&padded)
}
pub fn pack_header_fields(
mcs_index: u8,
payload_len: u32,
sequence_num: u32,
flags: u8,
scrambler_seed: u32,
) -> [u8; HEADER_FIELD_BYTES] {
let mut out = [0u8; HEADER_FIELD_BYTES];
out[0] = mcs_index;
out[1..5].copy_from_slice(&payload_len.to_be_bytes());
out[5..9].copy_from_slice(&sequence_num.to_be_bytes());
out[9] = flags;
out[10..14].copy_from_slice(&scrambler_seed.to_be_bytes());
out
}
fn map_bits_to_iq(base: &OfdmConfig, constellation: ConstellationOrder, bits: &[u8]) -> Vec<C32> {
let cfg = symbol_config(base, constellation);
let mut modstage = OfdmMod::new(&cfg);
modstage.modulate(bits)
}
fn map_bits_to_iq_scattered(
base: &OfdmConfig,
constellation: ConstellationOrder,
bits: &[u8],
mapper: &mut crate::waveform::dvb_t::ScatteredPilotMapper,
) -> Vec<C32> {
use crate::waveform::dvb_t::{dvb_t_map_symbol, is_dvb_t_constellation};
let n_data = mapper.num_data_carriers();
let n_fft = mapper.n_fft();
let cp_len = base.carrier_plan.cp_len();
let vbits = constellation.bits_per_symbol();
let bps = n_data * vbits;
if bps == 0 {
return Vec::new();
}
let n_symbols = bits.len().div_ceil(bps);
let mut padded = bits.to_vec();
padded.resize(n_symbols * bps, 0);
let dvb_t_map = is_dvb_t_constellation(constellation);
let mut sym_mapper = crate::modulate::ofdm::ideal_symbol_mapper(constellation);
let mut ifft = crate::multicarrier::IfftBlock::new(n_fft);
let mut cp_insert = crate::multicarrier::CyclicPrefixInsert::new(n_fft, cp_len);
let mut symbols = vec![C32::default(); n_data];
let mut freq = vec![C32::default(); n_fft];
let mut time = vec![C32::default(); n_fft];
let sps = n_fft + cp_len;
let mut out = vec![C32::default(); n_symbols * sps];
let g = base.gain;
for s in 0..n_symbols {
let bit_off = s * bps;
let sym_bits = &padded[bit_off..bit_off + bps];
if dvb_t_map {
for (c, chunk) in sym_bits.chunks(vbits).enumerate() {
symbols[c] = dvb_t_map_symbol(chunk).expect("DVB-T order");
}
} else {
sym_mapper.process(sym_bits, &mut symbols);
}
mapper.map_symbol(&symbols, &mut freq);
ifft.process(&freq, &mut time);
let cp_out = &mut out[s * sps..(s + 1) * sps];
cp_insert.process(&time, cp_out);
if g != 1.0 {
for v in cp_out.iter_mut() {
*v = C32::new(g * v.re, g * v.im);
}
}
}
out
}
pub fn symbol_config(base: &OfdmConfig, constellation: ConstellationOrder) -> OfdmConfig {
OfdmConfig::new(
base.carrier_plan.clone(),
base.fs,
base.rf_hz,
base.gain,
constellation,
)
.with_rx_window_backoff(base.rx_window_backoff)
}
#[derive(Debug, Clone)]
pub struct OfdmFrameMod {
cfg: OfdmConfig,
mcs_table: McsTable,
preamble: OfdmPreamble,
cache: Arc<CodecCache>,
}
pub(crate) fn assert_baseband(cfg: &OfdmConfig) {
assert!(
cfg.rf_hz == 0.0,
"OFDM frame assembly is baseband-only: got rf_hz = {} Hz. Modulate at \
rf_hz = 0.0 and upconvert the whole burst with one continuous Rotator.",
cfg.rf_hz
);
}
impl OfdmFrameMod {
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 {
assert_baseband(&cfg);
Self {
cfg,
mcs_table,
preamble,
cache,
}
}
pub fn config(&self) -> &OfdmConfig {
&self.cfg
}
pub fn preamble(&self) -> &OfdmPreamble {
&self.preamble
}
pub fn modulate_frame(&self, frame: &FramePacket, per_frame_seed: u32) -> Vec<C32> {
let mut out = Vec::new();
let mut scattered = self.cfg.dvb_t_scattered.then(|| {
let guard = crate::waveform::dvb_t::GuardInterval::from_cp_len_2k(
self.cfg.carrier_plan.cp_len(),
)
.expect("DVB-T scattered link requires a 2K guard interval");
crate::waveform::dvb_t::ScatteredPilotMapper::new(guard)
});
let mut map = |constellation, bits: &[u8]| match scattered.as_mut() {
Some(m) => map_bits_to_iq_scattered(&self.cfg, constellation, bits, m),
None => map_bits_to_iq(&self.cfg, constellation, bits),
};
out.extend_from_slice(&generate_ofdm_preamble(&self.preamble, &self.cfg));
if self.cfg.header_format.has_header_block() {
let fields = pack_header_fields(
frame.metadata.mcs_index,
frame.payload.len() as u32,
frame.metadata.sequence_num,
frame.metadata.flags,
per_frame_seed,
);
let header_bits = encode_chain(
&fields,
self.cfg.header_crc,
OuterFec::None,
InnerFec::Ldpc(HEADER_LDPC),
InterleaverKind::None,
InterleaverKind::None,
ScramblerKind::None,
ScramblerPos::BeforeOuterFec,
0,
&self.cache,
);
out.extend_from_slice(&map(HEADER_CONSTELLATION, &header_bits));
}
let mcs = self
.mcs_table
.get(frame.metadata.mcs_index)
.expect("mcs_index must be in the MCS table");
let payload_bits = encode_chain(
&frame.payload,
self.cfg.payload_crc,
mcs.outer_fec,
mcs.inner_fec,
self.cfg.outer_interleaver,
self.cfg.inner_interleaver,
self.cfg.scrambler,
self.cfg.scrambler_pos,
per_frame_seed,
&self.cache,
);
out.extend_from_slice(&map(mcs.constellation, &payload_bits));
self.apply_symbol_windowing(&mut out);
if let Some(lowpass) = self.cfg.tx_lowpass {
lowpass.apply(&mut out);
}
out
}
fn apply_symbol_windowing(&self, out: &mut [C32]) {
let roll_off = self.cfg.carrier_plan.window_roll_off();
if roll_off == 0 {
return;
}
let sps = self.cfg.samples_per_ofdm_symbol();
let start = self.preamble.num_repeats * self.preamble.repeat_len;
let mut win = SymbolWindow::new(sps, roll_off);
let mut off = start;
while off + sps <= out.len() {
let symbol: Vec<C32> = out[off..off + sps].to_vec();
win.process(&symbol, &mut out[off..off + sps]);
off += sps;
}
}
}
pub fn plan_of(cfg: &OfdmConfig) -> CarrierPlan {
cfg.carrier_plan.clone()
}