I. Introduction

Image non disponible

Gros utilisateur des webradios du site di.fm depuis de nombreuses années, je me suis penché il y a quelque temps sur le protocole SHOUTcast utilisé par un certain nombre de radios pour streamer du flux audio mp3 par HTTP.

Les spécifications du protocole créé par Nullsoft (Winamp) ne sont pas clairement définies, ni vraiment publiques. La documentation à ce sujet est assez rare et relève plutôt du reverse engineering et de la bidouille avec Wireshark :). Je me suis amusé à programmer une application qui parse et enregistre les flux SHOUcast, SHOUTcast Recorder. Aujourd'hui je profite de mon expérience sur le sujet pour regrouper un peu toutes les informations que j'ai trouvées sur ce protocole.

Je tiens tout d'abord à préciser que je ne garantis pas l'exactitude à 100 % des informations qui vont suivre. Pour comprendre le fonctionnement de SHOUTcast, je me suis basé sur les sources de clients classiques, tels que MPlayer, VLC mais aussi sur différents forums et le peu de documents que j'ai pu repérer sur Internet. Si vous détectez la moindre erreur ou inexactitude dans mes propos, n'hésitez pas à m'en faire part, que je corrige mon article en conséquence.

II. Le protocole

SHOUTcast, initialement appelé I Can Yell (ICY), est donc le protocole utilisé pour streamer du son ainsi que, par la même occasion, différentes informations textuelles sur le flux en question. Les clients et serveurs SHOUTcast sont disponibles sur un certain nombre de plateformes. Mais je vais surtout m'intéresser ici au protocole en lui-même.

III. Premiers pas

SHOUTcast n'est autre que de la musique transmise par HTTP. Il suffit d'envoyer une simple requête HTTP pour initier la connexion.

Avant toute chose, le serveur va nous renvoyer dans son header HTTP différentes informations textuelles sur le flux.

Essayons avec la radio Chillout de di.fm, mais vous en trouverez d'autres sur Shoutcast.com.

 
Sélectionnez
curl http://scfire-ntc-aa04.stream.aol.com:80/stream/1035 | head

nous retourne

 
Sélectionnez
ICY 200 OK
icy-notice1: <BR>This stream requires <a href="http://www.winamp.com/">Winamp</a><BR>
icy-notice2: Firehose Ultravox/SHOUTcast Relay Server/Linux v2.6.0<BR>
icy-name: Chillout - D I G I T A L L Y - I M P O R T E D - ambient psy chillout, check out our trippy flavors!
icy-genre: Electronic Chillout Ambient
icy-url: http://www.di.fm/chillout
content-type: audio/mpeg
icy-pub: 1
icy-br: 96

ou plus en détail :

 
Sélectionnez
curl http://scfire-ntc-aa04.stream.aol.com:80/stream/1035 | hexdump -c | head -c 2400

