"m17core",
]
+[[package]]
+name = "m17rt-fastdemod"
+version = "0.1.0"
+dependencies = [
+ "clap",
+ "env_logger",
+ "log",
+ "m17core",
+]
+
[[package]]
name = "m17rt-mod"
version = "0.1.0"
resolver = "2"
members = [
"m17app", "m17codec2", "m17core", "tools/m17rt-demod", "tools/m17rt-mod", "tools/m17rt-txpacket", "tools/m17rt-rxpacket", "tools/m17rt-soundcards"
-, "tools/m17rt-netclient"]
+, "tools/m17rt-netclient", "tools/m17rt-fastdemod"]
}
SoundmodemEvent::BasebandInput(b) => {
for sample in &*b {
- if let Some(frame) = demodulator.demod(*sample) {
+ if let Some((frame, _)) = demodulator.demod(*sample) {
tnc.handle_frame(frame);
loop {
let n = tnc.read_kiss(&mut buf);
//! Buffer between `read()` calls
use std::{
- io::{self, ErrorKind, Read},
+ io::{self, Read},
sync::{Arc, Mutex, mpsc::Receiver},
};
let output = {
let rx = self.rx.lock().unwrap();
rx.recv()
- .map_err(|s| io::Error::new(ErrorKind::Other, format!("{:?}", s)))?
+ .map_err(|s| io::Error::other(format!("{:?}", s)))?
};
let to_write = output.len().min(buf.len());
buf[0..to_write].copy_from_slice(&output[0..to_write]);
interleave(&decoded[2..])
}
-pub(crate) fn parse_lsf(frame: &[f32] /* length 192 */) -> Option<LsfFrame> {
+pub(crate) fn parse_lsf(frame: &[f32] /* length 192 */) -> Option<(LsfFrame, u8)> {
let deinterleaved = frame_initial_decode(frame);
debug!("deinterleaved: {:?}", deinterleaved);
- let lsf = match fec::decode(&deinterleaved, 240, p_1) {
- Some(lsf) => LsfFrame(lsf),
+ let (lsf, errors) = match fec::decode(&deinterleaved, 240, p_1) {
+ Some((lsf, errors)) => (LsfFrame(lsf), errors),
None => return None,
};
debug!("full lsf: {:?}", lsf.0);
debug!("encryption type: {:?}", lsf.encryption_type());
debug!("can: {}", lsf.channel_access_number());
debug!("meta: {:?}", lsf.meta());
- Some(lsf)
+ Some((lsf, errors))
}
-pub(crate) fn parse_stream(frame: &[f32] /* length 192 */) -> Option<StreamFrame> {
+pub(crate) fn parse_stream(frame: &[f32] /* length 192 */) -> Option<(StreamFrame, u8)> {
let deinterleaved = frame_initial_decode(frame);
let stream_part = &deinterleaved[12..];
- let stream = fec::decode(stream_part, 144, p_2)?;
+ let (stream, errors) = fec::decode(stream_part, 144, p_2)?;
let frame_num = u16::from_be_bytes([stream[0], stream[1]]);
let eos = (frame_num & 0x8000) > 0;
let frame_num = frame_num & 0x7fff; // higher layer has to handle wraparound
"LICH: received part {counter} part {part:?} from raw {:?}",
&deinterleaved[0..12]
);
- Some(StreamFrame {
- lich_idx: counter,
- lich_part: part,
- frame_number: frame_num,
- end_of_stream: eos,
- stream_data: stream[2..18].try_into().unwrap(),
- })
+ Some((
+ StreamFrame {
+ lich_idx: counter,
+ lich_part: part,
+ frame_number: frame_num,
+ end_of_stream: eos,
+ stream_data: stream[2..18].try_into().unwrap(),
+ },
+ errors,
+ ))
} else {
None
}
}
-pub(crate) fn parse_packet(frame: &[f32] /* length 192 */) -> Option<PacketFrame> {
+pub(crate) fn parse_packet(frame: &[f32] /* length 192 */) -> Option<(PacketFrame, u8)> {
let deinterleaved = frame_initial_decode(frame);
- let packet = fec::decode(&deinterleaved, 206, p_3)?;
+ let (packet, errors) = fec::decode(&deinterleaved, 206, p_3)?;
let final_frame = (packet[25] & 0x80) > 0;
let number = (packet[25] >> 2) & 0x1f;
let counter = if final_frame {
index: number as usize,
}
};
- Some(PacketFrame {
- payload: packet[0..25].try_into().unwrap(),
- counter,
- })
+ Some((
+ PacketFrame {
+ payload: packet[0..25].try_into().unwrap(),
+ counter,
+ },
+ errors,
+ ))
}
pub(crate) fn decode_lich(type2_bits: &[u8]) -> Option<(u8, [u8; 5])> {
]);
let encoded = encode_lsf(&lsf);
let decoded = crate::decode::parse_lsf(&encoded);
- assert_eq!(decoded, Some(lsf));
+ assert!(matches!(decoded, Some((frame, _)) if frame == lsf));
}
#[test]
};
let encoded = encode_stream(&stream);
let decoded = crate::decode::parse_stream(&encoded);
- assert_eq!(decoded, Some(stream));
+ assert!(matches!(decoded, Some((frame, _)) if frame == stream));
}
#[test]
};
let encoded = encode_packet(&packet);
let decoded = crate::decode::parse_packet(&encoded);
- assert_eq!(decoded, Some(packet));
+ assert!(matches!(decoded, Some((frame, _)) if frame == packet));
let packet = PacketFrame {
payload: [0u8; 25],
};
let encoded = encode_packet(&packet);
let decoded = crate::decode::parse_packet(&encoded);
- assert_eq!(decoded, Some(packet));
+ assert!(matches!(decoded, Some((frame, _)) if frame == packet));
}
#[test]
type3: &[u8], // up to len 46
input_len: usize,
puncture: fn(usize) -> (bool, bool),
-) -> Option<[u8; 30]> {
+) -> Option<([u8; 30], u8)> {
let type3_bits = Bits::new(type3);
let mut type3_iter = type3_bits.iter();
let mut table = [[0u8; 32]; 244];
};
}
}
- Some(out)
+ Some((out, *best))
}
}
let encoded = encode(&lsf, 240, p_1);
assert_eq!(encoded, expected_encoded);
let decoded = decode(&encoded, 240, p_1);
- assert_eq!(decoded, Some(lsf));
+ assert_eq!(decoded, Some((lsf, 0)));
}
#[test]
if idx == 100 {
assert_eq!(decoded, None); // 7 bits is too much damage
} else {
- assert_eq!(decoded, Some(lsf)); // recovered from errors
+ assert!(matches!(decoded, Some((frame, _)) if frame == lsf)); // recovered from errors
}
}
}
use log::debug;
pub trait Demodulator {
- fn demod(&mut self, sample: i16) -> Option<Frame>;
+ /// Handle the next sample.
+ ///
+ /// If a frame can be decoded, return it, along with an indication of many errors were fixed by FEC.
+ fn demod(&mut self, sample: i16) -> Option<(Frame, u8)>;
+ /// Does somebody else appear to be transmitting at the moment?
fn data_carrier_detect(&self) -> bool;
}
}
impl Demodulator for SoftDemodulator {
- fn demod(&mut self, sample: i16) -> Option<Frame> {
+ fn demod(&mut self, sample: i16) -> Option<(Frame, u8)> {
self.filter_win[self.filter_cursor] = sample;
self.filter_cursor = (self.filter_cursor + 1) % 81;
let mut out: f32 = 0.0;
}
match c.burst {
SyncBurst::Lsf => {
- if let Some(frame) = parse_lsf(&pkt_samples) {
- return Some(Frame::Lsf(frame));
+ if let Some((frame, errors)) = parse_lsf(&pkt_samples) {
+ return Some((Frame::Lsf(frame), errors));
}
}
SyncBurst::Bert => {
// TODO: BERT
}
SyncBurst::Stream => {
- if let Some(frame) = parse_stream(&pkt_samples) {
- return Some(Frame::Stream(frame));
+ if let Some((frame, errors)) = parse_stream(&pkt_samples) {
+ return Some((Frame::Stream(frame), errors));
}
}
SyncBurst::Packet => {
- if let Some(frame) = parse_packet(&pkt_samples) {
- return Some(Frame::Packet(frame));
+ if let Some((frame, errors)) = parse_packet(&pkt_samples) {
+ return Some((Frame::Packet(frame), errors));
}
}
SyncBurst::Preamble | SyncBurst::EndOfTransmission => {
--- /dev/null
+[package]
+name = "m17rt-fastdemod"
+version = "0.1.0"
+edition = "2024"
+license = "MIT"
+authors = ["Thomas Karpiniec <tom.karpiniec@outlook.com"]
+publish = false
+
+[dependencies]
+m17core = { path = "../../m17core" }
+
+clap = { version = "4.5.39", features = ["derive"] }
+env_logger = "0.11.6"
+log = "0.4.22"
--- /dev/null
+use std::{error::Error, fs::File, io::Read, path::PathBuf};
+
+use clap::Parser;
+use m17core::{
+ modem::{Demodulator, SoftDemodulator},
+ protocol::Frame,
+};
+
+#[derive(Parser)]
+struct Args {
+ #[arg(short = 'i', help = "Input RRC file")]
+ input: PathBuf,
+}
+
+fn main() -> Result<(), Box<dyn Error>> {
+ env_logger::init();
+ let args = Args::parse();
+
+ let mut file = File::open(&args.input)?;
+ let mut baseband = vec![];
+ file.read_to_end(&mut baseband)?;
+
+ let mut total = 0;
+ let mut demod = SoftDemodulator::new();
+ for (idx, sample) in baseband
+ .chunks(2)
+ .map(|pair| i16::from_le_bytes([pair[0], pair[1]]))
+ .enumerate()
+ {
+ if let Some((frame, errors)) = demod.demod(sample) {
+ total += 1;
+ let frame_desc = match frame {
+ Frame::Lsf(_) => "lsf",
+ Frame::Stream(_) => "stream",
+ Frame::Packet(_) => "packet",
+ };
+ println!("sample {}: {} with {} errors", idx, frame_desc, errors);
+ }
+ }
+
+ println!("\ntotal successful decodes: {}", total);
+
+ Ok(())
+}