RtlSdrManager 0.8.0
See the version list below for details.
dotnet add package RtlSdrManager --version 0.8.0
NuGet\Install-Package RtlSdrManager -Version 0.8.0
<PackageReference Include="RtlSdrManager" Version="0.8.0" />
<PackageVersion Include="RtlSdrManager" Version="0.8.0" />
<PackageReference Include="RtlSdrManager" />
paket add RtlSdrManager --version 0.8.0
#r "nuget: RtlSdrManager, 0.8.0"
#:package RtlSdrManager@0.8.0
#addin nuget:?package=RtlSdrManager&version=0.8.0
#tool nuget:?package=RtlSdrManager&version=0.8.0
RTL-SDR Manager Library for .NET
A modern .NET library for managing RTL-SDR devices
RTL-SDR Manager provides a high-level, type-safe API for controlling RTL2832U-based software-defined radio devices from .NET applications. The library handles device lifecycle, tuner configuration, and sample acquisition with support for synchronous and asynchronous operations, multiple simultaneous devices, and advanced features such as KerberosSDR coherent arrays.
Features
Async/Await Support — Non-blocking sample reading with concurrent queue buffering for real-time signal processing.
Multiple Tuner Support — Works with E4000, R820T/R828D, FC0012, FC0013, and FC2580 tuner chips.
Advanced Configuration — Gain control, frequency correction (PPM), direct sampling for HF reception, and bias tee power control.
KerberosSDR Ready — Frequency dithering and GPIO control for coherent SDR arrays used in direction finding.
Type-Safe Frequency API — Strongly-typed
Frequencyvalues with factory methods (FromHz,FromKHz,FromMHz,FromGHz) and arithmetic operations.Cross-Platform — Runs on Windows, Linux, and macOS via platform-specific native interop.
High Performance — Uses
LibraryImportsource-generated P/Invoke for optimal native library calls.Production Ready — Proper exception handling,
IDisposablepatterns, null safety, and scoped console output suppression.
Installation
Via NuGet Package Manager
# .NET CLI
dotnet add package RtlSdrManager
# Package Manager Console (Visual Studio)
Install-Package RtlSdrManager
# PackageReference (in .csproj)
<PackageReference Include="RtlSdrManager" Version="0.8.0" />
Prerequisites
The librtlsdr native library must be installed on the system. Any 2.x release works,
but 2.0.3 or later is recommended: earlier versions can block indefinitely inside the
native reader when a device is unplugged during an asynchronous reading, so the failure
surfaces as a stop timeout from StopReadSamplesAsync() rather than as the underlying
error (see Asynchronous Sample Reading).
Windows:
# Using Chocolatey
choco install rtl-sdr
# Or download from: https://github.com/osmocom/rtl-sdr/releases
Linux (Ubuntu/Debian):
sudo apt-get install librtlsdr-dev
macOS:
brew install librtlsdr
Quick Start
Basic Usage
using RtlSdrManager;
using RtlSdrManager.Modes;
// Get the singleton device manager
var manager = RtlSdrDeviceManager.Instance;
// Check available devices
Console.WriteLine($"Found {manager.CountDevices} RTL-SDR device(s)");
// Open the first device with a friendly name
manager.OpenManagedDevice(0, "my-rtl-sdr");
// Configure the device
var device = manager["my-rtl-sdr"];
device.CenterFrequency = Frequency.FromMHz(1090); // ADS-B frequency
device.SampleRate = Frequency.FromMHz(2);
device.TunerGainMode = TunerGainModes.AGC;
device.AGCMode = AGCModes.Enabled;
device.ResetDeviceBuffer();
Console.WriteLine($"Tuner: {device.TunerType}");
Console.WriteLine($"Center Frequency: {device.CenterFrequency.MHz} MHz");
Console Output Suppression
By default, librtlsdr diagnostic messages (such as "Found Rafael Micro R820T tuner" and "[R82XX] PLL not locked!") are shown on stdout/stderr. The library provides a global static property to suppress these messages during device operations:
// Suppress librtlsdr diagnostic output globally
RtlSdrDeviceManager.SuppressLibraryConsoleOutput = true;
// Device operations are now silent
manager.OpenManagedDevice(0, "my-rtl-sdr");
var device = manager["my-rtl-sdr"];
device.SampleRate = Frequency.FromMHz(2.4);
// Re-enable output
RtlSdrDeviceManager.SuppressLibraryConsoleOutput = false;
device.CenterFrequency = Frequency.FromMHz(1090); // Shows librtlsdr messages
Suppression is scoped to individual device operations using reference-counted file descriptor redirection. Stdout and stderr are redirected to /dev/null (Unix/macOS) or NUL (Windows) only for the duration of each native call, then restored. This prevents interference with console applications and avoids file descriptor corruption when multiple devices operate concurrently.
For detailed documentation, see Console Output Suppression.
Synchronous Sample Reading
// Read samples synchronously (blocking)
var samples = device.ReadSamples(256 * 1024);
foreach (var sample in samples)
{
// Process I/Q samples
Console.WriteLine($"I: {sample.I}, Q: {sample.Q}");
}
Asynchronous Sample Reading
// Configure async buffer
device.MaxAsyncBufferSize = 512 * 1024;
device.DropSamplesOnFullBuffer = true;
// Start async reading in background
device.StartReadSamplesAsync();
// Option 1: Event-based (recommended for real-time processing)
device.SamplesAvailable += (sender, args) =>
{
var samples = device.GetSamplesFromAsyncBuffer(args.SampleCount);
// Process samples in real-time
};
// Option 2: Manual polling (for custom processing logic)
while (running)
{
var samples = device.GetSamplesFromAsyncBuffer(16 * 1024);
if (samples.Count > 0)
{
// Process the batch of I/Q samples
Console.WriteLine($"Received {samples.Count} samples");
}
else
{
await Task.Delay(100);
}
}
// Stop reading when done
device.StopReadSamplesAsync();
// Clean up
manager.CloseManagedDevice("my-rtl-sdr");
If the reading stops on its own — a full buffer with DropSamplesOnFullBuffer disabled, a
throwing SamplesAvailable handler, or a device failure — the error is captured and
rethrown by StopReadSamplesAsync(), and stays observable via AsyncReadException until
the next StartReadSamplesAsync().
Unplugging a device mid-reading belongs to that last case, but how it surfaces depends on
the native library. With librtlsdr 2.0.3 or later, the native reader returns promptly and
you get the underlying error. With earlier versions it can block indefinitely instead, and
StopReadSamplesAsync() gives up after five seconds with a stop-timeout error — the device
is then left untouched on purpose, so no native callback runs against freed state.
Raw Buffer Mode (Zero-Copy)
For high-throughput applications, raw buffer mode eliminates per-sample object allocation by
delivering raw byte buffers directly from the native callback via ArrayPool<byte>:
Choosing a read mode
Asynchronous reading offers two delivery modes, selected by UseRawBufferMode (default false).
The mode is captured once when StartReadSamplesAsync is called, so set it beforehand; toggling
it mid-stream has no effect until the next start.
Default (UseRawBufferMode = false) |
Raw buffer mode (UseRawBufferMode = true) |
|
|---|---|---|
| Delivery | per-sample IQData structs via ConcurrentQueue<IQData> |
pooled byte[] buffers via Channel |
| Consume with | GetSamplesFromAsyncBuffer(...) → .I / .Q |
GetRawSamplesFromAsyncBuffer() → raw bytes |
| Ease of use | high — no buffer lifetime to manage | manual — must call buffer.Return() exactly once |
| Per-sample overhead | enqueue/dequeue + per-slot bookkeeping | none (one memcpy per buffer) |
| Best for | moderate sample rates, simplicity | high sample rates, minimal GC pressure |
Since v0.7.1 the default mode stores each IQData as two bytes internally (the .I / .Q
accessors stay int), so it is already compact; raw buffer mode remains the choice when you
want to avoid per-sample handling entirely.
// Enable raw buffer mode before starting async reading
device.UseRawBufferMode = true;
device.MaxAsyncBufferSize = 512 * 1024;
device.DropSamplesOnFullBuffer = true;
device.StartReadSamplesAsync(requestedSamples: 131072);
device.SamplesAvailable += (sender, args) =>
{
var buffer = device.GetRawSamplesFromAsyncBuffer();
if (buffer == null) return;
try
{
// Access raw interleaved I/Q bytes: [I0, Q0, I1, Q1, ...]
ReadOnlySpan<byte> raw = buffer.Data.AsSpan(0, buffer.ByteLength);
for (int i = 0; i < buffer.ByteLength; i += 2)
{
byte iSample = raw[i];
byte qSample = raw[i + 1];
// Process sample pair...
}
}
finally
{
buffer.Return(); // Return pooled buffer — must be called exactly once
}
};
// Stop reading when done
device.StopReadSamplesAsync();
Manual Gain Control
// Switch to manual gain mode
device.TunerGainMode = TunerGainModes.Manual;
// Get supported gain values
var gains = device.SupportedTunerGains;
Console.WriteLine($"Supported gains: {string.Join(", ", gains)} dB");
// Set specific gain
device.TunerGain = 42.1; // dB
// Or use convenience methods
device.SetMaximumTunerGain();
device.SetMinimumTunerGain();
Frequency Operations
// Create frequencies with different units
var freq1 = Frequency.FromHz(1090_000_000);
var freq2 = Frequency.FromKHz(1090_000);
var freq3 = Frequency.FromMHz(1090);
var freq4 = Frequency.FromGHz(1.09);
// Convert between units
Console.WriteLine($"{freq1.Hz} Hz");
Console.WriteLine($"{freq1.KHz} KHz");
Console.WriteLine($"{freq1.MHz} MHz");
Console.WriteLine($"{freq1.GHz} GHz");
// Arithmetic operations
var shifted = freq1 + Frequency.FromKHz(100); // Add 100 KHz offset
var doubled = freq1 * 2;
// Comparison
if (freq1 > Frequency.FromMHz(100))
{
Console.WriteLine("Above 100 MHz");
}
Advanced Features
Bias Tee (for powering external LNAs)
// Enable bias tee on GPIO 0 (most common)
device.SetBiasTee(BiasTeeModes.Enabled);
// Or target a specific GPIO pin (0..7), on any tuner
device.SetBiasTeeGPIO(gpio: 1, BiasTeeModes.Enabled);
// The bias tee stays powered after the device is closed, so turn it off explicitly
device.SetBiasTee(BiasTeeModes.Disabled);
Note: GPIO pins 4 and 6 are reserved. Pin 4 is pulsed to reset the tuner while the device is opened, and pin 6 selects the band filter on FC0012 tuners. They are not blocked, but using them as a bias tee control will produce confusing behavior.
Direct Sampling (HF reception)
// Enable direct sampling on I-ADC, before choosing a frequency
device.DirectSamplingMode = DirectSamplingModes.InPhaseADCInputEnabled;
// Or on Q-ADC
device.DirectSamplingMode = DirectSamplingModes.QuadratureADCInputEnabled;
// The reachable range is now the ADC's: 0 Hz to half the crystal frequency,
// which is 0 - 14.4 MHz on the usual 28.8 MHz crystal
Console.WriteLine(string.Join(" and ", device.SupportedFrequencyRanges));
device.CenterFrequency = Frequency.FromMHz(7.1); // 40 m amateur band
// Disable, then set a frequency the tuner can reach
device.DirectSamplingMode = DirectSamplingModes.Disabled;
device.CenterFrequency = Frequency.FromMHz(145);
Frequencies above half the crystal are received by aliasing rather than tuned to directly: subtract the wanted frequency from the crystal frequency. See Direct Sampling for the detail.
Frequency Correction (PPM)
// Set frequency correction in PPM
device.FrequencyCorrection = 10; // +10 PPM
KerberosSDR Support
// Enable KerberosSDR mode (required for these features)
device.KerberosSDRMode = KerberosSDRModes.Enabled;
// Enable frequency dithering (for R820T only)
device.FrequencyDitheringMode = FrequencyDitheringModes.Enabled;
// Control GPIO pins directly
device.SetGPIO(gpio: 1, GPIOModes.Enabled);
Documentation
Detailed guides for specific use cases:
- Basic Setup — Device initialization and first sample acquisition
- Device Management — Managing multiple RTL-SDR devices simultaneously
- Manual Gain Control — Configuring tuner gain settings
- Direct Sampling — Using direct sampling modes for HF reception
- Frequency Correction — PPM calibration and frequency correction
- Bias Tee — Powering external LNAs via bias tee
- KerberosSDR — Coherent SDR array features
- Console Output Suppression — Controlling native library diagnostic output
Sample Applications
The samples/ directory contains complete working examples:
- Demo1 — Event-based async sample reading
- Demo2 — Manual polling from async buffer
- Demo3 — Synchronous sample reading
- Demo4 — Device information and configuration
- Demo5 — Raw buffer mode for zero-copy sample processing
Building from Source
Requirements
- .NET 10.0 SDK or later
- librtlsdr native library
Build Commands
# Clone the repository
git clone https://github.com/nandortoth/rtlsdr-manager.git
cd rtlsdr-manager
# Build the entire solution
dotnet build
# Run the test suite
dotnet test
# Verify behavior that needs a real device (attach a dongle first)
dotnet run --project tools/HwVerify
# Create NuGet packages
dotnet pack --configuration Release
# Or use the convenience script
./build.sh
The test suite covers only hardware-independent components, so it needs no dongle and no
librtlsdr installation. Everything that depends on a device is checked by tools/HwVerify
instead, which opens the first device, prints PASS/FAIL/SKIP per check, and exits
nonzero if anything failed. See the Contributing Guide
for what it changes on the device and how to add checks.
Build Output
The build process creates:
- NuGet packages —
artifacts/packages/ - Library binaries —
artifacts/binaries/RtlSdrManager/ - Sample binaries —
artifacts/binaries/Samples/
Running Samples
# Using the convenience script
./runsample.sh
# Or manually
dotnet run --project samples/RtlSdrManager.Samples
Architecture
rtlsdr-manager/
├── src/
│ └── RtlSdrManager/ # Main library
│ ├── Exceptions/ # Custom exception types
│ ├── Hardware/ # Hardware type definitions
│ ├── Interop/ # P/Invoke wrappers
│ └── Modes/ # Enumeration types
├── tests/
│ └── RtlSdrManager.Tests/ # xUnit test suite
├── tools/
│ └── HwVerify/ # Hardware verification harness
├── samples/
│ └── RtlSdrManager.Samples/ # Example applications
└── docs/ # Documentation
System Requirements
- .NET Runtime — 10.0 or later
- Operating System — Windows, Linux, macOS
- Hardware — RTL-SDR compatible device (RTL2832U-based)
- Native Library — librtlsdr installed on the system (2.x; 2.0.3 or later recommended)
Supported Devices
This library supports RTL-SDR devices with the following tuners:
| Tuner | Frequency Range | Notes |
|---|---|---|
| Elonics E4000 | 52 -- 2200 MHz | No longer manufactured. Often cannot lock near 1100 -- 1250 MHz; the exact gap varies by device |
| Rafael Micro R820T | 24 -- 1766 MHz | Most common, excellent performance |
| Rafael Micro R828D | 24 -- 1766 MHz | Similar to R820T |
| Fitipower FC0012 | 22 -- 948.6 MHz | Basic performance |
| Fitipower FC0013 | 22 -- 1100 MHz | Basic performance |
| FCI FC2580 | 146 -- 308 MHz, 438 -- 924 MHz | Good performance |
Known Limitations
Repeated asynchronous readings on macOS
Starting a second asynchronous reading in the same process can terminate it. Ending a reading releases its transfer buffers without waiting for every canceled transfer to report, and a late completion then faults inside the USB layer. The failure is abrupt, with no managed exception to catch.
Close the device and open it again between readings rather than calling
StartReadSamplesAsync() a second time on the same instance. That greatly reduces the risk
without removing it entirely.
Most applications never meet this. It requires all of macOS, the asynchronous API, and more than one reading per process. Unaffected are:
- Streaming once per device, which is the usual shape
ReadSamples(), the synchronous API, on any platform- Changing
CenterFrequencywhile a reading is running, so scanning by retuning mid-stream is safe
The root cause is in the native layer's cancellation path, which waits for transfers to settle on Windows but not on the other platforms.
Contributing
Contributions are welcome. Please read the Contributing Guide for development setup, coding standards, and the pull request process. This project follows the Contributor Covenant Code of Conduct.
License
RTL-SDR Manager Library for .NET is free software, released under the GNU General Public License v3.0 or later.
Links
- NuGet Package: https://www.nuget.org/packages/RtlSdrManager/
- GitHub Repository: https://github.com/nandortoth/rtlsdr-manager
- Issue Tracker: https://github.com/nandortoth/rtlsdr-manager/issues
- Changelog: CHANGELOG.md
- librtlsdr: https://github.com/osmocom/rtl-sdr
Acknowledgments
- Osmocom rtl-sdr project — The native
librtlsdrlibrary that this project wraps. - KerberosSDR project — Coherent SDR extensions for direction finding and passive radar.
Contact
- Author: Nandor Toth
- Email: dev@nandortoth.com
- Issues: github.com/nandortoth/rtlsdr-manager/issues
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- No dependencies.
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
v0.8.0 (2026-08-25):
ADDED:
- SupportedFrequencyRanges on RtlSdrManagedDevice, with the TunerCapabilities,
DemodulatorCapabilities and FrequencyRange types behind it, reporting what the
device can currently reach: the tuner's ranges, or the ADC's while direct
sampling is active.
- OpenManagedDeviceBySerial(), opening a device by serial number rather than by
index. Indices are positional and shift when devices are plugged or unplugged;
a serial stays with the device. Throws if several devices share the serial.
- TransferBufferCount, controlling how many buffers the device fills in rotation
during an asynchronous reading. Trades tolerance of a slow consumer against
worst-case latency; previously fixed at 15 and unreachable.
FIXED:
- Direct sampling is usable at last. CenterFrequency validated against the tuner's
range even while direct sampling bypassed the tuner, so every HF frequency was
rejected. The range is now the ADC's: 0 Hz to half the crystal frequency.
- The E4000 accepts exactly 1100 and 1250 MHz, which an asymmetric comparison
rejected even though both are documented as supported.
- 0.0 dB is accepted as a tuner gain on the R820T/R828D, where it is the tuner's
lowest gain step. It was treated as an error value, so SupportedTunerGains
reported 28 of 29 gains and TunerGain = 0.0 threw.
- SetMaximumTunerGain()/SetMinimumTunerGain() on a tuner without manual gain
control now name the cause, instead of throwing "Sequence contains no elements".
- SetBiasTeeGPIO() works on every tuner, not just the R820T. The GPIO pins belong
to the demodulator, so the restriction was never justified.
CHANGED:
- BREAKING: using a disposed device now throws ObjectDisposedException. Most
importantly, StartReadSamplesAsync() on a disposed device terminated the process;
configuring one, resetting the dropped-sample counter and StopReadSamplesAsync()
succeeded silently. A disposed device can still be inspected: DeviceInfo,
ToString(), AsyncReadException, DroppedSamplesCount and the configuration
getters keep working.
- BREAKING: reading TunerGain, CenterFrequency or SampleRate before one has been
set throws InvalidOperationException instead of RtlSdrLibraryExecutionException.
All three reported a device failure for what is a usage error.
- Enabling direct sampling resets the center frequency to 0 Hz when the current one
is beyond the ADC's reach, because the demodulator would truncate it silently.
- BREAKING: SetBiasTeeGPIO() throws ArgumentOutOfRangeException for a pin outside
0..7, instead of RtlSdrLibraryExecutionException.
- BREAKING: RtlSdrDeviceManager.Devices is now IReadOnlyDictionary, so the device
table can no longer be mutated by callers. Reading and enumeration unchanged.
- BREAKING: StartReadSamplesAsync() caps a single read at 8388608 samples (16 MiB)
and the total across all transfer buffers at 256 MiB. The old limit allowed a
~4 GB request, allocated once per buffer, failing with no useful diagnostic.
- SetMinimumTunerGain() selects 0.0 dB on the R820T/R828D instead of 0.9 dB.
- The E4000's 1100 to 1250 MHz gap is no longer rejected up front: its boundaries
vary between devices, so the tuner decides and reports the outcome.
KNOWN LIMITATION:
- On macOS, starting a second asynchronous reading in the same process can fault
inside the USB layer. Close the device and open it again between readings.
Streaming once per device is unaffected, as is ReadSamples(), and as is
retuning while a reading is running.
See CHANGELOG.md for complete details and previous releases.