Utiliser la reconnaissance vocale de Google

Publié le 18/05/2013

Et si on se faisait une petit application de reconnaissance vocale maison qui marche sur le desktop ? Je recherche depuis des mois, voir des années, une solution sous Linux qui me permettrait de faire correctement de la reconnaissance vocale proprement. Mais mes recherches n’ont jamais été fructueuses, que ce soit du coté de CMU Sphinx ou je ne sais quel “Perlbox”… Mais voilà, Google propose sur Android, et depuis peu (quelques mois en fait) une api sur Chromium/Chrome qui fonctionne vraiment bien.

Alors avant toutes choses: très important, utiliser l’api de Google hors Android et hors navigateur semble ne pas être autorisé. Si vous utilisez mes exemples, vous êtes responsables de vos actes en cas de souci avec la loi. Je ne vous propose cet article qu’à des fins de recherche.

Relisez bien ce que je viens de taper… qu’on soit bien clair, ce que je vous propose est utile à des fins de tests jusqu’à ce que Google nous autorise un jour (on y croit…) à faire mumuse sans restriction avec leur API… ou (rêvons un peu) qu’il rende tout ça publique.

On passe à la suite. Comment ça fonctionne ?

Si vous visitez http://translate.google.com vous remarquerez une icone de micro en bas du textarea de texte à traduire. C’est une nouvelle implémentation qui permet de proposer de dicter le texte via le micro plutôt que de taper à la main le texte. Ceci est possible depuis l’introduction d’un attribut “x-speech” dans les inputs et textareas. Une très bonne explication se trouve sur la page: http://tilap.net/champs-de-saisie-vocale-en-html5-x-webkit-speech/

Lorsque vous pressez le bouton, le navigateur enregistre votre voix. L’encodage est FLAC (voilà une idée géniale) à 16kHz. Puis il le poste sur la page:

https://www.google.com/speech-api/v1/recognize

Ne cherchez pas à ouvrir la page depuis votre navigateur, cette adresse n’accèpte pas les requêtes GET. Bref, cette adresse prend aussi des arguments, comme la langue source, le nombre de résultats attendus, etc…

Pour préparer notre petit programme de reconnaissance, on va utiliser:

  • Gstreamer pour capturer votre voix
  • curl pour poster tout ça à Google

Gstreamer n’est pas plus simple, mais il a le mérite de ne pas me prendre la tête pour le choix de la source d’enregistrement.

Pour le pipe Gstreamer, on va utiliser:

  • autoaudiosrc pour laisser pulse nous donner la source (donc vérifiez vos paramètres pour vérifier que votre micro fonctionne)
  • audioresample pour rééchantillonner le fichier proprement
  • utiliser la capacité audio/x-raw-int pour demander un “rate” de 16kHz (16000 hz)
  • pour le moment: envoyer ça dans un fichier .flac

C’est parti, dans un terminal:

gst-launch autoaudiosrc ! audioresample ! audio/x-raw-int,rate=16000 ! \
flacenc ! filesink location=/tmp/voice.flac

Dites quelques mots, pas trop long, puis presssez CTRL+C. Vérifiez votre fichier en l’écoutant avec mplayer, vlc, ou la commande “play” pour vous assurer que vous êtes audible et qu’il n’y a pas trop de bruit.

Maintenant, on va l’envoyer à Google pour voir si il reconnait correctement la voix.

L’idée c’est qu’il faut envoyer les informations nécessaires pour construire une requête POST bien propre. A savoir donc:

  • le Content-Type qui est un audio/x-flac; rate=16000
  • les données binaires (le fichier de son)
  • un argument pour lui dire qui est le client, je vais mettre chromium
  • un argument pour lui dire que je veux au pire 10 résultats de reconnaissance

Donc la commande “curl” est la suivante:

curl -X POST --data-binary @/tmp/voice.flac \
--header 'Content-type: audio/x-flac; rate=16000' \
'https://www.google.com/speech-api/v1/recognize?client=chromium&lang=fr-FR&maxresults=10'

