RTL-SDR, Police Scanners, and You


I’ve had a passing interest in radio for years and upon discovering software defined radios (SDR) a few years ago, I was incredibly excited at the potential of these devices. For $30, give or take, you could have a radio dongle that can scan a huge amount of radio bandwidth and do all sorts of neat stuff with that. I set up an ADS-B receiver and recorded plane tracks locally (as well as shared to adsbexchange) to track police helicopters and other protest air traffic, but then I threw it in a closet (still running) and forgot about it. Now we’re in a particularly interesting moment in time so it’s the perfect opportunity to re-purpose these ADS-B devices for tracking police scanners. Here’s how you can do the same.

Getting Started

For the purposes of this guide, I’m going to make a lot of assumptions. I’m assuming you’re starting with no equipment, that you know how to use a command line and ssh but maybe don’t have everything memorized, that you can troubleshoot on at least a basic level, and that you can look up ancillary information to adjust this tutorial to your own needs. There are a number of different ways of achieving the end result (cubicsdr for example is very easy and straightforward), but this guide assumes you want to leave things running 24/7 without tying up your laptop/desktop and wasting all that power.


You’ll need:

Technically you could buy any R820T2 SDR and some are very cheap, but the RTL SDR is the cheapest one I would recommend to get consistent quality (my worst SDR has tuning off my 30MHz for example).

First Setup

Okay, you’ve got your pi so now we’re going to set this up as a headless install because this is going to live in a closet anyway. Don’t be scared, I’ve got everything mapped out for you. If you’re on Windows you’ll need to download Putty or similar. If you’re not familiar with SSH, here is a tutorial I hope is decent.

To begin, we need to download the Pi operating system and create a disk image. The Raspberry Pi foundation has a program for doing this called Raspberry Pi Imager. Download it and create a disk image of raspian on your microSD card. Once the program is done, add a file called ssh (no extension) to the root of the microSD. Insert the card, plug in your raspberry pi to the wall and to your router/switch/whatever and wait for it to boot. After a few minutes, log into your router and check DHCP leases for a device called something like “raspberry pi”. Note this IP and then connect to it via ssh, the default log in is pi with password raspberry.

At first setup, run sudo raspi-config and go through the prompts. Change the main user information especially. Check for updates sudo apt update and install them sudo apt full-upgrade.

Install RTLSDR-Airband

There are a number of excellent software packages for SDR, but we’re going to use RTLSDR-Airband because it’s fairly lightweight, optimized for the raspberry pi, can listen to as many channels as you have processing power for within the bandwidth of the device (~2.54 MHz or so for our RTLSDR sticks), and can easily output icecast and other streams of the resulting audio. Unfortunately, RTLSDR-Airband is not available in any packages, but fortunately it has excellent docs to help us install.


First, we need to install the necessary packages and dependencies to build RTLSDR-Airband.

sudo apt-get install build-essential libmp3lame-dev libshout3-dev libconfig++-dev

If you’re using Raspberry Pi v1, v2 or v3, additionally install Broadcom VideoCore GPU development headers and libraries:

sudo apt-get install libraspberrypi-dev

On all platforms other than RPi v1, v2 and v3, FFTW library is required:

sudo apt-get install libfftw3-dev

Since we’re using RTL-SDR dongles, we need to install the drivers for them. These are handled by rtl-sdr library, which is packaged in most Linux distributions. On a Raspbian/Debian/Ubuntu install it with:

apt-get install librtlsdr-dev


create a directory for this, I made mkdir RTLSDR-Airband and then cd into it. Time to get all the files and unzip them (check to see the latest release here and swap as necessary):

wget -O v3.1.0.tar.gz https://github.com/szpajder/RTLSDR-Airband/archive/v3.1.0.tar.gz
tar xvfz v3.1.0.tar.gz
cd RTLSDR-Airband-3.1.0

It’s time to compile. We’re going to use the make command, but there are a few flags we need to set. We can check these flags with make help or with the docs. In our case, we need to set a PLATFORM= argument. We’re assuming you’re using a rasp pi so:

So, for my raspberry pi 4 I would need PLATFORM=armv8-generic

But there’s still an additional flag we need to set. Because we’re monitoring narrow band FM channels, we need to enable NFM support (which unfortunately comes with a performance hit). The flag for this is NFM=1. There are other flags that can be set like PULSE=1 if we want PulseAudio output, but we’re reading headless so there’s no need.

The final make command for my raspberry pi is:

make PLATFORM=armv8-generic NFM=1

Let it run until it finishes (might take a moment), then run sudo make install. The program should now be installed to /usr/local/bin/rtl_airband. The default configuration file will be installed to /usr/local/etc/rtl_airband.conf which we need to configure in a moment. First, we need to calibrate our dongle.

RTL-SDR Dongle Offset Tuning

RTL-SDR’s are great and cheap, but they also vary quite a bit in quality because of that low cost.

