Decoding and Listening to HD Radio (NRSC-5) with an RTL-SDR

HD Radio is a high definition terrestrial digital broadcast signal that is only used in North America. It is easily recognized by the two rectangular blocks on either side of a broadcast FM station signal on a spectrum analyzer/waterfall display. Since HD Radio uses a proprietary protocol, finding a way to decode it has been difficult and so this signal has been inaccessible to SDR users for a long time. Back in February of this year we posted about Phil Burrs attempt, where he was able to create a partial implementation (up to layer 2) of the HD Radio standard, but didn’t get far enough to decode any audio in layer 3.

However, now cyber security researcher ‘Theori’ has created a full RTL-SDR based decoder for the HD Radio protocol. In his post Theori explains that the HD Radio system is split into three layers. Layer 1 finds the signals and does decoding and error correction. Layer 2 is a multiplexing layer, which allows various layer 3 applications to share the bandwidth. Layer 3 is the audio data layer. In his post he explains how these layers work in detail. 

One of the main findings was the discovery of the audio compression codec. Theori found that the codec was essentially HE-AAC with some minor modifications. The modifications were minor enough that he was able to adapt the open source FAAD2 library for HD Radio audio decoding.

Theori’s code is open source and available on GitHub. The code includes the patch to modify FAAD2 for HD Radio and it is automatically applied during the build. A sample file for testing the decoder is also provided and we tested the decoder with the sample and it worked well. The decoding can also be performed in real time and examples of that are also on the git readme.

