La configuration audio

Malheureusement la gestion du son sous linux est particulièrement pénible et surtout très compliquée. Cet aspect de l'application RadioK est celui qui demande le plus d'efforts pour obtenir un résultat satisfaisant. La contrainte à respecter pour établir la configuration audio était de rester le plus simple possible tout en permettant de gérer des haut-parleurs ou des casques, différents types de micro, branchés en usb ou non et de rester homogène sur la raspberry et sur des desktops. Pour tenir cette contrainte, sans s'arracher les cheveux, on ne peut guère faire l'économie de la lecture et de la compréhension de la documentation de base sur le sujet.

La première chose à faire consiste à vérifier que la sortie audio fonctionne correctement sur la rpi. On peut tester par exemple dans l'ordre :


aplay /usr/share/sounds/purple/login.wav
speaker-test -c2 -twav
mplayer un_fichier.mp3
Si tout va bien on devrait entendre des sons. On peut (on doit ?) googler quelquechose comme raspberry audio pour trouver de la doc. Une des pages la moins inutile est celle-ci.

Il faut également vérifier que l'entrée audio est correctement prise en compte. Pour ce faire on peut utiliser ce genre de commande :

arecord -d 10 -t wav ma_voix.wav
En parlant pendant 10 secondes devant le micro on fabrique ainsi un fichier ma_voix.wav que l'on doit pourvoir écouter ensuite avec aplay.

Pour régler les niveaux de sortie et d'entrée on peut utiliser la commande alsamixer(1) depuis un terminal xterm. L'interface est des plus frustes mais elle permet de définir les volumes des haut-parleurs et du micro.

La solution pour avoir du son sur les haut-parleurs sans utiliser la sortie hdmi et pour pouvoir brancher un micro sur la rpi consiste à utiliser une carte son comme la Logilink 250743. L'installation ne pose guère de problème car le module driver est disponible dans les distributions linux.

La difficulté consiste à bien identifier le nom du control pour modifier le volume de sortie et utiliser le même nom - PCM - sur différents matériels de manière à avoir les scripts pour trouver la valeur du volume ou pour la changer qui soient identiques sur différentes machines.
Ces scripts qui encapsulent amixer get PCM et amixer set PCM sont ainsi portables sans modification. Mais sur la rpi avec certaines cartes son il arrive que PCM n'existe pas. Il faut alors créer un fichier ~/.asoundrc pour configurer la bibliothèque alsa. La syntaxe des fichiers de configuration alsa est particulièrement imbittable. Ce fichier produit l'effet escompté :

pcm.softvol {
  type softvol
  slave {
     pcm "cards.pcm.default"
  }
  control {
     name  "PCM"
     card  0
  }
}
pcm.!default {
  type  plug
  slave.pcm "softvol"
}
            
Le contenu de ce fichier est décrit sur cette page.

La première implémentation

Une première implémentation a été expérimentée. Elle se base sur les routines disponibles dans la bibliothèque pocket sphinx. Le programme réalisé s'exécute localement sur le processeur de la machine, et il fonctionne en tentant de déchiffrer des mots prononcés en anglais.

Le principe de fonctionnement est le suivant : une liste de mots à reconnaître est préparée, une fois compilée elle est fournie en paramètre du programme de reconnaissance vocale. Celui-ci prend biensûr aussi en entrée le signal provenant du micro. Il attend en continu les paroles prononcées devant le micro et génère la transcription en chaîne de caractères des mots reconnus. Ces mots sont transmis au server http qui les utilisent pour commander la radio. Les fichiers de cette implémentation sont dans le répertoire RADIOK_HOME/vox/ps.

La liste des mots est stockée dans le fichier corpus-en.txt. La version actuelle du programme travaille en anglais, il y aura peut-être par la suite une version comprenant le français. La liste proposée contient plus de mots que nécessaire mais ça permet d'expérimenter et de choisir ensuite le vocabulaire le plus efficace. La liste doit être compilée par le progamme lmtool. Les fichiers produits portant des noms comme nnnn.lm et nnnn.dic sont fournis en paramètres du programme de reconnaissance. À chaque fois que le corpus est modifié il faut recompiler la liste. On obtient alors de nouveaux fichiers nnnn.*. Ce nom - nnnn - est assigné à la variable corpus dans les scripts listen.sh et trywords.sh il faut donc les mettre à jour.