UPDATE: There’s an easier, more accurate tool we can use called rtl_test. I’ll explain below and leave the old method up as well for reference.

Newer, Better Method

The RTL suite of tools has a test functionality built in to find tuner offset, so let’s use that. It should already be installed, but to double check run

sudo apt install rtl-sdr

and install the suite of tools. Now to find the ppm offset of your device, run

rtl_test -p

and let it start calculating. As your dongle heats up, the tuner will drift until it’s settles in about 30 minutes, so you can let this run for a while if you like. Every 10 seconds it updates the ppm calculation. Check back in a while or when it seems to not be moving and you have your offset! It may be negative or positive, but it should be a whole number ~ -5 to 5 (any more or less and your tuner may be wonky.

Mine are mostly around -3.

Older, Worse Method

There’s a tool called Kalibrate-RTL to help us find our tuner offset.


I made a new folder called kal in the RTLSDR-Airband folder we created earlier. It’s time to build kalibrate-rtl. The commands below install dependencies, download the kalibrate files, and navigate into the proper folder respectively:

sudo apt install build-essential libtool automake autoconf librtlsdr-dev libfftw3-dev
git clone https://github.com/steve-m/kalibrate-rtl
cd kalibrate-rtl

Before we go any further there’s a bug we need to fix in Raspberry Pi installs. We need to edit the librtlsdr.pc at /usr/lib/arm-linux-gnueabihf/pkgconfig/ using your editor of choice. I’ll do this with nano: sudo nano /usr/lib/arm-linux-gnueabihf/pkgconfig/librtlsdr.pc and then edit the file to look like the following:


If there’s anything extra, leave it. Save, write, and close (ctrl+x in nano). If you moved directories, return to kalibrate-rtl git folder. Time to build and install:

./bootstrap && CXXFLAGS='-W -Wall -O3'
sudo make install


I had a ton of trouble finding a band using the default commands everyone shared online but finally got it working with the following:

./src/kal -s EGSM -e 32 -g 49

The reason this one works (probably) is that it’s set to search EGSM, guesses the error amount -e 32 (if you don’t guess it fails for unknown reasons), and sets the gain -g 49,6 (it apparently doesn’t set your gain to anything reasonable if you don’t force it). This will run (slowly) and eventually find some channels. Once you find a strong channel, write it down and run this (replacing with the channel, for example 2 with no <>):

./src/kal -e 32 -c <YOUR CHANNEL HERE> -g 49

In a moment you should get a readout of your average absolute error. I ran mine a few times and ended up around 3.9ppm. If you want to check to see how many Hz this is, use this calculator and enter the frequency you searched (GSM-900 channel 2 for me at 935.4Mhz). I was off by 3.64806Hz give or take which isn’t bad.

UPDATE: If you’re having trouble with this tool or getting huge ppm’s, it may be working incorrectly. Do a sanity check with a GUI radio program. We’ve found that most of the RTL-SDR V3’s only have a 2-4ppm offset (positive or negative).

RTLSDR-Airband Config

See example configs I’ve written in RTLSDR-Airband Example Configs.

First, we need to set our device config(s). By default, as you plug in dongles it will number up 0, then 1, then 2, etc (these are defined as index in the config. However if we have multiple dongles that we want to stay consistent (say they have different antennas), it’s important to give them serials which will be consistent.

If you haven’t already, install rtl-sdr package with the command sudo apt install rtl-sdr. Plug in your first dongle and then run rtl_eeprom -s 00000101 or whatever you want the serial to be, just make sure it isn’t 00000001 as that will conflict with device index numbers. Once set, unplug and then plug in the next dongle to set that serial (I did 00000201) and so on. You must unplug after each serial is entered and upon plugging back in, it should be set. Run rtl_test to confirm.

After all that, we can start working on the config, here’s what mine looks like:

  type = "rtlsdr";
  serial = "0000101";
  gain = 35;
  centerfreq = 476.725;
  correction = -3;
  sample_rate = 1.0;
  mode = "multichannel";

To quickly walk through this, type sets the device type, serial sets the device (you must use "XXXXXXXX" or alternatively you can put index = 0 if you don’t want to set serial numbers),gain sets power (20-40 should work well, higher isn’t always better), centerfreq sets the center frequency we tune to (we can listen within 0.5MHz above and below this, RTL-SDR’s will do at most 2.56MHz), correction is that ppm you measured above rounded to the narrowest whole number (no decimals), sample_rate is the bandwidth we’ll be looking at, and mode when set to multichannel tells the program we’ll be listening to several different frequencies at once.

Next, we need to add channels and outputs. This looks like this:

      freq = 476.7625;
	  modulation = "nfm";
	  highpass = 320;
	  type = "icecast";
	  server = "icecast.example.com";
          port = 8000;
          mountpoint = "brooklyn84-88";
          name = "Brooklyn Precincts 84,88";
          genre = "Brooklyn";
		  description = "NYPD Brooklyn Precincts 84,88 476.7625";
          username = "source";
          password = "password";

For this block, freq defines the frequency we’re looking at (make sure it’s within range of the center frequency and bandwidth set earlier), modulation specifies that this is a narrowband fm station, highpass = 320; gets rid of annoying hum on the frequencies I listen to with a simple high pass filter that attenuates everything below 320Hz, outputs defines our audio streams (in this case I’m using icecast and the settings should be relatively self-explanatory. For more details on config settings, check RTLSDR-Airband’s Wiki.

The above sections are just partial configs and missing closing tags and other little things. I’ve posted full example configs in RTLSDR-Airband Example Configs for you to look at and modify for your own needs.

This is a good moment to take a break for a second and figure out what frequencies you might want to be listening for. RadioReference.com is your friend here as it has a database of the frequencies of almost every city, police precinct, etc in the United States. Further, you can see if those channels are encrypted (we can’t listen to those), using a P25 system (this tutorial won’t work for that but I’ll create a new one soon), or general analog broadcast (like NYPD uses and what this tutorial is set up for). I find it handy to record all these frequencies in an excel or google sheets doc where I can organize them and see what makes the most sense to listen to.

Well now that you’ve got your frequencies all picked out, it’s time to edit the config on your raspberry pi. Use your favorite text editor to edit /usr/local/etc/rtl_airband.conf. For this guide, we’ll use nano because it’s easy sudo nano /usr/local/etc/rtl_airband.conf. Since we’ve already got the config made, we can just delete everything and paste in our new one (ALT+A and arrow down to select all and CTRL+K to cut, then copy and paste in your new config).


It’s time to test! On a raspberry pi, RTLSDR-Airband needs to run as root to access GPU hardware so sudo /usr/local/bin/rtl_airband -f will start the program (provided we didn’t break the config). In a moment you should get a waterfall readout of the channels you’re monitoring. If the device is working properly, the small numbers should get bigger when a signal is detected - Success! If you’re streaming to icecast, check the server to see if the feeds are coming in (and note that the default icecast server config is limited to 2 streams and needs to be edited). Once you confirmed everything is working, kill the program with CTRL+C. Now you can edit the config if needed or switch over to running without any interface using sudo /usr/local/bin/rtl_airband. Check to see you’re not overloading your processing power with top and after you confirm and close (CTRL+C), you can disconnect and let your pi feed data 24/7.

If you need to kill the program to say edit and reload a config the command is sudo killall rtl_airband.

I went ahead and created a service for the program after finding it crashed occasionally after 2 or so days of uptime. There are docs for this, but the short version is navigate to your rtl_airband folder (/home/pi/RTLSDR-Airband/RTLSDR-Airband-3.1.0 for me if we’re using the standard pi user) where there is a service config already for us to copy. I made a quick change to force the service to restart on failure, you can do this by sudo nano /init.d/rtl_airband.service and change Restart=No to:


Save (ctrl+x and y) and then run these commands to copy the config over and create the service:

sudo cp init.d/rtl_airband.service /etc/systemd/system
sudo chown root.root /etc/systemd/system/rtl_airband.service
sudo systemctl daemon-reload
sudo systemctl enable rtl_airband

Now to run it, you simply need to type sudo systemctl start rtl_airband and you’re set!

Potential Troubleshooting

If you’re running three or four RTL-SDR’s (the pi can’t really handle more than that), you probably will run into a USB buffer issue that silently fails and causes the third dongle to do nothing. Here’s the fix, simply run

sudo sh -c 'echo 1024 > /sys/module/usbcore/parameters/usbfs_memory_mb'

and everything should start normally. However, this won’t stay around after boot, to do that you need to edit local.rc by doing the following:

sudo nano /etc/rc.local to open the config file. Above the # line about IP’s, add sudo sh -c 'echo 1024 > /sys/module/usbcore/parameters/usbfs_memory_mb' & and make sure you include the &. CTRL+X and Y to save and then sudo reboot to test. Once you log back in, confirm this worked by typing cat /sys/module/usbcore/parameters/usbfs_memory_mb which should print 1024. If it does, congrats! You’re all set to run airband without any issues.

On additional thing to look out for if you’re running tons of channels (my pi has more than 40 being recorded) is that you may have to reduce some of the computational load. Adding fft_size = 256; to the very top of your config will help if the speech sounds garbled.

Wrap Up

Hopefully this information empowers you to monitor your local police (find their radio frequencies here to ensure better accountability and protect the vulnerable on the ground. Icecast is a great tool for building a single place to monitor feeds for the technically less inclined who will be able to simply click your feed and listen in (maybe a future tutorial). These tools are powerful and we can use them for a lot of good with the right organization and drive.

If you’d like to see this tutorial in action, check out nypd.radio12.org.