Enregistrer un multiplex de radio DAB+

Expérimentons la captation de stations de radio DAB+, voire directement d'un multiplex complet et regardons les outils logiciels et matériels nécessaires.

  • Un ordinateur sous linux (ex: Raspberry Pi)
  • Une antenne accordée sur les fréquences du DAB+ (VHF bande III, entre 180 MHz et 230 MHz)
  • Un récepteur SDR de type clé RTL-SDR.

Un multiplex DAB+ faisant 1.536 MHz de bande passante, le récepteur et le CPU de l'ordinateur doivent supporter au moins cette largeur de bande.

De haut en bas: antenne UHF (TNT), VHF Bande III (DAB+), VHF Bande II (FM)
De haut en bas: antenne UHF (TNT), VHF Bande III (DAB+), VHF Bande II (FM)

Au milieu, une antenne adaptée à la réception DAB+

Une clé RTL-SDR
Une clé RTL-SDR

Une clé RTL-SDR pour faire de la radio logicielle, reliée à l'antenne DAB+

L'OS est un Debian 11 (ou Raspberry Pi OS) fraichement installé.

Après avoir réglé un soucis de droit avec udev, nous nous servirons des outils libres suivants que nous téléchargerons et compilerons :

  • dabtools : un enregistreur de flux brut ETI du multiplex
  • dablin : un lecteur de fichier ETI et démultiplexeur / extracteur d'audio
  • welle-cli : issu du projet welle-io, permet également l'enregistrement d'un multiplex

Sous Debian/Ubuntu, pour utiliser notre périphérique rtl-sdr, il faut accorder certains droits à udev sous peine d'avoir à l'usage le message d'erreur suivant :

1Please fix the device permissions, e.g. by installing the udev rules file rtl-sdr.rules
bash

Récupérons l'identifiant constructeur de la clé RTL-SDR :

1$ lsusb
2Bus 003 Device 002: ID 0bda:2838 Realtek Semiconductor Corp. RTL2838 DVB-T
bash

Note: sous CentOS, lsusb est installable via yum install usbutils

En root, créer un fichier /etc/udev/rules.d/20.rtlsdr.rules avec le contenu suivant :

1SUBSYSTEM=="usb", ATTRS{idVendor}=="0bda", ATTRS{idProduct}=="2838", GROUP="adm", MODE="0666", SYMLINK+="rtl_sdr"
bash

Note: adapter le nom du GROUP: adm, admindl ...

Relancer udev (en root)

Sous Debian :

1systemctl restart udev
bash

Sous CentOS :

1udevadm control --reload-rules && udevadm trigger
bash

Débrancher/rebrancher la clé usb

un lien /dev/rtl_sdr doit maintenant être présent et le périphérique est désormais accessible à tous les users du groupe adm

Source: https://www.instructables.com/rtl-sdr-on-Ubuntu/

Nous allons utiliser la commande dab2eti issue du projet dabtools

Les dépendances requises sont installables en root via la commande suivante :

1apt install git cmake librtlsdr-dev libfftw3-dev
bash

Compilation

1git clone https://github.com/Opendigitalradio/dabtools
2cd dabtools
3mkdir build
4cd build
5cmake ..
6make
7sudo make install
bash

Pour enregistrer le multiplex "towerCast" du Canal 9D (208.064 MHz) avec un gain de +29.7dB

 1$ dab2eti 208064000 297 > 9D-towercast.eti
 2Found 1 device(s):
 3  0:  Realtek, RTL2838UHIDIR, SN: 00000001
 4
 5Using device 0: Generic RTL2832U OEM
 6Detached kernel driver
 7Found Rafael Micro R820T tuner
 8Supported gain values (29): 0.0 0.9 1.4 2.7 3.7 7.7 8.7 12.5 14.4 15.7 16.6 19.7 20.7 22.9 25.4 28.0 29.7 32.8 33.8 36.4 37.2 38.6 40.2 42.1 43.4 43.9 44.5 48.0 49.6
 9[R82XX] PLL not locked!
10Tuned to 208064000 Hz.
11Tuner gain set to 29.70 dB.
12Waiting for sync...
13Allocating 32 zero-copy buffers
14startup_delay=1
15Locked
16ENSEMBLE_INFO: EId=0xf200, CIFCount = 12 228
17SubChId= 1, slForm=1, StartAddress=144, size= 72, bitrate= 96, ASCTy=0x3f
18SubChId= 2, slForm=1, StartAddress=216, size= 72, bitrate= 96, ASCTy=0x3f
19SubChId= 3, slForm=1, StartAddress=288, size= 72, bitrate= 96, ASCTy=0x3f
20SubChId= 4, slForm=1, StartAddress= 72, size= 72, bitrate= 96, ASCTy=0x3f
21SubChId= 6, slForm=1, StartAddress=360, size= 36, bitrate= 48, ASCTy=0x3f
22SubChId= 8, slForm=1, StartAddress=  0, size= 72, bitrate= 96, ASCTy=0x3f
bash

(Ctrl+C pour arrêter)

Note: au 18/06/2022 je n'arrive plus à accrocher la synchronisation d'un mux ...

