Experiment: Examining Sydney WFM Subcarrier Signals (RDS, ACS)

When I was still in school, I can remember my Dad buying a “special radio” from a broadcaster to listen to some ethnic radio broadcasts. The radio was quite expensive, and it looked and felt just like a regular portable radio – being equally flimsy and tinny. It had provision to switch between three stations, or regular FM operation. It worked – it pulled in three ethnic programs not on the FM dial, and it was intriguing to me.

How was this “normal-looking” radio pulling in stations that other regular radios couldn’t? It wasn’t appreciably more complex or heavy … so I figured it shouldn’t be a major issue to modify some other radios to receive it.

A few years later, I did some searching online about hidden radio stations, which led me to the whole idea of subcarrier services and SCA decoders. While schematics were easily available online, my circuit construction ability at the time wasn’t up to the task of building one to try, and I didn’t have any test equipment to identify the discriminator output of an FM receiver. After consulting with my Dad, he also told me that it wasn’t really worth the hassle of trying, as there’s every chance that it wasn’t being broadcast using broadcast-FM subcarrier.

As it turns out, a few years later, I got into radio scanning as a hobby, and I found that the services were being sent on VHF in narrow-FM as a special license provision for ethnic language services. A subcarrier decoder wouldn’t have helped. My recent exploration in building radio kits reinvigorated my latent desire to explore subcarrier services, to see whether there are any interesting things happening with the broadcast FM services on the air.

Subcarrier Basics

Broadcast FM signals are wide-FM signals with a width of around 180khz nominally. Because of this, the FM carrier itself can carry more than just the “high fedility” audio that we’re used to. The whole idea of the subcarrier system is to use frequencies above the audible to carry signals which are modulated in some way and can be recovered.

