]> code.octet-stream.net Git - m17rt/blobdiff - m17app/src/rtlsdr.rs
RTLSDR input via rtl_fm
[m17rt] / m17app / src / rtlsdr.rs
diff --git a/m17app/src/rtlsdr.rs b/m17app/src/rtlsdr.rs
new file mode 100644 (file)
index 0000000..33f8070
--- /dev/null
@@ -0,0 +1,90 @@
+use std::{
+    io::Read,
+    process::{Child, Command, Stdio},
+    sync::{
+        mpsc::{sync_channel, Receiver, SyncSender},
+        Arc, Mutex, RwLock,
+    },
+    time::{Duration, Instant},
+};
+
+use cpal::{
+    traits::{DeviceTrait, HostTrait, StreamTrait},
+    SampleFormat, SampleRate, Stream,
+};
+
+use crate::{
+    error::M17Error,
+    soundmodem::{InputSource, OutputBuffer, OutputSink, SoundmodemEvent},
+};
+
+pub struct RtlSdr {
+    frequency_mhz: f32,
+    device_index: usize,
+    rtlfm: Mutex<Option<Child>>,
+}
+
+impl RtlSdr {
+    pub fn new(device_index: usize, frequency_mhz: f32) -> Result<Self, M17Error> {
+        Ok(Self {
+            device_index,
+            frequency_mhz,
+            rtlfm: Mutex::new(None),
+        })
+    }
+}
+
+impl InputSource for RtlSdr {
+    fn start(&self, tx: SyncSender<SoundmodemEvent>) {
+        // TODO: error handling
+        let mut cmd = Command::new("rtl_fm")
+            .args([
+                "-E",
+                "offset",
+                "-f",
+                &format!("{:.6}M", self.frequency_mhz),
+                "-d",
+                &self.device_index.to_string(),
+                "-s",
+                "48k",
+            ])
+            .stdout(Stdio::piped())
+            .spawn()
+            .unwrap();
+        let mut stdout = cmd.stdout.take().unwrap();
+        let mut buf = [0u8; 1024];
+        let mut leftover: Option<u8> = None;
+        std::thread::spawn(move || {
+            while let Ok(n) = stdout.read(&mut buf) {
+                let mut start_idx = 0;
+                let mut samples = vec![];
+                if let Some(left) = leftover {
+                    if n > 0 {
+                        samples.push(i16::from_le_bytes([left, buf[0]]));
+                        start_idx = 1;
+                        leftover = None;
+                    }
+                }
+                for sample in buf[start_idx..n].chunks(2) {
+                    if sample.len() == 2 {
+                        samples.push(i16::from_le_bytes([sample[0], sample[1]]))
+                    } else {
+                        leftover = Some(sample[0]);
+                    }
+                }
+                if tx
+                    .send(SoundmodemEvent::BasebandInput(samples.into()))
+                    .is_err()
+                {
+                    break;
+                }
+            }
+        });
+    }
+
+    fn close(&self) {
+        if let Some(mut process) = self.rtlfm.lock().unwrap().take() {
+            let _ = process.kill();
+        }
+    }
+}