Également issu du projet Open Digital Radio, dablin est un outil permettant de lire un fichier .eti qui est un format conteneur de l'ensemble des données brutes (audio+metadata) d'un multiplex DAB+.

Compilation

1sudo apt install libsdl2-dev libmpg123-dev libfaad-dev
2git clone https://github.com/Opendigitalradio/dablin
3cd dablin
4mkdir build
5cd build
6cmake ..
7make
8sudo make install
bash

Récupération de la liste des services à l'intérieur du multiplex

1$ dablin 9D-towercast.eti 2>&1 | grep "programme service label"
2FICDecoder: SId 0xF204: programme service label 'FIP' ('FIP')
3FICDecoder: SId 0xF208: programme service label 'Mouv'' ('Mouv'')
4FICDecoder: SId 0xF201: programme service label 'France Inter' ('Inter')
5FICDecoder: SId 0xF202: programme service label 'France Culture' ('Culture')
6FICDecoder: SId 0xF203: programme service label 'France Musique' ('Musique')
7FICDecoder: SId 0xF206: programme service label 'France Info' ('Info')
bash

On veut par exemple extraire France Inter (serviceId 0xF204), en gardant le codec audio d'origine sans réencodage (-u) :

1dablin 9D-towercast.eti -u -s 0xf204 > france-inter.aac
bash

Vérifions le format de fichier généré

1$ file france-inter.aac
20xf204.aac: MPEG-4 LOAS
bash

On peut aussi décoder le flux audio pour avoir du pcm avec -p :

1dablin 9D-towercast.eti -p -s 0xf204 > france-inter.pcm
bash

Vérifions également le format de fichier généré

1$ file france-inter.pcm
2data
bash

Attention, c'est un flux pcm brut sans entête. welle-cli nous a quand même donné quelques indications sur le format :

1PCMOutput: format set; samplerate: 48000, channels: 2, output: 32bit float
bash

ajoutons les bonnes entêtes pour générer un .wav valide :

1ffmpeg -f f32le -ar 48k -ac 2 -i france-inter.pcm france-inter.wav
bash

Et voilà

Autre méthode d'enregistrement d'un multiplex DAB+ complet avec cette fois la commande welle-cli issu du projet welle-io.

1sudo apt install git cmake libfftw3-dev libmp3lame-dev libmpg123-dev librtlsdr-dev libfaad-dev libasound2-dev
2cd $HOME
3mkdir src && cd src
4git clone https://github.com/AlbrechtL/welle.io.git && cd welle.io
5mkdir build && cd build
6cmake .. -DRTLSDR=1 -DBUILD_WELLE_IO=OFF -DBUILD_WELLE_CLI=ON
7make
8sudo make install
bash

cmake non installé par défaut et la version proposée de base est la v2.x. welle-cli nécessiste une v3 mini. cmake3 est disponible dans les dépôt epel qu'il faut activer :

1yum install epel-release yum-utils
2yum install cmake3
3ln -s /usr/bin/cmake3 /usr/bin/cmake
bash

Dépendances :

1yum install git gcc-c++ fftw-devel lame-devel mpg123-devel rtl-sdr-devel
bash

faad2 est distribué via le dépot rpm fusion, activons le :

1yum install https://mirrors.rpmfusion.org/free/el/rpmfusion-free-release-7.noarch.rpm
2yum install faad2-devel
bash

zut, à la compilation: unsupported gcc compiler 4.8.5 pour installer en parallèle une version + récente de gcc

1yum install centos-release-scl
2yum install devtoolset-7
3scl enable devtoolset-7 bash
4gcc --version
5gcc (GCC) 7.3.1
gdscript3

La compilation sous CentOS peut alors se dérouler normalement.

1yum install gcc-c++ # v 8.5.0 OK
2yum install cmake   # v 3.20 OK
bash

Activation des powertools pour ces dépendances :

1dnf config-manager --set-enabled powertools
2yum install git gcc-c++ fftw-devel lame-devel mpg123-devel rtl-sdr-devel
bash

Activation de rpm-fusion-free pour cette dépendance :

1yum install rpm-fusion-free
2yum install faad2-devel
bash
 1$ welle-cli -c 11B -D
 2$ ls
 3-rw-r--r-- 1 pi   pi    2310188 sept. 20 14:26  GENERATIONS.wav
 4-rw-r--r-- 1 pi   pi    2318380 sept. 20 14:26 'CHANTE FRANCE.wav'
 5-rw-r--r-- 1 pi   pi    2310188 sept. 20 14:26  FG.wav
 6-rw-r--r-- 1 pi   pi    2310188 sept. 20 14:26  EVASION.wav
 7-rw-r--r-- 1 pi   pi    2310188 sept. 20 14:26 'TSF JAZZ.wav'
 8-rw-r--r-- 1 pi   pi    2310188 sept. 20 14:26  Melody.wav
 9-rw-r--r-- 1 pi   pi    2318380 sept. 20 14:26 'BLEU PARIS.wav'
