]> code.octet-stream.net Git - m17rt/commitdiff
fast demod bin master
authorThomas Karpiniec <tom.karpiniec@outlook.com>
Tue, 15 Jul 2025 11:13:55 +0000 (21:13 +1000)
committerThomas Karpiniec <tom.karpiniec@outlook.com>
Tue, 15 Jul 2025 11:13:55 +0000 (21:13 +1000)
Cargo.lock
Cargo.toml
m17app/src/soundmodem.rs
m17app/src/util/out_buffer.rs
m17core/src/decode.rs
m17core/src/encode.rs
m17core/src/fec.rs
m17core/src/modem.rs
tools/m17rt-fastdemod/Cargo.toml [new file with mode: 0644]
tools/m17rt-fastdemod/src/main.rs [new file with mode: 0644]

index 4800984ee50ae4400ec7c6415a37ab349c912985..51fa1428abcb776a61d02336ed0b0f976ee24dd8 100644 (file)
@@ -547,6 +547,16 @@ dependencies = [
  "m17core",
 ]
 
  "m17core",
 ]
 
+[[package]]
+name = "m17rt-fastdemod"
+version = "0.1.0"
+dependencies = [
+ "clap",
+ "env_logger",
+ "log",
+ "m17core",
+]
+
 [[package]]
 name = "m17rt-mod"
 version = "0.1.0"
 [[package]]
 name = "m17rt-mod"
 version = "0.1.0"
index 3ea321813861d765fb5af77feadeba19e6270d94..2883db341ba16c0fc9cbc8ed9b6ced2f5aa9dee3 100644 (file)
@@ -2,4 +2,4 @@
 resolver = "2"
 members = [
     "m17app", "m17codec2", "m17core", "tools/m17rt-demod", "tools/m17rt-mod", "tools/m17rt-txpacket", "tools/m17rt-rxpacket", "tools/m17rt-soundcards"
 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"]
index c782d86f85cfacde7a8a02936b3107d8fcdf2e14..19a3dd528ba13ee0068f99f66ba10a8db2a2befa 100644 (file)
@@ -234,7 +234,7 @@ fn spawn_soundmodem_worker(
                 }
                 SoundmodemEvent::BasebandInput(b) => {
                     for sample in &*b {
                 }
                 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);
                             tnc.handle_frame(frame);
                             loop {
                                 let n = tnc.read_kiss(&mut buf);
index 06d8e915708055ad76973cbc4015bfc36e0951c2..399ae716a974282d36d3aa971c998b224eafcaba 100644 (file)
@@ -1,7 +1,7 @@
 //! Buffer between `read()` calls
 
 use std::{
 //! Buffer between `read()` calls
 
 use std::{
-    io::{self, ErrorKind, Read},
+    io::{self, Read},
     sync::{Arc, Mutex, mpsc::Receiver},
 };
 
     sync::{Arc, Mutex, mpsc::Receiver},
 };
 
@@ -49,7 +49,7 @@ impl Read for OutBuffer {
         let output = {
             let rx = self.rx.lock().unwrap();
             rx.recv()
         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]);
         };
         let to_write = output.len().min(buf.len());
         buf[0..to_write].copy_from_slice(&output[0..to_write]);
index 4bc628869d0fd76df48da34344fdf1a1e881b7cf..3195ed5d86e9398abc253e7c1b947cf90436772e 100644 (file)
@@ -91,11 +91,11 @@ pub(crate) fn frame_initial_decode(frame: &[f32] /* length 192 */) -> [u8; 46] {
     interleave(&decoded[2..])
 }
 
     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 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);
         None => return None,
     };
     debug!("full lsf: {:?}", lsf.0);
