Live streaming of DVB radio

DVB radio live streaming with EPG and RDS information

If you have Squeezebox media players and a logitech media server in the house you probably like to be able to receive radio with it. without problems you can listen to different webstreams, but actually you can receive a lot of radio programms via satellite or cable televion. these DVB data streams have (technical) a high quality and it scales very well. this means that the reception is guaranteed, since it is actually radio reception. however, the radio programmes on DVB-S and DVB-C are mpeg transport streams.

[view of the player] [view of the player]
DVB stream received as (shoutcast compatible) stream by a squeezebox replica. currently played station SWR1 BW "Der Nachmittag". On the other player the RDS option is active and therefore the current title is displayed.

MPEG transport streams are not played by the squeezebox and most internet and web radios. Therefore a conversion is necessary. With this conversion you can convert DVB -C/-S radio programs into something that most webradios and the squeezeboxes will understand.

Since you need a specialized application to use a TV card (or sat>ip) I use tvheadend for the actual reception. VDR works in principle as well, but I get along better with the concept of tvheadend.

To listen to the streams with a webradio or the Squeezebox you have to convert the MPEG transport stream into something that the webradio or the Squeezebox can understand. For this I use the software ts2shout which I wrote. This software converts an MPEG transport stream into a shoutcast or, alternatively, a native MPEG audio stream.

For streaming the radio programmes from cable or satellite I use the "stream profile" "pass" which is already prepared as standard in tvheadend.

TV card / SAT>IP

For the integration of the satellite antenna i write more on an extra page. otherwise you don't need any special drivers for this.


The conversion is done by apache2 together with a cgi- and filter program, which is available here:

ts2shout is intentionally minimalistic, but autoconfigures. A suitable audio MPEG payload is searched in the MPEG transport stream and it will also extract the EPG information about the current broadcast (new also including RDS support - radio data system). This will be converted to shoutcast format (started without parameters) or into a naked mpeg audio data stream (called with parameter noshout). The crc checks over the mpeg control and the RDS frames are made. A "naked" mpeg audio stream can also be generated directly with tvheadend, by using the corresponding transcoding module. ts2shout is only needed if you want a "shoutcast"-compatible audio stream, that contains broadcast information about the current radio broadcast from the EPG or RDS. ts2shout can also provide both, either mpeg EIT or RDS data (radio data system) in the stream. This means that you can directly get information about the current radio transmission on the display.

ts2shout ... as filter

ts2shout will be installed in /usr/local/bin as default. It's a "dual-use" application. On the one hand you can use it regularly at the command line as "classic" unix filter. You can supply a MPEG transport stream on stdin (standard input) and you'll get the naked mpeg audio stream (or shoutcast media stream) on stdout (standard output). Typically one uses this mode for testing.

ts2shout ... as CGI application

This is the main application. when used as CGI application the relevant stream parameters are taken directly from the mpeg frames and sent as "shoutcast-compatible" http headers as soon as they were collected. After that the regular streaming of mpeg data starts. In cgi mode, the input data is streamed with the help of libcurl. As it is usual for cgi programs the result is written to stdout. A parameter cache is used to reduce stream startup time.

There are some hacks in the code so that the EPG displayed on a squeezebox also makes sense, but nevertheless many information about the current program is displayed. Sometimes the EPG information about the current broadcast is over 600 characters long (mainly in the "cultural programs" e.g. SWR2 or Bayern 4 Klassik). The squeezeboxes scroll through the text and pause at the beginning of the text a short time

[lms ansicht eines langen titels] [lms ansicht eines langen titels]
reception of dvb stream. display in lms webinterface, currently running are "swr2 feature" or "bayern 4 jazztime".

Installing of apache2 for radio streaming

I use apache2 in combination with "mod_action" and "mod_cgi" as webserver for the CGI program. But in principle every webserver should work with the support of CGI programs (so practically all of them). But here I only describe Apache2 because I know it best and use it myself. The following change in /etc/apache2/sites-available/000-default.conf is necessary after installing the module mod_action and mod_cgi (a2enmod action; a2enmod cgi). As you can imagine, this configuration section doesn't have to be at this exact point, of course. The configuration has to be inserted exactly where it suits you for the virtual host and (virtual) subdirectory. In the Debian default installation there is a minimal server configuration in /etc/apache2/sites-available/000-default.conf, there you can insert it normally. In Debian /cgi-bin/ is searched in /usr/lib/cgi-bin/ by default. I have created a symlink ln -s /usr/local/bin/ts2shout /usr/lib/cgi-bin/. Please see my repository lms-dvb-radio-helper for an example.

