Синтез речи в Android

advertisement
Синтез речи в Android-приложении
Не так давно пришлось прикручивать к нашему приложению озвучку с помощью Text-to-Speech (TTS).
Об этом-то я и хочу сегодня рассказать.
Quick Start
TTS можно использовать двумя способами. Во-первых, можно завязываться на конкретный движок,
покупать библиотеку и работать через неѐ. Про этот вариант ничего не могу сказать, знаю только
теоретически. Второй, общеизвестный вариант — использовать стандартное API. Голоса в этом случае
являются просто приложениями, установленными в системе.
Вообще-то заставить приложение говорить не так сложно, и мануалов по этому поводу полно. Но для
полноты картины приведу начальные сведения.
Начиная с версии 1.6 в SDK есть стандартный класс TextToSpeech.
Подключение в приложение
Простейшая схема такова:
MainActivity.java
public class StartActivity extends Activity {
private static final String enginePackageName = "com.svox.pico";
private static final String SAMPLE_TEXT = "Synthesizes speech from text for
immediate playback or to create a sound file.";
TextToSpeech tts;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
tts = new TextToSpeech(this, new OnInitListener() {
@Override
public void onInit(int status) {
if (status == TextToSpeech.SUCCESS) {
tts.setEngineByPackageName(enginePackageName);
tts.setLanguage(Locale.UK);
speak();
}
}
});
}
private void speak() {
tts.speak(SAMPLE_TEXT, TextToSpeech.QUEUE_FLUSH, null);
}
@Override
protected void onDestroy() {
super.onDestroy();
tts.shutdown();
}
}
Все вроде понятно. Создали экземпляр TextToSpeech, инициализировали в специальном листенере
(задавать голос мы можем только в onInit), и с тех пор можем синтезировать и проигрывать речь с
помощью метода speak. Обращу внимание, что это только схема, более приближенное к реальности
приложение можно найти в примере к статье.
Метод speak
Рассмотрим подробнее сигнатуру метода speak:
speak(String text, int queueMode, HashMap params)
text
Текст, который нужно прочитать
queueMode

TextToSpeech.QUEUE_FLUSH, если хочется, чтобы предыдущая фраза прерывалась и

сразу начиналась следующая
TextToSpeech.QUEUE_ADD, если хочется, чтобы предыдущая фраза договорилась до
конца только после этого началась следующая
params
Массив дополнительных параметров. Возможные параметры:

TextToSpeech.Engine.KEY_PARAM_STREAM — поток, в котором будет воспроизводиться
звук.

TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID — идентификатор фразы. Пригодится,
если хочется обрабатывать событие окончания говорения, и при этом не запутаться в
произносимых фразах.
Другие полезные методы
playSilence(long durationInMs, int queueMode, HashMap params)
Проигрывает тишину в течение заданного времени. Параметры те же, что у speak.
stop()
Останавливает воспроизведение
synthesizeToFile(String text, HashMap params, String filename)
Записывает синтезированную речь в файл. Параметры те же, что у speak.
addSpeech(String text, String filename), addSpeech(String text, String packagename, int
resourceId)
Задает маппинг между фразой и существующим файлом/ресурсом. Если такой маппинг задан,
то вместо синтезированной речи метод speak будет воспроизводить данный файл.
setOnUtteranceCompletedListener(TextToSpeech.OnUtteranceCompletedListener listener)
Задает слушателя для события окончания фразы.
areDefaultsEnforced()
Установлена ли в настройках TTS галочка «Мои настройки». О ней будет подробнее.
setPitch(float pitch)
Задает тембр голоса. 1 — обычное значение, чем меньше значение, тем ниже голос.
setSpeechRate(float speechRate)
Задает скорость речи. 1 — обычное значение, чем меньше значение, тем медленнее говорим.
TTS engines
Вкратце расскажу об известных TTS-движках. Как уже говорилось ранее, голоса — это просто
сторонние приложения. Посмотрим, что у нас есть под Android.
Pico
Стандартный TTS-движок, знает 5 языков, поставляется бесплатно. Говорит неплохо, но
русского не знает.
eSpeak
Свободный TTS-движок. Знает очень много языков. По-русски тоже говорит, но отвратительно.
SVOX
Довольно известный движок. Под Android распространяется следующим образом. Есть
бесплатная программа-оболочка и платные голоса, которыми можно управлять из этой
оболочки. Голосов очень много. Достаточно неплохо говорит по-русски, хотя есть проблемы с
ударениями. В общем-то голос SVOX оказался единственным вариантом для русской озвучки
приложения.
Loquendo
Также известный и качественный движок. К сожалению, в Android представлен мало. Для
английского языка есть голос Susan, а вот для русского языка приложения нет, хотя вообще-то
Loquendo говорить по-русски умеет.
А теперь немного о сложностях.
Проверка наличия голосовых данных
Pico TTS поставляется по умолчанию с системой. Но на некоторых моделях телефонов не установлены
голосовые пакеты. Внешне это проявляется, например, в том, что в системных настройках синтеза
речи всѐ задизаблено и предлагается скачать и установить некие ресурсы:
В официальном мануале описан способ обработки этой ситуации.
CheckVoiceActivity.java
public class CheckVoiceActivity extends Activity {
TextToSpeech tts;
private static final int REQUEST_CODE = 150;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
}
public void onPrepareSpeech(View view) {
Intent checkIntent = new Intent();
checkIntent.setAction(TextToSpeech.Engine.ACTION_CHECK_TTS_DATA);
checkIntent.setPackage(Consts.ENGINE);
startActivityForResult(checkIntent, REQUEST_CODE);
}
protected void onActivityResult(int requestCode, int resultCode, Intent data)
{
if (requestCode == REQUEST_CODE)
{
if (resultCode == TextToSpeech.Engine.CHECK_VOICE_DATA_PASS)
{
// Голосовые данные установлены, можно создавать экземпляр TextToSpeech
...
}
else
{
// голосовые данные отсутствуют, предлагаем установить
Intent installIntent = new Intent();
installIntent.setAction(TextToSpeech.Engine.ACTION_INSTALL_TTS_DATA);
installIntent.setPackage(Consts.ENGINE);
startActivity(installIntent);
}
}
}
...
}
Особенности работы под Android 2.1
Наше приложение должно было разговаривать не абы каким голосом, а исключительно красивым.
Соответственно, была задача выбрать нужный нам TTS-движок из всех установленных у пользователя.
В Android 2.2 у класса TextToSpeech есть метод setEngineByPackageName, но что делать в 2.1, где
такого метода нет?
Существует известный обход этой проблемы, с использованием дополнительной программы и
дополнительной библиотеки. В плане юзабилити, конечно, не ахти, ведь придется заставлять
пользователя ставить какой-то сторонний софт. Зато работает. Итак:



Устанавливаем на телефон приложение Text-to-speech Extended (ссылка на маркет:
market://details?id=com.google.tts)
Подключаем к нашему приложению библиотеку от eyes-free.
Вместо привычного TextToSpeech используем класс TextToSpeechBeta из этой библиотеки
Имеет смысл написать класс-оболочку такого примерно вида:
TextToSpeechWrapper
package com.demos.tts;
import java.util.HashMap;
import java.util.Locale;
import com.google.tts.TextToSpeechBeta;
import
import
import
import
import
android.content.Context;
android.os.Build;
android.speech.tts.TextToSpeech;
android.speech.tts.TextToSpeech.OnInitListener;
android.speech.tts.TextToSpeech.OnUtteranceCompletedListener;
/**
* Оболочка над TTS/TTSE
*
* @author darja.ryazhskikh
*
*/
public class TextToSpeechWrapper {
private TextToSpeech tts;
private TextToSpeechBeta ttse;
private Context context;
public TextToSpeechWrapper(Context context) {
this.context = context;
}
/**
* Создаем стандартный TextToSpeech в случае версии Android от 2.2, и объект
* TextToSpeechBeta для 2.1
*
* @param context
* @param listener
*/
public void init(final OnInitListener listener) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.FROYO) {
tts = new TextToSpeech(context, listener);
ttse = null;
} else {
if (TextToSpeechBeta.isInstalled(context)) {
ttse = new TextToSpeechBeta(context,
new TextToSpeechBeta.OnInitListener() {
@Override
public void onInit(int status, int version) {
listener.onInit(status);
}
});
} else {
ttse = null;
}
tts = null;
}
}
/**
* Проверяет, установлена ли в настройках TTS галочка
* "Always use my settings"
*
* @return
*/
public Boolean areDefaultsEnforced() {
if (tts != null)
return tts.areDefaultsEnforced();
else if (ttse != null)
return ttse.areDefaultsEnforcedExtended();
else
return null;
}
public boolean setEngineByPackageName(String engine) {
boolean success = false;
try {
if (tts != null)
success = tts.setEngineByPackageName(engine) == TextToSpeech.SUCCESS;
else if (ttse != null)
success = ttse.setEngineByPackageNameExtended(engine) ==
TextToSpeechBeta.SUCCESS;
} catch (Exception e) {
e.printStackTrace();
}
return success;
}
public void speak(String text, HashMap<String, String> params) {
if (tts != null)
tts.speak(text, TextToSpeech.QUEUE_FLUSH, params);
else if (ttse != null)
ttse.speak(text, TextToSpeech.QUEUE_FLUSH, params);
}
public void stop() {
if (tts != null)
tts.stop();
else if (ttse != null)
ttse.stop();
}
public boolean isLanguageAvailable(Locale loc) {
if (tts != null) {
int result = tts.isLanguageAvailable(loc);
return result >= TextToSpeech.LANG_AVAILABLE;
} else if (ttse != null)
return ttse.isLanguageAvailable(loc) >= TextToSpeechBeta.LANG_AVAILABLE;
return false;
}
public boolean setLanguage(Locale loc) {
if (tts != null)
return tts.setLanguage(loc) >= TextToSpeech.LANG_AVAILABLE;
else if (ttse != null)
return ttse.setLanguage(loc) >= TextToSpeechBeta.LANG_AVAILABLE;
return false;
}
/**
* Задает слушателя на окончание фразы
*
* @param listener
*/
public void setOnUtteranceCompletedListener(
final OnUtteranceCompletedListener listener) {
if (tts != null)
tts.setOnUtteranceCompletedListener(listener);
else if (ttse != null)
ttse.setOnUtteranceCompletedListener(new
TextToSpeechBeta.OnUtteranceCompletedListener() {
@Override
public void onUtteranceCompleted(String utteranceId) {
listener.onUtteranceCompleted(utteranceId);
}
});
}
public void shutdown() {
if (tts != null)
tts.shutdown();
if (ttse != null)
ttse.shutdown();
}
}
Конкретная реализация может быть и другой.
Конфигурируем TTS
Нам нужно сконфигурировать TTS определенным голосом. Голос, в свою очередь, определяется
следующими параметрами:


