# Changelog All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [0.7.1] - 2026-07-24 ### Changed - `IQData` stores its I/Q components as two `byte` fields internally (2 bytes per sample instead of 8) to match the RTL2832U's unsigned 8-bit output. The `I` and `Q` accessors remain `int` and record semantics (value equality, `with`, deconstruction) are preserved, so this is **not** a breaking change. Memory footprint drops ~4x for the `List` returned by `ReadSamples` and ~2x for the async `ConcurrentQueue` buffer (the queue's per-slot bookkeeping caps the async win at ~2x) ## [0.7.0] - 2026-07-21 ### Added - `AsyncReadException` property on `RtlSdrManagedDevice` to observe the error that stopped asynchronous reading without stopping it explicitly - `RefreshDevices()` on `RtlSdrDeviceManager` for re-enumerating the RTL-SDR devices at runtime (e.g. after plugging in a device) - XML documentation file is generated and shipped in the NuGet package, so consumers get IntelliSense documentation (including newly documented enum members) - Unit test project (`tests/RtlSdrManager.Tests`, xunit) covering the hardware-independent components: `Frequency`/`CrystalFrequency` conversions and arithmetic, `RawSampleBuffer`, `StartReadSamplesAsync` argument validation, and console suppression scope pairing ### Changed - **BREAKING**: target framework is `net10.0` only; the `net9.0` target was dropped (.NET 9 is a Standard Term Support release that reaches end of support on November 10, 2026, while .NET 10 is the current LTS release; this reverts the 0.6.2 widening) - **BREAKING**: state errors now throw `InvalidOperationException` instead of `RtlSdrLibraryExecutionException`: reading `AsyncBuffer`, `GetSamplesFromAsyncBuffer` or `GetRawSamplesFromAsyncBuffer` before `StartReadSamplesAsync`, and using `TunerBandwidth` in automatic mode or `TunerGain` in AGC mode - **BREAKING**: `CloseAllManagedDevice()` is a no-op when no devices are open (previously threw `InvalidOperationException`) - **BREAKING**: `RtlSdrDeviceManager.Instance` no longer throws when no RTL-SDR device is present; check `CountDevices` instead. The manager now also works when a device is plugged in only after the application started (previously the "no devices" error was cached forever by the singleton) - `KerberosSDRMode` is now settable after opening a device (`init` → `set`); previously the KerberosSDR feature set (`FrequencyDitheringMode`, `SetGPIO`) was unreachable because instances are only created internally by the manager - `StartReadSamplesAsync` now validates its argument: the requested sample count must be greater than zero and its byte size (requested samples * 2) must be a multiple of 512, otherwise `ArgumentOutOfRangeException` is thrown (previously zero caused a `DivideByZeroException` in raw buffer mode) ### Fixed - Async callback no longer throws managed exceptions across the native boundary, which terminated the process (triggered by a full buffer with `DropSamplesOnFullBuffer = false` — the default — or by a throwing `SamplesAvailable` handler); such errors now stop the reading and surface via `StopReadSamplesAsync()` / `AsyncReadException` - Device instances no longer hold a strong `GCHandle` self-reference for their whole lifetime; undisposed devices can now be finalized and the USB handle released (the handle now roots the device only while async reading is active) - `StopReadSamplesAsync` reliably waits for the worker thread (bounded join) and no longer masks a captured streaming error with a spurious cancel error - Toggling `SuppressLibraryConsoleOutput` while an operation is in progress can no longer leave stdout/stderr permanently redirected or corrupt the suppression reference count (the scope now captures the enter decision) - `Dispose()` no longer propagates exceptions from stopping async reading - `ArgumentOutOfRangeException` thrown by the `CenterFrequency`, `CrystalFrequency`, `SampleRate` and `TunerGain` setters now carries a proper `ParamName`, actual value and message (previously the message was misused as the parameter name) - Errors from the native asynchronous read (e.g. device failure while streaming) are now surfaced via `StopReadSamplesAsync()` / `AsyncReadException` instead of silently ending the streaming; a requested stop is recognized and is never reported as an error, even though the native read can return a nonzero code on cancellation - Changing `UseRawBufferMode` while an asynchronous reading is running no longer crashes the native callback; the mode is captured at `StartReadSamplesAsync` and changes take effect at the next start - Setting `TunerGain` no longer risks sending an off-by-one gain value to the device (`(int)(49.6 * 10)` truncated to 495 instead of 496); the conversion and the validation now use rounded tenths of dB - Corrected the error message of the `TunerBandwidthSelectionMode` setter (said "tuner gain mode" instead of "tuner bandwidth") - Corrected the `DirectSamplingModes` XML documentation (said "Tuner gain modes") ### Removed - Unused `suppressConsoleOutput` parameter of the internal `OpenDevice` helper and the unused generic `ExecuteWithSuppression` overload ## [0.6.3] - 2026-06-26 ### Changed - Synchronous `ReadSamples` now validates its argument: a negative or excessively large `requestedSamples` throws `ArgumentOutOfRangeException` (previously an `OverflowException` surfaced from buffer allocation); a count of zero returns an empty list without performing a device read ### Performance - Async callback (IQData mode): eliminated the per-callback intermediate `IQData[]` allocation and collapsed the two-pass build/enqueue into a single direct-enqueue loop, removing sustained GC pressure (~128 KB allocated ~146×/sec at 2.4 MSPS) - Async callback: skip `SamplesAvailableEventArgs` allocation and sample-count math when there are no `SamplesAvailable` subscribers - Synchronous `ReadSamples`: replaced per-call `GCHandle` pinning + `new byte[]` with a `fixed` block and a pooled `ArrayPool` scratch buffer ### Fixed - Synchronous `ReadSamples` no longer leaks its scratch buffer when a read error is thrown (the pooled buffer is now released via `try/finally`) - Documentation: corrected the coherent-sampling snippet in `docs/KERBEROS_SDR.md`, which called a non-existent `.Length` member on an `IQData` value ## [0.6.2] - 2026-05-24 ### Changed - Multi-target `net9.0` and `net10.0` (was `net10.0` only), so the NuGet package can be consumed from both .NET 9 and .NET 10 projects ## [0.6.1] - 2026-03-19 ### Added - Native library fallback paths for additional Linux distributions - RedHat/Fedora/SUSE (`/usr/lib64`, `/usr/local/lib64`) - Debian/Ubuntu armhf for 32-bit Raspberry Pi (`/usr/lib/arm-linux-gnueabihf`) - Alpine/minimal distros (`/lib`) - Versioned `.so.0` variants for all paths (runtime-only package support without `-dev`) ## [0.6.0] - 2026-03-14 ### Added - **Raw buffer mode** for zero-copy sample delivery via `Channel` - New `RawSampleBuffer` type wrapping pooled `byte[]` buffers from `ArrayPool` - New `UseRawBufferMode` property to opt-in to raw buffer mode (default: `false`) - New `GetRawSamplesFromAsyncBuffer()` method returning `RawSampleBuffer?` - Bounded `Channel` with configurable capacity derived from `MaxAsyncBufferSize` - Backpressure-aware: respects `DropSamplesOnFullBuffer` when channel is full - Demo5: Raw buffer mode sample application ### Performance - Raw buffer mode eliminates per-sample `IQData` object allocation in async callback (~144M allocations per 30s at 2.4 MSPS with 2 devices) - Single `memcpy` per callback replaces per-sample struct construction and queue operations - `ArrayPool` reuse eliminates steady-state heap allocation in the native callback path - `Channel` with `SingleWriter`/`SingleReader` for lock-free buffer handoff ## [0.5.3] - 2026-03-07 ### Performance - Pre-allocated `List` capacity in synchronous `ReadSamples` to eliminate dynamic resizing - Batched IQ sample construction in async callback to improve cache locality and reduce lock contention - Replaced busy-wait spin loop in `GetSamplesFromAsyncBuffer` with single `TryDequeue` and early exit - Pre-allocated `List` capacity in `GetSamplesFromAsyncBuffer` - Cached supported tuner gains from librtlsdr to avoid repeated P/Invoke calls on every access ### Fixed - Demo4 now opens all available devices instead of only the first one ## [0.5.2] - 2026-01-17 ### Fixed - Fixed permanent stdout/stderr redirection that broke console applications - Console output suppression now uses **scoped suppression** with reference counting - Suppression only active during device operations (OpenManagedDevice), stdout restored between operations - Allows console applications (Spectre.Console, etc.) to properly initialize and detect terminal capabilities - Preserves file descriptor corruption fix from v0.5.1 (global singleton suppressor with reference counting) ### Changed - **BREAKING**: Default behavior changed - librtlsdr diagnostic messages now shown by default - `SuppressLibraryConsoleOutput` defaults to `false` (previously `true`) - Applications can set `RtlSdrDeviceManager.SuppressLibraryConsoleOutput = true` to suppress messages - Console output suppression is now configuration-based (boolean flag) instead of immediately creating global suppressor - Suppressor lifecycle managed via reference-counted scopes (RAII pattern with `SuppressionScope` helper class) ## [0.5.1] - 2025-11-27 ### Changed - **BREAKING**: Console output suppression now uses global singleton pattern instead of per-device suppression - `RtlSdrDeviceManager.SuppressLibraryConsoleOutput` is now a property (not auto-property) that manages global state - Removed per-device `SuppressLibraryConsoleOutput` property from `RtlSdrManagedDevice` - Changes apply immediately to all open devices - Uses `System.Threading.Lock` for thread-safe global suppressor management ### Fixed - Fixed file descriptor corruption when multiple RTL-SDR devices are opened simultaneously - Console output suppression now uses a single global `ConsoleOutputSuppressor` instance - Prevents each device from creating its own file descriptor redirections - Eliminates crashes and undefined behavior with multiple devices ### Removed - Per-device console output suppression control (replaced with global control) ## [0.5.0] - 2025-10-23 ### Added - Windows support to native library resolver - IComparable support to `Frequency` type - Improved frequency arithmetic with overflow protection - `build.sh` script for easy building and NuGet package creation - `runsample.sh` script for running sample applications - Modern .NET project structure with `src/` and `samples/` folders - Source Link support for debugging NuGet packages - Comprehensive XML documentation - `Frequency` type with strongly-typed unit conversions (Hz, KHz, MHz, GHz) - `DeviceInfo` as modern record type - `CrystalFrequency` type for RTL2832 and tuner crystal settings - Comprehensive `.editorconfig` with 60+ code quality rules - `.gitattributes` for consistent cross-platform line endings - [Console output suppression](docs/CONSOLE_OUTPUT_SUPPRESSION.md) for librtlsdr diagnostic messages - Performance analyzers (CA1827, CA1829, CA1841, CA1851) - Security analyzers for P/Invoke (CA2101, CA3075, CA5350, CA5351) - Nullable reference type warnings (CS8600-CS8604, CS8618, CS8625) - Standard exception constructors for all custom exception types - Input validation with proper exception types (`ArgumentNullException`, `ArgumentException`) - Improved [README.md](README.md) with comprehensive documentation and examples - Cross-platform build support (LF line endings) ### Changed - **BREAKING**: Migrated from .NET Core 3.1 to .NET 9.0 - **BREAKING**: Namespace reorganization (Modes/, Hardware/, Interop/) - **BREAKING**: Project restructured into `src/` and `samples/` directories - Replaced legacy `DllImport` with source-generated `LibraryImport` for better performance - Split `RtlSdrLibraryWrapper` into `LibRtlSdr` and `LibResolver` for better separation - Modernized value types (`DeviceInfo`, `Frequency`, `CrystalFrequency`) - Improved build system with artifact generation - Exception handling: `IndexOutOfRangeException` replaced with appropriate types - `OpenManagedDevice`: Now validates device index and friendly name - `CloseManagedDevice`: Added input validation for friendly name - `CloseAllManagedDevice`: Changed exception type to `InvalidOperationException` - Indexer `[string friendlyName]`: Added input validation and proper exception types - `RtlSdrManagedDevice.Dispose()`: Implemented proper dispose pattern with `Dispose(bool disposing)` - EditorConfig: Changed line endings from CRLF to LF for cross-platform compatibility - EditorConfig: Adjusted brace enforcement from warning to suggestion ### Fixed - Dispose pattern now properly separates managed and unmanaged resource cleanup - Finalizer no longer accesses potentially disposed managed resources - `CancellationTokenSource` is now properly disposed in async samples - Device opening now provides clear error messages for invalid indices - Demo2: Fixed `CancellationTokenSource` disposal using `using var` declaration ### Removed - .NET Core 3.1 support (now requires .NET 9.0+) - Old NuGet package creation scripts (replaced by `build.sh`) - Legacy Visual Studio project files ### Performance - LibraryImport provides better P/Invoke performance - AOT compilation compatibility - Compile-time safety improvements ## [0.2.1] - 2020-01-10 ### Added - KerberosSDR support for coherent SDR arrays - Frequency dithering control for R820T tuners - Direct GPIO control for synchronization - Bias Tee control methods - `SetBiasTee()` for GPIO 0 - `SetBiasTeeGPIO()` for specific GPIO pins on R820T - `KerberosSDRModes` enumeration - `GPIOModes` enumeration for GPIO control - `BiasTeeModes` enumeration ### Changed - Extended `RtlSdrManagedDevice` with KerberosSDR-specific features - Improved documentation for advanced features ### Fixed - Minor documentation issues and typos ## [0.2.0] - 2018-06-10 ### Added - Singleton pattern for `RtlSdrDeviceManager` - Thread-safe device manager instance ### Fixed - Critical bug in `DeviceInfo` handling that could cause incorrect device identification - Improved device enumeration reliability ### Changed - `RtlSdrDeviceManager` now uses lazy singleton initialization - Better memory management for device instances ## [0.1.3] - 2018-06-09 ### Added - First public release on NuGet.org - NuGet package metadata and icon - Package publishing workflow ### Changed - Improved package description and tags - Added proper licensing information to NuGet package ## [0.1.2] - 2018-06-03 ### Added - Crystal frequency control for RTL2832 and tuner IC - `CrystalFrequency` property with get/set support - Validation for crystal frequency ranges (max 28.8 MHz) - Tuner bandwidth selection - Automatic bandwidth selection mode - Manual bandwidth control - `TunerBandwidthSelectionModes` enumeration - Direct sampling support for HF reception - I-ADC direct sampling mode - Q-ADC direct sampling mode - `DirectSamplingModes` enumeration - Offset tuning mode for zero-IF tuners - Improved DC offset handling - `OffsetTuningModes` enumeration ### Changed - Enhanced `RtlSdrManagedDevice` with advanced tuner controls - Improved frequency handling and validation - Better support for HF reception scenarios ## [0.1.1] - 2018-05-12 ### Added - Initial public release of RTL-SDR Manager - Core device management functionality - Device enumeration and information retrieval - Device opening and closing - Multiple device support with friendly names - Basic device configuration - Center frequency control with validation - Sample rate configuration - Gain control (AGC and manual modes) - Frequency correction (PPM) - Sample reading capabilities - Synchronous sample reading - Asynchronous sample reading with event support - Configurable buffer management - Tuner support - Elonics E4000 - Rafael Micro R820T/R828D - Fitipower FC0012/FC0013 - FCI FC2580 - Exception handling - `RtlSdrDeviceException` - `RtlSdrLibraryExecutionException` - `RtlSdrManagedDeviceException` - Comprehensive demo applications - Event-based async reading (Demo1) - Manual buffer polling (Demo2) - Synchronous reading (Demo3) - Device information display (Demo4) ### Dependencies - .NET Core 3.1 - librtlsdr native library ## [0.1.0] - 2018-05-01 ### Added - Initial development version - Basic P/Invoke wrappers for librtlsdr - Core architecture design - Project structure and build system --- ## Version History Summary | Version | Date | Key Changes | |---------|------------|-------------| | **0.7.1** | 2026-07-24 | `IQData` byte-backed storage (~2-4x less memory, non-breaking) | | **0.7.0** | 2026-07-21 | Async crash/leak fixes, net10.0-only, hardened stop/dispose, tests, XML docs | | **0.6.3** | 2026-06-26 | Async/sync hot-path CPU & allocation optimizations | | **0.6.2** | 2026-05-24 | Multi-target net9.0 and net10.0 for broader consumer compatibility | | **0.6.1** | 2026-03-19 | Native library paths for additional Linux distributions | | **0.6.0** | 2026-03-14 | Raw buffer mode for zero-copy sample delivery | | **0.5.3** | 2026-03-07 | Performance optimizations for sample reading pipeline | | **0.5.2** | 2026-01-17 | Fixed permanent stdout redirection, scoped suppression | | **0.5.1** | 2025-11-27 | Fixed multi-device console suppression bug | | **0.5.0** | 2025-10-23 | .NET migration, modern architecture, Source Link | | **0.2.1** | 2020-01-10 | KerberosSDR support, Bias Tee control | | **0.2.0** | 2018-06-10 | Singleton pattern, bug fixes | | **0.1.3** | 2018-06-09 | First NuGet release | | **0.1.2** | 2018-06-03 | Crystal frequency, direct sampling, offset tuning | | **0.1.1** | 2018-05-12 | Initial public release | | **0.1.0** | 2018-05-01 | Development version | --- ## Upgrade Notes ### Upgrading to 0.7.0 from 0.6.x **Breaking changes:** 1. **Target framework is now `net10.0` only.** The `net9.0` target was dropped. Consumers must target .NET 10. (.NET 9 is a Standard Term Support release reaching end of support on 2026-11-10; .NET 10 is the current LTS.) 2. **`RtlSdrDeviceManager.Instance` no longer throws when no device is present.** Detect the absence of devices with `CountDevices` instead of catching an exception: ```csharp // Old (0.6.x): construction threw RtlSdrDeviceException with no device present // New (0.7.0): if (RtlSdrDeviceManager.Instance.CountDevices == 0) { // no RTL-SDR device on the system } ``` The manager also recovers when a device is plugged in after startup — call `RefreshDevices()` to re-enumerate (the previous behavior cached the "no devices" error permanently). 3. **State errors now throw `InvalidOperationException`** (previously `RtlSdrLibraryExecutionException`): reading `AsyncBuffer`, `GetSamplesFromAsyncBuffer`, or `GetRawSamplesFromAsyncBuffer` before `StartReadSamplesAsync`, and using `TunerBandwidth` in automatic mode or `TunerGain` in AGC mode. Update any `catch` clauses accordingly. 4. **`CloseAllManagedDevice()` is now a no-op when no devices are open** (previously threw `InvalidOperationException`). **Behavior changes to be aware of:** - **Buffer overflow with `DropSamplesOnFullBuffer = false`** no longer throws from the native callback (which previously crashed the process). The reading stops and the error is thrown from `StopReadSamplesAsync()`; it is also readable via the new `AsyncReadException` property until the next `StartReadSamplesAsync()` resets it. ```csharp try { device.StopReadSamplesAsync(); } catch (RtlSdrManagedDeviceException ex) { // ex.InnerException is the error that stopped the reading (e.g. buffer overflow) } ``` - **`KerberosSDRMode` is now settable** after opening a device, so the KerberosSDR feature set (`FrequencyDitheringMode`, `SetGPIO`) is reachable. It still cannot be disabled once enabled. - **`StartReadSamplesAsync` validates its argument:** the requested sample count must be greater than zero and its byte size (`requestedSamples * 2`) must be a multiple of 512. ### Upgrading to 0.6.0 from 0.5.x **No breaking changes.** The new raw buffer mode is opt-in. To use raw buffer mode for high-throughput applications: ```csharp 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 { ReadOnlySpan raw = buffer.Data.AsSpan(0, buffer.ByteLength); // Process interleaved I/Q bytes directly: [I0, Q0, I1, Q1, ...] for (int i = 0; i < buffer.ByteLength; i += 2) { byte iSample = raw[i]; byte qSample = raw[i + 1]; // ... } } finally { buffer.Return(); // Return pooled buffer — must be called exactly once } }; ``` The existing `IQData`-based API (`GetSamplesFromAsyncBuffer`, `AsyncBuffer`, `ReadSamples`) continues to work unchanged when `UseRawBufferMode` is `false` (the default). ### Upgrading to 0.5.0 from 0.2.x **Breaking Changes:** 1. **Framework Requirement** ```xml netcoreapp3.1 net9.0 ``` 2. **Namespace Changes** ```csharp // Old using RtlSdrManager.Types; // New using RtlSdrManager.Modes; // For enumerations using RtlSdrManager.Hardware; // For hardware types ``` 3. **Project Structure** - Source files moved from `RtlSdrManager/` to `src/RtlSdrManager/` - Samples moved from `RtlSdrManagerDemo/` to `samples/RtlSdrManager.Samples/` 4. **Type Changes** - `Frequency` is now an immutable record struct - Use factory methods: `Frequency.FromMHz()`, `FromKHz()`, etc. **Migration Example:** ```csharp // Old (0.2.x) using RtlSdrManager.Types; var freq = new Frequency(1090000000); // Hz device.CenterFrequency = freq; // New (0.5.0) using RtlSdrManager; var freq = Frequency.FromMHz(1090); // Clearer intent device.CenterFrequency = freq; // Can also use arithmetic var shifted = freq + Frequency.FromKHz(100); ``` **Console Output Suppression (updated in v0.5.1 and v0.5.2):** Since v0.5.2, `librtlsdr` diagnostic messages are shown by default. Suppression is controlled via a single global static property and is scoped to individual device operations: ```csharp // Suppress librtlsdr diagnostic messages globally RtlSdrDeviceManager.SuppressLibraryConsoleOutput = true; manager.OpenManagedDevice(0, "my-device"); var device = manager["my-device"]; device.SampleRate = Frequency.FromMHz(2); // Silent // Re-enable output RtlSdrDeviceManager.SuppressLibraryConsoleOutput = false; ``` ### Upgrading to 0.2.0 from 0.1.x - No breaking changes, fully backward compatible - Recommended to use singleton pattern: `RtlSdrDeviceManager.Instance` - Legacy instantiation still works but is deprecated --- ## Contributing See [CONTRIBUTING.md](CONTRIBUTING.md) for contribution guidelines. ## License This project is licensed under the GNU General Public License v3.0 or later. See [LICENSE.md](LICENSE.md) for details. --- [0.7.1]: https://github.com/nandortoth/rtlsdr-manager/releases/tag/v0.7.1 [0.7.0]: https://github.com/nandortoth/rtlsdr-manager/releases/tag/v0.7.0 [0.6.3]: https://github.com/nandortoth/rtlsdr-manager/releases/tag/v0.6.3 [0.6.2]: https://github.com/nandortoth/rtlsdr-manager/releases/tag/v0.6.2 [0.6.1]: https://github.com/nandortoth/rtlsdr-manager/releases/tag/v0.6.1 [0.6.0]: https://github.com/nandortoth/rtlsdr-manager/releases/tag/v0.6.0 [0.5.3]: https://github.com/nandortoth/rtlsdr-manager/releases/tag/v0.5.3 [0.5.2]: https://github.com/nandortoth/rtlsdr-manager/releases/tag/v0.5.2 [0.5.1]: https://github.com/nandortoth/rtlsdr-manager/releases/tag/v0.5.1 [0.5.0]: https://github.com/nandortoth/rtlsdr-manager/releases/tag/v0.5.0 [0.2.1]: https://github.com/nandortoth/rtlsdr-manager/releases/tag/v0.2.1 [0.2.0]: https://github.com/nandortoth/rtlsdr-manager/releases/tag/v0.2.0 [0.1.3]: https://github.com/nandortoth/rtlsdr-manager/releases/tag/v0.1.3 [0.1.2]: https://github.com/nandortoth/rtlsdr-manager/releases/tag/v0.1.2 [0.1.1]: https://github.com/nandortoth/rtlsdr-manager/releases/tag/v0.1.1 [0.1.0]: https://github.com/nandortoth/rtlsdr-manager/releases/tag/v0.1.0