RTL-SDR Guide
RemoteRF supports RTL-SDR devices, widely popular low-cost software-defined radio receivers based on the Realtek RTL2832U demodulator and Rafael Micro R820T/R820T2 (or compatible) tuners. The RTL-SDR is an RX-only device (no transmitter) capable of wideband reception typically spanning ~24 MHz to ~1.766 GHz with stable sampling rates up to 2.4 MSPS (8-bit ADC resolution). This guide covers how to initialize, configure, and stream baseband IQ signals with a reserved RTL-SDR through RemoteRF using Python.
Device Initialization
To interface with an RTL-SDR through RemoteRF, import the device driver from the remoterf library and initialize an rtlsdr object with the token issued upon making your reservation.
This token is used by the RemoteRF platform to validate your reservation and route subsequent interfacing to the correct RTL-SDR corresponding to that reservation.
from remoterf.drivers.rtl_sdr import RtlSdr
rtlsdr = RtlSdr(token='dUx6lG3I9lA') # token issued when making a reservationOnce initialized, the rest of your Python script follows standard PyRtlSdr usage, exactly as if connected locally via USB.
Example Script
The Python script below demonstrates how to configure an RTL-SDR and capture baseband signals with it via the RemoteRF platform.
Because the RTL-SDR is a receive-only device, it is dedicated exclusively to wideband RF reception and spectrum monitoring.
In this example, the RTL-SDR is tuned to 915 MHz with a 2.048 MSPS sample rate and captures a block of 131,072 complex baseband samples using rtlsdr.read_samples().
The FFT of these received samples is then plotted to inspect the RF frequency spectrum around the center frequency.
Note that the device initialization lines described above have already been included in the code below. You should not need to change any of the code below except for the token you received upon making a reservation.
import numpy as np
import matplotlib.pyplot as plt
# token provided when making reservation
TOKEN = "dUx6lG3I9lA"
# the below code can also be used to ensure you have the most
# up-to-date drivers pulled from the RemoteRF server
# from remoterf.drivers import ensure_driver
# ensure_driver(token=TOKEN)
# for interfacing with RTL-SDR through RemoteRF
from remoterf.drivers.rtl_sdr import RtlSdr
# ---------------------------------------------------------------
# RTL-SDR system parameters.
# ---------------------------------------------------------------
sample_rate = 2.048e6 # baseband sampling rate in Hz (stable: 1.024, 2.048, or 2.4 MSps)
center_freq_Hz = 915e6 # center RF frequency in Hz (~24 MHz to ~1.766 GHz)
gain_setting = 20.7 # tuner gain in dB (or 'auto' for hardware AGC)
num_samples = 131_072 # number of complex samples to capture (multiples of 512 / powers of 2)
# ---------------------------------------------------------------
# Initialize RTL-SDR object using issued token.
# ---------------------------------------------------------------
rtlsdr = RtlSdr(token=TOKEN) # create RTL-SDR object
# Configure Tuner & Sampling Parameters
rtlsdr.sample_rate = int(sample_rate) # set baseband sampling rate
rtlsdr.center_freq = int(center_freq_Hz) # set center tuning frequency
rtlsdr.gain = gain_setting # set tuner gain in dB (or 'auto')
# ---------------------------------------------------------------
# Receive with RTL-SDR!
# ---------------------------------------------------------------
try:
rtlsdr.reset_buffer() # flush stale samples from buffer to be safe
except Exception:
pass
rx_signal = rtlsdr.read_samples(num_samples=num_samples) # capture raw samples from RTL-SDR
# ---------------------------------------------------------------
# Clean up device once done receiving.
# ---------------------------------------------------------------
rtlsdr.close() # safely end use
# ---------------------------------------------------------------
# Take FFT of received signal.
# ---------------------------------------------------------------
rx_fft = np.abs(np.fft.fftshift(np.fft.fft(rx_signal)))
f = np.linspace(-sample_rate / 2, sample_rate / 2, len(rx_fft))
plt.figure(figsize=(9, 5))
plt.plot(f / 1e3, rx_fft, color="tab:purple")
plt.xlabel("Frequency (kHz)")
plt.ylabel("Magnitude")
plt.title(f"FFT of Received Signal (RTL-SDR @ {center_freq_Hz / 1e6:.1f} MHz)")
plt.grid(True)
plt.show()Properties and Methods
When interacting with an RTL-SDR through RemoteRF, client scripts communicate with an rtlsdr object generated dynamically from RemoteRF’s built-in rtl_sdr schema. This interface exposes a curated, schema-exposed subset of PyRtlSdr over the remote RPC connection.
Properties
| Property | Type | Access | Range / Options | Description |
|---|---|---|---|---|
sample_rate |
int |
Read / Write | 225 kHz to 3.2 MHz | Complex baseband sampling rate in samples per second. Stable rates without sample loss are typically 1.024 MSPS, 2.048 MSPS, and 2.4 MSPS. |
center_freq |
int |
Read / Write | 24 MHz to 1.766 GHz | Center RF tuning frequency in Hz (exact frequency range depends on the installed tuner model). |
gain |
float / str |
Read / Write | Discrete dB values or 'auto' |
Tuner frontend gain in dB, or 'auto' for automatic hardware gain control (AGC). Supported discrete gains can be queried via valid_gains_db. |
freq_correction |
int |
Read / Write | Frequency offset in PPM | Frequency correction factor in parts-per-million (PPM) to compensate for crystal oscillator offset. |
tuner_type |
str |
Read-only | e.g., 'RTLSDR_TUNER_R820T' |
Reports the detected tuner IC hardware model. |
valid_gains_db |
list[float] |
Read-only | List of floats in dB | List of discrete gain values in dB supported by the active tuner frontend. |
bandwidth |
int |
Read / Write | Bandwidth in Hz (0 for auto) |
Analog baseband filter bandwidth in Hz (supported on select tuners). Set to 0 for automatic selection based on sample rate. |
direct_sampling |
int |
Read / Write | 0 (off), 1 (I), 2 (Q) |
Direct sampling mode setting to bypass the tuner for HF reception below 28 MHz. |
offset_tuning |
bool |
Read / Write | True, False |
Enables or disables offset tuning to avoid the DC center spike (supported on tuners like E4000/FC0012/FC0013). |
Methods
| Method | Arguments | Returns | Description |
|---|---|---|---|
rtlsdr.read_samples(num_samples=131072) |
num_samples (int, optional) |
numpy.ndarray (complex64) |
Synchronously reads a bounded array of complex IQ samples. Sample values are normalized complex numbers with magnitudes between −1.0 and +1.0. |
rtlsdr.read_bytes(num_bytes) |
num_bytes (int) |
bytes |
Synchronously reads raw, packed unsigned 8-bit interleaved IQ bytes ([I0, Q0, I1, Q1, ...]) with values from 0 to 255 (centered at 127.5). |
rtlsdr.reset_buffer() |
None | None |
Flushes and resets internal USB sample buffers to discard stale IQ data before starting a capture. |
rtlsdr.close() |
None | None |
Safely closes the client session and releases remote resources without terminating server management. |
rtlsdr.get_tuner_type() |
None | str |
Queries and returns the detected tuner hardware identifier string. |
rtlsdr.get_gains() |
None | list[float] |
Queries and returns the list of discrete tuner gain values in dB supported by the hardware. |
rtlsdr.set_freq_correction(ppm) |
ppm (int) |
None |
Sets the crystal frequency correction offset in parts-per-million. |
Tips and Tricks
Common Configuration Template
For quick reference, the following template brings together the most commonly configured RF, filter, buffer, and gain properties for an RTL-SDR session:
# Common RTL-SDR Configuration Template
rtlsdr.sample_rate = int(2.048e6) # baseband sampling rate in Hz (stable: 1.024, 2.048, or 2.4 MSps)
rtlsdr.center_freq = int(915e6) # center RF frequency in Hz (~24 MHz to ~1.766 GHz)
rtlsdr.gain = 20.7 # tuner gain in dB (or 'auto' for hardware AGC)Receive-Only Architecture (No Transmission)
Unlike the ADALM-PLUTO or HackRF, the RTL-SDR is an inexpensive, receive-only software-defined radio based on the Realtek RTL2832U demodulator. It has no digital-to-analog converter (DAC) or power amplifier and cannot transmit RF signals. It is dedicated exclusively to wideband RF reception, spectrum monitoring, and signal analysis.
Supported Sample Rates and Avoiding Dropped Samples
The RTL2832U ADC natively operates at 28.8 MHz and decimates down to your requested sample rate. Recommended, stable sample rates include:
1.024e6(1.024 MSPS)2.048e6(2.048 MSPS)2.4e6(2.4 MSPS)
Rates below 1 MSPS can exhibit filter distortion or alias artifacts, while rates exceeding 2.4 MSPS (up to ~3.2 MSPS theoretical limit) frequently experience USB bus buffer overflow and dropped samples. Power-of-two buffer sizes or multiples of 512 (such as 131_072 or 262_144 samples) provide optimal DMA transfer efficiency.
Tuner Types and Frequency Coverage
Most modern RTL-SDR dongles (such as the RTL-SDR Blog V3/V4 or generic DVB-T dongles) use the Rafael Micro R820T or R820T2 tuner, covering roughly 24 MHz to 1.766 GHz. Other tuner variants include the Elonics E4000 (52–2200 MHz with a gap near 1100–1250 MHz) and Fitipower FC0012/FC0013. You can inspect your reserved dongle’s tuner model using rtlsdr.tuner_type.
Tuner Gain and AGC Selection
RTL-SDR tuners support discrete gain steps rather than continuous adjustments. You can inspect all available gain values with rtlsdr.valid_gains_db (or rtlsdr.get_gains()). Setting rtlsdr.gain = 'auto' enables the RTL2832U hardware automatic gain control (AGC), which is convenient for general monitoring but may cause amplitude fluctuations during quantitative signal measurements. For repeatable measurements, select a fixed numerical gain from the supported list (e.g., 20.7 dB).
DC Offset and the Center Spike
Because the RTL-SDR uses direct conversion (zero-IF) or low-IF architectures depending on the tuner, a sharp DC offset spike often appears at the exact center frequency (0 Hz baseband) in FFT plots. To inspect a signal of interest cleanly, tune the center frequency slightly off to the side (e.g., 250 kHz higher or lower) so the signal falls outside the central DC carrier null.
Flushing the Receive Buffer
Always call rtlsdr.reset_buffer() before capturing data to flush residual samples accumulated from prior operations:
try:
rtlsdr.reset_buffer() # flush stale samples from buffer to be safe
except Exception:
pass
rx_signal = rtlsdr.read_samples(num_samples=131072)Connecting to Multiple RTL-SDRs at Once
Multiple RTL-SDR devices can be reserved concurrently for multi-channel monitoring (e.g., monitoring different frequency bands simultaneously):
rtlsdr1 = RtlSdr(token='dUx6lG3I9lA') # first RTL-SDR (e.g., tuned to 915 MHz)
rtlsdr2 = RtlSdr(token='ajBMv80jMog') # second RTL-SDR (e.g., tuned to 100 MHz FM)Safely Closing the Device
Always call rtlsdr.close() at the end of your script to safely disconnect and release server resources:
rtlsdr.close() # safely end use