Engine — задается функцией setEngineByPackageName.
Locale — задается функцией setLanguage.
Вариант 1, легкий, но редкий
Так работает Loquendo. Пишем:
tts.setEngineByPackageName("com.loquendo.tts.susan");
И всѐ начинает работать.
Вариант 2, сложный и частый
Так работают Pico и SVOX. У них есть оболочка (engine) и подключаемые модули (голоса).
Рассмотрим на примере Pico
tts.setEngineByPackageName("com.svox.pico");
tts.setLanguage(Locale.US);
Тоже вроде все работает. Проблемы начинаются, когда у одной локали оказывается несколько
голосов. Такое имеет место для SVOX. У одного языка может быть мужской, женский и детский
голос. Это разные приложения, у них разные названия пакетов, но с точки зрения TTS все это одно и
то же.
Если установлено несколько голосов для одной локали, выбран будет тот, который указан в
настройках SVOX как дефолтный. Однако, мы это никак отследить не можем. Печально.
Общие проблемы для обоих вариантов
TTS-движок задизаблен в настройках TextToSpeech
Вот так:
У меня так и не получилось отловить эту ситуацию. По идее, setEngineByPackageName должен бы
вернуть ERROR, и мы бы догадались, что что-то не так. Но он отрабатывает на ура, и приложение
разговаривает, чем попало.
Галочка "Использовать мои настройки"
Это тоже достаточно вредная штука, и еѐ нужно учитывать. Дело в том, что пользователь может
выставить собственные настройки TTS и эту галочку.
И тогда вся ваша конфигурация не будет применяться. Отслеживать состояние этой настройки можно
с помощью метода areDefaultsEnforced (в Android 2.2 и выше. Если версия меньше, нужен TTSE и
метод areDefaultsEnforcedExtended)
Заключение
Собственно, вот и все, что накопилось за те две недели, что я занимаюсь озвучкой приложения.
Субъективное ощущение от этого API — сыровато. Не хватает доступа ко всем настройкам TTS в
системе. Для пользователя они слишком сложные и неочевидные ("Мои настройки" — яркий пример).
Разнобой в опциях различных TTS-движков также печалит. В общем, использовать TTS не так сложно,
а вот обрабатывать различные его состояния — целое дело.
Ссылки


An introduction to Text-To-Speech in Android
Using Text to Speech in Android
Пример
Исходники к статье прилагаются. Там рассмотрены следующие ситуации:



Простая инициализация TTS
Проверка голосовых данных Pico
Использование TextToSpeechBeta
Download