Le programme de reconnaissance s'appelle whatusay (what you say ?). Le fichier source est whatusay.c. Il s'agit tout simplement d'une version simplifiée de pocketsphinx. Une grande partie du code qui n'était pas pertinent pour RadioK a été purement et simplement supprimée. Inversement du code nécessaire à la communication avec le server web a été ajouté. Ce code s'appuie sur la bibliothèque curl. Chaque mot décodé est transmis par une requête http au server.

Le programme whatusay accepte un certain nombre d'arguments dont les 2 principaux utilisés pour RadioK sont -adcdev et -url. Le premier -adcdev permet de spécifier le nom du device d'entrée. Là aussi il faut faire un effort pour comprendre quelle valeur mettre. Normalement quelque chose comme hw:n avec n égal à l'indice du device d'entrée (0 ou 1 ou plus) doit marcher. Le deuxième argument, spécifique à RadioK, -url permet de définir l'url du serveur auquel envoyer les commandes interprétées par whatusay.

Pour refabriquer le programme il faut installer cmusphinx et curl puis lancer make dans le répertoire RADIOK_HOME/vox/ps. Le Makefile a été gardé délibérément très simple. Il doit éventuellement être modifié en fonction de la plateforme.

Pour tester le programme simplement sans communiquer avec le serveur web afin de voir comment l'analyse vocale fonctionne il suffit de le lancer avec comme url la chaîne 'null'. Le petit script trywords.sh permet de faire des tests simplement. Une fois lancé il suffit de parler devant le micro. Les mots compris sont affichés sur la sortie standard. Le taux de réussite est assez statisfaisant. L'expérience montre qu'il dépend beaucoup du volume reçu par le micro : en parlant à voix suffisamment haute à une distance comprise entre 10 et 60 cm la plupart des mots sont compris et plus on s'éloigne du micro plus il faut parler fort. Le taux de réussite dépend aussi des mots : plus ils sont long mieux ils sont décodés, le programme fonctionne mieux avec des mots de 3 syllabes qu'avec les monosyllables. Dans l'autre sens le décodage peut se déclencher sur les bruits s'ils sont forts : le fait d'éternuer à 3 mètres du micro peut activer une commande.

Le programme whatusay peut communiquer avec le serveur web qui gère les scripts de contrôle de la radio. L'url du serveur est par default http://localhost:18000 mais peut être spécifiée différemment sur la ligne de commande. On peut donc contrôller l'application en faisant aussi tourner whatusay sur une autre machine que la raspberry. Le programme génère la requête /vox/process/mot pour le serveur à chaque traitement d'une parole, mot étant la transcription de la parole comprise.

Le code du server pour traiter les requêtes de commandes vocales se trouve dans le fichier vox.js dans le sous-répertoire www/kontrol/server. Les mots connus sont regroupés en liste de synonymes, c'est-à-dire conduisant à la même opération. Cela donne une certaine souplesse pour tester différentes expressions. Certains mots sont mieux compris ou plus faciles à prononcer pour un non-anglophone.

Les commandes comprises sont les suivantes:

  • démarrer la radio : music, wake up, begin, radio, play, run, start
  • arrêter la radio : terminate, shut up, silence, cancel, halt, stop, quiet, sleep
  • changer la station : first, last, next, previous
  • choisir la station : zero, one, two, three, four, five, six, seven
  • augmenter le volume : more, louder, plus, higher
  • diminuer le volume : less, softer, minus, lower
Les mots en gras sont les mieux compris et donc les plus utilisés. À noter cependant que cette page de documentation n'est pas forcèment synchronisé avec le contenu du fichier mis sur github.

La méthode app.get extrait le paramètre word et regarde dans quel groupe de synomymes il est présent puis exécute le shell script qui correspond. La reconnaissance vocale est loin d'être fiable à 100% aussi il est important de donner un feed back pour informer l'utilisateur si la commande a été comprise ou non. Ce retour est lui aussi sous forme audio. Des phrases ont été enregistrées et sauvegardées dans des fichiers .wav. Elles sont jouées par la commande aplay(1) après la prise en compte des requêtes vocales.

