m17app 0.1.0

M17 digital radio - high level API, integration with sound cards, serial PTT and TCP/IP
Documentation
use std::{
    io::Read,
    process::{Child, Command, Stdio},
    sync::{mpsc::SyncSender, Mutex},
};

use crate::{
    error::M17Error,
    soundmodem::{InputSource, 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;
                }
            }
        });
        *self.rtlfm.lock().unwrap() = Some(cmd);
    }

    fn close(&self) {
        if let Some(mut process) = self.rtlfm.lock().unwrap().take() {
            let _ = process.kill();
        }
    }
}