├── design └── icon │ ├── RtlSdrManager.Icon.128.png │ ├── RtlSdrManager.Icon.256.png │ ├── RtlSdrManager.Icon.32.png │ ├── RtlSdrManager.Icon.512.png │ ├── RtlSdrManager.Icon.64.png │ ├── RtlSdrManager.Icon.afdesign │ └── RtlSdrManager.Icon.svg ├── src └── RtlSdrManager │ ├── RtlSdrManager.Icon.png │ ├── Modes │ ├── AGCModes.cs │ ├── TestModes.cs │ ├── TunerGainModes.cs │ ├── GPIOModes.cs │ ├── KerberosSDRModes.cs │ ├── BiasTeeModes.cs │ ├── OffsetTuningModes.cs │ ├── TunerBandwidthSelectionModes.cs │ ├── DirectSamplingModes.cs │ └── FrequencyDitheringModes.cs │ ├── Hardware │ └── TunerTypes.cs │ ├── IQData.cs │ ├── DeviceInfo.cs │ ├── SampleAvailableEventArgs.cs │ ├── CrystalFrequency.cs │ ├── Exceptions │ ├── RtlSdrDeviceException.cs │ ├── RtlSdrManagedDeviceException.cs │ └── RtlSdrLibraryExecutionException.cs │ ├── Interop │ ├── SafeRtlSdrHandle.cs │ ├── LibResolver.cs │ └── ConsoleOutputSuppressor.cs │ ├── RtlSdrManager.csproj │ ├── RtlSdrManagedDevice.Sync.cs │ ├── Frequency.cs │ ├── RtlSdrManagedDevice.Async.cs │ └── RtlSdrDeviceManager.cs ├── .gitignore ├── RtlSdrManager.sln.DotSettings ├── .gitattributes ├── samples └── RtlSdrManager.Samples │ ├── RtlSdrManager.Samples.csproj │ ├── Demo3.cs │ ├── Demo1.cs │ ├── Program.cs │ ├── Demo2.cs │ └── Demo4.cs ├── docs ├── BASIC_SETUP.md ├── MANUAL_GAIN_CONTROL.md ├── DIRECT_SAMPLING.md ├── KERBEROS_SDR.md ├── BIAS_TEE.md ├── DEVICE_MANAGEMENT.md ├── FREQUENCY_CORRECTION.md └── CONSOLE_OUTPUT_SUPPRESSION.md ├── runsample.sh ├── RtlSdrManager.sln ├── CODE_OF_CONDUCT.md ├── CHANGELOG.md ├── README.md ├── CONTRIBUTING.md └── .editorconfig /design/icon/RtlSdrManager.Icon.128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nandortoth/rtlsdr-manager/HEAD/design/icon/RtlSdrManager.Icon.128.png -------------------------------------------------------------------------------- /design/icon/RtlSdrManager.Icon.256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nandortoth/rtlsdr-manager/HEAD/design/icon/RtlSdrManager.Icon.256.png -------------------------------------------------------------------------------- /design/icon/RtlSdrManager.Icon.32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nandortoth/rtlsdr-manager/HEAD/design/icon/RtlSdrManager.Icon.32.png -------------------------------------------------------------------------------- /design/icon/RtlSdrManager.Icon.512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nandortoth/rtlsdr-manager/HEAD/design/icon/RtlSdrManager.Icon.512.png -------------------------------------------------------------------------------- /design/icon/RtlSdrManager.Icon.64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nandortoth/rtlsdr-manager/HEAD/design/icon/RtlSdrManager.Icon.64.png -------------------------------------------------------------------------------- /design/icon/RtlSdrManager.Icon.afdesign: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nandortoth/rtlsdr-manager/HEAD/design/icon/RtlSdrManager.Icon.afdesign -------------------------------------------------------------------------------- /src/RtlSdrManager/RtlSdrManager.Icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nandortoth/rtlsdr-manager/HEAD/src/RtlSdrManager/RtlSdrManager.Icon.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Build results 2 | [Bb]in/ 3 | [Oo]bj/ 4 | artifacts/ 5 | 6 | # IDEs 7 | .idea/ 8 | .vs/ 9 | .vscode/ 10 | .claude/ 11 | 12 | # User-specific files 13 | *.user 14 | *.suo 15 | *.userprefs 16 | 17 | # OS files 18 | .DS_Store 19 | Thumbs.db 20 | -------------------------------------------------------------------------------- /RtlSdrManager.sln.DotSettings: -------------------------------------------------------------------------------- 1 | 2 | AGC 3 | GPIO 4 | SDR 5 | FC 6 | ADC 7 | IQ -------------------------------------------------------------------------------- /src/RtlSdrManager/Modes/AGCModes.cs: -------------------------------------------------------------------------------- 1 | // RTL-SDR Manager Library for .NET 2 | // Copyright (C) 2018-2025 Nandor Toth 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see http://www.gnu.org/licenses. 16 | 17 | namespace RtlSdrManager.Modes; 18 | 19 | /// 20 | /// AGC modes of the RTL2832. 21 | /// 22 | public enum AGCModes 23 | { 24 | Disabled, 25 | Enabled 26 | } 27 | -------------------------------------------------------------------------------- /src/RtlSdrManager/Modes/TestModes.cs: -------------------------------------------------------------------------------- 1 | // RTL-SDR Manager Library for .NET 2 | // Copyright (C) 2018-2025 Nandor Toth 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see http://www.gnu.org/licenses. 16 | 17 | namespace RtlSdrManager.Modes; 18 | 19 | /// 20 | /// Test modes of the RTL2832. 21 | /// 22 | public enum TestModes 23 | { 24 | Disabled, 25 | Enabled 26 | } 27 | -------------------------------------------------------------------------------- /src/RtlSdrManager/Modes/TunerGainModes.cs: -------------------------------------------------------------------------------- 1 | // RTL-SDR Manager Library for .NET 2 | // Copyright (C) 2018-2025 Nandor Toth 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see http://www.gnu.org/licenses. 16 | 17 | namespace RtlSdrManager.Modes; 18 | 19 | /// 20 | /// Tuner gain modes for the device. 21 | /// 22 | public enum TunerGainModes 23 | { 24 | AGC, 25 | Manual 26 | } 27 | -------------------------------------------------------------------------------- /src/RtlSdrManager/Modes/GPIOModes.cs: -------------------------------------------------------------------------------- 1 | // RTL-SDR Manager Library for .NET 2 | // Copyright (C) 2018-2025 Nandor Toth 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see http://www.gnu.org/licenses. 16 | 17 | namespace RtlSdrManager.Modes; 18 | 19 | /// 20 | /// GPIO modes for the GPIO of the device. 21 | /// 22 | public enum GPIOModes 23 | { 24 | Disabled, 25 | Enabled 26 | } 27 | -------------------------------------------------------------------------------- /src/RtlSdrManager/Modes/KerberosSDRModes.cs: -------------------------------------------------------------------------------- 1 | // RTL-SDR Manager Library for .NET 2 | // Copyright (C) 2018-2025 Nandor Toth 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see http://www.gnu.org/licenses. 16 | 17 | namespace RtlSdrManager.Modes; 18 | 19 | /// 20 | /// KerberosSDR mode enablement. 21 | /// 22 | public enum KerberosSDRModes 23 | { 24 | Disabled, 25 | Enabled 26 | } 27 | -------------------------------------------------------------------------------- /src/RtlSdrManager/Modes/BiasTeeModes.cs: -------------------------------------------------------------------------------- 1 | // RTL-SDR Manager Library for .NET 2 | // Copyright (C) 2018-2025 Nandor Toth 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see http://www.gnu.org/licenses. 16 | 17 | namespace RtlSdrManager.Modes; 18 | 19 | /// 20 | /// Bias Tee modes for the GPIOs of the device. 21 | /// 22 | public enum BiasTeeModes 23 | { 24 | Disabled, 25 | Enabled 26 | } 27 | -------------------------------------------------------------------------------- /src/RtlSdrManager/Modes/OffsetTuningModes.cs: -------------------------------------------------------------------------------- 1 | // RTL-SDR Manager Library for .NET 2 | // Copyright (C) 2018-2025 Nandor Toth 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see http://www.gnu.org/licenses. 16 | 17 | namespace RtlSdrManager.Modes; 18 | 19 | /// 20 | /// Offset tuning for zero-IF tuners. 21 | /// 22 | public enum OffsetTuningModes 23 | { 24 | Disabled, 25 | Enabled 26 | } 27 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Source code 5 | *.cs text eol=lf diff=csharp 6 | *.csx text eol=lf diff=csharp 7 | *.vb text eol=lf 8 | *.fs text eol=lf 9 | 10 | # Solution and project files 11 | *.sln text eol=lf merge=union 12 | *.csproj text eol=lf merge=union 13 | *.vbproj text eol=lf merge=union 14 | *.fsproj text eol=lf merge=union 15 | *.proj text eol=lf 16 | *.props text eol=lf 17 | *.targets text eol=lf 18 | 19 | # Configuration files 20 | *.config text eol=lf 21 | *.json text eol=lf 22 | *.xml text eol=lf 23 | *.yml text eol=lf 24 | *.yaml text eol=lf 25 | 26 | # Scripts 27 | *.sh text eol=lf 28 | *.bash text eol=lf 29 | 30 | # Windows-specific files 31 | *.bat text eol=crlf 32 | *.cmd text eol=crlf 33 | 34 | # Documentation 35 | *.md text eol=lf 36 | *.txt text eol=lf 37 | LICENSE* text eol=lf 38 | 39 | # Binary files 40 | *.dll binary 41 | *.exe binary 42 | *.png binary 43 | *.jpg binary 44 | *.jpeg binary 45 | *.gif binary 46 | *.ico binary 47 | *.snupkg binary 48 | *.nupkg binary 49 | -------------------------------------------------------------------------------- /src/RtlSdrManager/Modes/TunerBandwidthSelectionModes.cs: -------------------------------------------------------------------------------- 1 | // RTL-SDR Manager Library for .NET 2 | // Copyright (C) 2018-2025 Nandor Toth 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see http://www.gnu.org/licenses. 16 | 17 | namespace RtlSdrManager.Modes; 18 | 19 | /// 20 | /// Tuner bandwidth selection modes for the device. 21 | /// 22 | public enum TunerBandwidthSelectionModes 23 | { 24 | Automatic, 25 | Manual 26 | } 27 | -------------------------------------------------------------------------------- /src/RtlSdrManager/Modes/DirectSamplingModes.cs: -------------------------------------------------------------------------------- 1 | // RTL-SDR Manager Library for .NET 2 | // Copyright (C) 2018-2025 Nandor Toth 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see http://www.gnu.org/licenses. 16 | 17 | namespace RtlSdrManager.Modes; 18 | 19 | /// 20 | /// Tuner gain modes for the device. 21 | /// 22 | public enum DirectSamplingModes 23 | { 24 | Disabled, 25 | InPhaseADCInputEnabled, 26 | QuadratureADCInputEnabled 27 | } 28 | -------------------------------------------------------------------------------- /src/RtlSdrManager/Hardware/TunerTypes.cs: -------------------------------------------------------------------------------- 1 | // RTL-SDR Manager Library for .NET 2 | // Copyright (C) 2018-2025 Nandor Toth 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see http://www.gnu.org/licenses. 16 | 17 | namespace RtlSdrManager.Hardware; 18 | 19 | /// 20 | /// Supported RTL-SDR tuner types. 21 | /// 22 | public enum TunerTypes 23 | { 24 | Unknown, 25 | E4000, 26 | FC0012, 27 | FC0013, 28 | FC2580, 29 | R820T, 30 | R828D 31 | } 32 | -------------------------------------------------------------------------------- /samples/RtlSdrManager.Samples/RtlSdrManager.Samples.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | Exe 4 | net9.0 5 | 0.5.1 6 | 0.5.1.0 7 | 0.5.1.0 8 | Nandor Toth 9 | RTL-SDR Manager Library for .NET 10 | 0.5.1.0 11 | latest 12 | RtlSdrManager.Samples 13 | RtlSdrManager.Samples 14 | 15 | 16 | false 17 | 18 | 19 | false 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /src/RtlSdrManager/Modes/FrequencyDitheringModes.cs: -------------------------------------------------------------------------------- 1 | // RTL-SDR Manager Library for .NET 2 | // Copyright (C) 2018-2025 Nandor Toth 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see http://www.gnu.org/licenses. 16 | 17 | namespace RtlSdrManager.Modes; 18 | 19 | /// 20 | /// Frequency dithering for R820T tuners. 21 | /// 22 | public enum FrequencyDitheringModes 23 | { 24 | Disabled, 25 | Enabled, 26 | 27 | // This value is used for the devices, which do not support 28 | // the dithering. It is for internal usage. 29 | NotSet 30 | } 31 | -------------------------------------------------------------------------------- /src/RtlSdrManager/IQData.cs: -------------------------------------------------------------------------------- 1 | // RTL-SDR Manager Library for .NET 2 | // Copyright (C) 2018-2025 Nandor Toth 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see http://www.gnu.org/licenses. 16 | 17 | namespace RtlSdrManager; 18 | 19 | /// 20 | /// Immutable record to store I/Q data (analytic signal), representing the signal from RTL-SDR device. 21 | /// 22 | /// In-Phase signal component. 23 | /// Quadrature signal component. 24 | public readonly record struct IQData(int I, int Q) 25 | { 26 | /// 27 | /// Returns a string representation of the I/Q data. 28 | /// 29 | /// String value of the I/Q data instance. 30 | public override string ToString() => $"I: {I,3}, Q: {Q,3}"; 31 | } 32 | -------------------------------------------------------------------------------- /src/RtlSdrManager/DeviceInfo.cs: -------------------------------------------------------------------------------- 1 | // RTL-SDR Manager Library for .NET 2 | // Copyright (C) 2018-2025 Nandor Toth 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see http://www.gnu.org/licenses. 16 | 17 | namespace RtlSdrManager; 18 | 19 | /// 20 | /// Immutable record containing fundamental information about an RTL-SDR device. 21 | /// 22 | /// Index of the device on the system. 23 | /// Serial number of the device. 24 | /// Manufacturer of the device. 25 | /// Product type of the device. 26 | /// Name of the device. 27 | public readonly record struct DeviceInfo( 28 | uint Index, 29 | string Serial, 30 | string Manufacturer, 31 | string ProductType, 32 | string Name); 33 | -------------------------------------------------------------------------------- /src/RtlSdrManager/SampleAvailableEventArgs.cs: -------------------------------------------------------------------------------- 1 | // RTL-SDR Manager Library for .NET 2 | // Copyright (C) 2018-2025 Nandor Toth 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see http://www.gnu.org/licenses. 16 | 17 | using System; 18 | 19 | namespace RtlSdrManager; 20 | 21 | /// 22 | /// Event arguments for SamplesAvailable event. 23 | /// 24 | /// 25 | public class SamplesAvailableEventArgs : EventArgs 26 | { 27 | /// 28 | /// Create an instance of SamplesAvailableEventArgs. 29 | /// 30 | /// Available samples. 31 | /// 32 | internal SamplesAvailableEventArgs(int count) 33 | { 34 | SampleCount = count; 35 | } 36 | 37 | /// 38 | /// Return the amount of the available samples. 39 | /// 40 | public int SampleCount { get; } 41 | } 42 | -------------------------------------------------------------------------------- /src/RtlSdrManager/CrystalFrequency.cs: -------------------------------------------------------------------------------- 1 | // RTL-SDR Manager Library for .NET 2 | // Copyright (C) 2018-2025 Nandor Toth 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see http://www.gnu.org/licenses. 16 | 17 | namespace RtlSdrManager; 18 | 19 | /// 20 | /// Immutable record to store RTL-SDR's crystal frequencies. 21 | /// 22 | /// Frequency value used to clock the RTL2832. 23 | /// Frequency value used to clock the tuner IC. 24 | public readonly record struct CrystalFrequency( 25 | Frequency Rtl2832Frequency, 26 | Frequency TunerFrequency) 27 | { 28 | /// 29 | /// Returns a string representation of the crystal frequencies. 30 | /// 31 | /// String value of the CrystalFrequency instance. 32 | public override string ToString() => 33 | $"RTL2832: {Rtl2832Frequency.MHz} MHz; Tuner: {TunerFrequency.MHz} MHz"; 34 | } 35 | -------------------------------------------------------------------------------- /src/RtlSdrManager/Exceptions/RtlSdrDeviceException.cs: -------------------------------------------------------------------------------- 1 | // RTL-SDR Manager Library for .NET 2 | // Copyright (C) 2018-2025 Nandor Toth 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see http://www.gnu.org/licenses. 16 | 17 | using System; 18 | 19 | namespace RtlSdrManager.Exceptions; 20 | 21 | /// 22 | /// Class for managing exceptions to RTL-SDR device. 23 | /// 24 | /// 25 | public class RtlSdrDeviceException : Exception 26 | { 27 | /// 28 | /// Initializes a new instance of the class. 29 | /// 30 | public RtlSdrDeviceException() 31 | { 32 | } 33 | 34 | /// 35 | /// Initializes a new instance of the class with a specified error message. 36 | /// 37 | /// The message that describes the error. 38 | public RtlSdrDeviceException(string message) : base(message) 39 | { 40 | } 41 | 42 | /// 43 | /// Initializes a new instance of the class with a specified error message 44 | /// and a reference to the inner exception that is the cause of this exception. 45 | /// 46 | /// The error message that explains the reason for the exception. 47 | /// The exception that is the cause of the current exception, or a null reference if no inner exception is specified. 48 | public RtlSdrDeviceException(string message, Exception innerException) : base(message, innerException) 49 | { 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /docs/BASIC_SETUP.md: -------------------------------------------------------------------------------- 1 | # Use Case: Basic Device Setup and Sample Reading 2 | 3 | ## Objective 4 | Demonstrate how to initialize an RTL-SDR device, configure basic parameters, and read samples asynchronously. 5 | 6 | ## Scenario 7 | A developer wants to start capturing radio signals at a specific frequency (e.g., 1090 MHz for ADS-B aircraft tracking) with automatic gain control. 8 | 9 | ## Prerequisites 10 | - RTL-SDR device connected via USB 11 | - RtlSdrManager library installed 12 | 13 | ## Implementation 14 | 15 | ```csharp 16 | using RtlSdrManager; 17 | 18 | // Initialize the manager instance 19 | var manager = RtlSdrDeviceManager.Instance; 20 | 21 | // Check if any devices are available 22 | var deviceCount = manager.Count; 23 | if (deviceCount == 0) 24 | { 25 | Console.WriteLine("No RTL-SDR devices found"); 26 | return; 27 | } 28 | 29 | // Open the first device with a friendly name 30 | manager.OpenManagedDevice(0, "my-rtl-sdr"); 31 | 32 | // Configure basic parameters 33 | manager["my-rtl-sdr"].CenterFrequency = Frequency.FromMHz(1090); 34 | manager["my-rtl-sdr"].SampleRate = Frequency.FromMHz(2); 35 | manager["my-rtl-sdr"].TunerGainMode = TunerGainModes.AGC; 36 | manager["my-rtl-sdr"].AGCMode = AGCModes.Enabled; 37 | manager["my-rtl-sdr"].MaxAsyncBufferSize = 512 * 1024; 38 | manager["my-rtl-sdr"].DropSamplesOnFullBuffer = true; 39 | 40 | // Reset the device buffer before starting 41 | manager["my-rtl-sdr"].ResetDeviceBuffer(); 42 | 43 | // Start asynchronous sample reading 44 | manager["my-rtl-sdr"].StartReadSamplesAsync(); 45 | 46 | // Read samples from the buffer 47 | while (true) 48 | { 49 | if (manager["my-rtl-sdr"].AsyncBuffer.TryDequeue(out var data)) 50 | { 51 | Console.WriteLine($"Received {data.Length} samples"); 52 | // Process the IQ data here 53 | } 54 | else 55 | { 56 | Thread.Sleep(100); 57 | } 58 | } 59 | ``` 60 | 61 | ## Expected Results 62 | - Device initializes successfully 63 | - Samples are continuously read from the device 64 | - Buffer manages data flow automatically 65 | - AGC adjusts gain levels automatically 66 | 67 | ## Notes 68 | - The `DropSamplesOnFullBuffer` setting prevents buffer overflow by dropping old samples 69 | - Async reading runs in a background thread 70 | - Sample rate of 2 MHz provides good coverage for most applications 71 | -------------------------------------------------------------------------------- /src/RtlSdrManager/Exceptions/RtlSdrManagedDeviceException.cs: -------------------------------------------------------------------------------- 1 | // RTL-SDR Manager Library for .NET 2 | // Copyright (C) 2018-2025 Nandor Toth 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see http://www.gnu.org/licenses. 16 | 17 | using System; 18 | 19 | namespace RtlSdrManager.Exceptions; 20 | 21 | /// 22 | /// Class for handling exceptions in case of RtlSdrManagedDevice. 23 | /// 24 | /// 25 | public class RtlSdrManagedDeviceException : Exception 26 | { 27 | /// 28 | /// Initializes a new instance of the class. 29 | /// 30 | public RtlSdrManagedDeviceException() 31 | { 32 | } 33 | 34 | /// 35 | /// Initializes a new instance of the class with a specified error message. 36 | /// 37 | /// The message that describes the error. 38 | public RtlSdrManagedDeviceException(string message) : base(message) 39 | { 40 | } 41 | 42 | /// 43 | /// Initializes a new instance of the class with a specified error message 44 | /// and a reference to the inner exception that is the cause of this exception. 45 | /// 46 | /// The error message that explains the reason for the exception. 47 | /// The exception that is the cause of the current exception, or a null reference if no inner exception is specified. 48 | public RtlSdrManagedDeviceException(string message, Exception innerException) : base(message, innerException) 49 | { 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/RtlSdrManager/Interop/SafeRtlSdrHandle.cs: -------------------------------------------------------------------------------- 1 | // RTL-SDR Manager Library for .NET 2 | // Copyright (C) 2018-2025 Nandor Toth 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see http://www.gnu.org/licenses. 16 | 17 | using System; 18 | using Microsoft.Win32.SafeHandles; 19 | 20 | namespace RtlSdrManager.Interop; 21 | 22 | /// 23 | /// Represents a safe handle for RTL-SDR devices that ensures proper cleanup. 24 | /// This handle automatically closes the device when disposed or finalized. 25 | /// 26 | internal sealed class SafeRtlSdrHandle : SafeHandleZeroOrMinusOneIsInvalid 27 | { 28 | /// 29 | /// Initializes a new instance of the SafeRtlSdrHandle class. 30 | /// 31 | public SafeRtlSdrHandle() : base(ownsHandle: true) 32 | { 33 | } 34 | 35 | /// 36 | /// Initializes a new instance of the SafeRtlSdrHandle class with the specified handle. 37 | /// 38 | /// An IntPtr object that represents the pre-existing handle to use. 39 | /// true to reliably release the handle during finalization; false to prevent it. 40 | public SafeRtlSdrHandle(IntPtr existingHandle, bool ownsHandle) : base(ownsHandle) 41 | { 42 | SetHandle(existingHandle); 43 | } 44 | 45 | /// 46 | /// Executes the code required to free the handle. 47 | /// 48 | /// true if the handle is released successfully; otherwise, false. 49 | protected override bool ReleaseHandle() 50 | { 51 | // Close the RTL-SDR device 52 | // Return value: 0 on success 53 | return LibRtlSdr.rtlsdr_close(handle) == 0; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/RtlSdrManager/Exceptions/RtlSdrLibraryExecutionException.cs: -------------------------------------------------------------------------------- 1 | // RTL-SDR Manager Library for .NET 2 | // Copyright (C) 2018-2025 Nandor Toth 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see http://www.gnu.org/licenses. 16 | 17 | using System; 18 | 19 | namespace RtlSdrManager.Exceptions; 20 | 21 | /// 22 | /// Class for handling exceptions during native RTL-SDR library usage. 23 | /// 24 | /// 25 | public class RtlSdrLibraryExecutionException : Exception 26 | { 27 | /// 28 | /// Initializes a new instance of the class. 29 | /// 30 | public RtlSdrLibraryExecutionException() 31 | { 32 | } 33 | 34 | /// 35 | /// Initializes a new instance of the class with a specified error message. 36 | /// 37 | /// The message that describes the error. 38 | public RtlSdrLibraryExecutionException(string message) : base(message) 39 | { 40 | } 41 | 42 | /// 43 | /// Initializes a new instance of the class with a specified error message 44 | /// and a reference to the inner exception that is the cause of this exception. 45 | /// 46 | /// The error message that explains the reason for the exception. 47 | /// The exception that is the cause of the current exception, or a null reference if no inner exception is specified. 48 | public RtlSdrLibraryExecutionException(string message, Exception innerException) : base(message, innerException) 49 | { 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /runsample.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # RTL-SDR Manager Sample Runner Script 4 | # This script builds the project and runs the sample application 5 | # 6 | # Copyright (c) 2018-2025 Nandor Toth 7 | # 8 | # This program is free software: you can redistribute it and/or modify 9 | # it under the terms of the GNU General Public License as published by 10 | # the Free Software Foundation, either version 3 of the License, or 11 | # (at your option) any later version. 12 | # 13 | # This program is distributed in the hope that it will be useful, 14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | # GNU General Public License for more details. 17 | # 18 | # You should have received a copy of the GNU General Public License 19 | # along with this program. If not, see . 20 | 21 | set -e # Exit on error 22 | clear 23 | 24 | # Configuration 25 | PROJECT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" 26 | BUILD_SCRIPT="$PROJECT_ROOT/build.sh" 27 | ARTIFACTS_DIR="$PROJECT_ROOT/artifacts" 28 | SAMPLES_DIR="$ARTIFACTS_DIR/binaries/Samples" 29 | SAMPLE_EXE="$SAMPLES_DIR/RtlSdrManager.Samples" 30 | 31 | echo "================================================" 32 | echo "RTL-SDR Manager Sample Runner" 33 | echo "================================================" 34 | echo "" 35 | 36 | # Step 1: Check if build script exists 37 | if [ ! -f "$BUILD_SCRIPT" ]; then 38 | echo "Error: build.sh not found at $BUILD_SCRIPT" 39 | exit 1 40 | fi 41 | 42 | # Step 2: Run the build script 43 | echo "Running build script in the background..." 44 | echo "" 45 | bash "$BUILD_SCRIPT" > /dev/null 2>&1 46 | 47 | # Step 3: Verify sample executable exists 48 | echo "================================================" 49 | echo "RUNNING SAMPLE APPLICATION" 50 | echo "================================================" 51 | echo "" 52 | 53 | if [ ! -f "$SAMPLE_EXE" ]; then 54 | echo "Error: Sample executable not found at $SAMPLE_EXE" 55 | exit 1 56 | fi 57 | 58 | echo "Starting sample application from artifacts..." 59 | echo "Executable: $SAMPLE_EXE" 60 | echo "" 61 | 62 | # Step 4: Run the sample application 63 | dotnet "$SAMPLE_EXE.dll" 64 | 65 | # Capture exit code 66 | SAMPLE_EXIT_CODE=$? 67 | 68 | echo "" 69 | echo "================================================" 70 | 71 | if [ $SAMPLE_EXIT_CODE -eq 0 ]; then 72 | echo "✓ Running sample is finished!" 73 | else 74 | echo "✗ Sample exited with code: $SAMPLE_EXIT_CODE" 75 | fi 76 | 77 | echo "================================================" 78 | 79 | exit $SAMPLE_EXIT_CODE 80 | -------------------------------------------------------------------------------- /samples/RtlSdrManager.Samples/Demo3.cs: -------------------------------------------------------------------------------- 1 | // RTL-SDR Manager Library for .NET Core 2 | // Copyright (C) 2018-2025 Nandor Toth 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see http://www.gnu.org/licenses. 16 | 17 | using System; 18 | using System.Collections.Generic; 19 | using RtlSdrManager.Exceptions; 20 | using RtlSdrManager.Modes; 21 | 22 | namespace RtlSdrManager.Samples; 23 | 24 | /// 25 | /// Demo for RtlSdrManager. 26 | /// 27 | /// In this demo: 28 | /// - Samples will be received synchronously. 29 | /// - Simply print the first 5 samples. 30 | /// 31 | public static class Demo3 32 | { 33 | /// 34 | /// Run the demo. 35 | /// 36 | public static void Run() 37 | { 38 | // Initialize the Manager instance. 39 | RtlSdrDeviceManager manager = RtlSdrDeviceManager.Instance; 40 | 41 | // Open a managed device and set some parameters. 42 | try 43 | { 44 | manager.OpenManagedDevice(0, "my-rtl-sdr"); 45 | } 46 | catch (RtlSdrDeviceException e) 47 | { 48 | Console.WriteLine(e); 49 | return; 50 | } 51 | catch 52 | { 53 | Console.WriteLine("Failed to open the RTL-SDR device."); 54 | return; 55 | } 56 | 57 | manager["my-rtl-sdr"].CenterFrequency = Frequency.FromMHz(1090); 58 | manager["my-rtl-sdr"].SampleRate = Frequency.FromMHz(2); 59 | manager["my-rtl-sdr"].TunerGainMode = TunerGainModes.AGC; 60 | manager["my-rtl-sdr"].AGCMode = AGCModes.Enabled; 61 | manager["my-rtl-sdr"].ResetDeviceBuffer(); 62 | 63 | // Read samples. 64 | List samples = manager["my-rtl-sdr"].ReadSamples(256 * 1024); 65 | 66 | // Dump the first five samples. 67 | Console.WriteLine("Samples (first five):"); 68 | for (int i = 0; i < 5; i++) 69 | { 70 | Console.WriteLine($" {i + 1}: {samples[i]}"); 71 | } 72 | 73 | // Close the device. 74 | manager.CloseAllManagedDevice(); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /docs/MANUAL_GAIN_CONTROL.md: -------------------------------------------------------------------------------- 1 | # Use Case: Manual Gain Control 2 | 3 | ## Objective 4 | Configure RTL-SDR device with manual gain control for optimal signal reception in specific conditions. 5 | 6 | ## Scenario 7 | A user needs precise control over the tuner gain to avoid saturation from strong signals or to maximize weak signal reception. 8 | 9 | ## Prerequisites 10 | - RTL-SDR device connected 11 | - Knowledge of expected signal strength in the target frequency range 12 | 13 | ## Implementation 14 | 15 | ```csharp 16 | using RtlSdrManager; 17 | 18 | var manager = RtlSdrDeviceManager.Instance; 19 | manager.OpenManagedDevice(0, "my-device"); 20 | 21 | // Disable automatic gain control 22 | manager["my-device"].TunerGainMode = TunerGainModes.Manual; 23 | manager["my-device"].AGCMode = AGCModes.Disabled; 24 | 25 | // Get available gain values for this tuner 26 | var availableGains = manager["my-device"].TunerGains; 27 | Console.WriteLine("Available gain values:"); 28 | foreach (var gain in availableGains) 29 | { 30 | Console.WriteLine($" {gain} (units of 0.1 dB)"); 31 | } 32 | 33 | // Set specific gain value (e.g., 296 = 29.6 dB) 34 | manager["my-device"].TunerGain = 296; 35 | 36 | // Configure other parameters 37 | manager["my-device"].CenterFrequency = Frequency.FromMHz(100); 38 | manager["my-device"].SampleRate = Frequency.FromMHz(2); 39 | 40 | // Start receiving 41 | manager["my-device"].ResetDeviceBuffer(); 42 | manager["my-device"].StartReadSamplesAsync(); 43 | 44 | Console.WriteLine($"Device configured with manual gain: {manager["my-device"].TunerGain * 0.1} dB"); 45 | ``` 46 | 47 | ## Testing Different Gain Values 48 | 49 | ```csharp 50 | // Function to test different gain settings 51 | void TestGainSetting(int gainValue) 52 | { 53 | manager["my-device"].TunerGain = gainValue; 54 | Thread.Sleep(1000); // Let it stabilize 55 | 56 | // Read some samples and check signal strength 57 | if (manager["my-device"].AsyncBuffer.TryDequeue(out var data)) 58 | { 59 | // Analyze signal strength here 60 | Console.WriteLine($"Gain {gainValue * 0.1} dB - Sample count: {data.Length}"); 61 | } 62 | } 63 | 64 | // Test each available gain setting 65 | foreach (var gain in availableGains) 66 | { 67 | TestGainSetting(gain); 68 | } 69 | ``` 70 | 71 | ## Expected Results 72 | - Gain remains fixed at the specified value 73 | - No automatic adjustments occur 74 | - Signal quality depends on chosen gain value 75 | - Different gain values affect signal-to-noise ratio 76 | 77 | ## Notes 78 | - Gain values are in tenths of dB (e.g., 296 = 29.6 dB) 79 | - Too high gain can cause saturation and distortion 80 | - Too low gain reduces sensitivity to weak signals 81 | - Optimal gain depends on signal strength and interference levels 82 | - Use AGC initially to find appropriate manual gain values 83 | -------------------------------------------------------------------------------- /RtlSdrManager.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.27703.2000 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RtlSdrManager", "src\RtlSdrManager\RtlSdrManager.csproj", "{5E1ED170-5650-4C73-8DE8-CC577E2BA704}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RtlSdrManager.Samples", "samples\RtlSdrManager.Samples\RtlSdrManager.Samples.csproj", "{4AD683D7-F246-47C1-8A76-294741E19A18}" 9 | EndProject 10 | Global 11 | GlobalSection(SolutionProperties) = preSolution 12 | HideSolutionNode = FALSE 13 | EndGlobalSection 14 | GlobalSection(ExtensibilityGlobals) = postSolution 15 | SolutionGuid = {6B4865F3-52A7-48F8-9734-FAC09BF8B6C4} 16 | EndGlobalSection 17 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 18 | Debug|Any CPU = Debug|Any CPU 19 | Debug|x64 = Debug|x64 20 | Debug|x86 = Debug|x86 21 | Release|Any CPU = Release|Any CPU 22 | Release|x64 = Release|x64 23 | Release|x86 = Release|x86 24 | EndGlobalSection 25 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 26 | {5E1ED170-5650-4C73-8DE8-CC577E2BA704}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 27 | {5E1ED170-5650-4C73-8DE8-CC577E2BA704}.Debug|Any CPU.Build.0 = Debug|Any CPU 28 | {5E1ED170-5650-4C73-8DE8-CC577E2BA704}.Debug|x64.ActiveCfg = Debug|Any CPU 29 | {5E1ED170-5650-4C73-8DE8-CC577E2BA704}.Debug|x64.Build.0 = Debug|Any CPU 30 | {5E1ED170-5650-4C73-8DE8-CC577E2BA704}.Debug|x86.ActiveCfg = Debug|Any CPU 31 | {5E1ED170-5650-4C73-8DE8-CC577E2BA704}.Debug|x86.Build.0 = Debug|Any CPU 32 | {5E1ED170-5650-4C73-8DE8-CC577E2BA704}.Release|Any CPU.ActiveCfg = Release|Any CPU 33 | {5E1ED170-5650-4C73-8DE8-CC577E2BA704}.Release|Any CPU.Build.0 = Release|Any CPU 34 | {5E1ED170-5650-4C73-8DE8-CC577E2BA704}.Release|x64.ActiveCfg = Release|Any CPU 35 | {5E1ED170-5650-4C73-8DE8-CC577E2BA704}.Release|x64.Build.0 = Release|Any CPU 36 | {5E1ED170-5650-4C73-8DE8-CC577E2BA704}.Release|x86.ActiveCfg = Release|Any CPU 37 | {5E1ED170-5650-4C73-8DE8-CC577E2BA704}.Release|x86.Build.0 = Release|Any CPU 38 | {4AD683D7-F246-47C1-8A76-294741E19A18}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 39 | {4AD683D7-F246-47C1-8A76-294741E19A18}.Debug|Any CPU.Build.0 = Debug|Any CPU 40 | {4AD683D7-F246-47C1-8A76-294741E19A18}.Debug|x64.ActiveCfg = Debug|Any CPU 41 | {4AD683D7-F246-47C1-8A76-294741E19A18}.Debug|x64.Build.0 = Debug|Any CPU 42 | {4AD683D7-F246-47C1-8A76-294741E19A18}.Debug|x86.ActiveCfg = Debug|Any CPU 43 | {4AD683D7-F246-47C1-8A76-294741E19A18}.Debug|x86.Build.0 = Debug|Any CPU 44 | {4AD683D7-F246-47C1-8A76-294741E19A18}.Release|Any CPU.ActiveCfg = Release|Any CPU 45 | {4AD683D7-F246-47C1-8A76-294741E19A18}.Release|Any CPU.Build.0 = Release|Any CPU 46 | {4AD683D7-F246-47C1-8A76-294741E19A18}.Release|x64.ActiveCfg = Release|Any CPU 47 | {4AD683D7-F246-47C1-8A76-294741E19A18}.Release|x64.Build.0 = Release|Any CPU 48 | {4AD683D7-F246-47C1-8A76-294741E19A18}.Release|x86.ActiveCfg = Release|Any CPU 49 | {4AD683D7-F246-47C1-8A76-294741E19A18}.Release|x86.Build.0 = Release|Any CPU 50 | EndGlobalSection 51 | EndGlobal 52 | -------------------------------------------------------------------------------- /docs/DIRECT_SAMPLING.md: -------------------------------------------------------------------------------- 1 | # Use Case: Direct Sampling Mode 2 | 3 | ## Objective 4 | Enable direct sampling mode for receiving HF frequencies (below 30 MHz) without an upconverter. 5 | 6 | ## Scenario 7 | A user wants to receive shortwave radio, amateur radio, or other HF signals directly using the RTL-SDR's direct sampling capability. 8 | 9 | ## Prerequisites 10 | - RTL-SDR device with direct sampling support 11 | - Appropriate antenna for HF frequencies 12 | - Understanding of I-ADC vs Q-ADC sampling modes 13 | 14 | ## Implementation 15 | 16 | ### Using I-ADC Direct Sampling 17 | 18 | ```csharp 19 | using RtlSdrManager; 20 | 21 | var manager = RtlSdrDeviceManager.Instance; 22 | manager.OpenManagedDevice(0, "hf-receiver"); 23 | 24 | // Enable direct sampling on I-ADC input 25 | manager["hf-receiver"].DirectSamplingMode = DirectSamplingModes.I_ADC; 26 | 27 | // Configure for HF reception 28 | manager["hf-receiver"].CenterFrequency = Frequency.FromMHz(14.2); // 20m amateur band 29 | manager["hf-receiver"].SampleRate = Frequency.FromMHz(2.4); 30 | manager["hf-receiver"].TunerGainMode = TunerGainModes.AGC; 31 | manager["hf-receiver"].AGCMode = AGCModes.Enabled; 32 | 33 | // Start receiving 34 | manager["hf-receiver"].ResetDeviceBuffer(); 35 | manager["hf-receiver"].StartReadSamplesAsync(); 36 | 37 | Console.WriteLine("Direct sampling enabled for HF reception"); 38 | ``` 39 | 40 | ### Using Q-ADC Direct Sampling 41 | 42 | ```csharp 43 | // Alternative: Use Q-ADC input 44 | manager["hf-receiver"].DirectSamplingMode = DirectSamplingModes.Q_ADC; 45 | 46 | // Q-ADC typically provides better performance on some devices 47 | Console.WriteLine("Using Q-ADC direct sampling"); 48 | ``` 49 | 50 | ### Switching Between Modes 51 | 52 | ```csharp 53 | // Disable direct sampling to return to normal tuner mode 54 | manager["hf-receiver"].DirectSamplingMode = DirectSamplingModes.Disabled; 55 | Console.WriteLine("Direct sampling disabled - using normal tuner"); 56 | 57 | // Now can tune VHF/UHF frequencies normally 58 | manager["hf-receiver"].CenterFrequency = Frequency.FromMHz(145); // 2m band 59 | ``` 60 | 61 | ## Frequency Coverage 62 | 63 | ```csharp 64 | // Direct sampling mode typically covers: 65 | // - Long Wave: 30 kHz - 300 kHz 66 | // - Medium Wave: 300 kHz - 3 MHz 67 | // - Short Wave: 3 MHz - 30 MHz 68 | 69 | // Example: Receiving AM broadcast band 70 | manager["hf-receiver"].DirectSamplingMode = DirectSamplingModes.Q_ADC; 71 | manager["hf-receiver"].CenterFrequency = Frequency.FromkHz(1000); // 1 MHz MW 72 | manager["hf-receiver"].SampleRate = Frequency.FromMHz(2); 73 | ``` 74 | 75 | ## Expected Results 76 | - Device receives HF frequencies without external upconverter 77 | - Frequency range extends down to DC (0 Hz) 78 | - Tuner chip is bypassed 79 | - Sampling uses ADC directly from antenna input 80 | 81 | ## Notes 82 | - Direct sampling bypasses the tuner chip 83 | - I-ADC and Q-ADC inputs may have different performance characteristics 84 | - Sample rate and gain settings still apply 85 | - Not all RTL-SDR devices support direct sampling equally well 86 | - Hardware modifications may improve HF reception (bias tee removal, better filtering) 87 | - Antenna design is critical for HF reception quality 88 | -------------------------------------------------------------------------------- /design/icon/RtlSdrManager.Icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /samples/RtlSdrManager.Samples/Demo1.cs: -------------------------------------------------------------------------------- 1 | // RTL-SDR Manager Library for .NET Core 2 | // Copyright (C) 2018-2025 Nandor Toth 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see http://www.gnu.org/licenses. 16 | 17 | using System; 18 | using System.Collections.Generic; 19 | using System.Threading; 20 | using RtlSdrManager.Exceptions; 21 | using RtlSdrManager.Modes; 22 | 23 | namespace RtlSdrManager.Samples; 24 | 25 | /// 26 | /// Demo for RtlSdrManager. 27 | /// 28 | /// In this demo: 29 | /// - Samples will be received asynchronously. 30 | /// - Samples will be handled by SamplesAvailable event. 31 | /// 32 | public static class Demo1 33 | { 34 | /// 35 | /// Run the demo. 36 | /// 37 | public static void Run() 38 | { 39 | // Initialize the Manager instance. 40 | RtlSdrDeviceManager manager = RtlSdrDeviceManager.Instance; 41 | 42 | // Open a managed device and set some parameters. 43 | try 44 | { 45 | manager.OpenManagedDevice(0, "my-rtl-sdr"); 46 | } 47 | catch (RtlSdrDeviceException e) 48 | { 49 | Console.WriteLine(e); 50 | return; 51 | } 52 | catch 53 | { 54 | Console.WriteLine("Failed to open the RTL-SDR device."); 55 | return; 56 | } 57 | 58 | manager["my-rtl-sdr"].CenterFrequency = Frequency.FromMHz(1090); 59 | manager["my-rtl-sdr"].SampleRate = Frequency.FromMHz(2); 60 | manager["my-rtl-sdr"].TunerGainMode = TunerGainModes.AGC; 61 | manager["my-rtl-sdr"].AGCMode = AGCModes.Enabled; 62 | manager["my-rtl-sdr"].MaxAsyncBufferSize = 512 * 1024; 63 | manager["my-rtl-sdr"].DropSamplesOnFullBuffer = true; 64 | manager["my-rtl-sdr"].ResetDeviceBuffer(); 65 | 66 | // Start asynchronous sample reading. 67 | manager["my-rtl-sdr"].StartReadSamplesAsync(8 * 16384); 68 | 69 | // Subscribe on the event with the function. 70 | manager["my-rtl-sdr"].SamplesAvailable += (_, args) => 71 | { 72 | // Read the samples. 73 | List samples = manager["my-rtl-sdr"].GetSamplesFromAsyncBuffer(args.SampleCount); 74 | Console.WriteLine($"{samples.Count} samples handled, and removed from the buffer."); 75 | 76 | // Dump the first five samples. 77 | Console.WriteLine("Samples (first five):"); 78 | for (int i = 0; i < 5; i++) 79 | { 80 | Console.WriteLine($" {i + 1}: {samples[i]}"); 81 | } 82 | }; 83 | 84 | // Sleep the thread for 5 second before stop the samples reading. 85 | Thread.Sleep(5000); 86 | 87 | // Stop the reading of the samples. 88 | manager["my-rtl-sdr"].StopReadSamplesAsync(); 89 | 90 | // Close the device. 91 | manager.CloseAllManagedDevice(); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/RtlSdrManager/RtlSdrManager.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | net9.0 4 | latest 5 | true 6 | enable 7 | 8 | 9 | 10 | 11 | 0.5.1 12 | 0.5.1.0 13 | 0.5.1.0 14 | Nandor Toth 15 | Nandor Toth 16 | RTL-SDR Manager Library for .NET 17 | 18 | 19 | 20 | 21 | RtlSdrManager 22 | RTL-SDR Manager for .NET 23 | A modern .NET library for managing RTL-SDR devices with support for async operations, multiple tuner types, and advanced features. 24 | rtl-sdr;sdr;radio;rtl2832;software-defined-radio;rf;tuner 25 | https://github.com/nandortoth/rtlsdr-manager 26 | https://github.com/nandortoth/rtlsdr-manager 27 | git 28 | GPL-3.0-or-later 29 | false 30 | README.md 31 | Copyright (c) 2018-2025 Nandor Toth 32 | 33 | 34 | 35 | 36 | false 37 | true 38 | snupkg 39 | true 40 | true 41 | true 42 | 43 | 44 | 45 | 46 | 47 | v0.5.1 (2025-11-27): 48 | 49 | BREAKING CHANGES: 50 | - Console output suppression now uses global singleton pattern instead of per-device suppression 51 | - RtlSdrDeviceManager.SuppressLibraryConsoleOutput is now a property managing global state 52 | - Removed per-device SuppressLibraryConsoleOutput property from RtlSdrManagedDevice 53 | 54 | FIXES: 55 | - Fixed file descriptor corruption when multiple RTL-SDR devices are opened simultaneously 56 | - Console output suppression now uses a single global ConsoleOutputSuppressor instance 57 | - Eliminates crashes and undefined behavior with multiple devices 58 | 59 | See CHANGELOG.md for complete details and upgrade guide. 60 | 61 | 62 | 63 | 64 | 65 | RtlSdrManager.Icon.128.png 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | -------------------------------------------------------------------------------- /src/RtlSdrManager/RtlSdrManagedDevice.Sync.cs: -------------------------------------------------------------------------------- 1 | // RTL-SDR Manager Library for .NET 2 | // Copyright (C) 2018-2025 Nandor Toth 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see http://www.gnu.org/licenses. 16 | 17 | using System; 18 | using System.Collections.Generic; 19 | using System.Runtime.InteropServices; 20 | using RtlSdrManager.Exceptions; 21 | using RtlSdrManager.Interop; 22 | 23 | namespace RtlSdrManager; 24 | 25 | /// 26 | /// Class for a managed (opened) RTL-SDR device. 27 | /// 28 | /// 29 | public sealed partial class RtlSdrManagedDevice 30 | { 31 | #region Methods 32 | 33 | /// 34 | /// Read samples (I/Q) from the device. 35 | /// 36 | /// Amount of requested samples. 37 | /// I/Q data from the device as an IqData list. 38 | /// 39 | public List ReadSamples(int requestedSamples) 40 | { 41 | // I/Q data means 2 bytes. 42 | int requestedBytes = requestedSamples * 2; 43 | 44 | // Initialize the buffer. 45 | byte[] buffer = new byte[requestedBytes]; 46 | var bufferPinned = GCHandle.Alloc(buffer, GCHandleType.Pinned); 47 | IntPtr bufferPointer = bufferPinned.AddrOfPinnedObject(); 48 | 49 | // Read the ubytes from the device. 50 | int returnCode = LibRtlSdr.rtlsdr_read_sync(_deviceHandle!, 51 | bufferPointer, requestedBytes, out int receivedBytes); 52 | 53 | // Overflow happened. 54 | if (returnCode == -8) 55 | { 56 | throw new RtlSdrLibraryExecutionException( 57 | "Problem happened during reading bytes from the device (overflow, device provided more data). " + 58 | $"Error code: {returnCode}, requested bytes: {requestedBytes}, device index: {DeviceInfo.Index}."); 59 | } 60 | 61 | // Error happened during reading the data. 62 | if (returnCode != 0) 63 | { 64 | throw new RtlSdrLibraryExecutionException( 65 | "Problem happened during reading bytes from the device. " + 66 | $"Error code: {returnCode}, requested bytes: {requestedBytes}, device index: {DeviceInfo.Index}."); 67 | } 68 | 69 | // Amount of the received bytes is different from the requested. 70 | if (receivedBytes != requestedBytes) 71 | { 72 | throw new RtlSdrLibraryExecutionException( 73 | "Problem happened during reading bytes from the device. " + 74 | $"Error code: {returnCode}, requested bytes: {requestedBytes}, " + 75 | $"received bytes: {receivedBytes}, device index: {DeviceInfo.Index}."); 76 | } 77 | 78 | // Release the memory object. 79 | bufferPinned.Free(); 80 | 81 | // Convert byte array to IqData list. 82 | var iqData = new List(); 83 | for (int i = 0; i < buffer.Length; i += 2) 84 | { 85 | iqData.Add(new IQData(buffer[i], buffer[i + 1])); 86 | } 87 | 88 | // Return the IqData list. 89 | return iqData; 90 | } 91 | 92 | #endregion 93 | } 94 | -------------------------------------------------------------------------------- /samples/RtlSdrManager.Samples/Program.cs: -------------------------------------------------------------------------------- 1 | // RTL-SDR Manager Library for .NET Core 2 | // Copyright (C) 2018-2025 Nandor Toth 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see http://www.gnu.org/licenses. 16 | 17 | using System; 18 | 19 | namespace RtlSdrManager.Samples; 20 | 21 | /// 22 | /// Simple demo for RtlSdrManager. 23 | /// 24 | public static class Program 25 | { 26 | /// 27 | /// Main function of the demo. 28 | /// 29 | public static void Main() 30 | { 31 | // Disable console output suppression for the sample application 32 | // so that Console.WriteLine and Console.ReadKey work normally 33 | RtlSdrDeviceManager.SuppressLibraryConsoleOutput = false; 34 | 35 | // Check the available devices. 36 | if (RtlSdrDeviceManager.Instance.CountDevices == 0) 37 | { 38 | Console.Clear(); 39 | Console.WriteLine("There is no RTL-SDR device on the system."); 40 | Console.WriteLine("It is not possible to run the demos."); 41 | return; 42 | } 43 | 44 | // ConsoleKey buffer. 45 | ConsoleKey selectedDemo; 46 | 47 | do 48 | { 49 | // Clear the console. 50 | Console.Clear(); 51 | 52 | // Display available demos. 53 | Console.WriteLine("-------------------------------------------------------"); 54 | Console.WriteLine("Which demo do you want to run?"); 55 | Console.WriteLine("-------------------------------------------------------"); 56 | Console.WriteLine(" [1] DEMO 1"); 57 | Console.WriteLine(" Samples will be read asynchronously."); 58 | Console.WriteLine(" Samples will be handled by SamplesAvailable event."); 59 | Console.WriteLine(" [2] DEMO 2"); 60 | Console.WriteLine(" Samples will be read asynchronously."); 61 | Console.WriteLine(" Samples will be read directly from the buffer."); 62 | Console.WriteLine(" [3] DEMO 3"); 63 | Console.WriteLine(" Samples will be read synchronously."); 64 | Console.WriteLine(" Simply print the first 5 samples."); 65 | Console.WriteLine(" [4] DEMO 4"); 66 | Console.WriteLine(" Show RTL-SDR device(s) on the system."); 67 | Console.WriteLine(" Show the detailed parameters of the opened device(s)."); 68 | Console.WriteLine("-------------------------------------------------------"); 69 | 70 | // Display the possibilities. 71 | Console.Write("Please select [1, 2, 3, 4 or ESC to quit]: "); 72 | 73 | // Read from the console. 74 | selectedDemo = Console.ReadKey().Key; 75 | Console.WriteLine("\n"); 76 | } while (selectedDemo != ConsoleKey.D1 && 77 | selectedDemo != ConsoleKey.D2 && 78 | selectedDemo != ConsoleKey.D3 && 79 | selectedDemo != ConsoleKey.D4 && 80 | selectedDemo != ConsoleKey.Escape); 81 | 82 | // Run the appropriate demo. 83 | switch (selectedDemo) 84 | { 85 | case ConsoleKey.D1: 86 | Demo1.Run(); 87 | break; 88 | case ConsoleKey.D2: 89 | Demo2.Run(); 90 | break; 91 | case ConsoleKey.D3: 92 | Demo3.Run(); 93 | break; 94 | case ConsoleKey.D4: 95 | Demo4.Run(); 96 | break; 97 | case ConsoleKey.Escape: 98 | break; 99 | } 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /docs/KERBEROS_SDR.md: -------------------------------------------------------------------------------- 1 | # Use Case: KerberosSDR Direction Finding 2 | 3 | ## Objective 4 | Configure RTL-SDR devices in KerberosSDR mode for coherent multichannel reception and direction finding applications. 5 | 6 | ## Scenario 7 | A user has a KerberosSDR (4 coherent RTL-SDR receivers) and wants to perform direction finding or beamforming operations. 8 | 9 | ## Prerequisites 10 | - KerberosSDR hardware (4 synchronized RTL-SDR receivers) 11 | - Understanding of coherent reception requirements 12 | - Appropriate antenna array setup 13 | 14 | ## Implementation 15 | 16 | ### Basic KerberosSDR Setup 17 | 18 | ```csharp 19 | using RtlSdrManager; 20 | 21 | var manager = RtlSdrDeviceManager.Instance; 22 | 23 | // Open all 4 KerberosSDR channels 24 | for (uint i = 0; i < 4; i++) 25 | { 26 | manager.OpenManagedDevice(i, $"kerberos-ch{i}"); 27 | } 28 | 29 | // Enable KerberosSDR mode on all channels 30 | for (int i = 0; i < 4; i++) 31 | { 32 | var device = manager[$"kerberos-ch{i}"]; 33 | 34 | // Enable KerberosSDR mode for coherent operation 35 | device.KerberosSDRMode = KerberosSDRModes.Enabled; 36 | 37 | // Configure identical parameters for all channels 38 | device.CenterFrequency = Frequency.FromMHz(433); 39 | device.SampleRate = Frequency.FromMHz(2.4); 40 | device.TunerGainMode = TunerGainModes.Manual; 41 | device.TunerGain = 296; // 29.6 dB 42 | device.AGCMode = AGCModes.Disabled; 43 | 44 | // Configure buffer settings 45 | device.MaxAsyncBufferSize = 512 * 1024; 46 | device.DropSamplesOnFullBuffer = true; 47 | 48 | device.ResetDeviceBuffer(); 49 | } 50 | 51 | Console.WriteLine("All 4 KerberosSDR channels configured"); 52 | ``` 53 | 54 | ### Enable Frequency Dithering 55 | 56 | ```csharp 57 | // Enable frequency dithering to reduce DC spike 58 | for (int i = 0; i < 4; i++) 59 | { 60 | var device = manager[$"kerberos-ch{i}"]; 61 | device.FrequencyDitheringMode = FrequencyDitheringModes.Enabled; 62 | } 63 | 64 | Console.WriteLine("Frequency dithering enabled for improved DC offset handling"); 65 | ``` 66 | 67 | ### GPIO Control for External Hardware 68 | 69 | ```csharp 70 | // Control GPIO pins for external switching or control 71 | var mainDevice = manager["kerberos-ch0"]; 72 | 73 | // Enable GPIO pin 1 (e.g., for antenna switching) 74 | mainDevice.SetGPIO(1, GPIOModes.Enabled); 75 | Console.WriteLine("GPIO 1 enabled"); 76 | 77 | // Disable GPIO pin 1 78 | mainDevice.SetGPIO(1, GPIOModes.Disabled); 79 | Console.WriteLine("GPIO 1 disabled"); 80 | ``` 81 | 82 | ### Synchronous Sample Acquisition 83 | 84 | ```csharp 85 | // Start all channels simultaneously for coherent reception 86 | for (int i = 0; i < 4; i++) 87 | { 88 | manager[$"kerberos-ch{i}"].StartReadSamplesAsync(); 89 | } 90 | 91 | // Read samples from all channels 92 | while (true) 93 | { 94 | IQData[] channelData = new IQData[4]; 95 | bool allChannelsReady = true; 96 | 97 | for (int i = 0; i < 4; i++) 98 | { 99 | if (!manager[$"kerberos-ch{i}"].AsyncBuffer.TryDequeue(out channelData[i])) 100 | { 101 | allChannelsReady = false; 102 | break; 103 | } 104 | } 105 | 106 | if (allChannelsReady) 107 | { 108 | // Process coherent data from all 4 channels 109 | ProcessCoherentSamples(channelData); 110 | } 111 | else 112 | { 113 | Thread.Sleep(10); 114 | } 115 | } 116 | 117 | void ProcessCoherentSamples(IQData[] samples) 118 | { 119 | // Implement direction finding or beamforming algorithm here 120 | Console.WriteLine($"Processing {samples[0].Length} samples from 4 channels"); 121 | } 122 | ``` 123 | 124 | ## Expected Results 125 | - All 4 channels operate coherently with synchronized sampling 126 | - Frequency dithering reduces DC offset artifacts 127 | - GPIO control enables external hardware integration 128 | - Samples can be phase-compared for direction finding 129 | 130 | ## Notes 131 | - KerberosSDR mode ensures clock synchronization between channels 132 | - All channels must use identical configuration (frequency, sample rate, gain) 133 | - Frequency dithering slightly varies the center frequency to reduce DC spike 134 | - GPIO control can be used for antenna switching or bias tee control 135 | - Sample alignment is critical for direction finding accuracy 136 | - Consider using manual gain to ensure consistent signal levels across channels 137 | -------------------------------------------------------------------------------- /docs/BIAS_TEE.md: -------------------------------------------------------------------------------- 1 | # Use Case: Bias Tee Control 2 | 3 | ## Objective 4 | Enable and control the bias tee to power active antennas or LNAs directly from the RTL-SDR device. 5 | 6 | ## Scenario 7 | A user has an active antenna or LNA that requires DC power and wants to supply it through the coaxial cable using the RTL-SDR's bias tee feature. 8 | 9 | ## Prerequisites 10 | - RTL-SDR device with bias tee support (RTL-SDR Blog V3, etc.) 11 | - Active antenna or LNA that accepts bias tee power 12 | - Understanding of voltage/current requirements 13 | 14 | ## Implementation 15 | 16 | ### Enabling Bias Tee 17 | 18 | ```csharp 19 | using RtlSdrManager; 20 | 21 | var manager = RtlSdrDeviceManager.Instance; 22 | manager.OpenManagedDevice(0, "my-device"); 23 | 24 | // Enable bias tee to power active antenna 25 | manager["my-device"].BiasTeeMode = BiasTeeModes.Enabled; 26 | 27 | Console.WriteLine("Bias tee enabled - antenna is now powered"); 28 | 29 | // Configure device normally 30 | manager["my-device"].CenterFrequency = Frequency.FromMHz(1090); 31 | manager["my-device"].SampleRate = Frequency.FromMHz(2); 32 | manager["my-device"].TunerGainMode = TunerGainModes.AGC; 33 | manager["my-device"].AGCMode = AGCModes.Enabled; 34 | 35 | // Start receiving 36 | manager["my-device"].ResetDeviceBuffer(); 37 | manager["my-device"].StartReadSamplesAsync(); 38 | ``` 39 | 40 | ### Disabling Bias Tee 41 | 42 | ```csharp 43 | // Disable bias tee when done or when changing antennas 44 | manager["my-device"].BiasTeeMode = BiasTeeModes.Disabled; 45 | 46 | Console.WriteLine("Bias tee disabled - antenna power removed"); 47 | ``` 48 | 49 | ### Safe Bias Tee Usage Pattern 50 | 51 | ```csharp 52 | void UseBiasTee(Action receiveAction) 53 | { 54 | var device = manager["my-device"]; 55 | 56 | try 57 | { 58 | // Enable bias tee 59 | device.BiasTeeMode = BiasTeeModes.Enabled; 60 | Console.WriteLine("Bias tee ON"); 61 | 62 | // Wait for LNA to stabilize 63 | Thread.Sleep(500); 64 | 65 | // Perform reception 66 | receiveAction(); 67 | } 68 | finally 69 | { 70 | // Always disable bias tee when done 71 | device.BiasTeeMode = BiasTeeModes.Disabled; 72 | Console.WriteLine("Bias tee OFF"); 73 | } 74 | } 75 | 76 | // Usage 77 | UseBiasTee(() => 78 | { 79 | // Your reception code here 80 | device.StartReadSamplesAsync(); 81 | Thread.Sleep(10000); // Receive for 10 seconds 82 | device.StopReadSamplesAsync(); 83 | }); 84 | ``` 85 | 86 | ### Application-Specific Configuration 87 | 88 | ```csharp 89 | // For ADS-B reception with powered LNA 90 | void ConfigureADSBWithLNA() 91 | { 92 | var device = manager["adsb-device"]; 93 | 94 | // Enable bias tee for LNA 95 | device.BiasTeeMode = BiasTeeModes.Enabled; 96 | 97 | // Reduce gain since LNA provides amplification 98 | device.TunerGainMode = TunerGainModes.Manual; 99 | device.TunerGain = 150; // 15.0 dB (lower than without LNA) 100 | 101 | device.CenterFrequency = Frequency.FromMHz(1090); 102 | device.SampleRate = Frequency.FromMHz(2); 103 | 104 | Console.WriteLine("ADS-B receiver configured with powered LNA"); 105 | } 106 | 107 | // For satellite reception with powered antenna 108 | void ConfigureSatelliteReception() 109 | { 110 | var device = manager["sat-device"]; 111 | 112 | // Enable bias tee for active antenna 113 | device.BiasTeeMode = BiasTeeModes.Enabled; 114 | 115 | device.CenterFrequency = Frequency.FromMHz(137.5); // NOAA satellites 116 | device.SampleRate = Frequency.FromMHz(2.4); 117 | device.TunerGainMode = TunerGainModes.AGC; 118 | device.AGCMode = AGCModes.Enabled; 119 | 120 | Console.WriteLine("Satellite receiver configured with powered antenna"); 121 | } 122 | ``` 123 | 124 | ## Expected Results 125 | - Bias tee supplies DC power through the coaxial cable 126 | - Active antenna or LNA receives power and operates 127 | - Signal quality improves due to active amplification 128 | - Gain settings may need adjustment due to LNA gain 129 | 130 | ## Safety Warnings 131 | 132 | ⚠️ **IMPORTANT SAFETY INFORMATION:** 133 | 134 | - **NEVER** enable bias tee with passive antennas or unamplified connections 135 | - **NEVER** enable bias tee when the antenna port is connected to another receiver 136 | - **ALWAYS** check your equipment specifications before enabling bias tee 137 | - **ALWAYS** disable bias tee before disconnecting antennas 138 | - Incorrect use can damage equipment 139 | 140 | ## Notes 141 | - Typical bias tee voltage: 4.5V - 5V DC 142 | - Current capacity varies by device (typically 50-100 mA) 143 | - Check LNA current requirements before use 144 | - Some devices may not support bias tee 145 | - Bias tee affects all frequencies on that receiver 146 | - Use lower gain settings with powered LNAs to avoid saturation 147 | - Always disable bias tee when switching to passive antennas 148 | -------------------------------------------------------------------------------- /samples/RtlSdrManager.Samples/Demo2.cs: -------------------------------------------------------------------------------- 1 | // RTL-SDR Manager Library for .NET Core 2 | // Copyright (C) 2018-2025 Nandor Toth 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see http://www.gnu.org/licenses. 16 | 17 | using System; 18 | using System.Threading; 19 | using System.Threading.Tasks; 20 | using RtlSdrManager.Exceptions; 21 | using RtlSdrManager.Modes; 22 | 23 | namespace RtlSdrManager.Samples; 24 | 25 | /// 26 | /// Demo for RtlSdrManager. 27 | /// 28 | /// In this demo: 29 | /// - Samples will be received asynchronously. 30 | /// - Samples will be received directly from the buffer. 31 | /// 32 | public static class Demo2 33 | { 34 | /// 35 | /// Run the demo. 36 | /// 37 | public static void Run() 38 | { 39 | // Initialize the Manager instance. 40 | RtlSdrDeviceManager manager = RtlSdrDeviceManager.Instance; 41 | 42 | // Open a managed device and set some parameters. 43 | try 44 | { 45 | manager.OpenManagedDevice(0, "my-rtl-sdr"); 46 | } 47 | catch (RtlSdrDeviceException e) 48 | { 49 | Console.WriteLine(e); 50 | return; 51 | } 52 | catch 53 | { 54 | Console.WriteLine("Failed to open the RTL-SDR device."); 55 | return; 56 | } 57 | 58 | manager["my-rtl-sdr"].CenterFrequency = Frequency.FromMHz(1090); 59 | manager["my-rtl-sdr"].SampleRate = Frequency.FromMHz(2); 60 | manager["my-rtl-sdr"].TunerGainMode = TunerGainModes.AGC; 61 | manager["my-rtl-sdr"].AGCMode = AGCModes.Enabled; 62 | manager["my-rtl-sdr"].MaxAsyncBufferSize = 512 * 1024; 63 | manager["my-rtl-sdr"].DropSamplesOnFullBuffer = true; 64 | manager["my-rtl-sdr"].ResetDeviceBuffer(); 65 | 66 | // Use cancellation token. 67 | using var cts = new CancellationTokenSource(); 68 | CancellationToken token = cts.Token; 69 | 70 | // Start asynchronous sample reading. 71 | manager["my-rtl-sdr"].StartReadSamplesAsync(); 72 | 73 | // Create a task, which will dequeue the items from the buffer. 74 | Task.Factory.StartNew(() => 75 | { 76 | // Counter for demo purposes. 77 | // Only every seventy-five thousandth data will be showed. 78 | int counter = 0; 79 | 80 | // Read samples from the buffer, till cancellation request. 81 | while (!token.IsCancellationRequested) 82 | { 83 | // If the buffer is empty, wait for 0.1 sec. 84 | if (manager["my-rtl-sdr"].AsyncBuffer.IsEmpty) 85 | { 86 | Thread.Sleep(100); 87 | continue; 88 | } 89 | 90 | // Dequeue from the buffer. 91 | if (!manager["my-rtl-sdr"].AsyncBuffer.TryDequeue(out IQData data)) 92 | { 93 | Thread.Sleep(100); 94 | continue; 95 | } 96 | 97 | // Increase the counter. 98 | counter++; 99 | if (counter % 75000 != 0) 100 | { 101 | continue; 102 | } 103 | 104 | // Show the data. 105 | Console.WriteLine(data); 106 | counter = 0; 107 | } 108 | }, token); 109 | 110 | // Create a separated task, which continuously check the buffer's size. 111 | Task.Factory.StartNew(() => 112 | { 113 | while (!token.IsCancellationRequested) 114 | { 115 | int bufferSize = manager["my-rtl-sdr"].AsyncBuffer.Count; 116 | Console.WriteLine($"Still unhandled samples: {bufferSize}"); 117 | Thread.Sleep(250); 118 | } 119 | }, token); 120 | 121 | // Sleep the thread for 5 second before stop the samples reading. 122 | Thread.Sleep(5000); 123 | 124 | // Cancel the tasks. 125 | cts.Cancel(); 126 | 127 | // Stop the reading of the samples. 128 | manager["my-rtl-sdr"].StopReadSamplesAsync(); 129 | 130 | // Close the device. 131 | manager.CloseAllManagedDevice(); 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /docs/DEVICE_MANAGEMENT.md: -------------------------------------------------------------------------------- 1 | # Use Case: Multi-Device Management 2 | 3 | ## Objective 4 | Demonstrate how to enumerate, identify, and manage multiple RTL-SDR devices simultaneously. 5 | 6 | ## Scenario 7 | A system has multiple RTL-SDR devices connected and needs to identify, configure, and use them for different purposes. 8 | 9 | ## Prerequisites 10 | - Multiple RTL-SDR devices connected 11 | - Each device potentially having different characteristics 12 | 13 | ## Implementation 14 | 15 | ### Device Enumeration 16 | 17 | ```csharp 18 | using RtlSdrManager; 19 | 20 | var manager = RtlSdrDeviceManager.Instance; 21 | 22 | // Get total number of connected devices 23 | var deviceCount = manager.Count; 24 | Console.WriteLine($"Found {deviceCount} RTL-SDR device(s)"); 25 | 26 | if (deviceCount == 0) 27 | { 28 | Console.WriteLine("No devices found. Please connect an RTL-SDR device."); 29 | return; 30 | } 31 | 32 | // Enumerate all devices and show their information 33 | for (uint i = 0; i < deviceCount; i++) 34 | { 35 | var deviceInfo = manager.GetDeviceInfo(i); 36 | 37 | Console.WriteLine($"\nDevice {i}:"); 38 | Console.WriteLine($" Manufacturer: {deviceInfo.Manufacturer}"); 39 | Console.WriteLine($" Product: {deviceInfo.Product}"); 40 | Console.WriteLine($" Serial: {deviceInfo.Serial}"); 41 | } 42 | ``` 43 | 44 | ### Opening Multiple Devices 45 | 46 | ```csharp 47 | // Open multiple devices with descriptive names 48 | if (deviceCount >= 2) 49 | { 50 | manager.OpenManagedDevice(0, "adsb-receiver"); 51 | manager.OpenManagedDevice(1, "fm-radio"); 52 | 53 | Console.WriteLine("Opened 2 devices with friendly names"); 54 | } 55 | 56 | // List all open devices 57 | var openDevices = manager.GetManagedDeviceNames(); 58 | Console.WriteLine("\nOpen devices:"); 59 | foreach (var name in openDevices) 60 | { 61 | Console.WriteLine($" - {name}"); 62 | } 63 | ``` 64 | 65 | ### Device-Specific Configuration 66 | 67 | ```csharp 68 | // Configure first device for ADS-B reception 69 | var adsbDevice = manager["adsb-receiver"]; 70 | adsbDevice.CenterFrequency = Frequency.FromMHz(1090); 71 | adsbDevice.SampleRate = Frequency.FromMHz(2); 72 | adsbDevice.TunerGainMode = TunerGainModes.AGC; 73 | adsbDevice.AGCMode = AGCModes.Enabled; 74 | 75 | // Configure second device for FM radio 76 | var fmDevice = manager["fm-radio"]; 77 | fmDevice.CenterFrequency = Frequency.FromMHz(100); 78 | fmDevice.SampleRate = Frequency.FromMHz(1.2); 79 | fmDevice.TunerGainMode = TunerGainModes.Manual; 80 | fmDevice.TunerGain = 200; // 20.0 dB 81 | 82 | Console.WriteLine("Both devices configured for different applications"); 83 | ``` 84 | 85 | ### Checking Device Capabilities 86 | 87 | ```csharp 88 | void DisplayDeviceCapabilities(string deviceName) 89 | { 90 | var device = manager[deviceName]; 91 | 92 | Console.WriteLine($"\n{deviceName} Capabilities:"); 93 | Console.WriteLine($" Tuner Type: {device.TunerType}"); 94 | Console.WriteLine($" Available Gains: {string.Join(", ", device.TunerGains.Select(g => $"{g * 0.1:F1} dB"))}"); 95 | Console.WriteLine($" Current Frequency: {device.CenterFrequency}"); 96 | Console.WriteLine($" Current Sample Rate: {device.SampleRate}"); 97 | } 98 | 99 | foreach (var deviceName in manager.GetManagedDeviceNames()) 100 | { 101 | DisplayDeviceCapabilities(deviceName); 102 | } 103 | ``` 104 | 105 | ### Closing Devices 106 | 107 | ```csharp 108 | // Close a specific device 109 | manager.CloseManagedDevice("adsb-receiver"); 110 | Console.WriteLine("Closed adsb-receiver device"); 111 | 112 | // Close all devices 113 | manager.CloseAllManagedDevices(); 114 | Console.WriteLine("All devices closed"); 115 | ``` 116 | 117 | ### Device Identification by Serial Number 118 | 119 | ```csharp 120 | // Find device with specific serial number 121 | uint? FindDeviceBySerial(string serial) 122 | { 123 | for (uint i = 0; i < manager.Count; i++) 124 | { 125 | var info = manager.GetDeviceInfo(i); 126 | if (info.Serial == serial) 127 | { 128 | return i; 129 | } 130 | } 131 | return null; 132 | } 133 | 134 | var targetSerial = "00000001"; 135 | var deviceIndex = FindDeviceBySerial(targetSerial); 136 | 137 | if (deviceIndex.HasValue) 138 | { 139 | manager.OpenManagedDevice(deviceIndex.Value, "target-device"); 140 | Console.WriteLine($"Found and opened device with serial {targetSerial}"); 141 | } 142 | else 143 | { 144 | Console.WriteLine($"Device with serial {targetSerial} not found"); 145 | } 146 | ``` 147 | 148 | ## Expected Results 149 | - All connected devices are discovered 150 | - Device information is accurately retrieved 151 | - Multiple devices can be operated simultaneously 152 | - Each device can have independent configuration 153 | - Devices can be identified by serial number 154 | 155 | ## Notes 156 | - Device indices are 0-based 157 | - Friendly names make device management more intuitive 158 | - DeviceInfo provides manufacturer, product, and serial number 159 | - Each device operates independently with its own buffer 160 | - Closing devices properly releases hardware resources 161 | - Serial numbers can be used for persistent device identification across reconnects 162 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, caste, color, religion, or sexual 10 | identity and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | * Demonstrating empathy and kindness toward other people 21 | * Being respectful of differing opinions, viewpoints, and experiences 22 | * Giving and gracefully accepting constructive feedback 23 | * Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | * Focusing on what is best not just for us as individuals, but for the overall 26 | community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | * The use of sexualized language or imagery, and sexual attention or advances of 31 | any kind 32 | * Trolling, insulting or derogatory comments, and personal or political attacks 33 | * Public or private harassment 34 | * Publishing others' private information, such as a physical or electronic 35 | address, without their explicit permission 36 | * Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies when 54 | an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official e-mail address, 56 | posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement at 63 | dev@nandortoth.com. 64 | 65 | All complaints will be reviewed and investigated promptly and fairly. 66 | 67 | All community leaders are obligated to respect the privacy and security of the 68 | reporter of any incident. 69 | 70 | ## Enforcement Guidelines 71 | 72 | Community leaders will follow these Community Impact Guidelines in determining 73 | the consequences for any action they deem in violation of this Code of Conduct: 74 | 75 | ### 1. Correction 76 | 77 | **Community Impact**: Use of inappropriate language or other behavior deemed 78 | unprofessional or unwelcome in the community. 79 | 80 | **Consequence**: A private, written warning from community leaders, providing 81 | clarity around the nature of the violation and an explanation of why the 82 | behavior was inappropriate. A public apology may be requested. 83 | 84 | ### 2. Warning 85 | 86 | **Community Impact**: A violation through a single incident or series of 87 | actions. 88 | 89 | **Consequence**: A warning with consequences for continued behavior. No 90 | interaction with the people involved, including unsolicited interaction with 91 | those enforcing the Code of Conduct, for a specified period of time. This 92 | includes avoiding interactions in community spaces as well as external channels 93 | like social media. Violating these terms may lead to a temporary or permanent 94 | ban. 95 | 96 | ### 3. Temporary Ban 97 | 98 | **Community Impact**: A serious violation of community standards, including 99 | sustained inappropriate behavior. 100 | 101 | **Consequence**: A temporary ban from any sort of interaction or public 102 | communication with the community for a specified period of time. No public or 103 | private interaction with the people involved, including unsolicited interaction 104 | with those enforcing the Code of Conduct, is allowed during this period. 105 | Violating these terms may lead to a permanent ban. 106 | 107 | ### 4. Permanent Ban 108 | 109 | **Community Impact**: Demonstrating a pattern of violation of community 110 | standards, including sustained inappropriate behavior, harassment of an 111 | individual, or aggression toward or disparagement of classes of individuals. 112 | 113 | **Consequence**: A permanent ban from any sort of public interaction within the 114 | community. 115 | 116 | ## Attribution 117 | 118 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 119 | version 2.1, available at 120 | [https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1]. 121 | 122 | Community Impact Guidelines were inspired by 123 | [Mozilla's code of conduct enforcement ladder][Mozilla CoC]. 124 | 125 | For answers to common questions about this code of conduct, see the FAQ at 126 | [https://www.contributor-covenant.org/faq][FAQ]. Translations are available at 127 | [https://www.contributor-covenant.org/translations][translations]. 128 | 129 | [homepage]: https://www.contributor-covenant.org 130 | [v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html 131 | [Mozilla CoC]: https://github.com/mozilla/diversity 132 | [FAQ]: https://www.contributor-covenant.org/faq 133 | [translations]: https://www.contributor-covenant.org/translations 134 | -------------------------------------------------------------------------------- /samples/RtlSdrManager.Samples/Demo4.cs: -------------------------------------------------------------------------------- 1 | // RTL-SDR Manager Library for .NET Core 2 | // Copyright (C) 2018-2025 Nandor Toth 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see http://www.gnu.org/licenses. 16 | 17 | using System; 18 | using System.Linq; 19 | using RtlSdrManager.Exceptions; 20 | using RtlSdrManager.Modes; 21 | 22 | namespace RtlSdrManager.Samples; 23 | 24 | /// 25 | /// Demo for RtlSdrManager. 26 | /// 27 | /// In this demo: 28 | /// - Show RTL-SDR device(s) on the system. 29 | /// - Show the detailed parameter of the opened device(s). 30 | /// 31 | public static class Demo4 32 | { 33 | /// 34 | /// Main function of the demo. 35 | /// 36 | public static void Run() 37 | { 38 | // Initialize the Manager instance. 39 | RtlSdrDeviceManager manager = RtlSdrDeviceManager.Instance; 40 | 41 | // Go through on all the devices on the system. 42 | Console.WriteLine("AVAILABLE DEVICES"); 43 | foreach (DeviceInfo device in manager.Devices.Values) 44 | { 45 | Console.WriteLine($" Device (index: {device.Index}):\n" + 46 | $" {"Manufacturer",-12}: {device.Manufacturer}\n" + 47 | $" {"Product",-12}: {device.ProductType}\n" + 48 | $" {"Serial",-12}: {device.Serial}\n" + 49 | $" {"Name",-12}: {device.Name}\n"); 50 | } 51 | 52 | // Quick check about the devices, before opening any device. 53 | Console.WriteLine("DETAILS - BEFORE MANAGING ANY OF THEM"); 54 | Console.WriteLine($" Number of device(s) on the system: {manager.CountDevices}\n" + 55 | $" Managed device(s) on the system: {manager.CountManagedDevices}\n"); 56 | 57 | // Open a managed device and set some parameters. 58 | try 59 | { 60 | manager.OpenManagedDevice(0, "my-rtl-sdr"); 61 | } 62 | catch (RtlSdrDeviceException e) 63 | { 64 | Console.WriteLine(e); 65 | return; 66 | } 67 | catch 68 | { 69 | Console.WriteLine("Failed to open the RTL-SDR device."); 70 | return; 71 | } 72 | 73 | manager["my-rtl-sdr"].CenterFrequency = Frequency.FromMHz(1090); 74 | manager["my-rtl-sdr"].SampleRate = Frequency.FromMHz(2); 75 | manager["my-rtl-sdr"].TunerGainMode = TunerGainModes.AGC; 76 | manager["my-rtl-sdr"].FrequencyCorrection = 10; 77 | manager["my-rtl-sdr"].AGCMode = AGCModes.Enabled; 78 | manager["my-rtl-sdr"].TestMode = TestModes.Disabled; 79 | manager["my-rtl-sdr"].ResetDeviceBuffer(); 80 | 81 | // Quick check about the devices, after opening one. 82 | Console.WriteLine("DETAILS - AFTER OPENING ONE"); 83 | Console.WriteLine($" Number of device(s) on the system: {manager.CountDevices}\n" + 84 | $" Managed device(s) on the system: {manager.CountManagedDevices}\n"); 85 | 86 | 87 | // Go through on the managed devices (using manager's IEnumerable). 88 | Console.WriteLine("OPENED DEVICES"); 89 | foreach (RtlSdrManagedDevice device in manager) 90 | { 91 | Console.WriteLine($" Device (index: {device.DeviceInfo.Index}):\n" + 92 | $" {"Manufacturer",-22}: {device.DeviceInfo.Manufacturer}\n" + 93 | $" {"Product",-22}: {device.DeviceInfo.ProductType}\n" + 94 | $" {"Serial",-22}: {device.DeviceInfo.Serial}\n" + 95 | $" {"Name",-22}: {device.DeviceInfo.Name}\n" + 96 | $" {"Tuner type",-22}: {device.TunerType}\n" + 97 | $" {"Center frequency",-22}: {device.CenterFrequency.MHz} MHz\n" + 98 | $" {"Crystal frequency",-22}: {device.CrystalFrequency}\n" + 99 | $" {"Frequency correction",-22}: {device.FrequencyCorrection} ppm\n" + 100 | $" {"Bandwidth selection",-22}: {device.TunerBandwidthSelectionMode}\n" + 101 | $" {"Sample rate",-22}: {device.SampleRate.MHz} MHz\n" + 102 | $" {"Direct sampling mode",-22}: {device.DirectSamplingMode}\n" + 103 | $" {"AGC mode",-22}: {device.AGCMode}\n" + 104 | $" {"Tuner gain mode",-22}: {device.TunerGainMode}\n" + 105 | $" {"Offset tuning mode",-22}: {device.OffsetTuningMode}\n" + 106 | $" {"KerberosSDR mode",-22}: {device.KerberosSDRMode}\n" + 107 | $" {"Frequency dithering",-22}: {device.FrequencyDitheringMode}\n" + 108 | $" {"Test mode",-22}: {device.TestMode}"); 109 | Console.Write($" {"Supported tuner gains",-22}: "); 110 | 111 | // Display supported gains in a fancy format. 112 | for (int i = 0; i < device.SupportedTunerGains.Count; i++) 113 | { 114 | if (i % 5 == 0 && i != 0) 115 | { 116 | Console.Write($"\n {" ",-22}: "); 117 | } 118 | 119 | Console.Write($"{device.SupportedTunerGains.ElementAt(i),4:F1} dB "); 120 | } 121 | 122 | Console.WriteLine("\n"); 123 | } 124 | 125 | // Close the device. 126 | manager.CloseAllManagedDevice(); 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /docs/FREQUENCY_CORRECTION.md: -------------------------------------------------------------------------------- 1 | # Use Case: Frequency Correction and Crystal Tuning 2 | 3 | ## Objective 4 | Calibrate and correct frequency offset caused by crystal oscillator inaccuracies in RTL-SDR devices. 5 | 6 | ## Scenario 7 | A user notices that received frequencies are slightly off from expected values and needs to calibrate the device's frequency accuracy. 8 | 9 | ## Prerequisites 10 | - RTL-SDR device 11 | - Known reference signal (FM station, GSM tower, or calibration signal) 12 | - Method to measure frequency offset (spectrum analyzer, SDR software) 13 | 14 | ## Implementation 15 | 16 | ### Setting Frequency Correction (PPM) 17 | 18 | ```csharp 19 | using RtlSdrManager; 20 | 21 | var manager = RtlSdrDeviceManager.Instance; 22 | manager.OpenManagedDevice(0, "my-device"); 23 | 24 | // Set frequency correction in parts per million (PPM) 25 | // Positive values increase frequency, negative values decrease it 26 | manager["my-device"].FrequencyCorrection = 52; // Example: +52 PPM 27 | 28 | Console.WriteLine($"Frequency correction set to {manager["my-device"].FrequencyCorrection} PPM"); 29 | 30 | // Configure device 31 | manager["my-device"].CenterFrequency = Frequency.FromMHz(100); 32 | manager["my-device"].SampleRate = Frequency.FromMHz(2); 33 | ``` 34 | 35 | ### Calibration Process Using Known Signal 36 | 37 | ```csharp 38 | void CalibrateDevice(string deviceName, double knownFrequencyMHz) 39 | { 40 | var device = manager[deviceName]; 41 | 42 | Console.WriteLine($"Calibrating device using signal at {knownFrequencyMHz} MHz"); 43 | 44 | // Start with no correction 45 | device.FrequencyCorrection = 0; 46 | device.CenterFrequency = Frequency.FromMHz(knownFrequencyMHz); 47 | device.SampleRate = Frequency.FromMHz(2); 48 | device.TunerGainMode = TunerGainModes.AGC; 49 | device.AGCMode = AGCModes.Enabled; 50 | 51 | device.ResetDeviceBuffer(); 52 | device.StartReadSamplesAsync(); 53 | 54 | Console.WriteLine("Listen to the signal and measure the frequency offset"); 55 | Console.WriteLine("Then calculate PPM = (measured_freq - actual_freq) / actual_freq * 1,000,000"); 56 | Console.WriteLine("\nExample: If 100.0 MHz signal appears at 100.005 MHz:"); 57 | Console.WriteLine("PPM = (100.005 - 100.0) / 100.0 * 1,000,000 = 50 PPM"); 58 | } 59 | 60 | // Example: Calibrate using FM radio station 61 | CalibrateDevice("my-device", 100.0); // Known FM station at 100.0 MHz 62 | ``` 63 | 64 | ### Applying Calculated PPM Correction 65 | 66 | ```csharp 67 | void ApplyCalibration(string deviceName, double measuredFreqMHz, double actualFreqMHz) 68 | { 69 | var device = manager[deviceName]; 70 | 71 | // Calculate PPM offset 72 | double ppmOffset = ((measuredFreqMHz - actualFreqMHz) / actualFreqMHz) * 1_000_000; 73 | 74 | // Round to nearest integer 75 | int ppmCorrection = (int)Math.Round(ppmOffset); 76 | 77 | // Apply correction 78 | device.FrequencyCorrection = ppmCorrection; 79 | 80 | Console.WriteLine($"Measured: {measuredFreqMHz} MHz"); 81 | Console.WriteLine($"Actual: {actualFreqMHz} MHz"); 82 | Console.WriteLine($"Calculated PPM: {ppmOffset:F2}"); 83 | Console.WriteLine($"Applied correction: {ppmCorrection} PPM"); 84 | } 85 | 86 | // Example: If 100.0 MHz FM station appears at 100.005 MHz 87 | ApplyCalibration("my-device", 100.005, 100.0); 88 | ``` 89 | 90 | ### Testing Calibration Accuracy 91 | 92 | ```csharp 93 | void VerifyCalibration(string deviceName, params double[] knownFrequencies) 94 | { 95 | var device = manager[deviceName]; 96 | 97 | Console.WriteLine($"\nVerifying calibration with {device.FrequencyCorrection} PPM correction:"); 98 | 99 | foreach (var freq in knownFrequencies) 100 | { 101 | device.CenterFrequency = Frequency.FromMHz(freq); 102 | Thread.Sleep(500); // Let it stabilize 103 | 104 | Console.WriteLine($" Tuned to: {freq} MHz - Check if signal is centered"); 105 | } 106 | } 107 | 108 | // Test with multiple known frequencies 109 | VerifyCalibration("my-device", 100.0, 145.0, 433.0, 1090.0); 110 | ``` 111 | 112 | ### Advanced: Temperature Compensation 113 | 114 | ```csharp 115 | // Store PPM values at different temperatures 116 | Dictionary temperaturePPM = new Dictionary 117 | { 118 | { 20.0, 52 }, // Room temperature 119 | { 30.0, 48 }, // Warm 120 | { 15.0, 56 } // Cool 121 | }; 122 | 123 | void ApplyTemperatureCompensation(string deviceName, double currentTemp) 124 | { 125 | var device = manager[deviceName]; 126 | 127 | // Find closest temperature measurement 128 | var closestTemp = temperaturePPM.Keys 129 | .OrderBy(t => Math.Abs(t - currentTemp)) 130 | .First(); 131 | 132 | device.FrequencyCorrection = temperaturePPM[closestTemp]; 133 | 134 | Console.WriteLine($"Applied PPM correction for {closestTemp}°C: {device.FrequencyCorrection} PPM"); 135 | } 136 | ``` 137 | 138 | ### Crystal Frequency Information 139 | 140 | ```csharp 141 | // Get crystal frequencies (RTL2832 and tuner IC) 142 | var device = manager["my-device"]; 143 | 144 | var rtlFreq = device.CrystalFrequency; 145 | Console.WriteLine($"RTL2832 Crystal Frequency: {rtlFreq.Hz} Hz"); 146 | 147 | var tunerFreq = device.TunerCrystalFrequency; 148 | Console.WriteLine($"Tuner Crystal Frequency: {tunerFreq.Hz} Hz"); 149 | 150 | // Note: These are nominal values and don't reflect actual accuracy 151 | ``` 152 | 153 | ## Expected Results 154 | - Received signals appear at correct frequencies 155 | - Frequency accuracy improves significantly 156 | - Calibration remains stable over time (temperature dependent) 157 | - PPM correction applies to all tuned frequencies 158 | 159 | ## Calibration Tips 160 | 161 | 1. **Use Multiple Reference Frequencies**: Calibrate using signals across the spectrum 162 | 2. **Account for Temperature**: PPM offset varies with temperature 163 | 3. **Common PPM Ranges**: Most RTL-SDR devices have -100 to +100 PPM offset 164 | 4. **Stable Reference Signals**: 165 | - FM radio stations 166 | - GSM base stations 167 | - ADS-B signals (1090 MHz) 168 | - Amateur radio beacons 169 | 170 | ## Notes 171 | - PPM correction is device-specific and should be saved per device 172 | - Temperature changes affect crystal frequency 173 | - Cheap RTL-SDR devices may have larger PPM offsets 174 | - Calibration improves with device warm-up time 175 | - Some applications (narrow-band) are more sensitive to frequency errors 176 | - PPM value is applied as: actual_freq = tuned_freq * (1 + PPM/1,000,000) 177 | -------------------------------------------------------------------------------- /src/RtlSdrManager/Interop/LibResolver.cs: -------------------------------------------------------------------------------- 1 | // RTL-SDR Manager Library for .NET 2 | // Copyright (C) 2018-2025 Nandor Toth 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see http://www.gnu.org/licenses. 16 | // 17 | // The source code uses "rtl-sdr" that was released under GPLv2 license. 18 | // The original work that may be used under the terms of that license, 19 | // please see https://github.com/steve-m/librtlsdr. 20 | // 21 | // rtl-sdr, turns your Realtek RTL2832 based DVB dongle into a SDR receiver 22 | // Copyright (C) 2012-2013 by Steve Markgraf 23 | // Copyright (C) 2012 by Dimitri Stolnikov 24 | // 25 | // This program is free software: you can redistribute it and/or modify 26 | // it under the terms of the GNU General Public License as published by 27 | // the Free Software Foundation, either version 2 of the License, or 28 | // (at your option) any later version. 29 | // 30 | // This program is distributed in the hope that it will be useful, 31 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 32 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 33 | // GNU General Public License for more details. 34 | // 35 | // You should have received a copy of the GNU General Public License 36 | // along with this program. If not, see http://www.gnu.org/licenses. 37 | 38 | using System; 39 | using System.IO; 40 | using System.Reflection; 41 | using System.Runtime.InteropServices; 42 | 43 | namespace RtlSdrManager.Interop; 44 | 45 | /// 46 | /// Handles cross-platform native library resolution for librtlsdr. 47 | /// 48 | internal static class LibResolver 49 | { 50 | private const string LibraryName = "librtlsdr"; 51 | 52 | /// 53 | /// Registers the custom DLL import resolver for the assembly. 54 | /// Call this once during initialization. 55 | /// 56 | public static void Register(Assembly assembly) => NativeLibrary.SetDllImportResolver(assembly, Resolve); 57 | 58 | /// 59 | /// Custom DLL import resolver for finding librtlsdr on different platforms. 60 | /// 61 | private static IntPtr Resolve(string libraryName, Assembly assembly, DllImportSearchPath? searchPath) 62 | { 63 | IntPtr handle = IntPtr.Zero; 64 | 65 | // Only handle librtlsdr 66 | if (libraryName != LibraryName) 67 | { 68 | return handle; 69 | } 70 | 71 | // 1. Try environment variable override first 72 | string? customPath = Environment.GetEnvironmentVariable("RTLSDR_LIBRARY_PATH"); 73 | if (!string.IsNullOrEmpty(customPath) && File.Exists(customPath)) 74 | { 75 | if (NativeLibrary.TryLoad(customPath, out handle)) 76 | { 77 | return handle; 78 | } 79 | } 80 | 81 | // 2. Try standard resolution 82 | if (NativeLibrary.TryLoad(libraryName, assembly, searchPath, out handle)) 83 | { 84 | return handle; 85 | } 86 | 87 | // 3. Platform-specific fallback paths 88 | if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) 89 | { 90 | return TryLoadMacOs(out handle) ? handle : IntPtr.Zero; 91 | } 92 | 93 | if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) 94 | { 95 | return TryLoadLinux(out handle) ? handle : IntPtr.Zero; 96 | } 97 | 98 | if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) 99 | { 100 | return TryLoadWindows(out handle) ? handle : IntPtr.Zero; 101 | } 102 | 103 | return IntPtr.Zero; 104 | } 105 | 106 | private static bool TryLoadMacOs(out IntPtr handle) 107 | { 108 | string[] macPaths = 109 | [ 110 | "/opt/homebrew/lib/librtlsdr.dylib", // Apple Silicon 111 | "/opt/homebrew/lib/librtlsdr.0.dylib", 112 | "/usr/local/lib/librtlsdr.dylib", // Intel Mac 113 | "/usr/local/lib/librtlsdr.0.dylib" 114 | ]; 115 | 116 | foreach (string path in macPaths) 117 | { 118 | if (File.Exists(path) && NativeLibrary.TryLoad(path, out handle)) 119 | { 120 | return true; 121 | } 122 | } 123 | 124 | handle = IntPtr.Zero; 125 | return false; 126 | } 127 | 128 | private static bool TryLoadLinux(out IntPtr handle) 129 | { 130 | string[] linuxPaths = 131 | [ 132 | "/usr/lib/librtlsdr.so", 133 | "/usr/lib/x86_64-linux-gnu/librtlsdr.so", 134 | "/usr/lib/aarch64-linux-gnu/librtlsdr.so", 135 | "/usr/local/lib/librtlsdr.so" 136 | ]; 137 | 138 | foreach (string path in linuxPaths) 139 | { 140 | if (File.Exists(path) && NativeLibrary.TryLoad(path, out handle)) 141 | { 142 | return true; 143 | } 144 | } 145 | 146 | handle = IntPtr.Zero; 147 | return false; 148 | } 149 | 150 | private static bool TryLoadWindows(out IntPtr handle) 151 | { 152 | string[] windowsPaths = 153 | [ 154 | "rtlsdr.dll", 155 | "librtlsdr.dll", 156 | Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), 157 | "rtl-sdr", 158 | "rtlsdr.dll"), 159 | Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86), 160 | "rtl-sdr", 161 | "rtlsdr.dll") 162 | ]; 163 | 164 | foreach (string path in windowsPaths) 165 | { 166 | if (NativeLibrary.TryLoad(path, out handle)) 167 | { 168 | return true; 169 | } 170 | } 171 | 172 | handle = IntPtr.Zero; 173 | return false; 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /src/RtlSdrManager/Interop/ConsoleOutputSuppressor.cs: -------------------------------------------------------------------------------- 1 | // RTL-SDR Manager Library for .NET 2 | // Copyright (C) 2018-2025 Nandor Toth 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see http://www.gnu.org/licenses. 16 | 17 | using System; 18 | using System.Runtime.InteropServices; 19 | 20 | namespace RtlSdrManager.Interop; 21 | 22 | /// 23 | /// Utility class to temporarily suppress console output (stdout and stderr) at the native level. 24 | /// This is useful for suppressing diagnostic messages from the native librtlsdr library. 25 | /// 26 | internal sealed partial class ConsoleOutputSuppressor : IDisposable 27 | { 28 | private readonly int _oldStdout; 29 | private readonly int _oldStderr; 30 | private readonly bool _isSupported; 31 | 32 | /// 33 | /// Initializes a new instance of the class. 34 | /// Redirects native stdout and stderr to /dev/null (Unix) or NUL (Windows). 35 | /// 36 | public ConsoleOutputSuppressor() 37 | { 38 | _isSupported = false; 39 | 40 | try 41 | { 42 | if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) 43 | { 44 | // Windows implementation 45 | // Flush stdout and stderr buffers first 46 | _ = Windows.fflush(IntPtr.Zero); // Flush all streams 47 | 48 | _oldStdout = Windows._dup(1); // Duplicate stdout 49 | _oldStderr = Windows._dup(2); // Duplicate stderr 50 | 51 | // Open NUL device 52 | int nullFile = Windows._open("NUL", Windows.O_WRONLY); 53 | if (nullFile == -1) 54 | { 55 | return; 56 | } 57 | 58 | _ = Windows._dup2(nullFile, 1); // Redirect stdout to NUL 59 | _ = Windows._dup2(nullFile, 2); // Redirect stderr to NUL 60 | _ = Windows._close(nullFile); 61 | _isSupported = true; 62 | } 63 | else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux) || 64 | RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) 65 | { 66 | // Unix/Linux/macOS implementation 67 | // Flush stdout and stderr buffers first 68 | _ = Unix.fflush(IntPtr.Zero); // Flush all streams 69 | 70 | _oldStdout = Unix.dup(1); // Duplicate stdout 71 | _oldStderr = Unix.dup(2); // Duplicate stderr 72 | 73 | // Open /dev/null 74 | int nullFile = Unix.open("/dev/null", Unix.O_WRONLY, 0); 75 | if (nullFile == -1) 76 | { 77 | return; 78 | } 79 | 80 | _ = Unix.dup2(nullFile, 1); // Redirect stdout to /dev/null 81 | _ = Unix.dup2(nullFile, 2); // Redirect stderr to /dev/null 82 | _ = Unix.close(nullFile); 83 | _isSupported = true; 84 | } 85 | } 86 | catch 87 | { 88 | // If anything fails, silently continue without suppression 89 | _isSupported = false; 90 | } 91 | } 92 | 93 | /// 94 | /// Restores the original stdout and stderr file descriptors. 95 | /// 96 | public void Dispose() 97 | { 98 | if (!_isSupported) 99 | { 100 | return; 101 | } 102 | 103 | try 104 | { 105 | if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) 106 | { 107 | _ = Windows.fflush(IntPtr.Zero); // Flush all streams before restoring 108 | _ = Windows._dup2(_oldStdout, 1); // Restore stdout 109 | _ = Windows._dup2(_oldStderr, 2); // Restore stderr 110 | _ = Windows._close(_oldStdout); 111 | _ = Windows._close(_oldStderr); 112 | } 113 | else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux) || 114 | RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) 115 | { 116 | _ = Unix.fflush(IntPtr.Zero); // Flush all streams before restoring 117 | _ = Unix.dup2(_oldStdout, 1); // Restore stdout 118 | _ = Unix.dup2(_oldStderr, 2); // Restore stderr 119 | _ = Unix.close(_oldStdout); 120 | _ = Unix.close(_oldStderr); 121 | } 122 | } 123 | catch 124 | { 125 | // Silently fail - best effort restoration 126 | } 127 | } 128 | 129 | /// 130 | /// Native Windows C runtime functions for file descriptor manipulation. 131 | /// 132 | private static partial class Windows 133 | { 134 | public const int O_WRONLY = 0x0001; 135 | 136 | [LibraryImport("msvcrt.dll", SetLastError = true, StringMarshalling = StringMarshalling.Utf8)] 137 | [UnmanagedCallConv(CallConvs = [typeof(System.Runtime.CompilerServices.CallConvCdecl)])] 138 | public static partial int _open(string filename, int oflag); 139 | 140 | [LibraryImport("msvcrt.dll", SetLastError = true)] 141 | [UnmanagedCallConv(CallConvs = [typeof(System.Runtime.CompilerServices.CallConvCdecl)])] 142 | public static partial int _dup(int fd); 143 | 144 | [LibraryImport("msvcrt.dll", SetLastError = true)] 145 | [UnmanagedCallConv(CallConvs = [typeof(System.Runtime.CompilerServices.CallConvCdecl)])] 146 | public static partial int _dup2(int fd1, int fd2); 147 | 148 | [LibraryImport("msvcrt.dll", SetLastError = true)] 149 | [UnmanagedCallConv(CallConvs = [typeof(System.Runtime.CompilerServices.CallConvCdecl)])] 150 | public static partial int _close(int fd); 151 | 152 | [LibraryImport("msvcrt.dll", SetLastError = true)] 153 | [UnmanagedCallConv(CallConvs = [typeof(System.Runtime.CompilerServices.CallConvCdecl)])] 154 | public static partial int fflush(IntPtr stream); 155 | } 156 | 157 | /// 158 | /// Native Unix/Linux/macOS POSIX functions for file descriptor manipulation. 159 | /// 160 | private static partial class Unix 161 | { 162 | public const int O_WRONLY = 0x0001; 163 | 164 | [LibraryImport("libc", EntryPoint = "open", SetLastError = true, StringMarshalling = StringMarshalling.Utf8)] 165 | [UnmanagedCallConv(CallConvs = [typeof(System.Runtime.CompilerServices.CallConvCdecl)])] 166 | public static partial int open(string pathname, int flags, int mode); 167 | 168 | [LibraryImport("libc", EntryPoint = "dup", SetLastError = true)] 169 | [UnmanagedCallConv(CallConvs = [typeof(System.Runtime.CompilerServices.CallConvCdecl)])] 170 | public static partial int dup(int oldfd); 171 | 172 | [LibraryImport("libc", EntryPoint = "dup2", SetLastError = true)] 173 | [UnmanagedCallConv(CallConvs = [typeof(System.Runtime.CompilerServices.CallConvCdecl)])] 174 | public static partial int dup2(int oldfd, int newfd); 175 | 176 | [LibraryImport("libc", EntryPoint = "close", SetLastError = true)] 177 | [UnmanagedCallConv(CallConvs = [typeof(System.Runtime.CompilerServices.CallConvCdecl)])] 178 | public static partial int close(int fd); 179 | 180 | [LibraryImport("libc", EntryPoint = "fflush", SetLastError = true)] 181 | [UnmanagedCallConv(CallConvs = [typeof(System.Runtime.CompilerServices.CallConvCdecl)])] 182 | public static partial int fflush(IntPtr stream); 183 | } 184 | } 185 | -------------------------------------------------------------------------------- /src/RtlSdrManager/Frequency.cs: -------------------------------------------------------------------------------- 1 | // RTL-SDR Manager Library for .NET 2 | // Copyright (C) 2018-2025 Nandor Toth 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see http://www.gnu.org/licenses. 16 | 17 | using System; 18 | 19 | namespace RtlSdrManager; 20 | 21 | /// 22 | /// Immutable record representing a frequency value in Hertz with unit conversions and comparison support. 23 | /// 24 | /// Frequency value in Hertz. 25 | public readonly record struct Frequency(uint Hz) : IComparable, IComparable 26 | { 27 | /// 28 | /// Gets the frequency in Kilohertz (KHz). 29 | /// 30 | public double KHz => Hz / 1_000.0; 31 | 32 | /// 33 | /// Gets the frequency in Megahertz (MHz). 34 | /// 35 | public double MHz => Hz / 1_000_000.0; 36 | 37 | /// 38 | /// Gets the frequency in Gigahertz (GHz). 39 | /// 40 | public double GHz => Hz / 1_000_000_000.0; 41 | 42 | /// 43 | /// Creates a frequency from Hertz. 44 | /// 45 | /// Frequency in Hertz. 46 | /// A new Frequency instance. 47 | public static Frequency FromHz(uint hz) => new(hz); 48 | 49 | /// 50 | /// Creates a frequency from Hertz. 51 | /// 52 | /// Frequency in Hertz. 53 | /// A new Frequency instance. 54 | /// Thrown when the value is negative or exceeds the maximum frequency. 55 | public static Frequency FromHz(double hz) 56 | { 57 | ArgumentOutOfRangeException.ThrowIfNegative(hz); 58 | double rounded = Math.Round(hz); 59 | return rounded > uint.MaxValue ? throw new ArgumentOutOfRangeException(nameof(hz), hz, 60 | $"Frequency {hz} Hz exceeds maximum value.") : new Frequency((uint)rounded); 61 | } 62 | 63 | /// 64 | /// Creates a frequency from Kilohertz. 65 | /// 66 | /// Frequency in Kilohertz. 67 | /// A new Frequency instance. 68 | /// Thrown when the value is negative or exceeds the maximum frequency. 69 | public static Frequency FromKHz(double khz) 70 | { 71 | ArgumentOutOfRangeException.ThrowIfNegative(khz); 72 | double hz = Math.Round(khz * 1_000.0); 73 | return hz > uint.MaxValue ? 74 | throw new ArgumentOutOfRangeException(nameof(khz), khz, $"Frequency {khz} KHz exceeds maximum value.") : 75 | new Frequency((uint)hz); 76 | } 77 | 78 | /// 79 | /// Creates a frequency from Megahertz. 80 | /// 81 | /// Frequency in Megahertz. 82 | /// A new Frequency instance. 83 | /// Thrown when the value is negative or exceeds the maximum frequency. 84 | public static Frequency FromMHz(double mhz) 85 | { 86 | ArgumentOutOfRangeException.ThrowIfNegative(mhz); 87 | double hz = Math.Round(mhz * 1_000_000.0); 88 | return hz > uint.MaxValue ? 89 | throw new ArgumentOutOfRangeException(nameof(mhz), mhz, $"Frequency {mhz} MHz exceeds maximum value.") : 90 | new Frequency((uint)hz); 91 | } 92 | 93 | /// 94 | /// Creates a frequency from Gigahertz. 95 | /// 96 | /// Frequency in Gigahertz. 97 | /// A new Frequency instance. 98 | /// Thrown when the value is negative or exceeds the maximum frequency. 99 | public static Frequency FromGHz(double ghz) 100 | { 101 | ArgumentOutOfRangeException.ThrowIfNegative(ghz); 102 | double hz = Math.Round(ghz * 1_000_000_000.0); 103 | return hz > uint.MaxValue ? 104 | throw new ArgumentOutOfRangeException(nameof(ghz), ghz, $"Frequency {ghz} GHz exceeds maximum value.") : 105 | new Frequency((uint)hz); 106 | } 107 | 108 | /// 109 | /// Compares this frequency to another frequency. 110 | /// 111 | /// The frequency to compare to. 112 | /// A value indicating the relative order of the frequencies. 113 | public int CompareTo(Frequency other) => Hz.CompareTo(other.Hz); 114 | 115 | /// 116 | /// Compares this frequency to another object. 117 | /// 118 | /// The object to compare to. 119 | /// A value indicating the relative order of the objects. 120 | /// Thrown when obj is not a Frequency. 121 | public int CompareTo(object? obj) 122 | { 123 | if (obj is null) 124 | { 125 | return 1; 126 | } 127 | 128 | return obj is not Frequency other ? 129 | throw new ArgumentException($"Object must be of type {nameof(Frequency)}.", nameof(obj)) : 130 | CompareTo(other); 131 | } 132 | 133 | /// 134 | /// Returns a string representation of the frequency in all units. 135 | /// 136 | public override string ToString() => $"{Hz} Hz; {KHz:F3} KHz; {MHz:F6} MHz; {GHz:F9} GHz"; 137 | 138 | // Comparison operators 139 | public static bool operator <(Frequency left, Frequency right) => left.Hz < right.Hz; 140 | public static bool operator >(Frequency left, Frequency right) => left.Hz > right.Hz; 141 | public static bool operator <=(Frequency left, Frequency right) => left.Hz <= right.Hz; 142 | public static bool operator >=(Frequency left, Frequency right) => left.Hz >= right.Hz; 143 | 144 | // Arithmetic operators 145 | /// 146 | /// Adds two frequencies together. 147 | /// 148 | /// Thrown when the result exceeds the maximum frequency. 149 | public static Frequency operator +(Frequency left, Frequency right) => 150 | new(checked(left.Hz + right.Hz)); 151 | 152 | /// 153 | /// Subtracts one frequency from another. 154 | /// 155 | /// Thrown when the result would be negative. 156 | public static Frequency operator -(Frequency left, Frequency right) => 157 | new Frequency(checked(left.Hz - right.Hz)); 158 | 159 | /// 160 | /// Multiplies a frequency by a scalar value. 161 | /// 162 | public static Frequency operator *(Frequency frequency, double multiplier) 163 | { 164 | double result = Math.Round(frequency.Hz * multiplier); 165 | return result is < 0 or > uint.MaxValue 166 | ? throw new OverflowException($"Frequency multiplication result {result} Hz is out of range.") 167 | : new Frequency((uint)result); 168 | } 169 | 170 | /// 171 | /// Multiplies a frequency by a scalar value (commutative). 172 | /// 173 | public static Frequency operator *(double multiplier, Frequency frequency) => frequency * multiplier; 174 | 175 | /// 176 | /// Divides a frequency by a scalar value. 177 | /// 178 | public static Frequency operator /(Frequency frequency, double divisor) 179 | { 180 | if (divisor == 0) 181 | { 182 | throw new DivideByZeroException("Cannot divide frequency by zero."); 183 | } 184 | 185 | double result = Math.Round(frequency.Hz / divisor); 186 | return result < 0 187 | ? throw new OverflowException($"Frequency division result {result} Hz is out of range.") 188 | : new Frequency((uint)result); 189 | } 190 | } 191 | -------------------------------------------------------------------------------- /src/RtlSdrManager/RtlSdrManagedDevice.Async.cs: -------------------------------------------------------------------------------- 1 | // RTL-SDR Manager Library for .NET 2 | // Copyright (C) 2018-2025 Nandor Toth 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see http://www.gnu.org/licenses. 16 | 17 | using System; 18 | using System.Collections.Concurrent; 19 | using System.Collections.Generic; 20 | using System.Runtime.InteropServices; 21 | using System.Threading; 22 | using RtlSdrManager.Exceptions; 23 | using RtlSdrManager.Interop; 24 | 25 | namespace RtlSdrManager; 26 | 27 | /// 28 | /// Class for a managed (opened) RTL-SDR device. 29 | /// 30 | /// 31 | public sealed partial class RtlSdrManagedDevice 32 | { 33 | #region Fields, Constants, Properties, Events 34 | 35 | /// 36 | /// Initialize the callback function for async sample reading (I/Q). 37 | /// 38 | private readonly unsafe LibRtlSdr.RtlSdrReadAsyncDelegate _asyncCallback = 39 | SamplesAvailableCallback; 40 | 41 | /// 42 | /// Async I/Q buffer (FIFO). 43 | /// 44 | private ConcurrentQueue? _asyncBuffer; 45 | 46 | /// 47 | /// Worker thread for async reader. 48 | /// 49 | private Thread? _asyncWorker; 50 | 51 | /// 52 | /// Default amount of requested samples from RTL-SDR device. 53 | /// 54 | private const uint AsyncDefaultReadLength = 16384; 55 | 56 | /// 57 | /// Event to notify subscribers, if the new samples are available. 58 | /// 59 | public event EventHandler? SamplesAvailable; 60 | 61 | /// 62 | /// Accessor for the async I/Q buffer. 63 | /// 64 | public ConcurrentQueue AsyncBuffer 65 | { 66 | get 67 | { 68 | // Check the buffer. It can be reachable, if there is an async reading. 69 | if (_asyncBuffer == null) 70 | { 71 | throw new RtlSdrLibraryExecutionException( 72 | "The async buffer is not initialized yet. " + 73 | "StartReadSamplesAsync function must be invoked first."); 74 | } 75 | 76 | // Return the buffer. 77 | return _asyncBuffer; 78 | } 79 | } 80 | 81 | /// 82 | /// Maximum size of async I/Q buffer. 83 | /// 84 | public uint MaxAsyncBufferSize { get; set; } 85 | 86 | /// 87 | /// Define the behavior if the buffer is full. 88 | /// Drop samples (true), or throw exception (false). 89 | /// 90 | public bool DropSamplesOnFullBuffer { get; set; } 91 | 92 | /// 93 | /// Counter for dropped I/Q samples. 94 | /// It is possible to reset the counter with . 95 | /// 96 | public uint DroppedSamplesCount { get; private set; } 97 | 98 | #endregion 99 | 100 | #region Methods 101 | 102 | /// 103 | /// Reset the counter for dropped I/Q samples. 104 | /// 105 | public void ResetDroppedSamplesCounter() => DroppedSamplesCount = 0; 106 | 107 | /// 108 | /// Get I/Q samples from the async buffer. 109 | /// 110 | /// Maximum amount of requested I/Q samples. If there are fewer samples in the buffer, 111 | /// than the requested amount, maxCount will be reduced. 112 | /// List if I/Q samples. 113 | /// 114 | public List GetSamplesFromAsyncBuffer(int maxCount) 115 | { 116 | // Check the buffer. It can be reachable, if there is an async reading. 117 | if (_asyncBuffer == null) 118 | { 119 | throw new RtlSdrLibraryExecutionException( 120 | "The async buffer is not initialized yet. " + 121 | "StartReadSamplesAsync function must be invoked first."); 122 | } 123 | 124 | // Initialize the local buffer. 125 | var iqData = new List(); 126 | 127 | // Check the available samples in the async buffer. 128 | if (maxCount > _asyncBuffer.Count) 129 | { 130 | maxCount = _asyncBuffer.Count; 131 | } 132 | 133 | // Dequeue of the samples from the async buffer. 134 | for (int i = 0; i < maxCount; i++) 135 | { 136 | while (true) 137 | { 138 | if (!_asyncBuffer.TryDequeue(out IQData data)) 139 | { 140 | continue; 141 | } 142 | 143 | iqData.Add(data); 144 | break; 145 | } 146 | } 147 | 148 | // Return the local buffer. 149 | return iqData; 150 | } 151 | 152 | /// 153 | /// Callback function for async reading (I/Q). 154 | /// 155 | /// Buffer to store samples. 156 | /// Length of the buffer. 157 | /// Device context. 158 | private static unsafe void SamplesAvailableCallback(byte* buf, uint len, IntPtr ctx) 159 | { 160 | // Get the context pointer. 161 | var context = GCHandle.FromIntPtr(ctx); 162 | 163 | // If the context pointer does not exist, everything must be stopped. 164 | if (!context.IsAllocated) 165 | { 166 | return; 167 | } 168 | 169 | // Get the context target (actual instance of RtlSdrManagedDevice). 170 | var target = (RtlSdrManagedDevice?)context.Target; 171 | if (target == null) 172 | { 173 | return; 174 | } 175 | 176 | // Count of I/Q data. 177 | int length = (int)len / 2; 178 | 179 | // The buffer is guaranteed to be non-null when the callback is active 180 | ConcurrentQueue? buffer = target._asyncBuffer!; 181 | 182 | // Check the async buffer usage. 183 | if (buffer.Count + length >= target.MaxAsyncBufferSize) 184 | { 185 | // Throw an exception, if dropping samples was not asked. 186 | if (!target.DropSamplesOnFullBuffer) 187 | { 188 | throw new RtlSdrManagedDeviceException( 189 | "The async buffer of the managed device is full. " + 190 | $"Current buffer size: {buffer.Count + length} I/Q samples, " + 191 | $"Maximum buffer size: {target.MaxAsyncBufferSize} I/Q samples, " + 192 | $"Device index: {target.DeviceInfo.Index}."); 193 | } 194 | 195 | // Drop samples, since the async buffer is full, but increase the counter. 196 | target.DroppedSamplesCount += (uint)length; 197 | return; 198 | } 199 | 200 | // Add the samples to the async buffer. 201 | for (int i = 0; i < length; i++) 202 | { 203 | var iqData = new IQData(*buf++, *buf++); 204 | buffer.Enqueue(iqData); 205 | } 206 | 207 | // Raise the SampleAvailable event. 208 | target.OnSamplesAvailable(new SamplesAvailableEventArgs(length)); 209 | } 210 | 211 | /// 212 | /// Ensure that registered delegates receive the SamplesAvailable event. 213 | /// 214 | /// Event argument. 215 | private void OnSamplesAvailable(SamplesAvailableEventArgs e) 216 | { 217 | // If there are subscriber(s), raise event. 218 | SamplesAvailable?.Invoke(this, e); 219 | } 220 | 221 | /// 222 | /// Worker method to asynchronously read data from the RTL-SDR device. 223 | /// 224 | /// 225 | private void SamplesAsyncReader(object? readLength) 226 | { 227 | // Read from device. 228 | LibRtlSdr.rtlsdr_read_async(_deviceHandle!, _asyncCallback, 229 | (IntPtr)_deviceContext, 0, (uint)readLength!); 230 | } 231 | 232 | /// 233 | /// Start reading samples (I/Q) from the device asynchronously. 234 | /// 235 | /// Amount of requested samples by one device read. 236 | /// 237 | public void StartReadSamplesAsync(uint requestedSamples = AsyncDefaultReadLength) 238 | { 239 | // If the buffer does not exist, must be initialized. 240 | _asyncBuffer ??= new ConcurrentQueue(); 241 | 242 | // Check the worker thread. 243 | if (_asyncWorker != null) 244 | { 245 | throw new RtlSdrLibraryExecutionException( 246 | "Problem happened during asynchronous data reading from the device. " + 247 | $"The worker thread is already started. Device index: {DeviceInfo.Index}."); 248 | } 249 | 250 | // Start the worker with normal priority. 251 | _asyncWorker = new Thread(SamplesAsyncReader) 252 | { 253 | Priority = ThreadPriority.Highest 254 | }; 255 | _asyncWorker.Start(requestedSamples * 2); 256 | } 257 | 258 | /// 259 | /// Stop reading samples from the device. 260 | /// 261 | public void StopReadSamplesAsync() 262 | { 263 | // Check if the worker thread is running 264 | if (_asyncWorker == null) 265 | { 266 | return; 267 | } 268 | 269 | // Cancel the reading with the native function. 270 | int returnCode = LibRtlSdr.rtlsdr_cancel_async(_deviceHandle!); 271 | 272 | // If we did not get 0, there is an error. 273 | if (returnCode != 0) 274 | { 275 | throw new RtlSdrLibraryExecutionException( 276 | "Problem happened during stopping asynchronous data reading. " + 277 | $"Error code: {returnCode}, device index: {DeviceInfo.Index}."); 278 | } 279 | 280 | // Wait for the worker thread to finish. 281 | if (_asyncWorker.ThreadState == ThreadState.Running) 282 | { 283 | _asyncWorker.Join(); 284 | } 285 | 286 | // Release the worker. 287 | _asyncWorker = null; 288 | 289 | // Empty the buffer. 290 | _asyncBuffer = null; 291 | } 292 | 293 | #endregion 294 | } 295 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), 5 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 6 | 7 | ## [0.5.1] - 2025-11-27 8 | 9 | ### Changed 10 | - **BREAKING**: Console output suppression now uses global singleton pattern instead of per-device suppression 11 | - `RtlSdrDeviceManager.SuppressLibraryConsoleOutput` is now a property (not auto-property) that manages global state 12 | - Removed per-device `SuppressLibraryConsoleOutput` property from `RtlSdrManagedDevice` 13 | - Changes apply immediately to all open devices 14 | - Uses `System.Threading.Lock` for thread-safe global suppressor management 15 | 16 | ### Fixed 17 | - Fixed file descriptor corruption when multiple RTL-SDR devices are opened simultaneously 18 | - Console output suppression now uses a single global `ConsoleOutputSuppressor` instance 19 | - Prevents each device from creating its own file descriptor redirections 20 | - Eliminates crashes and undefined behavior with multiple devices 21 | 22 | ### Removed 23 | - Per-device console output suppression control (replaced with global control) 24 | 25 | ## [0.5.0] - 2025-10-23 26 | 27 | ### Added 28 | - Windows support to native library resolver 29 | - IComparable support to `Frequency` type 30 | - Improved frequency arithmetic with overflow protection 31 | - `build.sh` script for easy building and NuGet package creation 32 | - `runsample.sh` script for running sample applications 33 | - Modern .NET project structure with `src/` and `samples/` folders 34 | - Source Link support for debugging NuGet packages 35 | - Comprehensive XML documentation 36 | - `Frequency` type with strongly-typed unit conversions (Hz, KHz, MHz, GHz) 37 | - `DeviceInfo` as modern record type 38 | - `CrystalFrequency` type for RTL2832 and tuner crystal settings 39 | - Comprehensive `.editorconfig` with 60+ code quality rules 40 | - `.gitattributes` for consistent cross-platform line endings 41 | - [Console output suppression](docs/CONSOLE_OUTPUT_SUPPRESSION.md) for librtlsdr diagnostic messages 42 | - Performance analyzers (CA1827, CA1829, CA1841, CA1851) 43 | - Security analyzers for P/Invoke (CA2101, CA3075, CA5350, CA5351) 44 | - Nullable reference type warnings (CS8600-CS8604, CS8618, CS8625) 45 | - Standard exception constructors for all custom exception types 46 | - Input validation with proper exception types (`ArgumentNullException`, `ArgumentException`) 47 | - Improved [README.md](README.md) with comprehensive documentation and examples 48 | - Cross-platform build support (LF line endings) 49 | 50 | ### Changed 51 | - **BREAKING**: Migrated from .NET Core 3.1 to .NET 9.0 52 | - **BREAKING**: Namespace reorganization (Modes/, Hardware/, Interop/) 53 | - **BREAKING**: Project restructured into `src/` and `samples/` directories 54 | - Replaced legacy `DllImport` with source-generated `LibraryImport` for better performance 55 | - Split `RtlSdrLibraryWrapper` into `LibRtlSdr` and `LibResolver` for better separation 56 | - Modernized value types (`DeviceInfo`, `Frequency`, `CrystalFrequency`) 57 | - Improved build system with artifact generation 58 | - Exception handling: `IndexOutOfRangeException` replaced with appropriate types 59 | - `OpenManagedDevice`: Now validates device index and friendly name 60 | - `CloseManagedDevice`: Added input validation for friendly name 61 | - `CloseAllManagedDevice`: Changed exception type to `InvalidOperationException` 62 | - Indexer `[string friendlyName]`: Added input validation and proper exception types 63 | - `RtlSdrManagedDevice.Dispose()`: Implemented proper dispose pattern with `Dispose(bool disposing)` 64 | - EditorConfig: Changed line endings from CRLF to LF for cross-platform compatibility 65 | - EditorConfig: Adjusted brace enforcement from warning to suggestion 66 | 67 | ### Fixed 68 | - Dispose pattern now properly separates managed and unmanaged resource cleanup 69 | - Finalizer no longer accesses potentially disposed managed resources 70 | - `CancellationTokenSource` is now properly disposed in async samples 71 | - Device opening now provides clear error messages for invalid indices 72 | - Demo2: Fixed `CancellationTokenSource` disposal using `using var` declaration 73 | 74 | ### Removed 75 | - .NET Core 3.1 support (now requires .NET 9.0+) 76 | - Old NuGet package creation scripts (replaced by `build.sh`) 77 | - Legacy Visual Studio project files 78 | 79 | ### Performance 80 | - LibraryImport provides better P/Invoke performance 81 | - AOT compilation compatibility 82 | - Compile-time safety improvements 83 | 84 | ## [0.2.1] - 2020-01-10 85 | 86 | ### Added 87 | - KerberosSDR support for coherent SDR arrays 88 | - Frequency dithering control for R820T tuners 89 | - Direct GPIO control for synchronization 90 | - Bias Tee control methods 91 | - `SetBiasTee()` for GPIO 0 92 | - `SetBiasTeeGPIO()` for specific GPIO pins on R820T 93 | - `KerberosSDRModes` enumeration 94 | - `GPIOModes` enumeration for GPIO control 95 | - `BiasTeeModes` enumeration 96 | 97 | ### Changed 98 | - Extended `RtlSdrManagedDevice` with KerberosSDR-specific features 99 | - Improved documentation for advanced features 100 | 101 | ### Fixed 102 | - Minor documentation issues and typos 103 | 104 | ## [0.2.0] - 2018-06-10 105 | 106 | ### Added 107 | - Singleton pattern for `RtlSdrDeviceManager` 108 | - Thread-safe device manager instance 109 | 110 | ### Fixed 111 | - Critical bug in `DeviceInfo` handling that could cause incorrect device identification 112 | - Improved device enumeration reliability 113 | 114 | ### Changed 115 | - `RtlSdrDeviceManager` now uses lazy singleton initialization 116 | - Better memory management for device instances 117 | 118 | ## [0.1.3] - 2018-06-09 119 | 120 | ### Added 121 | - First public release on NuGet.org 122 | - NuGet package metadata and icon 123 | - Package publishing workflow 124 | 125 | ### Changed 126 | - Improved package description and tags 127 | - Added proper licensing information to NuGet package 128 | 129 | ## [0.1.2] - 2018-06-03 130 | 131 | ### Added 132 | - Crystal frequency control for RTL2832 and tuner IC 133 | - `CrystalFrequency` property with get/set support 134 | - Validation for crystal frequency ranges (max 28.8 MHz) 135 | - Tuner bandwidth selection 136 | - Automatic bandwidth selection mode 137 | - Manual bandwidth control 138 | - `TunerBandwidthSelectionModes` enumeration 139 | - Direct sampling support for HF reception 140 | - I-ADC direct sampling mode 141 | - Q-ADC direct sampling mode 142 | - `DirectSamplingModes` enumeration 143 | - Offset tuning mode for zero-IF tuners 144 | - Improved DC offset handling 145 | - `OffsetTuningModes` enumeration 146 | 147 | ### Changed 148 | - Enhanced `RtlSdrManagedDevice` with advanced tuner controls 149 | - Improved frequency handling and validation 150 | - Better support for HF reception scenarios 151 | 152 | ## [0.1.1] - 2018-05-12 153 | 154 | ### Added 155 | - Initial public release of RTL-SDR Manager 156 | - Core device management functionality 157 | - Device enumeration and information retrieval 158 | - Device opening and closing 159 | - Multiple device support with friendly names 160 | - Basic device configuration 161 | - Center frequency control with validation 162 | - Sample rate configuration 163 | - Gain control (AGC and manual modes) 164 | - Frequency correction (PPM) 165 | - Sample reading capabilities 166 | - Synchronous sample reading 167 | - Asynchronous sample reading with event support 168 | - Configurable buffer management 169 | - Tuner support 170 | - Elonics E4000 171 | - Rafael Micro R820T/R828D 172 | - Fitipower FC0012/FC0013 173 | - FCI FC2580 174 | - Exception handling 175 | - `RtlSdrDeviceException` 176 | - `RtlSdrLibraryExecutionException` 177 | - `RtlSdrManagedDeviceException` 178 | - Comprehensive demo applications 179 | - Event-based async reading (Demo1) 180 | - Manual buffer polling (Demo2) 181 | - Synchronous reading (Demo3) 182 | - Device information display (Demo4) 183 | 184 | ### Dependencies 185 | - .NET Core 3.1 186 | - librtlsdr native library 187 | 188 | ## [0.1.0] - 2018-05-01 189 | 190 | ### Added 191 | - Initial development version 192 | - Basic P/Invoke wrappers for librtlsdr 193 | - Core architecture design 194 | - Project structure and build system 195 | 196 | --- 197 | 198 | ## Version History Summary 199 | 200 | | Version | Date | Key Changes | 201 | |---------|------------|-------------| 202 | | **0.5.1** | 2025-11-27 | Fixed multi-device console suppression bug | 203 | | **0.5.0** | 2025-10-23 | .NET 9.0 migration, modern architecture, Source Link | 204 | | **0.2.1** | 2020-01-10 | KerberosSDR support, Bias Tee control | 205 | | **0.2.0** | 2018-06-10 | Singleton pattern, bug fixes | 206 | | **0.1.3** | 2018-06-09 | First NuGet release | 207 | | **0.1.2** | 2018-06-03 | Crystal frequency, direct sampling, offset tuning | 208 | | **0.1.1** | 2018-05-12 | Initial public release | 209 | | **0.1.0** | 2018-05-01 | Development version | 210 | 211 | --- 212 | 213 | ## Upgrade Notes 214 | 215 | ### Upgrading to 0.5.0 from 0.2.x 216 | 217 | **Breaking Changes:** 218 | 219 | 1. **Framework Requirement** 220 | ```xml 221 | 222 | netcoreapp3.1 223 | 224 | 225 | net9.0 226 | ``` 227 | 228 | 2. **Namespace Changes** 229 | ```csharp 230 | // Old 231 | using RtlSdrManager.Types; 232 | 233 | // New 234 | using RtlSdrManager.Modes; // For enumerations 235 | using RtlSdrManager.Hardware; // For hardware types 236 | ``` 237 | 238 | 3. **Project Structure** 239 | - Source files moved from `RtlSdrManager/` to `src/RtlSdrManager/` 240 | - Samples moved from `RtlSdrManagerDemo/` to `samples/RtlSdrManager.Samples/` 241 | 242 | 4. **Type Changes** 243 | - `Frequency` is now an immutable record struct 244 | - Use factory methods: `Frequency.FromMHz()`, `FromKHz()`, etc. 245 | 246 | **Migration Example:** 247 | 248 | ```csharp 249 | // Old (0.2.x) 250 | using RtlSdrManager.Types; 251 | 252 | var freq = new Frequency(1090000000); // Hz 253 | device.CenterFrequency = freq; 254 | 255 | // New (0.5.0) 256 | using RtlSdrManager; 257 | 258 | var freq = Frequency.FromMHz(1090); // Clearer intent 259 | device.CenterFrequency = freq; 260 | 261 | // Can also use arithmetic 262 | var shifted = freq + Frequency.FromKHz(100); 263 | ``` 264 | 265 | **New Feature - Console Output Suppression:** 266 | 267 | By default, librtlsdr diagnostic messages are now suppressed. You can control this per-device: 268 | 269 | ```csharp 270 | // Suppress by default (enabled by default in 0.5.0) 271 | manager.OpenManagedDevice(0, "my-device"); 272 | var device = manager["my-device"]; 273 | 274 | // Toggle suppression at runtime for specific device 275 | device.SuppressLibraryConsoleOutput = false; // Show messages 276 | device.SampleRate = Frequency.FromMHz(2); // Will show librtlsdr output 277 | 278 | // Or set global default for new devices 279 | RtlSdrDeviceManager.SuppressLibraryConsoleOutput = false; 280 | ``` 281 | 282 | ### Upgrading to 0.2.0 from 0.1.x 283 | 284 | - No breaking changes, fully backward compatible 285 | - Recommended to use singleton pattern: `RtlSdrDeviceManager.Instance` 286 | - Legacy instantiation still works but is deprecated 287 | 288 | --- 289 | 290 | ## Contributing 291 | 292 | See [CONTRIBUTING.md](CONTRIBUTING.md) for contribution guidelines. 293 | 294 | ## License 295 | 296 | This project is licensed under the GNU General Public License v3.0 or later. 297 | See [LICENSE.md](LICENSE.md) for details. 298 | 299 | --- 300 | 301 | [0.5.1]: https://github.com/nandortoth/rtlsdr-manager/releases/tag/v0.5.1 302 | [0.5.0]: https://github.com/nandortoth/rtlsdr-manager/releases/tag/v0.5.0 303 | [0.2.1]: https://github.com/nandortoth/rtlsdr-manager/releases/tag/v0.2.1 304 | [0.2.0]: https://github.com/nandortoth/rtlsdr-manager/releases/tag/v0.2.0 305 | [0.1.3]: https://github.com/nandortoth/rtlsdr-manager/releases/tag/v0.1.3 306 | [0.1.2]: https://github.com/nandortoth/rtlsdr-manager/releases/tag/v0.1.2 307 | [0.1.1]: https://github.com/nandortoth/rtlsdr-manager/releases/tag/v0.1.1 308 | [0.1.0]: https://github.com/nandortoth/rtlsdr-manager/releases/tag/v0.1.0 309 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # RTL-SDR Manager Library for .NET 2 | 3 | [![NuGet Version](https://img.shields.io/nuget/v/RtlSdrManager.svg)](https://www.nuget.org/packages/RtlSdrManager/) 4 | [![NuGet Downloads](https://img.shields.io/nuget/dt/RtlSdrManager.svg)](https://www.nuget.org/packages/RtlSdrManager/) 5 | [![License](https://img.shields.io/badge/license-GPL--3.0-blue.svg)](LICENSE.md) 6 | [![.NET Version](https://img.shields.io/badge/.NET-9.0-purple.svg)](https://dotnet.microsoft.com/download) 7 | 8 | A modern, high-performance .NET library for managing RTL-SDR devices with support for async operations, multiple tuner types, and advanced features like KerberosSDR. 9 | 10 | ## ✨ Features 11 | 12 | - 🚀 **Async/Await Support** - Non-blocking sample reading with concurrent queue buffering 13 | - 🎛️ **Multiple Tuner Support** - E4000, R820T/R828D, FC0012, FC0013, FC2580 14 | - 🔧 **Advanced Configuration** - Gain control, frequency correction, direct sampling, bias tee 15 | - 📡 **KerberosSDR Ready** - Frequency dithering and GPIO control for coherent SDR arrays 16 | - 🔒 **Type-Safe API** - Strongly-typed frequency values with unit conversions 17 | - 💾 **Cross-Platform** - Works on Windows, Linux, and macOS 18 | - ⚡ **High Performance** - LibraryImport P/Invoke for optimal native interop 19 | - 🛡️ **Production Ready** - Proper exception handling, disposal patterns, and null safety 20 | - 🔇 **Console Output Control** - Suppress or capture native library diagnostic messages 21 | 22 | ## 📦 Installation 23 | 24 | ### Via NuGet Package Manager 25 | 26 | ```bash 27 | # .NET CLI 28 | dotnet add package RtlSdrManager 29 | 30 | # Package Manager Console (Visual Studio) 31 | Install-Package RtlSdrManager 32 | 33 | # PackageReference (in .csproj) 34 | 35 | ``` 36 | 37 | ### Prerequisites 38 | 39 | You must have the `librtlsdr` native library installed on your system: 40 | 41 | **Windows:** 42 | ```powershell 43 | # Using Chocolatey 44 | choco install rtl-sdr 45 | 46 | # Or download from: https://github.com/osmocom/rtl-sdr/releases 47 | ``` 48 | 49 | **Linux (Ubuntu/Debian):** 50 | ```bash 51 | sudo apt-get install librtlsdr-dev 52 | ``` 53 | 54 | **macOS:** 55 | ```bash 56 | brew install librtlsdr 57 | ``` 58 | 59 | ## 🚀 Quick Start 60 | 61 | ### Basic Usage 62 | 63 | ```csharp 64 | using RtlSdrManager; 65 | using RtlSdrManager.Modes; 66 | 67 | // Get the singleton device manager 68 | var manager = RtlSdrDeviceManager.Instance; 69 | 70 | // Check available devices 71 | Console.WriteLine($"Found {manager.CountDevices} RTL-SDR device(s)"); 72 | 73 | // Open the first device with a friendly name 74 | manager.OpenManagedDevice(0, "my-rtl-sdr"); 75 | 76 | // Configure the device 77 | var device = manager["my-rtl-sdr"]; 78 | device.CenterFrequency = Frequency.FromMHz(1090); // ADS-B frequency 79 | device.SampleRate = Frequency.FromMHz(2); 80 | device.TunerGainMode = TunerGainModes.AGC; 81 | device.AGCMode = AGCModes.Enabled; 82 | device.ResetDeviceBuffer(); 83 | 84 | Console.WriteLine($"Tuner: {device.TunerType}"); 85 | Console.WriteLine($"Center Frequency: {device.CenterFrequency.MHz} MHz"); 86 | ``` 87 | 88 | ### Console Output Suppression 89 | 90 | By default, the library suppresses all console output from `librtlsdr` (like "Found Rafael Micro R820T tuner" and "[R82XX] PLL not locked!") by redirecting native stdout/stderr file descriptors to `/dev/null` (Unix/macOS) or `NUL` (Windows). You can control this behavior globally: 91 | 92 | ```csharp 93 | // Disable suppression globally (default is true) 94 | RtlSdrDeviceManager.SuppressLibraryConsoleOutput = false; 95 | 96 | // Open a device - will now show librtlsdr messages 97 | manager.OpenManagedDevice(0, "my-rtl-sdr"); 98 | var device = manager["my-rtl-sdr"]; 99 | device.SampleRate = Frequency.FromMHz(2); // Will show librtlsdr output 100 | 101 | // Re-enable suppression globally 102 | RtlSdrDeviceManager.SuppressLibraryConsoleOutput = true; 103 | device.SampleRate = Frequency.FromMHz(2.4); // Silent 104 | ``` 105 | 106 | **Note:** The suppression uses a global singleton pattern (v0.5.1+) to prevent file descriptor corruption when multiple devices are opened simultaneously. Changes to the suppression setting apply immediately to all devices. 107 | 108 | ### Synchronous Sample Reading 109 | 110 | ```csharp 111 | // Read samples synchronously (blocking) 112 | var samples = device.ReadSamples(256 * 1024); 113 | 114 | foreach (var sample in samples) 115 | { 116 | // Process I/Q samples 117 | Console.WriteLine($"I: {sample.I}, Q: {sample.Q}"); 118 | } 119 | ``` 120 | 121 | ### Asynchronous Sample Reading 122 | 123 | ```csharp 124 | // Configure async buffer 125 | device.MaxAsyncBufferSize = 512 * 1024; 126 | device.DropSamplesOnFullBuffer = true; 127 | 128 | // Start async reading in background 129 | device.StartReadSamplesAsync(); 130 | 131 | // Option 1: Event-based (recommended for real-time processing) 132 | device.SamplesAvailable += (sender, args) => 133 | { 134 | var samples = device.GetSamplesFromAsyncBuffer(args.SampleCount); 135 | // Process samples in real-time 136 | }; 137 | 138 | // Option 2: Manual polling (for custom processing logic) 139 | while (running) 140 | { 141 | if (device.AsyncBuffer.TryDequeue(out var data)) 142 | { 143 | // Process data 144 | Console.WriteLine($"Received {data.Samples.Length} samples"); 145 | } 146 | else 147 | { 148 | await Task.Delay(100); 149 | } 150 | } 151 | 152 | // Stop reading when done 153 | device.StopReadSamplesAsync(); 154 | 155 | // Clean up 156 | manager.CloseManagedDevice("my-rtl-sdr"); 157 | ``` 158 | 159 | ### Manual Gain Control 160 | 161 | ```csharp 162 | // Switch to manual gain mode 163 | device.TunerGainMode = TunerGainModes.Manual; 164 | 165 | // Get supported gain values 166 | var gains = device.SupportedTunerGains; 167 | Console.WriteLine($"Supported gains: {string.Join(", ", gains)} dB"); 168 | 169 | // Set specific gain 170 | device.TunerGain = 42.1; // dB 171 | 172 | // Or use convenience methods 173 | device.SetMaximumTunerGain(); 174 | device.SetMinimumTunerGain(); 175 | ``` 176 | 177 | ### Frequency Operations 178 | 179 | ```csharp 180 | // Create frequencies with different units 181 | var freq1 = Frequency.FromHz(1090_000_000); 182 | var freq2 = Frequency.FromKHz(1090_000); 183 | var freq3 = Frequency.FromMHz(1090); 184 | var freq4 = Frequency.FromGHz(1.09); 185 | 186 | // Convert between units 187 | Console.WriteLine($"{freq1.Hz} Hz"); 188 | Console.WriteLine($"{freq1.KHz} KHz"); 189 | Console.WriteLine($"{freq1.MHz} MHz"); 190 | Console.WriteLine($"{freq1.GHz} GHz"); 191 | 192 | // Arithmetic operations 193 | var shifted = freq1 + Frequency.FromKHz(100); // Add 100 KHz offset 194 | var doubled = freq1 * 2; 195 | 196 | // Comparison 197 | if (freq1 > Frequency.FromMHz(100)) 198 | { 199 | Console.WriteLine("Above 100 MHz"); 200 | } 201 | ``` 202 | 203 | ### Advanced Features 204 | 205 | #### Bias Tee (for powering external LNAs) 206 | 207 | ```csharp 208 | // Enable bias tee on GPIO 0 (most common) 209 | device.SetBiasTee(BiasTeeModes.Enabled); 210 | 211 | // For R820T tuners, specify GPIO pin 212 | device.SetBiasTeeGPIO(gpio: 0, BiasTeeModes.Enabled); 213 | ``` 214 | 215 | #### Direct Sampling (HF reception) 216 | 217 | ```csharp 218 | // Enable direct sampling on I-ADC 219 | device.DirectSamplingMode = DirectSamplingModes.I_ADC; 220 | 221 | // Or on Q-ADC 222 | device.DirectSamplingMode = DirectSamplingModes.Q_ADC; 223 | 224 | // Disable 225 | device.DirectSamplingMode = DirectSamplingModes.Disabled; 226 | ``` 227 | 228 | #### Frequency Correction (PPM) 229 | 230 | ```csharp 231 | // Set frequency correction in PPM 232 | device.FrequencyCorrection = 10; // +10 PPM 233 | ``` 234 | 235 | #### KerberosSDR Support 236 | 237 | ```csharp 238 | // Enable KerberosSDR mode (required for these features) 239 | device.KerberosSDRMode = KerberosSDRModes.Enabled; 240 | 241 | // Enable frequency dithering (for R820T only) 242 | device.FrequencyDitheringMode = FrequencyDitheringModes.Enabled; 243 | 244 | // Control GPIO pins directly 245 | device.SetGPIO(gpio: 1, GPIOModes.Enabled); 246 | ``` 247 | 248 | ## 📚 Documentation 249 | 250 | For more detailed information and advanced usage scenarios: 251 | 252 | - [**Basic Setup**](docs/BASIC_SETUP.md) - Getting started with device initialization 253 | - [**Device Management**](docs/DEVICE_MANAGEMENT.md) - Managing multiple RTL-SDR devices 254 | - [**Manual Gain Control**](docs/MANUAL_GAIN_CONTROL.md) - Configuring tuner gain settings 255 | - [**Direct Sampling**](docs/DIRECT_SAMPLING.md) - Using direct sampling modes for HF 256 | - [**Frequency Correction**](docs/FREQUENCY_CORRECTION.md) - PPM frequency correction 257 | - [**Bias Tee**](docs/BIAS_TEE.md) - Enabling bias tee for powering external LNAs 258 | - [**KerberosSDR**](docs/KERBEROS_SDR.md) - Advanced features for KerberosSDR arrays 259 | 260 | ### Sample Applications 261 | 262 | Check out the [`samples/`](samples/) directory for complete working examples: 263 | 264 | - **Demo1** - Event-based async sample reading 265 | - **Demo2** - Manual polling from async buffer 266 | - **Demo3** - Synchronous sample reading 267 | - **Demo4** - Device information and configuration 268 | 269 | ## 🔧 Building from Source 270 | 271 | ### Requirements 272 | 273 | - .NET 9.0 SDK or later 274 | - librtlsdr native library 275 | 276 | ### Build Commands 277 | 278 | ```bash 279 | # Clone the repository 280 | git clone https://github.com/nandortoth/rtlsdr-manager.git 281 | cd rtlsdr-manager 282 | 283 | # Build the entire solution 284 | dotnet build 285 | 286 | # Run tests (if available) 287 | dotnet test 288 | 289 | # Create NuGet packages 290 | dotnet pack --configuration Release 291 | 292 | # Or use the convenience script 293 | ./build.sh 294 | ``` 295 | 296 | ### Build Output 297 | 298 | The build process creates: 299 | - **NuGet packages** → `artifacts/packages/` 300 | - **Library binaries** → `artifacts/binaries/RtlSdrManager/` 301 | - **Sample binaries** → `artifacts/binaries/Samples/` 302 | 303 | ### Running Samples 304 | 305 | ```bash 306 | # Using the convenience script 307 | ./runsample.sh 308 | 309 | # Or manually 310 | dotnet run --project samples/RtlSdrManager.Samples 311 | ``` 312 | 313 | ## 🏗️ Architecture 314 | 315 | ``` 316 | RtlSdrManager/ 317 | ├── src/ 318 | │ └── RtlSdrManager/ # Main library 319 | │ ├── Exceptions/ # Custom exception types 320 | │ ├── Hardware/ # Hardware type definitions 321 | │ ├── Interop/ # P/Invoke wrappers 322 | │ └── Modes/ # Enumeration types 323 | ├── samples/ 324 | │ └── RtlSdrManager.Samples/ # Example applications 325 | └── docs/ # Documentation 326 | ``` 327 | 328 | ## 🤝 Contributing 329 | 330 | Contributions are welcome! Please feel free to submit issues, fork the repository, and create pull requests. 331 | 332 | 1. Fork the repository 333 | 2. Create your feature branch (`git checkout -b feature/amazing-feature`) 334 | 3. Commit your changes (`git commit -m 'Add amazing feature'`) 335 | 4. Push to the branch (`git push origin feature/amazing-feature`) 336 | 5. Open a Pull Request 337 | 338 | Please ensure your code: 339 | - Follows the EditorConfig style guidelines 340 | - Builds without warnings 341 | - Includes XML documentation for public APIs 342 | - Includes unit tests for new features (when applicable) 343 | 344 | ## 📋 System Requirements 345 | 346 | - **.NET Runtime:** 9.0 or later 347 | - **Operating System:** Windows, Linux, macOS 348 | - **Hardware:** RTL-SDR compatible device (RTL2832U-based) 349 | - **Native Library:** librtlsdr installed on the system 350 | 351 | ## 📝 Supported Devices 352 | 353 | This library supports RTL-SDR devices with the following tuners: 354 | 355 | | Tuner | Frequency Range | Notes | 356 | |-------|----------------|-------| 357 | | **Elonics E4000** | 52-1100 MHz, 1250-2200 MHz | No longer manufactured | 358 | | **Rafael Micro R820T** | 24-1766 MHz | Most common, excellent performance | 359 | | **Rafael Micro R828D** | 24-1766 MHz | Similar to R820T | 360 | | **Fitipower FC0012** | 22-948.6 MHz | Basic performance | 361 | | **Fitipower FC0013** | 22-1100 MHz | Basic performance | 362 | | **FCI FC2580** | 146-308 MHz, 438-924 MHz | Good performance | 363 | 364 | ## 📜 License 365 | 366 | This project is licensed under the **GNU General Public License v3.0 or later** - see the [LICENSE.md](LICENSE.md) file for details. 367 | 368 | ``` 369 | RTL-SDR Manager Library for .NET 370 | Copyright (C) 2018-2025 Nandor Toth 371 | 372 | This program is free software: you can redistribute it and/or modify 373 | it under the terms of the GNU General Public License as published by 374 | the Free Software Foundation, either version 3 of the License, or 375 | (at your option) any later version. 376 | ``` 377 | 378 | ## 🔗 Links 379 | 380 | - **NuGet Package:** https://www.nuget.org/packages/RtlSdrManager/ 381 | - **GitHub Repository:** https://github.com/nandortoth/rtlsdr-manager 382 | - **Issue Tracker:** https://github.com/nandortoth/rtlsdr-manager/issues 383 | - **Changelog:** [CHANGELOG.md](CHANGELOG.md) 384 | - **librtlsdr:** https://github.com/osmocom/rtl-sdr 385 | 386 | ## 🙏 Acknowledgments 387 | 388 | - [Osmocom rtl-sdr project](https://osmocom.org/projects/rtl-sdr/wiki) for the excellent native library 389 | - [KerberosSDR project](https://github.com/rtlsdrblog/rtl-sdr-kerberos) for coherent SDR extensions 390 | - All contributors and users of this library 391 | 392 | ## 📊 Project Status 393 | 394 | - ✅ **Stable** - Production ready 395 | - ✅ **Actively Maintained** - Regular updates and bug fixes 396 | - ✅ **.NET 9.0** - Modern .NET with latest features 397 | - ✅ **Cross-Platform** - Windows, Linux, macOS support 398 | 399 | --- 400 | 401 | **Made with ❤️ by [Nandor Toth](https://github.com/nandortoth)** 402 | -------------------------------------------------------------------------------- /src/RtlSdrManager/RtlSdrDeviceManager.cs: -------------------------------------------------------------------------------- 1 | // RTL-SDR Manager Library for .NET 2 | // Copyright (C) 2018-2025 Nandor Toth 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see http://www.gnu.org/licenses. 16 | 17 | using System; 18 | using System.Collections; 19 | using System.Collections.Generic; 20 | using System.Linq; 21 | using System.Runtime.InteropServices; 22 | using System.Text; 23 | using System.Threading; 24 | using RtlSdrManager.Exceptions; 25 | using RtlSdrManager.Interop; 26 | 27 | namespace RtlSdrManager; 28 | 29 | /// 30 | /// Class for managing RTL-SDR devices. 31 | /// The class uses the "librtlsdr" shared library. 32 | /// 33 | /// 34 | public class RtlSdrDeviceManager : IEnumerable 35 | { 36 | #region Fields 37 | 38 | /// 39 | /// Lazy singleton pattern. 40 | /// 41 | private static readonly Lazy Singleton = new(() => new RtlSdrDeviceManager()); 42 | 43 | /// 44 | /// Dictionary for the managed (opened) RTL-SDR devices. 45 | /// 46 | private readonly Dictionary _managedDevices; 47 | 48 | #endregion 49 | 50 | #region Constructor and Indexer 51 | 52 | /// 53 | /// Create a new RtlSdrDeviceManager instance for RTL-SDR devices. 54 | /// 55 | private RtlSdrDeviceManager() 56 | { 57 | // Initialize the dictionary for the managed devices. 58 | _managedDevices = new Dictionary(); 59 | 60 | // Initialize the list for the devices on the system, and fill it up. 61 | Devices = new Dictionary(); 62 | Devices = GetAllDeviceInfo(); 63 | } 64 | 65 | /// 66 | /// Indexer to return the managed (opened) RTL-SDR device which has the given friendly name. 67 | /// 68 | /// Friendly name of the RTL-SDR device. 69 | /// Thrown when friendlyName is null. 70 | /// Thrown when friendlyName is empty or whitespace, or when no device with the given friendly name exists. 71 | public RtlSdrManagedDevice this[string friendlyName] 72 | { 73 | get 74 | { 75 | // Validate friendlyName parameter 76 | if (friendlyName == null) 77 | { 78 | throw new ArgumentNullException(nameof(friendlyName), "Friendly name cannot be null."); 79 | } 80 | 81 | if (string.IsNullOrWhiteSpace(friendlyName)) 82 | { 83 | throw new ArgumentException("Friendly name cannot be empty or whitespace.", nameof(friendlyName)); 84 | } 85 | 86 | // Check the dictionary because of the key. 87 | if (!_managedDevices.TryGetValue(friendlyName, out RtlSdrManagedDevice? device)) 88 | { 89 | throw new ArgumentException( 90 | $"No managed device with the friendly name '{friendlyName}' exists.", 91 | nameof(friendlyName)); 92 | } 93 | 94 | // Return the selected device. 95 | return device; 96 | } 97 | } 98 | 99 | #endregion 100 | 101 | #region Properties 102 | 103 | /// 104 | /// Return the instance (singleton pattern) 105 | /// 106 | public static RtlSdrDeviceManager Instance => Singleton.Value; 107 | 108 | /// 109 | /// Return the amount of the managed devices. 110 | /// 111 | public int CountManagedDevices => _managedDevices.Count; 112 | 113 | /// 114 | /// Return the amount of the supported RTL-SDR devices on the system. 115 | /// 116 | public int CountDevices => Devices.Count; 117 | 118 | /// 119 | /// Return the basic data of the supported RTL-SDR devices on the system. 120 | /// 121 | public Dictionary Devices { get; } 122 | 123 | // Global console output suppressor (singleton pattern) 124 | // CRITICAL: Must be a singleton to avoid file descriptor corruption when multiple devices are open 125 | private static ConsoleOutputSuppressor? _globalSuppressor; 126 | private static readonly Lock SuppressorLock = new(); 127 | 128 | /// 129 | /// Gets or sets the global console output suppression setting. 130 | /// Default is true (console output is suppressed). 131 | /// This affects messages like "Found Rafael Micro R820T tuner" and "[R82XX] PLL not locked!". 132 | /// IMPORTANT: Uses a global singleton suppressor to prevent file descriptor corruption 133 | /// when multiple devices are opened. Changing this value while devices are open is safe. 134 | /// 135 | public static bool SuppressLibraryConsoleOutput 136 | { 137 | get => _globalSuppressor != null; 138 | set 139 | { 140 | lock (SuppressorLock) 141 | { 142 | switch (value) 143 | { 144 | case true when _globalSuppressor == null: 145 | // Enable suppression globally 146 | _globalSuppressor = new ConsoleOutputSuppressor(); 147 | break; 148 | case false when _globalSuppressor != null: 149 | // Disable suppression globally 150 | _globalSuppressor.Dispose(); 151 | _globalSuppressor = null; 152 | break; 153 | } 154 | } 155 | } 156 | } 157 | 158 | /// 159 | /// Static constructor to initialize global console output suppression. 160 | /// Suppression is enabled by default (v0.5.0+). 161 | /// 162 | static RtlSdrDeviceManager() 163 | { 164 | // Enable suppression by default 165 | SuppressLibraryConsoleOutput = true; 166 | } 167 | 168 | #endregion 169 | 170 | #region Methods 171 | 172 | /// 173 | /// Get fundamental information about the device. 174 | /// 175 | /// 176 | private static DeviceInfo GetDeviceInfo(uint deviceIndex) 177 | { 178 | // Create buffers, where the results will be stored. 179 | byte[] serialBuffer = new byte[256]; 180 | byte[] manufacturerBuffer = new byte[256]; 181 | byte[] productBuffer = new byte[256]; 182 | 183 | // Get the device name. 184 | IntPtr nameBufferPtr = LibRtlSdr.rtlsdr_get_device_name(deviceIndex); 185 | string? nameBuffer = Marshal.PtrToStringUTF8(nameBufferPtr); 186 | 187 | // Get the other data of the device. 188 | int returnCode = LibRtlSdr.rtlsdr_get_device_usb_strings(deviceIndex, 189 | manufacturerBuffer, productBuffer, serialBuffer); 190 | if (returnCode != 0) 191 | { 192 | throw new RtlSdrLibraryExecutionException( 193 | "Problem happened during reading USB strings of the device. " + 194 | $"Error code: {returnCode}, device index: {deviceIndex}."); 195 | } 196 | 197 | // Convert byte arrays to strings 198 | string serial = Encoding.UTF8.GetString(serialBuffer).TrimEnd('\0'); 199 | string manufacturer = Encoding.UTF8.GetString(manufacturerBuffer).TrimEnd('\0'); 200 | string product = Encoding.UTF8.GetString(productBuffer).TrimEnd('\0'); 201 | 202 | // If everything is good, fill the device info. 203 | return new DeviceInfo(deviceIndex, serial, manufacturer, product, nameBuffer ?? string.Empty); 204 | } 205 | 206 | /// 207 | /// Get fundamental information about all the devices on the system. 208 | /// 209 | /// 210 | /// 211 | private static Dictionary GetAllDeviceInfo() 212 | { 213 | // Check the number of the devices on the system. 214 | uint deviceCount = LibRtlSdr.rtlsdr_get_device_count(); 215 | 216 | // If there is no device on the system, throw an exception. 217 | if (deviceCount == 0) 218 | { 219 | throw new RtlSdrDeviceException("There is no supported RTL-SDR device on the system."); 220 | } 221 | 222 | // Create the list, which will contain the devices. 223 | var devices = new Dictionary(); 224 | 225 | // Iterate the devices. 226 | for (uint i = 0; i < deviceCount; i++) 227 | { 228 | // If everything is good, add the device to the list. 229 | devices.Add(i, GetDeviceInfo(i)); 230 | } 231 | 232 | // Return the list. 233 | return devices; 234 | } 235 | 236 | /// 237 | /// Open RTL-SDR device for further usage. 238 | /// 239 | /// Index of the device. 240 | /// Friendly name of the device, later this can be used as a reference. 241 | /// Thrown when friendlyName is null. 242 | /// Thrown when friendlyName is empty or whitespace, or when a device with the given friendly name already exists. 243 | /// Thrown when the device index does not exist. 244 | public void OpenManagedDevice(uint index, string friendlyName) 245 | { 246 | // Validate friendlyName parameter 247 | if (friendlyName == null) 248 | { 249 | throw new ArgumentNullException(nameof(friendlyName), "Friendly name cannot be null."); 250 | } 251 | 252 | if (string.IsNullOrWhiteSpace(friendlyName)) 253 | { 254 | throw new ArgumentException("Friendly name cannot be empty or whitespace.", nameof(friendlyName)); 255 | } 256 | 257 | // Do we have device with this name? 258 | if (_managedDevices.ContainsKey(friendlyName)) 259 | { 260 | throw new ArgumentException( 261 | $"A managed device with the friendly name '{friendlyName}' already exists.", 262 | nameof(friendlyName)); 263 | } 264 | 265 | // Check if the device index exists 266 | if (!Devices.TryGetValue(index, out DeviceInfo device)) 267 | { 268 | throw new RtlSdrDeviceException( 269 | $"RTL-SDR device with index {index} does not exist. " + 270 | $"Available device indices: {string.Join(", ", Devices.Keys)}."); 271 | } 272 | 273 | // Create a new RtlSdrManagedDevice instance. 274 | var managedDevice = new RtlSdrManagedDevice(device); 275 | 276 | // Add the device to the dictionary. 277 | _managedDevices.Add(friendlyName, managedDevice); 278 | } 279 | 280 | /// 281 | /// Close the managed device. 282 | /// 283 | /// Friendly name of the device. 284 | /// Thrown when friendlyName is null. 285 | /// Thrown when friendlyName is empty or whitespace, or when no device with the given friendly name exists. 286 | public void CloseManagedDevice(string friendlyName) 287 | { 288 | // Validate friendlyName parameter 289 | if (friendlyName == null) 290 | { 291 | throw new ArgumentNullException(nameof(friendlyName), "Friendly name cannot be null."); 292 | } 293 | 294 | if (string.IsNullOrWhiteSpace(friendlyName)) 295 | { 296 | throw new ArgumentException("Friendly name cannot be empty or whitespace.", nameof(friendlyName)); 297 | } 298 | 299 | // Check the dictionary because of the key. 300 | if (!_managedDevices.TryGetValue(friendlyName, out RtlSdrManagedDevice? device)) 301 | { 302 | throw new ArgumentException( 303 | $"No managed device with the friendly name '{friendlyName}' exists.", 304 | nameof(friendlyName)); 305 | } 306 | 307 | // Dispose the managed device (which closes it). 308 | device.Dispose(); 309 | _managedDevices.Remove(friendlyName); 310 | } 311 | 312 | /// 313 | /// Close all the managed devices. 314 | /// 315 | /// Thrown when there are no managed devices to close. 316 | public void CloseAllManagedDevice() 317 | { 318 | // Check how many managed device we have. 319 | if (_managedDevices.Count == 0) 320 | { 321 | throw new InvalidOperationException("There are no managed (opened) RTL-SDR devices to close."); 322 | } 323 | 324 | // Close all the devices. 325 | string[] managedDeviceKeys = _managedDevices.Keys.ToArray(); 326 | foreach (string key in managedDeviceKeys) 327 | { 328 | CloseManagedDevice(key); 329 | } 330 | } 331 | 332 | #endregion 333 | 334 | #region Implement IEnumerable 335 | 336 | /// 337 | /// Implement IEnumerator interface. 338 | /// 339 | /// List of managed devices. 340 | /// 341 | public IEnumerator GetEnumerator() => 342 | _managedDevices.Values.GetEnumerator(); 343 | 344 | /// 345 | /// Implement IEnumerator interface. 346 | /// 347 | /// Enumerator. 348 | /// 349 | IEnumerator IEnumerable.GetEnumerator() => 350 | GetEnumerator(); 351 | 352 | #endregion 353 | } 354 | -------------------------------------------------------------------------------- /docs/CONSOLE_OUTPUT_SUPPRESSION.md: -------------------------------------------------------------------------------- 1 | # Console Output Suppression 2 | 3 | ## Overview 4 | 5 | The RTL-SDR Manager library provides comprehensive control over console output from the native `librtlsdr` library. By default, all diagnostic messages are suppressed to provide a cleaner user experience in production applications. 6 | 7 | ## Why Suppress Console Output? 8 | 9 | The native `librtlsdr` library writes diagnostic messages directly to stdout and stderr, including: 10 | 11 | - `"Found Rafael Micro R820T tuner"` 12 | - `"[R82XX] PLL not locked!"` 13 | - Tuner initialization messages 14 | - Hardware state changes 15 | - Debug information 16 | 17 | While these messages are useful during development and debugging, they can clutter application logs and confuse end users in production environments. 18 | 19 | ## How It Works 20 | 21 | The suppression mechanism works at the **native file descriptor level** using POSIX `dup()`/`dup2()` system calls on Unix/Linux/macOS and Windows C runtime equivalents on Windows: 22 | 23 | 1. **Save Original Descriptors**: Duplicates stdout (FD 1) and stderr (FD 2) 24 | 2. **Redirect to Null Device**: Redirects both to `/dev/null` (Unix) or `NUL` (Windows) 25 | 3. **Execute Operation**: Runs the librtlsdr function with output suppressed 26 | 4. **Restore Descriptors**: Restores original stdout/stderr when done 27 | 28 | This approach catches **all** output from the native library, regardless of whether it uses stdout, stderr, or direct file descriptor writes. 29 | 30 | ## Usage Examples 31 | 32 | ### Basic Usage (Default Behavior) 33 | 34 | By default, all console output is suppressed: 35 | 36 | ```csharp 37 | using RtlSdrManager; 38 | 39 | var manager = RtlSdrDeviceManager.Instance; 40 | 41 | // Open device - no "Found Rafael Micro R820T tuner" message 42 | manager.OpenManagedDevice(0, "my-device"); 43 | var device = manager["my-device"]; 44 | 45 | // Configure device - no "[R82XX] PLL not locked!" messages 46 | device.CenterFrequency = Frequency.FromMHz(1090); 47 | device.SampleRate = Frequency.FromMHz(2); 48 | device.TunerGainMode = TunerGainModes.Manual; 49 | device.TunerGain = 42.1; 50 | 51 | // All operations are silent by default 52 | ``` 53 | 54 | ### Enabling Output for Debugging 55 | 56 | Enable output globally for all new devices: 57 | 58 | ```csharp 59 | using RtlSdrManager; 60 | 61 | var manager = RtlSdrDeviceManager.Instance; 62 | 63 | // Disable suppression globally 64 | RtlSdrDeviceManager.SuppressLibraryConsoleOutput = false; 65 | 66 | // New devices will show all librtlsdr messages 67 | manager.OpenManagedDevice(0, "debug-device"); 68 | var device = manager["debug-device"]; 69 | 70 | // Will show: "Found Rafael Micro R820T tuner" 71 | // Will show: "[R82XX] PLL not locked!" (if applicable) 72 | device.CenterFrequency = Frequency.FromMHz(1090); 73 | ``` 74 | 75 | ### Per-Device Control 76 | 77 | Control suppression independently for each device: 78 | 79 | ```csharp 80 | using RtlSdrManager; 81 | 82 | var manager = RtlSdrDeviceManager.Instance; 83 | 84 | // Open two devices with default suppression 85 | manager.OpenManagedDevice(0, "device1"); 86 | manager.OpenManagedDevice(1, "device2"); 87 | 88 | var device1 = manager["device1"]; 89 | var device2 = manager["device2"]; 90 | 91 | // Enable output only for device1 92 | device1.SuppressLibraryConsoleOutput = false; 93 | 94 | // device1 shows output, device2 is silent 95 | device1.SampleRate = Frequency.FromMHz(2); // Shows librtlsdr messages 96 | device2.SampleRate = Frequency.FromMHz(2); // Silent 97 | ``` 98 | 99 | ### Runtime Toggle 100 | 101 | Toggle suppression dynamically during operation: 102 | 103 | ```csharp 104 | using RtlSdrManager; 105 | 106 | var manager = RtlSdrDeviceManager.Instance; 107 | manager.OpenManagedDevice(0, "my-device"); 108 | var device = manager["my-device"]; 109 | 110 | // Start with suppression enabled (default) 111 | Console.WriteLine("=== Silent Mode ==="); 112 | device.SampleRate = Frequency.FromMHz(2.0); 113 | device.CenterFrequency = Frequency.FromMHz(1090); 114 | 115 | // Enable output for troubleshooting 116 | device.SuppressLibraryConsoleOutput = false; 117 | Console.WriteLine("\n=== Debug Mode ==="); 118 | device.SampleRate = Frequency.FromMHz(2.4); // Shows output 119 | device.TunerGain = 40.0; // Shows output 120 | 121 | // Disable output again 122 | device.SuppressLibraryConsoleOutput = true; 123 | Console.WriteLine("\n=== Silent Mode Restored ==="); 124 | device.CenterFrequency = Frequency.FromMHz(1095); // Silent again 125 | ``` 126 | 127 | ### Mixed Device Configuration 128 | 129 | Use different settings for different devices in the same application: 130 | 131 | ```csharp 132 | using RtlSdrManager; 133 | 134 | var manager = RtlSdrDeviceManager.Instance; 135 | 136 | // Production device - keep suppression enabled 137 | manager.OpenManagedDevice(0, "production"); 138 | var prodDevice = manager["production"]; 139 | prodDevice.SuppressLibraryConsoleOutput = true; 140 | 141 | // Development device - enable diagnostic output 142 | manager.OpenManagedDevice(1, "development"); 143 | var devDevice = manager["development"]; 144 | devDevice.SuppressLibraryConsoleOutput = false; 145 | 146 | // Production device runs silently 147 | prodDevice.CenterFrequency = Frequency.FromMHz(1090); 148 | prodDevice.SampleRate = Frequency.FromMHz(2); 149 | 150 | // Development device shows all diagnostics 151 | devDevice.CenterFrequency = Frequency.FromMHz(1090); 152 | devDevice.SampleRate = Frequency.FromMHz(2); 153 | ``` 154 | 155 | ## Use Cases 156 | 157 | ### 1. Production Applications 158 | 159 | **Scenario**: End-user facing application where clean logs are important. 160 | 161 | ```csharp 162 | // Keep suppression enabled (default) 163 | var manager = RtlSdrDeviceManager.Instance; 164 | manager.OpenManagedDevice(0, "main"); 165 | var device = manager["main"]; 166 | 167 | // All operations are silent - users see only application messages 168 | Logger.Info("Configuring RTL-SDR receiver..."); 169 | device.CenterFrequency = Frequency.FromMHz(1090); 170 | device.SampleRate = Frequency.FromMHz(2); 171 | Logger.Info("Receiver configured successfully"); 172 | ``` 173 | 174 | ### 2. Development and Testing 175 | 176 | **Scenario**: Debugging tuner issues during development. 177 | 178 | ```csharp 179 | // Enable output to see hardware diagnostics 180 | RtlSdrDeviceManager.SuppressLibraryConsoleOutput = false; 181 | 182 | var manager = RtlSdrDeviceManager.Instance; 183 | manager.OpenManagedDevice(0, "test"); 184 | var device = manager["test"]; 185 | 186 | // See tuner initialization messages 187 | Console.WriteLine("Testing tuner configuration..."); 188 | device.CenterFrequency = Frequency.FromMHz(1090); 189 | device.TunerGainMode = TunerGainModes.Manual; 190 | 191 | // Observe PLL lock status 192 | device.TunerGain = 42.1; 193 | ``` 194 | 195 | ### 3. Logging and Diagnostics 196 | 197 | **Scenario**: Capture librtlsdr messages for analysis without displaying to console. 198 | 199 | ```csharp 200 | using System.IO; 201 | 202 | // Redirect console output to capture librtlsdr messages 203 | var originalOut = Console.Out; 204 | var logWriter = new StringWriter(); 205 | Console.SetOut(logWriter); 206 | 207 | // Enable librtlsdr output 208 | var manager = RtlSdrDeviceManager.Instance; 209 | RtlSdrDeviceManager.SuppressLibraryConsoleOutput = false; 210 | 211 | manager.OpenManagedDevice(0, "logging-test"); 212 | var device = manager["logging-test"]; 213 | device.CenterFrequency = Frequency.FromMHz(1090); 214 | 215 | // Restore and analyze captured output 216 | Console.SetOut(originalOut); 217 | string capturedOutput = logWriter.ToString(); 218 | Logger.Debug($"LibRtlSdr output: {capturedOutput}"); 219 | ``` 220 | 221 | ### 4. Multi-Device Setup 222 | 223 | **Scenario**: Managing multiple RTL-SDR devices with different logging requirements. 224 | 225 | ```csharp 226 | var manager = RtlSdrDeviceManager.Instance; 227 | 228 | // Primary device - silent operation 229 | manager.OpenManagedDevice(0, "primary"); 230 | var primary = manager["primary"]; 231 | primary.SuppressLibraryConsoleOutput = true; 232 | 233 | // Secondary device - diagnostic mode 234 | manager.OpenManagedDevice(1, "secondary"); 235 | var secondary = manager["secondary"]; 236 | secondary.SuppressLibraryConsoleOutput = false; 237 | 238 | // Reference device - silent operation 239 | manager.OpenManagedDevice(2, "reference"); 240 | var reference = manager["reference"]; 241 | reference.SuppressLibraryConsoleOutput = true; 242 | 243 | // Only secondary device shows output 244 | ConfigureAllDevices(primary, secondary, reference); 245 | ``` 246 | 247 | ### 5. Conditional Debugging 248 | 249 | **Scenario**: Enable diagnostics based on configuration or command-line flags. 250 | 251 | ```csharp 252 | using RtlSdrManager; 253 | 254 | public class RtlSdrApplication 255 | { 256 | private readonly bool _debugMode; 257 | 258 | public RtlSdrApplication(bool debugMode) 259 | { 260 | _debugMode = debugMode; 261 | 262 | // Set global default based on debug mode 263 | RtlSdrDeviceManager.SuppressLibraryConsoleOutput = !debugMode; 264 | } 265 | 266 | public void Run() 267 | { 268 | var manager = RtlSdrDeviceManager.Instance; 269 | manager.OpenManagedDevice(0, "main"); 270 | var device = manager["main"]; 271 | 272 | if (_debugMode) 273 | { 274 | Console.WriteLine("=== Debug Mode Enabled ==="); 275 | Console.WriteLine("LibRtlSdr diagnostics will be shown"); 276 | } 277 | 278 | // Configure device - output controlled by debug mode 279 | device.CenterFrequency = Frequency.FromMHz(1090); 280 | device.SampleRate = Frequency.FromMHz(2); 281 | } 282 | } 283 | 284 | // Usage 285 | static void Main(string[] args) 286 | { 287 | bool debugMode = args.Contains("--debug"); 288 | var app = new RtlSdrApplication(debugMode); 289 | app.Run(); 290 | } 291 | ``` 292 | 293 | ## API Reference 294 | 295 | ### Global Setting 296 | 297 | ```csharp 298 | // Property on RtlSdrDeviceManager 299 | public static bool SuppressLibraryConsoleOutput { get; set; } 300 | ``` 301 | 302 | - **Default**: `true` (suppression enabled) 303 | - **Purpose**: Sets the default for newly opened devices 304 | - **Scope**: Global - affects all devices opened after the value is changed 305 | 306 | ### Per-Device Setting 307 | 308 | ```csharp 309 | // Property on RtlSdrManagedDevice 310 | public bool SuppressLibraryConsoleOutput { get; set; } 311 | ``` 312 | 313 | - **Default**: Inherited from `RtlSdrDeviceManager.SuppressLibraryConsoleOutput` at device open time 314 | - **Purpose**: Controls suppression for a specific device instance 315 | - **Scope**: Instance - can be changed at runtime without affecting other devices 316 | 317 | ## Technical Details 318 | 319 | ### Platform-Specific Implementation 320 | 321 | **Unix/Linux/macOS**: 322 | - Uses POSIX `dup()`, `dup2()`, `open()`, `close()` functions from `libc` 323 | - Redirects to `/dev/null` device 324 | - Includes `fflush()` calls to ensure buffer synchronization 325 | 326 | **Windows**: 327 | - Uses C runtime `_dup()`, `_dup2()`, `_open()`, `_close()` functions from `msvcrt.dll` 328 | - Redirects to `NUL` device 329 | - Includes `fflush()` calls to ensure buffer synchronization 330 | 331 | ### Scope of Suppression 332 | 333 | The suppression affects: 334 | - ✅ Device opening and initialization 335 | - ✅ Frequency changes (center frequency, sample rate) 336 | - ✅ Gain control operations (AGC, manual gain) 337 | - ✅ Tuner configuration (bandwidth, gain mode) 338 | - ✅ Advanced features (direct sampling, offset tuning, bias tee) 339 | - ✅ All librtlsdr diagnostic messages 340 | 341 | The suppression does NOT affect: 342 | - ❌ .NET Console.WriteLine() calls in your application 343 | - ❌ Logging from your application code 344 | - ❌ Exception messages from the library 345 | 346 | ### Performance Impact 347 | 348 | The suppression mechanism has minimal performance overhead: 349 | - File descriptor operations are very fast (microseconds) 350 | - Only active during librtlsdr function calls 351 | - No buffering or string processing required 352 | - Zero impact when suppression is disabled 353 | 354 | ## Best Practices 355 | 356 | ### 1. Use Default Suppression in Production 357 | 358 | ```csharp 359 | // Production code - rely on defaults 360 | var manager = RtlSdrDeviceManager.Instance; 361 | manager.OpenManagedDevice(0, "production-device"); 362 | // Suppression is enabled by default 363 | ``` 364 | 365 | ### 2. Enable Diagnostics During Development 366 | 367 | ```csharp 368 | #if DEBUG 369 | RtlSdrDeviceManager.SuppressLibraryConsoleOutput = false; 370 | #endif 371 | ``` 372 | 373 | ### 3. Per-Device Control for Mixed Scenarios 374 | 375 | ```csharp 376 | // Multiple devices with different requirements 377 | var mainDevice = manager["main"]; 378 | mainDevice.SuppressLibraryConsoleOutput = true; // Production device 379 | 380 | var testDevice = manager["test"]; 381 | testDevice.SuppressLibraryConsoleOutput = false; // Test device 382 | ``` 383 | 384 | ### 4. Temporary Diagnostics 385 | 386 | ```csharp 387 | // Temporarily enable for troubleshooting 388 | var originalSetting = device.SuppressLibraryConsoleOutput; 389 | try 390 | { 391 | device.SuppressLibraryConsoleOutput = false; 392 | // Diagnose issue 393 | device.CenterFrequency = Frequency.FromMHz(1090); 394 | } 395 | finally 396 | { 397 | device.SuppressLibraryConsoleOutput = originalSetting; 398 | } 399 | ``` 400 | 401 | ## Troubleshooting 402 | 403 | ### Messages Still Appearing 404 | 405 | If you still see console output: 406 | 407 | 1. **Check the setting is enabled**: 408 | ```csharp 409 | Console.WriteLine($"Suppression enabled: {device.SuppressLibraryConsoleOutput}"); 410 | ``` 411 | 412 | 2. **Verify timing** - The setting must be applied before the operation: 413 | ```csharp 414 | device.SuppressLibraryConsoleOutput = true; // Set first 415 | device.SampleRate = Frequency.FromMHz(2); // Then perform operation 416 | ``` 417 | 418 | 3. **Check for .NET console writes** - Make sure the output is from librtlsdr, not your code 419 | 420 | ### Platform-Specific Issues 421 | 422 | If suppression doesn't work on a specific platform: 423 | 424 | - The implementation gracefully falls back to no suppression if the native APIs fail 425 | - Check that the platform supports the required POSIX/Win32 functions 426 | - Look for any platform-specific restrictions on file descriptor manipulation 427 | 428 | ## See Also 429 | 430 | - [Basic Setup](BASIC_SETUP.md) - Getting started with RTL-SDR Manager 431 | - [Device Management](DEVICE_MANAGEMENT.md) - Managing multiple devices 432 | - [Manual Gain Control](MANUAL_GAIN_CONTROL.md) - Advanced tuner configuration 433 | - [Main README](../README.md) - Library overview and features 434 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to RTL-SDR Manager 2 | 3 | Thank you for your interest in contributing to RTL-SDR Manager! This document provides guidelines and instructions for contributing to the project. 4 | 5 | ## 📋 Table of Contents 6 | 7 | - [Code of Conduct](#code-of-conduct) 8 | - [Getting Started](#getting-started) 9 | - [Development Setup](#development-setup) 10 | - [How to Contribute](#how-to-contribute) 11 | - [Coding Standards](#coding-standards) 12 | - [Pull Request Process](#pull-request-process) 13 | - [Reporting Bugs](#reporting-bugs) 14 | - [Suggesting Features](#suggesting-features) 15 | - [Documentation](#documentation) 16 | - [Community](#community) 17 | 18 | ## 📜 Code of Conduct 19 | 20 | This project adheres to the [Contributor Covenant Code of Conduct](CODE_OF_CONDUCT.md). By participating, you are expected to uphold this code. Please report unacceptable behavior to the project maintainers. 21 | 22 | ## 🚀 Getting Started 23 | 24 | ### Prerequisites 25 | 26 | Before you begin, ensure you have the following installed: 27 | 28 | - **.NET 9.0 SDK or later** - [Download here](https://dotnet.microsoft.com/download) 29 | - **Git** - Version control system 30 | - **librtlsdr** - Native RTL-SDR library for your platform 31 | - **Windows:** `choco install rtl-sdr` or download from [releases](https://github.com/osmocom/rtl-sdr/releases) 32 | - **Linux:** `sudo apt-get install librtlsdr-dev` 33 | - **macOS:** `brew install librtlsdr` 34 | - **IDE** (optional but recommended): 35 | - Visual Studio 2022 36 | - JetBrains Rider 37 | - Visual Studio Code with C# extension 38 | 39 | ### Fork and Clone 40 | 41 | 1. Fork the repository on GitHub 42 | 2. Clone your fork locally: 43 | ```bash 44 | git clone https://github.com/YOUR-USERNAME/rtlsdr-manager.git 45 | cd rtlsdr-manager 46 | ``` 47 | 3. Add the upstream repository: 48 | ```bash 49 | git remote add upstream https://github.com/nandortoth/rtlsdr-manager.git 50 | ``` 51 | 52 | ## 🛠️ Development Setup 53 | 54 | ### Building the Project 55 | 56 | ```bash 57 | # Restore dependencies 58 | dotnet restore 59 | 60 | # Build the solution 61 | dotnet build 62 | 63 | # Build in Release mode 64 | dotnet build --configuration Release 65 | 66 | # Or use the convenience script 67 | ./build.sh 68 | ``` 69 | 70 | ### Running Tests 71 | 72 | ```bash 73 | # Run all tests (when available) 74 | dotnet test 75 | 76 | # Run tests with detailed output 77 | dotnet test --verbosity detailed 78 | 79 | # Run tests with coverage (if configured) 80 | dotnet test --collect:"XPlat Code Coverage" 81 | ``` 82 | 83 | ### Running Samples 84 | 85 | ```bash 86 | # Using the convenience script 87 | ./runsample.sh 88 | 89 | # Or manually 90 | dotnet run --project samples/RtlSdrManager.Samples 91 | 92 | # Build and run in Release mode 93 | dotnet run --project samples/RtlSdrManager.Samples --configuration Release 94 | ``` 95 | 96 | ### Verify Code Style 97 | 98 | The project uses `.editorconfig` for code style enforcement. Most IDEs will automatically apply these rules. 99 | 100 | ```bash 101 | # Format code according to .editorconfig 102 | dotnet format 103 | 104 | # Check formatting without making changes 105 | dotnet format --verify-no-changes 106 | ``` 107 | 108 | ## 🤝 How to Contribute 109 | 110 | ### Types of Contributions 111 | 112 | We welcome various types of contributions: 113 | 114 | - 🐛 **Bug fixes** - Fix issues and improve stability 115 | - ✨ **New features** - Add new functionality 116 | - 📝 **Documentation** - Improve or add documentation 117 | - 🧪 **Tests** - Add or improve test coverage 118 | - 🎨 **Code quality** - Refactoring and improvements 119 | - 🌍 **Examples** - Add sample applications 120 | - 🔧 **Tooling** - Improve build scripts and tools 121 | 122 | ### Contribution Workflow 123 | 124 | 1. **Check existing issues** - Look for existing issues or create a new one 125 | 2. **Discuss major changes** - For significant changes, open an issue first to discuss 126 | 3. **Create a branch** - Use a descriptive branch name 127 | 4. **Make your changes** - Follow coding standards 128 | 5. **Write tests** - Add tests for new functionality 129 | 6. **Update documentation** - Update README, docs, or XML comments 130 | 7. **Commit your changes** - Use clear commit messages 131 | 8. **Push to your fork** - Push your branch to GitHub 132 | 9. **Open a Pull Request** - Submit your PR with a clear description 133 | 134 | ### Branch Naming Convention 135 | 136 | Use descriptive branch names following this pattern: 137 | 138 | - `feature/description` - New features 139 | - `bugfix/description` - Bug fixes 140 | - `docs/description` - Documentation updates 141 | - `refactor/description` - Code refactoring 142 | - `test/description` - Test additions/improvements 143 | 144 | Examples: 145 | ```bash 146 | git checkout -b feature/add-async-cancellation 147 | git checkout -b bugfix/fix-frequency-overflow 148 | git checkout -b docs/improve-readme-examples 149 | ``` 150 | 151 | ## 📏 Coding Standards 152 | 153 | ### Code Style 154 | 155 | This project enforces code style through `.editorconfig`. Key guidelines: 156 | 157 | #### Formatting 158 | - **Indentation:** 4 spaces (no tabs) 159 | - **Line endings:** LF (Unix-style) 160 | - **Braces:** Allman style (opening brace on new line) 161 | - **File-scoped namespaces:** Required for new code 162 | 163 | ```csharp 164 | // ✅ Good - File-scoped namespace 165 | namespace RtlSdrManager; 166 | 167 | public class MyClass 168 | { 169 | public void MyMethod() 170 | { 171 | // Method body 172 | } 173 | } 174 | 175 | // ❌ Bad - Block-scoped namespace (legacy only) 176 | namespace RtlSdrManager 177 | { 178 | public class MyClass { } 179 | } 180 | ``` 181 | 182 | #### Naming Conventions 183 | - **Classes, Methods, Properties:** `PascalCase` 184 | - **Private fields:** `_camelCase` (with underscore prefix) 185 | - **Parameters, Local variables:** `camelCase` 186 | - **Constants:** `PascalCase` 187 | - **Interfaces:** `IPascalCase` (prefix with I) 188 | 189 | ```csharp 190 | // ✅ Good 191 | public class DeviceManager 192 | { 193 | private readonly string _deviceName; 194 | private int _deviceCount; 195 | 196 | public const int MaxDevices = 10; 197 | 198 | public void OpenDevice(string friendlyName) 199 | { 200 | var deviceIndex = 0; 201 | // ... 202 | } 203 | } 204 | ``` 205 | 206 | #### var Usage 207 | - **Don't use** for built-in types: `int`, `string`, `bool`, etc. 208 | - **Do use** when type is obvious: `new ClassName()` 209 | - **Don't use** when type is unclear: method return values 210 | 211 | ```csharp 212 | // ✅ Good 213 | int count = 5; 214 | string name = "test"; 215 | var manager = new RtlSdrDeviceManager(); 216 | var frequency = new Frequency(1000); 217 | 218 | // ❌ Bad 219 | var count = 5; // Use explicit type for primitives 220 | var result = GetSomething(); // Type not obvious 221 | ``` 222 | 223 | ### XML Documentation 224 | 225 | All public APIs must have XML documentation: 226 | 227 | ```csharp 228 | /// 229 | /// Opens an RTL-SDR device for management. 230 | /// 231 | /// The device index (0-based). 232 | /// A friendly name to reference the device. 233 | /// Thrown when friendlyName is null. 234 | /// Thrown when friendlyName is empty or a device with that name already exists. 235 | /// Thrown when the device cannot be opened. 236 | public void OpenManagedDevice(uint index, string friendlyName) 237 | { 238 | // Implementation 239 | } 240 | ``` 241 | 242 | ### Exception Handling 243 | 244 | Use appropriate exception types: 245 | 246 | ```csharp 247 | // ✅ Good - Proper exception types 248 | if (friendlyName == null) 249 | throw new ArgumentNullException(nameof(friendlyName)); 250 | 251 | if (string.IsNullOrWhiteSpace(friendlyName)) 252 | throw new ArgumentException("Cannot be empty", nameof(friendlyName)); 253 | 254 | if (!deviceExists) 255 | throw new RtlSdrDeviceException($"Device {index} not found"); 256 | 257 | // ❌ Bad - Wrong exception types 258 | if (friendlyName == null) 259 | throw new Exception("Name is null"); // Too generic 260 | 261 | if (!deviceExists) 262 | throw new IndexOutOfRangeException(); // Wrong type 263 | ``` 264 | 265 | ### Dispose Pattern 266 | 267 | For classes managing unmanaged resources: 268 | 269 | ```csharp 270 | public class MyResource : IDisposable 271 | { 272 | private bool _disposed; 273 | 274 | public void Dispose() 275 | { 276 | Dispose(disposing: true); 277 | GC.SuppressFinalize(this); 278 | } 279 | 280 | private void Dispose(bool disposing) 281 | { 282 | if (_disposed) 283 | return; 284 | 285 | if (disposing) 286 | { 287 | // Dispose managed resources 288 | } 289 | 290 | // Release unmanaged resources 291 | 292 | _disposed = true; 293 | } 294 | 295 | ~MyResource() 296 | { 297 | Dispose(disposing: false); 298 | } 299 | } 300 | ``` 301 | 302 | ### Async/Await Best Practices 303 | 304 | ```csharp 305 | // ✅ Good - Proper async/await 306 | public async Task ReadDataAsync(CancellationToken cancellationToken = default) 307 | { 308 | await Task.Delay(100, cancellationToken); 309 | return new IQData(); 310 | } 311 | 312 | // ✅ Good - Dispose IDisposable in async methods 313 | public async Task ProcessDataAsync() 314 | { 315 | using var cts = new CancellationTokenSource(); 316 | await ReadDataAsync(cts.Token); 317 | } 318 | 319 | // ❌ Bad - Async void (only for event handlers) 320 | public async void ProcessData() // Don't use async void 321 | { 322 | await Task.Delay(100); 323 | } 324 | ``` 325 | 326 | ## 🔄 Pull Request Process 327 | 328 | ### Before Submitting 329 | 330 | Ensure your PR meets these requirements: 331 | 332 | - [ ] Code follows the project's style guidelines (`.editorconfig`) 333 | - [ ] Code builds without warnings: `dotnet build` 334 | - [ ] All tests pass (when tests exist) 335 | - [ ] New code has XML documentation comments 336 | - [ ] README.md is updated (if needed) 337 | - [ ] CHANGELOG.md is updated with your changes 338 | - [ ] Commit messages are clear and descriptive 339 | 340 | ### PR Title Format 341 | 342 | Use a clear, descriptive title: 343 | 344 | - `feat: Add support for async cancellation tokens` 345 | - `fix: Correct frequency overflow in calculations` 346 | - `docs: Improve README installation instructions` 347 | - `refactor: Simplify device manager initialization` 348 | - `test: Add unit tests for Frequency type` 349 | 350 | ### PR Description Template 351 | 352 | ```markdown 353 | ## Description 354 | Brief description of what this PR does. 355 | 356 | ## Motivation and Context 357 | Why is this change needed? What problem does it solve? 358 | 359 | ## How Has This Been Tested? 360 | Describe how you tested your changes. 361 | 362 | ## Types of changes 363 | - [ ] Bug fix (non-breaking change which fixes an issue) 364 | - [ ] New feature (non-breaking change which adds functionality) 365 | - [ ] Breaking change (fix or feature that would cause existing functionality to change) 366 | - [ ] Documentation update 367 | 368 | ## Checklist 369 | - [ ] My code follows the code style of this project 370 | - [ ] I have updated the documentation accordingly 371 | - [ ] I have added tests to cover my changes (if applicable) 372 | - [ ] All new and existing tests passed 373 | - [ ] I have updated CHANGELOG.md 374 | ``` 375 | 376 | ### Review Process 377 | 378 | 1. A maintainer will review your PR 379 | 2. Feedback may be provided - please address comments 380 | 3. Once approved, a maintainer will merge your PR 381 | 4. Your contribution will be included in the next release 382 | 383 | ## 🐛 Reporting Bugs 384 | 385 | ### Before Reporting 386 | 387 | - Check if the bug has already been reported in [Issues](https://github.com/nandortoth/rtlsdr-manager/issues) 388 | - Ensure you're using the latest version 389 | - Verify the issue is reproducible 390 | 391 | ### Bug Report Template 392 | 393 | When reporting bugs, include: 394 | 395 | ```markdown 396 | **Description** 397 | Clear description of the bug. 398 | 399 | **To Reproduce** 400 | Steps to reproduce the behavior: 401 | 1. Initialize device with '...' 402 | 2. Set frequency to '...' 403 | 3. Call method '...' 404 | 4. See error 405 | 406 | **Expected Behavior** 407 | What you expected to happen. 408 | 409 | **Actual Behavior** 410 | What actually happened. 411 | 412 | **Environment** 413 | - OS: [e.g., Windows 10, Ubuntu 22.04, macOS 14] 414 | - .NET Version: [e.g., 9.0.1] 415 | - RtlSdrManager Version: [e.g., 0.5.0] 416 | - RTL-SDR Device: [e.g., RTL2832U with R820T tuner] 417 | - librtlsdr Version: [if known] 418 | 419 | **Additional Context** 420 | Any other relevant information, logs, or screenshots. 421 | ``` 422 | 423 | ## 💡 Suggesting Features 424 | 425 | ### Feature Request Guidelines 426 | 427 | When suggesting features: 428 | 429 | 1. **Check existing issues** - See if it's already been suggested 430 | 2. **Describe the use case** - Why is this feature needed? 431 | 3. **Provide examples** - How would the API look? 432 | 4. **Consider alternatives** - Are there other solutions? 433 | 434 | ### Feature Request Template 435 | 436 | ```markdown 437 | **Is your feature request related to a problem?** 438 | Clear description of the problem. 439 | 440 | **Describe the solution you'd like** 441 | Clear description of what you want to happen. 442 | 443 | **Describe alternatives you've considered** 444 | Any alternative solutions or features you've considered. 445 | 446 | **Additional context** 447 | Any other context or screenshots about the feature request. 448 | 449 | **Proposed API (if applicable)** 450 | ```csharp 451 | // Example of how the API might look 452 | device.NewFeature(parameter); 453 | ``` 454 | 455 | ## 📚 Documentation 456 | 457 | ### Documentation Contributions 458 | 459 | Documentation improvements are always welcome: 460 | 461 | - **README.md** - Main project documentation 462 | - **CHANGELOG.md** - Version history (follow [Keep a Changelog](https://keepachangelog.com/)) 463 | - **docs/** - Detailed feature documentation 464 | - **Code comments** - XML documentation for public APIs 465 | - **Examples** - Sample applications showing usage 466 | 467 | ### Documentation Style 468 | 469 | - Use clear, concise language 470 | - Include code examples where appropriate 471 | - Keep examples up-to-date with the current API 472 | - Use proper markdown formatting 473 | - Add links to related documentation 474 | 475 | ## 🌍 Community 476 | 477 | ### Getting Help 478 | 479 | - **GitHub Issues** - For bugs and feature requests 480 | - **GitHub Discussions** - For questions and discussions (if enabled) 481 | - **Email** - Contact the maintainer at dev@nandortoth.com 482 | 483 | ### Stay Updated 484 | 485 | - Watch the repository for updates 486 | - Check [CHANGELOG.md](CHANGELOG.md) for version changes 487 | - Follow releases for new versions 488 | 489 | ## 📄 License 490 | 491 | By contributing to RTL-SDR Manager, you agree that your contributions will be licensed under the [GNU General Public License v3.0 or later](LICENSE.md). 492 | 493 | --- 494 | 495 | ## 🙏 Thank You! 496 | 497 | Thank you for contributing to RTL-SDR Manager! Your efforts help make this project better for everyone. 498 | 499 | **Happy coding!** 🚀 500 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: https://EditorConfig.org 2 | # 3 | # top-most EditorConfig file 4 | root = true 5 | 6 | ########################################### 7 | # All files 8 | ########################################### 9 | [*] 10 | charset = utf-8 11 | indent_style = space 12 | insert_final_newline = true 13 | trim_trailing_whitespace = true 14 | 15 | ########################################### 16 | # Code files 17 | ########################################### 18 | [*.{cs,csx,vb,vbx}] 19 | indent_size = 4 20 | 21 | ########################################### 22 | # Solution and project files 23 | ########################################### 24 | [*.{sln,csproj,vbproj,fsproj,vcxproj,vcxproj.filters,proj,projitems,shproj}] 25 | indent_size = 2 26 | 27 | ########################################### 28 | # XML and config files 29 | ########################################### 30 | [*.{xml,config,props,targets,nuspec,resx,ruleset,vsixmanifest,vsct}] 31 | indent_size = 2 32 | 33 | ########################################### 34 | # JSON files 35 | ########################################### 36 | [*.json] 37 | indent_size = 2 38 | 39 | ########################################### 40 | # YAML files 41 | ########################################### 42 | [*.{yml,yaml}] 43 | indent_size = 2 44 | 45 | ########################################### 46 | # Shell scripts 47 | ########################################### 48 | [*.sh] 49 | end_of_line = lf 50 | indent_size = 2 51 | 52 | ########################################### 53 | # Git config files 54 | ########################################### 55 | [.gitattributes] 56 | insert_final_newline = true 57 | 58 | [.gitignore] 59 | insert_final_newline = true 60 | 61 | ########################################### 62 | # Markdown files 63 | ########################################### 64 | [*.md] 65 | trim_trailing_whitespace = false 66 | 67 | ########################################### 68 | # C# files 69 | ########################################### 70 | [*.cs] 71 | 72 | #### Core EditorConfig Options #### 73 | 74 | # Indentation and spacing 75 | indent_size = 4 76 | indent_style = space 77 | tab_width = 4 78 | 79 | # New line preferences 80 | # Note: Using LF for cross-platform compatibility. 81 | # If you need CRLF on Windows, let Git handle it via .gitattributes: 82 | # * text=auto 83 | # *.cs text eol=lf 84 | end_of_line = lf 85 | insert_final_newline = true 86 | 87 | #### .NET Coding Conventions #### 88 | 89 | # Organize usings 90 | dotnet_sort_system_directives_first = true 91 | dotnet_separate_import_directive_groups = false 92 | 93 | # this. preferences 94 | dotnet_style_qualification_for_field = false:warning 95 | dotnet_style_qualification_for_property = false:warning 96 | dotnet_style_qualification_for_method = false:warning 97 | dotnet_style_qualification_for_event = false:warning 98 | 99 | # Language keywords vs BCL types preferences 100 | dotnet_style_predefined_type_for_locals_parameters_members = true:warning 101 | dotnet_style_predefined_type_for_member_access = true:warning 102 | 103 | # Parentheses preferences 104 | dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:suggestion 105 | dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:suggestion 106 | dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:suggestion 107 | dotnet_style_parentheses_in_other_operators = never_if_unnecessary:suggestion 108 | 109 | # Modifier preferences 110 | dotnet_style_require_accessibility_modifiers = for_non_interface_members:warning 111 | dotnet_style_readonly_field = true:warning 112 | 113 | # Expression-level preferences 114 | dotnet_style_object_initializer = true:suggestion 115 | dotnet_style_collection_initializer = true:suggestion 116 | dotnet_style_explicit_tuple_names = true:warning 117 | dotnet_style_null_propagation = true:warning 118 | dotnet_style_coalesce_expression = true:warning 119 | dotnet_style_prefer_is_null_check_over_reference_equality_method = true:warning 120 | dotnet_style_prefer_inferred_tuple_names = true:suggestion 121 | dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion 122 | dotnet_style_prefer_auto_properties = true:suggestion 123 | dotnet_style_prefer_conditional_expression_over_assignment = true:suggestion 124 | dotnet_style_prefer_conditional_expression_over_return = true:suggestion 125 | dotnet_style_prefer_compound_assignment = true:suggestion 126 | dotnet_style_prefer_simplified_interpolation = true:suggestion 127 | dotnet_style_prefer_simplified_boolean_expressions = true:suggestion 128 | 129 | # Namespace preferences 130 | dotnet_style_namespace_match_folder = true:suggestion 131 | 132 | #### C# Coding Conventions #### 133 | 134 | # var preferences 135 | csharp_style_var_for_built_in_types = false:suggestion 136 | csharp_style_var_when_type_is_apparent = true:suggestion 137 | csharp_style_var_elsewhere = false:suggestion 138 | 139 | # Expression-bodied members 140 | csharp_style_expression_bodied_methods = when_on_single_line:suggestion 141 | csharp_style_expression_bodied_constructors = false:suggestion 142 | csharp_style_expression_bodied_operators = when_on_single_line:suggestion 143 | csharp_style_expression_bodied_properties = true:suggestion 144 | csharp_style_expression_bodied_indexers = true:suggestion 145 | csharp_style_expression_bodied_accessors = true:suggestion 146 | csharp_style_expression_bodied_lambdas = true:suggestion 147 | csharp_style_expression_bodied_local_functions = when_on_single_line:suggestion 148 | 149 | # Pattern matching preferences 150 | csharp_style_pattern_matching_over_is_with_cast_check = true:warning 151 | csharp_style_pattern_matching_over_as_with_null_check = true:warning 152 | csharp_style_prefer_switch_expression = true:suggestion 153 | csharp_style_prefer_pattern_matching = true:suggestion 154 | csharp_style_prefer_not_pattern = true:suggestion 155 | csharp_style_prefer_extended_property_pattern = true:suggestion 156 | 157 | # Null-checking preferences 158 | csharp_style_throw_expression = true:suggestion 159 | csharp_style_conditional_delegate_call = true:warning 160 | 161 | # Modifier preferences 162 | csharp_prefer_static_local_function = true:warning 163 | csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async:suggestion 164 | 165 | # Code-block preferences 166 | csharp_prefer_braces = true:suggestion 167 | csharp_prefer_simple_using_statement = true:suggestion 168 | csharp_style_namespace_declarations = file_scoped:warning 169 | csharp_style_prefer_method_group_conversion = true:suggestion 170 | csharp_style_prefer_top_level_statements = false:suggestion 171 | 172 | # Expression-level preferences 173 | csharp_prefer_simple_default_expression = true:suggestion 174 | csharp_style_pattern_local_over_anonymous_function = true:suggestion 175 | csharp_style_inlined_variable_declaration = true:warning 176 | csharp_style_deconstructed_variable_declaration = true:suggestion 177 | csharp_style_prefer_index_operator = true:suggestion 178 | csharp_style_prefer_range_operator = true:suggestion 179 | csharp_style_implicit_object_creation_when_type_is_apparent = true:suggestion 180 | csharp_style_prefer_tuple_swap = true:suggestion 181 | csharp_style_prefer_utf8_string_literals = true:suggestion 182 | csharp_style_unused_value_expression_statement_preference = discard_variable:suggestion 183 | csharp_style_unused_value_assignment_preference = discard_variable:suggestion 184 | 185 | # 'using' directive preferences 186 | csharp_using_directive_placement = outside_namespace:warning 187 | 188 | # New line preferences 189 | csharp_style_allow_embedded_statements_on_same_line_experimental = false:warning 190 | csharp_style_allow_blank_lines_between_consecutive_braces_experimental = false:suggestion 191 | csharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental = false:suggestion 192 | 193 | #### C# Formatting Rules #### 194 | 195 | # New line preferences 196 | csharp_new_line_before_open_brace = all 197 | csharp_new_line_before_else = true 198 | csharp_new_line_before_catch = true 199 | csharp_new_line_before_finally = true 200 | csharp_new_line_before_members_in_object_initializers = true 201 | csharp_new_line_before_members_in_anonymous_types = true 202 | csharp_new_line_between_query_expression_clauses = true 203 | 204 | # Indentation preferences 205 | csharp_indent_case_contents = true 206 | csharp_indent_switch_labels = true 207 | csharp_indent_labels = one_less_than_current 208 | csharp_indent_block_contents = true 209 | csharp_indent_braces = false 210 | csharp_indent_case_contents_when_block = false 211 | 212 | # Space preferences 213 | csharp_space_after_cast = false 214 | csharp_space_after_keywords_in_control_flow_statements = true 215 | csharp_space_between_parentheses = false 216 | csharp_space_before_colon_in_inheritance_clause = true 217 | csharp_space_after_colon_in_inheritance_clause = true 218 | csharp_space_around_binary_operators = before_and_after 219 | csharp_space_between_method_declaration_parameter_list_parentheses = false 220 | csharp_space_between_method_declaration_empty_parameter_list_parentheses = false 221 | csharp_space_between_method_declaration_name_and_open_parenthesis = false 222 | csharp_space_between_method_call_parameter_list_parentheses = false 223 | csharp_space_between_method_call_empty_parameter_list_parentheses = false 224 | csharp_space_between_method_call_name_and_opening_parenthesis = false 225 | csharp_space_after_comma = true 226 | csharp_space_before_comma = false 227 | csharp_space_after_dot = false 228 | csharp_space_before_dot = false 229 | csharp_space_after_semicolon_in_for_statement = true 230 | csharp_space_before_semicolon_in_for_statement = false 231 | csharp_space_around_declaration_statements = false 232 | csharp_space_before_open_square_brackets = false 233 | csharp_space_between_empty_square_brackets = false 234 | csharp_space_between_square_brackets = false 235 | 236 | # Wrapping preferences 237 | csharp_preserve_single_line_statements = false 238 | csharp_preserve_single_line_blocks = true 239 | 240 | #### Naming Conventions #### 241 | 242 | # Naming rules 243 | 244 | dotnet_naming_rule.interface_should_be_begins_with_i.severity = warning 245 | dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface 246 | dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i 247 | 248 | dotnet_naming_rule.types_should_be_pascal_case.severity = warning 249 | dotnet_naming_rule.types_should_be_pascal_case.symbols = types 250 | dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case 251 | 252 | dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = warning 253 | dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members 254 | dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case 255 | 256 | dotnet_naming_rule.private_or_internal_field_should_be_begins_with_underscore.severity = warning 257 | dotnet_naming_rule.private_or_internal_field_should_be_begins_with_underscore.symbols = private_or_internal_field 258 | dotnet_naming_rule.private_or_internal_field_should_be_begins_with_underscore.style = begins_with_underscore 259 | 260 | dotnet_naming_rule.constant_fields_should_be_pascal_case.severity = warning 261 | dotnet_naming_rule.constant_fields_should_be_pascal_case.symbols = constant_fields 262 | dotnet_naming_rule.constant_fields_should_be_pascal_case.style = pascal_case 263 | 264 | dotnet_naming_rule.static_readonly_fields_should_be_pascal_case.severity = warning 265 | dotnet_naming_rule.static_readonly_fields_should_be_pascal_case.symbols = static_readonly_fields 266 | dotnet_naming_rule.static_readonly_fields_should_be_pascal_case.style = pascal_case 267 | 268 | # Symbol specifications 269 | 270 | dotnet_naming_symbols.interface.applicable_kinds = interface 271 | dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected 272 | dotnet_naming_symbols.interface.required_modifiers = 273 | 274 | dotnet_naming_symbols.private_or_internal_field.applicable_kinds = field 275 | dotnet_naming_symbols.private_or_internal_field.applicable_accessibilities = internal, private, private_protected 276 | dotnet_naming_symbols.private_or_internal_field.required_modifiers = 277 | 278 | dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum 279 | dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected 280 | dotnet_naming_symbols.types.required_modifiers = 281 | 282 | dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method 283 | dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected 284 | dotnet_naming_symbols.non_field_members.required_modifiers = 285 | 286 | dotnet_naming_symbols.constant_fields.applicable_kinds = field 287 | dotnet_naming_symbols.constant_fields.applicable_accessibilities = * 288 | dotnet_naming_symbols.constant_fields.required_modifiers = const 289 | 290 | dotnet_naming_symbols.static_readonly_fields.applicable_kinds = field 291 | dotnet_naming_symbols.static_readonly_fields.applicable_accessibilities = * 292 | dotnet_naming_symbols.static_readonly_fields.required_modifiers = static, readonly 293 | 294 | # Naming styles 295 | 296 | dotnet_naming_style.pascal_case.required_prefix = 297 | dotnet_naming_style.pascal_case.required_suffix = 298 | dotnet_naming_style.pascal_case.word_separator = 299 | dotnet_naming_style.pascal_case.capitalization = pascal_case 300 | 301 | dotnet_naming_style.begins_with_i.required_prefix = I 302 | dotnet_naming_style.begins_with_i.required_suffix = 303 | dotnet_naming_style.begins_with_i.word_separator = 304 | dotnet_naming_style.begins_with_i.capitalization = pascal_case 305 | 306 | dotnet_naming_style.begins_with_underscore.required_prefix = _ 307 | dotnet_naming_style.begins_with_underscore.required_suffix = 308 | dotnet_naming_style.begins_with_underscore.word_separator = 309 | dotnet_naming_style.begins_with_underscore.capitalization = camel_case 310 | 311 | #### Code Quality Rules #### 312 | 313 | # CA1000: Do not declare static members on generic types 314 | dotnet_diagnostic.CA1000.severity = warning 315 | 316 | # CA1001: Types that own disposable fields should be disposable 317 | dotnet_diagnostic.CA1001.severity = warning 318 | 319 | # CA1010: Collections should implement generic interface 320 | dotnet_diagnostic.CA1010.severity = warning 321 | 322 | # CA1031: Do not catch general exception types 323 | dotnet_diagnostic.CA1031.severity = suggestion 324 | 325 | # CA1032: Implement standard exception constructors 326 | dotnet_diagnostic.CA1032.severity = warning 327 | 328 | # CA1062: Validate arguments of public methods 329 | dotnet_diagnostic.CA1062.severity = warning 330 | 331 | # CA1063: Implement IDisposable correctly 332 | dotnet_diagnostic.CA1063.severity = warning 333 | 334 | # CA1303: Do not pass literals as localized parameters 335 | dotnet_diagnostic.CA1303.severity = none 336 | 337 | # CA1304: Specify CultureInfo 338 | dotnet_diagnostic.CA1304.severity = suggestion 339 | 340 | # CA1305: Specify IFormatProvider 341 | dotnet_diagnostic.CA1305.severity = suggestion 342 | 343 | # CA1716: Identifiers should not match keywords 344 | dotnet_diagnostic.CA1716.severity = suggestion 345 | 346 | # CA1806: Do not ignore method results 347 | dotnet_diagnostic.CA1806.severity = warning 348 | 349 | # CA1810: Initialize reference type static fields inline 350 | dotnet_diagnostic.CA1810.severity = suggestion 351 | 352 | # CA1812: Avoid uninstantiated internal classes 353 | dotnet_diagnostic.CA1812.severity = warning 354 | 355 | # CA1819: Properties should not return arrays 356 | dotnet_diagnostic.CA1819.severity = suggestion 357 | 358 | # CA1821: Remove empty finalizers 359 | dotnet_diagnostic.CA1821.severity = warning 360 | 361 | # CA1822: Mark members as static 362 | dotnet_diagnostic.CA1822.severity = suggestion 363 | 364 | # CA1825: Avoid zero-length array allocations 365 | dotnet_diagnostic.CA1825.severity = warning 366 | 367 | # CA1826: Use property instead of Linq Enumerable method 368 | dotnet_diagnostic.CA1826.severity = suggestion 369 | 370 | # CA1827: Do not use Count/LongCount when Any can be used 371 | dotnet_diagnostic.CA1827.severity = warning 372 | 373 | # CA1829: Use Length/Count property instead of Enumerable.Count method 374 | dotnet_diagnostic.CA1829.severity = warning 375 | 376 | # CA1841: Prefer Dictionary Contains methods 377 | dotnet_diagnostic.CA1841.severity = warning 378 | 379 | # CA1851: Possible multiple enumerations 380 | dotnet_diagnostic.CA1851.severity = suggestion 381 | 382 | # CA2000: Dispose objects before losing scope 383 | dotnet_diagnostic.CA2000.severity = warning 384 | 385 | # CA2007: Do not directly await a Task (library code) 386 | dotnet_diagnostic.CA2007.severity = none 387 | 388 | # CA2101: Specify marshaling for P/Invoke string arguments 389 | dotnet_diagnostic.CA2101.severity = warning 390 | 391 | # CA2211: Non-constant fields should not be visible 392 | dotnet_diagnostic.CA2211.severity = warning 393 | 394 | # CA2225: Operator overloads have named alternates 395 | dotnet_diagnostic.CA2225.severity = suggestion 396 | 397 | # CA3075: Insecure DTD Processing 398 | dotnet_diagnostic.CA3075.severity = warning 399 | 400 | # CA5350: Do not use weak cryptographic algorithms 401 | dotnet_diagnostic.CA5350.severity = warning 402 | 403 | # CA5351: Do not use broken cryptographic algorithms 404 | dotnet_diagnostic.CA5351.severity = warning 405 | 406 | #### IDE Code Style Rules #### 407 | 408 | # IDE0001: Simplify name 409 | dotnet_diagnostic.IDE0001.severity = suggestion 410 | 411 | # IDE0002: Simplify member access 412 | dotnet_diagnostic.IDE0002.severity = suggestion 413 | 414 | # IDE0003: Remove this or Me qualification 415 | dotnet_diagnostic.IDE0003.severity = warning 416 | 417 | # IDE0004: Remove unnecessary cast 418 | dotnet_diagnostic.IDE0004.severity = warning 419 | 420 | # IDE0005: Remove unnecessary using directives 421 | dotnet_diagnostic.IDE0005.severity = warning 422 | 423 | # IDE0011: Add braces 424 | dotnet_diagnostic.IDE0011.severity = suggestion 425 | 426 | # IDE0040: Add accessibility modifiers 427 | dotnet_diagnostic.IDE0040.severity = warning 428 | 429 | # IDE0055: Fix formatting 430 | dotnet_diagnostic.IDE0055.severity = warning 431 | 432 | # IDE0161: Use file-scoped namespace 433 | dotnet_diagnostic.IDE0161.severity = warning 434 | 435 | #### Nullable Reference Types #### 436 | 437 | # CS8600: Converting null literal or possible null value to non-nullable type 438 | dotnet_diagnostic.CS8600.severity = warning 439 | 440 | # CS8601: Possible null reference assignment 441 | dotnet_diagnostic.CS8601.severity = warning 442 | 443 | # CS8602: Dereference of a possibly null reference 444 | dotnet_diagnostic.CS8602.severity = warning 445 | 446 | # CS8603: Possible null reference return 447 | dotnet_diagnostic.CS8603.severity = warning 448 | 449 | # CS8604: Possible null reference argument 450 | dotnet_diagnostic.CS8604.severity = warning 451 | 452 | # CS8618: Non-nullable field must contain a non-null value when exiting constructor 453 | dotnet_diagnostic.CS8618.severity = warning 454 | 455 | # CS8625: Cannot convert null literal to non-nullable reference type 456 | dotnet_diagnostic.CS8625.severity = warning 457 | --------------------------------------------------------------------------------