Turn raw SDR radio samples into a position fix. gnss-rcv implements the full
GPS L1 C/A pipeline from scratch — signal acquisition, tracking, navigation
message decoding and least-squares positioning — with no GNSS library doing the
heavy lifting. Feed it an IQ recording (several formats / sample rates) or a live
rtl-sdr dongle, and it computes your latitude and longitude.
$ cargo run --release -- -f resources/gpssim_gen_2xi16 -t 2xi16 -x
file: resources/gpssim_gen_2xi16 -- 2xi16 350.4 MiB duration: 44.9 secs
G01: TRCK cn0=51.0 dopp=-2755 code_idx= 80 phi=-0.89 ts_sec=3.001
...
position fix: 46.207328,6.155321 h=0.4km https://maps.google.com/?ll=46.21,6.16flowchart LR
IQ["IQ samples<br/>(SDR / file)"] --> ACQ[Acquisition]
ACQ --> TRK["Tracking<br/>FLL · PLL · DLL"]
TRK --> NAV["Nav decode<br/>ephemeris"]
NAV --> SOL["Position<br/>solve"]
SOL --> FIX["lat / lon"]
- Acquisition — an FFT search over code phase × Doppler finds which satellites are present and their rough frequency/phase.
- Tracking — carrier (FLL/PLL) and code (DLL) loops lock onto each satellite and stay aligned as the signal drifts.
- Nav decode — the 50 bps navigation message is demodulated; three subframes yield each satellite's ephemeris (its precise orbit + clock).
- Position solve — pseudoranges from ≥4 satellites are combined into a single-point position fix via gnss-rtk.
The whole chain is exercised end-to-end by an integration test that simulates a recording for a chosen date/location and checks the receiver recovers it (see Simulate a recording).
cargo build --release
# Get a recording — either download a real capture...
./resources/fetch.sh nov3 # 12.7 GiB real rtl-sdr capture (2xf32)
# ...or simulate one (needs gps-sdr-sim; downloads the ephemeris for you):
./resources/gen_gpssim.sh # ~350 MiB, Geneva, ends in a verified fix
# Run the receiver until it computes the first position fix:
cargo run --release -- -f resources/gpssim_gen_2xi16 -t 2xi16 -xWith no -f, it runs against the default development recording (2xf32,
2.046 MHz, zero IF).
As it processes the IQ data, the receiver periodically writes a diagnostic web
page (plots/index.html + images, enabled with -p) showing the decoder's
internal state:
There is also a live UI (-u):
$ RUST_LOG=info cargo run --release -- -f path/to/recording.bin -t <format>-t |
sample layout |
|---|---|
2xf32 |
interleaved float32 I/Q (default) |
2xi16 |
interleaved int16 I/Q (e.g. gps-sdr-sim -b 16) |
i8 |
single int8, real-only |
rtlsdr-file |
interleaved uint8 I/Q (an rtl_sdr capture) |
1bit |
8 hard-limited 1-bit real samples packed per byte |
The PRN code is resampled to the actual rate, so any sampling frequency works.
Set the rate with --fs and the intermediate frequency with --fi (both in Hz,
default 2.046 MHz / 0 Hz):
# 1-bit real recording sampled at 5.456 MHz (IF 4.092 MHz aliases to 1.364 MHz):
$ cargo run --release -- -f resources/gps.samples.1bit.I.fs5456.if4092.bin \
-t 1bit --fs 5456000 --fi 1364000--num-msec N/--off-msec N: process only N ms, or start N ms into the file.--sats 1,11,30: restrict acquisition to a subset of PRNs.-p/--plots: write per-SV diagnostic PNGs toplots/(off by default).-x/--exit-on-fix: stop as soon as the first position fix is computed.-u: open the UI;-l <file>: also write logs to a file.
Use the helper script to fetch downloadable recordings into resources/:
$ ./resources/fetch.sh # list what's available
$ ./resources/fetch.sh nov3 # the main dev recording (2xf32, 12.7 GiB)
$ ./resources/fetch.sh cttc # gnss-sdr's CTTC Spain capture (2xi16, ~1.1 GiB)
$ ./resources/fetch.sh all # everythingThe recording used for most of the development is nov_3_time_18_48_st_ives
(gypsum release,
unzip into resources/, -t 2xf32). Another good one is gnss-sdr's classic
CTTC Spain capture
(complex int16, 4 MHz, -t 2xi16 --fs 4000000). A few other online SDR captures
at 1575.42 MHz:
- https://jeremyclark.ca/wp/telecom/rtl-sdr-for-satellite-gps/
- https://s-taka.org/en/gnss-sdr-with-rtl-tcp/
- https://destevez.net/2022/03/timing-sdr-recordings-with-gps/
Details on every recording: resources/README.md.
gps-sdr-sim generates an IQ recording
for a chosen date and location (2× int16 per sample, use -t 2xi16):
$ ./gps-sdr-sim -b 16 -d 45 -t 2026/04/28,17:00:00 -l 46.2075,6.1557,375 \
-e brdc1180.26n -s 2046000 # Geneva, Jet d'Eau./resources/gen_gpssim.sh [date,time] [lat,lon,alt] automates this end-to-end:
it downloads the matching broadcast ephemeris and runs gps-sdr-sim for you.
WIP / caveat: I haven't been able to identify satellites using rtl-sdr directly with my hardware setup — unclear whether it's a bug or my setup. The recording-file path above is the well-tested one.
Install librtlsdr first: sudo apt install librtlsdr-dev (Linux) or
brew install librtlsdr (macOS).
Run directly off a connected dongle (-d)
With an rtl-sdr dongle and a GPS L1 antenna:
$ RUST_LOG=warn cargo run --release -- -dStream from a remote dongle over rtl_tcp (-s)
Run rtl_tcp on the host with the dongle, then connect from gnss-rcv on another
host (it auto-configures sample rate, center frequency, etc.):
$ rtl_tcp -a # on the device host
$ RUST_LOG=warn cargo run --release -- -s <hostname>Record to a file for later replay
$ rtl_biast -d 0 -b 1 # power the GPS/LNA antenna
$ rtl_sdr -f 1575420000 -s 2046000 -n 20460000 output.bin # 10 s of L1- RTL-SDR
- Software Defined GPS
- GPS-SDR-SIM
- Python GPS software: Gypsum
- SWIFT-NAV
- Raw GPS Signal
- PocketSDR
Any code contribution is welcome!
- test + fix rtlsdr support
- support SBAS, Galileo, QZSS, BeiDou
- use the received Almanac to decide which satellites are in view

