content/media/webspeech/synth/pico/nsPicoService.cpp

Tue, 06 Jan 2015 21:39:09 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Tue, 06 Jan 2015 21:39:09 +0100
branch
TOR_BUG_9701
changeset 8
97036ab72558
permissions
-rw-r--r--

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

mercurial