eSpeak NG is an open source speech synthesizer that supports more than hundred languages and accents.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

SpeechSynthesis.java 14KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392
  1. /*
  2. * Copyright (C) 2012-2013 Reece H. Dunn
  3. * Copyright (C) 2011 Google Inc.
  4. *
  5. * Licensed under the Apache License, Version 2.0 (the "License");
  6. * you may not use this file except in compliance with the License.
  7. * You may obtain a copy of the License at
  8. *
  9. * http://www.apache.org/licenses/LICENSE-2.0
  10. *
  11. * Unless required by applicable law or agreed to in writing, software
  12. * distributed under the License is distributed on an "AS IS" BASIS,
  13. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. * See the License for the specific language governing permissions and
  15. * limitations under the License.
  16. */
  17. /*
  18. * This file implements the Java API to eSpeak using the JNI bindings.
  19. *
  20. * Android Version: 4.0 (Ice Cream Sandwich)
  21. * API Version: 14
  22. */
  23. package com.reecedunn.espeak;
  24. import android.content.Context;
  25. import android.content.res.Configuration;
  26. import android.content.res.Resources;
  27. import android.speech.tts.TextToSpeech;
  28. import android.util.DisplayMetrics;
  29. import android.util.Log;
  30. import java.io.File;
  31. import java.util.HashMap;
  32. import java.util.LinkedList;
  33. import java.util.List;
  34. import java.util.Locale;
  35. import java.util.Map;
  36. public class SpeechSynthesis {
  37. private static final String TAG = SpeechSynthesis.class.getSimpleName();
  38. public static final int GENDER_UNSPECIFIED = 0;
  39. public static final int GENDER_MALE = 1;
  40. public static final int GENDER_FEMALE = 2;
  41. static {
  42. System.loadLibrary("ttsespeak");
  43. nativeClassInit();
  44. }
  45. private final Context mContext;
  46. private final SynthReadyCallback mCallback;
  47. private final String mDatapath;
  48. private boolean mInitialized = false;
  49. public SpeechSynthesis(Context context, SynthReadyCallback callback) {
  50. // First, ensure the data directory exists, otherwise init will crash.
  51. final File dataPath = CheckVoiceData.getDataPath(context);
  52. if (!dataPath.exists()) {
  53. Log.e(TAG, "Missing voice data");
  54. dataPath.mkdirs();
  55. }
  56. mContext = context;
  57. mCallback = callback;
  58. mDatapath = dataPath.getParentFile().getPath();
  59. attemptInit();
  60. }
  61. @Override
  62. protected void finalize() {
  63. nativeDestroy();
  64. }
  65. public static String getVersion() {
  66. return nativeGetVersion();
  67. }
  68. public int getSampleRate() {
  69. return nativeGetSampleRate();
  70. }
  71. public int getChannelCount() {
  72. return nativeGetChannelCount();
  73. }
  74. public int getAudioFormat() {
  75. return nativeGetAudioFormat();
  76. }
  77. public int getBufferSizeInBytes() {
  78. final int bufferSizeInMillis = nativeGetBufferSizeInMillis();
  79. final int sampleRate = nativeGetSampleRate();
  80. return (bufferSizeInMillis * sampleRate) / 1000;
  81. }
  82. public List<Voice> getAvailableVoices() {
  83. final List<Voice> voices = new LinkedList<Voice>();
  84. final String[] results = nativeGetAvailableVoices();
  85. for (int i = 0; i < results.length; i += 4) {
  86. final String name = results[i];
  87. final String identifier = results[i + 1];
  88. final int gender = Integer.parseInt(results[i + 2]);
  89. final int age = Integer.parseInt(results[i + 3]);
  90. final Voice voice = new Voice(name, identifier, gender, age);
  91. voices.add(voice);
  92. }
  93. return voices;
  94. }
  95. public void setVoiceByProperties(
  96. String name, String languages, int gender, int age, int variant) {
  97. nativeSetVoiceByProperties(name, languages, gender, age, variant);
  98. }
  99. public void setRate(int rate) {
  100. nativeSetRate(rate);
  101. }
  102. public void setPitch(int pitch) {
  103. nativeSetPitch(pitch);
  104. }
  105. public void synthesize(String text) {
  106. nativeSynthesize(text);
  107. }
  108. public void stop() {
  109. nativeStop();
  110. }
  111. private void nativeSynthCallback(byte[] audioData) {
  112. if (mCallback == null)
  113. return;
  114. if (audioData == null) {
  115. mCallback.onSynthDataComplete();
  116. } else {
  117. mCallback.onSynthDataReady(audioData);
  118. }
  119. }
  120. private void attemptInit() {
  121. if (mInitialized) {
  122. return;
  123. }
  124. if (!CheckVoiceData.hasBaseResources(mContext)) {
  125. Log.e(TAG, "Missing base resources");
  126. return;
  127. }
  128. if (!nativeCreate(mDatapath)) {
  129. Log.e(TAG, "Failed to initialize speech synthesis library");
  130. return;
  131. }
  132. Log.i(TAG, "Initialized synthesis library with sample rate = " + getSampleRate());
  133. mInitialized = true;
  134. }
  135. public static String getSampleText(Context context, Locale locale) {
  136. final DisplayMetrics metrics = context.getResources().getDisplayMetrics();
  137. final Configuration config = context.getResources().getConfiguration();
  138. final String language = getIanaLocaleCode(locale.getLanguage(), mJavaToIanaLanguageCode);
  139. final String country = getIanaLocaleCode(locale.getCountry(), mJavaToIanaCountryCode);
  140. config.locale = new Locale(language, country, locale.getVariant());
  141. Resources res = new Resources(context.getAssets(), metrics, config);
  142. return res.getString(R.string.sample_text, config.locale.getDisplayName(config.locale));
  143. }
  144. private int mNativeData;
  145. private static native final boolean nativeClassInit();
  146. private native final boolean nativeCreate(String path);
  147. private native final boolean nativeDestroy();
  148. private native final static String nativeGetVersion();
  149. private native final int nativeGetSampleRate();
  150. private native final int nativeGetChannelCount();
  151. private native final int nativeGetAudioFormat();
  152. private native final int nativeGetBufferSizeInMillis();
  153. private native final String[] nativeGetAvailableVoices();
  154. private native final boolean nativeSetVoiceByProperties(
  155. String name, String languages, int gender, int age, int variant);
  156. private native final boolean nativeSetRate(int rate);
  157. private native final boolean nativeSetPitch(int pitch);
  158. private native final boolean nativeSynthesize(String text);
  159. private native final boolean nativeStop();
  160. public interface SynthReadyCallback {
  161. void onSynthDataReady(byte[] audioData);
  162. void onSynthDataComplete();
  163. }
  164. public class Voice {
  165. public final String name;
  166. public final String identifier;
  167. public final int gender;
  168. public final int age;
  169. public final Locale locale;
  170. public Voice(String name, String identifier, int gender, int age) {
  171. this.name = name;
  172. this.identifier = identifier;
  173. this.gender = gender;
  174. this.age = age;
  175. if (name.equals("en-sc")) {
  176. // 'SC' is not a country code.
  177. locale = new Locale("en", "GB", "scotland");
  178. } else if (name.equals("en-wi")) {
  179. // 'WI' is not a country code.
  180. locale = new Locale("en", "029");
  181. } else if (name.equals("es-la")) {
  182. // 'LA' is the country code for Laos, not Latin America.
  183. locale = new Locale("es", "419");
  184. } else if (name.equals("hy-west")) {
  185. // 'west' is not a country code.
  186. locale = new Locale("hy", "", "arevmda");
  187. } else if (name.equals("zh-yue")) {
  188. // Android/Java does not support macrolanguages.
  189. locale = new Locale("zh", "HK");
  190. } else {
  191. String[] parts = name.split("-");
  192. switch (parts.length) {
  193. case 1: // language
  194. locale = new Locale(parts[0]);
  195. break;
  196. case 2: // language-country
  197. if (parts[1].equals("uk")) {
  198. // 'uk' is the language code for Ukranian, not Great Britain.
  199. parts[1] = "GB";
  200. }
  201. locale = new Locale(parts[0], parts[1]);
  202. break;
  203. case 3: // language-country-variant
  204. if (parts[1].equals("uk")) {
  205. // 'uk' is the language code for Ukranian, not Great Britain.
  206. parts[1] = "GB";
  207. }
  208. locale = new Locale(parts[0], parts[1], parts[2]);
  209. break;
  210. default:
  211. locale = null;
  212. }
  213. }
  214. }
  215. /**
  216. * Attempts a partial match against a query locale.
  217. *
  218. * @param query The locale to match.
  219. * @return A text-to-speech availability code. One of:
  220. * <ul>
  221. * <li>{@link TextToSpeech#LANG_NOT_SUPPORTED}
  222. * <li>{@link TextToSpeech#LANG_AVAILABLE}
  223. * <li>{@link TextToSpeech#LANG_COUNTRY_AVAILABLE}
  224. * <li>{@link TextToSpeech#LANG_COUNTRY_VAR_AVAILABLE}
  225. * </ul>
  226. */
  227. public int match(Locale query) {
  228. if (!locale.getISO3Language().equals(query.getISO3Language())) {
  229. return TextToSpeech.LANG_NOT_SUPPORTED;
  230. } else if (!locale.getISO3Country().equals(query.getISO3Country())) {
  231. return TextToSpeech.LANG_AVAILABLE;
  232. } else if (!locale.getVariant().equals(query.getVariant())) {
  233. return TextToSpeech.LANG_COUNTRY_AVAILABLE;
  234. } else {
  235. return TextToSpeech.LANG_COUNTRY_VAR_AVAILABLE;
  236. }
  237. }
  238. @Override
  239. public String toString() {
  240. return locale.toString().replace('_', '-');
  241. }
  242. }
  243. private static String getIanaLocaleCode(String code, final Map<String, String> javaToIana) {
  244. final String iana = javaToIana.get(code);
  245. if (iana != null) {
  246. return iana;
  247. }
  248. return code;
  249. }
  250. private static final Map<String, String> mJavaToIanaLanguageCode = new HashMap<String, String>();
  251. private static final Map<String, String> mJavaToIanaCountryCode = new HashMap<String, String>();
  252. static {
  253. mJavaToIanaLanguageCode.put("afr", "af");
  254. mJavaToIanaLanguageCode.put("aka", "ak");
  255. mJavaToIanaLanguageCode.put("amh", "am");
  256. mJavaToIanaLanguageCode.put("aze", "az");
  257. mJavaToIanaLanguageCode.put("bul", "bg");
  258. mJavaToIanaLanguageCode.put("bos", "bs");
  259. mJavaToIanaLanguageCode.put("cat", "ca");
  260. mJavaToIanaLanguageCode.put("ces", "cs");
  261. mJavaToIanaLanguageCode.put("cym", "cy");
  262. mJavaToIanaLanguageCode.put("dan", "da");
  263. mJavaToIanaLanguageCode.put("deu", "de");
  264. mJavaToIanaLanguageCode.put("div", "dv");
  265. mJavaToIanaLanguageCode.put("ell", "el");
  266. mJavaToIanaLanguageCode.put("eng", "en");
  267. mJavaToIanaLanguageCode.put("epo", "eo");
  268. mJavaToIanaLanguageCode.put("spa", "es");
  269. mJavaToIanaLanguageCode.put("est", "et");
  270. mJavaToIanaLanguageCode.put("fin", "fi");
  271. mJavaToIanaLanguageCode.put("fra", "fr");
  272. mJavaToIanaLanguageCode.put("gle", "ga");
  273. mJavaToIanaLanguageCode.put("hin", "hi");
  274. mJavaToIanaLanguageCode.put("hrv", "hr");
  275. mJavaToIanaLanguageCode.put("hat", "ht");
  276. mJavaToIanaLanguageCode.put("hun", "hu");
  277. mJavaToIanaLanguageCode.put("hye", "hy");
  278. mJavaToIanaLanguageCode.put("ind", "in"); // NOTE: The deprecated 'in' code is used by Java/Android.
  279. mJavaToIanaLanguageCode.put("isl", "is");
  280. mJavaToIanaLanguageCode.put("ita", "it");
  281. mJavaToIanaLanguageCode.put("kat", "ka");
  282. mJavaToIanaLanguageCode.put("kaz", "kk");
  283. mJavaToIanaLanguageCode.put("kal", "kl");
  284. mJavaToIanaLanguageCode.put("kan", "kn");
  285. mJavaToIanaLanguageCode.put("kor", "ko");
  286. mJavaToIanaLanguageCode.put("kur", "ku");
  287. mJavaToIanaLanguageCode.put("lat", "la");
  288. mJavaToIanaLanguageCode.put("lit", "lt");
  289. mJavaToIanaLanguageCode.put("lav", "lv");
  290. mJavaToIanaLanguageCode.put("mkd", "mk");
  291. mJavaToIanaLanguageCode.put("mal", "ml");
  292. mJavaToIanaLanguageCode.put("mlt", "mt");
  293. mJavaToIanaLanguageCode.put("nep", "ne");
  294. mJavaToIanaLanguageCode.put("nld", "nl");
  295. mJavaToIanaLanguageCode.put("nor", "no");
  296. mJavaToIanaLanguageCode.put("pan", "pa");
  297. mJavaToIanaLanguageCode.put("pol", "pl");
  298. mJavaToIanaLanguageCode.put("por", "pt");
  299. mJavaToIanaLanguageCode.put("ron", "ro");
  300. mJavaToIanaLanguageCode.put("rus", "ru");
  301. mJavaToIanaLanguageCode.put("kin", "rw");
  302. mJavaToIanaLanguageCode.put("sin", "si");
  303. mJavaToIanaLanguageCode.put("slk", "sk");
  304. mJavaToIanaLanguageCode.put("slv", "sl");
  305. mJavaToIanaLanguageCode.put("sqi", "sq");
  306. mJavaToIanaLanguageCode.put("srp", "sr");
  307. mJavaToIanaLanguageCode.put("swe", "sv");
  308. mJavaToIanaLanguageCode.put("swa", "sw");
  309. mJavaToIanaLanguageCode.put("tam", "ta");
  310. mJavaToIanaLanguageCode.put("tel", "te");
  311. mJavaToIanaLanguageCode.put("tsn", "tn");
  312. mJavaToIanaLanguageCode.put("tur", "tr");
  313. mJavaToIanaLanguageCode.put("tat", "tt");
  314. mJavaToIanaLanguageCode.put("urd", "ur");
  315. mJavaToIanaLanguageCode.put("vie", "vi");
  316. mJavaToIanaLanguageCode.put("wol", "wo");
  317. mJavaToIanaLanguageCode.put("zho", "zh");
  318. mJavaToIanaLanguageCode.put("yue", "zh");
  319. mJavaToIanaCountryCode.put("029", ""); // Locale.getCountry() does not map numeric country codes.
  320. mJavaToIanaCountryCode.put("419", ""); // Locale.getCountry() does not map numeric country codes.
  321. mJavaToIanaCountryCode.put("BEL", "BE");
  322. mJavaToIanaCountryCode.put("BRA", "BR");
  323. mJavaToIanaCountryCode.put("FRA", "FR");
  324. mJavaToIanaCountryCode.put("GBR", "GB");
  325. mJavaToIanaCountryCode.put("PRT", "PT");
  326. mJavaToIanaCountryCode.put("USA", "US");
  327. }
  328. }