But now the required configuration section:

    <Location /radio>
        Order Deny,Allow
        Deny from all
        Allow from
        Allow from ::1
        Allow from
        <If "%{HTTP:Icy-MetaData} in {'1'}">
                SetEnv "MetaData" "1"
        # If you prefer RDS over MPEG EPG
        # RDS = Radio Daten System
        # (If there is no RDS, the EPG is the data source)
        SetEnv RDS 1
        SetEnv TVHEADEND "http://localhost:9981/stream/channelname"
		# The radio stations are called up with the URL e.g. 
		# using "/radio/SWR1 BW" or (URL encoded) /radio/SWR1%20BW if you want
		# to listen to the radio programme "SWR1 BW" from the tvheadend channel list.
        # Only A-Z/a-z/0-9, spaces and "-" are possible because 
		# of the regular expression. You may have to change other characters
        # in the channel names in the tvheadend frontend.
        SetEnvIf REQUEST_URI "([-A-Za-z0-9_ ]*)$" PROGRAMMNO=$1
        Options +ExecCGI
        Action ts2shout /cgi-bin/ts2shout virtual
        SetHandler ts2shout

This configuration block provides the following: apache is instructed to use the cgi application ts2shout for all accesses to URLs below /radio. When called, the environment variables tvheadend and MetaData and PROGRAMMNO are set. These are evaluated and used by ts2shout! the URL $TVHEADEND/$PROGRAMMNO is used by ts2shout using libcurl to access tvheadend. The received mpeg transport stream is searched for streaming information (stream rate, bitrate) and the station name. If this has been received, then the http headers on stdout are sent to apache and then the mpeg data stream in a continuous data stream (In shoutcast mode or not, depending on the possibly received Icy-MetaData http header). A new stream takes about half a second until the stream starts. It starts when the cgi program has collected all the information. I implemented a streaming parameter cache to reduce the stream startup time to an absolute minimum.

By parsing Icy-MetaData in the request and setting the correct stream- and bitrate the streams should be compatible to many webradios (not only to Squeezebox).

Event errors are logged to stderr and thus to the apache error log - usually found in /var/log/apache2/error.log. If everything is set up and configured correctly, the following is written from ts2shout to the apache error log.

[Thu Apr 01 22:42:10.794748 2021] [ts2shout:info] [pid 25450] init_structures(): Subscribing to MPEG-TS PID 0, 17, 18 (CHANNEL_TYPE_PAT, CHANNEL_TYPE_SDT, CHANNEL_TYPE_EIT)
[Thu Apr 01 22:42:10.794797 2021] [ts2shout:info] [pid 25450] ts2shout version v0.93 compiled 01.04.2021 22:41 started
[Thu Apr 01 22:42:10.794803 2021] [ts2shout:info] [pid 25450] MPEG streaming with shoutcast StreamTitles in CGI mode with RDS support.
[Thu Apr 01 22:42:10.796282 2021] [ts2shout:info] [pid 25450] fetch_cached_parameters(): found parameters for programme SWR1 BW
[Thu Apr 01 22:42:10.863627 2021] [ts2shout:info] [pid 25450] add_channel(): Subscribing to MPEG-TS PID 1000 (Type CHANNEL_TYPE_PMT)
[Thu Apr 01 22:42:10.863650 2021] [ts2shout:info] [pid 25450] extract_pat_payload(): Added 1 possible PMT id(s) with transport_stream_id: 1093.
[Thu Apr 01 22:42:10.925733 2021] [ts2shout:info] [pid 25450] add_payload_from_pmt(): stream language `deu'
[Thu Apr 01 22:42:10.925775 2021] [ts2shout:info] [pid 25450] add_payload_from_pmt(): Found MPEG 1 audio stream in PID 1001 (service_id 28465)
[Thu Apr 01 22:42:10.925780 2021] [ts2shout:info] [pid 25450] add_channel(): Subscribing to MPEG-TS PID 1001 (Type CHANNEL_TYPE_PAYLOAD)
[Thu Apr 01 22:42:11.004767 2021] [ts2shout:info] [pid 25450] Synced to MPEG-1 layer 2, 320 kbps, 48000 Hz, Stereo
[Thu Apr 01 22:42:11.804335 2021] [ts2shout:info] [pid 25450] SDT: Stream is station SWR1 BW from network ARD SWR.
[Thu Apr 01 22:42:11.944531 2021] [ts2shout:info] [pid 25450] EIT: Current transmission `Musik Klub Soul'
[Thu Apr 01 22:42:23.402846 2021] [ts2shout:info] [pid 25450] RDS: RDS data found, using RDS instead of EIT.
[Thu Apr 01 22:42:23.402895 2021] [ts2shout:info] [pid 25450] RDS:  New York state of mind / Billy Joel 
[Thu Apr 01 22:42:43.024693 2021] [ts2shout:info] [pid 25450] RDS: Billy Joel - New York state of mind 
[Thu Apr 01 22:43:02.986612 2021] [ts2shout:info] [pid 25450] RDS: Billy Joel - New York state of mind Musik Klub Soul mit Barbara Scherrer 
[Thu Apr 01 22:43:23.083714 2021] [ts2shout:info] [pid 25450] RDS: Billy Joel - New York state of mind Eins gehört gehört -SWR1 Baden-Württemberg- UKW, DAB+, SWR1 App 
[Thu Apr 01 22:43:39.724112 2021] [ts2shout:info] [pid 25450] curl_download: Terminated after fetching 18.31 MB and writing 3.39 MB. Exiting.