qui nous renvoie :

 
Sélectionnez
00000000  49 43 59 20 32 30 30 20  4f 4b 0d 0a 69 63 79 2d  |ICY 200 OK..icy-|
00000010  6e 6f 74 69 63 65 31 3a  20 3c 42 52 3e 54 68 69  |notice1: <BR>Thi|
00000020  73 20 73 74 72 65 61 6d  20 72 65 71 75 69 72 65  |s stream require|
00000030  73 20 3c 61 20 68 72 65  66 3d 22 68 74 74 70 3a  |s <a href="http:|
00000040  2f 2f 77 77 77 2e 77 69  6e 61 6d 70 2e 63 6f 6d  |//www.winamp.com|
00000050  2f 22 3e 57 69 6e 61 6d  70 3c 2f 61 3e 3c 42 52  |/">Winamp</a><BR|
00000060  3e 0d 0a 69 63 79 2d 6e  6f 74 69 63 65 32 3a 20  |>..icy-notice2: |
00000070  46 69 72 65 68 6f 73 65  20 55 6c 74 72 61 76 6f  |Firehose Ultravo|
00000080  78 2f 53 48 4f 55 54 63  61 73 74 20 52 65 6c 61  |x/SHOUTcast Rela|
00000090  79 20 53 65 72 76 65 72  2f 4c 69 6e 75 78 20 76  |y Server/Linux v|
000000a0  32 2e 36 2e 30 3c 42 52  3e 0d 0a 69 63 79 2d 6e  |2.6.0<BR>..icy-n|
000000b0  61 6d 65 3a 20 43 68 69  6c 6c 6f 75 74 20 2d 20  |ame: Chillout - |
000000c0  44 20 49 20 47 20 49 20  54 20 41 20 4c 20 4c 20  |D I G I T A L L |
000000d0  59 20 2d 20 49 20 4d 20  50 20 4f 20 52 20 54 20  |Y - I M P O R T |
000000e0  45 20 44 20 2d 20 61 6d  62 69 65 6e 74 20 70 73  |E D - ambient ps|
000000f0  79 20 63 68 69 6c 6c 6f  75 74 2c 20 63 68 65 63  |y chillout, chec|
00000100  6b 20 6f 75 74 20 6f 75  72 20 74 72 69 70 70 79  |k out our trippy|
00000110  20 66 6c 61 76 6f 72 73  21 0d 0a 69 63 79 2d 67  | flavors!..icy-g|
00000120  65 6e 72 65 3a 20 45 6c  65 63 74 72 6f 6e 69 63  |enre: Electronic|
00000130  20 43 68 69 6c 6c 6f 75  74 20 41 6d 62 69 65 6e  | Chillout Ambien|
00000140  74 0d 0a 69 63 79 2d 75  72 6c 3a 20 68 74 74 70  |t..icy-url: http|
00000150  3a 2f 2f 77 77 77 2e 64  69 2e 66 6d 2f 63 68 69  |://www.di.fm/chi|
00000160  6c 6c 6f 75 74 0d 0a 63  6f 6e 74 65 6e 74 2d 74  |llout..content-t|
00000170  79 70 65 3a 20 61 75 64  69 6f 2f 6d 70 65 67 0d  |ype: audio/mpeg.|
00000180  0a 69 63 79 2d 70 75 62  3a 20 31 0d 0a 69 63 79  |.icy-pub: 1..icy|
00000190  2d 62 72 3a 20 39 36 0d  0a 0d 0a 57 0b 2b 21 ff  |-br: 96....W.+!.|
000001a0  9d 6a 9d 35 15 e7 c1 56  c9 b6 54 22 33 b9 ec f5  |.j.5...V..T"3...|
000001b0  63 8f a9 dd 4f 15 79 57  f7 4d 55 3d 34 51 3c cd  |c...O.yW.MU=4Q<.|
000001c0  ae 9c 72 e7 4d c7 13 0e  d7 d8 fc cb 98 f4 b3 59  |..r.M..........Y|
000001d0  1f 48 b3 da b4 24 00 00  01 df c0 0d f3 b1 08 07  |.H...$..........|

Ce header est donc composé de champs icy se terminant tous par un <CR> (\r\n), soit 0d 0a en hexa. Le début du header commence par le code de réponse (similaire à HTTP). Je n'ai pas eu l'occasion de recevoir autre chose que le code 200.

Voici une liste, sans doute non exhaustive, des champs possibles du header.

Champs Contenu
icy-notice1 et icy-notice2 Informations diverses
icy-name Nom de la radio
icy-genre Genre
icy-url URL de la radio
icy-pub Si le flux est public : 1 ou privé : 0
icy-br Bitrate (échantillonnage)
icy-metaint Fréquence d'apparition des packets metadata (voir plus loin)
Content-Type Comme pour HTTP, format du flux
icy-irc Contact irc
icy-icq Contact icq
icy-aim Contact aim (Nullesoft appartient à AOL :p)

La fin du header est marquée par un double <CR>, c'est-à-dire la chaîne \r\n\r\n, ou autrement dit 0d 0a 0d 0a. Les données brutes mp3 suivent directement.

Ainsi, une simple redirection de la sortie standard de curl permet d'enregistrer le flux SHOUTcast :

 
Sélectionnez
curl http://scfire-ntc-aa04.stream.aol.com:80/stream/1035 > ./out.mp3

