├── 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 |
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 | [](https://www.nuget.org/packages/RtlSdrManager/)
4 | [](https://www.nuget.org/packages/RtlSdrManager/)
5 | [](LICENSE.md)
6 | [](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 |
--------------------------------------------------------------------------------