Similarly, radio programs encoded in AAC can also be played (here Radio Iran from Hotbird 13E as an example). For AAC formats on original Squeezeboxes see here.

[Thu Apr 01 22:54:20.444637 2021] [ts2shout:info] [pid 25705] init_structures(): Subscribing to MPEG-TS PID 0, 17, 18 (CHANNEL_TYPE_PAT, CHANNEL_TYPE_SDT, CHANNEL_TYPE_EIT)
[Thu Apr 01 22:54:20.444749 2021] [ts2shout:info] [pid 25705] ts2shout version v0.93 compiled 01.04.2021 22:41 started
[Thu Apr 01 22:54:20.444766 2021] [ts2shout:info] [pid 25705] MPEG streaming with shoutcast StreamTitles in CGI mode with RDS support.
[Thu Apr 01 22:54:20.448883 2021] [ts2shout:info] [pid 25705] fetch_cached_parameters(): found parameters for programme RADIO IRAN
[Thu Apr 01 22:54:20.885770 2021] [ts2shout:info] [pid 25705] add_channel(): Subscribing to MPEG-TS PID 151 (Type CHANNEL_TYPE_PMT)
[Thu Apr 01 22:54:20.885846 2021] [ts2shout:info] [pid 25705] extract_pat_payload(): Added 1 possible PMT id(s) with transport_stream_id: 11300.
[Thu Apr 01 22:54:20.885885 2021] [ts2shout:info] [pid 25705] add_payload_from_pmt(): stream language `per'
[Thu Apr 01 22:54:20.885901 2021] [ts2shout:info] [pid 25705] add_payload_from_pmt(): HE-AAC maximum bitrate 4.8 kByte/s (38.3 kBit/s)
[Thu Apr 01 22:54:20.885923 2021] [ts2shout:info] [pid 25705] add_payload_from_pmt(): Found HE-AAC audio stream in PID 1511 (service_id 1551)
[Thu Apr 01 22:54:20.885937 2021] [ts2shout:info] [pid 25705] add_channel(): Subscribing to MPEG-TS PID 1511 (Type CHANNEL_TYPE_PAYLOAD)
[Thu Apr 01 22:54:20.886917 2021] [ts2shout:info] [pid 25705] SDT: Stream is station RADIO IRAN from network IRIB.
[Thu Apr 01 22:54:21.506100 2021] [ts2shout:info] [pid 25705] EIT: Current transmission `راه شب- فرهنگ و اندیشه(زنده) '

The first 2 lines are written directly with program start. ts2shout starts reading the mpeg transport stream and searches for the PAT (programme association table) to find the pid of the PMT (programme map table). The PMT is also read to get the pid of the actual "audio payload". This is all done automatically and read from the data stream. When the data from PAT and PMT have been processed the audio data stream is output. As soon as "synced to mpeg audio" and the "SDT:" line have been logged (if no cached station name is found), the audio stream is forwarded in the CGI. In addition to the audio data, data from the EIT (event information table, better known as EPG) is also transferred to determine the current program. If RDS data are available, they can be evaluated preferentially (see above). As a goodie the current radio texts (in case of a change) are logged.

A line that appears often when stopping streaming [cgid:error] [pid 589:tid 139682125932288] (32)broken pipe: [client] ah02550: failed to flush cgi output to client is not an error in the narrow sense, but is caused by how the streaming is aborted by the clients. ts2shout tries to stop the streaming of tvheadend as cleanly as possible.

Tested player software

In this table I would like to describe players that can display the stream title in shoutcast format and therefore can use the programme information in the stream of ts2shout.

DirectionProgramme nameDescription
targetSqueezeboxThat is what I am primarily interested in
targetcmusA curses command line player. It supports the display of the station name and the RDS or EPG data
sourcevdruser feedback: works with current git version

