Previous  | Next  | Home

Speak to Me


 

A very useful feature of the Android API (available in Android 1.6 and later) is a rich and easy to use set of text to speech (TTS) resources that allow conversion of text strings to spoken voice. These capabilites are implemented primarily through the TextToSpeech class. By employing methods and class constants of the Locale class, it is generally possible to customize both the language and dialect of the spoken voice (subject to availability of language data on the device). Since the device can be self-aware of position if it has access to Location services, it is even possible to automatically switch the default language to correspond to the present location of the device. This project gives a concise introduction to the basics of text to speech conversion.

 

Creating the Project in Android Studio

Following the general procedure in Creating a New Project, either choose Start a new Android Studio project from the Android Studio homepage, or from the Android Studio interface choose File > New > New Project. Fill out the fields in the resulting screens as follows,


Application Name: SpeakToMe
Company Domain:< YourNamespace >
Package Name: <YourNamespace> . speaktome
Project Location: <ProjectPath> SpeakToMe
Target Devices: Phone and Tablet; Min SDK API 21
Add an Activity: Empty Activity
Activity Name: MainActivity (check the Generate Layout File box)
Layout Name: activity_main

where you should substitute your namespace for <YourNamespace> (com.lightcone in my case) and <ProjectPath> is the path to the directory where you will store this Android Studio Project (/home/guidry/StudioProjects/ in my case). If you have chosen to use version control for your projects, go ahead and commit this project to version control.

 

Filling out the Code

Now we modify this skeleton project to accomplish the following.

  1. Query the device to see whether it has adequate language data installed. If it doesn't, send the device to try to get the required data. If it does have the language data, launch a new instance of TextToSpeech.

  2. Put an editable text field on the screen to accept input text strings, a Speak button to initiate text to speech conversion on the input string, and a Clear button to clear the field, but disable input until the text to speech (TTS) instance is initialized.

  3. Once the TextToSpeech instance is initialized, enable the input text fields and buttons, set language and properties of the TTS instance, and speak introductory instructions.

  4. Use the methods of the TextToSpeech class to speak input text strings entered by the user when the Speak button is pressed.

Let us now create and populate the XML and Java files needed for this project.

 

The XML Files

Let's first define the main layout and some strings that will be required. Open res/values/strings.xml and edit it to read


<?xml version="1.0" encoding="utf-8"?> <resources> <string name="app_name">Speak To Me</string> <string name="goLabel">Speak</string> <string name="clearLabel">Clear Text</string> <string name="enter_text">(Enter text)</string> <string name="action_settings">Settings</string> </resources>

Then open res/layout/activity_main.xml and implement the layout for an EditText field and two Buttons:


<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent" android:orientation="vertical" > <EditText android:id="@+id/speak_input" android:layout_width="fill_parent" android:layout_height="wrap_content" android:hint="@string/enter_text" android:inputType="text" /> <LinearLayout android:layout_width="fill_parent" android:layout_height="wrap_content" android:orientation="horizontal" > <Button android:id="@+id/speak_button" android:layout_width="fill_parent" android:layout_height="wrap_content" android:layout_weight="1.0" android:text="@string/goLabel" /> <Button android:id="@+id/clear_button" android:layout_width="fill_parent" android:layout_height="wrap_content" android:layout_weight="1.0" android:text="@string/clearLabel" /> </LinearLayout> </LinearLayout>

Now we implement the Java code that will allow text entered in the text field to be converted to speech when the Speak button is pressed.

 

The Java Class Files

Open the file MainActivity.java and edit it to read


