User Tools

Site Tools


tuto_icestream

Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Both sides previous revisionPrevious revision
tuto_icestream [2020/02/26 20:59] – [IceStream v2.1-2 : cooking - installation et utilisation] raven14tuto_icestream [2020/03/03 09:12] (current) raven14
Line 1: Line 1:
 +====== IceStream ======
 +
 +Icestream est un logiciel de stream audio qui permet de mixer, recevoir ou envoyer des flux audio à travers un réseau local ou Internet.
 +
 +
 +==== IceStream v2.1-2 : cooking - installation et utilisation ====
 +
 +=== 1 - La mise à jour du logiciel ===
 +
 +Les nouvelles fonctionnalités et changements :
 + 
 +General
 +  * Les paramètres peuvent désormais être sauvegardé et chargé, ainsi qu'importé et exporté
 +  * Amélioration de la gestion des chemins
 +Lecteur
 +  * Ajout d'un vu-Mètre rudimentaire
 +  * Ajout d'une trackbar non interactive
 +  * Fix du contrôle de volume
 +  * Fix du comportement du lecteur
 +  * Amélioration général de la lisibilité générale
 +Client
 +  * Transition d'ices à Liquidsoap
 +  * Ajout du support pour le format mp3
 +  * Ajout d'une supervision des logs et statut du client
 +  * Ajout d'une option de sauvegarde du stream en local
 +  * Ajout d'une option de duplication du stream en basse qualité
 +Server
 +  * Mise à jour vers IceCast2
 +  * Ajout d'une vue Web vers le serveur
 +  * Ajout d'une supervision des logs et statut du client
 +
 +
 +=== 2 - Installation How-To ===
 +
 +== [RECOMMENDÉ] Par package : ==
 +
 +L'installation la plus facile se fait à partir du package (.deb) situé [[https://sourceforge.net/projects/icestream/files/|ici]].
 +
 +Double-clicker (//ou click-droit -> Installer logiciel//) le fichier installera Icestream, ainsi que ces dépendances.
 +
 +== [ALTERNATIVE] ==
 +
 +Vous pouvez également installer le package via dpkg, puis apt pour les dépendances :
 +<code>
 +dpkg -i ~/chemin/vers/icestream_x.x-x_x.deb
 +apt -f install
 +</code>
 +
 +== À partir des sources :==
 +
 +Si vous souhaitez compiler directement IceStream, vous trouverez ce dont vous avez besoin [[https://sourceforge.net/p/icestream/code/ci/master/tree/|ici]].
 +N'oubliez pas d'installer les dépendances nécessaire :
 +<code>
 +apt install debhelper-compat, libvorbis-dev, libxml2-dev, libxslt1-dev, qt4-qmake, g++, libqt4-dev, libqtwebkit4
 +</code>
 +
 +=== 3 - Comment utiliser Icestream ===
 +
 +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.
 +
 +
 +== [ FAQ ] ==
 +
 +- 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 :
 +<code>/etc/init.d/icecast2 stop</code>
 +
 +
 +===4 - Détails===
 +[**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.\\ 
 +
 +==Fichiers de configurations Client/Serveur==
 +
 +__Serveur__\\ 
 +Un guide des plus complet peut être trouvé [[https://people.xiph.org/~epirat/Icecast/Docs/config_file/|ici]].\\ 
 +__Client__\\ 
 +La documentation du fichier de config Liquidsoap peut être trouvé [[https://www.liquidsoap.info/doc-dev/settings.html|ici]].\\ 
 +Exemple :
 +<code>
 +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)
 +</code>
 +
 +__Creation d'un fichier de config Client/Server__
 +La création d'un fichier de config ce sont en utilisant [[https://doc.qt.io/archives/qt-4.8/qfile.html|QFile]].\\ 
 +La configuration d'IceCast devant être sous format XML, il est sage d'utiliser [[https://doc.qt.io/archives/qt-4.8/qtextstream.html|QXmlStreamWriter]], qui permet l'écriture de contenu XML très simplement.\\ 
 +La configuration de Liquidsoap devant être en Plain Text, [[https://doc.qt.io/archives/qt-4.8/qtextstream.html|QTextStream]] sera utiliser.
 +
 +<code>
 +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();
 +</code>
 +
 +
 +
 +== Mise à jour IceCast2 ==
 +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.
 +<code>
 +void launchIceCast()
 +{
 +[...]
 +    serverStreamCommande = "icecast2";
 +[...]
 +}
 +</code>
 +
 +==La vue Web==
 +Une vue Web (comme un explorateur internet) vers la page d'admin du serveur à été ajouté.
 +
 +Pour cela on utilise un objet [[https://doc.qt.io/archives/qt-4.8/qwebview.html|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 [[https://doc.qt.io/archives/qt-4.8/qtimer.html|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.
 +
 +<code>
 +[ Après lancement ]
 +QTimer::singleShot(1000, this, SLOT(slotOpenAdminView()));
 +
 +void slotOpenAdminView(){
 +    ui->iceWebView->load(iceAdminUrl);
 +    ui->iceWebView->show();
 +}
 +</code>
 +
 +
 +
 +==Supervision des logs et statut d'un QProcess==
 +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 [[https://doc.qt.io/archives/qt-4.8/qfilesystemwatcher.html|QFileSystemWatcher]] est utilisé ici afin d'obtenir un signal à chaque écriture dans le fichier 
 +<code>
 +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 ;
 +    }
 +}
 +</code>
 +Avec une fonction permettant de parser les nouvelles lignes ajoutées
 +<code>
 +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; }
 +</code>
 +**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 [[https://doc.qt.io/archives/qt-4.8/qprocess.html|QProcess]], le statut (ON/OFF) est obtenue grâce aux changements du [[https://doc.qt.io/archives/qt-4.8/qprocess.html#stateChanged|QProcess::ProcessState]] tel que :
 +<code>
 +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
 +}
 +</code>
 +
 +==Ajout d'un format audio (ici mp3)==
 +La documentation sur les formats audio se trouve [[https://www.liquidsoap.info/doc-dev/encoding_formats.html|ici]].\\ 
 +La configuration ayant besoin d'être donné à Liquidsoap concernant le format diffère de format à format.\\ 
 +<code>
 +// 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() + "),";
 +</code>
 +La ligne correspondante au format doit pouvoir être interchangeable si l'on veut pouvoir utiliser plusieurs formats.
 +<code>
 +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;
 +</code>
 +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é.
 +<code>
 +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;
 +</code>
 +
 +==Ajout d'une option Liquidsoap==
 +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 [[https://doc.qt.io/archives/qt-4.8/qcheckbox.html|QCheckBox]] afin de savoir si l'utilisateur à choisi cette option.
 +<code>
 +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;
 +    }
 +</code>
 +
 +
 +==Utilisation de QProcess==
 +Un [[https://doc.qt.io/archives/qt-4.8/qprocess.html|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 [[https://doc.qt.io/archives/qt-4.8/qstring.html|QString]], ainsi qu'une [[https://doc.qt.io/archives/qt-4.8/qstringlist.html|QStringList]] contenant tout possible argument que désirez passer à votre application/commande.
 +<code>
 +void startVuMeter(){
 +    QStringList arguments;
 +    QString output = "MPlayer [%1]";
 +    arguments << "-t" << "dpm" << output_one.arg(playaProcess->pid()));
 +    vuMeterProcess->start("meterbridge", arguments);
 +}
 +</code>
 +Note : Ici est également démontré l'utilisation de la fonction [[https://doc.qt.io/archives/qt-4.8/qstring.html#arg|.arg]] de QString, remplacant "%1", par la valeur donnée.
 +
 +
 +==Le stdin & stdout de MPlayer==
 +__Exemple : La trackbar__\\ 
 +Elle consiste d'une simple [[https://doc.qt.io/archives/qt-4.8/qprogressbar.html|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.
 +
 +<code>
 +QProgressBar::chunk {
 + background-color:rgb(57, 119, 163);
 + margin-left: 1px;
 + margin-right: 1px;
 +    border: 1px solid rgb(110, 95, 100);
 +    border-radius: 3px;
 +}
 +</code>
 +La position de la piste est obtenu en envoyant à MPlayer la commande "get_time_pos" toutes les secondes. 
 +<code>
 +poller = new QTimer(this);
 +poller->start(1000);       
 +               
 +connect(poller, SIGNAL(timeout()), this, SLOT(slotpollCurrentTime()));
 +</code>
 +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.
 +<code>
 +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));
 +    }
 +}
 +</code>
 +
 +__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.
 +
 +<code>
 +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();
 +</code>
 +
 +
 +==La sauvegarde des paramètres de l'UI (save, load)==
 +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 [[https://doc.qt.io/archives/qt-4.8/qtextstream.html|QXmlStreamWriter]].
 +<code>
 +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;
 +</code>
 +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 [[https://doc.qt.io/archives/qt-4.8/qmap.html|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//"]".
 +<code>
 +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;
 +
 +</code>
 +==== Installation Icestream v1.2 : ====
 +
 +Pour l'installer télécharger les sources :
 +[[http://apo33.org/pub/icestream/IceStream.tar.gz|IceStream ]]
 +
 +
 +Les dépendances :
 +
 +<code>sudo apt-get install mplayer qjackctl libvorbis-dev libxml2-dev libxslt1-dev qt4-qmake g++ libqt4-dev</code>
 +
 +Puis les paquets en root :
 +
 +<code>
 +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
 +</code>
 +
 +==== Utilisation : ====
 +Vous le lancer comme ceci en utilisateur ** simple** (id NOT sudo):
 +<code>icestream</code>
 +
 +Pour l'utilisation vous pouvez suivre  {{tutoicestream.pdf|ce tuto}}
 +Enjoy !
 +
  
tuto_icestream.txt · Last modified: 2020/03/03 09:12 by raven14