I've written about redsea, my RDS decoder project, many times before. It has changed a lot lately; it even has a version number, 0.7.6 as of this writing. What follows is a summary of its current state and possible future developments.
Input formats
Redsea can decode several types of data streams. The command-line switches to activate these can be found in the readme.
Its main use, perhaps, is to demodulate an FM multiplex carrier, as received using a cheap rtl-sdr radio dongle and demodulated using rtl_fm. The multiplex is an FM demodulated signal sampled at 171 kHz, a convenient multiple of the RDS data rate (1187.5 bps) and the subcarrier frequency (57 kHz). There's a convenience shell script that starts both redsea and the rtl_fm receiver. For example, ./rtl-rx.sh -f 88.0M would start reception on 88.0 MHz.
It can also decode an "ASCII binary" stream (--input-ascii):
0001100100111001000101110000101110011000010010110010011001000000100001 1010010000011010110100010000000100000001101110000100010111000010111001 1001000010110000111111011101101011001010101110100011111101000011100010 100000011010010001011100001
Or hex-encoded RDS groups one per line (--input-hex), which is the format used by RDS Spy:
6201 01D8 E704 594C 6201 01D9 2217 4520 6201 E1C1 594C 6202 6201 01DA 1139 594B 6201 21DC 2020 2020
Output formats
The default output has changed drastically. There used to be no strict format to it, rather it was just a human-readable terminal display. This sort of output format will probably return at some point, as an option. But currently redsea outputs line-delimited JSON, where every group is a JSON object on a separate line. It is quite verbose but machine readable and well-suited for post-processing:
{"pi":"0x6201","group":"0A","tp":false,"prog_type":"Serious classical","ta":tru
e,"is_music":true,"alt_freqs":[87.9,88.5,89.2,89.5,89.8,90.9,93.2],"ps":"YLE YK
SI"}
{"pi":"0x6201","group":"14A","tp":false,"prog_type":"Serious classical","other_
network":{"pi":"0x6205","tp":false,"has_linkage":false}}
{"pi":"0x6201","group":"0A","tp":false,"prog_type":"Serious classical","ta":tru
e,"is_music":true,"partial_ps":"YL "}
{"pi":"0x6201","group":"2A","tp":false,"prog_type":"Serious classical","partial
_radiotext":"Yöklassinen."}
{"pi":"0x6201","group":"0A","tp":false,"prog_type":"Serious classical","ta":tru
e,"is_music":true,"partial_ps":"YLE "}
{"pi":"0x6201","group":"0A","tp":false,"prog_type":"Serious classical","ta":tru
e,"is_music":true,"partial_ps":"YLE YK "}
{"pi":"0x6201","group":"2A","tp":false,"prog_type":"Serious classical","partial
_radiotext":"Yöklassinen."}
{"pi":"0x6201","group":"0A","tp":false,"prog_type":"Serious classical","ta":tru
e,"is_music":true,"alt_freqs":[87.9,88.5,89.2,89.5,89.8,90.9,93.2],"ps":"YLE YK
SI"}
Someone on GitHub hinted about jq, a command-line tool that can color and filter JSON, among other things:
> ./rtl-rx.sh -f 87.9M | jq -c
{"pi":"0x6201","group":"0A","tp":false,"prog_type":"Serious classical","ta":tru
e,"is_music":true,"partial_ps":"YL "}
{"pi":"0x6201","group":"14A","tp":false,"prog_type":"Serious classical","other_
network":{"pi":"0x6202","tp":false}}
{"pi":"0x6201","group":"0A","tp":false,"prog_type":"Serious classical","ta":tru
e,"is_music":true,"partial_ps":"YLE "}
{"pi":"0x6201","group":"0A","tp":false,"prog_type":"Serious classical","ta":tru
e,"is_music":true,"partial_ps":"YLE YK "}
{"pi":"0x6201","group":"1A","tp":false,"prog_type":"Serious classical","prog_it
em_started":{"day":9,"time":"23:10"},"has_linkage":false}
^C
> ./rtl-rx.sh -f 87.9M | grep "\"radiotext\"" | jq ".radiotext"
"Yöklassinen."
"Yöklassinen."
"Yöklassinen."
"Yöklassinen."
"Yöklassinen."
"Yöklassinen."
"Yöklassinen."
The output can be timestamped using the ts utility from moreutils.
Additionally, redsea can output hex-endoded groups, the same format mentioned above.
Fast and lightweight
I've made an effort to make redsea fast and lightweight, so that it could be run real-time on cheap single-board computers like the Raspberry Pi 1. I rewrote it in C++ and chose liquid-dsp as the DSP library, which seems to work very well for the purpose.
Redsea now uses around 40% CPU on the Pi 1. Enough cycles will be left for the FM receiver, rtl_fm, which has a similar CPU demand. On my laptop, redsea has negligible CPU usage (0.9% of a single core). Redsea only runs a single thread and takes up 1500 kilobytes of memory.
Sensitivity
I've gotten several reports that redsea requires a stronger signal than other RDS decoders. This has been improved in recent versions, but I think it still has problems with even many local stations.
Let's examine how a couple of test signals go through the demodulator in Subcarrier::demodulateMoreBits() and list possible problems. The test signals shall be called the good one (blue) and the noisy one (magenta). They were recorded on different channels using different antenna setups. Here are their average demodulated power spectra:
The noise floor around the RDS subcarrier is roughly 23 dB higher in the noisy signal. Redsea recovers 99.9 % of transmitted blocks from the good signal and 60.1 % from the noisy one.
Below, redsea locks onto our good-quality signal. Time is in seconds.
Out of the noisy signal, redsea could recover a majority of blocks as well, even though the PLL and constellations are all over the place:
1) PLL
There's some jitter in the 57 kHz PLL, especially pronounced when the signal is noisy. One would expect a PLL to slowly converge on a frequency, but instead it just fluctuates around it. The PLL is from the liquid-dsp library (internal PLL of the NCO object).
- Is this an issue?
- What could affect this? Loop filter bandwidth?
- What about the gain, i.e. the multiplier applied to the phase error?
2) Symbol synchronizer
- Is liquid's symbol synchronizer being used correctly?
- What should be the correct values for bandwidth, delay, excess bandwidth factor?
- Do we really need a separate PLL and symbol synchronizer? Couldn't they be combined somehow? Afterall, the PLL already gives us a multiple of the symbol speed (57,000 / 48 = 1187.5).
3) Pilot tone
The PLL could potentially be made to lock onto the pilot tone instead. It would yield a much higher SNR.
- According to the specs, the RDS subcarrier is phase-locked to the pilot, but can we trust this? Also, the phase difference is not defined in the standard.
- What about mono stations with no pilot tone?
- Perhaps a command-line option?
4) rtl_fm
- Are the parameters for rtl_fm (gain, filter) optimal?
- Is there a poor-quality resampling phase somewhere, such as the one mentioned in the rtl_fm guide? Probably not, since we don't specify -r
- Is the bandwidth (171 kHz) right?
Other features (perhaps you can help!)
Besides the basic RDS features (program service name, radiotext, etc.) redsea can decode some Open Data applications as well. It receives traffic messages from the TMC service and prints them in English. These are partially encrypted in some areas. It can also decode RadioText+, a service used in some parts of Germany to transmit such information as artist/title tags, studio hotline numbers and web links.
If there's an interesting service in your area you'd like redsea to support, please tell me! I've heard eRT (Enhanced RadioText) being in use somewhere in the world, and RASANT is used to send DGPS corrections in Germany, but I haven't seen any good data on those.
A minute or two of example data would be helpful; you can get hex output by adding the -x switch to the redsea command in rtl-rx.sh.
![[Image: Spectrum plots of the two signals superimposed.]](https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjxJb8tfNcyfwlqRoibN9ffpl4QTwWtHbxI_XwUQURfVL9Sabkfj7MVufWlpiHT5q1bSgi13xowZ_YVcFHu82JkG3JcnUALlyf0qe_tMvqG7rPaAfOAp8kp8haCQfhysja4ITKwXE6j_bQn/s520/viker-puhe.png)
![[Image: A graph of several signal properties against time.]](https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiWhUWCDPhsTT7vtVZJibWzn4K_2SoG9EOY75kRFeLXZgK7PN8ML5O33DeD43SGMKDcErB_dvzwmC7DhzV18pHLlrrEcLsRdjfQw8o964RBjAUVHDCLIiHhExYFocZDKAgMbMXfDPUWaa1f/s520/NZjst7F.png)
![[Image: A graph of several signal properties against time.]](https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEibHzljSbMrQw5VMB4ErTyv3bxmE5tFl4MKbu6Y7njkCOsF8cnY07FpLUynNBXQ5owOVwwvxaGEXI1xvcp7kciC5T9DXDfJUri1rGDixvq8FcnKYBgleIstRJ4ooUSkTywe8pXk_H9oQdkp/s520/wXmOjiN.png)
![[Image: Screenshot of textual output from redsea, with some parts explained.]](https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEghDSuLUBhcKfHe0dEKhePRSEWFhrKu3tNkSLDL7rHZQUfMFDTcbS2AhNSlM2HRToqNGoq6YSdk3fIxa3fcF2cgGkfCmpmNA_QVIfaYgLN21JJ-LwF-Avf__oVdquGFti67-DKlqYrqZj6T/s520/rds-groups.png)
![[Image: Oscillograms illustrating how the RDS subcarrier is gradually processed in redsea and finally reduced to a series of 1's and 0's.]](https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi0-32woZW6BB5JDgpyzAf3g6a9QG30gFenMBF9dyxaWlwVDJs3tcnuvOY8W47uLsHahxks2255_giLHj6AP7uDGm_o8CWR0JKg0EniI5Zq50bzKexdQ5PD1zwSSEasGilj7iEj0x8zg7a2/s500/redsea-waves-all.png)
![[Image: Photo of a liquid crystal display showing the text: 8589 IBUS 6.5.70d]](https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjIxM06NvCn-6lRmadmoMFcAWaqAavrEczgpsNUyaTOdyggRTKBZ02VA3WlsjrbEZCpeF0mv41i8SWRCeDbVTrmtpXB_bkhKf-u6MVRgvm2YlN0m2Q1gOGzvMpiNDLFEypPVXpops-wZwdd/s400/IMG_0541a.jpg)
![[Image: An infographic titled 'Bus & Destination Packet', showing the hex bytes of a 64-byte packet, divided into fields that are labeled according to their apparent purpose. There are counters, size fields, the bus stop identifier, bus line identifiers, and apparent references to other types of packets. Several fields containing Latin-1 text are also transcribed. The text in them reads '23N RUSKEASUO BRUNAKÄRR'.]](https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjCvBNi49Nq5EFwmA6gAkOYbH07bMg5E6msCtUvwxmilZ-tj-sSZS6n8Wu8x2QUO0Iy2JY4JxUJaNuKHUuJGAMilZjjKaTxVdUbLiSzwMpYLDwx4rgbUIG6V-IsSZlGXEMmZeaTmLMBlLfp/s540/bus_dest_packet.png)
![[Image: An infographic titled 'Minutes Packet', showing another type of labeled packet. Most fields are one byte long and they contain the number of minutes until the next bus arrives, in 7 bits, and one bit telling whether this estimate is based on positioning or time tables. Information about which number belongs to which bus is contained in the references from other types of packets.]](https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjBvGn07zM_WjPnJ3xMxBC5nqWxFzzazr7EZ_4WJUtUyEUTBAej0QaWDrZBTnav9z_IEfim9Agvfqu2C9TJGztJogAVRYDJBEJRpU22Fb-1ILjJTIaRzzFlpNQ8j9jnwRaxiKttXRljdm0L/s540/bus_min_packet.png)
![[Image: Photo of a small liquid crystal display kit with its PCB showing, obviously home-soldered to a bunch of wires, and displaying the text: '72 TAPANILA ~12'.]](https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg3pZBIJ8IT_tPSlezjPW8g1z8DDJGNqexgODiPD4ZOeXUNMQ1AhRFyPuXFTzAOj8rFdq98Ks3MyOZ6gwrx8MdfDnXTJy8iokN2csJIJmCcOJ0I0kqX1Yw7yRyIMd1XreSoVAgwxEIS6jpW/s400/FScOI7G.jpg)
![[Image: A spectrogram showing a signal at the audible frequency range, labeled 'mono', and four carriers centered at 19, 38, 57, and 76 kHz, labeled pilot, 2f, 3f, and 4f, respectively. Pilot is a pure sinusoid; 2f and 3f are several kHz wide signals with mirrored sidebands; and 4f is 20 kHz wide and resembles wideband noise.]](https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi59H46xOh66oRU-x29wkkCwEQ-6if2ELMxo-a2ofHBE6Z1_kZRU4iYGDUD1j9435-Y00GZKsFZI6XhgBl8DG4i_sSjLVXh0s5FCfi8Ob2cM6y8AiajWeSI7tPbfm7Nqm_ltro3nZliKm7E/s540/darc.png)
![[Image: Three oscillograms followed by a stream of 1s and 0s. The first oscillogram is quite nondescript; the second one actually shows two waveforms, red and blue, in the same graph, with the red dominating in envelope power where blue is suppressed and vice versa. The third oscillograms shows a graph apparently following their envelope power difference, with sample points at regular intervals. The sign of this plot at sample points dictates whether a 0 or 1 is shown below that sample.]](https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi57Su6_LISIEe_8jUatXSGTZvtYa_9cb7yKvTzGwso8ymHmzwhgIPD4wkf8cwfFeP14mnS3H7ExkwoWf-0wUsMqxgtJAqGxbuhUUYgiUvL3k992I_JdW63tM31ONXk0kUlva3_kC_58xlc/s540/DARCb.png)
![[Image: Photo of a rugged LCD display mounted on a metal pole, displaying the text '55K FORSBY ~6' and stamped with the logo 'HSL HRT'.]](https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhPqX6IrlRSG8OliEhfo6AW4ma7KL3jNu6GKt5JbDbHTGWhw6v9taZwWj4-HWXwdW69y3aUXnDArh668ChdtU52o4mSEjEUPzwVoVYJonPyIb-nGahkE9plqtnvA45Aa3PF3Cm6y-y5XJCx/s400/IMG_6102.jpg)
![[Image: A cipher diagram beginning with the 16-bit hex words C1A0 and F3D5, labeled 'location' and 'key', respectively. The 'location' word goes into a bitwise right rotation block, controlled by the first nybble of 'key'. The third and fourth nybble of 'key', taken as a single byte, go to a bitwise left shift block controlled by the second nybble of 'key'. Outputs from these two bitwise blocks are XORed. The result is the hex word 85E9, labeled 'encrypted location'.]](https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg526X5zUY0KG9EDFojAVok_8ZTGPZy6OVhhaAMGnp1DVoI6BW3te9m3Wx3YznCEvlj7I7WWOntLAEXw2hfObtK0jqV__avyFcy648_MBlfmK68VCVQBEGvJ33tvKuf76sTIStAGDm0gE8f/s240/rdstmc.png)
![[Image: A pink, heart-shaped Post-It note with a hand-written columnar XOR calculation in binary. One of the operands is shifted left from the alignment. The result is 85E9.]](https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhJGrUAjCtScScKP1PvQTSIdaHAgqOLXkOLgCwaCLV2OADGoQD2HUdWd8At0Uzf56ox4jJ0YscKaiVGPbW_DeEdM7lbYxH43prWMIa-AQ-T45YtdWHCWsDf_Y1CVCb4gix8YEQGnshaW1LO/s300/IMG_3512p.jpg)
![[Image: Screenshot of a GUI with the title 'RDS-TMC'. It's divided into three sections. The first one, labeled 'TMC Service', tells that the service provider is 'MMN TMC+', we're using location table 6/17, and the data is encrypted with key number 5. The second section, labeled 'Received messages', shows a scrollable columnar list of traffic messages received, with a one-word description, an icon and rough location. The third section, 'Message', shows the selected message in detail. A map displays the affected road segment. A long event description is printed in Finnish, along with the road name, exact coordinates, speed limit, time of expiration, and the last time the message was received.]](https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi2IJZkvEH0BkYoI5GfOyyyy18gzVLb4tJXVh7n-zryhHn9xRzo02fE1wXfTTDM5RdGcO-WBBY2aeadLdf9r8ey12NXiSt8vpbWMVC2WmqQr9PxGV7EE7XheZ5HQCcfarqumWtMNAqe3uuU/s480/Screenshot-1.png)
![[Image: Spectrogram with a frequency scale from 0 to 22000 Hz. There's a signal that looks like music, from 0 to 17000 Hz. A pure sine tone is centered at 19000 Hz. A low-power, double-sideband signal with a bandwidth of 4000 Hz is also centered at 19000 Hz.]](https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjga6Qz9Gz5GlzAzqRORbdyPSyudYI3oV0gOUycuuVDiIuhysU4PCwAzmROahAFBS1X_eYtni7s7jMsL8hmlueiNBQ-HkOSGjz7xGKmCINN9g0b1KtYDaN7CErEP4O2KohfAIorxxownAE5/s480/Screen+Shot+2014-10-29+at+12.19.54.png)
![[Image: A screenshot resembling an LCD with various text fields. The dominating elements read 'YLESUOMI' and 'Radioterapeutti'. Smaller fields read '6203', '94.0', and 'Varied Speech'. A row of indicators shows positive status for 'RadioText', 'EON', 'TMC', and 'TP', and negative status for 'RT+', 'eRT', and 'TA'. There's also a signal level indicator showing roughly 40%.]](https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi9PAdXpGoXY2VQEKi817ujRGnl_s34MpkbW27By43-dpdrkFwegaoCeJDlde-3kkBccvDMSMC80JnjrEtkWyhELhKz-Y8ajruCCgNP0iAMwaWyYB6wShSJSLvbZRb7ORGVHnuPZBNPAnWw/s320/rds-ylesuomi2.png)
![[Image: The schematic diagram with the part containing the RDS decoder chip magnified. The chip has 16 pins, 4 of which are encircled and labeled Qual, Data, Ground, and Clock.]](https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgUXpgcyZeQN-RXg5qFKVHusRWY8bbOQyo0zcZMSSYKrTq3VuJr_4RmXaVV_43MW8HDE7sNGQyp8sleGDFdY8E5lPBW1W37dxlctSYZLDIaxLvAd6Wrwz0iAWp_YY1IBvOO7zWTb8IdIM3v/s320/path4119.png)
![[Image: Oscillogram showing a signal alternating rougly between two states, with a lot of noise superimposed.]](https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgCwBsBZxbldi2kj0X70n1FPq3CzEXS_fJ79avMB9cdxWh218Z-YyBw5li1QDuNDd-riGFmaufVzyYEX5Qa197F0jZVlHuG5x1Fv0_6M5vKqrNmXobhrTRT36U1kPkuc0gE7mACr0ariUqj/s450/dataa.png)
![[Image: Schematic diagram of the cable and connector setup with a voltage divider. Pins 10 and 12 of a DB-25 are connected to arbitraty points A and B, respectively, via 1 k-ohm resistors. Point A and B are both connected to point C via 200 ohm resistors each. Point C is connected to pin 23 of the DB-25. Points A and B are connected to terminals 1 and 2 of a 3.5mm socket, respectively.]](https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjvLkvon4u5aRTFIEGkjJYcxHsA_pG-FQ_7moiZ4rXlz3nKMaiBeYK96rGUPS-Rj7jiimDfHPrNYwE_tNsk-ZKxvL0Xmn-8shzj-EYldCLzhnW6aV-1jvQ9kOfbhIGF8PaUFF3KnnqWg23B/s320/voltdiv-schematic.png)
![[Image: The above voltage divider in a series of photos, showing how it fits inside the casing of a parallel port printer adapter.]](https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiRRfSRyYjO-ssZx3cCf4oOND9q-jvrJk0XMkYIP7e2FPJO4Gms4AxTS332dJmbkUdaPLodZ-9bowBDy4BIag9qB0nSu3A1UUGZlsFEmUFXJL8_YnlhIPjovex1YQgKB-x_xdOMTKsmx4gD/s320/voltdiv.jpg)
![[Image: A two-channel oscillogram. One channel shows a square wave with regular transition intervals, the other one transitions only at some transition points of the first one.]](https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiA8HzuyjF1tectW2iwsz_VM9JCXZimkpLiM3oyvAZmYc8rRS6yOJVD3UBBUXrtCLHdocW4AD9zsWiOvICUAXA2ubU-pGREm3eZmfjYzhUQn_UED54pK2vgG4d6qkKhG9Ammb_UUdtdXH8D/s450/signals.png)
![[Image: Screenshot of a terminal and a small GUI window. The terminal is showing a stream of text describing RDS information. The little window is showing the text YLEYKSI and Muistojen bulevardi.]](https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgeX4MZ3sKUwXPwWmDBNfjnekHdmS81c-_MCfgxOllMpNl5WS0xomTuqB7R9Pe0wjEs_tv9ZnlRvA-8G1IgtHk0yt5vBQE5PKR5izGnChCpCrr_8N4X9jCSvBYf7khyrmD-BjPxPLzBmIqN/s320/Screenshot+-+11082012+-+01%253A07%253A19+PM.png)
