rx_tools: command-line SDR tools for RTL-SDR, bladeRF, HackRF, and more (rx_fm, rx_sdr, rx_power) — ported from librtlsdr

rx_power showing ~5 GHz captured on a HackRF over 9 hours

librtlsdr provides quite a few useful command-line tools for RTL-SDR dongles (including rtl_fm, rtl_sdr, and rtl_power). rx_tools is derived from these popular tools, but adds support for other more powerful SDR hardware, including the bladeRF and HackRF, thanks to the SoapySDR abstraction layer.

As they are intended to be a mostly drop-in replacement for librtlsdr’s tools:

  • rtl_fm → rx_fm
  • rtl_sdr → rx_sdr
  • rtl_power → rx_power

…existing documentation and guides on using rtl_* should be still useful when using rx_*, but with added hardware compatibility and other enhancements. In this article I’ll provide some background and motivation for these tools, and at the end demonstrate a couple interesting use cases.

If you just want to try out the new tools now, check out or skip to the end for examples, otherwise if you’re interested in more context, read on.


The Pioneers: GNU Radio, USRP

was founded in 2001, and one of it first developers created and . Featured in Wired in 2006, , although has arguably existed in some form since the 1970s, GNU Radio and the USRP were clearly breaking new ground in building the foundation of modern software-defined radio:

An early Universal Software Radio Peripheral (photo from )

Their original goal was to receive digital television (something I also experimented with, with some success: ), they succeeded and then some. However, the USRP is quite an expensive device, prices ranging in the $1000's of dollars, out of the range of casual hobbyists.

The Affordable: RTL-SDR, librtlsdr

Then came an unexpected development: the . The designers of the Realtek , primarily used as an inexpensive ($5–20) television tuner, built-in a software-defined radio mode originally for receiving and radio broadcasts. When developing a Linux driver for this chip, discovered the SDR mode and the rest is history.

was born, unlocking the true potential of the device, and bringing SDR into the hands of even the most casual hobbyist. Although more limited than say the USRP in many ways, the RTL-SDR is quite a powerful peripheral, a good start for those new to SDR. For more background and a more comprehensive introduction, see Luaradio’s .

Intel Atom N270, 1.60 GHz (from ), a low-power CPU

As a low-cost device, the RTL-SDR is commonly used on low-cost single-board computers especially the , also priced in the tens of dollars, or personal computers built on the . With not much computing power to spare, command-line tools are preferred, hence librtlsdr ships with several, most usefully: rtl_fm, rtl_sdr, and rtl_power.

The Mid-Range

You may have noticed a glaring gap in cost, orders of magnitude, of the these two SDRs discussed above:

  • $1000’s: USRP
  • $100’s: ???
  • $10's: RTL-SDR

What lies in between? There are several products, ranging from the low to high hundreds of dollars. The two I am familiar with and will be using in this article are the Great Scott Gadget’s and Nuand’s (brief comparison of the two here: ), but this is an interesting emerging area, expect more developments as the high-end and low-end SDRs converge.

Yet there is one problem: software.

A Survey of SDR Interfacing Libraries

The great promise of SDR is “it’s just software” — with sufficiently capable hardware, you can write radio software to do practically anything. However, not all software is made equal.

SDR applications must interface with the hardware by some mechanism. Software built on GNU Radio, such as , commonly uses the osmocom abstraction layer, supporting a multitude of devices. RTL-SDR, HackRF, bladeRF, you name it.

Not all software is as compatible, some is written hardcoded to use libraries from specific SDR vendors. If you’re lucky there might be a fork for your SDR. As I covered in , the “kalibrate” GSM frequency calibration tool has at least four separate forks: , , , and — one for each SDR.

has support for all of RTL-SDR, HackRF, and bladeRF, but it accomplished this feat by linking against all three libraries: , , . The developer had to specifically code for each device individually, a lot of work and not scalable to new devices.

The excellent ADS-B receiver uses librtlsdr directly (just like rtl_fm/rtl_sdr/rtl_power), so it only supports RTL-SDR. More details in , but there’s also for the SDRplay RSP, and for the RTL-SDR, HackRF, , and (but not the bladeRF).

Not compatible with your SDR

Messy, isn’t it?

There have been efforts to improve this situation. was announced March 2014 in , but it has been inactive for two years. is a pretty good abstraction layer, but focused on GNU Radio.

Even within the librtlsdr library there is fragmentation: ostensibly git://git.osmocom.org/rtl-sdr.git is the official version, but I also found and to (note master vs development branch), not to mention , which has many tool enhancements from the original author of rtl_fm and rtl_power, .

