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.

Le matériel

  • 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)

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

Une clé RTL-SDR

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

Les logiciels

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

Résolution d'un problème de droit

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

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

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"

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

Relancer udev (en root)

Sous Debian :

1systemctl restart udev

Sous CentOS :

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

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/

dabtools

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

Compilation

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

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

(Ctrl+C pour arrêter)

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

dablin

É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

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')

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

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

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

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

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

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

1$ file france-inter.pcm
2data

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

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

Et voilà

welle-cli

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

Compilation sous Debian :

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

Compilation sous CentOS7 :

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

Dépendances :

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

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

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

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

Compilation sous Rock Linux 8:

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

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

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

1yum install rpm-fusion-free
2yum install faad2-devel

Exemple d'enregistrement du multiplex 11B (dans le répertoire courant) :

 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'

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

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 ?

Patches sauvages pour welle-cli

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());

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     }

Bilan

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...

comments powered by Disqus