10-rw-r--r-- 1 pi   pi    2318380 sept. 20 14:26 'SUD RADIO.wav'
11-rw-r--r-- 1 pi   pi    2310188 sept. 20 14:26 'OUI FM.wav'
12-rw-r--r-- 1 pi   pi    2310188 sept. 20 14:26  NOVA.wav
13-rw-r--r-- 1 pi   pi    2310188 sept. 20 14:26 'J A Z Z Radio.wav'
14-rw-r--r-- 1 pi   pi    2310188 sept. 20 14:26  VOLTAGE.wav
15-rw-r--r-- 1 pi   pi    2310188 sept. 20 14:26 'Radio Notre Dame.wav'
bash

Examinons le format généré :

1$ file BLEU\ PARIS.wav
2BLEU PARIS.wav: RIFF (little-endian) data, WAVE audio, Microsoft PCM, 16 bit, stereo 48000 Hz
bash

Quand on le lit avec mpv un warning se produit

1[ffmpeg/demuxer] wav: Estimating duration from bitrate, this may be inaccurate

les compteurs dans les entêtes ne sont pas bons. On peut réparer ça avec ffmpeg en le réencapsulant

1ffmpeg -i TEST2\ DAB+\ TFL.wav -c copy TEST2\ DAB+\ TFL\ fix.wav

Le fichier se lit mais ffmpeg a ajouté un subchunk LIST-INFO non essentiel. On peut demander à ce que ne soit pas ajouté avec l'option -bitexact :

1ffmpeg -i TEST2\ DAB+\ TFL.wav -c copy -bitexact TEST2\ DAB+\ TFL\ fix2.wav

On aura alors un fichier .wav valide, et de la même taille que le fichier avant réparation.

Note: avec un Raspberry Pi 2, je constate des drops audio. Peut être un cpu pas assez puissant pour décoder tout un multiplex ?

Un fork est disponible ici https://github.com/aerogus/welle.io.git et va encore plus loin que les mini patchs suivants :

welle-cli enregistre localement les flux en basant le nommage des fichiers sur le serviceLabel. Si on veut plutôt se baser sur le serviceId voici un patch (qui sette aussi le répertoire de stockage) :

 1diff --git a/src/welle-cli/welle-cli.cpp b/src/welle-cli/welle-cli.cpp
 2index dcd1019..6175973 100644
 3--- a/src/welle-cli/welle-cli.cpp
 4+++ b/src/welle-cli/welle-cli.cpp
 5@@ -626,7 +626,15 @@ int main(int argc, char **argv)
 6                 }
 7                 cerr << endl;
 8
 9-                string dumpFilePrefix = s.serviceLabel.utf8_label();
10+               // origine : nommage avec serviceLabel
11+                //string dumpFilePrefix = s.serviceLabel.utf8_label();
12+
13+               // hack: nommage avec le sid ex: 0xf00d dans un répertoire dédié
14+               std::stringstream stream;
15+               stream << "0x" << std::setfill('0') << std::hex << s.serviceId;
16+                string dumpFilePrefix = "/home/pi/rec/" + stream.str();
17+                //
18+
19                 dumpFilePrefix.erase(std::find_if(dumpFilePrefix.rbegin(), dumpFilePrefix.rend(),
20                             [](int ch) { return !std::isspace(ch); }).base(), dumpFilePrefix.end());
diff

Si l'on choisit d'utiliser welle-cli via son serveur web, il faut savoir que le flux DAB+ natif (AAC), sera décodé en linéaire puis réencodé en mp3. Pas l'idéal, alors autant setter l'encodeur pour perdre un minimum de qualité.

 1diff --git a/src/welle-cli/webprogrammehandler.cpp b/src/welle-cli/webprogrammehandler.cpp
 2index 6f4a977..b67648b 100644
 3--- a/src/welle-cli/webprogrammehandler.cpp
 4+++ b/src/welle-cli/webprogrammehandler.cpp
 5@@ -226,8 +226,14 @@ void WebProgrammeHandler::onNewAudio(std::vector<int16_t>&& audioData,
 6     if (not lame_initialised) {
 7         lame_set_in_samplerate(lame.lame, rate);
 8         lame_set_num_channels(lame.lame, channels);
 9-        lame_set_VBR(lame.lame, vbr_default);
10-        lame_set_VBR_q(lame.lame, 2);
11+
12+        lame_set_brate(lame.lame, 320);
13+        lame_set_mode(lame.lame, STEREO);
14+        lame_set_quality(lame.lame, 2);
15+
16+        //lame_set_VBR(lame.lame, vbr_default);
17+        //lame_set_VBR_q(lame.lame, 2);
18+
19         lame_init_params(lame.lame);
20         lame_initialised = true;
21     }
diff

Ces méthodes sont encore un peu expérimentales. J'arrive bien à récupérer le flux audio brut sans réencodage mais j'aurai aimé un outil d'extraction direct en aac de l'ensemble des flux du multiplex (ça se scripte), voire de faire ça en temps réel. Deplus il serait bon d'avoir des découpes horaires propres "avec raccordement" et sans overlap. Bref encore beaucoup de choses à découvrir dans le domaine...