Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
godotengine
GitHub Repository: godotengine/godot
Path: blob/master/platform/android/tts_android.cpp
10277 views
1
/**************************************************************************/
2
/* tts_android.cpp */
3
/**************************************************************************/
4
/* This file is part of: */
5
/* GODOT ENGINE */
6
/* https://godotengine.org */
7
/**************************************************************************/
8
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
9
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
10
/* */
11
/* Permission is hereby granted, free of charge, to any person obtaining */
12
/* a copy of this software and associated documentation files (the */
13
/* "Software"), to deal in the Software without restriction, including */
14
/* without limitation the rights to use, copy, modify, merge, publish, */
15
/* distribute, sublicense, and/or sell copies of the Software, and to */
16
/* permit persons to whom the Software is furnished to do so, subject to */
17
/* the following conditions: */
18
/* */
19
/* The above copyright notice and this permission notice shall be */
20
/* included in all copies or substantial portions of the Software. */
21
/* */
22
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
23
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
24
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
25
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
26
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
27
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
28
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
29
/**************************************************************************/
30
31
#include "tts_android.h"
32
33
#include "java_godot_wrapper.h"
34
#include "os_android.h"
35
#include "thread_jandroid.h"
36
37
bool TTS_Android::initialized = false;
38
jobject TTS_Android::tts = nullptr;
39
jclass TTS_Android::cls = nullptr;
40
41
Thread TTS_Android::init_thread;
42
SafeFlag TTS_Android::quit_request;
43
SafeFlag TTS_Android::init_done;
44
45
jmethodID TTS_Android::_init = nullptr;
46
jmethodID TTS_Android::_is_speaking = nullptr;
47
jmethodID TTS_Android::_is_paused = nullptr;
48
jmethodID TTS_Android::_get_state = nullptr;
49
jmethodID TTS_Android::_get_voices = nullptr;
50
jmethodID TTS_Android::_speak = nullptr;
51
jmethodID TTS_Android::_pause_speaking = nullptr;
52
jmethodID TTS_Android::_resume_speaking = nullptr;
53
jmethodID TTS_Android::_stop_speaking = nullptr;
54
55
HashMap<int, Char16String> TTS_Android::ids;
56
57
void TTS_Android::_thread_function(void *self) {
58
JNIEnv *env = get_jni_env();
59
ERR_FAIL_NULL(env);
60
61
env->CallVoidMethod(tts, _init);
62
63
uint64_t sleep = 200;
64
while (env->CallIntMethod(tts, _get_state) == INIT_STATE_UNKNOWN && !quit_request.is_set()) {
65
OS::get_singleton()->delay_usec(1000 * sleep);
66
}
67
init_done.set();
68
}
69
70
void TTS_Android::initialize_tts(bool p_wait) {
71
if (!_init || !_get_state || !tts) {
72
return;
73
}
74
JNIEnv *env = get_jni_env();
75
ERR_FAIL_NULL(env);
76
77
if (!init_thread.is_started() && !init_done.is_set()) {
78
init_thread.start(TTS_Android::_thread_function, nullptr);
79
}
80
81
if (env->CallIntMethod(tts, _get_state) == INIT_STATE_SUCCESS) {
82
initialized = true;
83
return;
84
}
85
86
// If it's not initialized at launch wait for 1 second for TTS init.
87
if (p_wait) {
88
uint64_t sleep = 200;
89
uint64_t wait = 1000000;
90
uint64_t time = OS::get_singleton()->get_ticks_usec();
91
while (OS::get_singleton()->get_ticks_usec() - time < wait) {
92
OS::get_singleton()->delay_usec(1000 * sleep);
93
if (init_done.is_set()) {
94
break;
95
}
96
}
97
}
98
99
if (env->CallIntMethod(tts, _get_state) == INIT_STATE_SUCCESS) {
100
initialized = true;
101
}
102
}
103
104
void TTS_Android::setup(jobject p_tts) {
105
JNIEnv *env = get_jni_env();
106
ERR_FAIL_NULL(env);
107
108
tts = env->NewGlobalRef(p_tts);
109
quit_request.clear();
110
init_done.clear();
111
112
jclass c = env->GetObjectClass(tts);
113
cls = (jclass)env->NewGlobalRef(c);
114
115
_init = env->GetMethodID(cls, "init", "()V");
116
_is_speaking = env->GetMethodID(cls, "isSpeaking", "()Z");
117
_is_paused = env->GetMethodID(cls, "isPaused", "()Z");
118
_get_state = env->GetMethodID(cls, "getState", "()I");
119
_get_voices = env->GetMethodID(cls, "getVoices", "()[Ljava/lang/String;");
120
_speak = env->GetMethodID(cls, "speak", "(Ljava/lang/String;Ljava/lang/String;IFFIZ)V");
121
_pause_speaking = env->GetMethodID(cls, "pauseSpeaking", "()V");
122
_resume_speaking = env->GetMethodID(cls, "resumeSpeaking", "()V");
123
_stop_speaking = env->GetMethodID(cls, "stopSpeaking", "()V");
124
125
bool tts_enabled = GLOBAL_GET("audio/general/text_to_speech");
126
if (tts_enabled) {
127
initialize_tts(false);
128
}
129
}
130
131
void TTS_Android::terminate() {
132
JNIEnv *env = get_jni_env();
133
ERR_FAIL_NULL(env);
134
135
if (init_thread.is_started()) {
136
quit_request.set();
137
init_thread.wait_to_finish();
138
}
139
140
if (cls) {
141
env->DeleteGlobalRef(cls);
142
}
143
if (tts) {
144
env->DeleteGlobalRef(tts);
145
}
146
}
147
148
void TTS_Android::_java_utterance_callback(int p_event, int p_id, int p_pos) {
149
if (unlikely(!initialized)) {
150
initialize_tts();
151
}
152
ERR_FAIL_COND_MSG(!initialized || tts == nullptr, "Text to Speech not initialized.");
153
if (ids.has(p_id)) {
154
int pos = 0;
155
if ((DisplayServer::TTSUtteranceEvent)p_event == DisplayServer::TTS_UTTERANCE_BOUNDARY) {
156
// Convert position from UTF-16 to UTF-32.
157
const Char16String &string = ids[p_id];
158
for (int i = 0; i < MIN(p_pos, string.length()); i++) {
159
char16_t c = string[i];
160
if ((c & 0xfffffc00) == 0xd800) {
161
i++;
162
}
163
pos++;
164
}
165
} else if ((DisplayServer::TTSUtteranceEvent)p_event != DisplayServer::TTS_UTTERANCE_STARTED) {
166
ids.erase(p_id);
167
}
168
DisplayServer::get_singleton()->tts_post_utterance_event((DisplayServer::TTSUtteranceEvent)p_event, p_id, pos);
169
}
170
}
171
172
bool TTS_Android::is_speaking() {
173
if (unlikely(!initialized)) {
174
initialize_tts();
175
}
176
ERR_FAIL_COND_V_MSG(!initialized || tts == nullptr, false, "Text to Speech not initialized.");
177
if (_is_speaking) {
178
JNIEnv *env = get_jni_env();
179
180
ERR_FAIL_NULL_V(env, false);
181
return env->CallBooleanMethod(tts, _is_speaking);
182
} else {
183
return false;
184
}
185
}
186
187
bool TTS_Android::is_paused() {
188
if (unlikely(!initialized)) {
189
initialize_tts();
190
}
191
ERR_FAIL_COND_V_MSG(!initialized || tts == nullptr, false, "Text to Speech not initialized.");
192
if (_is_paused) {
193
JNIEnv *env = get_jni_env();
194
195
ERR_FAIL_NULL_V(env, false);
196
return env->CallBooleanMethod(tts, _is_paused);
197
} else {
198
return false;
199
}
200
}
201
202
Array TTS_Android::get_voices() {
203
if (unlikely(!initialized)) {
204
initialize_tts();
205
}
206
ERR_FAIL_COND_V_MSG(!initialized || tts == nullptr, Array(), "Text to Speech not initialized.");
207
Array list;
208
if (_get_voices) {
209
JNIEnv *env = get_jni_env();
210
ERR_FAIL_NULL_V(env, list);
211
212
jobject voices_object = env->CallObjectMethod(tts, _get_voices);
213
jobjectArray *arr = reinterpret_cast<jobjectArray *>(&voices_object);
214
215
jsize len = env->GetArrayLength(*arr);
216
for (int i = 0; i < len; i++) {
217
jstring jStr = (jstring)env->GetObjectArrayElement(*arr, i);
218
String str = jstring_to_string(jStr, env);
219
Vector<String> tokens = str.split(";", true, 2);
220
if (tokens.size() == 2) {
221
Dictionary voice_d;
222
voice_d["name"] = tokens[1];
223
voice_d["id"] = tokens[1];
224
voice_d["language"] = tokens[0];
225
list.push_back(voice_d);
226
}
227
env->DeleteLocalRef(jStr);
228
}
229
}
230
return list;
231
}
232
233
void TTS_Android::speak(const String &p_text, const String &p_voice, int p_volume, float p_pitch, float p_rate, int p_utterance_id, bool p_interrupt) {
234
if (unlikely(!initialized)) {
235
initialize_tts();
236
}
237
ERR_FAIL_COND_MSG(!initialized || tts == nullptr, "Text to Speech not initialized.");
238
if (p_interrupt) {
239
stop();
240
}
241
242
if (p_text.is_empty()) {
243
DisplayServer::get_singleton()->tts_post_utterance_event(DisplayServer::TTS_UTTERANCE_CANCELED, p_utterance_id);
244
return;
245
}
246
247
ids[p_utterance_id] = p_text.utf16();
248
249
if (_speak) {
250
JNIEnv *env = get_jni_env();
251
ERR_FAIL_NULL(env);
252
253
jstring jStrT = env->NewStringUTF(p_text.utf8().get_data());
254
jstring jStrV = env->NewStringUTF(p_voice.utf8().get_data());
255
env->CallVoidMethod(tts, _speak, jStrT, jStrV, CLAMP(p_volume, 0, 100), CLAMP(p_pitch, 0.f, 2.f), CLAMP(p_rate, 0.1f, 10.f), p_utterance_id, p_interrupt);
256
env->DeleteLocalRef(jStrT);
257
env->DeleteLocalRef(jStrV);
258
}
259
}
260
261
void TTS_Android::pause() {
262
if (unlikely(!initialized)) {
263
initialize_tts();
264
}
265
ERR_FAIL_COND_MSG(!initialized || tts == nullptr, "Text to Speech not initialized.");
266
if (_pause_speaking) {
267
JNIEnv *env = get_jni_env();
268
269
ERR_FAIL_NULL(env);
270
env->CallVoidMethod(tts, _pause_speaking);
271
}
272
}
273
274
void TTS_Android::resume() {
275
if (unlikely(!initialized)) {
276
initialize_tts();
277
}
278
ERR_FAIL_COND_MSG(!initialized || tts == nullptr, "Text to Speech not initialized.");
279
if (_resume_speaking) {
280
JNIEnv *env = get_jni_env();
281
282
ERR_FAIL_NULL(env);
283
env->CallVoidMethod(tts, _resume_speaking);
284
}
285
}
286
287
void TTS_Android::stop() {
288
if (unlikely(!initialized)) {
289
initialize_tts();
290
}
291
ERR_FAIL_COND_MSG(!initialized || tts == nullptr, "Text to Speech not initialized.");
292
for (const KeyValue<int, Char16String> &E : ids) {
293
DisplayServer::get_singleton()->tts_post_utterance_event(DisplayServer::TTS_UTTERANCE_CANCELED, E.key);
294
}
295
ids.clear();
296
297
if (_stop_speaking) {
298
JNIEnv *env = get_jni_env();
299
300
ERR_FAIL_NULL(env);
301
env->CallVoidMethod(tts, _stop_speaking);
302
}
303
}
304
305