Android : Reconnaissance et synthèse vocale

Partager cet article

Temps estimé pour la lecture de cet article : 29 min

Aujourd’hui, j’ai envie de parler de reconnaissance vocale et de synthèse vocale. Android, nous propose quelques solutions très intéressantes, plongeons-nous directement dans le vif du sujet.

Reconnaissance vocale

android-voice-search-wave

La première chose que l’on va voir, c’est comment faire de la reconnaissance vocale afin d’ajouter des aides pour la navigation : remplir un formulaire avec la voix par exemple. On va utiliser un intent pour se faire, notamment avec la classe RecognizerIntent. On appelle alors l’action : ACTION_RECOGNIZE_SPEECH. Cette action va créer un prompt affiché à l’utilisateur, que l’on va ensuite récupérer dans la fonction onActivityResult. Attention, cette solution nécessite internet.

Voyons, une petite fonction, qui va permettre de créer l’intent vu précédemment.

private void promptSpeechInput() {
    Intent intent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);
    intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL, RecognizerIntent.LANGUAGE_MODEL_FREE_FORM);
    intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE, Locale.getDefault().getDisplayLanguage());
    intent.putExtra(RecognizerIntent.EXTRA_PROMPT, "Vous pouvez parler ...");
    try {
        startActivityForResult(intent, REQ_CODE_SPEECH_INPUT);
    } catch (ActivityNotFoundException a) {
        Toast.makeText(getApplicationContext(), "Désolé, votre appareil ne supporte pas d'entrée vocale...", Toast.LENGTH_SHORT).show();
    }
}

On ajoute 3 informations en plus pour le prompt :

  • On précise que l’on va utiliser un modèle de langage basé sur la reconnaissance vocale de forme libre. En effet, par défaut google propose deux types de reconnaissances : la forme libre utilisée plus pour la dictation et le modèle internet, utilisé surtout pour les courtes phrases.
  • On précise la langue à reconnaître, ici on l’obtient grâce à la langue choisie par l’utilisateur pour son système.
  • Enfin, le message que l’on va afficher dans le prompt.