Cela me retourne (j’ai reformaté pour plus de lisibilité):

{
"status": 0,
"id": "cf3c79ce01920853aedc0825f33ac0ea-1",
"hypotheses": [
    {
        "utterance": "bonjour et bienvenue",
        "confidence": 0.7474387
    },
    {
        "utterance": "bonjour et bienvenu"
    },
    {
        "utterance": "bonjour bienvenue"
    },
    {
        "utterance": "bonjour et bienvenue à"
    },
    {
        "utterance": "bonjour et bienvenue chez"
    },
    {
        "utterance": "bonjour et bienvenue au"
    }
    ]
}

C’est donc au format JSON que la réponse est faite (plutôt pratique). Vous remarquez que ce sont les “hypothèses” de reconnaissance qui vont nous intéresser et que nous avons quelques possibilité avec des points (confidence) qui oscillent de 0.0 à 1.0. Si vous tentiez sans le “maxresults”, vous n’auriez en réponse que l’hypothèse la plus haute:

curl -X POST --data-binary @/tmp/voice.flac \
--header 'Content-Type: audio/x-flac; rate=16000;' \
'https://www.google.com/speech-api/v1/recognize?client=chromium&lang=fr-FR'

{
"status": 0,
"id": "06eee2607513b6f23081891d051736bc-1",
"hypotheses": [
    {
        "utterance": "bonjour et bienvenue",
        "confidence": 0.7474387
    }
    ]
}

Bon me reste une ou deux choses à faire, notamment faire en sorte que l’enregistrement de ma voix s’arrête quand je ne parle plus… ha ça c’est un truc que j’ai gratté un moment, mais j’ai fini par trouver !

Il existe un pad gstreamer nommé “vader”. Ce pad est magique, il déclenche la lecture du pipeline gstreamer quand le niveau d’enregistrement monte, puis il met en pause quand vous ne parlez plus. Pour tester, voici ce que vous pouvez faire:

gst-launch autoaudiosrc ! vader auto_threshold=true ! audioconvert ! \
audioresample ! audio/x-raw-int,rate=16000 ! \
flacenc ! filesink location=/tmp/tmpaudio.flac

Alors, attendez… pour couper le pipeline vous allez devoir presser CTRL+C… sauf que vader va peut-être l’entendre donc faites doucement.

Commencez à parler, dites “un deux trois” par exemple. Attendez une dizaine de secondes puis dites “quatre cinq”. Pesses calmement CTRL+C et écoutez le fichier /tmp/tmpaudio.flac. Vous allez vous rendre compte que la dizaine de secondes de pause a disparue. C’est tout le but, l’encodage ne s’est fait que lorque vous parliez.

Voilà, on approche du but:

  • on sait enregistrer des phrases au moment où on parle
  • on sait envoyer un fichier à Google qui peut être utilisé pour de la reconnaissance

Hé, dites… ça vous dirait de faire un petit programme qui écrit ce que vous dites ? Python est un petit bijou pour ça.

Je vous explique pas à pas.

On va utiliser le module “gst” qui est le module gstreamer de python. Ce module a une méthode bien pratique qui permet de donner le pipeline à la manière de gst-launch plutôt que de s’entêter à faire les pad un à un et de les raccorder.

Petit rappel, les pads, ainsi que le pipeline gstreamer, envoit des évènements. On regarde ce que donne le pad “vader”:

gst-inspect vader
...
Element Signals:
   "vader-start" :  void user_function (GstElement* object,
                                   guint64 arg0,
                                   gpointer user_data);
   "vader-stop" :  void user_function (GstElement* object,
                                  guint64 arg0,
                                  gpointer user_data);

Deux évènements à retenir: vader-start et vader-stop… inutile de vous dire à quoi il servent.

Ensuite, en ce qui concerne python et gst, on peut mettre en pause ou en état de démarrage le pipeline via la méthode set_state() en lui donnant en argument gst.STATE_PAUSED ou gst.STATE_PLAYING

On va utiliser urllib2 pour envoyer les données à Google.

