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