@@ -108,13 +108,13 @@ pub(crate) fn parse_lsf(frame: &[f32] /* length 192 */) -> Option<LsfFrame> {
     debug!("encryption type: {:?}", lsf.encryption_type());
     debug!("can: {}", lsf.channel_access_number());
     debug!("meta: {:?}", lsf.meta());
     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 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
     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
@@ -125,21 +125,24 @@ pub(crate) fn parse_stream(frame: &[f32] /* length 192 */) -> Option<StreamFrame
             "LICH: received part {counter} part {part:?} from raw {:?}",
             &deinterleaved[0..12]
         );
             "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
     }
 }
 
     } 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 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 {
     let final_frame = (packet[25] & 0x80) > 0;
     let number = (packet[25] >> 2) & 0x1f;
     let counter = if final_frame {
@@ -151,10 +154,13 @@ pub(crate) fn parse_packet(frame: &[f32] /* length 192 */) -> Option<PacketFrame
             index: number as usize,
         }
     };
             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])> {
 }
 
 pub(crate) fn decode_lich(type2_bits: &[u8]) -> Option<(u8, [u8; 5])> {
index 94d280bb246563e734ff3cc89fc6226c42cd2573..0ba20d78b8d500d11e7bf499bc0e74f4ef1b481c 100644 (file)
@@ -113,7 +113,7 @@ mod tests {
         ]);
         let encoded = encode_lsf(&lsf);
         let decoded = crate::decode::parse_lsf(&encoded);
         ]);
         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]
     }
 
     #[test]
@@ -127,7 +127,7 @@ mod tests {
         };
         let encoded = encode_stream(&stream);
         let decoded = crate::decode::parse_stream(&encoded);
         };
         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]
     }
 
     #[test]
@@ -138,7 +138,7 @@ mod tests {
         };
         let encoded = encode_packet(&packet);
         let decoded = crate::decode::parse_packet(&encoded);
         };
         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 packet = PacketFrame {
             payload: [0u8; 25],
@@ -146,7 +146,7 @@ mod tests {
         };
         let encoded = encode_packet(&packet);
         let decoded = crate::decode::parse_packet(&encoded);
         };
         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]
     }
 
     #[test]
index 5a49f870d9fc92abc28002ec45dd0fa6d066ff67..3bf709d02d3b87c71b9c5b75e203b409ea227a85 100644 (file)
@@ -212,7 +212,7 @@ pub(crate) fn decode(
     type3: &[u8], // up to len 46
     input_len: usize,
     puncture: fn(usize) -> (bool, bool),
     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];
     let type3_bits = Bits::new(type3);
     let mut type3_iter = type3_bits.iter();
     let mut table = [[0u8; 32]; 244];
@@ -266,7 +266,7 @@ pub(crate) fn decode(
                 };
             }
         }
                 };
             }
         }
-        Some(out)
+        Some((out, *best))
     }
 }
 
     }
 }
 
@@ -332,7 +332,7 @@ mod tests {
         let encoded = encode(&lsf, 240, p_1);
         assert_eq!(encoded, expected_encoded);
         let decoded = decode(&encoded, 240, p_1);
         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]
     }
 
     #[test]
@@ -352,7 +352,7 @@ mod tests {
             if idx == 100 {
                 assert_eq!(decoded, None); // 7 bits is too much damage
             } else {
             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
             }
         }
     }
             }
         }
     }
index 255678fd97c2b6328962c376e729983a0f360878..e7f249fe1b88c2b43402029cf551c22bdd4158ec 100644 (file)
@@ -9,7 +9,11 @@ use crate::shaping::RRC_48K;
 use log::debug;
 
 pub trait Demodulator {
 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;
 }
 
     fn data_carrier_detect(&self) -> bool;
 }
 
@@ -67,7 +71,7 @@ impl SoftDemodulator {
 }
 
 impl Demodulator for SoftDemodulator {
 }
 
 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;
         self.filter_win[self.filter_cursor] = sample;
         self.filter_cursor = (self.filter_cursor + 1) % 81;
         let mut out: f32 = 0.0;
@@ -102,21 +106,21 @@ impl Demodulator for SoftDemodulator {
                 }
                 match c.burst {
                     SyncBurst::Lsf => {
                 }
                 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 => {
                         }
                     }
                     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 => {
                         }
                     }
                     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 => {
                         }
                     }
                     SyncBurst::Preamble | SyncBurst::EndOfTransmission => {
diff --git a/tools/m17rt-fastdemod/Cargo.toml b/tools/m17rt-fastdemod/Cargo.toml
new file mode 100644 (file)
index 0000000..18f08a1
--- /dev/null
@@ -0,0 +1,14 @@
+[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"
diff --git a/tools/m17rt-fastdemod/src/main.rs b/tools/m17rt-fastdemod/src/main.rs
new file mode 100644 (file)
index 0000000..bc14f71
--- /dev/null
@@ -0,0 +1,44 @@
+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(())
+}