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 and are discussed in the Using Text-To-Speech Android document. 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. In this project we give a concise introduction to the basics of text to speech conversion.
Open Eclipse and create a project with the following specifications
where we note that the minimum SDK version has been set to 4 (corresponding to Android 1.6). Now we modify this skeleton project to accomplish the following.
We begin by defining the main layout and some strings that we will need. Open res/values/strings.xml and edit it to read
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="hello">SpeakToMe</string>
<string name="app_name">Speak To Me</string>
<string name="goLabel">Speak</string>
<string name="clearLabel">Clear Text</string>
</resources>
Then open res/layout/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:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent" >
<EditText
android:id="@+id/speak_input"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:hint="(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.
Open the file SpeakToMe.java and edit it to read
package com.lightcone.speaktome;
import java.util.Locale;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
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 SpeakToMe extends Activity implements OnInitListener, OnClickListener {
private static final int CHECK_DATA = 0;
private static final Locale defaultLocale = Locale.UK; // Set language to British English
private static final String logLabel = "TTS ++++++++++++++++++++";
private TextToSpeech tts;
private boolean isInit = false;
private View speakButton;
private View clearButton;
private EditText speakText;
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.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(logLabel, "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(logLabel,"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(logLabel, temp);
}
} else {
// missing data, so install it on the device
Log.i(logLabel, "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(logLabel, "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(logLabel, "Failure: TextToSpeech instance tts was 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);
} else {
tts.speak(text, TextToSpeech.QUEUE_ADD, null);
}
} else {
Log.i(logLabel, "Failure: TextToSpeech instance tts was not properly initialized");
}
}
// Release TTS resources when finished
@Override
protected void onDestroy(){
super.onDestroy();
Log.i(logLabel,"Destroy");
tts.shutdown();
}
}
If you now execute in a emulator or on a phone with TTS capability, you should see the following display
and the TTS syntesizer should speak brief instructions. If you enter some text and click Speak, the synthesizer should voice the text that you have entered.
Let's summarize how SpeakToMe.java implements this functionality.
checkIntent.setAction(TextToSpeech.Engine.ACTION_CHECK_TTS_DATA);
where
ACTION_CHECK_TTS_DATA is a constant of the
TextToSpeech.Engine class
that starts an activity to verify proper installation and availability of language resource files on the system. We then use the Intent in the
startActivityForResult(checkIntent, CHECK_DATA);
statement, where the
startActivityForResult(Intent intent, int requestCode) method of Activity is like
the
startActivity (Intent) method except that it returns a result:
when the activity that was started exits, our
onActivityResult()
method (discussed below) will be called with the requestCode specified above as one argument and a resultCode as another argument.
|
The language options may be device dependent, and may depend on where the device is deployed. They may also be overridden by current phone settings.
For example, the present example speaks with a British voice (as requested) when run on an Android 2.1 emulator, but when run on an AT&T Captivate (Samsung Galaxy S) phone in the United States it defaults to U.S. English, even though the checks output to logcat indicate that Locale.UK is installed. This is because for the Captivate the option Settings > Text-to-speech > Always use my settings is checked by default. This causes the Captivate to override app Locale commands and always use the language specified in Settings > Text-to-speech > Language. If this option is unchecked, the Captivate will then respond to app commands to reset the Locale and the current app will begin speaking in a British voice.
In this example the listing of available Locales has about 80 entries for the emulator versus only a little more than half of that for the Captivate phone. Also, if you run on a device, you may be presented with a choice for TTS engines when you run the app. For the specific Captivate mentioned above, one can choose either Pico TTS or TTS Service Extended, for example. |
Intent installIntent = new Intent();
installIntent.setAction(TextToSpeech.Engine.ACTION_INSTALL_TTS_DATA);
startActivity(installIntent);
to create an Intent called installIntent, set its action to install TTS data through the TextToSpeech.Engine class constant
ACTION_INSTALL_TTS_DATA,
and launch a new Activity using startActivity(installIntent) with this Intent as argument.
This should trigger the platform TextToSpeech engine to start the activity that installs the resource files on the device that are required for TTS to be operational.
| Because the user could interrupt this process at any point, we should not generally assume that this step will always be carried out. We won't worry about that here, but in a realistic application one should use the constant CHECK_VOICE_DATA_PASS as above to confirm that the data have been installed on the device before proceeding. |
| The onDestroy() method is generally used to perform any final cleanup before an activity is destroyed, either because someone 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 while the rest of its application is still running. 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. Two 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 at the link SpeakToMe. |