diff --git a/config.example.toml b/config.example.toml
index 76ce98d821857eaaf68568e60293ccabcbeaf66f..4b1e964c480dfd761de57729da313f9897525a21 100644
--- a/config.example.toml
+++ b/config.example.toml
@@ -1,3 +1,18 @@
+# rtl_fm parameters
+frequency = 430_500_000 # center frequency, Hz
+device_id = 0 # ID of your RTL-SDR; 0 is default
+
+# if set to true:
+#  - expects 48k samples/second, 16-bit LE IQ.
+#  - will not start rtl_fm process
+#  - will ignore above settings
+# probably don't want to touch this unless you're bypassing rtl_fm for some reason
+use_stdin = false
+
+# max FSK deviation, Hz
+# if you don't know what this means, leave it as-is
+max_deviation = 15_000
+
 [felinet]
 address = "https://felinet.cats.radio"
 callsign = "CHANGEME"
diff --git a/src/config.rs b/src/config.rs
index af64d71550f0955cb5688a88c061183f49123721..15d9372f32d44780689b7e3647fe76d81ad28f21 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -30,6 +30,12 @@ pub struct FelinetConfig {
 
 #[derive(Deserialize, Clone)]
 pub struct Config {
+    pub frequency: u32,
+    #[serde(default)]
+    pub device_id: u32,
+    #[serde(default)]
+    pub use_stdin: bool,
+    pub max_deviation: i32,
     pub felinet: Option<FelinetConfig>,
 }
 
diff --git a/src/decoder/mod.rs b/src/decoder/mod.rs
index 97554aef8158afb16a86c92da0c62a7af568fac3..57ebe6c58a09bfcdaaf444fc20e21d35685ba99f 100644
--- a/src/decoder/mod.rs
+++ b/src/decoder/mod.rs
@@ -6,7 +6,7 @@ use uuid::Uuid;
 
 use crate::{
     codec2::{Complex, Fsk},
-    config::FelinetConfig,
+    config::Config,
     decoder::rtl::RtlSdr,
     felinet::PacketIn as SemiPacketIn,
     util::{append_internet_to_packet_route, print_packet},
@@ -19,11 +19,7 @@ mod demod;
 mod packet;
 mod rtl;
 
-pub fn decode_forever(
-    felinet_send: mpsc::Sender<SemiPacketIn>,
-    felinet_config: &Option<FelinetConfig>,
-    uuid: &Uuid,
-) {
+pub fn decode_forever(felinet_send: mpsc::Sender<SemiPacketIn>, config: &Config, uuid: &Uuid) {
     const USE_STDIN: bool = false;
 
     let bytes: Box<dyn Iterator<Item = u8>> = if USE_STDIN {
@@ -31,7 +27,7 @@ pub fn decode_forever(
 
         Box::new(stdin.bytes().map(|x| x.expect("Could not read from stdin")))
     } else {
-        let rtl = RtlSdr::new().unwrap();
+        let rtl = RtlSdr::new(config.frequency, config.device_id).unwrap();
         Box::new(rtl)
     };
 
@@ -51,7 +47,7 @@ pub fn decode_forever(
     demod.demod(|mut p| {
         print_packet(&p);
 
-        if let Some(fc) = felinet_config {
+        if let Some(fc) = &config.felinet {
             if append_internet_to_packet_route(&fc.callsign, fc.ssid, &mut p).is_none() {
                 return;
             };
diff --git a/src/decoder/rtl.rs b/src/decoder/rtl.rs
index 563d3b5bc673aa7f35a2949b2b62ef24345224fd..2285b05c8d229e51f27afd4f6deaa0d17b37f7e9 100644
--- a/src/decoder/rtl.rs
+++ b/src/decoder/rtl.rs
@@ -4,26 +4,19 @@ use anyhow::Context;
 use subprocess::{Communicator, Popen, PopenConfig, Redirection};
 
 pub struct RtlSdr {
+    process: Popen,
     comms: Communicator,
     out_buf: VecDeque<u8>,
     err_buf: Vec<u8>,
 }
 
 impl RtlSdr {
-    pub fn new() -> anyhow::Result<Self> {
-        let mut p = Popen::create(
+    pub fn new(freq: u32, device_id: u32) -> anyhow::Result<Self> {
+        let freq = format!("{freq}");
+        let device_id = format!("{device_id}");
+        let mut process = Popen::create(
             &[
-                "rtl_fm",
-                "-f",
-                "430500000",
-                "-M",
-                "raw",
-                "-F9",
-                "-p",
-                "0",
-                "-d",
-                "0",
-                "-s",
+                "rtl_fm", "-f", &freq, "-M", "raw", "-F9", "-p", "0", "-d", &device_id, "-s",
                 "48000",
             ],
             PopenConfig {
@@ -35,9 +28,10 @@ impl RtlSdr {
         )
         .context("Error starting rtl-fm")?;
 
-        let comms = p.communicate_start(None).limit_size(1024);
+        let comms = process.communicate_start(None).limit_size(1024);
 
         Ok(Self {
+            process,
             comms,
             out_buf: VecDeque::new(),
             err_buf: vec![],
@@ -49,28 +43,34 @@ impl Iterator for RtlSdr {
     type Item = u8;
 
     fn next(&mut self) -> Option<Self::Item> {
-        if let Some(b) = self.out_buf.pop_front() {
-            return Some(b);
-        }
+        while self.out_buf.is_empty() {
+            let (out, err) = self.comms.read().ok()?;
+            let out = out.expect("Missing stdout");
+            let err = err.expect("Missing stderr");
 
-        let (out, err) = self.comms.read().ok()?;
-        let out = out.expect("Missing stdout");
-        let err = err.expect("Missing stderr");
+            self.out_buf.extend(out.iter());
+            for e in err {
+                if e == b'\n' {
+                    handle_err(&self.err_buf);
+                    self.err_buf.clear();
+                } else {
+                    self.err_buf.push(e);
+                }
+            }
 
-        self.out_buf.extend(out.iter());
-        for e in err {
-            if e == b'\n' {
+            if self.process.poll().is_some() {
                 handle_err(&self.err_buf);
                 self.err_buf.clear();
-            } else {
-                self.err_buf.push(e);
+                return None;
             }
         }
 
-        self.out_buf.pop_front()
+        Some(self.out_buf.pop_front().unwrap())
     }
 }
 
 fn handle_err(err: &[u8]) {
-    eprintln!("[rtl_fm] {}", String::from_utf8_lossy(err));
+    if !err.is_empty() {
+        eprintln!("[rtl_fm] {}", String::from_utf8_lossy(err));
+    }
 }
diff --git a/src/main.rs b/src/main.rs
index 8d8a75489fb3327f51211a9be72c87996343ba9a..c54d67114adf3583b4536c7c2fe9efef95929d19 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -72,7 +72,7 @@ async fn gate_forever(config: Config) -> anyhow::Result<()> {
     }
 
     tokio::task::spawn_blocking(move || {
-        decoder::decode_forever(tx, &config.felinet, &uuid);
+        decoder::decode_forever(tx, &config, &uuid);
     })
     .await?;