package <YourNamespace>.speaktome; import android.os.Bundle; import android.support.v7.app.AppCompatActivity; import android.view.Menu; import java.util.Locale; import android.content.Intent; import android.speech.tts.TextToSpeech; import android.speech.tts.TextToSpeech.OnInitListener; import android.util.Log; import android.view.View; import android.view.View.OnClickListener; import android.widget.EditText; //Class to test text-to-speech capabilities public class MainActivity extends AppCompatActivity implements OnInitListener, OnClickListener { private static final int CHECK_DATA = 0; private static final Locale defaultLocale = Locale.UK; // British English private static final String TAG = "TTS"; private TextToSpeech tts; private boolean isInit = false; private View speakButton; private View clearButton; private EditText speakText; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // Identify buttons and add click listeners speakButton = findViewById(R.id.speak_button); speakButton.setOnClickListener(this); clearButton = findViewById(R.id.clear_button); clearButton.setOnClickListener(this); // Identify EditText field speakText = (EditText) findViewById(R.id.speak_input); // Disable text field and speak button until text to speech initialization is done // (See method onInit() below) speakButton.setEnabled(false); speakText.setEnabled(false); // Use an Intent and startActivityForResult to check whether TTS data installed on the // device. Result returned and acted on in method onActivityResult(int, int, Intent) below. Intent checkIntent = new Intent(); checkIntent.setAction(TextToSpeech.Engine.ACTION_CHECK_TTS_DATA); startActivityForResult(checkIntent, CHECK_DATA); } // Create the TTS instance if TextToSpeech language data are installed on device. If not // installed, attempt to install it on the device. protected void onActivityResult(int requestCode, int resultCode, Intent data) { if (requestCode == CHECK_DATA) { if (resultCode == TextToSpeech.Engine.CHECK_VOICE_DATA_PASS) { // Success, so create the TTS instance. But can't use it to speak until // the onInit(status) callback defined below runs, indicating initialization. Log.i(TAG, "Success, let's talk"); tts = new TextToSpeech(this, this); // Use static Locales method to list available locales on device Locale locales [] = Locale.getAvailableLocales(); Log.i(TAG,"Locales Available on Device:"); for(int i=0; i<locales.length; i++){ String temp = "Locale "+i+": "+locales[i]+" Language=" +locales[i].getDisplayLanguage(); if(locales[i].getDisplayCountry() != "") { temp += " Country="+locales[i].getDisplayCountry(); } Log.i(TAG, temp); } } else { // missing data, so install it on the device Log.i(TAG, "Missing Data; Install it"); Intent installIntent = new Intent(); installIntent.setAction(TextToSpeech.Engine.ACTION_INSTALL_TTS_DATA); startActivity(installIntent); } } } // Must wait for initialization before any speech can be synthesized. The class // implements OnInitListener and the following callback has been overridden to // implement actions that will be executed once initialized. @Override public void onInit(int status) { if(status == TextToSpeech.SUCCESS){ isInit = true; // Enable input text field and speak button now that we are initialized speakButton.setEnabled(true); speakText.setEnabled(true); // Set to a language locale after checking availability Log.i(TAG, "available="+tts.isLanguageAvailable(Locale.UK)); tts.setLanguage(defaultLocale); // Examples of voice controls. Set to defaults of 1.0. tts.setPitch(1.0F); tts.setSpeechRate(1.0F); // Issue a greeting and instructions in the default language speakGreeting(defaultLocale.getDisplayLanguage()); } else { isInit = false; Log.i(TAG, "Failure: TTS instance not properly initialized"); } } // Method to issue greeting and instructions public void speakGreeting(String currentLanguage){ String text1 = "Let's test text to speech in "+currentLanguage+". "; text1 += "Enter some "+currentLanguage+" text and press the speak button."; sayIt(text1, true); } // Implement text to speech for an arbitrary string entered in the EditText field // for the Speak button and text clear for the Clear button. @Override public void onClick(View v) { switch(v.getId()){ case R.id.speak_button: String speakString = speakText.getText().toString(); sayIt(speakString,true); break; case R.id.clear_button: speakText.setText(""); break; } } // Method to speak a string. The boolean flushQ determines whether the text is // appended to the queue (if false), or if the queue is flushed first (if true). public void sayIt(String text, boolean flushQ){ if(isInit){ if(flushQ){ tts.speak(text, TextToSpeech.QUEUE_FLUSH, null, null); } else { tts.speak(text, TextToSpeech.QUEUE_ADD, null, null); } } else { Log.i(TAG, "Failure: TTS instance not properly initialized"); } } // Release TTS resources when finished @Override protected void onDestroy(){ super.onDestroy(); Log.i(TAG,"Destroy"); tts.shutdown(); } }

If this is now executed in a emulator or on a phone with TTS capability, you should see the following display (the keyboard may not be displayed until you touch the text input field)



and the TTS synthesizer should speak brief instructions. If you enter some text and click Speak, the synthesizer should voice the text entered (with the present locale set to British English).

 

How It Works

Let's summarize how MainActivity.java implements this functionality.


The onDestroy() method is generally used to perform any final cleanup before an activity is destroyed, either because something called finish() on it, or because the system is temporarily destroying this instance of the activity to save space. onDestroy() is not guaranteed to be called (the system may kill the activity's process directly without calling this method), so it is not a good place to save data. However, it is a good place to free resources like threads or TTS engines associated with an activity, so that a destroyed activity does not leave such things about when not needed. Note that Android generally requires a call through to the superclass method (super.onDestroy() in this case) when a lifecycle method is overridden.

This simple exercise illustrates many of the basic features of TextToSpeech and should serve as a starting template for more ambitious applications. Three possibilities are suggested below in the Exercises. A project implementing a Spinner permitting the user to choose a language (Exercise 1) may be found at this Exercise solution.


The complete project for the application described above is archived on GitHub at the link SpeakToMe. Instructions for installing it in Android Studio may be found in Packages for All Projects.

Last modified: July 8, 2016


Exercises

1. Modify this project to provide a Spinner popup menu allowing the user to select the language for the TTS voice. See the Spinner Tutorial for how to deal with Spinner widgets and use the onInit callback of the OnInitListener interface to ensure that the TTS engine is not invoked before it is initialized when the language is selected. [Solution]

2. Extend Exercise 1 by using Location services to automatically determine where the device is and choose an appropriate language for the TTS voice.

3. The method setOnUtteranceProgressListener(UtteranceProgressListener) can be used to monitor the progress of speech synthesis triggered by a speech request issued asynchronously using speak (charSequence text, int queueMode, Bundle params, String id). Modify the present app to include monitoring of speech synthesis and appropriate handling of any errors that occur.


Previous  | Next  | Home