I'm just playing around with cheap RTL based SDR dongle and I wanted to implement a simplistic FM radio receiver. I managed to get mono sound with no problem, however I decided to implement automatic scan for radio stations.

My first approach was to detect peaks in power spectrum, however the result did not satisfy me as only a few channels were detected, much less than using a cheap standalone radio receiver. Please note that tuning in specific frequency known to be radio station, but not visible as a clear peak in power spectrum, gave a demodulated sound of reasonable quality.

Then I approached the fact that what I'm looking for is frequency modulated signal and tried to construct some tune-in measure based on a FM constellation (standard deviation of modulus of samples - lower better as it would mean that constellation resemble a circle). However this method also gave poor result.

Next idea was to use the fact that structure of radio channel is known and try to detect stereo pilot (19 kHz tone in demodulated band, tired to do a tone analysis on band-passed portion from 17 to 21 kHz and use THD as a tune-in measure). However, again the method gave many false-positive results and the tune-in measure turned out to be very imprecise.

My last idea, that I have not yet tested, is to measure SINAD of demodulated mono audio but I'm afraid of the results similar to the stereo pilot case.

So my question is: how to do it correctly? I mean - what measure can be adopted to detect presence of FM signal and to estimate what the carrier frequency is?


Of the methods you've already tried, the "looking for peaks in the spectrum" to me sounds like the most promising, but:

My guess is that your "spectrum" is actually a PSD estimate done with the FFT. Which is a fine method of getting an understanding of the spectrum – but as you probably noticed, the "peaks" will not be very clear.

Think about it: The whole principle of FM is to shift a carrier's frequency around. So, looking for energy in a single FFT bin won't work overly well, because these bins will be too narrow to "contain" all the energy of a single station (unless you choose very low FFT length -> wide bins, but then you can't tell stations apart).

A different way of looking at the FFT (which is just an implementation of the DFT) than being a (base) transform between time and frequency domain is understanding it as filter bank of sinc-shaped filters. So, if your radio stations' spectra happened to have sinc shape, and be $\frac{f_\text{sample}}{N_\text{FFT}}$ spaced, this would work indeed very well.

Sadly, the description doesn't fit FM radio stations. But: with a less specific filter bank, we might be able to find channels!

First observation is that if it is somewhat wide in spectrum, and in the UHF band between 87 and 108 MHz, it's an FM station, or illegal. So, let's not waste too much thought on the specific spectral shape of an FM station¹; instead, let's just

  1. use a filter that lets through about 160 kHz of bandwidth
  2. Calculate the power of all that signal that passes the filter (simply by magnitude-squaring the samples and averaging the output)
  3. Take that filter+detector and apply it to all potential channels

Filtering Now, the design of that filter (1.) might be rather simplistic: I'll take an arbitrary real-valued low-pass filter with a cutoff frequency of 80 kHz; that will let through the 160 kHz between -80 kHz and +80 kHz around its center frequency.

Magnitude squaring Remember that digital samples are proportional to the voltage at the ADC, but will be digital here. |·|² is simply a way of calculating a number that is proportional to the power of the signal – and since we're in no way interested in absolute powers, but only in what is "stronger" than some arbitrary threshold, this will do.

Applying the filter all over the spectrum The old-school spectrum analyzer method would be to take a mixer, tune it sequentially to different frequencies, apply the filter to the downmixed signal, and calculate the power. In fact, we can do something like that! But then, we only get info about a single station at a time. That might or might not be good enough for us, and it's certainly as good as the average car radio does (which also scans!).
Now, we're proud applicants of DSP, so we might as well go back to the filter bank idea: Let's just take that filter, replicate it, shifted by 50 kHZ (which I presume is the FM station frequency raster), and apply those 420 filters simultaneously!

Well, that would require that you "see" the full 21 MHz of FM-allocated spectrum at once, and your RTL-dongle won't do that. Instead, let's settle with the 2 MHz that your dongle are able to sample at once. That's 40 filters. I'll come back later and have a signal processing flow graph and code for you.

¹ Bessel functions, by the way. You don't want to analytically deal with those.

² $\frac{(108 - 87)\,\text{MHz}}{50\,\text{kHz}}=\frac{21\cdot10^3}{5\cdot10^1}=420$

  • $\begingroup$ Wow @Marcus! Thank you for so such an elaborate answer! I'll try to implement it asap and I'm looking forward for the processing flow graph to see if I got it right. $\endgroup$ – kaolpr Jan 22 '18 at 14:10
  • $\begingroup$ Note, by the way that the "tune to every potential station and see whether an FM demod outputs a low pass signal or just noise" is an excellent method, too, but probably more intense to implement in parallel. $\endgroup$ – Marcus Müller Jan 22 '18 at 15:18

Looking for peaks or humps in the spectrum is not highly reliable when trying to detect W-FM signals down near the RF noise floor, as the spectrum is wide and varying.

A more reliable method is use an audio noise squelch algorithm on the demodulated FM to compare the demodulated audio spectrum against a noise spectrum. Usually the behavior over time of the portion of the audio spectrum up near the audio cut-off frequency is very different between FM demodulated noise, and a typical voice or music signal.

  • $\begingroup$ I've done exactly this (use squelch algorithms) with my experimental RTL-SDR demodulators (DSP code written in Swift for iOS devices), and it seems to work. $\endgroup$ – hotpaw2 Jan 22 '18 at 15:28

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.