RSS Tags

C'est toi le titre


Décrocher une radio Liquidsoap sur des flux distants

Martin Kirchgessner, 2020-08-26

Tags : liquidsoap  en_français 


Depuis les articles précédents nous avons utilisé Liquidsoap pour faire une radio qui passe de la musique et des émissions enregistrées, en supposant que par ailleurs vous avez une bascule matérielle pour faire des émissions en direct. Dans cet article nous allons plutôt faire ce direct à distance, en envoyant un flux encodé via Internet, ou bien laisser l'antenne à un autre flux. Cela fait partie des fonctions de base de Liquidsoap, mais il y a quelques subtilités d'écriture ou de configuration.

Nous allons commencer par deux méthodes pour les décrochages vers des flux existants. Enfin nous verrons comment faire de Liquidsoap un serveur prêt à retransmettre votre flux - autrement dit : comment prendre l'antenne.

Reprendre un flux externe dès qu'il apparaît

Dans un script Liquidsoap, créer une source qui lit un flux distant s'écrit en une ligne :

source = input.http("http://live.campusgrenoble.org:9000/rcg112")

Et voilà ! Si le flux vient d'une adresse en HTTPs, utilisez plutôt input.https. Cette source inclut un tampon (pré-chargement) de lecture de quelques secondes pour amortir les aléas du téléchargement, mais il n'est peut-être pas suffisant. Attention à d'abord expérimenter hors antenne pour vérifier que le débit est suffisamment constant ! En cas d'erreurs vous pouvez ajuster le tampon avec les options buffer (tampon minimum avant de démarrer, en secondes) et max (durée maximum en mémoire tampon, en secondes). Les secondes doivent être des nombres en virgule flottante, donc s'écrire avec un point, ce qui donne par exemple :

source = input.http("http://live.campusgrenoble.org:9000/rcg112", buffer=3., max=30.)

Pour un flux temporaire (typiquement, celui créé par une émission en direct) il n'y a plus qu'à diriger cette source vers une sortie. En mettant cette source en premier dans la liste d'un fallback, par exemple parmi les émissions et musiques des articles précédents, elle prendra l'antenne dès qu'elle sera disponible.

Cette méthode n'est adaptée qu'à un flux qui n'existe que temporairement : la source input.http est faite pour jouer dès qu'elle peut. C'est à dire que :

Donc ça n'est pas terrible pour un flux qui joue en permanence, mais qu'on ne voudrait reprendre que sur une plage horaire donnée. Il ne suffirait pas de mettre la source dans un switch. On va plutôt affiner la lecture du flux avec une deuxième méthode.

Reprendre temporairement un flux externe permanent

Il est possible d'activer la source HTTP(s) uniquement quand on veut lui laisser l'antenne. Il va nous falloir l'unique moyen de passer des commandes à Liquidsoap pendant l'exécution : le serveur. Activez-le (par sécurité, en écoutant uniquement sur localhost) en ajoutant au début du script :

set("server.telnet", true)
set("server.telnet.bind_addr", "127.0.0.1")

En créant la source input.http, on ajoute le paramètre autostart=false pour qu'elle ne commence pas la lecture du flux tout de suite :

source = input.http(id="rcg", "http://live.campusgrenoble.org:9000/rcg112", autostart=false, buffer=3., max=30.)

On a aussi ajouté id, qui donne le nom interne de la source. Ce nom interne va être utilisé dans la commande serveur pour démarrer le flux. Pour démarrer ou arrêter la source de cet exemple, les commandes seront rcg.start et rcg.stop.

Depuis le script, on peut passer des commandes au serveur avec la fonction server.execute. On peut aussi lancer des fonctions à heures fixe (plus généralement, dès que le test pred est vrai) grâce à exec_at. Cela donne, pour par exemple écouter Radio Campus Grenoble tous les jours de 15h à 16h :

exec_at(pred={ 15h00m00s },
  { list.iter(log(label="ecoute_RCG"), server.execute("rcg.start")) }
)
exec_at(pred={ 16h00m00s },
  { list.iter(log(label="ecoute_RCG"), server.execute("rcg.stop")) }
)

Ici list.iter et log s'assurent que tout ce que pourrait écrire la commande sera bien recopiée dans le log. Le label ecoute_RCG est décoratif : c'est un prefixe pour les messages de log qui passeront par là, mettez ce que vous voulez. Par contre le nom avant .stop (ou .start) doit correspondre à l'id de la source !

Gestion du temps et buffers

A cause du buffer (tampon de pré-chargement) de la source input.http, il y a quelques subtilités à prendre en compte si vous êtes à la seconde près.