HD Radio Spectrum
HD Radio Spectrum


  1. BrodieBruce901

    I’ve created a Facebook group for those with accounts
    I figured it would be an easy way to share tips, scripts, reception reports, and provide or get help. If you friended me on Facebook for the purpose of any of those things, that is the place to go.

  2. BrodieBruce901

    Has anyone else tried no decode DX’ing just attempting long distance block detection? I let my sdr run the night before and was able to detect a few blocks with an indoor telescopic dipole of a station at 60 miles away as the crow flies. I can copy the station fine in analog with some static and a custom filter. I would love to play with it during a tropospheric ducting event. I was just curious if anyone else has tried this.

    • jcoles

      Can you explain how this “no decode DX’ing” is done? As far as I can tell, nrsc5 provides only audio decode, not data (RDS?) recovery.

      • BrodieBruce901

        So I tuned to a station that I can barely get in analog. I parked nrsc5 on that station over night and was able to get a couple of these: First block @ 6 It’s a similar concept to trying to DX with HD lock lights on radios. If you get a block, a sync, or better yet a demodulation of the audio even for a second of a distant station you’re doing it.

        • BrodieBruce901

          I was able to lock this morning at 60+ miles with an indoor antenna with no amplifier.

          Timing offset: 1085.875000, slope: 19.156250 (adjust)
          MER: -6.983389 dB (lower), -7.291071 dB (upper)
          BER: 0.161592, avg: 0.161592, min: 0.161592, max: 0.161592
          failed to fix header
          unable to find program, or corrupted.
          lost sync (-1, -1)!
          Timing offset: 994.687500, slope: -15.250000 (adjust)

  3. cr08

    Maybe someone who is more aware of the fine details can answer this curiosity for me: Given the modification to the AAC audio stream, how much effort would be needed to have it modified on the fly back to a standard bit stream that is compatible with regular AAC/HE-AAC decoders on the market?

    My thought, purely for research or personal purposes, is eventually being able to bit stream the audio to an Icecast server, no transcoding, and have it behave with standard AAC decoders.

    • Andrew

      I had considered doing this, but it seemed like too much work. After your comment, I couldn’t resist though. The complexity comes from the fact that parsing an AAC packet is non-trivial, e.g. huffman tables, etc. I borrowed some code from FAAD2 to ease the pain.

      The code is in github ( I confirmed that I was able to stream it to a local icecast server and play it with VLC.

      • cr08

        Awesome. Glad to hear that made it in. Still need to play around with it some more and tweak my setup. Been trying to run it in a VM but the PPM is fluctuating wildly all over the place and won’t sync up. So going to try and move it over to a Pi 3 and see how that fares.

  4. K2DLS

    Has anyone gotten this to compile under Cygwin x86_64 ? I’m running into many unresolved symbols during the linking phase, such as R_X86_64_PC32. Can provide complete error info if someone would like to take a look.

    • David P

      Okay, that is slick! I’m not fighting with ppms and guessing on gain anymore, and most everything is coming in from my town.

      Thanks! I’ll have to keep an eye on this as things improve. Wish I knew some code so I could help out..

    • BrodieBruce901

      The experimental branch is working great on x86. Lower CPU usage and I’m now able to pull just about all the local stations. I was also able to build the experimental branch on ARM and I’m also seeing substantially low CPU usage there but I am still unable to over the air decode with the exact same sdr setup for some reason. It’s incredible work and the new ARM code paves the way for some exciting projects.

    • William

      Fixed that
      Now my problem is that it says
      CMake Error: The following variables are used in this project, but they are set to NOTFOUND.
      Please set them or make sure they are set and tested correctly in the CMake files:
      linked by target “nrsc5” in directory /home/william/build2/src

      — Configuring incomplete, errors occurred!
      See also “/home/william/build2/CMakeFiles/CMakeOutput.log”.

      I would love the help!

        • David P

          Yes! This was just (presuming you have a Ubuntu/Debian variant) making sure all of the -dev packages were loaded, like librtlsdr-dev. I had the same thing happen, but I had to install autoconf as well.

          • William

            Thanks for the help!
            My problem now is that is says “nrsc5: command not found”.
            Sorry if my comment is a duplicate as my previous comment in response to this has disappeared on my side.
            I am running Ubuntu 16.04.2LTS

            • jcoles

              If your current directory is your build folder, try src/nrsc5, as the README suggests.
              It sounds as if you have not fully completed the installation. After the make step, use the command “sudo make install”. After that, you can invoke nrsc5 no matter what the current directory. The README omits that important step.

        • William

          Thank you soo much! It finished completely!
          It’s just now the next problem is when I run the command “nrsc5” it just says “nrsc5: command not found”
          Could be I’m doing a stupid?

  5. jcoles

    For those having problems, note that nrsc5 is very fussy about ppm adjustment. Experimentation is necessary. For me, only ppm values between 44 and 46 work.
    Thanks Theori for your excellent work.

  6. Mangosman

    Whilst it is a great piece of detective work, it has been known that the compression was a modified version of MP4 which is HE-AAC compression for a very long time. This is version 1. Version 2 used in the DAB+ versions used in Australian and Europe also include Parameter stereo which sends a mono signal along with a PS signal which steers the sound around the sound stage and only 3 kbit/s required. SBR is available in Version 1 and 2. The latest compression is xHE-AAC which has improved compression of speech signals where 12 kbit/s produces good quality speech which is used in Digital Radio Mondiale.
    HD radio with FM simulcast options for channel data rates are;
    P1 service mode 25 kbit/s, P2 74 kbit/s, P3 0 kbit/s in the standard hybrid mode
    P2 service mode 25, 74, 12 kbit;s Extended hybrid mode which has digital sidebands closer to the stations FM signal Extended hybrid mode
    P3 service mode 25, 74, 25 kbit/s Extended hybrid mode
    P4 service mode 25, 74, 50 kbit/s Extended hybrid mode
    Greater data rates are available if the FM signal is switched off to give a pure digital signal.
    In DAB+ countries the lowest data rate used for a single program is 32 kbit/s using SBR and PS.
    The digital signal is 1/25th of the FM power. This is the reason why receivers will mix the digital and analog signal on the main program as the digital error rate rises. When the digital falls over the cliff, you are listening to FM which, when the noise level is high will fade the left-right signal until it becomes mono the last event is that the FM mute operates to stop the annoying hiss heard when there is no FM signal.
    In Australia the main DAB+ transmitters which are a pure COFDM signal are 50 kW effective radiated power vertically polarised to match car antennas and telescopic antenna in portable radios. They typically carry 20 – 24 programs on a channel bandwidth of 1.5 MHz with the channel frequency between 174 – 230 MHz as compared to 88 – 108 MHz for your HD radio. From the same towers FM transmitters are 150 kW with horizontal and vertical polarisation.

  7. J Hill

    Good work guys , you only need one of the sideband/ jamming signals to get a decode , most of the stations runnign the dual sideband are only doing it to jam the smaller stations , most broadcasters and sirius/XM sounds like trash due to codec stacking .
    Next i would like to see a pirate HD transmitter come out so we can reclaim the airwaves before corporate stations change the laws and shut down all the small independent analog stations we love and make everyone buy new HD radio’s as they did with HD TV…
    fight the power !

  8. Jeff

    I cant get anything to tune in rtlfm works fine with
    rtl_fm -f 94500000 -M wbfm -s 200000 -r 48000 – | aplay -r 48k -f S16_LE
    but doing the same with this I cant get it to sync it has a clear view in gqrx of the sidebands
    src/nrsc5 -g 490 94500000 0
    I do see a potential problem [R82XX] PLL not locked! it doesnt say that with rtl_fm unless I change the frequeency to something crazy
    I have tried a whole range of gain to see if that made a difference but nothing changes
    Anything I can look into? Thanks

    • jcoles

      I have the same problem, but dropping the gain parameter doesn’t help. What is your successful command line for nrsc5? I might just be missing something that the developer thought was obvious.
      Does the program keep spitting out lines like
      avg: 1200.250000, slope: -34.500000, freqerr: 50.750717
      when it is working properly? If so, what sort of values do you get. I have no idea what are good or bad numbers.

      • Jeff

        My successful command was src/nrsc5 -p 32 94500000 0
        It doesnt work on lower signal channels I had to find ones with the highest sidebands I could in gqrx
        yea it outputs that if its working properly or not
        The first thing to look for if its working is first block @ 18 it takes about 5 seconds
        on lower signal it may find a block synchronize then become unsynchronized on lower signal you wont see anything just the avg: stuff I havent worked out the gain to get the lower signals working very well yet
        Heres a output on a high signal stations

        [0] Generic RTL2832U OEM
        Found Rafael Micro R820T tuner
        Exact sample rate is: 1488375.071248 Hz
        [R82XX] PLL not locked!
        avg: 1152.312500, slope: 0.000000, freqerr: -20.000446
        avg: 1152.375000, slope: 0.031250, freqerr: -17.717134
        first block @ 30
        BER: 0.026043, avg: 0.026043, min: 0.026043, max: 0.026043
        pdu_seq: 0, seq: 0, nop: 33
        ignoring partial pdu
        avg: 1154.000000, slope: -0.093750, freqerr: -20.059990
        BER: 0.034008, avg: 0.030025, min: 0.026043, max: 0.034008
        pdu_seq: 1, seq: 32, nop: 32

        • jcoles

          Thanks, Jeff.
          My problem was with the -p parameter. That value is critical. I found the right value for my RTL-SDR device through trial and error.
          In my area there is only one FM station that has HD sidebands.

          • David P

            I went into gqrx and tried as hard as I could to get to the center frequency by fixing the ppm. This went a long way to getting to where I needed to be. One trick was to adjust to according to my local NOAA station by ear first, then by matching up the lines.

            • Jeff

              What I did was run rtl_test -p for ten minutes or so it gives you the ppm
              real sample rate: 2048067 current PPM: 33 cumulative PPM: 32
              just use that ppm!

    • Robin Cross

      As a retired Chief Engineer of several NPR Stations, this is not true. HD stands for nothing. Frequency response is 20 Hz to 20K Hz. HD does eliminate multipath due to redundant sidebands. I am stating facts. Subjective quality assessments are for you to decide.

      • BrodieBruce901

        I thought it was marketing term standing for Hybrid Digital. Full digital (which I know is not legal) is said to be able to carry a 5.1 surround signal. That’s what iBiquity claimed anyway.

      • Eric

        It’s the redundant sidebands that eliminate multipath. Both of them form a channel.

        FWIW the mulitpath is not eliminated but DSP allows it to demod and recover the channel during multipath conditions. Channel estimation, channel prediction, add a cyclic prefix are all things that help.

      • BrodieBruce901

        Someone else has asked for it as well. I don’t know that I will make a github repo for scripts so simple but I told them I would clean it up and host it somewhere when I have time in the next few days. Just send me a friend request and I will let you know when it’s up. I plan on recompiling from the new branch so I don’t have to mess with gain or ppm adjustments but I will host the old scripts and add a ppm adjust for those that can’t upgrade to the new code for some reason.

  9. Burban

    No, the sidebands are redundant (or so iBuiquity told us in the beginning) That’s why you can run different power levels–e.g. -14 dBc on one side, -20 dBc on the other to avoid interfering with a station on the -20 side.

    • Andrew

      Both sidebands carry information and are both required to successfully decode a digital FM signal. You can view it as half of the information is in the lower sideband and the other half in the upper sideband. Because of the interleaving and convolutional coding, you would quite likely fail to decode without both sidebands.

        • Philipp

          The information is redundant and yes “some” receivers can happily decode HD on a single sideband; I have demonstrated that before on a Sony receiver; it can be done. I have publically shown this receiver operating on a single all-digital MP5 sideband within an HD Multiplex signal and produce audio. However, my experimentation has shown that some receivers loose HD lock quite easily on a single sideband and some won’t initially lock in even with clean signals. I assumed, but I have not proven, that these receivers look at both sidebands for symbol tracking and can get thrown off with random noise on one side; the difference may be between a receiver decoding the entire signal in an FFT or independently on a per sideband basis. I am convinced a robust receiver could be built for single sideband operation … this code base could prove that. Great job …

          • Rob

            Good to see you here, Philipp! I’ve been following your progress for the past several years, and I’m pleased to know that a mind such as yours finds value here. HD Radio can be more than what it is today, and your work is helping to prove that. Cheers!

  10. Kyle

    I’m hoping someone takes this and makes a gui version of this, or at least some more complaining from the software when things aren’t working. I was able to play the samples, but I’m unable to make it go live.

    avg: 617.062500, slope: 1.593750, freqerr: 146.766266

    do I need to change things so that these numbers are different?

    • Andrew

      Those messages are informational and describe the sampling offset error and frequency error. They do not directly indicate an error or not.

      If the signal is not syncing, then you should make sure you have a fairly clean signal and the gain/ppm options are correct.

      • Kyle

        Thanks, I’ve reran rtl_test -p and let it run for awhile, I initially ran it briefly, and it corrected the tuning enough for NFM. It looks like I do get output on the command line if it does tune properly. Now I just have a bunch of bad headers and CRCs but maybe I can raise the gain to help that out.

        crc mismatch!
        BER: 0.105525, avg: 0.078304, min: 0.042724, max: 0.110771
        failed to fix header
        unable to find program, or corrupted.
        avg: 1442.812500, slope: -0.125000, freqerr: 85.688263
        BER: 0.078568, avg: 0.078307, min: 0.042724, max: 0.110771
        failed to fix header
        unable to find program, or corrupted.
        BER: 0.085330, avg: 0.078370, min: 0.042724, max: 0.110771
        pdu_seq: 0, seq: 60, nop: 33

        The gain is requiring a bit of trial and error, because I can receive the analog FM with a gain of zero :-/

        • BrodieBruce901

          It could be lack of processing resources. My 4 year old core i5 can do it just fine but it runs a core at 90%. My chromebook running Ubuntu can’t manage it. It pegs a core out at 100%. It seems nrsc5 is not multi threaded so even though I have the cores to spare, it just can’t do it. It tries though. It finds the blocks and syncs, but immediately looses sync. I’ve also noticed the signal has to be pretty strong and clean.

  11. Mocha

    Wow! Well done! Easy to use and works really well. Only problem is the content. There’s nothing out there worth listening to and it’s not surprising this didn’t take off commercially. It was like when we got an SCA decoder, also cool but nothing worth listening to.

    • Christopher Gioconda

      On the east coast of the US, especially between NYC and DC, plenty of popular stations have fantastic secondary channels, and the primary stations sound way better than SiriusXM.

Post a comment

You may use the following HTML:
<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>