Un des problèmes à résoudre pour la mise en place de cette fonctionnalité est le réglage des volumes relatifs du son produit par la radio via mplayer et du son de feed back produit par aplay. Pour que ce feed back serve à quelque chose son volume doit être légèrement supérieur à celui de la radio afin d'être entendu distinctement. La solution consiste à éditer les fichiers de feed back à l'aide du programme audacity. Ce programme est très bien fait et très complet. Il permet de créer assez facilement les fichiers son dont on a besoin, de les modifier à sa guise et de régler l'amplitude des signaux enregistrés.

Sur le GitHub de RadioK dans le répertoire sounds on trouve les fichiers son utilisés pour le feed back.

Cette version fonctionne relativement correctement. Les performances sur la raspberry sont acceptables. Il faut en général 2 à 3 secondes pour être compris. Cependant l'utilisation de l'anglais comme langue implique une bonne prononciation, en particulier un effort sur l'accent tonique. Par ailleurs l'application peut se déclencher sur des bruits parasites : il faut mieux éviter d'éternuer trop près du micro et de claquer trop fort une porte.

La seconde implémentation

Une deuxième approche a été tentée. Elle se base sur l'utilisation du moteur de reconnaissance vocale de google qui est disponible en ligne. Avec cette technique on peut utiliser le français comme vocabulaire de commande. On devrait d'ailleurs pouvoir utiliser n'importe quelle langue gérée par google en changeant simplement un paramètre lors du lancement du programme. Cette implémentation utilise des requêtes http pour communiquer avec google, c'est à dire pour envoyer un fichier son et recevoir en retour les chaines de caractères résultant de l'interprétation par le moteur de google.

Le code de cette version se trouve dans le répertoire vox/fr. Le programme final s'appelle command. Les autres programmes ont été écrits pour tester les différentes étapes à coder pour arriver au résultat final. L'intégration des différents morceaux n'a pas été facile car ils sont écrits en utilisant des routines venant de bibliothèques différentes, à la documentation limitée et au style de programmation particulièrement dégoûtant.

La première étape est classique : elle consiste à décoder les options données sur la ligne de commande. Mais en général les paramètres par défaut sont suffisants pour faire fonctionner le programme : on peut le lancer simplement en tapant : command.
En revanche il est indispensable d'avoir défini la variable d'environnement GOOGLE_KEY. La valeur est fournie par Google quand on s'identifie comme utilisateur de l'API - Voir la documentation.

Ensuite on doit initialiser les paramètres de curl pour communiquer et avec google et avec radiok. Il n'y a rien de très compliqué, il suffit de lire la doc de libcurl.

Une fois les initialisations effectuées on entre dans la boucle permanente pour écouter et tenter de reconnaître les paroles prononcées. Pour en sortir on peut dire à haute voix "abandon" ou on peut tout simplement balancer un Control-C.

Au départ de cette boucle on appelle la routine get_utterance() qui détecte les sons entendus. Un son borné par des instants de silence est stocké sous forme brute dans le tableau d'échantillons utterance.

Pour passer ces données son à google il va falloir d'abord les compresser en utilisant un encodage flac. Cet algorithme est adapté aux fichiers son, il compresse les données sans perte. L'ennui est que la bibliothèque disponible pour manipuler les fichier à compresser est particulièrement mal foutue et mal documentée. Après pas mal d'essais je suis finalement parvenu à quelquechose. La routine make_flac_encoder() initialise le traitement puis enflac_utterance() effectue la compression. Le résultat est mis dans un fichier temporaire /tmp/utterance.flac que je n'ai pas réussi à éviter.

La suite du traitement est codé dans la fonction interpret_flac(). Le fichier flac est transmis à google en utilisant curl_easy_setopt() pour passer son contenu en paramètre POST.
La réponse de google est analysée en utilisant la fonction json_parse() qui permet de retrouver les éléments d'un contenu formatté en json. Finalement, si tout s'est bien passé, on obtient un mot en sortie de parse_google_content(). Il correspond à l'interprétation par google de ce qui a été entendu.

Le mot est ensuite passé au serveur de radiok par la routine send_command(). Cette fois aussi on utilise curl pour communiquer. Comme pour la première implémentation le serveur effectue une opération choisie en fonction du mot reçu.

Après plusieurs semaines d'utilisation je constate que cette implémentation marche très bien et vraiment mieux que l'implémentation basée sur un traitement local. En revanche il faut un peu plus de temps pour obtenir le résultat d'un commande.