Icestream est un logiciel de stream audio qui permet de mixer, recevoir ou envoyer des flux audio à travers un réseau local ou Internet.
Les nouvelles fonctionnalités et changements :
General
Lecteur
Client
Server
L'installation la plus facile se fait à partir du package (.deb) situé ici.
Double-clicker (ou click-droit → Installer logiciel) le fichier installera Icestream, ainsi que ces dépendances.
Vous pouvez également installer le package via dpkg, puis apt pour les dépendances :
dpkg -i ~/chemin/vers/icestream_x.x-x_x.deb apt -f install
Si vous souhaitez compiler directement IceStream, vous trouverez ce dont vous avez besoin ici. N'oubliez pas d'installer les dépendances nécessaire :
apt install debhelper-compat, libvorbis-dev, libxml2-dev, libxslt1-dev, qt4-qmake, g++, libqt4-dev, libqtwebkit4
L'interface consiste de trois onglets.
- Les lecteurs, pouvant jouer la majorité des formats audio et vidéo, que ce soit en local, ou en réseau.
- Le client, permettant de transmettre le flux vers un serveur.
- Le serveur, si vous souhaitez héberger votre propre serveur.
Pour lire un fichier/flux, il vous suffira d'insérer de cliquer sur le bouton représentant un dossier et de sélectionner votre audio, ou bien directement insérer le chemin/URL dans la barre de texte. Vous pourrez ensuite cliquer le bouton Play.
Pour envoyé votre flux vers un serveur, il vous suffira d'entrer les paramètres nécessaire à la connexion (adresse, port, mot de passe). Vous pouvez également modifier les paramètres audio à votre guise (Il est conseillé d'avoir un taux d’échantillonnage supérieur à 44100).
Il vous faudra ensuite, dans l'onglet “Connections” de qjackctl, lier les lecteurs (appelés “MPlayer”) au client “Liquidsoap”. Cette interface vous permettra de gérer quels flux sont envoyés au serveur, choisissant ainsi ce que l'auditeur peut entendre.
Pour héberger votre serveur, il vous suffira de rentrer les paramètres demandés.
Le mot de passe et port demandés sont bien ceux qu'il faudra transmettre au client.
Une fois le serveur démarré, une fenêtre s'ouvrira vers la page de gestion de votre serveur, vous permettant de contrôler sont statut.
- Mon audio ne joue pas !
→ Assurez vous d'avoir démarré JACK, via qjackctl.
- J'héberge le serveur IceCast moi-même, mais je n'arrive pas à m'y connecter !
→ Assurez vous d'avoir correctement insérer l'adresse et le port de votre serveur.
Il est également possible qu'IceCast tourne en fond. Pour régler cela, vous pouvez faire :
/etc/init.d/icecast2 stop
[AVERTISSEMENT]
Les solutions présentées ci-dessous ne sont absolument pas les meilleures pratiques possible, mais les ébauches d'un débutant.
Une grande partie des ces méthodes pourraient être améliorées.
Veillez donc à rester critique, et à garder votre propre vision des choses. Merci.
Cette section consiste à expliquer en plus de détails les changements effectué au code
Note : IceStream à été conçu avec Qt Creator et Qt Designer, compilé sous qt4.8.
Serveur
Un guide des plus complet peut être trouvé ici.
Client
La documentation du fichier de config Liquidsoap peut être trouvé ici.
Exemple :
set("log.stdout", false) set("log.file", true) set("log.file.path", "/tmp/liquidsoap.log") set("log.level", 3) set("frame.audio.size", 2048) full = input.jack(id="liquidsoap") output.icecast(%vorbis(samplerate=96000, channels=1 , quality=0.5), host="monhost.com", port=XXXX, password="monpass", mount="monnomdestream", name="test", description="test stream", genre="Etcetera", full)
Creation d'un fichier de config Client/Server
La création d'un fichier de config ce sont en utilisant QFile.
La configuration d'IceCast devant être sous format XML, il est sage d'utiliser QXmlStreamWriter, qui permet l'écriture de contenu XML très simplement.
La configuration de Liquidsoap devant être en Plain Text, QTextStream sera utiliser.
void CreateLiquidConfig() { QFile file(clientFilePath); file.open(QFile::WriteOnly | QFile::Text); QTextStream stream(&file); stream.setCodec("ISO 8859-1"); stream << "set(\"log.stdout\", false)" << endl; ... file.close(); ---------------------------------------------------------------------- QXmlStreamWriter writer(&file); writer.setCodec("ISO 8859-1"); writer.setAutoFormatting(true); writer.writeStartDocument(); writer.writeStartElement("icecast"); writer.writeEndElement(); ... file.close();
IceCast(1) étant obsolète, il a fallu passer à IceCast2.
Au niveau code, il a simplement fallu remplacer un mot-clef. Le fichier de configuration est lui resté le même.
void launchIceCast() { [...] serverStreamCommande = "icecast2"; [...] }
Une vue Web (comme un explorateur internet) vers la page d'admin du serveur à été ajouté.
Pour cela on utilise un objet QWebView, placé dans l'UI via Qt Designer.
À la confirmation du bon lancement du serveur, on ouvre la vue, en lui donnant l'adresse vers où pointé :
Ici un timer est utilisé pour donner un peu de temps au serveur de s'initialiser correctement. Tenter d'afficher la vue trop rapidement est voué à l'échec.
[ Après lancement ] QTimer::singleShot(1000, this, SLOT(slotOpenAdminView())); void slotOpenAdminView(){ ui->iceWebView->load(iceAdminUrl); ui->iceWebView->show(); }
Le but était d'avoir un œil sur le statut d'IceCast et de LiquidSoap
Les fichiers de logs se trouvent dans /tmp/ (voir configuration server/client)
Un QFileSystemWatcher est utilisé ici afin d'obtenir un signal à chaque écriture dans le fichier
liquidsoap_watcher = new QFileSystemWatcher(this); liquidsoap_watcher->addPath("/tmp/liquidsoap.log"); connect(liquidsoap_watcher,SIGNAL(fileChanged(QString)),SLOT(slotLiquidlog(QString))); slotLiquidlog(QString filepath) { QString log = getLogs(filepath); if((log != lastLiquidlog) && !(log.contains(lastLiquidlog))){ ui->liquidsoap_logWindow->append(log); lastLiquidlog = log ; } }
Avec une fonction permettant de parser les nouvelles lignes ajoutées
getLogs(QString path){ QFile file(path); QString log; QString lastlog[3]; if (file.open(QIODevice::ReadOnly | QIODevice::Text)){ file.seek(file.size()-1); for (int i=0;(i<=1) && (file.pos() > 0);) { QString token = file.read(1); file.seek(file.pos()-2); if (token == "\n"){ i++; } } log = file.readAll(); file.close(); log.remove(0, 2); log.remove(log.size()-1, 1); // Fix pour une erreur dans la lecture } return log; }
Note : Cette méthode complète est loin d'être parfaite, et devrait être amélioré si vous étiez à vous inspirer des ces snippets.
Les applications étant lancées en tant que QProcess, le statut (ON/OFF) est obtenue grâce aux changements du QProcess::ProcessState tel que :
connect(streamProcess,SIGNAL(stateChanged(QProcess::ProcessState)),SLOT(slotstreamProcess_StateChanged(QProcess::ProcessState))); void slotstreamProcess_StateChanged(QProcess::ProcessState i) { if(i == 0){ ui->streamStatus->setIcon(QIcon(ledred)); } // OFF else { ui->streamStatus->setIcon(QIcon(ledgreen)); } // ON }
La documentation sur les formats audio se trouve ici.
La configuration ayant besoin d'être donné à Liquidsoap concernant le format diffère de format à format.
// Exemple avec ogg output_setting = "output.icecast(%vorbis(samplerate=" + ui->comboBox_SampleRate_ogg->currentText() + ", channels=" + ui->comboBox_Channels_ogg->currentText() + " , quality=" + ui->comboBox_Quality_ogg->currentText() + "),";
La ligne correspondante au format doit pouvoir être interchangeable si l'on veut pouvoir utiliser plusieurs formats.
void audioFormatSettingsPicker(int i){ QString output_setting; switch (i) { case 0: // ogg output_setting = "output.icecast(%vorbis(samplerate=" + ui->comboBox_SampleRate_ogg->currentText() + ", channels=" + ui->comboBox_Channels_ogg->currentText() + " , quality=" + ui->comboBox_Quality_ogg->currentText() + "),"; break; case 1: // mp3 output_setting = "output.icecast(%mp3.vbr(samplerate=" + ui->comboBox_SampleRate_mp3->currentText() + ", quality=" + ui->comboBox_Quality_mp3->currentText() + ", " + ui->comboBox_StereoMode_mp3->currentText() + "),"; break;
Il faut également que l'utilisateur puisse sélectionner ce format et ses paramètres. Dans le cas d'IceStream, les paramètres de chaque formats sont contenu dans leurs propres QFrame.
C'est frame sont ensuite cachées et affichées selon le format sélectionné.
connect(ui->comboBox_Format, SIGNAL(currentIndexChanged(int)),SLOT(audioFormatFrameHandler(int))); void audioFormatFrameHandler(int i){ switch (i) { case 0: ui->audioFrame_mp3->hide(); ui->audioFrame_mp3->setEnabled(0); ui->audioFrame_ogg->setEnabled(1); ui->audioFrame_ogg->show(); break; case 1: ui->audioFrame_ogg->hide(); ui->audioFrame_ogg->setEnabled(0); ui->audioFrame_mp3->setEnabled(1); ui->audioFrame_mp3->show(); break;
Liquidsoap est un client puissant, offrant plus que la possibilité d'envoyer un flux.
Lors de la création du fichier de configuration, on utilisera “checkState()” de QCheckBox afin de savoir si l'utilisateur à choisi cette option.
void createLiquidConfig() { QFile file(clientFilePath); // Ouverture du fichier en écriture et en texte. file.open(QFile::WriteOnly | QFile::Text); QTextStream stream(&file); [...] if(ui->optionStreamDump->checkState()){ stream << "output.file(%wav(stereo=true, channels=2, samplesize=16, header=false)," << endl; stream << "'dump/%d-%m-%Y-%Hh%M.wav'," << endl; stream << "fallible=true," << endl; stream << "reopen_on_metadata=false," << endl; stream << "on_stop=shutdown," << endl; stream << "dump)" << endl; }
Un QProcess permet de lancer une application/commande, et de lui attacher un objet.
L'exemple ci-dessous correspond à une version simplifié au lancement du vu-mètre d'IceStream.
QProcess à besoin du nom de l'application/commande quand une QString, ainsi qu'une QStringList contenant tout possible argument que désirez passer à votre application/commande.
void startVuMeter(){ QStringList arguments; QString output = "MPlayer [%1]"; arguments << "-t" << "dpm" << output_one.arg(playaProcess->pid())); vuMeterProcess->start("meterbridge", arguments); }
Note : Ici est également démontré l'utilisation de la fonction .arg de QString, remplacant “%1”, par la valeur donnée.
Exemple : La trackbar
Elle consiste d'une simple QProgressBar, personnalisé pour mieux s'accorder au concept d'une trackbar.
Le snippet si dessous correspond à la stylesheet utilisé pour IceStream. La syntax est dans l'esprit du CSS.
QProgressBar::chunk { background-color:rgb(57, 119, 163); margin-left: 1px; margin-right: 1px; border: 1px solid rgb(110, 95, 100); border-radius: 3px; }
La position de la piste est obtenu en envoyant à MPlayer la commande “get_time_pos” toutes les secondes.
poller = new QTimer(this); poller->start(1000); connect(poller, SIGNAL(timeout()), this, SLOT(slotpollCurrentTime()));
La réponse d'MPlayer est alors “ANS_TIME_POSITION=XX.x”. Il suffit donc d'extraire cette valeur et de mettre à jour la barre.
connect(lecteurProcess, SIGNAL(readyReadStandardOutput()), this, SLOT(slotprocessMessage())); void slotprocessMessage(){ while (lecteurProcess->canReadLine()) { buffer.remove(0, 18); // Retire "ANS_TIME_POSITION=" currentTime = buffer.toFloat(); trackBar->setValue(static_cast<int>(currentTime)); } }
Exemple : Le volume
Le principe est le même que pour la trackbar, mais cet fois-ci pas besoin de lire le stdout.
Le petit détail ennuyant avec MPlayer est qu'il est impossible de lui donner une valeur à affecter au volume.
Il faut donc lui envoyer la commande “volume +” ou “volume -” le nombre de fois correspondant au volume désiré.
Lorsque le volume est modifié par l'utilisateur, une fonction doit être appelé, qui gère quelle instructions donner à MPlayer.
void setVolume(int i) { int val = (i-volumeAncienneValeur); QString lecteurVolume; if((i-volAncienneValeur) >= 0){ for (int n=0;n<=val;n++) { lecteurVolume= "volume +1\n"; char *volumeProc=playerVolume.toAscii().data(); lecteurProcess->write(volumeProc); } } else { for (int n=0;n>=val;val++) { playerVolume = "volume -1\n"; char *volumeProc=playerVolume.toAscii().data(); lecteurProcess->write(volumeProc); } } volAncienneValeur=slider->sliderPosition();
Pour éviter que l'utilisateur ait à remplir les champs de l'UI à chaque démarrage du logiciel, il est totalement possible de sauvegarder les valeurs de ces champs pendant une session, et de les réutiliser.
Pour créer le fichier de config, on utilise le même principe que pour la config de Liquidsoap, c'est-à-dire avec QXmlStreamWriter.
void makeConfig(QString chemin){ QFile file(chemin); file.open(QFile::WriteOnly | QFile::Text); QTextStream outStream(&file); outStream << "server_port = " << ui->Line_Local_Port->text() << endl; outStream << "server_pass = " << ui->Line_Local_Pass->text() << endl; [...] file.close;
Cela produira un fichier contenant, ligne par ligne, les nom & valeurs que vous lui avez donné.
Note: N'oubliez pas de convertir vos types speciaux, tel qu'un Booléan, car ils pourraient mal se traduire en valeur brut.
Maintenant, il faut lire ces valeurs, et les réattribués à leurs champs respectifs.
Ici, une QMap composé de deux QString nous simplifie grandement la tâche.
Le but est de lire l'intégralité du fichier, et dans ranger dans la QMap, chaque nom et sa valeur, ligne par ligne.
Puis on finit par remplir les champs en utilisant “settingsMap[”nomdemavaleur“]”.
QMap<QString, QString> settingsMap; void getConfig(QString chemin) { QFile file(chemin); QString current_line; file.open(QFile::ReadOnly | QFile::Text); QTextStream outStream(&file); while(!outStream.atEnd()){ std::string a = outStream.readLine().toStdString(); size_t pos = a.find('='); QString key = QString::fromStdString(a.substr(0, pos - 1)); QString value = QString::fromStdString(a.substr(pos + 2)); settingsMap[key] = value; } ui->Line_Local_Port->setText(settingsMap["server_port"]); ui->Line_Local_Pass->setText(settingsMap["server_pass"]); [...] file.close;
Pour l'installer télécharger les sources : IceStream
Les dépendances :
sudo apt-get install mplayer qjackctl libvorbis-dev libxml2-dev libxslt1-dev qt4-qmake g++ libqt4-dev
Puis les paquets en root :
sudo su tar xvzf IceStream.tar.gz cd IceStream/ dpkg -i libshout_2.2-1_i386.deb dpkg -i libtheora_1.2-1_i386.deb dpkg -i icecast-2.3.2_3-1_i386.deb dpkg -i ices-2.0_4-1_i386.deb cd IHMB/ qmake -project qmake . make sudo cp IHMB /usr/local/bin/icestream
Vous le lancer comme ceci en utilisateur simple (id NOT sudo):
icestream
Pour l'utilisation vous pouvez suivre ce tuto Enjoy !