Et enfin, pour que le programme écoute en continue sans couper le script, on va utiliser le module gtk pour utiliser la boucle itérative “gtk.main()”

L’algo est le suivant:

on crée le pipeline en nommant le pad vader "vad" (afin de le récupérer)
on écoute les évènement du pad vader
quand vader stop son écoute:
    on met en pause tout le pipeline pour ne pas relancer l'écoute tout de suite
    on copie le fichier flac en mémoire
    on vide le fichier flac pour ne pas cumuler nos phrases
    on remet le pipeline en écoute
    on envoit la requête à Google
    si ça a marché, on affiche la meilleures hypothèse de reconnaissance

Voilà… et bien lisez un peu le source:

import gst
import gtk
import urllib2
import json
import logging
import os

# file where we record our voice (removed at end)
FLACFILE='/tmp/tmpvoice.flac'

#to be clean on logs
logging.getLogger().setLevel(logging.DEBUG)

def on_vader_start(ob, message):
    """ Just to be sure that vader has reconnized that you're speaking
    we set a trace """
    logging.debug("Listening...")

def on_vader_stop(ob, message):
    """ This function is launched when vader stopped to listen
    That happend when you stop to talk """

    logging.debug("Processing...")

    # pause pipeline to not break our file
    pipe.set_state(gst.STATE_PAUSED)

    # get content of the file
    flacfile = open(FLACFILE, 'r').read()

    # empty file, to not reprocess
    # next time
    open(FLACFILE, 'w').write('')

    #file is empty, continue to listen
    pipe.set_state(gst.STATE_PLAYING)

    try:
        # hey, Google ! what did I said ?
        req = urllib2.Request('https://www.google.com/speech-api/v1/'
                              'recognize?client=chromium&lang=fr-FR&maxresults=10',
                flacfile, {'Content-Type': 'audio/x-flac; rate=16000'})
        res = urllib2.urlopen(req)
        # thanks google...
        resp = res.read()
        resp = json.loads(resp)
        print resp['hypotheses'][0]['utterance']
    except:
        logging.error("An error occured...")



#the main pipeline
pipe = gst.parse_launch('autoaudiosrc ! vader auto_threshold=true name=vad '
                        '! audioconvert ! audioresample ! '
                        'audio/x-raw-int,rate=16000 ! flacenc ! '
                        'filesink location=%s' % FLACFILE)

bus = pipe.get_bus()
bus.add_signal_watch()

vader = pipe.get_by_name('vad')
vader.connect('vader-start', on_vader_start)
vader.connect('vader-stop', on_vader_stop)

try:
    # start the pipeline now
    pipe.set_state(gst.STATE_PLAYING)
    logging.info("Press CTRL+C to stop")
    gtk.main()

except KeyboardInterrupt:
    # stop pipeline 
    pipe.set_state(gst.STATE_NULL)
    # remove our flac file
    os.remove(FLACFILE)

Voici un exemple de choses que j’ai put voir en lançant mon script python:

$ python voice.py 
** Message: pygobject_register_sinkfunc is deprecated (GstObject)
INFO:root:Press CTRL+C to stop
DEBUG:root:Listening...
DEBUG:root:Processing...
ceci est un exemple de reconnaissance vocale
DEBUG:root:Listening...
DEBUG:root:Processing...
comme vous pouvez voir cela fonctionne plutôt pas mal

Bon il est clair qu’il faudra certainement régler le niveau du micro pour pas tout enregistrer tout le temps, voir créer un raccourcis qui lance cette reconnaissance one shot… et puis pourquoi pas se faire des jeux de commandes qui peuvent executer des actions…

En gros, tout est possible, mais encore une fois: Google n’a pas donné explicitement son accord pour utiliser leur outil de reconnaissance vocale comme ça… alors allez-y molo avec ce jouet - c’est purement un exemple à titre de recherche !

Petite parenthèse: j’ai pas mal testé pocketsphinx, y’a bien un moment où je vais réussir à en tirer un truc plus propre… mais rien n’a égalé ce que j’ai réussi à faire via Google…

comments powered by Disqus