Passons maintenant, au résultat obtenu :

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
    switch (requestCode) {
        case REQ_CODE_SPEECH_INPUT: {
            if (resultCode == RESULT_OK && null != data) {
                ArrayList<String> buffer = data.getStringArrayListExtra(RecognizerIntent.EXTRA_RESULTS);
                String result = buffer.get(0);

                Pattern ff = Pattern.compile("Final|fantasy");
                if (ff.matcher(result).find()) {
                    try {
                        AssetFileDescriptor afd = getAssets().openFd("ffx-victory.mp3");
                        MediaPlayer player = new MediaPlayer();
                        player.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength());
                        player.prepare();
                        player.start();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
            break;
        }
        default:
            break;
    }
}

On récupère le résultat de notre appel et on utilise la classe Pattern d’android, pour définir un ensemble de mots que l’on veut détecter. Ici, mon but c’est tout simplement de lancer une musique, si la personne prononce les mots final fantasy.

Pour ce faire, on utilise simplement les assets et la classe MediaPlayer d’Android qui est très simple à utiliser, la documentation étant très complète.

Enfin, on évite que la musique continue de jouer après qu’on est fermée l’application :

@Override
protected void onStop() {
    if(player != null) {
        if(player.isPlaying()) {
            player.stop();
        }
    }
    super.onStop();
}

Si, on joue une chanson, on arrête de la jouer et c’est tout ! Simple comme bonjour.

Remarque : cela suppose qu’on est passé le MediaPlayer en attribut de notre Activité sinon on ne pourrait pas y accéder de cette façon.

Lecture de texte

android-voice-search-reading

Vous êtes maintenant capable de faire de la reconnaissance vocale mais si on faisait l’inverse, c’est-à-dire faire lire du texte à notre smartphone, si on veut se réaliser une application pour lire des livres par exemple. Pour ce faire, Android proprose une classe appelée TextToSpeech.

Nous allons réaliser un objet classe qui aura pour but de faciliter l’utilisation de cette classe, appelons le Speaker :

import java.util.Locale;

import android.content.Context;
import android.media.AudioManager;

import android.speech.tts.TextToSpeech;
import android.speech.tts.TextToSpeech.OnInitListener;

public class Speaker implements OnInitListener {
    private static Locale language = Locale.FRANCE;

    private static TextToSpeech tts;
    private boolean isReady = false;

    public Speaker(Context context){
        tts = new TextToSpeech(context, this);
        tts.setPitch(0.8f);
        tts.setSpeechRate(0.9f);
    }

    @Override
    public void onInit(int status) {
        if(status == TextToSpeech.SUCCESS){
            tts.setLanguage(language);
            isReady = true;
        } else{
            isReady = false;
        }
    }

    public void speak(String text){
        if(isReady) {
            tts.speak(text, TextToSpeech.QUEUE_ADD, null, null);
        }
    }

    public void setSpeedRate(float speechrate) {
        tts.setSpeechRate(speechrate);
    }

    public void setPitchRate(float pitchrate) {
        tts.setPitch(pitchrate);
    }

    public boolean isSpeaking() {
        return tts.isSpeaking();
    }

    public void pause(int duration){
        tts.playSilentUtterance(duration, TextToSpeech.QUEUE_ADD, null);
    }

    public void stop() {
        tts.stop();
    }

    public void destroy() {
        tts.shutdown();
    }
}

Regardons un peu en détail :

  • Nous devons implémenter la méthode onInit car notre classe implémente l’interface OnInitListener. On utilise cette méthode afin de déterminer si notre objet TextToSpeech est prêt à être utilisé.
  • Les méthodes setPitchRate et setSpeedRate permettent comme leur nom l’indique de régler la force et la vitesse de la voix.
  • La variable static language permet de définir la langue qui va être utilisée par la voix synthétique.

Et maintenant, utilisons cette classe :

import android.content.ActivityNotFoundException;
import android.content.Intent;
import android.content.res.AssetFileDescriptor;
import android.content.res.Resources;
import android.speech.RecognizerIntent;
import android.speech.tts.TextToSpeech;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;

import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Locale;
import java.util.regex.Pattern;

public class MainActivity extends AppCompatActivity {
    private final int CHECK_CODE = 0x1;
    private final int SHORT_DURATION = 1000;

    private Button launchPrompt, readText;
    private Speaker speaker;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        readText = (Button) findViewById(R.id.readText);
        readText.setOnClickListener(readListener);

        checkTTS();
    }

    private void checkTTS(){
        Intent check = new Intent();
        check.setAction(TextToSpeech.Engine.ACTION_CHECK_TTS_DATA);
        startActivityForResult(check, CHECK_CODE);
    }

    View.OnClickListener readListener = new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            speakOut();
        }
    };

    @Override
    protected void onRestart() {
        super.onRestart();
        checkTTS();
    }

    private void speakOut() {
        if(!speaker.isSpeaking()) {
            speaker.speak(readFile(R.raw.martin_luther_king));
            speaker.pause(SHORT_DURATION);
        }
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        switch (requestCode) {
            case CHECK_CODE: {
                if (resultCode == TextToSpeech.Engine.CHECK_VOICE_DATA_PASS) {
                    speaker = new Speaker(this);
                } else {
                    Intent install = new Intent();
                    install.setAction(TextToSpeech.Engine.ACTION_INSTALL_TTS_DATA);
                    startActivity(install);
                }
            }
            default:
                break;
        }
    }

    @Override
    protected void onStop() {
        speaker.destroy();
        super.onStop();
    }

    private String readFile(int rawfile) {
        String result = new String();
        try {
            Resources res = getResources();
            InputStream input_stream = res.openRawResource(rawfile);

            byte[] b = new byte[input_stream.available()];
            input_stream.read(b);
            result = new String(b);
        } catch (Exception e) {
            Log.e("readFile", e.getMessage());
        }
        return result;
    }

}

Détaillons cette activité :

  • La fonction checkTTS est primordiale, elle permet de vérifier si le smartphone possède les outils nécessaires afin d’utilser la classe TextToSpeech. La fonction onActivityResult est alors appelé avec le code CHECK_CODE, si tout est ok on peut initialiser notre classe sinon, on propose à l’utilisateur d’installer ce dont on a besoin, on utilise pour cela un Intent.
  • La seconde chose intéressante, c’est la fonction readFile, qui prend en paramètre, un fichier raw. On récupére alors le contenu du fichier dans une String que l’on renvoit.
  • Enfin, la dernière fonction intéressante, c’est la fonction speakOut() qui est assez simple d’utilisation, on vérifie juste que notre objet n’est pas déjà en cours d’utilisation grâce à la fonction isPlaying, si ce n’est pas le cas, on joue le texte précédemment chargé et à la fin on fait une courte pause afin d’éviter de permettre à l’utilisateur de spammer.

Et voilà, vous pouvez ensuite facilement adapter ce code, vous pouvez par exemple permettre de mettre en pause le texte joué (la fonction est déjà présentre dans notre classe, pratique non ?) ou encore même ajouté des Widgets afin de gérer la force et le débit de la voix. N’hésitez pas à expérimenter !

Code source

Si vous voulez tester directement le code, il est disponible sur mon dépôt github. Vous y trouverez les deux exemples vus ci-dessus.

1 comment

  1. Salut,
    C’est génial ce que tu fais continue!
    J’ai téléchargé tes fichiers sur Github, au début ça ne marchait pas, j’ai baissé tout le rock’n’roll derrière moi et ça a tout détecté et lancé la musique! Je vais m’en servir de base car j’aimerais déclencher ma caméra en disant « cheese » via une application que je créé! J’ai essayé hier mais j’étais découragée car ça ne marchait pas, je vais essayé de tirer parti de ton projet pour solutionner le mien.
    Merci en tout cas, c’est tout à fait le genre de tutos et de ressources qui fonctionnent qui m’intéressent.

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *