librtlsdr-rs
Pure-Rust port of librtlsdr. Talks to RTL2832U-based DVB-T dongles
directly over USB via rusb — no C librtlsdr library, no headers,
no pkg-config. Covers all five tuner families shipped in real-world
dongles: R820T / R820T2 / R828D, E4000, FC0012, FC0013, FC2580.
Quick start
use ;
Sample values are interleaved unsigned 8-bit I/Q pairs, the native
RTL-SDR format. Convert to centred i8 (or f32 in [-1, 1]) at the
consumer if needed.
Streaming
For long-running capture, the Reader split lets one half of the device stream samples on a worker thread while the other half retains control of tuning, gain, and bias-T:
use RtlSdrDevice;
#
Async runtime support
Per-runtime async Stream adapters are gated behind cargo features —
no async runtime is pulled in by default.
| Feature | Method | Backend |
|---|---|---|
tokio |
RtlSdrReader::stream_samples_tokio |
tokio::task::spawn_blocking + tokio::sync::mpsc |
smol |
RtlSdrReader::stream_samples_smol |
blocking::unblock + async-channel |
async-std users — please migrate to smol (the upstream-recommended replacement now that async-std is unmaintained per RUSTSEC-2025-0052). The smol feature uses the same
blocking::unblockprimitive async-std previously offered.
[]
= { = "0.1", = ["tokio"] }
Public surface
The committed surface is intentionally narrow:
RtlSdrDevice— the device handle. Open viaRtlSdrDevice::openorRtlSdrDeviceBuilder.RtlSdrReader— streaming-focused handle (device.reader()).list_devices/get_device_count/get_device_name/get_device_usb_strings/get_index_by_serial— enumeration helpers.RtlSdrError— unified error type returned by every fallible operation.TunerType— IC family identifier.
Linux permissions
On Linux you typically need a udev rule so the dongle is accessible without root:
# /etc/udev/rules.d/20-rtlsdr.rules
SUBSYSTEM=="usb", ATTRS{idVendor}=="0bda", ATTRS{idProduct}=="2838", MODE="0666"
SUBSYSTEM=="usb", ATTRS{idVendor}=="0bda", ATTRS{idProduct}=="2832", MODE="0666"
Reload with sudo udevadm control --reload-rules && sudo udevadm trigger,
then unplug + replug the dongle.
If the kernel's DVB driver auto-binds to the dongle, blacklist it:
# /etc/modprobe.d/blacklist-dvb_usb_rtl28xxu.conf
blacklist dvb_usb_rtl28xxu
Why a pure-Rust port?
- No C build dependency. Build cleanly on any rustc target that
supports
rusb— nopkg-config, noapt install librtlsdr-dev, no Windows DLL-shipping headaches. - Cross-platform USB.
rusbworks on Linux, macOS, and Windows with the same API. - Rust-native API. Owned types, real error enums, optional per-runtime async streaming.
- Faithful behavior. Register addresses, gain tables, and tuner initialisation sequences are transcribed directly from upstream librtlsdr — same hardware, same numbers, same expectations.
Live hardware tests
A handful of integration tests exercise real USB I/O. They're
#[ignore] by default. Plug in a dongle and run the suite for
whichever async runtime you ship against:
# tokio Stream variant
# smol Stream variant
--test-threads=1 is important: cargo's default parallel
test runner has multiple threads each call RtlSdrDevice::open(0)
and only one wins the USB-interface claim — the rest see
Resource busy and skip via open_or_skip's defensive fallback.
Without --test-threads=1 the suite reports 5 passed even
though only 1 test actually exercised hardware. The skip messages
do point at this when they fire (per audit issue #31).
Both test files mirror the same three scenarios (smoke, parent- retunes-during-stream, dropping-stream-stops-worker) so the runtime backends stay at parity. Neither runs in CI (no hardware).
License
GPL-2.0-or-later. The upstream librtlsdr C source is GPL-2.0-or-later; since this crate is a faithful port (the register tables and tuner init sequences are transcribed directly), it inherits the same license. See LICENSE and NOTICE.