|
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
|
2 /* vim:set ts=2 sw=2 sts=2 et cindent: */ |
|
3 /* This Source Code Form is subject to the terms of the Mozilla Public |
|
4 * License, v. 2.0. If a copy of the MPL was not distributed with this |
|
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
6 |
|
7 #include "nsISupports.h" |
|
8 #include "nsPicoService.h" |
|
9 #include "nsPrintfCString.h" |
|
10 #include "nsIWeakReferenceUtils.h" |
|
11 #include "SharedBuffer.h" |
|
12 #include "nsISimpleEnumerator.h" |
|
13 |
|
14 #include "mozilla/dom/nsSynthVoiceRegistry.h" |
|
15 #include "mozilla/dom/nsSpeechTask.h" |
|
16 |
|
17 #include "nsIFile.h" |
|
18 #include "nsThreadUtils.h" |
|
19 #include "prenv.h" |
|
20 |
|
21 #include "mozilla/DebugOnly.h" |
|
22 #include <dlfcn.h> |
|
23 |
|
24 // Pico API constants |
|
25 |
|
26 // Size of memory allocated for pico engine and voice resources. |
|
27 // We only have one voice and its resources loaded at once, so this |
|
28 // should always be enough. |
|
29 #define PICO_MEM_SIZE 2500000 |
|
30 |
|
31 // Max length of returned strings. Pico will never return longer strings, |
|
32 // so this amount should be good enough for preallocating. |
|
33 #define PICO_RETSTRINGSIZE 200 |
|
34 |
|
35 // Max amount we want from a single call of pico_getData |
|
36 #define PICO_MAX_CHUNK_SIZE 128 |
|
37 |
|
38 // Arbitrary name for loaded voice, it doesn't mean anything outside of Pico |
|
39 #define PICO_VOICE_NAME "pico" |
|
40 |
|
41 // Return status from pico_getData meaning there is more data in the pipeline |
|
42 // to get from more calls to pico_getData |
|
43 #define PICO_STEP_BUSY 201 |
|
44 |
|
45 // For performing a "soft" reset between utterances. This is used when one |
|
46 // utterance is interrupted by a new one. |
|
47 #define PICO_RESET_SOFT 0x10 |
|
48 |
|
49 // Currently, Pico only provides mono output. |
|
50 #define PICO_CHANNELS_NUM 1 |
|
51 |
|
52 // Pico's sample rate is always 16000 |
|
53 #define PICO_SAMPLE_RATE 16000 |
|
54 |
|
55 // The path to the language files in Gonk |
|
56 #define GONK_PICO_LANG_PATH "/system/tts/lang_pico" |
|
57 |
|
58 namespace mozilla { |
|
59 namespace dom { |
|
60 |
|
61 StaticRefPtr<nsPicoService> nsPicoService::sSingleton; |
|
62 |
|
63 class PicoApi |
|
64 { |
|
65 public: |
|
66 |
|
67 PicoApi() : mInitialized(false) {} |
|
68 |
|
69 bool Init() |
|
70 { |
|
71 if (mInitialized) { |
|
72 return true; |
|
73 } |
|
74 |
|
75 void* handle = dlopen("libttspico.so", RTLD_LAZY); |
|
76 |
|
77 if (!handle) { |
|
78 NS_WARNING("Failed to open libttspico.so, pico cannot run"); |
|
79 return false; |
|
80 } |
|
81 |
|
82 pico_initialize = |
|
83 (pico_Status (*)(void*, uint32_t, pico_System*))dlsym( |
|
84 handle, "pico_initialize"); |
|
85 |
|
86 pico_terminate = |
|
87 (pico_Status (*)(pico_System*))dlsym(handle, "pico_terminate"); |
|
88 |
|
89 pico_getSystemStatusMessage = |
|
90 (pico_Status (*)(pico_System, pico_Status, pico_Retstring))dlsym( |
|
91 handle, "pico_getSystemStatusMessage");; |
|
92 |
|
93 pico_loadResource = |
|
94 (pico_Status (*)(pico_System, const char*, pico_Resource*))dlsym( |
|
95 handle, "pico_loadResource"); |
|
96 |
|
97 pico_unloadResource = |
|
98 (pico_Status (*)(pico_System, pico_Resource*))dlsym( |
|
99 handle, "pico_unloadResource"); |
|
100 |
|
101 pico_getResourceName = |
|
102 (pico_Status (*)(pico_System, pico_Resource, pico_Retstring))dlsym( |
|
103 handle, "pico_getResourceName"); |
|
104 |
|
105 pico_createVoiceDefinition = |
|
106 (pico_Status (*)(pico_System, const char*))dlsym( |
|
107 handle, "pico_createVoiceDefinition"); |
|
108 |
|
109 pico_addResourceToVoiceDefinition = |
|
110 (pico_Status (*)(pico_System, const char*, const char*))dlsym( |
|
111 handle, "pico_addResourceToVoiceDefinition"); |
|
112 |
|
113 pico_releaseVoiceDefinition = |
|
114 (pico_Status (*)(pico_System, const char*))dlsym( |
|
115 handle, "pico_releaseVoiceDefinition"); |
|
116 |
|
117 pico_newEngine = |
|
118 (pico_Status (*)(pico_System, const char*, pico_Engine*))dlsym( |
|
119 handle, "pico_newEngine"); |
|
120 |
|
121 pico_disposeEngine = |
|
122 (pico_Status (*)(pico_System, pico_Engine*))dlsym( |
|
123 handle, "pico_disposeEngine"); |
|
124 |
|
125 pico_resetEngine = |
|
126 (pico_Status (*)(pico_Engine, int32_t))dlsym(handle, "pico_resetEngine"); |
|
127 |
|
128 pico_putTextUtf8 = |
|
129 (pico_Status (*)(pico_Engine, const char*, const int16_t, int16_t*))dlsym( |
|
130 handle, "pico_putTextUtf8"); |
|
131 |
|
132 pico_getData = |
|
133 (pico_Status (*)(pico_Engine, void*, int16_t, int16_t*, int16_t*))dlsym( |
|
134 handle, "pico_getData"); |
|
135 |
|
136 mInitialized = true; |
|
137 return true; |
|
138 } |
|
139 |
|
140 typedef signed int pico_Status; |
|
141 typedef char pico_Retstring[PICO_RETSTRINGSIZE]; |
|
142 |
|
143 pico_Status (* pico_initialize)(void*, uint32_t, pico_System*); |
|
144 pico_Status (* pico_terminate)(pico_System*); |
|
145 pico_Status (* pico_getSystemStatusMessage)( |
|
146 pico_System, pico_Status, pico_Retstring); |
|
147 |
|
148 pico_Status (* pico_loadResource)(pico_System, const char*, pico_Resource*); |
|
149 pico_Status (* pico_unloadResource)(pico_System, pico_Resource*); |
|
150 pico_Status (* pico_getResourceName)( |
|
151 pico_System, pico_Resource, pico_Retstring); |
|
152 pico_Status (* pico_createVoiceDefinition)(pico_System, const char*); |
|
153 pico_Status (* pico_addResourceToVoiceDefinition)( |
|
154 pico_System, const char*, const char*); |
|
155 pico_Status (* pico_releaseVoiceDefinition)(pico_System, const char*); |
|
156 pico_Status (* pico_newEngine)(pico_System, const char*, pico_Engine*); |
|
157 pico_Status (* pico_disposeEngine)(pico_System, pico_Engine*); |
|
158 |
|
159 pico_Status (* pico_resetEngine)(pico_Engine, int32_t); |
|
160 pico_Status (* pico_putTextUtf8)( |
|
161 pico_Engine, const char*, const int16_t, int16_t*); |
|
162 pico_Status (* pico_getData)( |
|
163 pico_Engine, void*, const int16_t, int16_t*, int16_t*); |
|
164 |
|
165 private: |
|
166 |
|
167 bool mInitialized; |
|
168 |
|
169 } sPicoApi; |
|
170 |
|
171 #define PICO_ENSURE_SUCCESS_VOID(_funcName, _status) \ |
|
172 if (_status < 0) { \ |
|
173 PicoApi::pico_Retstring message; \ |
|
174 sPicoApi.pico_getSystemStatusMessage( \ |
|
175 nsPicoService::sSingleton->mPicoSystem, _status, message); \ |
|
176 NS_WARNING( \ |
|
177 nsPrintfCString("Error running %s: %s", _funcName, message).get()); \ |
|
178 return; \ |
|
179 } |
|
180 |
|
181 #define PICO_ENSURE_SUCCESS(_funcName, _status, _rv) \ |
|
182 if (_status < 0) { \ |
|
183 PicoApi::pico_Retstring message; \ |
|
184 sPicoApi.pico_getSystemStatusMessage( \ |
|
185 nsPicoService::sSingleton->mPicoSystem, _status, message); \ |
|
186 NS_WARNING( \ |
|
187 nsPrintfCString("Error running %s: %s", _funcName, message).get()); \ |
|
188 return _rv; \ |
|
189 } |
|
190 |
|
191 class PicoVoice |
|
192 { |
|
193 public: |
|
194 |
|
195 PicoVoice(const nsAString& aLanguage) |
|
196 : mLanguage(aLanguage) {} |
|
197 |
|
198 ~PicoVoice() {} |
|
199 |
|
200 NS_INLINE_DECL_THREADSAFE_REFCOUNTING(PicoVoice) |
|
201 |
|
202 // Voice language, in BCB-47 syntax |
|
203 nsString mLanguage; |
|
204 |
|
205 // Language resource file |
|
206 nsCString mTaFile; |
|
207 |
|
208 // Speaker resource file |
|
209 nsCString mSgFile; |
|
210 }; |
|
211 |
|
212 class PicoCallbackRunnable : public nsRunnable, |
|
213 public nsISpeechTaskCallback |
|
214 { |
|
215 friend class PicoSynthDataRunnable; |
|
216 |
|
217 public: |
|
218 PicoCallbackRunnable(const nsAString& aText, PicoVoice* aVoice, |
|
219 float aRate, float aPitch, nsISpeechTask* aTask, |
|
220 nsPicoService* aService) |
|
221 : mText(NS_ConvertUTF16toUTF8(aText)) |
|
222 , mRate(aRate) |
|
223 , mPitch(aPitch) |
|
224 , mFirstData(true) |
|
225 , mTask(aTask) |
|
226 , mVoice(aVoice) |
|
227 , mService(aService) { } |
|
228 |
|
229 ~PicoCallbackRunnable() { } |
|
230 |
|
231 NS_DECL_ISUPPORTS_INHERITED |
|
232 NS_DECL_NSISPEECHTASKCALLBACK |
|
233 |
|
234 NS_IMETHOD Run() MOZ_OVERRIDE; |
|
235 |
|
236 bool IsCurrentTask() { return mService->mCurrentTask == mTask; } |
|
237 |
|
238 private: |
|
239 void DispatchSynthDataRunnable(already_AddRefed<SharedBuffer>&& aBuffer, |
|
240 size_t aBufferSize); |
|
241 |
|
242 nsCString mText; |
|
243 |
|
244 float mRate; |
|
245 |
|
246 float mPitch; |
|
247 |
|
248 bool mFirstData; |
|
249 |
|
250 // We use this pointer to compare it with the current service task. |
|
251 // If they differ, this runnable should stop. |
|
252 nsISpeechTask* mTask; |
|
253 |
|
254 // We hold a strong reference to the service, which in turn holds |
|
255 // a strong reference to this voice. |
|
256 PicoVoice* mVoice; |
|
257 |
|
258 // By holding a strong reference to the service we guarantee that it won't be |
|
259 // destroyed before this runnable. |
|
260 nsRefPtr<nsPicoService> mService; |
|
261 }; |
|
262 |
|
263 NS_IMPL_ISUPPORTS_INHERITED(PicoCallbackRunnable, nsRunnable, nsISpeechTaskCallback) |
|
264 |
|
265 // nsRunnable |
|
266 |
|
267 NS_IMETHODIMP |
|
268 PicoCallbackRunnable::Run() |
|
269 { |
|
270 MOZ_ASSERT(!NS_IsMainThread()); |
|
271 PicoApi::pico_Status status = 0; |
|
272 |
|
273 if (mService->CurrentVoice() != mVoice) { |
|
274 mService->LoadEngine(mVoice); |
|
275 } else { |
|
276 status = sPicoApi.pico_resetEngine(mService->mPicoEngine, PICO_RESET_SOFT); |
|
277 PICO_ENSURE_SUCCESS("pico_unloadResource", status, NS_ERROR_FAILURE); |
|
278 } |
|
279 |
|
280 // Add SSML markup for pitch and rate. Pico uses a minimal parser, |
|
281 // so no namespace is needed. |
|
282 nsPrintfCString markedUpText( |
|
283 "<pitch level=\"%0.0f\"><speed level=\"%0.0f\">%s</speed></pitch>", |
|
284 std::min(std::max(50.0f, mPitch * 100), 200.0f), |
|
285 std::min(std::max(20.0f, mRate * 100), 500.0f), |
|
286 mText.get()); |
|
287 |
|
288 const char* text = markedUpText.get(); |
|
289 size_t buffer_size = 512, buffer_offset = 0; |
|
290 nsRefPtr<SharedBuffer> buffer = SharedBuffer::Create(buffer_size); |
|
291 int16_t text_offset = 0, bytes_recv = 0, bytes_sent = 0, out_data_type = 0; |
|
292 int16_t text_remaining = markedUpText.Length() + 1; |
|
293 |
|
294 // Run this loop while this is the current task |
|
295 while (IsCurrentTask()) { |
|
296 if (text_remaining) { |
|
297 status = sPicoApi.pico_putTextUtf8(mService->mPicoEngine, |
|
298 text + text_offset, text_remaining, |
|
299 &bytes_sent); |
|
300 PICO_ENSURE_SUCCESS("pico_putTextUtf8", status, NS_ERROR_FAILURE); |
|
301 // XXX: End speech task on error |
|
302 text_remaining -= bytes_sent; |
|
303 text_offset += bytes_sent; |
|
304 } else { |
|
305 // If we already fed all the text to the engine, send a zero length buffer |
|
306 // and quit. |
|
307 DispatchSynthDataRunnable(already_AddRefed<SharedBuffer>(nullptr), 0); |
|
308 break; |
|
309 } |
|
310 |
|
311 do { |
|
312 // Run this loop while the result of getData is STEP_BUSY, when it finishes |
|
313 // synthesizing audio for the given text, it returns STEP_IDLE. We then |
|
314 // break to the outer loop and feed more text, if there is any left. |
|
315 if (!IsCurrentTask()) { |
|
316 // If the task has changed, quit. |
|
317 break; |
|
318 } |
|
319 |
|
320 if (buffer_size - buffer_offset < PICO_MAX_CHUNK_SIZE) { |
|
321 // The next audio chunk retrieved may be bigger than our buffer, |
|
322 // so send the data and flush the buffer. |
|
323 DispatchSynthDataRunnable(buffer.forget(), buffer_offset); |
|
324 buffer_offset = 0; |
|
325 buffer = SharedBuffer::Create(buffer_size); |
|
326 } |
|
327 |
|
328 status = sPicoApi.pico_getData(mService->mPicoEngine, |
|
329 (uint8_t*)buffer->Data() + buffer_offset, |
|
330 PICO_MAX_CHUNK_SIZE, |
|
331 &bytes_recv, &out_data_type); |
|
332 PICO_ENSURE_SUCCESS("pico_getData", status, NS_ERROR_FAILURE); |
|
333 buffer_offset += bytes_recv; |
|
334 } while (status == PICO_STEP_BUSY); |
|
335 } |
|
336 |
|
337 return NS_OK; |
|
338 } |
|
339 |
|
340 void |
|
341 PicoCallbackRunnable::DispatchSynthDataRunnable( |
|
342 already_AddRefed<SharedBuffer>&& aBuffer, size_t aBufferSize) |
|
343 { |
|
344 class PicoSynthDataRunnable MOZ_FINAL : public nsRunnable |
|
345 { |
|
346 public: |
|
347 PicoSynthDataRunnable(already_AddRefed<SharedBuffer>& aBuffer, |
|
348 size_t aBufferSize, bool aFirstData, |
|
349 PicoCallbackRunnable* aCallback) |
|
350 : mBuffer(aBuffer) |
|
351 , mBufferSize(aBufferSize) |
|
352 , mFirstData(aFirstData) |
|
353 , mCallback(aCallback) { |
|
354 } |
|
355 |
|
356 NS_IMETHOD Run() |
|
357 { |
|
358 MOZ_ASSERT(NS_IsMainThread()); |
|
359 |
|
360 if (!mCallback->IsCurrentTask()) { |
|
361 return NS_ERROR_NOT_AVAILABLE; |
|
362 } |
|
363 |
|
364 nsISpeechTask* task = mCallback->mTask; |
|
365 |
|
366 if (mFirstData) { |
|
367 task->Setup(mCallback, PICO_CHANNELS_NUM, PICO_SAMPLE_RATE, 2); |
|
368 } |
|
369 |
|
370 return task->SendAudioNative( |
|
371 mBufferSize ? static_cast<short*>(mBuffer->Data()) : nullptr, mBufferSize / 2); |
|
372 } |
|
373 |
|
374 private: |
|
375 nsRefPtr<SharedBuffer> mBuffer; |
|
376 |
|
377 size_t mBufferSize; |
|
378 |
|
379 bool mFirstData; |
|
380 |
|
381 nsRefPtr<PicoCallbackRunnable> mCallback; |
|
382 }; |
|
383 |
|
384 nsCOMPtr<nsIRunnable> sendEvent = |
|
385 new PicoSynthDataRunnable(aBuffer, aBufferSize, mFirstData, this); |
|
386 NS_DispatchToMainThread(sendEvent); |
|
387 mFirstData = false; |
|
388 } |
|
389 |
|
390 // nsISpeechTaskCallback |
|
391 |
|
392 NS_IMETHODIMP |
|
393 PicoCallbackRunnable::OnPause() |
|
394 { |
|
395 return NS_OK; |
|
396 } |
|
397 |
|
398 NS_IMETHODIMP |
|
399 PicoCallbackRunnable::OnResume() |
|
400 { |
|
401 return NS_OK; |
|
402 } |
|
403 |
|
404 NS_IMETHODIMP |
|
405 PicoCallbackRunnable::OnCancel() |
|
406 { |
|
407 mService->mCurrentTask = nullptr; |
|
408 return NS_OK; |
|
409 } |
|
410 |
|
411 NS_INTERFACE_MAP_BEGIN(nsPicoService) |
|
412 NS_INTERFACE_MAP_ENTRY(nsISpeechService) |
|
413 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsISpeechService) |
|
414 NS_INTERFACE_MAP_END |
|
415 |
|
416 NS_IMPL_ADDREF(nsPicoService) |
|
417 NS_IMPL_RELEASE(nsPicoService) |
|
418 |
|
419 nsPicoService::nsPicoService() |
|
420 : mInitialized(false) |
|
421 , mVoicesMonitor("nsPicoService::mVoices") |
|
422 , mCurrentTask(nullptr) |
|
423 , mPicoSystem(nullptr) |
|
424 , mPicoEngine(nullptr) |
|
425 , mSgResource(nullptr) |
|
426 , mTaResource(nullptr) |
|
427 , mPicoMemArea(nullptr) |
|
428 { |
|
429 DebugOnly<nsresult> rv = NS_NewNamedThread("Pico Worker", getter_AddRefs(mThread)); |
|
430 MOZ_ASSERT(NS_SUCCEEDED(rv)); |
|
431 rv = mThread->Dispatch(NS_NewRunnableMethod(this, &nsPicoService::Init), NS_DISPATCH_NORMAL); |
|
432 MOZ_ASSERT(NS_SUCCEEDED(rv)); |
|
433 } |
|
434 |
|
435 nsPicoService::~nsPicoService() |
|
436 { |
|
437 // We don't worry about removing the voices because this gets |
|
438 // destructed at shutdown along with the voice registry. |
|
439 MonitorAutoLock autoLock(mVoicesMonitor); |
|
440 mVoices.Clear(); |
|
441 |
|
442 if (mThread) { |
|
443 mThread->Shutdown(); |
|
444 } |
|
445 |
|
446 UnloadEngine(); |
|
447 } |
|
448 |
|
449 // nsISpeechService |
|
450 |
|
451 NS_IMETHODIMP |
|
452 nsPicoService::Speak(const nsAString& aText, const nsAString& aUri, |
|
453 float aRate, float aPitch, nsISpeechTask* aTask) |
|
454 { |
|
455 NS_ENSURE_TRUE(mInitialized, NS_ERROR_NOT_AVAILABLE); |
|
456 |
|
457 MonitorAutoLock autoLock(mVoicesMonitor); |
|
458 bool found = false; |
|
459 PicoVoice* voice = mVoices.GetWeak(aUri, &found); |
|
460 NS_ENSURE_TRUE(found, NS_ERROR_NOT_AVAILABLE); |
|
461 |
|
462 mCurrentTask = aTask; |
|
463 nsRefPtr<PicoCallbackRunnable> cb = new PicoCallbackRunnable(aText, voice, aRate, aPitch, aTask, this); |
|
464 return mThread->Dispatch(cb, NS_DISPATCH_NORMAL); |
|
465 } |
|
466 |
|
467 NS_IMETHODIMP |
|
468 nsPicoService::GetServiceType(SpeechServiceType* aServiceType) |
|
469 { |
|
470 *aServiceType = nsISpeechService::SERVICETYPE_DIRECT_AUDIO; |
|
471 return NS_OK; |
|
472 } |
|
473 |
|
474 struct VoiceTraverserData |
|
475 { |
|
476 nsPicoService* mService; |
|
477 nsSynthVoiceRegistry* mRegistry; |
|
478 }; |
|
479 |
|
480 // private methods |
|
481 |
|
482 static PLDHashOperator |
|
483 PicoAddVoiceTraverser(const nsAString& aUri, |
|
484 nsRefPtr<PicoVoice>& aVoice, |
|
485 void* aUserArg) |
|
486 { |
|
487 // If we are missing either a language or a voice resource, it is invalid. |
|
488 if (aVoice->mTaFile.IsEmpty() || aVoice->mSgFile.IsEmpty()) { |
|
489 return PL_DHASH_REMOVE; |
|
490 } |
|
491 |
|
492 VoiceTraverserData* data = static_cast<VoiceTraverserData*>(aUserArg); |
|
493 |
|
494 nsAutoString name; |
|
495 name.AssignLiteral("Pico "); |
|
496 name.Append(aVoice->mLanguage); |
|
497 |
|
498 DebugOnly<nsresult> rv = |
|
499 data->mRegistry->AddVoice( |
|
500 data->mService, aUri, name, aVoice->mLanguage, true); |
|
501 NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "Failed to add voice"); |
|
502 |
|
503 return PL_DHASH_NEXT; |
|
504 } |
|
505 |
|
506 void |
|
507 nsPicoService::Init() |
|
508 { |
|
509 MOZ_ASSERT(!NS_IsMainThread()); |
|
510 MOZ_ASSERT(!mInitialized); |
|
511 |
|
512 sPicoApi.Init(); |
|
513 |
|
514 // Use environment variable, or default android/b2g path |
|
515 nsAutoCString langPath(PR_GetEnv("PICO_LANG_PATH")); |
|
516 |
|
517 if (langPath.IsEmpty()) { |
|
518 langPath.AssignLiteral(GONK_PICO_LANG_PATH); |
|
519 } |
|
520 |
|
521 nsCOMPtr<nsIFile> voicesDir; |
|
522 NS_NewNativeLocalFile(langPath, true, getter_AddRefs(voicesDir)); |
|
523 |
|
524 nsCOMPtr<nsISimpleEnumerator> dirIterator; |
|
525 nsresult rv = voicesDir->GetDirectoryEntries(getter_AddRefs(dirIterator)); |
|
526 |
|
527 if (NS_FAILED(rv)) { |
|
528 NS_WARNING(nsPrintfCString("Failed to get contents of directory: %s", langPath.get()).get()); |
|
529 return; |
|
530 } |
|
531 |
|
532 bool hasMoreElements = false; |
|
533 rv = dirIterator->HasMoreElements(&hasMoreElements); |
|
534 MOZ_ASSERT(NS_SUCCEEDED(rv)); |
|
535 |
|
536 MonitorAutoLock autoLock(mVoicesMonitor); |
|
537 |
|
538 while (hasMoreElements && NS_SUCCEEDED(rv)) { |
|
539 nsCOMPtr<nsISupports> supports; |
|
540 rv = dirIterator->GetNext(getter_AddRefs(supports)); |
|
541 MOZ_ASSERT(NS_SUCCEEDED(rv)); |
|
542 |
|
543 nsCOMPtr<nsIFile> voiceFile = do_QueryInterface(supports); |
|
544 MOZ_ASSERT(voiceFile); |
|
545 |
|
546 nsAutoCString leafName; |
|
547 voiceFile->GetNativeLeafName(leafName); |
|
548 |
|
549 nsAutoString lang; |
|
550 |
|
551 if (GetVoiceFileLanguage(leafName, lang)) { |
|
552 nsAutoString uri; |
|
553 uri.AssignLiteral("urn:moz-tts:pico:"); |
|
554 uri.Append(lang); |
|
555 |
|
556 bool found = false; |
|
557 PicoVoice* voice = mVoices.GetWeak(uri, &found); |
|
558 |
|
559 if (!found) { |
|
560 voice = new PicoVoice(lang); |
|
561 mVoices.Put(uri, voice); |
|
562 } |
|
563 |
|
564 // Each voice consists of two lingware files: A language resource file, |
|
565 // suffixed by _ta.bin, and a speaker resource file, suffixed by _sb.bin. |
|
566 // We currently assume that there is a pair of files for each language. |
|
567 if (StringEndsWith(leafName, NS_LITERAL_CSTRING("_ta.bin"))) { |
|
568 rv = voiceFile->GetPersistentDescriptor(voice->mTaFile); |
|
569 MOZ_ASSERT(NS_SUCCEEDED(rv)); |
|
570 } else if (StringEndsWith(leafName, NS_LITERAL_CSTRING("_sg.bin"))) { |
|
571 rv = voiceFile->GetPersistentDescriptor(voice->mSgFile); |
|
572 MOZ_ASSERT(NS_SUCCEEDED(rv)); |
|
573 } |
|
574 } |
|
575 |
|
576 rv = dirIterator->HasMoreElements(&hasMoreElements); |
|
577 } |
|
578 |
|
579 NS_DispatchToMainThread(NS_NewRunnableMethod(this, &nsPicoService::RegisterVoices)); |
|
580 } |
|
581 |
|
582 void |
|
583 nsPicoService::RegisterVoices() |
|
584 { |
|
585 VoiceTraverserData data = { this, nsSynthVoiceRegistry::GetInstance() }; |
|
586 mVoices.Enumerate(PicoAddVoiceTraverser, &data); |
|
587 |
|
588 mInitialized = true; |
|
589 } |
|
590 |
|
591 bool |
|
592 nsPicoService::GetVoiceFileLanguage(const nsACString& aFileName, nsAString& aLang) |
|
593 { |
|
594 nsACString::const_iterator start, end; |
|
595 aFileName.BeginReading(start); |
|
596 aFileName.EndReading(end); |
|
597 |
|
598 // The lingware filename syntax is language_(ta/sg).bin, |
|
599 // we extract the language prefix here. |
|
600 if (FindInReadable(NS_LITERAL_CSTRING("_"), start, end)) { |
|
601 end = start; |
|
602 aFileName.BeginReading(start); |
|
603 aLang.Assign(NS_ConvertUTF8toUTF16(Substring(start, end))); |
|
604 return true; |
|
605 } |
|
606 |
|
607 return false; |
|
608 } |
|
609 |
|
610 void |
|
611 nsPicoService::LoadEngine(PicoVoice* aVoice) |
|
612 { |
|
613 PicoApi::pico_Status status = 0; |
|
614 |
|
615 if (mPicoSystem) { |
|
616 UnloadEngine(); |
|
617 } |
|
618 |
|
619 if (!mPicoMemArea) { |
|
620 mPicoMemArea = new uint8_t[PICO_MEM_SIZE]; |
|
621 } |
|
622 |
|
623 status = sPicoApi.pico_initialize(mPicoMemArea, PICO_MEM_SIZE, &mPicoSystem); |
|
624 PICO_ENSURE_SUCCESS_VOID("pico_initialize", status); |
|
625 |
|
626 status = sPicoApi.pico_loadResource(mPicoSystem, aVoice->mTaFile.get(), &mTaResource); |
|
627 PICO_ENSURE_SUCCESS_VOID("pico_loadResource", status); |
|
628 |
|
629 status = sPicoApi.pico_loadResource(mPicoSystem, aVoice->mSgFile.get(), &mSgResource); |
|
630 PICO_ENSURE_SUCCESS_VOID("pico_loadResource", status); |
|
631 |
|
632 status = sPicoApi.pico_createVoiceDefinition(mPicoSystem, PICO_VOICE_NAME); |
|
633 PICO_ENSURE_SUCCESS_VOID("pico_createVoiceDefinition", status); |
|
634 |
|
635 char taName[PICO_RETSTRINGSIZE]; |
|
636 status = sPicoApi.pico_getResourceName(mPicoSystem, mTaResource, taName); |
|
637 PICO_ENSURE_SUCCESS_VOID("pico_getResourceName", status); |
|
638 |
|
639 status = sPicoApi.pico_addResourceToVoiceDefinition( |
|
640 mPicoSystem, PICO_VOICE_NAME, taName); |
|
641 PICO_ENSURE_SUCCESS_VOID("pico_addResourceToVoiceDefinition", status); |
|
642 |
|
643 char sgName[PICO_RETSTRINGSIZE]; |
|
644 status = sPicoApi.pico_getResourceName(mPicoSystem, mSgResource, sgName); |
|
645 PICO_ENSURE_SUCCESS_VOID("pico_getResourceName", status); |
|
646 |
|
647 status = sPicoApi.pico_addResourceToVoiceDefinition( |
|
648 mPicoSystem, PICO_VOICE_NAME, sgName); |
|
649 PICO_ENSURE_SUCCESS_VOID("pico_addResourceToVoiceDefinition", status); |
|
650 |
|
651 status = sPicoApi.pico_newEngine(mPicoSystem, PICO_VOICE_NAME, &mPicoEngine); |
|
652 PICO_ENSURE_SUCCESS_VOID("pico_newEngine", status); |
|
653 |
|
654 if (sSingleton) { |
|
655 sSingleton->mCurrentVoice = aVoice; |
|
656 } |
|
657 } |
|
658 |
|
659 void |
|
660 nsPicoService::UnloadEngine() |
|
661 { |
|
662 PicoApi::pico_Status status = 0; |
|
663 |
|
664 if (mPicoEngine) { |
|
665 status = sPicoApi.pico_disposeEngine(mPicoSystem, &mPicoEngine); |
|
666 PICO_ENSURE_SUCCESS_VOID("pico_disposeEngine", status); |
|
667 status = sPicoApi.pico_releaseVoiceDefinition(mPicoSystem, PICO_VOICE_NAME); |
|
668 PICO_ENSURE_SUCCESS_VOID("pico_releaseVoiceDefinition", status); |
|
669 mPicoEngine = nullptr; |
|
670 } |
|
671 |
|
672 if (mSgResource) { |
|
673 status = sPicoApi.pico_unloadResource(mPicoSystem, &mSgResource); |
|
674 PICO_ENSURE_SUCCESS_VOID("pico_unloadResource", status); |
|
675 mSgResource = nullptr; |
|
676 } |
|
677 |
|
678 if (mTaResource) { |
|
679 status = sPicoApi.pico_unloadResource(mPicoSystem, &mTaResource); |
|
680 PICO_ENSURE_SUCCESS_VOID("pico_unloadResource", status); |
|
681 mTaResource = nullptr; |
|
682 } |
|
683 |
|
684 if (mPicoSystem) { |
|
685 status = sPicoApi.pico_terminate(&mPicoSystem); |
|
686 PICO_ENSURE_SUCCESS_VOID("pico_terminate", status); |
|
687 mPicoSystem = nullptr; |
|
688 } |
|
689 } |
|
690 |
|
691 PicoVoice* |
|
692 nsPicoService::CurrentVoice() |
|
693 { |
|
694 MOZ_ASSERT(!NS_IsMainThread()); |
|
695 |
|
696 return mCurrentVoice; |
|
697 } |
|
698 |
|
699 // static methods |
|
700 |
|
701 nsPicoService* |
|
702 nsPicoService::GetInstance() |
|
703 { |
|
704 MOZ_ASSERT(NS_IsMainThread()); |
|
705 if (XRE_GetProcessType() != GeckoProcessType_Default) { |
|
706 MOZ_ASSERT(false, "nsPicoService can only be started on main gecko process"); |
|
707 return nullptr; |
|
708 } |
|
709 |
|
710 if (!sSingleton) { |
|
711 sSingleton = new nsPicoService(); |
|
712 } |
|
713 |
|
714 return sSingleton; |
|
715 } |
|
716 |
|
717 already_AddRefed<nsPicoService> |
|
718 nsPicoService::GetInstanceForService() |
|
719 { |
|
720 nsRefPtr<nsPicoService> picoService = GetInstance(); |
|
721 return picoService.forget(); |
|
722 } |
|
723 |
|
724 void |
|
725 nsPicoService::Shutdown() |
|
726 { |
|
727 if (!sSingleton) { |
|
728 return; |
|
729 } |
|
730 |
|
731 sSingleton->mCurrentTask = nullptr; |
|
732 |
|
733 sSingleton = nullptr; |
|
734 } |
|
735 |
|
736 } // namespace dom |
|
737 } // namespace mozilla |