Bien sûr, cette solution enregistre également le header, ce qui occasionne un glitch en début de mp3, si vous utilisez cette solution pour enregistrer le flux.

IV. Aller plus loin, les blocs metadata

Le protocole SHOUTcast permet également de récupérer des informations sur la musique transmise. Il s'agit des metadata, ce sont des blocs envoyés à intervalles réguliers, qui peuvent contenir un message textuel.

Par défaut, les blocs metadata ne sont pas transmis. Car en effet, ils sont envoyés directement dans le flux, entre deux blocs de données mp3. Ainsi, si l'on enregistre directement les données issues du serveur, nous avons droit à un flux musical interrompu par de petits glitches réguliers. Le client doit donc prendre en compte ces blocs et les échapper du flux musical.

Donc, c'est au client de demander ces petits paquets magiques, en rajoutant le champ suivant dans la requête HTTP.

 
Sélectionnez
Icy-MetaData:1

En rajoutant ce champ à la requête, le serveur nous retourne un champ ICY supplémentaire, icy-metaint. Il s'agit d'un entier correspondant au nombre d'octets séparant deux blocs metadata. Cette valeur dépend complètement des serveurs, et doit être à tout prix récupérée si l'on souhaite traiter correctement ces blocs.

Par exemple la radio Chillout nous renvoie 16384, la radio Frequence3, quant à elle, 32768.

V. Isoler les blocs metadata et les lire

Nous avons donc envoyé au serveur une requête HTTP qui demande les blocs metadata, nous avons reçu un header qui contient le champ `icy-metaint` que nous avons enregistré. Il faut maintenant compter les octets reçus pour savoir quand les blocs metadata arriveront. Le compte commence dès le premier octet de flux MP3 reçu. C'est-à-dire, juste après le double <CR> \r\n\r\n.

Une fois le compte atteint, nous tombons sur un bloc metadata. Ce dernier commence par un octet contenant la taille du bloc. Pour avoir la taille en octets du bloc, il suffit de multiplier la valeur de ce premier octet par 16. Il n'y a alors plus qu'à parcourir le bloc, et de réinitialiser le compteur après le dernier octet de metadata reçu. On peut alors recommencer le compte jusqu'au prochain bloc.

Quand il n'est pas vide, un bloc metadata contient ce genre de chaîne :

 
Sélectionnez
StreamTitle='Pete Namlook & Hubertus Held - Missing You';StreamUrl='';

C'est StreamTitle qui nous intéresse ici, il n'y a plus qu'à l'extraire.

Cependant, 98 % du temps, les blocs metadata envoyés par le serveur sont vides et contiennent juste un seul octet à zéro. Eh oui, vu la fréquence d'envoi assez élevée, le serveur n'a pas besoin de nous notifier en permanence le nom de la musique.

En pratique, les serveurs envoient un bloc renseigné de metadata à la connexion, puis à chaque changement de musique. Certains serveurs sont plus bavards que d'autres. Il est même possible de détecter la pub qui est parfois annoncée par metadata !

Voici un schéma qui représente les choses :

Image non disponible

VI. Utiliser le protocle SHOUTcast

Il existe de très nombreux logiciels pour lire et même enregistrer les flux SHOUTcast. Mais étonnement, je n'ai trouvé que peu de documentation. J'ai avant tout développé SHOUTcast Recorder pour le fun et surtout pour comprendre comment fonctionne le protocole. Dans sa version actuelle, les mp3 sont enregistrés et coupés à la réception des blocs metadata. Malheureusement, les serveurs envoient ces blocs quelques secondes avant le changement de musique, et cette période dépend de chaque radio. Les logiciels d'enregistrement de flux se basent sur le petit blanc dans le signal sonore entre deux musiques pour faire une découpe nette. De mon côté, je n'ai pas encore eu le temps de trouver d'autres solutions, ni de m'essayer à implémenter cette technique overkill. Je ferai ça quand j'aurai le temps.

Amusez-vous bien :D

VII. Sources

Cet article a été également publié sur mon blog.

J'en profite pour remercier les gens qui m'avaient aidé ici en 2008 dans mes premiers bricolages avec SHOUTcast ainsi que ClaudeLELOUP pour sa relecture.