Avec la méthode ci-dessus, la source ne commencera à télécharger qu'à 15h00:00. Comme notre fonction pred est 15h00m00s, si on démarre Liquidsoap à 15h00:01 rien ne se passera avant le lendemain 15h. De plus notre source à été réglée avec buffer=3., ce qui veut dire qu'il faut pré-charger 3 secondes de son pour que Liquidsoap considère la source comme disponible. Enfin, il peut y avoir un délai entre le moment où on lance la lecture du flux et le moment où le flux nous arrive du serveur - comptez entre quelques milisecondes et (si vous faites dans l'intercontinental) quelques secondes. Du coup, la source sera effectivement activée à 15h00:03 et des poussières.

Inversement, quand on arrête le flux il reste du son dans le buffer : avec nos réglages (max=30.), il peut contenir jusqu'à 30 secondes. Dans l'exemple ci-dessus, le téléchargement du flux va s'arrêter dès la commande stop, donc à 16h00:00. Mais si à ce moment-là on a 23 secondes de son pré-chargées, la source sera encore disponible pendant 23 secondes. Et il va falloir diffuser ces 23 secondes. Si une autre source prend l'antenne à 16h00:00 (par exemple, parce qu'elle est devant dans fallback et qu'elle commence à 16h00:00) alors nos 23 secondes resteront dans le buffer ; Liquidsoap les jouera dès que possible, selon ce qu'il y a dans le reste du script, mais probablement trop en décalé pour ça ait un sens à l'antenne. Autrement dit : en combinant une input.http aux autres sources, attention à vous assurer qu'elle continue à jouer tant qu'elle est disponible !

Je vous recommande de mettre buffer=10., max=30.. Si vous êtes sûrs de la connection entre les deux machines, mettez moins.

Recevoir un flux directement dans Liquidsoap

Nous allons maintenant créer une source qui se comporte comme un serveur Icecast avec input.harbor. Coté "client", donc depuis le studio qui créé le flux, on connectera l'appli (utilisez butt !) vers la machine qui éxecute Liquidsoap au point de montage configuré. Dans cet exemple, le point de montage s'appellera direct :

direct_exterieur = input.harbor(id="direct_exterieur",
  buffer=10.,
  max=30.,
  port=8005,
  user="michel",
  password="superphrasedepasse",
  "direct"
)

La méthode la plus simple pour intégrer cette source est de la mettre en tête d'un fallback(track_sensitive=false. Ainsi, on prend l'antenne dès que le flux est lancé :

grille = fallback([direct_exterieur, emissions, semaine_musicale],
  transitions=[crossfade_3s, crossfade_3s, crossfade_3s],
  track_sensitive=false, id="grille")

Vous devrez vous assurer que vous pouvez atteindre le serveur depuis Internet : si vous n'avez pas une IP fixe ou un cable qui y mène directement, utilisez la NAT ou un tunnel SSH. Après cet article lisez également le chapitre sur harbor dans la documentation de Liquidsoap.

A partir de là vous avez un serveur qui attend des connections : c'est simple à configurer, mais on entre dans des considérations qui dépassent Liquidsoap et sont importantes puisqu'il s'agit de la sécurité de la machine et donc de votre antenne. Pour la sécurité : le plus important reste d'utiliser des mots de passe longs (vous pouvez mettre des phrases) et de les changer régulièrement (grand minimum tous les ans). Vous pouvez en plus utiliser les techniques suivantes.

Séparer la gestion des mots de passes du script Liquidsoap

Au lieu de préciser user et password lors de la définition de la source, on peut plutôt lui passer une fonction auth qui décidera quoi faire dès que quelqu'un essaira de se connecter. En plus d'éviter de devoir redémmarrer Liquidsoap pour changer le(s) mot(s) de passe, cela permet de donner/retirer des comptes individuels : c'est le plus sûr si cette prise d'antenne est régulière par des personnes différentes.

Pour enregistrer/chiffrer/vérifier ces mots de passes, il faut un programme extérieur : vous pouvez utiliser auth.py, script Python téléchargeable ici. La source se créé alors comme ci-dessous, avec une fonction auth qui appelle auth.py :

def auth(user,password) = 
  ret = get_process_output("/home/radio/auth.py #{user} #{password}")
  if string.trim(ret) == "ok" then
    true
  else
    false
  end
end

direct_exterieur = input.harbor(id="direct_exterieur",
  buffer=10.,
  max=30.,
  auth=auth,
  port=8005,
  "direct"  # mountpoint name
)

On utilise string.trim car en Python print("ok") donne très exactement "ok\n".

Avec les deux blocs ci-desus, Liquidsoap appelera directement notre script pour vérifier les mots de passe. Pour enregistrer des mots de passe, l'utilisation d'auth.py est expliquée au début du script ou si vous l'appelez sans arguments.

Chiffrer le flux

Vous pouvez aussi utiliser une connection chiffrée, pour que le flux et surtout le mot de passe donné au début du flux ne soient pas transférés en clair sur Internet. Pour cela, assurez-vous aussi que votre client Icecast peut streamer avec SSL. La source à utiliser coté Liquidsoap est la variante input.harbor.ssl. Cette source a besoin de certificats SSL.

Si votre machine est accessible directement depuis Internet (en particulier son port 80), vous pouvez obtenir des certificats en installant Certbot. Exécutez alors sudo certbot certonly --standalone Une fois les certificats obtenus, indiquez les à Liquidsoap en ajoutant au début du script :

set("harbor.ssl.certificate", "/etc/letsencrypt/live/myradio.com/cert.pem")
set("harbor.ssl.private_key`", "/etc/letsencrypt/live/myradio.com/privkey.pem")

Si votre clé privée nécessite un mot de passe, ajoutez une ligne set("harbor.ssl.password", "...").

Conclusion

Nous voilà avec plusieurs possiblités de décrochages avec Liquidsoap. Elles prennent la forme de sources, que l'ont peut ajouter et intégrer à celles vues dans les articles précédents. Vous trouverez ici le fichier .liq complet ; le schéma des sources et éléments qu'il utilise commence à se complexifier :

schemadelaradioliquidsoap

Ces sources sont plutôt sensibles aux connections irregulières : si le flux n'arrive plus, la source est considérée comme non disponible alors Liquidsoap zappera vers la prochaine source disponible. Selon ce qu'il y a dans le reste du script, ça peut donner des surprises - par exemple, avec le script complet ci-dessus, ça passe un jingle... avant de revenir au flux, si la réception à repris. Un buffer de 10 secondes n'est pas de trop, mais surtout testez d'avance !