]> code.octet-stream.net Git - m17rt/blob - m17app/src/rtlsdr.rs
Fix timing bugs and add documentation
[m17rt] / m17app / src / rtlsdr.rs
1 use std::{
2 io::Read,
3 process::{Child, Command, Stdio},
4 sync::{mpsc::SyncSender, Mutex},
5 };
6
7 use crate::{
8 error::M17Error,
9 soundmodem::{InputSource, SoundmodemEvent},
10 };
11
12 pub struct RtlSdr {
13 frequency_mhz: f32,
14 device_index: usize,
15 rtlfm: Mutex<Option<Child>>,
16 }
17
18 impl RtlSdr {
19 pub fn new(device_index: usize, frequency_mhz: f32) -> Result<Self, M17Error> {
20 Ok(Self {
21 device_index,
22 frequency_mhz,
23 rtlfm: Mutex::new(None),
24 })
25 }
26 }
27
28 impl InputSource for RtlSdr {
29 fn start(&self, tx: SyncSender<SoundmodemEvent>) {
30 // TODO: error handling
31 let mut cmd = Command::new("rtl_fm")
32 .args([
33 "-E",
34 "offset",
35 "-f",
36 &format!("{:.6}M", self.frequency_mhz),
37 "-d",
38 &self.device_index.to_string(),
39 "-s",
40 "48k",
41 ])
42 .stdout(Stdio::piped())
43 .spawn()
44 .unwrap();
45 let mut stdout = cmd.stdout.take().unwrap();
46 let mut buf = [0u8; 1024];
47 let mut leftover: Option<u8> = None;
48 std::thread::spawn(move || {
49 while let Ok(n) = stdout.read(&mut buf) {
50 let mut start_idx = 0;
51 let mut samples = vec![];
52 if let Some(left) = leftover {
53 if n > 0 {
54 samples.push(i16::from_le_bytes([left, buf[0]]));
55 start_idx = 1;
56 leftover = None;
57 }
58 }
59 for sample in buf[start_idx..n].chunks(2) {
60 if sample.len() == 2 {
61 samples.push(i16::from_le_bytes([sample[0], sample[1]]))
62 } else {
63 leftover = Some(sample[0]);
64 }
65 }
66 if tx
67 .send(SoundmodemEvent::BasebandInput(samples.into()))
68 .is_err()
69 {
70 break;
71 }
72 }
73 });
74 *self.rtlfm.lock().unwrap() = Some(cmd);
75 }
76
77 fn close(&self) {
78 if let Some(mut process) = self.rtlfm.lock().unwrap().take() {
79 let _ = process.kill();
80 }
81 }
82 }