The most basic sub-carrier that would be familiar to most is stereo FM. The “baseband” signal up to about 15khz carries the monaural information. A 19khz pilot tone is sent to indicate stereo transmission and the difference signal (L-R) is sent above this (from about 23 to 53khz. This is information “above the audible” encoded for compatible receivers to recover. Notably, older monaural receivers would just receive the baseband signal (filtered) and not be greatly disturbed by the presence of these higher frequency signals.

The same principle can be applied to other services, for example, “hidden radio”, data and paging services known as Subsidiary Communication Authority (SCA) overseas or Auxiliary Communication Services (ACS) in Australia. This can be used to carry one or even two audio programs for radio-reading services, muzak, telemetry, etc. The old fashioned analog ACS used an FM signal at 67khz (more common) or 92khz which carried a secondary audio program. This subcarrier would be modulated with a low power (~5%) to avoid taking too much from the baseband or causing reception problems. You can think of this as a mono NFM signal inside an FM broadcast with limited quality.

Other ACS uses include data carriage, a common one being Radio Data System (RDS) used to convey station ID, radio text (song names, weather information) and other free-form data. This uses a 58khz carrier. A now defunct system is Microsoft Directband which uses a 67.65khz carrier.

Because the wide FM carrier can handle signals outside the audible range, finding such signals requires some detective work. Traditionally, if you had an old fashioned radio with a discriminator output and a spectrum analyzer, you might be able to find it that way. Now, with the advent of SDRs, looking for them is not as hard although decoding can still pose a challenge.

Looking for Subcarriers

Previously, I used SDRConsoleV2 with my rtl-sdr dongle to scan the FM band and receive RDS. This also gives us a graph of the baseband audio right up to 100khz which makes identifying subcarriers possible. This is a low-cost method, but I wanted to try something different.

Instead, I used my Tektronix RSA306 with the Audio Analysis option, with the bandwidth set to 200khz to capture as much as possible. I methodically went through the band and found a few examples of different stations that illustrate what the baseband looks like.


The diagram has been broken up into the mono baseband (orange), the pilot tone (turquoise), the stereo difference (L-R) signal (pink), radio data system/RDS (blue) and two ACS carriers (yellow). As it turns out, across the whole Sydney FM band, only one station has an ACS service on it. That was a little disappointing. The station in question is 102.5Mhz 2MBS Fine Music.

Decoding ACS

To decode ACS is like “unwrapping an onion”. First, you need to decode the full WFM carrier into its baseband, and then decode the ACS carrier from that to recover its baseband.

Success in doing this has been reported by SDRPlay, through a rather convoluted process. The original solution calls for two SDR# instances, one modified so that it can decode NFM wider than 32khz (default limit). The second instance is configured to decode from sound card, and a virtual audio cable is used to link both instances, but must run at 192khz sample rate so as to pass the 92khz ACS carrier completely.

To do this, if you are using Virtual Audio Card, open its Control Panel and check the card is set to do up to 192khz.


Then in Windows mixer, check both under recording and playback for that device that the default format is either 1 channel or 2 channels at 192khz. If this isn’t done, then resampling may occur that results in frequency limitations – you will notice this if the 19khz stereo pilot tone is missing, odd products start showing up, the 58khz RDS is absent, etc.

ensure-192kA modification to SDRSharp.exe.Config is needed, and after a little searching, it turns out that this value has to be changed to 150000 from 32000 (highlighted in yellow):


Unfortunately, the examples of ACS decoding provided were all dealing with 67khz carrier, and thus an NFM bandwidth of 150khz (75khz response) would be enough to pass it. In my case, the 92khz carrier requires a wider bandwidth, but simply modifying this value to anything larger than 150000 results in an error when demodulation is attempted.

bad-subcarrThe second suggestion to get around this is to use a plugin known as MPX Output from rtl-sdr.eu. This plugin is supposed to pull the baseband and send it straight through a virtual audio card at 192khz.

mpx-out-pluginI had great hopes for this, as it might have circumvented the 150khz limitation on NFM bandwidth. To my dismay, it was not successful as it seems there is a nice roll-off prior to the 92khz point. This may be an internal limitation of SDR#, or a choice of the plugin to filter to avoid aliasing. Whatever the cause, this will not let you decode 92khz ACS, although 67khz ACS and below is just fine.


Somewhat annoyed, but not yet giving up, I went an entirely different route and tried to employ HDSDR with Balint Seeber’s ExtIO plugin to do the decoding as I know HDSDR has a 192khz sample rate output option and you could widen the FM filter all the way up.


For whatever reason, while the spectrum appeared to be full of signal, the signal to noise was exceedingly poor and the RDS looked deep within the noise. The ACS signal was nowhere to be seen, and the gain setting on the ExtIO plugin wouldn’t behave either.

I tried pushing harder – SDRConsole v2 and v3 were tried, but nope, they won’t decode the full bandwidth audio and they don’t have any facilities for decoding the ACS even though v2 is happy to show its existence. I went back to my Tektronix RSA306 with the audio demodulation capability, but it only saves 32khz sample rate WAV files, which isn’t helpful.

The only option which seemed to have any success was to use a Linux machine with the rtl-sdr libraries installed. Then, by using

rtl_fm -f <frequency> -s 192000 -g <gain> -p <ppm> output.raw

I was able to capture a raw file with baseband samples. This had mangled headers when I tried it, so I couldn’t just use it as a .wav file, but importing it into GoldWave as a .raw file and selecting PCM signed 16-bit little-endian mono at 192khz did the trick.


The SCA was decoded with a lot of noise, as the signal recorded by rtl_fm was not very good. Subsequent attempts were only worse, but I suspect the gain/offset and signal strength may have been part of the problem. Regardless, the little snippet I heard sounded like some Indian music at the time. But lets just say, it was very noisy especially as it’s the extreme edge of the passband of 192khz sampling and due to the reception equipment involved. I didn’t expect it to be this difficult to determine what the ACS was.

RDS Exploration

To quell my disappointment at the lack of interesting ACS services on the air at the present time, I decided to look at a common ACS system, namely Radio Data System (RDS). Previously, I relied on the scant data shown on SDR# and SDRConsole, but now that I have my 192khz loopback and MPX Output plugins set-up, I was now ready for the full details.

To get all the details, a freeware program called RDS Spy was used. I had been meaning to explore this for a long while, but never had the push until now.


RDS Spy basically tells you everything within the RDS data stream in different ways, including the transmission groups in use, decoded station data, group proportions, open data applications (raw data), etc.

As a result, I decided to catalogue all of the stations in the Sydney area that I could hear for all of their RDS characteristics. Groups that weren’t used were omitted from the table to make it as compact as possible.


The most common group sent, is of course 0A or 0B as this is the basic station information. In fact, only 102.5Mhz FineMus sends 0B, with every other station using version A groups instead. Other groups seen on the air include 1A (2 stations) for program item number and slow labelling codes, 2A for radio text (most stations), 3A for application identification (4 stations), 4A for time and date (most stations).


The emission percentage rate of the packets is normally fairly consistent (+/- 0.3%) as the packets are emitted at fixed intervals (e.g. graph above for KIIS1065).


Of all the RDS stations, most stations spend the lions share of the bitrate on 0A/0B group, although Nova969 and smoothfm seem to spend a large portion on 2A (radio text) instead. Of all stations, only a few have any data-groups – 8A, 11A, 12A. These belong to Hope1032, 2DAY FM, TRIPLE M and KIIS1065.


On all the stations where application ID 4BD7 was observed, it mostly carried null data. The example above is from Hope1032. From the ODA list, 4BD7 is used for Radio Text Plus functionality – hence three three stations with 4BD7 all have Radio Text Plus.


On the stations where application ID CD46 was observed, it seemed to carry alternating binary patterns. The example above is from KIIS1065. The patterns were different for each station. From the ODA list, this application is used to do TMC alert – so it looks like this is used to carry traffic information of some sort.


Of course, on some stations, you have a mix of data. In this case, I was observing 2DAY FM, and it seems that application C3B0 was carrying all nulls at the time, although very infrequently emitted. According to the ODA listing, this specific application is used for iTunes tagging – supported by 2DAY FM and TRIPLE M only. Whether it actually sees any real use is not certain.


FBi Radio 94.5Mhz has an oddly misconfigured RDS encoder that sends just the time and 5A (transparent data) packets. No basic station information is sent at all. The 5A group frames are all identical.


I always had a desire to try and uncover more about the hidden radio services carried on broadcast FM signals. At this time, it seems only one station in the Sydney area carries an ACS program on a 92khz subcarrier, and decoding it was quite a difficulty, but a small sample was heard (even if very noisy). Instead, I turned my eyes to RDS, which is much more common, but through using RDS Spy, a lot more information could be gathered. Only a few stations carry data services on their RDS, including TMC channel, Radio Text Plus and iTunes tagging.

About lui_gough

I'm a bit of a nut for electronics, computing, photography, radio, satellite and other technical hobbies. Click for more about me!
This entry was posted in Computing, Radio and tagged , , , , , . Bookmark the permalink.

2 Responses to Experiment: Examining Sydney WFM Subcarrier Signals (RDS, ACS)

  1. Anthony Eden says:

    Hey there,
    Good to see someone else playing with RDS and subcarriers. RDS Spy is great software, eh? πŸ™‚

    I’m a tech at Hope 103.2, and I setup the RT+ tagging just recently. When I set it up, I don’t think any other Sydney stations were successfully transmitting RT+ data. I’ll have to check again to see what the 2x SCA (Southern Cross Austereo) stations are doing. Our RT+ defiantly works, so you must’ve been monitoring it at a time when we weren’t playing music (hence the null data).

    I looked into the iTunes Tagging ODA, but couldn’t find a protocol spec online. I think it was mainly used by iPod Nano’s back in the day, so it’s probably not very popular now.

    CD46 is used for Traffic data (often received by in-car GPS receivers). In Australia, the data service is managed by Intelematics (SUNA). Unfortunately it’s ‘encrypted’, so we can’t just decode that and use it ourselves.

    I’d be interested if you have any luck decoding that ACS channel. It’s interesting how cheap analog circuitry has been doing this for years (admittedly as a niche product), but it’s fairly difficult to get the SDR setups to do the same.

  2. David Wilson says:

    Try this (for the 92 kHz using HDSDR feeding VB cable feeding RDSSpy, set the filter in HDSDR in the MPX plot in the lower right…drag the left end of the graph all the way to the right (96kHz, not real good but the best you can do. Drag the left of the filter graph (if you do not see it, carefully hover over the y-axis of the plot and click and drag to the right) to about 87 kHz. Doing this makes all the difference in the world for me and I do it also for RDSSpy ( using about 53 to 61 kHz)

Error: Comment is Missing!