michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* vim:set ts=2 sw=2 sts=2 et cindent: */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "nsISupports.h" michael@0: #include "nsPicoService.h" michael@0: #include "nsPrintfCString.h" michael@0: #include "nsIWeakReferenceUtils.h" michael@0: #include "SharedBuffer.h" michael@0: #include "nsISimpleEnumerator.h" michael@0: michael@0: #include "mozilla/dom/nsSynthVoiceRegistry.h" michael@0: #include "mozilla/dom/nsSpeechTask.h" michael@0: michael@0: #include "nsIFile.h" michael@0: #include "nsThreadUtils.h" michael@0: #include "prenv.h" michael@0: michael@0: #include "mozilla/DebugOnly.h" michael@0: #include michael@0: michael@0: // Pico API constants michael@0: michael@0: // Size of memory allocated for pico engine and voice resources. michael@0: // We only have one voice and its resources loaded at once, so this michael@0: // should always be enough. michael@0: #define PICO_MEM_SIZE 2500000 michael@0: michael@0: // Max length of returned strings. Pico will never return longer strings, michael@0: // so this amount should be good enough for preallocating. michael@0: #define PICO_RETSTRINGSIZE 200 michael@0: michael@0: // Max amount we want from a single call of pico_getData michael@0: #define PICO_MAX_CHUNK_SIZE 128 michael@0: michael@0: // Arbitrary name for loaded voice, it doesn't mean anything outside of Pico michael@0: #define PICO_VOICE_NAME "pico" michael@0: michael@0: // Return status from pico_getData meaning there is more data in the pipeline michael@0: // to get from more calls to pico_getData michael@0: #define PICO_STEP_BUSY 201 michael@0: michael@0: // For performing a "soft" reset between utterances. This is used when one michael@0: // utterance is interrupted by a new one. michael@0: #define PICO_RESET_SOFT 0x10 michael@0: michael@0: // Currently, Pico only provides mono output. michael@0: #define PICO_CHANNELS_NUM 1 michael@0: michael@0: // Pico's sample rate is always 16000 michael@0: #define PICO_SAMPLE_RATE 16000 michael@0: michael@0: // The path to the language files in Gonk michael@0: #define GONK_PICO_LANG_PATH "/system/tts/lang_pico" michael@0: michael@0: namespace mozilla { michael@0: namespace dom { michael@0: michael@0: StaticRefPtr nsPicoService::sSingleton; michael@0: michael@0: class PicoApi michael@0: { michael@0: public: michael@0: michael@0: PicoApi() : mInitialized(false) {} michael@0: michael@0: bool Init() michael@0: { michael@0: if (mInitialized) { michael@0: return true; michael@0: } michael@0: michael@0: void* handle = dlopen("libttspico.so", RTLD_LAZY); michael@0: michael@0: if (!handle) { michael@0: NS_WARNING("Failed to open libttspico.so, pico cannot run"); michael@0: return false; michael@0: } michael@0: michael@0: pico_initialize = michael@0: (pico_Status (*)(void*, uint32_t, pico_System*))dlsym( michael@0: handle, "pico_initialize"); michael@0: michael@0: pico_terminate = michael@0: (pico_Status (*)(pico_System*))dlsym(handle, "pico_terminate"); michael@0: michael@0: pico_getSystemStatusMessage = michael@0: (pico_Status (*)(pico_System, pico_Status, pico_Retstring))dlsym( michael@0: handle, "pico_getSystemStatusMessage");; michael@0: michael@0: pico_loadResource = michael@0: (pico_Status (*)(pico_System, const char*, pico_Resource*))dlsym( michael@0: handle, "pico_loadResource"); michael@0: michael@0: pico_unloadResource = michael@0: (pico_Status (*)(pico_System, pico_Resource*))dlsym( michael@0: handle, "pico_unloadResource"); michael@0: michael@0: pico_getResourceName = michael@0: (pico_Status (*)(pico_System, pico_Resource, pico_Retstring))dlsym( michael@0: handle, "pico_getResourceName"); michael@0: michael@0: pico_createVoiceDefinition = michael@0: (pico_Status (*)(pico_System, const char*))dlsym( michael@0: handle, "pico_createVoiceDefinition"); michael@0: michael@0: pico_addResourceToVoiceDefinition = michael@0: (pico_Status (*)(pico_System, const char*, const char*))dlsym( michael@0: handle, "pico_addResourceToVoiceDefinition"); michael@0: michael@0: pico_releaseVoiceDefinition = michael@0: (pico_Status (*)(pico_System, const char*))dlsym( michael@0: handle, "pico_releaseVoiceDefinition"); michael@0: michael@0: pico_newEngine = michael@0: (pico_Status (*)(pico_System, const char*, pico_Engine*))dlsym( michael@0: handle, "pico_newEngine"); michael@0: michael@0: pico_disposeEngine = michael@0: (pico_Status (*)(pico_System, pico_Engine*))dlsym( michael@0: handle, "pico_disposeEngine"); michael@0: michael@0: pico_resetEngine = michael@0: (pico_Status (*)(pico_Engine, int32_t))dlsym(handle, "pico_resetEngine"); michael@0: michael@0: pico_putTextUtf8 = michael@0: (pico_Status (*)(pico_Engine, const char*, const int16_t, int16_t*))dlsym( michael@0: handle, "pico_putTextUtf8"); michael@0: michael@0: pico_getData = michael@0: (pico_Status (*)(pico_Engine, void*, int16_t, int16_t*, int16_t*))dlsym( michael@0: handle, "pico_getData"); michael@0: michael@0: mInitialized = true; michael@0: return true; michael@0: } michael@0: michael@0: typedef signed int pico_Status; michael@0: typedef char pico_Retstring[PICO_RETSTRINGSIZE]; michael@0: michael@0: pico_Status (* pico_initialize)(void*, uint32_t, pico_System*); michael@0: pico_Status (* pico_terminate)(pico_System*); michael@0: pico_Status (* pico_getSystemStatusMessage)( michael@0: pico_System, pico_Status, pico_Retstring); michael@0: michael@0: pico_Status (* pico_loadResource)(pico_System, const char*, pico_Resource*); michael@0: pico_Status (* pico_unloadResource)(pico_System, pico_Resource*); michael@0: pico_Status (* pico_getResourceName)( michael@0: pico_System, pico_Resource, pico_Retstring); michael@0: pico_Status (* pico_createVoiceDefinition)(pico_System, const char*); michael@0: pico_Status (* pico_addResourceToVoiceDefinition)( michael@0: pico_System, const char*, const char*); michael@0: pico_Status (* pico_releaseVoiceDefinition)(pico_System, const char*); michael@0: pico_Status (* pico_newEngine)(pico_System, const char*, pico_Engine*); michael@0: pico_Status (* pico_disposeEngine)(pico_System, pico_Engine*); michael@0: michael@0: pico_Status (* pico_resetEngine)(pico_Engine, int32_t); michael@0: pico_Status (* pico_putTextUtf8)( michael@0: pico_Engine, const char*, const int16_t, int16_t*); michael@0: pico_Status (* pico_getData)( michael@0: pico_Engine, void*, const int16_t, int16_t*, int16_t*); michael@0: michael@0: private: michael@0: michael@0: bool mInitialized; michael@0: michael@0: } sPicoApi; michael@0: michael@0: #define PICO_ENSURE_SUCCESS_VOID(_funcName, _status) \ michael@0: if (_status < 0) { \ michael@0: PicoApi::pico_Retstring message; \ michael@0: sPicoApi.pico_getSystemStatusMessage( \ michael@0: nsPicoService::sSingleton->mPicoSystem, _status, message); \ michael@0: NS_WARNING( \ michael@0: nsPrintfCString("Error running %s: %s", _funcName, message).get()); \ michael@0: return; \ michael@0: } michael@0: michael@0: #define PICO_ENSURE_SUCCESS(_funcName, _status, _rv) \ michael@0: if (_status < 0) { \ michael@0: PicoApi::pico_Retstring message; \ michael@0: sPicoApi.pico_getSystemStatusMessage( \ michael@0: nsPicoService::sSingleton->mPicoSystem, _status, message); \ michael@0: NS_WARNING( \ michael@0: nsPrintfCString("Error running %s: %s", _funcName, message).get()); \ michael@0: return _rv; \ michael@0: } michael@0: michael@0: class PicoVoice michael@0: { michael@0: public: michael@0: michael@0: PicoVoice(const nsAString& aLanguage) michael@0: : mLanguage(aLanguage) {} michael@0: michael@0: ~PicoVoice() {} michael@0: michael@0: NS_INLINE_DECL_THREADSAFE_REFCOUNTING(PicoVoice) michael@0: michael@0: // Voice language, in BCB-47 syntax michael@0: nsString mLanguage; michael@0: michael@0: // Language resource file michael@0: nsCString mTaFile; michael@0: michael@0: // Speaker resource file michael@0: nsCString mSgFile; michael@0: }; michael@0: michael@0: class PicoCallbackRunnable : public nsRunnable, michael@0: public nsISpeechTaskCallback michael@0: { michael@0: friend class PicoSynthDataRunnable; michael@0: michael@0: public: michael@0: PicoCallbackRunnable(const nsAString& aText, PicoVoice* aVoice, michael@0: float aRate, float aPitch, nsISpeechTask* aTask, michael@0: nsPicoService* aService) michael@0: : mText(NS_ConvertUTF16toUTF8(aText)) michael@0: , mRate(aRate) michael@0: , mPitch(aPitch) michael@0: , mFirstData(true) michael@0: , mTask(aTask) michael@0: , mVoice(aVoice) michael@0: , mService(aService) { } michael@0: michael@0: ~PicoCallbackRunnable() { } michael@0: michael@0: NS_DECL_ISUPPORTS_INHERITED michael@0: NS_DECL_NSISPEECHTASKCALLBACK michael@0: michael@0: NS_IMETHOD Run() MOZ_OVERRIDE; michael@0: michael@0: bool IsCurrentTask() { return mService->mCurrentTask == mTask; } michael@0: michael@0: private: michael@0: void DispatchSynthDataRunnable(already_AddRefed&& aBuffer, michael@0: size_t aBufferSize); michael@0: michael@0: nsCString mText; michael@0: michael@0: float mRate; michael@0: michael@0: float mPitch; michael@0: michael@0: bool mFirstData; michael@0: michael@0: // We use this pointer to compare it with the current service task. michael@0: // If they differ, this runnable should stop. michael@0: nsISpeechTask* mTask; michael@0: michael@0: // We hold a strong reference to the service, which in turn holds michael@0: // a strong reference to this voice. michael@0: PicoVoice* mVoice; michael@0: michael@0: // By holding a strong reference to the service we guarantee that it won't be michael@0: // destroyed before this runnable. michael@0: nsRefPtr mService; michael@0: }; michael@0: michael@0: NS_IMPL_ISUPPORTS_INHERITED(PicoCallbackRunnable, nsRunnable, nsISpeechTaskCallback) michael@0: michael@0: // nsRunnable michael@0: michael@0: NS_IMETHODIMP michael@0: PicoCallbackRunnable::Run() michael@0: { michael@0: MOZ_ASSERT(!NS_IsMainThread()); michael@0: PicoApi::pico_Status status = 0; michael@0: michael@0: if (mService->CurrentVoice() != mVoice) { michael@0: mService->LoadEngine(mVoice); michael@0: } else { michael@0: status = sPicoApi.pico_resetEngine(mService->mPicoEngine, PICO_RESET_SOFT); michael@0: PICO_ENSURE_SUCCESS("pico_unloadResource", status, NS_ERROR_FAILURE); michael@0: } michael@0: michael@0: // Add SSML markup for pitch and rate. Pico uses a minimal parser, michael@0: // so no namespace is needed. michael@0: nsPrintfCString markedUpText( michael@0: "%s", michael@0: std::min(std::max(50.0f, mPitch * 100), 200.0f), michael@0: std::min(std::max(20.0f, mRate * 100), 500.0f), michael@0: mText.get()); michael@0: michael@0: const char* text = markedUpText.get(); michael@0: size_t buffer_size = 512, buffer_offset = 0; michael@0: nsRefPtr buffer = SharedBuffer::Create(buffer_size); michael@0: int16_t text_offset = 0, bytes_recv = 0, bytes_sent = 0, out_data_type = 0; michael@0: int16_t text_remaining = markedUpText.Length() + 1; michael@0: michael@0: // Run this loop while this is the current task michael@0: while (IsCurrentTask()) { michael@0: if (text_remaining) { michael@0: status = sPicoApi.pico_putTextUtf8(mService->mPicoEngine, michael@0: text + text_offset, text_remaining, michael@0: &bytes_sent); michael@0: PICO_ENSURE_SUCCESS("pico_putTextUtf8", status, NS_ERROR_FAILURE); michael@0: // XXX: End speech task on error michael@0: text_remaining -= bytes_sent; michael@0: text_offset += bytes_sent; michael@0: } else { michael@0: // If we already fed all the text to the engine, send a zero length buffer michael@0: // and quit. michael@0: DispatchSynthDataRunnable(already_AddRefed(nullptr), 0); michael@0: break; michael@0: } michael@0: michael@0: do { michael@0: // Run this loop while the result of getData is STEP_BUSY, when it finishes michael@0: // synthesizing audio for the given text, it returns STEP_IDLE. We then michael@0: // break to the outer loop and feed more text, if there is any left. michael@0: if (!IsCurrentTask()) { michael@0: // If the task has changed, quit. michael@0: break; michael@0: } michael@0: michael@0: if (buffer_size - buffer_offset < PICO_MAX_CHUNK_SIZE) { michael@0: // The next audio chunk retrieved may be bigger than our buffer, michael@0: // so send the data and flush the buffer. michael@0: DispatchSynthDataRunnable(buffer.forget(), buffer_offset); michael@0: buffer_offset = 0; michael@0: buffer = SharedBuffer::Create(buffer_size); michael@0: } michael@0: michael@0: status = sPicoApi.pico_getData(mService->mPicoEngine, michael@0: (uint8_t*)buffer->Data() + buffer_offset, michael@0: PICO_MAX_CHUNK_SIZE, michael@0: &bytes_recv, &out_data_type); michael@0: PICO_ENSURE_SUCCESS("pico_getData", status, NS_ERROR_FAILURE); michael@0: buffer_offset += bytes_recv; michael@0: } while (status == PICO_STEP_BUSY); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: PicoCallbackRunnable::DispatchSynthDataRunnable( michael@0: already_AddRefed&& aBuffer, size_t aBufferSize) michael@0: { michael@0: class PicoSynthDataRunnable MOZ_FINAL : public nsRunnable michael@0: { michael@0: public: michael@0: PicoSynthDataRunnable(already_AddRefed& aBuffer, michael@0: size_t aBufferSize, bool aFirstData, michael@0: PicoCallbackRunnable* aCallback) michael@0: : mBuffer(aBuffer) michael@0: , mBufferSize(aBufferSize) michael@0: , mFirstData(aFirstData) michael@0: , mCallback(aCallback) { michael@0: } michael@0: michael@0: NS_IMETHOD Run() michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: michael@0: if (!mCallback->IsCurrentTask()) { michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: } michael@0: michael@0: nsISpeechTask* task = mCallback->mTask; michael@0: michael@0: if (mFirstData) { michael@0: task->Setup(mCallback, PICO_CHANNELS_NUM, PICO_SAMPLE_RATE, 2); michael@0: } michael@0: michael@0: return task->SendAudioNative( michael@0: mBufferSize ? static_cast(mBuffer->Data()) : nullptr, mBufferSize / 2); michael@0: } michael@0: michael@0: private: michael@0: nsRefPtr mBuffer; michael@0: michael@0: size_t mBufferSize; michael@0: michael@0: bool mFirstData; michael@0: michael@0: nsRefPtr mCallback; michael@0: }; michael@0: michael@0: nsCOMPtr sendEvent = michael@0: new PicoSynthDataRunnable(aBuffer, aBufferSize, mFirstData, this); michael@0: NS_DispatchToMainThread(sendEvent); michael@0: mFirstData = false; michael@0: } michael@0: michael@0: // nsISpeechTaskCallback michael@0: michael@0: NS_IMETHODIMP michael@0: PicoCallbackRunnable::OnPause() michael@0: { michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: PicoCallbackRunnable::OnResume() michael@0: { michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: PicoCallbackRunnable::OnCancel() michael@0: { michael@0: mService->mCurrentTask = nullptr; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_INTERFACE_MAP_BEGIN(nsPicoService) michael@0: NS_INTERFACE_MAP_ENTRY(nsISpeechService) michael@0: NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsISpeechService) michael@0: NS_INTERFACE_MAP_END michael@0: michael@0: NS_IMPL_ADDREF(nsPicoService) michael@0: NS_IMPL_RELEASE(nsPicoService) michael@0: michael@0: nsPicoService::nsPicoService() michael@0: : mInitialized(false) michael@0: , mVoicesMonitor("nsPicoService::mVoices") michael@0: , mCurrentTask(nullptr) michael@0: , mPicoSystem(nullptr) michael@0: , mPicoEngine(nullptr) michael@0: , mSgResource(nullptr) michael@0: , mTaResource(nullptr) michael@0: , mPicoMemArea(nullptr) michael@0: { michael@0: DebugOnly rv = NS_NewNamedThread("Pico Worker", getter_AddRefs(mThread)); michael@0: MOZ_ASSERT(NS_SUCCEEDED(rv)); michael@0: rv = mThread->Dispatch(NS_NewRunnableMethod(this, &nsPicoService::Init), NS_DISPATCH_NORMAL); michael@0: MOZ_ASSERT(NS_SUCCEEDED(rv)); michael@0: } michael@0: michael@0: nsPicoService::~nsPicoService() michael@0: { michael@0: // We don't worry about removing the voices because this gets michael@0: // destructed at shutdown along with the voice registry. michael@0: MonitorAutoLock autoLock(mVoicesMonitor); michael@0: mVoices.Clear(); michael@0: michael@0: if (mThread) { michael@0: mThread->Shutdown(); michael@0: } michael@0: michael@0: UnloadEngine(); michael@0: } michael@0: michael@0: // nsISpeechService michael@0: michael@0: NS_IMETHODIMP michael@0: nsPicoService::Speak(const nsAString& aText, const nsAString& aUri, michael@0: float aRate, float aPitch, nsISpeechTask* aTask) michael@0: { michael@0: NS_ENSURE_TRUE(mInitialized, NS_ERROR_NOT_AVAILABLE); michael@0: michael@0: MonitorAutoLock autoLock(mVoicesMonitor); michael@0: bool found = false; michael@0: PicoVoice* voice = mVoices.GetWeak(aUri, &found); michael@0: NS_ENSURE_TRUE(found, NS_ERROR_NOT_AVAILABLE); michael@0: michael@0: mCurrentTask = aTask; michael@0: nsRefPtr cb = new PicoCallbackRunnable(aText, voice, aRate, aPitch, aTask, this); michael@0: return mThread->Dispatch(cb, NS_DISPATCH_NORMAL); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsPicoService::GetServiceType(SpeechServiceType* aServiceType) michael@0: { michael@0: *aServiceType = nsISpeechService::SERVICETYPE_DIRECT_AUDIO; michael@0: return NS_OK; michael@0: } michael@0: michael@0: struct VoiceTraverserData michael@0: { michael@0: nsPicoService* mService; michael@0: nsSynthVoiceRegistry* mRegistry; michael@0: }; michael@0: michael@0: // private methods michael@0: michael@0: static PLDHashOperator michael@0: PicoAddVoiceTraverser(const nsAString& aUri, michael@0: nsRefPtr& aVoice, michael@0: void* aUserArg) michael@0: { michael@0: // If we are missing either a language or a voice resource, it is invalid. michael@0: if (aVoice->mTaFile.IsEmpty() || aVoice->mSgFile.IsEmpty()) { michael@0: return PL_DHASH_REMOVE; michael@0: } michael@0: michael@0: VoiceTraverserData* data = static_cast(aUserArg); michael@0: michael@0: nsAutoString name; michael@0: name.AssignLiteral("Pico "); michael@0: name.Append(aVoice->mLanguage); michael@0: michael@0: DebugOnly rv = michael@0: data->mRegistry->AddVoice( michael@0: data->mService, aUri, name, aVoice->mLanguage, true); michael@0: NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "Failed to add voice"); michael@0: michael@0: return PL_DHASH_NEXT; michael@0: } michael@0: michael@0: void michael@0: nsPicoService::Init() michael@0: { michael@0: MOZ_ASSERT(!NS_IsMainThread()); michael@0: MOZ_ASSERT(!mInitialized); michael@0: michael@0: sPicoApi.Init(); michael@0: michael@0: // Use environment variable, or default android/b2g path michael@0: nsAutoCString langPath(PR_GetEnv("PICO_LANG_PATH")); michael@0: michael@0: if (langPath.IsEmpty()) { michael@0: langPath.AssignLiteral(GONK_PICO_LANG_PATH); michael@0: } michael@0: michael@0: nsCOMPtr voicesDir; michael@0: NS_NewNativeLocalFile(langPath, true, getter_AddRefs(voicesDir)); michael@0: michael@0: nsCOMPtr dirIterator; michael@0: nsresult rv = voicesDir->GetDirectoryEntries(getter_AddRefs(dirIterator)); michael@0: michael@0: if (NS_FAILED(rv)) { michael@0: NS_WARNING(nsPrintfCString("Failed to get contents of directory: %s", langPath.get()).get()); michael@0: return; michael@0: } michael@0: michael@0: bool hasMoreElements = false; michael@0: rv = dirIterator->HasMoreElements(&hasMoreElements); michael@0: MOZ_ASSERT(NS_SUCCEEDED(rv)); michael@0: michael@0: MonitorAutoLock autoLock(mVoicesMonitor); michael@0: michael@0: while (hasMoreElements && NS_SUCCEEDED(rv)) { michael@0: nsCOMPtr supports; michael@0: rv = dirIterator->GetNext(getter_AddRefs(supports)); michael@0: MOZ_ASSERT(NS_SUCCEEDED(rv)); michael@0: michael@0: nsCOMPtr voiceFile = do_QueryInterface(supports); michael@0: MOZ_ASSERT(voiceFile); michael@0: michael@0: nsAutoCString leafName; michael@0: voiceFile->GetNativeLeafName(leafName); michael@0: michael@0: nsAutoString lang; michael@0: michael@0: if (GetVoiceFileLanguage(leafName, lang)) { michael@0: nsAutoString uri; michael@0: uri.AssignLiteral("urn:moz-tts:pico:"); michael@0: uri.Append(lang); michael@0: michael@0: bool found = false; michael@0: PicoVoice* voice = mVoices.GetWeak(uri, &found); michael@0: michael@0: if (!found) { michael@0: voice = new PicoVoice(lang); michael@0: mVoices.Put(uri, voice); michael@0: } michael@0: michael@0: // Each voice consists of two lingware files: A language resource file, michael@0: // suffixed by _ta.bin, and a speaker resource file, suffixed by _sb.bin. michael@0: // We currently assume that there is a pair of files for each language. michael@0: if (StringEndsWith(leafName, NS_LITERAL_CSTRING("_ta.bin"))) { michael@0: rv = voiceFile->GetPersistentDescriptor(voice->mTaFile); michael@0: MOZ_ASSERT(NS_SUCCEEDED(rv)); michael@0: } else if (StringEndsWith(leafName, NS_LITERAL_CSTRING("_sg.bin"))) { michael@0: rv = voiceFile->GetPersistentDescriptor(voice->mSgFile); michael@0: MOZ_ASSERT(NS_SUCCEEDED(rv)); michael@0: } michael@0: } michael@0: michael@0: rv = dirIterator->HasMoreElements(&hasMoreElements); michael@0: } michael@0: michael@0: NS_DispatchToMainThread(NS_NewRunnableMethod(this, &nsPicoService::RegisterVoices)); michael@0: } michael@0: michael@0: void michael@0: nsPicoService::RegisterVoices() michael@0: { michael@0: VoiceTraverserData data = { this, nsSynthVoiceRegistry::GetInstance() }; michael@0: mVoices.Enumerate(PicoAddVoiceTraverser, &data); michael@0: michael@0: mInitialized = true; michael@0: } michael@0: michael@0: bool michael@0: nsPicoService::GetVoiceFileLanguage(const nsACString& aFileName, nsAString& aLang) michael@0: { michael@0: nsACString::const_iterator start, end; michael@0: aFileName.BeginReading(start); michael@0: aFileName.EndReading(end); michael@0: michael@0: // The lingware filename syntax is language_(ta/sg).bin, michael@0: // we extract the language prefix here. michael@0: if (FindInReadable(NS_LITERAL_CSTRING("_"), start, end)) { michael@0: end = start; michael@0: aFileName.BeginReading(start); michael@0: aLang.Assign(NS_ConvertUTF8toUTF16(Substring(start, end))); michael@0: return true; michael@0: } michael@0: michael@0: return false; michael@0: } michael@0: michael@0: void michael@0: nsPicoService::LoadEngine(PicoVoice* aVoice) michael@0: { michael@0: PicoApi::pico_Status status = 0; michael@0: michael@0: if (mPicoSystem) { michael@0: UnloadEngine(); michael@0: } michael@0: michael@0: if (!mPicoMemArea) { michael@0: mPicoMemArea = new uint8_t[PICO_MEM_SIZE]; michael@0: } michael@0: michael@0: status = sPicoApi.pico_initialize(mPicoMemArea, PICO_MEM_SIZE, &mPicoSystem); michael@0: PICO_ENSURE_SUCCESS_VOID("pico_initialize", status); michael@0: michael@0: status = sPicoApi.pico_loadResource(mPicoSystem, aVoice->mTaFile.get(), &mTaResource); michael@0: PICO_ENSURE_SUCCESS_VOID("pico_loadResource", status); michael@0: michael@0: status = sPicoApi.pico_loadResource(mPicoSystem, aVoice->mSgFile.get(), &mSgResource); michael@0: PICO_ENSURE_SUCCESS_VOID("pico_loadResource", status); michael@0: michael@0: status = sPicoApi.pico_createVoiceDefinition(mPicoSystem, PICO_VOICE_NAME); michael@0: PICO_ENSURE_SUCCESS_VOID("pico_createVoiceDefinition", status); michael@0: michael@0: char taName[PICO_RETSTRINGSIZE]; michael@0: status = sPicoApi.pico_getResourceName(mPicoSystem, mTaResource, taName); michael@0: PICO_ENSURE_SUCCESS_VOID("pico_getResourceName", status); michael@0: michael@0: status = sPicoApi.pico_addResourceToVoiceDefinition( michael@0: mPicoSystem, PICO_VOICE_NAME, taName); michael@0: PICO_ENSURE_SUCCESS_VOID("pico_addResourceToVoiceDefinition", status); michael@0: michael@0: char sgName[PICO_RETSTRINGSIZE]; michael@0: status = sPicoApi.pico_getResourceName(mPicoSystem, mSgResource, sgName); michael@0: PICO_ENSURE_SUCCESS_VOID("pico_getResourceName", status); michael@0: michael@0: status = sPicoApi.pico_addResourceToVoiceDefinition( michael@0: mPicoSystem, PICO_VOICE_NAME, sgName); michael@0: PICO_ENSURE_SUCCESS_VOID("pico_addResourceToVoiceDefinition", status); michael@0: michael@0: status = sPicoApi.pico_newEngine(mPicoSystem, PICO_VOICE_NAME, &mPicoEngine); michael@0: PICO_ENSURE_SUCCESS_VOID("pico_newEngine", status); michael@0: michael@0: if (sSingleton) { michael@0: sSingleton->mCurrentVoice = aVoice; michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsPicoService::UnloadEngine() michael@0: { michael@0: PicoApi::pico_Status status = 0; michael@0: michael@0: if (mPicoEngine) { michael@0: status = sPicoApi.pico_disposeEngine(mPicoSystem, &mPicoEngine); michael@0: PICO_ENSURE_SUCCESS_VOID("pico_disposeEngine", status); michael@0: status = sPicoApi.pico_releaseVoiceDefinition(mPicoSystem, PICO_VOICE_NAME); michael@0: PICO_ENSURE_SUCCESS_VOID("pico_releaseVoiceDefinition", status); michael@0: mPicoEngine = nullptr; michael@0: } michael@0: michael@0: if (mSgResource) { michael@0: status = sPicoApi.pico_unloadResource(mPicoSystem, &mSgResource); michael@0: PICO_ENSURE_SUCCESS_VOID("pico_unloadResource", status); michael@0: mSgResource = nullptr; michael@0: } michael@0: michael@0: if (mTaResource) { michael@0: status = sPicoApi.pico_unloadResource(mPicoSystem, &mTaResource); michael@0: PICO_ENSURE_SUCCESS_VOID("pico_unloadResource", status); michael@0: mTaResource = nullptr; michael@0: } michael@0: michael@0: if (mPicoSystem) { michael@0: status = sPicoApi.pico_terminate(&mPicoSystem); michael@0: PICO_ENSURE_SUCCESS_VOID("pico_terminate", status); michael@0: mPicoSystem = nullptr; michael@0: } michael@0: } michael@0: michael@0: PicoVoice* michael@0: nsPicoService::CurrentVoice() michael@0: { michael@0: MOZ_ASSERT(!NS_IsMainThread()); michael@0: michael@0: return mCurrentVoice; michael@0: } michael@0: michael@0: // static methods michael@0: michael@0: nsPicoService* michael@0: nsPicoService::GetInstance() michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: if (XRE_GetProcessType() != GeckoProcessType_Default) { michael@0: MOZ_ASSERT(false, "nsPicoService can only be started on main gecko process"); michael@0: return nullptr; michael@0: } michael@0: michael@0: if (!sSingleton) { michael@0: sSingleton = new nsPicoService(); michael@0: } michael@0: michael@0: return sSingleton; michael@0: } michael@0: michael@0: already_AddRefed michael@0: nsPicoService::GetInstanceForService() michael@0: { michael@0: nsRefPtr picoService = GetInstance(); michael@0: return picoService.forget(); michael@0: } michael@0: michael@0: void michael@0: nsPicoService::Shutdown() michael@0: { michael@0: if (!sSingleton) { michael@0: return; michael@0: } michael@0: michael@0: sSingleton->mCurrentTask = nullptr; michael@0: michael@0: sSingleton = nullptr; michael@0: } michael@0: michael@0: } // namespace dom michael@0: } // namespace mozilla