Aside: Better Compatibility through Unix Pipes

The power of Unix pipes can help improve the SDR interfacing compatibility situation (indeed, is solely focused on piping). dump1090 can read from stdin using “--ifile -” arguments, from any source. It can be piped from HackRF ‘-’ stdin/stdout using hackrf_transfer, with PR-261 .

rtl_tcp can listen on a TCP port, gr-osmosdr device flags rtl_tcp= can connect to to it; rtl_sdr ‘-’ pipes to stdout, rtl_fm (see ) can pipe sox for playing the demodulated audio, or for digital signal decoding.

Pipes are a powerful abstraction, but a full compatibility API offers more flexibility. For example, you may want a scanning tool to tune to various frequencies, which wouldn’t be possible over a Unix pipe dumping raw I/Q samples. A proper clean abstracted API is desired, fortunately there is one:

SoapySDR: a clean interface

Announced in November 2014 (), pothosware is under active development and I believe to be currently the best SDR abstraction layer.

It provides a vendor-neutral interface, including to librtlsdr, libhackrf, libbladerf, uhd, and even gr-osmosdr. Whatever SDR you have, it is most likely supported, or if not it support could be added by a plugin. SDRplay, Red Pitaya, S9C-ExtIO, EVB7, NovenaRF, Lime Suite LimeSDR, …

Existing usage

I first discovered SoapySDR through the great , the Cross-Platform Software-Defined Radio Application, which in 2015.

It turns out I was already using SoapySDR via gqrx, presumably through (the stack trace for HackRF showed ).

The up-and-coming embeddable flow graph signal processing framework is on .

Porting notes: rtl_fm, rtl_sdr, rtl_power

All said and done, I set out to port a subset of librtlsdr tools to SoapySDR in order to add support for additional devices, expanding compatibility beyond the fabled RTL-SDR to at least the bladeRF and HackRF.

My inital to SoapySDR was relatively straightforward, making the appropriate API changes, such as:

  • rtlsdr_dev_t → SoapySDRDevice
  • rtlsdr_open(&dev, index) → dev=SoapySDRDevice_make(&args)
  • rtlsdr_close → SoapySDRDevice_unmake
  • rtlsdr_set_center_freq → SoapySDRDevice_setFrequency
  • rtlsdr_set_agc_mode → SoapySDRDevice_setGainMode
  • rtlsdr_set_and_get_tuner_bandwidth → SoapySDRDevice_setBandwidth

rtlsdr_read_async() on a different thread was replaced with:

  • SoapySDRDevice_setupStream
  • SoapySDRDevice_activateStream
  • SoapySDRDevice_readStream
An RX port, on the bladeRF

Then I , since the tools are no longer tied to RTL-SDRs.

Supporting the HackRF out-of-the-box required tweaking the default settings. HackRF has three gain components, which can be set individually, or with setGain() distributed amongst all of them. TODO: , rtl

Supporting the bladeRF required switching to the format, from CU8/CS8, to support the 12-bit ADC. This is a good idea regardless, because both rtl_fm and rtl_power internally converted from 8-bit unsigned to 16-bit signed integers. , each 16-bit or 8-bit (or …):

  • CS16 = complex signed 16-bit
  • CS8 = complex signed 8-bit (+127 to convert from CU8; HackRF native)
  • CU8 = complex unsigned 8-bit (native RTL-SDR)

To allow the tools full use of stdout — for rx_fm, the demodulated FM audio, for rx_sdr, the raw I/Q samples, for rx_power, the CSV logging — I stdout to stderr, so any from SoapySDR or drivers will not interfere with the output.

Another tweak for bladeRF: continue on , seen at startup with bladeRF but quickly recovers.

frequencies (from Wikipedia)

For rx_power supporting HackRF, a more drastic change was needed: frequencies. rtl_power used 32-bit signed integers (uint32_t, or int on my system) for frequency calculations, which were limited to 2³¹ GHz (~2.1 GHz). Expanding to 64-bit signed allows 2⁶⁴ = ~9 exahertz, should be enough for anyone unless you are using software-defined or .

Not ported, other rtl_* tools & alternatives

librtlsdr includes a handful of other tools, which I did not port:

rtl_tcp, — allow using a remote RTL-SDR. This functionality is provided by , for all SDRs.

rtl_adsb — haven’t tested, seems that is more popular (although it also uses librtlsdr, could be worth porting as well).