There are certainly countless other players that can do this correctly. Shoutcast is quite common.

Select tvheadend radio stations comfortably in the Squeezebox

In the web interface of the LMS you can edit the favourites (I put the radio programmes there, then you can to which you can also simply assign a logo).

[Ansicht der LMS Oberfläche] [Ansicht des Players]
View of the favorites in the LMS surface and display on the Player, currently running programme SWR1 BW "Der Nachmittag".

You can add individual radio programmes to the favourites list under /var/lib/squeezeboxserver/prefs/favorites.opml. Attention: If you mistype in favorites.opml, the file will be reset when you start the logitechmediaserver. If you switch on the debugging for the favorites, you can see in the logfile which line does not fit.

<outline URL=" BW" icon="" text="SWR1 BW" type="audio" />

As it is a bit cumbersome to enter several radio programmes manually and especially as no logo can be assigned, I've written a perl program in the meantime to generate a station list automatically out of tvheadend. I've written a Perl script that can also be installed on the above mentioned Apache as CGI application. The following must be done:

Configuration section for the automatic station list

    # IP addresses as you need it
    <Location /station>
        Order Deny,Allow
        Deny from all
        Allow from
        Allow from ::1
        Allow from
        SetEnvIf REQUEST_URI "([-A-Za-z0-9_ ]*)$" PROGRAMMNO=$1
        Options +ExecCGI
        Action stationlist /cgi-bin/ virtual
        SetHandler stationlist

The station list is integrated similar to ts2shout and calls 2 REST functions from tvheadend and builds a practical Squeezebox list of all radio programmes configured and activated in tvheadend.

If you only refer to the main station list, you only have to adjust this once in the favourites and you can change the radio programmes with the logo otherwise manage via tvheadend. You don't need usernames or passwords, Apache does that purely via an access list or they are stored in the script. This is the only line that you have to add to your favourites, so that you can access can access all stations.

<outline URL="" icon="html/images/favorites.png" text="Alle Radioprogramme" type="playlist" />

This line must be added to the favourites list, has to be replaced with the hostname of the machine running Apache.

The script can and should be tested at the command line (wether the access to tvheadend works correctly) and also via the browser. Exactly one hierarchical level is supported, if you have not yet thought about the tagging of radio programmes the result is already very useful.

[Senderliste in Squeezer] [Ansicht der
Auswahlliste SWR]
View of the overall station overview (first picture) and the SWR station overview in the Android app Squeezer.

I downloaded the logos from Wikipedia and put them in a directory reachable by the Apache (channel directories) or as "picon" in tvheadend (individual programmes).

Station logo for tvheadend and Squeezebox

To ensure that the station logos can be used well both in the squeezeboxes and via tvheadend, e.g. in kodi, the following settings in tvheadend and LMS are useful Configuration->General (tvheadend) or Settings->Extended>Performance (LMS):

[picon Dialog tvheadend] [LMS Einstellungen]
Setting up the picons - for vlc, kodi etc.;Setting up the local image proxy in the LMS

To allow the piCore Player ("Squeezeboxes") to quickly load the icons in the Favorites selection, it makes sense to use the local "Imageproxy". The setting dialog is called Disk Envelopes Resize ("Plattenhüllen Größenanpassung") and should be set as shown in the screenshot above. Unfortunately Android App Squeezer has a bug where the pictures of the favorites (in my case the channel logos) are no longer displayed on the smartphone. With the following workaround in the LMS (version 7.9.0), the station logos are also displayed in the app. The bug in the app is that "//imageproxy" is called instead of "/imageproxy" (with a slash).

AAC and original Squeezebox Player

The original Squeezeboxes and older LMS installations have troubles with modern AAC(+) formats. (ts2shout logs those as "HE-AAC" into the Log). I've replaced the LMS-Binary faad by a small shellscript that uses ffmpeg:

mamba:/usr/share/squeezeboxserver/Bin/x86_64-linux>cat faad

#! /bin/bash
/usr/bin/ffmpeg -i /dev/stdin -vn -acodec pcm_s16le -f wav - 

The conversion is done correctly, then.

Advantages of this setup are the following

  • Privacy - The increased privacy compared to webstreaming because nobody knows which program you are listening to.
  • Quality - The high quality of MPEG-2 media streams, whether satellite or cable
  • Savings - Save bandwidth and costs
  • independence - Independence from streaming services. No monthly costs for streaming service.
  • full open source - A full open source solution - no tracking, no cloud.
  • failure safety - If you have "done everything right" everything works even if the internet has failed.