1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/content/media/webspeech/synth/pico/nsPicoService.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,737 @@ 1.4 +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 1.5 +/* vim:set ts=2 sw=2 sts=2 et cindent: */ 1.6 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.7 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.8 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.9 + 1.10 +#include "nsISupports.h" 1.11 +#include "nsPicoService.h" 1.12 +#include "nsPrintfCString.h" 1.13 +#include "nsIWeakReferenceUtils.h" 1.14 +#include "SharedBuffer.h" 1.15 +#include "nsISimpleEnumerator.h" 1.16 + 1.17 +#include "mozilla/dom/nsSynthVoiceRegistry.h" 1.18 +#include "mozilla/dom/nsSpeechTask.h" 1.19 + 1.20 +#include "nsIFile.h" 1.21 +#include "nsThreadUtils.h" 1.22 +#include "prenv.h" 1.23 + 1.24 +#include "mozilla/DebugOnly.h" 1.25 +#include <dlfcn.h> 1.26 + 1.27 +// Pico API constants 1.28 + 1.29 +// Size of memory allocated for pico engine and voice resources. 1.30 +// We only have one voice and its resources loaded at once, so this 1.31 +// should always be enough. 1.32 +#define PICO_MEM_SIZE 2500000 1.33 + 1.34 +// Max length of returned strings. Pico will never return longer strings, 1.35 +// so this amount should be good enough for preallocating. 1.36 +#define PICO_RETSTRINGSIZE 200 1.37 + 1.38 +// Max amount we want from a single call of pico_getData 1.39 +#define PICO_MAX_CHUNK_SIZE 128 1.40 + 1.41 +// Arbitrary name for loaded voice, it doesn't mean anything outside of Pico 1.42 +#define PICO_VOICE_NAME "pico" 1.43 + 1.44 +// Return status from pico_getData meaning there is more data in the pipeline 1.45 +// to get from more calls to pico_getData 1.46 +#define PICO_STEP_BUSY 201 1.47 + 1.48 +// For performing a "soft" reset between utterances. This is used when one 1.49 +// utterance is interrupted by a new one. 1.50 +#define PICO_RESET_SOFT 0x10 1.51 + 1.52 +// Currently, Pico only provides mono output. 1.53 +#define PICO_CHANNELS_NUM 1 1.54 + 1.55 +// Pico's sample rate is always 16000 1.56 +#define PICO_SAMPLE_RATE 16000 1.57 + 1.58 +// The path to the language files in Gonk 1.59 +#define GONK_PICO_LANG_PATH "/system/tts/lang_pico" 1.60 + 1.61 +namespace mozilla { 1.62 +namespace dom { 1.63 + 1.64 +StaticRefPtr<nsPicoService> nsPicoService::sSingleton; 1.65 + 1.66 +class PicoApi 1.67 +{ 1.68 +public: 1.69 + 1.70 + PicoApi() : mInitialized(false) {} 1.71 + 1.72 + bool Init() 1.73 + { 1.74 + if (mInitialized) { 1.75 + return true; 1.76 + } 1.77 + 1.78 + void* handle = dlopen("libttspico.so", RTLD_LAZY); 1.79 + 1.80 + if (!handle) { 1.81 + NS_WARNING("Failed to open libttspico.so, pico cannot run"); 1.82 + return false; 1.83 + } 1.84 + 1.85 + pico_initialize = 1.86 + (pico_Status (*)(void*, uint32_t, pico_System*))dlsym( 1.87 + handle, "pico_initialize"); 1.88 + 1.89 + pico_terminate = 1.90 + (pico_Status (*)(pico_System*))dlsym(handle, "pico_terminate"); 1.91 + 1.92 + pico_getSystemStatusMessage = 1.93 + (pico_Status (*)(pico_System, pico_Status, pico_Retstring))dlsym( 1.94 + handle, "pico_getSystemStatusMessage");; 1.95 + 1.96 + pico_loadResource = 1.97 + (pico_Status (*)(pico_System, const char*, pico_Resource*))dlsym( 1.98 + handle, "pico_loadResource"); 1.99 + 1.100 + pico_unloadResource = 1.101 + (pico_Status (*)(pico_System, pico_Resource*))dlsym( 1.102 + handle, "pico_unloadResource"); 1.103 + 1.104 + pico_getResourceName = 1.105 + (pico_Status (*)(pico_System, pico_Resource, pico_Retstring))dlsym( 1.106 + handle, "pico_getResourceName"); 1.107 + 1.108 + pico_createVoiceDefinition = 1.109 + (pico_Status (*)(pico_System, const char*))dlsym( 1.110 + handle, "pico_createVoiceDefinition"); 1.111 + 1.112 + pico_addResourceToVoiceDefinition = 1.113 + (pico_Status (*)(pico_System, const char*, const char*))dlsym( 1.114 + handle, "pico_addResourceToVoiceDefinition"); 1.115 + 1.116 + pico_releaseVoiceDefinition = 1.117 + (pico_Status (*)(pico_System, const char*))dlsym( 1.118 + handle, "pico_releaseVoiceDefinition"); 1.119 + 1.120 + pico_newEngine = 1.121 + (pico_Status (*)(pico_System, const char*, pico_Engine*))dlsym( 1.122 + handle, "pico_newEngine"); 1.123 + 1.124 + pico_disposeEngine = 1.125 + (pico_Status (*)(pico_System, pico_Engine*))dlsym( 1.126 + handle, "pico_disposeEngine"); 1.127 + 1.128 + pico_resetEngine = 1.129 + (pico_Status (*)(pico_Engine, int32_t))dlsym(handle, "pico_resetEngine"); 1.130 + 1.131 + pico_putTextUtf8 = 1.132 + (pico_Status (*)(pico_Engine, const char*, const int16_t, int16_t*))dlsym( 1.133 + handle, "pico_putTextUtf8"); 1.134 + 1.135 + pico_getData = 1.136 + (pico_Status (*)(pico_Engine, void*, int16_t, int16_t*, int16_t*))dlsym( 1.137 + handle, "pico_getData"); 1.138 + 1.139 + mInitialized = true; 1.140 + return true; 1.141 + } 1.142 + 1.143 + typedef signed int pico_Status; 1.144 + typedef char pico_Retstring[PICO_RETSTRINGSIZE]; 1.145 + 1.146 + pico_Status (* pico_initialize)(void*, uint32_t, pico_System*); 1.147 + pico_Status (* pico_terminate)(pico_System*); 1.148 + pico_Status (* pico_getSystemStatusMessage)( 1.149 + pico_System, pico_Status, pico_Retstring); 1.150 + 1.151 + pico_Status (* pico_loadResource)(pico_System, const char*, pico_Resource*); 1.152 + pico_Status (* pico_unloadResource)(pico_System, pico_Resource*); 1.153 + pico_Status (* pico_getResourceName)( 1.154 + pico_System, pico_Resource, pico_Retstring); 1.155 + pico_Status (* pico_createVoiceDefinition)(pico_System, const char*); 1.156 + pico_Status (* pico_addResourceToVoiceDefinition)( 1.157 + pico_System, const char*, const char*); 1.158 + pico_Status (* pico_releaseVoiceDefinition)(pico_System, const char*); 1.159 + pico_Status (* pico_newEngine)(pico_System, const char*, pico_Engine*); 1.160 + pico_Status (* pico_disposeEngine)(pico_System, pico_Engine*); 1.161 + 1.162 + pico_Status (* pico_resetEngine)(pico_Engine, int32_t); 1.163 + pico_Status (* pico_putTextUtf8)( 1.164 + pico_Engine, const char*, const int16_t, int16_t*); 1.165 + pico_Status (* pico_getData)( 1.166 + pico_Engine, void*, const int16_t, int16_t*, int16_t*); 1.167 + 1.168 +private: 1.169 + 1.170 + bool mInitialized; 1.171 + 1.172 +} sPicoApi; 1.173 + 1.174 +#define PICO_ENSURE_SUCCESS_VOID(_funcName, _status) \ 1.175 + if (_status < 0) { \ 1.176 + PicoApi::pico_Retstring message; \ 1.177 + sPicoApi.pico_getSystemStatusMessage( \ 1.178 + nsPicoService::sSingleton->mPicoSystem, _status, message); \ 1.179 + NS_WARNING( \ 1.180 + nsPrintfCString("Error running %s: %s", _funcName, message).get()); \ 1.181 + return; \ 1.182 + } 1.183 + 1.184 +#define PICO_ENSURE_SUCCESS(_funcName, _status, _rv) \ 1.185 + if (_status < 0) { \ 1.186 + PicoApi::pico_Retstring message; \ 1.187 + sPicoApi.pico_getSystemStatusMessage( \ 1.188 + nsPicoService::sSingleton->mPicoSystem, _status, message); \ 1.189 + NS_WARNING( \ 1.190 + nsPrintfCString("Error running %s: %s", _funcName, message).get()); \ 1.191 + return _rv; \ 1.192 + } 1.193 + 1.194 +class PicoVoice 1.195 +{ 1.196 +public: 1.197 + 1.198 + PicoVoice(const nsAString& aLanguage) 1.199 + : mLanguage(aLanguage) {} 1.200 + 1.201 + ~PicoVoice() {} 1.202 + 1.203 + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(PicoVoice) 1.204 + 1.205 + // Voice language, in BCB-47 syntax 1.206 + nsString mLanguage; 1.207 + 1.208 + // Language resource file 1.209 + nsCString mTaFile; 1.210 + 1.211 + // Speaker resource file 1.212 + nsCString mSgFile; 1.213 +}; 1.214 + 1.215 +class PicoCallbackRunnable : public nsRunnable, 1.216 + public nsISpeechTaskCallback 1.217 +{ 1.218 + friend class PicoSynthDataRunnable; 1.219 + 1.220 +public: 1.221 + PicoCallbackRunnable(const nsAString& aText, PicoVoice* aVoice, 1.222 + float aRate, float aPitch, nsISpeechTask* aTask, 1.223 + nsPicoService* aService) 1.224 + : mText(NS_ConvertUTF16toUTF8(aText)) 1.225 + , mRate(aRate) 1.226 + , mPitch(aPitch) 1.227 + , mFirstData(true) 1.228 + , mTask(aTask) 1.229 + , mVoice(aVoice) 1.230 + , mService(aService) { } 1.231 + 1.232 + ~PicoCallbackRunnable() { } 1.233 + 1.234 + NS_DECL_ISUPPORTS_INHERITED 1.235 + NS_DECL_NSISPEECHTASKCALLBACK 1.236 + 1.237 + NS_IMETHOD Run() MOZ_OVERRIDE; 1.238 + 1.239 + bool IsCurrentTask() { return mService->mCurrentTask == mTask; } 1.240 + 1.241 +private: 1.242 + void DispatchSynthDataRunnable(already_AddRefed<SharedBuffer>&& aBuffer, 1.243 + size_t aBufferSize); 1.244 + 1.245 + nsCString mText; 1.246 + 1.247 + float mRate; 1.248 + 1.249 + float mPitch; 1.250 + 1.251 + bool mFirstData; 1.252 + 1.253 + // We use this pointer to compare it with the current service task. 1.254 + // If they differ, this runnable should stop. 1.255 + nsISpeechTask* mTask; 1.256 + 1.257 + // We hold a strong reference to the service, which in turn holds 1.258 + // a strong reference to this voice. 1.259 + PicoVoice* mVoice; 1.260 + 1.261 + // By holding a strong reference to the service we guarantee that it won't be 1.262 + // destroyed before this runnable. 1.263 + nsRefPtr<nsPicoService> mService; 1.264 +}; 1.265 + 1.266 +NS_IMPL_ISUPPORTS_INHERITED(PicoCallbackRunnable, nsRunnable, nsISpeechTaskCallback) 1.267 + 1.268 +// nsRunnable 1.269 + 1.270 +NS_IMETHODIMP 1.271 +PicoCallbackRunnable::Run() 1.272 +{ 1.273 + MOZ_ASSERT(!NS_IsMainThread()); 1.274 + PicoApi::pico_Status status = 0; 1.275 + 1.276 + if (mService->CurrentVoice() != mVoice) { 1.277 + mService->LoadEngine(mVoice); 1.278 + } else { 1.279 + status = sPicoApi.pico_resetEngine(mService->mPicoEngine, PICO_RESET_SOFT); 1.280 + PICO_ENSURE_SUCCESS("pico_unloadResource", status, NS_ERROR_FAILURE); 1.281 + } 1.282 + 1.283 + // Add SSML markup for pitch and rate. Pico uses a minimal parser, 1.284 + // so no namespace is needed. 1.285 + nsPrintfCString markedUpText( 1.286 + "<pitch level=\"%0.0f\"><speed level=\"%0.0f\">%s</speed></pitch>", 1.287 + std::min(std::max(50.0f, mPitch * 100), 200.0f), 1.288 + std::min(std::max(20.0f, mRate * 100), 500.0f), 1.289 + mText.get()); 1.290 + 1.291 + const char* text = markedUpText.get(); 1.292 + size_t buffer_size = 512, buffer_offset = 0; 1.293 + nsRefPtr<SharedBuffer> buffer = SharedBuffer::Create(buffer_size); 1.294 + int16_t text_offset = 0, bytes_recv = 0, bytes_sent = 0, out_data_type = 0; 1.295 + int16_t text_remaining = markedUpText.Length() + 1; 1.296 + 1.297 + // Run this loop while this is the current task 1.298 + while (IsCurrentTask()) { 1.299 + if (text_remaining) { 1.300 + status = sPicoApi.pico_putTextUtf8(mService->mPicoEngine, 1.301 + text + text_offset, text_remaining, 1.302 + &bytes_sent); 1.303 + PICO_ENSURE_SUCCESS("pico_putTextUtf8", status, NS_ERROR_FAILURE); 1.304 + // XXX: End speech task on error 1.305 + text_remaining -= bytes_sent; 1.306 + text_offset += bytes_sent; 1.307 + } else { 1.308 + // If we already fed all the text to the engine, send a zero length buffer 1.309 + // and quit. 1.310 + DispatchSynthDataRunnable(already_AddRefed<SharedBuffer>(nullptr), 0); 1.311 + break; 1.312 + } 1.313 + 1.314 + do { 1.315 + // Run this loop while the result of getData is STEP_BUSY, when it finishes 1.316 + // synthesizing audio for the given text, it returns STEP_IDLE. We then 1.317 + // break to the outer loop and feed more text, if there is any left. 1.318 + if (!IsCurrentTask()) { 1.319 + // If the task has changed, quit. 1.320 + break; 1.321 + } 1.322 + 1.323 + if (buffer_size - buffer_offset < PICO_MAX_CHUNK_SIZE) { 1.324 + // The next audio chunk retrieved may be bigger than our buffer, 1.325 + // so send the data and flush the buffer. 1.326 + DispatchSynthDataRunnable(buffer.forget(), buffer_offset); 1.327 + buffer_offset = 0; 1.328 + buffer = SharedBuffer::Create(buffer_size); 1.329 + } 1.330 + 1.331 + status = sPicoApi.pico_getData(mService->mPicoEngine, 1.332 + (uint8_t*)buffer->Data() + buffer_offset, 1.333 + PICO_MAX_CHUNK_SIZE, 1.334 + &bytes_recv, &out_data_type); 1.335 + PICO_ENSURE_SUCCESS("pico_getData", status, NS_ERROR_FAILURE); 1.336 + buffer_offset += bytes_recv; 1.337 + } while (status == PICO_STEP_BUSY); 1.338 + } 1.339 + 1.340 + return NS_OK; 1.341 +} 1.342 + 1.343 +void 1.344 +PicoCallbackRunnable::DispatchSynthDataRunnable( 1.345 + already_AddRefed<SharedBuffer>&& aBuffer, size_t aBufferSize) 1.346 +{ 1.347 + class PicoSynthDataRunnable MOZ_FINAL : public nsRunnable 1.348 + { 1.349 + public: 1.350 + PicoSynthDataRunnable(already_AddRefed<SharedBuffer>& aBuffer, 1.351 + size_t aBufferSize, bool aFirstData, 1.352 + PicoCallbackRunnable* aCallback) 1.353 + : mBuffer(aBuffer) 1.354 + , mBufferSize(aBufferSize) 1.355 + , mFirstData(aFirstData) 1.356 + , mCallback(aCallback) { 1.357 + } 1.358 + 1.359 + NS_IMETHOD Run() 1.360 + { 1.361 + MOZ_ASSERT(NS_IsMainThread()); 1.362 + 1.363 + if (!mCallback->IsCurrentTask()) { 1.364 + return NS_ERROR_NOT_AVAILABLE; 1.365 + } 1.366 + 1.367 + nsISpeechTask* task = mCallback->mTask; 1.368 + 1.369 + if (mFirstData) { 1.370 + task->Setup(mCallback, PICO_CHANNELS_NUM, PICO_SAMPLE_RATE, 2); 1.371 + } 1.372 + 1.373 + return task->SendAudioNative( 1.374 + mBufferSize ? static_cast<short*>(mBuffer->Data()) : nullptr, mBufferSize / 2); 1.375 + } 1.376 + 1.377 + private: 1.378 + nsRefPtr<SharedBuffer> mBuffer; 1.379 + 1.380 + size_t mBufferSize; 1.381 + 1.382 + bool mFirstData; 1.383 + 1.384 + nsRefPtr<PicoCallbackRunnable> mCallback; 1.385 + }; 1.386 + 1.387 + nsCOMPtr<nsIRunnable> sendEvent = 1.388 + new PicoSynthDataRunnable(aBuffer, aBufferSize, mFirstData, this); 1.389 + NS_DispatchToMainThread(sendEvent); 1.390 + mFirstData = false; 1.391 +} 1.392 + 1.393 +// nsISpeechTaskCallback 1.394 + 1.395 +NS_IMETHODIMP 1.396 +PicoCallbackRunnable::OnPause() 1.397 +{ 1.398 + return NS_OK; 1.399 +} 1.400 + 1.401 +NS_IMETHODIMP 1.402 +PicoCallbackRunnable::OnResume() 1.403 +{ 1.404 + return NS_OK; 1.405 +} 1.406 + 1.407 +NS_IMETHODIMP 1.408 +PicoCallbackRunnable::OnCancel() 1.409 +{ 1.410 + mService->mCurrentTask = nullptr; 1.411 + return NS_OK; 1.412 +} 1.413 + 1.414 +NS_INTERFACE_MAP_BEGIN(nsPicoService) 1.415 + NS_INTERFACE_MAP_ENTRY(nsISpeechService) 1.416 + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsISpeechService) 1.417 +NS_INTERFACE_MAP_END 1.418 + 1.419 +NS_IMPL_ADDREF(nsPicoService) 1.420 +NS_IMPL_RELEASE(nsPicoService) 1.421 + 1.422 +nsPicoService::nsPicoService() 1.423 + : mInitialized(false) 1.424 + , mVoicesMonitor("nsPicoService::mVoices") 1.425 + , mCurrentTask(nullptr) 1.426 + , mPicoSystem(nullptr) 1.427 + , mPicoEngine(nullptr) 1.428 + , mSgResource(nullptr) 1.429 + , mTaResource(nullptr) 1.430 + , mPicoMemArea(nullptr) 1.431 +{ 1.432 + DebugOnly<nsresult> rv = NS_NewNamedThread("Pico Worker", getter_AddRefs(mThread)); 1.433 + MOZ_ASSERT(NS_SUCCEEDED(rv)); 1.434 + rv = mThread->Dispatch(NS_NewRunnableMethod(this, &nsPicoService::Init), NS_DISPATCH_NORMAL); 1.435 + MOZ_ASSERT(NS_SUCCEEDED(rv)); 1.436 +} 1.437 + 1.438 +nsPicoService::~nsPicoService() 1.439 +{ 1.440 + // We don't worry about removing the voices because this gets 1.441 + // destructed at shutdown along with the voice registry. 1.442 + MonitorAutoLock autoLock(mVoicesMonitor); 1.443 + mVoices.Clear(); 1.444 + 1.445 + if (mThread) { 1.446 + mThread->Shutdown(); 1.447 + } 1.448 + 1.449 + UnloadEngine(); 1.450 +} 1.451 + 1.452 +// nsISpeechService 1.453 + 1.454 +NS_IMETHODIMP 1.455 +nsPicoService::Speak(const nsAString& aText, const nsAString& aUri, 1.456 + float aRate, float aPitch, nsISpeechTask* aTask) 1.457 +{ 1.458 + NS_ENSURE_TRUE(mInitialized, NS_ERROR_NOT_AVAILABLE); 1.459 + 1.460 + MonitorAutoLock autoLock(mVoicesMonitor); 1.461 + bool found = false; 1.462 + PicoVoice* voice = mVoices.GetWeak(aUri, &found); 1.463 + NS_ENSURE_TRUE(found, NS_ERROR_NOT_AVAILABLE); 1.464 + 1.465 + mCurrentTask = aTask; 1.466 + nsRefPtr<PicoCallbackRunnable> cb = new PicoCallbackRunnable(aText, voice, aRate, aPitch, aTask, this); 1.467 + return mThread->Dispatch(cb, NS_DISPATCH_NORMAL); 1.468 +} 1.469 + 1.470 +NS_IMETHODIMP 1.471 +nsPicoService::GetServiceType(SpeechServiceType* aServiceType) 1.472 +{ 1.473 + *aServiceType = nsISpeechService::SERVICETYPE_DIRECT_AUDIO; 1.474 + return NS_OK; 1.475 +} 1.476 + 1.477 +struct VoiceTraverserData 1.478 +{ 1.479 + nsPicoService* mService; 1.480 + nsSynthVoiceRegistry* mRegistry; 1.481 +}; 1.482 + 1.483 +// private methods 1.484 + 1.485 +static PLDHashOperator 1.486 +PicoAddVoiceTraverser(const nsAString& aUri, 1.487 + nsRefPtr<PicoVoice>& aVoice, 1.488 + void* aUserArg) 1.489 +{ 1.490 + // If we are missing either a language or a voice resource, it is invalid. 1.491 + if (aVoice->mTaFile.IsEmpty() || aVoice->mSgFile.IsEmpty()) { 1.492 + return PL_DHASH_REMOVE; 1.493 + } 1.494 + 1.495 + VoiceTraverserData* data = static_cast<VoiceTraverserData*>(aUserArg); 1.496 + 1.497 + nsAutoString name; 1.498 + name.AssignLiteral("Pico "); 1.499 + name.Append(aVoice->mLanguage); 1.500 + 1.501 + DebugOnly<nsresult> rv = 1.502 + data->mRegistry->AddVoice( 1.503 + data->mService, aUri, name, aVoice->mLanguage, true); 1.504 + NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "Failed to add voice"); 1.505 + 1.506 + return PL_DHASH_NEXT; 1.507 +} 1.508 + 1.509 +void 1.510 +nsPicoService::Init() 1.511 +{ 1.512 + MOZ_ASSERT(!NS_IsMainThread()); 1.513 + MOZ_ASSERT(!mInitialized); 1.514 + 1.515 + sPicoApi.Init(); 1.516 + 1.517 + // Use environment variable, or default android/b2g path 1.518 + nsAutoCString langPath(PR_GetEnv("PICO_LANG_PATH")); 1.519 + 1.520 + if (langPath.IsEmpty()) { 1.521 + langPath.AssignLiteral(GONK_PICO_LANG_PATH); 1.522 + } 1.523 + 1.524 + nsCOMPtr<nsIFile> voicesDir; 1.525 + NS_NewNativeLocalFile(langPath, true, getter_AddRefs(voicesDir)); 1.526 + 1.527 + nsCOMPtr<nsISimpleEnumerator> dirIterator; 1.528 + nsresult rv = voicesDir->GetDirectoryEntries(getter_AddRefs(dirIterator)); 1.529 + 1.530 + if (NS_FAILED(rv)) { 1.531 + NS_WARNING(nsPrintfCString("Failed to get contents of directory: %s", langPath.get()).get()); 1.532 + return; 1.533 + } 1.534 + 1.535 + bool hasMoreElements = false; 1.536 + rv = dirIterator->HasMoreElements(&hasMoreElements); 1.537 + MOZ_ASSERT(NS_SUCCEEDED(rv)); 1.538 + 1.539 + MonitorAutoLock autoLock(mVoicesMonitor); 1.540 + 1.541 + while (hasMoreElements && NS_SUCCEEDED(rv)) { 1.542 + nsCOMPtr<nsISupports> supports; 1.543 + rv = dirIterator->GetNext(getter_AddRefs(supports)); 1.544 + MOZ_ASSERT(NS_SUCCEEDED(rv)); 1.545 + 1.546 + nsCOMPtr<nsIFile> voiceFile = do_QueryInterface(supports); 1.547 + MOZ_ASSERT(voiceFile); 1.548 + 1.549 + nsAutoCString leafName; 1.550 + voiceFile->GetNativeLeafName(leafName); 1.551 + 1.552 + nsAutoString lang; 1.553 + 1.554 + if (GetVoiceFileLanguage(leafName, lang)) { 1.555 + nsAutoString uri; 1.556 + uri.AssignLiteral("urn:moz-tts:pico:"); 1.557 + uri.Append(lang); 1.558 + 1.559 + bool found = false; 1.560 + PicoVoice* voice = mVoices.GetWeak(uri, &found); 1.561 + 1.562 + if (!found) { 1.563 + voice = new PicoVoice(lang); 1.564 + mVoices.Put(uri, voice); 1.565 + } 1.566 + 1.567 + // Each voice consists of two lingware files: A language resource file, 1.568 + // suffixed by _ta.bin, and a speaker resource file, suffixed by _sb.bin. 1.569 + // We currently assume that there is a pair of files for each language. 1.570 + if (StringEndsWith(leafName, NS_LITERAL_CSTRING("_ta.bin"))) { 1.571 + rv = voiceFile->GetPersistentDescriptor(voice->mTaFile); 1.572 + MOZ_ASSERT(NS_SUCCEEDED(rv)); 1.573 + } else if (StringEndsWith(leafName, NS_LITERAL_CSTRING("_sg.bin"))) { 1.574 + rv = voiceFile->GetPersistentDescriptor(voice->mSgFile); 1.575 + MOZ_ASSERT(NS_SUCCEEDED(rv)); 1.576 + } 1.577 + } 1.578 + 1.579 + rv = dirIterator->HasMoreElements(&hasMoreElements); 1.580 + } 1.581 + 1.582 + NS_DispatchToMainThread(NS_NewRunnableMethod(this, &nsPicoService::RegisterVoices)); 1.583 +} 1.584 + 1.585 +void 1.586 +nsPicoService::RegisterVoices() 1.587 +{ 1.588 + VoiceTraverserData data = { this, nsSynthVoiceRegistry::GetInstance() }; 1.589 + mVoices.Enumerate(PicoAddVoiceTraverser, &data); 1.590 + 1.591 + mInitialized = true; 1.592 +} 1.593 + 1.594 +bool 1.595 +nsPicoService::GetVoiceFileLanguage(const nsACString& aFileName, nsAString& aLang) 1.596 +{ 1.597 + nsACString::const_iterator start, end; 1.598 + aFileName.BeginReading(start); 1.599 + aFileName.EndReading(end); 1.600 + 1.601 + // The lingware filename syntax is language_(ta/sg).bin, 1.602 + // we extract the language prefix here. 1.603 + if (FindInReadable(NS_LITERAL_CSTRING("_"), start, end)) { 1.604 + end = start; 1.605 + aFileName.BeginReading(start); 1.606 + aLang.Assign(NS_ConvertUTF8toUTF16(Substring(start, end))); 1.607 + return true; 1.608 + } 1.609 + 1.610 + return false; 1.611 +} 1.612 + 1.613 +void 1.614 +nsPicoService::LoadEngine(PicoVoice* aVoice) 1.615 +{ 1.616 + PicoApi::pico_Status status = 0; 1.617 + 1.618 + if (mPicoSystem) { 1.619 + UnloadEngine(); 1.620 + } 1.621 + 1.622 + if (!mPicoMemArea) { 1.623 + mPicoMemArea = new uint8_t[PICO_MEM_SIZE]; 1.624 + } 1.625 + 1.626 + status = sPicoApi.pico_initialize(mPicoMemArea, PICO_MEM_SIZE, &mPicoSystem); 1.627 + PICO_ENSURE_SUCCESS_VOID("pico_initialize", status); 1.628 + 1.629 + status = sPicoApi.pico_loadResource(mPicoSystem, aVoice->mTaFile.get(), &mTaResource); 1.630 + PICO_ENSURE_SUCCESS_VOID("pico_loadResource", status); 1.631 + 1.632 + status = sPicoApi.pico_loadResource(mPicoSystem, aVoice->mSgFile.get(), &mSgResource); 1.633 + PICO_ENSURE_SUCCESS_VOID("pico_loadResource", status); 1.634 + 1.635 + status = sPicoApi.pico_createVoiceDefinition(mPicoSystem, PICO_VOICE_NAME); 1.636 + PICO_ENSURE_SUCCESS_VOID("pico_createVoiceDefinition", status); 1.637 + 1.638 + char taName[PICO_RETSTRINGSIZE]; 1.639 + status = sPicoApi.pico_getResourceName(mPicoSystem, mTaResource, taName); 1.640 + PICO_ENSURE_SUCCESS_VOID("pico_getResourceName", status); 1.641 + 1.642 + status = sPicoApi.pico_addResourceToVoiceDefinition( 1.643 + mPicoSystem, PICO_VOICE_NAME, taName); 1.644 + PICO_ENSURE_SUCCESS_VOID("pico_addResourceToVoiceDefinition", status); 1.645 + 1.646 + char sgName[PICO_RETSTRINGSIZE]; 1.647 + status = sPicoApi.pico_getResourceName(mPicoSystem, mSgResource, sgName); 1.648 + PICO_ENSURE_SUCCESS_VOID("pico_getResourceName", status); 1.649 + 1.650 + status = sPicoApi.pico_addResourceToVoiceDefinition( 1.651 + mPicoSystem, PICO_VOICE_NAME, sgName); 1.652 + PICO_ENSURE_SUCCESS_VOID("pico_addResourceToVoiceDefinition", status); 1.653 + 1.654 + status = sPicoApi.pico_newEngine(mPicoSystem, PICO_VOICE_NAME, &mPicoEngine); 1.655 + PICO_ENSURE_SUCCESS_VOID("pico_newEngine", status); 1.656 + 1.657 + if (sSingleton) { 1.658 + sSingleton->mCurrentVoice = aVoice; 1.659 + } 1.660 +} 1.661 + 1.662 +void 1.663 +nsPicoService::UnloadEngine() 1.664 +{ 1.665 + PicoApi::pico_Status status = 0; 1.666 + 1.667 + if (mPicoEngine) { 1.668 + status = sPicoApi.pico_disposeEngine(mPicoSystem, &mPicoEngine); 1.669 + PICO_ENSURE_SUCCESS_VOID("pico_disposeEngine", status); 1.670 + status = sPicoApi.pico_releaseVoiceDefinition(mPicoSystem, PICO_VOICE_NAME); 1.671 + PICO_ENSURE_SUCCESS_VOID("pico_releaseVoiceDefinition", status); 1.672 + mPicoEngine = nullptr; 1.673 + } 1.674 + 1.675 + if (mSgResource) { 1.676 + status = sPicoApi.pico_unloadResource(mPicoSystem, &mSgResource); 1.677 + PICO_ENSURE_SUCCESS_VOID("pico_unloadResource", status); 1.678 + mSgResource = nullptr; 1.679 + } 1.680 + 1.681 + if (mTaResource) { 1.682 + status = sPicoApi.pico_unloadResource(mPicoSystem, &mTaResource); 1.683 + PICO_ENSURE_SUCCESS_VOID("pico_unloadResource", status); 1.684 + mTaResource = nullptr; 1.685 + } 1.686 + 1.687 + if (mPicoSystem) { 1.688 + status = sPicoApi.pico_terminate(&mPicoSystem); 1.689 + PICO_ENSURE_SUCCESS_VOID("pico_terminate", status); 1.690 + mPicoSystem = nullptr; 1.691 + } 1.692 +} 1.693 + 1.694 +PicoVoice* 1.695 +nsPicoService::CurrentVoice() 1.696 +{ 1.697 + MOZ_ASSERT(!NS_IsMainThread()); 1.698 + 1.699 + return mCurrentVoice; 1.700 +} 1.701 + 1.702 +// static methods 1.703 + 1.704 +nsPicoService* 1.705 +nsPicoService::GetInstance() 1.706 +{ 1.707 + MOZ_ASSERT(NS_IsMainThread()); 1.708 + if (XRE_GetProcessType() != GeckoProcessType_Default) { 1.709 + MOZ_ASSERT(false, "nsPicoService can only be started on main gecko process"); 1.710 + return nullptr; 1.711 + } 1.712 + 1.713 + if (!sSingleton) { 1.714 + sSingleton = new nsPicoService(); 1.715 + } 1.716 + 1.717 + return sSingleton; 1.718 +} 1.719 + 1.720 +already_AddRefed<nsPicoService> 1.721 +nsPicoService::GetInstanceForService() 1.722 +{ 1.723 + nsRefPtr<nsPicoService> picoService = GetInstance(); 1.724 + return picoService.forget(); 1.725 +} 1.726 + 1.727 +void 1.728 +nsPicoService::Shutdown() 1.729 +{ 1.730 + if (!sSingleton) { 1.731 + return; 1.732 + } 1.733 + 1.734 + sSingleton->mCurrentTask = nullptr; 1.735 + 1.736 + sSingleton = nullptr; 1.737 +} 1.738 + 1.739 +} // namespace dom 1.740 +} // namespace mozilla