, rtl_eeprom, rtl_test —are RTL-SDR specific.

Now that the porting is out of the way, and we have these shiny new rx_fm/rx_sdr/rx_power tools, what can we do with them?

Example Usage

rx_fm + bladeRF: police scanner for UHF

kmkeen’s describes a number of use cases for the tool, also applicable to rx_fm. As a simple test, I used rx_fm with a bladeRF as a police scanner for the UHF band.

This use case admittedly does not showcase the advantages of bladeRF very well (12-bit ADC, full duplex TX/RX, FPGA, etc.) but it’s a decent example of the device-independent compatibilities of rx_fm.

Based on the rtl_fm example, I also merged the ‘-E wav’ support from . This adds a .wav (RIFF) file header, obviating the need to pass the audio format details to sox. uhf_scan_b.sh is simply:

./rx_fm -E wav -M fm -s 12k -l 25 -f … | play -t wav -

Frequencies omitted here, but they are passed in as multiple -f arguments. Use a site such as to find the FM public safety frequencies available in your area. Scan results after a period of multiple hours:

Public safety (UHF) audio captured using rx_fm on a bladeRF

rx_sdr: raw I/Q data for processing in GNU Radio or other tools

I haven’t seen many use cases for rtl_sdr, but it can be useful to capture raw samples for later analysis; rx_sdr is now the device-independent analogue. Instead of using e.g. hackrf_transfer, you can use rx_sdr, for any SDR.

The new ‘-F’ option specifies the output format, currently: CU8, CS8, CS16 (described above), or CF32. rx_sdr defaults to CU8 for drop-in compatibility with rtl_sdr. GNU Radio’s native format is CF32, complex float 32-bit. So to capture an FM broadcast station, you could run:

./rx_sdr -f 97.7M -F CF32 /tmp/fm

then use the “File Source” in GNU Radio (type “Complex”):

GNU Radio demodulating FM captured with rx_fm, flowgraph from

and the samples captured through rx_sdr are processed by GNU Radio.

rx_power + HackRF: scanning the spectrum up to 6 GHz

At last, we arrive at what I wanted to do all along, which initated this porting project: scan the full 6 GHz spectrum with a HackRF using rtl_power. With rx_power, this is finally possible.

To scan a large range of spectrum, generating CSV output:

./rx_power -f 0.1M:6G:1M -e 24h hackrf-power-1m.csv

Then convert to a .png using :

python ~/rf/rtl-sdr-misc/heatmap hackrf-power-1m.csv hackrf-1m.png

Here’s what we see:

rx_power from 0.1 MHz — 6 GHz, bin size 1 MHz, over 9 hour period with HackRF

The image is wide, but zooming in some interesting emissions can be found. 6 GHz is a significant expansion of the range observed with an RTL-SDR dongle up to about 1.7 GHz in . Some highlights:

The 2.4 GHz , including 802.11b/g/n :

2.4–2.5 GHz , captured with rx_power on HackRF

The (noise?) on the right extends to about 2.75 GHz. There is a regular repeating pattern 3.5–3.6 GHz, possibly interference?

, but the regular pattern may suggest something else, unclear. If anyone has any ideas let me know. Then there’s this above 4 GHz, in the (4–8 GHz), possibly satellites?

The shifting frequency suggests (but at a 1 MHz scale? maybe something else). There are various bands with weaker power at 4.72 GHz, 4.9 GHz, 5.2 GHz, 5.1 GHz (two strong bands), 5.16 GHz, 5.23 GHz:

What are these? Harmonics, artifacts, nonsense? Or something real?

It takes until 5.660 GHz to reach a known signal, faint but noticeable:

This is , operating on channel 132, in the .

If anyone wants to run rx_power with their SDR and post their resulting heatmap, feel free to, I’d be interested particularly in the GHz range.

Future Directions

building on SoapySDR for bladeRF and HackRF and RTL-SDR support is only the beginning, other SDRs ought to be supported, possibly requiring small tweaks, or they may already work — I’ve only tested with these three devices, but welcome bug reports from users using others.

Another benefit of rx_tools is that it is separated from the librtlsdr library, allowing it evolve independently. I have merged several patches from various librtlsdr forks, including my own small fixes/enhancements, and welcome any pull requests for anyone who wants to contribute further, making the command-line tool suite even better.

Even if you only have an RTL-SDR, you can still use rx_tools and benefit from the continued development, and continue using them once/if you decide to upgrade to a more powerful and sophisticated SDR device.