Un petit rappel pour ceux qui sont trop jeunes, mais la glorieuse époque du MP3 a vraiment décollé quand on a pu mettre des thèmes dans son lecteur. Et les balaises pour ça, c'était nullsoft avec son historique Winamp .
Les gens de Nullsoft ne se sont pas arrêtés en si bon chemin, et ils ont créé Shoutcast , un bricolage permettant la création de radios sur Internet. Pas de pinaillages de fréquences radio à partager, on écoute d'un coté un flot continu de tranches de mp3, et on arrose tous les auditeurs avec ces mêmes tranches. Techniquement, c'était une époque de Far West, et le leader écrasant avait raison. Donc, le protocole part du principe de défoncer HTTP, qui faisait à peu près le taff. Les RFCs, ou même de simples spécifications, c'est pour les loosers.
Le MP3 fonctionne super bien, mais il est breveté par l'institut Fraunhofer , et ça a engendré des procès bien sanglants. Du coup les libristes ont ripostés avec ogg-vorbis (ogg pour le format, vorbis pour une compression de son comparable au MP3), ils ont enquillé sur XMMS comme clone peu convaincant de Winamp, et Icecast comme alternative à Shoutcast.
Icecast
Icecast est libre, OK, mais à le même mépris sur la notion de spécification technique que son inspirateur, Shoutcast.
L'astuce fut donc de créer une lib client, libshout qui permet de causer à un serveur Shoutcast ou Icecast. La lib est en C, sous licence LGPL, pas relou à déployer, du coup, bah tous les gens qui veulent causer à Icecast n'ont qu'à utiliser libshout , et c'est marre. Pour ceux qui n'aiment pas coder en C, ils n'ont qu'à utiliser un binding.
Techniquement, lier un serveur à un unique client est un crime. Les derniers qui ont fait ça, c'était Microsoft avec IIS (le serveur) et IE6 (le client), et ça a fini dans du sang et des larmes.
Les gens civilisés écrivent des specs, fournissent une implémentation de référence, et demandent gentiment pour que soient créées au moins deux autres implémentations. Sinon, ce n'est pas un protocole, mais une coïncidence.
Icecast a commencé par créer un protocole basé sur HTTP, mais qui invente une nouvelle méthode, SOURCE
, parce que pourquoi pas. Ils se sont pris des cailloux, et du coup, dans la dernière version majeure, ils ont créé une nouvelle version du protocole, utilisant la méthode PUT
qui elle, existe. Ils ont promis de déprécier l'infâme SOURCE, mais leur outil en ligne de commande continue de l'utiliser par défaut. De toute façon, même avec une méthode existante dans HTTP, le protocole mis à jour d'Icecast ne respecte pas la norme HTTP. Les codes retour sont eux aussi bricolés, avec plein de 403 farfelus. Si vous aussi voulez du LOL vintage, allez lire le truc le plus proche de spec pour le protocole d'Icecast traine sur un gist . Ce bazar, c'est pour le flot montant, la publication.
Pour le flot descendant, c'est du même niveau. Les devs d'Icecast ont vu que HTTP/1.1 impose d'annoncer une taille pour le contenu servi. Comme Icecast ne connait pas cette taille, vu que c'est un flot à priori infini, bah, astuce dit la puce : "on a qu'à utiliser de l'HTTP/1.0". Protocole ringard rapidement corrigé par une 1.1 qui ne va pas forcément être très bien géré par les proxys.
Quand on prend le temps de lire les RFC d'HTTP, on peut voir qu'il est possible d'envoyer une réponse HTTP par lots, en annonçant la taille, puis le lot. À ce propos, la page Wikipedia est plus lisible que le gros TXT des RFC.
HTTP/2 ne gère pas le chunked transfert, mais il permet d'utiliser les primitives du multiplexage pour streamer du contenu.
Quand on se renseigne sur l'utilisation d'un proxy avec Icecast (genre, vous voulez tout bien ranger derrière un Nginx/Haproxy pour garantir un minimum de qualité), ça pique. Personne ne sait trop si le proxy va essayer de bufferiser (et ajouter de la latence, voir de tout péter), et comment le configurer correctement pour lever toutes ambiguïtés. Du coup, la recommandation officielle du mainteneur est de laisser Icecast sur son port farfelu, et de lui laisser gérer le SSL. Voilà.
Ironiquement, lors de la rédaction de l'article, il y a eut un incident sur le site d'Icecast, avec comme conséquence une bonne grosse alerte Firefox sur la configuration SSL. Une machine cassée avec un mélange dans les certificats, pour la petite histoire.
De toute façon, la configuration de TLS est spartiate, on peut l'activer, choisir les ciphers, et puis c'est tout. Rien sur la version minimale que l'on accepte, pas d'indice sur la gestion ou non de la version 1.3 de TLS.
Au-delà de Icecast.
C'est bien de médire, mais c'est mieux de proposer des trucs.
Il y a des gens qui construisent des tours Eiffel en allumettes, moi j'implémente des protocoles. Là, ce sera du Golang avec Streamcast .
L'avantage d'Icecast, c'est que c'est un standard de fait, géré par plein de logiciels, et il est indispensable de faire au plus simple pour la partie création de contenu. En attendant la décennie nécessaire pour proposer un Icecast 3 et sa généralisation dans les outils de production de son, donc autant commencer par implémenter la mochitude actuelle. Le client de référence sera butt sélectionné pour son nom, et son côté multiplateforme.
Pour le flot descendant, là, on est bien plus libre, la cible étant les navigateurs web contemporains, et VLC, parce que c'est quand même le fleuron de la French Tech que le monde nous envie. Donc, ce sera à Golang de gérer soit du chunked en HTTP/1.1 soit des DATA frames en HTTP/2.
Détails de l'implémentation
Le point de démarrage est le format ogg . Le format ogg est composé de pages avec un entête de taille fixe, qui contient les informations pour calculer la taille du corps, et ça recommence. Comme implémentation naïve, je me contente de chercher le OggS
qui annonce le début d'une page , jusqu'au OggS
suivant qui indique la fin de cette page (et le début de la suivante). Comme en Golang on ne yield
pas dans les itérateurs (c'est en Python, ça), j'écris la page dans une interface PageWriter
.
Dans le reste du code, ce sont des Page
qui vont circuler.
Comme entrée, ce n'est pas de l'HTTP, parce qu'Icecast a eu la flemme, mais un bête serveur TCP avec une pseudo machine à état . Le code se contente de gérer l'exemple fourni dans le gist de spécification. C'est fragile, facile à abuser, mais ça fonctionne nickel avec Butt .
Pour pouvoir démarrer la diffusion, il faut commencer par avoir les deux premières pages ogg
tel que le spécifié vorbis
(j'aime quand c'est spécifié). Voir vorbis/stream .
Chaque client devra recevoir ses deux pages d'entête, puis se branchera sur le flot de pages. Voir vorbis/subscriber .
Le point d'entrée HTTP pour la diffusion du flux va enregistrer le client dans un pubsub, et gèrera la désinscription quand la connexion sera interrompue. Un type mime adéquat, des entêtes pour neutraliser les caches, et hop, on peut envoyer le flux de musique. Voir web . Promis, vous pouvez mettre ça devant un proxy HTTP, il n'y aura pas de surprise.
Une page statique en HTML5 avec une balise <audio>
et l'on peut apprécier les joies de la normalisation, le rendu de la page ne sera même pas au courant que c'est du streaming, car c'est le rôle d'HTTP de gérer ça. La page de démo est en HTML5 vanilla, sans javascript.
Voilà, en un peu plus de 700 lignes de Golang, il est possible d'avoir un prototype assurant la diffusion d'un flux de pages d'ogg/vorbis vers plusieurs clients web. Le tout sans heurter la moindre norme HTTP pour la diffusion, et en profitant des outils existants pour la production.
Branchez votre casque, tapotez votre micro, et c'est parti !