From f41e1f808fbcf9014c0a5668fba4eff6dd051366 Mon Sep 17 00:00:00 2001 From: Bjorn Bringert Date: Mon, 14 Mar 2011 18:42:10 +0000 Subject: Port Pico to new TTS engine API Requires TTS engine API added in change I7614ff788e11f897e87052f684f1b4938d539fb7 The compatibility layer in pico/compat/src/com/android/tts/compat/SynthProxy.java pico/compat/jni/com_android_tts_compat_SynthProxy.cpp is based on these files removed from the old TTS engine framework: frameworks/base/packages/TtsService/src/android/tts/SynthProxy.java frameworks/base/packages/TtsService/jni/android_tts_SynthProxy.cpp Bug: 4150618 Change-Id: I7a2cca6b5cfbac6158a87fad69cc796140adb2f3 --- pico/Android.mk | 31 +- pico/AndroidManifest.xml | 7 + pico/compat/include/TtsEngine.h | 232 +++++++ pico/compat/jni/Android.mk | 30 + .../jni/com_android_tts_compat_SynthProxy.cpp | 706 +++++++++++++++++++++ pico/compat/jni/tts.h | 313 +++++++++ .../com/android/tts/compat/CompatTtsService.java | 145 +++++ .../src/com/android/tts/compat/SynthProxy.java | 183 ++++++ pico/proguard.flags | 2 + pico/src/com/svox/pico/PicoService.java | 29 + pico/tts/com_svox_picottsengine.cpp | 4 +- 11 files changed, 1679 insertions(+), 3 deletions(-) create mode 100644 pico/compat/include/TtsEngine.h create mode 100755 pico/compat/jni/Android.mk create mode 100644 pico/compat/jni/com_android_tts_compat_SynthProxy.cpp create mode 100644 pico/compat/jni/tts.h create mode 100755 pico/compat/src/com/android/tts/compat/CompatTtsService.java create mode 100755 pico/compat/src/com/android/tts/compat/SynthProxy.java create mode 100644 pico/proguard.flags create mode 100644 pico/src/com/svox/pico/PicoService.java (limited to 'pico') diff --git a/pico/Android.mk b/pico/Android.mk index 59f7adf..c6b0e8a 100755 --- a/pico/Android.mk +++ b/pico/Android.mk @@ -12,12 +12,17 @@ include $(CLEAR_VARS) LOCAL_MODULE_TAGS := optional -LOCAL_SRC_FILES := $(call all-subdir-java-files) +LOCAL_SRC_FILES := $(call all-java-files-under, src) \ + $(call all-java-files-under, compat) LOCAL_PACKAGE_NAME := PicoTts +LOCAL_REQUIRED_MODULES := libttscompat + +LOCAL_PROGUARD_FLAG_FILES := proguard.flags include $(BUILD_PACKAGE) + # Build Pico Shared Library LOCAL_PATH:= $(TOP_LOCAL_PATH)/tts @@ -27,7 +32,7 @@ LOCAL_SRC_FILES:= com_svox_picottsengine.cpp svox_ssml_parser.cpp LOCAL_C_INCLUDES += \ external/svox/pico/lib \ - frameworks + external/svox/pico/compat/include LOCAL_STATIC_LIBRARIES:= libsvoxpico @@ -85,4 +90,26 @@ LOCAL_LDFLAGS+= $(TOOL_LDFLAGS) include $(BUILD_STATIC_LIBRARY) + +# Build compatibility library +LOCAL_PATH:= $(TOP_LOCAL_PATH)/compat/jni +include $(CLEAR_VARS) + +LOCAL_MODULE:= libttscompat +LOCAL_MODULE_TAGS := optional + +LOCAL_SRC_FILES:= \ + com_android_tts_compat_SynthProxy.cpp + +LOCAL_SHARED_LIBRARIES := \ + libandroid_runtime \ + libnativehelper \ + libmedia \ + libutils \ + libcutils \ + libdl + +include $(BUILD_SHARED_LIBRARY) + + endif # TARGET_SIMULATOR diff --git a/pico/AndroidManifest.xml b/pico/AndroidManifest.xml index c8cb570..077c17f 100755 --- a/pico/AndroidManifest.xml +++ b/pico/AndroidManifest.xml @@ -18,6 +18,13 @@ + + + + + + diff --git a/pico/compat/include/TtsEngine.h b/pico/compat/include/TtsEngine.h new file mode 100644 index 0000000..998e353 --- /dev/null +++ b/pico/compat/include/TtsEngine.h @@ -0,0 +1,232 @@ +/* + * Copyright (C) 2009 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include + +// This header defines the interface used by the Android platform +// to access Text-To-Speech functionality in shared libraries that implement +// speech synthesis and the management of resources associated with the +// synthesis. +// An example of the implementation of this interface can be found in +// FIXME: add path+name to implementation of default TTS engine +// Libraries implementing this interface are used in: +// frameworks/base/tts/jni/android_tts_SpeechSynthesis.cpp + +namespace android { + +#define ANDROID_TTS_ENGINE_PROPERTY_CONFIG "engineConfig" +#define ANDROID_TTS_ENGINE_PROPERTY_PITCH "pitch" +#define ANDROID_TTS_ENGINE_PROPERTY_RATE "rate" +#define ANDROID_TTS_ENGINE_PROPERTY_VOLUME "volume" + + +enum tts_synth_status { + TTS_SYNTH_DONE = 0, + TTS_SYNTH_PENDING = 1 +}; + +enum tts_callback_status { + TTS_CALLBACK_HALT = 0, + TTS_CALLBACK_CONTINUE = 1 +}; + +// The callback is used by the implementation of this interface to notify its +// client, the Android TTS service, that the last requested synthesis has been +// completed. // TODO reword +// The callback for synthesis completed takes: +// @param [inout] void *& - The userdata pointer set in the original +// synth call +// @param [in] uint32_t - Track sampling rate in Hz +// @param [in] uint32_t - The audio format +// @param [in] int - The number of channels +// @param [inout] int8_t *& - A buffer of audio data only valid during the +// execution of the callback +// @param [inout] size_t & - The size of the buffer +// @param [in] tts_synth_status - indicate whether the synthesis is done, or +// if more data is to be synthesized. +// @return TTS_CALLBACK_HALT to indicate the synthesis must stop, +// TTS_CALLBACK_CONTINUE to indicate the synthesis must continue if +// there is more data to produce. +typedef tts_callback_status (synthDoneCB_t)(void *&, uint32_t, + uint32_t, int, int8_t *&, size_t&, tts_synth_status); + +class TtsEngine; +extern "C" TtsEngine* getTtsEngine(); + +enum tts_result { + TTS_SUCCESS = 0, + TTS_FAILURE = -1, + TTS_FEATURE_UNSUPPORTED = -2, + TTS_VALUE_INVALID = -3, + TTS_PROPERTY_UNSUPPORTED = -4, + TTS_PROPERTY_SIZE_TOO_SMALL = -5, + TTS_MISSING_RESOURCES = -6 +}; + +enum tts_support_result { + TTS_LANG_COUNTRY_VAR_AVAILABLE = 2, + TTS_LANG_COUNTRY_AVAILABLE = 1, + TTS_LANG_AVAILABLE = 0, + TTS_LANG_MISSING_DATA = -1, + TTS_LANG_NOT_SUPPORTED = -2 +}; + +class TtsEngine +{ +public: + virtual ~TtsEngine() {} + + // Initialize the TTS engine and returns whether initialization succeeded. + // @param synthDoneCBPtr synthesis callback function pointer + // @return TTS_SUCCESS, or TTS_FAILURE + virtual tts_result init(synthDoneCB_t synthDoneCBPtr, const char *engineConfig); + + // Shut down the TTS engine and releases all associated resources. + // @return TTS_SUCCESS, or TTS_FAILURE + virtual tts_result shutdown(); + + // Interrupt synthesis and flushes any synthesized data that hasn't been + // output yet. This will block until callbacks underway are completed. + // @return TTS_SUCCESS, or TTS_FAILURE + virtual tts_result stop(); + + // Returns the level of support for the language, country and variant. + // @return TTS_LANG_COUNTRY_VAR_AVAILABLE if the language, country and variant are supported, + // and the corresponding resources are correctly installed + // TTS_LANG_COUNTRY_AVAILABLE if the language and country are supported and the + // corresponding resources are correctly installed, but there is no match for + // the specified variant + // TTS_LANG_AVAILABLE if the language is supported and the + // corresponding resources are correctly installed, but there is no match for + // the specified country and variant + // TTS_LANG_MISSING_DATA if the required resources to provide any level of support + // for the language are not correctly installed + // TTS_LANG_NOT_SUPPORTED if the language is not supported by the TTS engine. + virtual tts_support_result isLanguageAvailable(const char *lang, const char *country, + const char *variant); + + // Load the resources associated with the specified language. The loaded + // language will only be used once a call to setLanguage() with the same + // language value is issued. Language and country values are coded according to the ISO three + // letter codes for languages and countries, as can be retrieved from a java.util.Locale + // instance. The variant value is encoded as the variant string retrieved from a + // java.util.Locale instance built with that variant data. + // @param lang pointer to the ISO three letter code for the language + // @param country pointer to the ISO three letter code for the country + // @param variant pointer to the variant code + // @return TTS_SUCCESS, or TTS_FAILURE + virtual tts_result loadLanguage(const char *lang, const char *country, const char *variant); + + // Load the resources associated with the specified language, country and Locale variant. + // The loaded language will only be used once a call to setLanguageFromLocale() with the same + // language value is issued. Language and country values are coded according to the ISO three + // letter codes for languages and countries, as can be retrieved from a java.util.Locale + // instance. The variant value is encoded as the variant string retrieved from a + // java.util.Locale instance built with that variant data. + // @param lang pointer to the ISO three letter code for the language + // @param country pointer to the ISO three letter code for the country + // @param variant pointer to the variant code + // @return TTS_SUCCESS, or TTS_FAILURE + virtual tts_result setLanguage(const char *lang, const char *country, const char *variant); + + // Retrieve the currently set language, country and variant, or empty strings if none of + // parameters have been set. Language and country are represented by their 3-letter ISO code + // @param[out] pointer to the retrieved 3-letter code language value + // @param[out] pointer to the retrieved 3-letter code country value + // @param[out] pointer to the retrieved variant value + // @return TTS_SUCCESS, or TTS_FAILURE + virtual tts_result getLanguage(char *language, char *country, char *variant); + + // Notifies the engine what audio parameters should be used for the synthesis. + // This is meant to be used as a hint, the engine implementation will set the output values + // to those of the synthesis format, based on a given hint. + // @param[inout] encoding in: the desired audio sample format + // out: the format used by the TTS engine + // @param[inout] rate in: the desired audio sample rate + // out: the sample rate used by the TTS engine + // @param[inout] channels in: the desired number of audio channels + // out: the number of channels used by the TTS engine + // @return TTS_SUCCESS, or TTS_FAILURE + virtual tts_result setAudioFormat(AudioSystem::audio_format& encoding, uint32_t& rate, + int& channels); + + // Set a property for the the TTS engine + // "size" is the maximum size of "value" for properties "property" + // @param property pointer to the property name + // @param value pointer to the property value + // @param size maximum size required to store this type of property + // @return TTS_PROPERTY_UNSUPPORTED, or TTS_SUCCESS, or TTS_FAILURE, + // or TTS_VALUE_INVALID + virtual tts_result setProperty(const char *property, const char *value, + const size_t size); + + // Retrieve a property from the TTS engine + // @param property pointer to the property name + // @param[out] value pointer to the retrieved language value + // @param[inout] iosize in: stores the size available to store the + // property value. + // out: stores the size required to hold the language + // value if getLanguage() returned + // TTS_PROPERTY_SIZE_TOO_SMALL, unchanged otherwise + // @return TTS_PROPERTY_UNSUPPORTED, or TTS_SUCCESS, + // or TTS_PROPERTY_SIZE_TOO_SMALL + virtual tts_result getProperty(const char *property, char *value, + size_t *iosize); + + // Synthesize the text. + // As the synthesis is performed, the engine invokes the callback to notify + // the TTS framework that it has filled the given buffer, and indicates how + // many bytes it wrote. The callback is called repeatedly until the engine + // has generated all the audio data corresponding to the text. + // Note about the format of the input: the text parameter may use the + // following elements + // and their respective attributes as defined in the SSML 1.0 specification: + // * lang + // * say-as: + // o interpret-as + // * phoneme + // * voice: + // o gender, + // o age, + // o variant, + // o name + // * emphasis + // * break: + // o strength, + // o time + // * prosody: + // o pitch, + // o contour, + // o range, + // o rate, + // o duration, + // o volume + // * mark + // Differences between this text format and SSML are: + // * full SSML documents are not supported + // * namespaces are not supported + // Text is coded in UTF-8. + // @param text the UTF-8 text to synthesize + // @param userdata pointer to be returned when the call is invoked + // @param buffer the location where the synthesized data must be written + // @param bufferSize the number of bytes that can be written in buffer + // @return TTS_SUCCESS or TTS_FAILURE + virtual tts_result synthesizeText(const char *text, int8_t *buffer, + size_t bufferSize, void *userdata); + +}; + +} // namespace android + diff --git a/pico/compat/jni/Android.mk b/pico/compat/jni/Android.mk new file mode 100755 index 0000000..242047a --- /dev/null +++ b/pico/compat/jni/Android.mk @@ -0,0 +1,30 @@ +LOCAL_PATH:= $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_MODULE:= libttscompat +LOCAL_MODULE_TAGS := optional + +LOCAL_SRC_FILES:= \ + android_tts_SynthProxy.cpp + +LOCAL_C_INCLUDES += \ + frameworks/base/native/include \ + $(JNI_H_INCLUDE) + +LOCAL_SHARED_LIBRARIES := \ + libandroid_runtime \ + libnativehelper \ + libmedia \ + libutils \ + libcutils + +ifeq ($(TARGET_SIMULATOR),true) + LOCAL_LDLIBS += -ldl +else + LOCAL_SHARED_LIBRARIES += libdl +endif + +LOCAL_ARM_MODE := arm + +include $(BUILD_SHARED_LIBRARY) + diff --git a/pico/compat/jni/com_android_tts_compat_SynthProxy.cpp b/pico/compat/jni/com_android_tts_compat_SynthProxy.cpp new file mode 100644 index 0000000..c9c38c4 --- /dev/null +++ b/pico/compat/jni/com_android_tts_compat_SynthProxy.cpp @@ -0,0 +1,706 @@ +/* + * Copyright (C) 2009-2010 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include + +#define LOG_TAG "SynthProxyJNI" + +#include +#include +#include +#include +#include +#include + +#include + +#include "tts.h" + +#define DEFAULT_TTS_RATE 16000 +#define DEFAULT_TTS_BUFFERSIZE 2048 + +// EQ + BOOST parameters +#define FILTER_LOWSHELF_ATTENUATION -18.0f // in dB +#define FILTER_TRANSITION_FREQ 1100.0f // in Hz +#define FILTER_SHELF_SLOPE 1.0f // Q +#define FILTER_GAIN 5.5f // linear gain + +// android.media.AudioFormat.ENCODING_ values +#define AUDIO_FORMAT_ENCODING_DEFAULT 1 +#define AUDIO_FORMAT_ENCODING_PCM_16_BIT 2 +#define AUDIO_FORMAT_ENCODING_PCM_8_BIT 3 + +using namespace android; + +// ---------------------------------------------------------------------------- +// EQ data +static double m_fa, m_fb, m_fc, m_fd, m_fe; +static double x0; // x[n] +static double x1; // x[n-1] +static double x2; // x[n-2] +static double out0;// y[n] +static double out1;// y[n-1] +static double out2;// y[n-2] + +static float fFilterLowshelfAttenuation = FILTER_LOWSHELF_ATTENUATION; +static float fFilterTransitionFreq = FILTER_TRANSITION_FREQ; +static float fFilterShelfSlope = FILTER_SHELF_SLOPE; +static float fFilterGain = FILTER_GAIN; +static bool bUseFilter = false; + +void initializeEQ() { + double amp = float(pow(10.0, fFilterLowshelfAttenuation / 40.0)); + double w = 2.0 * M_PI * (fFilterTransitionFreq / DEFAULT_TTS_RATE); + double sinw = float(sin(w)); + double cosw = float(cos(w)); + double beta = float(sqrt(amp)/fFilterShelfSlope); + + // initialize low-shelf parameters + double b0 = amp * ((amp+1.0F) - ((amp-1.0F)*cosw) + (beta*sinw)); + double b1 = 2.0F * amp * ((amp-1.0F) - ((amp+1.0F)*cosw)); + double b2 = amp * ((amp+1.0F) - ((amp-1.0F)*cosw) - (beta*sinw)); + double a0 = (amp+1.0F) + ((amp-1.0F)*cosw) + (beta*sinw); + double a1 = 2.0F * ((amp-1.0F) + ((amp+1.0F)*cosw)); + double a2 = -((amp+1.0F) + ((amp-1.0F)*cosw) - (beta*sinw)); + + m_fa = fFilterGain * b0/a0; + m_fb = fFilterGain * b1/a0; + m_fc = fFilterGain * b2/a0; + m_fd = a1/a0; + m_fe = a2/a0; +} + +void initializeFilter() { + x0 = 0.0f; + x1 = 0.0f; + x2 = 0.0f; + out0 = 0.0f; + out1 = 0.0f; + out2 = 0.0f; +} + +void applyFilter(int16_t* buffer, size_t sampleCount) { + + for (size_t i=0 ; i 32767.0f) { + buffer[i] = 32767; + } else if (out0 < -32768.0f) { + buffer[i] = -32768; + } else { + buffer[i] = (int16_t) out0; + } + } +} + + +// ---------------------------------------------------------------------------- + +static jmethodID synthesisRequest_start; +static jmethodID synthesisRequest_audioAvailable; +static jmethodID synthesisRequest_done; + +static Mutex engineMutex; + + + +typedef android_tts_engine_t *(*android_tts_entrypoint)(); + +// ---------------------------------------------------------------------------- +class SynthProxyJniStorage { + public: + android_tts_engine_t *mEngine; + void *mEngineLibHandle; + int8_t *mBuffer; + size_t mBufferSize; + + SynthProxyJniStorage() { + mEngine = NULL; + mEngineLibHandle = NULL; + mBufferSize = DEFAULT_TTS_BUFFERSIZE; + mBuffer = new int8_t[mBufferSize]; + memset(mBuffer, 0, mBufferSize); + } + + ~SynthProxyJniStorage() { + if (mEngine) { + mEngine->funcs->shutdown(mEngine); + mEngine = NULL; + } + if (mEngineLibHandle) { + int res = dlclose(mEngineLibHandle); + LOGE_IF( res != 0, "~SynthProxyJniStorage(): dlclose returned %d", res); + } + delete[] mBuffer; + } + +}; + +// ---------------------------------------------------------------------------- + +struct SynthRequestData { + SynthProxyJniStorage *jniStorage; + JNIEnv *env; + jobject request; + bool startCalled; +}; + +// ---------------------------------------------------------------------------- + +/* + * Calls into Java + */ + +static bool checkException(JNIEnv *env) +{ + jthrowable ex = env->ExceptionOccurred(); + if (ex == NULL) { + return false; + } + env->ExceptionClear(); + LOGE_EX(env, ex); + env->DeleteLocalRef(ex); + return true; +} + +static int callRequestStart(JNIEnv *env, jobject request, + uint32_t rate, android_tts_audio_format_t format, int channelCount) +{ + int encoding; + + switch (format) { + case ANDROID_TTS_AUDIO_FORMAT_DEFAULT: + encoding = AUDIO_FORMAT_ENCODING_DEFAULT; + break; + case ANDROID_TTS_AUDIO_FORMAT_PCM_8_BIT: + encoding = AUDIO_FORMAT_ENCODING_PCM_8_BIT; + break; + case ANDROID_TTS_AUDIO_FORMAT_PCM_16_BIT: + encoding = AUDIO_FORMAT_ENCODING_PCM_16_BIT; + break; + default: + LOGE("Can't play, bad format"); + return ANDROID_TTS_FAILURE; + } + + int result = env->CallIntMethod(request, synthesisRequest_start, rate, encoding, channelCount); + if (checkException(env)) { + return ANDROID_TTS_FAILURE; + } + return result; +} + +static int callRequestAudioAvailable(JNIEnv *env, jobject request, int8_t *buffer, + int offset, int length) +{ + // TODO: Not nice to have to copy the buffer. Use ByteBuffer? + jbyteArray javaBuffer = env->NewByteArray(length); + if (javaBuffer == NULL) { + LOGE("Failed to allocate byte array"); + return ANDROID_TTS_FAILURE; + } + + env->SetByteArrayRegion(javaBuffer, 0, length, static_cast(buffer + offset)); + if (checkException(env)) { + env->DeleteLocalRef(javaBuffer); + return ANDROID_TTS_FAILURE; + } + int result = env->CallIntMethod(request, synthesisRequest_audioAvailable, + javaBuffer, offset, length); + if (checkException(env)) { + env->DeleteLocalRef(javaBuffer); + return ANDROID_TTS_FAILURE; + } + env->DeleteLocalRef(javaBuffer); + return result; +} + +static int callRequestDone(JNIEnv *env, jobject request) +{ + int result = env->CallIntMethod(request, synthesisRequest_done); + if (checkException(env)) { + return ANDROID_TTS_FAILURE; + } + return result; +} + +/* + * Callback from TTS engine. + */ +extern "C" android_tts_callback_status_t +__ttsSynthDoneCB(void **pUserdata, uint32_t rate, + android_tts_audio_format_t format, int channelCount, + int8_t **pWav, size_t *pBufferSize, + android_tts_synth_status_t status) +{ + if (*pUserdata == NULL){ + LOGE("userdata == NULL"); + return ANDROID_TTS_CALLBACK_HALT; + } + + SynthRequestData *pRequestData = static_cast(*pUserdata); + SynthProxyJniStorage *pJniData = pRequestData->jniStorage; + JNIEnv *env = pRequestData->env; + + if (*pWav != NULL && *pBufferSize > 0) { + if (bUseFilter) { + applyFilter(reinterpret_cast(*pWav), *pBufferSize/2); + } + + if (!pRequestData->startCalled) { + // TODO: is encoding one of the AudioFormat.ENCODING_* constants? + pRequestData->startCalled = true; + if (callRequestStart(env, pRequestData->request, rate, format, channelCount) + != ANDROID_TTS_SUCCESS) { + return ANDROID_TTS_CALLBACK_HALT; + } + } + + if (callRequestAudioAvailable(env, pRequestData->request, *pWav, 0, *pBufferSize) + != ANDROID_TTS_SUCCESS) { + return ANDROID_TTS_CALLBACK_HALT; + } + + memset(*pWav, 0, *pBufferSize); + } + + if (pWav == NULL || status == ANDROID_TTS_SYNTH_DONE) { + callRequestDone(env, pRequestData->request); + env->DeleteGlobalRef(pRequestData->request); + delete pRequestData; + pRequestData = NULL; + return ANDROID_TTS_CALLBACK_HALT; + } + + *pBufferSize = pJniData->mBufferSize; + + return ANDROID_TTS_CALLBACK_CONTINUE; +} + + +// ---------------------------------------------------------------------------- +static int +com_android_tts_compat_SynthProxy_setLowShelf(JNIEnv *env, jobject thiz, jboolean applyFilter, + jfloat filterGain, jfloat attenuationInDb, jfloat freqInHz, jfloat slope) +{ + bUseFilter = applyFilter; + if (applyFilter) { + fFilterLowshelfAttenuation = attenuationInDb; + fFilterTransitionFreq = freqInHz; + fFilterShelfSlope = slope; + fFilterGain = filterGain; + + if (fFilterShelfSlope != 0.0f) { + initializeEQ(); + } else { + LOGE("Invalid slope, can't be null"); + return ANDROID_TTS_FAILURE; + } + } + + return ANDROID_TTS_SUCCESS; +} + +// ---------------------------------------------------------------------------- +static jint +com_android_tts_compat_SynthProxy_native_setup(JNIEnv *env, jobject thiz, + jstring nativeSoLib, jstring engConfig) +{ + int result = 0; + bUseFilter = false; + + const char *nativeSoLibNativeString = env->GetStringUTFChars(nativeSoLib, 0); + const char *engConfigString = env->GetStringUTFChars(engConfig, 0); + + void *engine_lib_handle = dlopen(nativeSoLibNativeString, + RTLD_NOW | RTLD_LOCAL); + if (engine_lib_handle == NULL) { + LOGE("com_android_tts_compat_SynthProxy_native_setup(): engine_lib_handle == NULL"); + } else { + android_tts_entrypoint get_TtsEngine = + reinterpret_cast(dlsym(engine_lib_handle, "android_getTtsEngine")); + + // Support obsolete/legacy binary modules + if (get_TtsEngine == NULL) { + get_TtsEngine = + reinterpret_cast(dlsym(engine_lib_handle, "getTtsEngine")); + } + + android_tts_engine_t *engine = (*get_TtsEngine)(); + if (engine) { + Mutex::Autolock l(engineMutex); + engine->funcs->init(engine, __ttsSynthDoneCB, engConfigString); + + SynthProxyJniStorage *pSynthData = new SynthProxyJniStorage(); + pSynthData->mEngine = engine; + pSynthData->mEngineLibHandle = engine_lib_handle; + result = reinterpret_cast(pSynthData); + } + } + + env->ReleaseStringUTFChars(nativeSoLib, nativeSoLibNativeString); + env->ReleaseStringUTFChars(engConfig, engConfigString); + + return result; +} + +static SynthProxyJniStorage *getSynthData(jint jniData) +{ + if (jniData == 0) { + LOGE("Engine not initialized"); + return NULL; + } + return reinterpret_cast(jniData); +} + +static void +com_android_tts_compat_SynthProxy_native_finalize(JNIEnv *env, jobject thiz, jint jniData) +{ + SynthProxyJniStorage* pSynthData = getSynthData(jniData); + if (pSynthData == NULL) { + return; + } + + Mutex::Autolock l(engineMutex); + + delete pSynthData; +} + +static void +com_android_tts_compat_SynthProxy_shutdown(JNIEnv *env, jobject thiz, jint jniData) +{ + com_android_tts_compat_SynthProxy_native_finalize(env, thiz, jniData); +} + +static int +com_android_tts_compat_SynthProxy_isLanguageAvailable(JNIEnv *env, jobject thiz, jint jniData, + jstring language, jstring country, jstring variant) +{ + SynthProxyJniStorage* pSynthData = getSynthData(jniData); + if (pSynthData == NULL) { + return ANDROID_TTS_LANG_NOT_SUPPORTED; + } + + android_tts_engine_t *engine = pSynthData->mEngine; + if (!engine) { + return ANDROID_TTS_LANG_NOT_SUPPORTED; + } + + const char *langNativeString = env->GetStringUTFChars(language, 0); + const char *countryNativeString = env->GetStringUTFChars(country, 0); + const char *variantNativeString = env->GetStringUTFChars(variant, 0); + + int result = engine->funcs->isLanguageAvailable(engine, langNativeString, + countryNativeString, variantNativeString); + + env->ReleaseStringUTFChars(language, langNativeString); + env->ReleaseStringUTFChars(country, countryNativeString); + env->ReleaseStringUTFChars(variant, variantNativeString); + + return result; +} + +static int +com_android_tts_compat_SynthProxy_setLanguage(JNIEnv *env, jobject thiz, jint jniData, + jstring language, jstring country, jstring variant) +{ + SynthProxyJniStorage* pSynthData = getSynthData(jniData); + if (pSynthData == NULL) { + return ANDROID_TTS_LANG_NOT_SUPPORTED; + } + + Mutex::Autolock l(engineMutex); + + android_tts_engine_t *engine = pSynthData->mEngine; + if (!engine) { + return ANDROID_TTS_LANG_NOT_SUPPORTED; + } + + const char *langNativeString = env->GetStringUTFChars(language, 0); + const char *countryNativeString = env->GetStringUTFChars(country, 0); + const char *variantNativeString = env->GetStringUTFChars(variant, 0); + + int result = engine->funcs->setLanguage(engine, langNativeString, + countryNativeString, variantNativeString); + + env->ReleaseStringUTFChars(language, langNativeString); + env->ReleaseStringUTFChars(country, countryNativeString); + env->ReleaseStringUTFChars(variant, variantNativeString); + + return result; +} + + +static int +com_android_tts_compat_SynthProxy_loadLanguage(JNIEnv *env, jobject thiz, jint jniData, + jstring language, jstring country, jstring variant) +{ + SynthProxyJniStorage* pSynthData = getSynthData(jniData); + if (pSynthData == NULL) { + return ANDROID_TTS_LANG_NOT_SUPPORTED; + } + + android_tts_engine_t *engine = pSynthData->mEngine; + if (!engine) { + return ANDROID_TTS_LANG_NOT_SUPPORTED; + } + + const char *langNativeString = env->GetStringUTFChars(language, 0); + const char *countryNativeString = env->GetStringUTFChars(country, 0); + const char *variantNativeString = env->GetStringUTFChars(variant, 0); + + int result = engine->funcs->loadLanguage(engine, langNativeString, + countryNativeString, variantNativeString); + + env->ReleaseStringUTFChars(language, langNativeString); + env->ReleaseStringUTFChars(country, countryNativeString); + env->ReleaseStringUTFChars(variant, variantNativeString); + + return result; +} + +static int +com_android_tts_compat_SynthProxy_setProperty(JNIEnv *env, jobject thiz, jint jniData, + jstring name, jstring value) +{ + SynthProxyJniStorage* pSynthData = getSynthData(jniData); + if (pSynthData == NULL) { + return ANDROID_TTS_FAILURE; + } + + Mutex::Autolock l(engineMutex); + + android_tts_engine_t *engine = pSynthData->mEngine; + if (!engine) { + return ANDROID_TTS_FAILURE; + } + + const char *nameChars = env->GetStringUTFChars(name, 0); + const char *valueChars = env->GetStringUTFChars(value, 0); + size_t valueLength = env->GetStringUTFLength(value); + + int result = engine->funcs->setProperty(engine, nameChars, valueChars, valueLength); + + env->ReleaseStringUTFChars(name, nameChars); + env->ReleaseStringUTFChars(name, valueChars); + + return result; +} + +static int +com_android_tts_compat_SynthProxy_speak(JNIEnv *env, jobject thiz, jint jniData, + jstring textJavaString, jobject request) +{ + SynthProxyJniStorage* pSynthData = getSynthData(jniData); + if (pSynthData == NULL) { + return ANDROID_TTS_FAILURE; + } + + initializeFilter(); + + Mutex::Autolock l(engineMutex); + + android_tts_engine_t *engine = pSynthData->mEngine; + if (!engine) { + return ANDROID_TTS_FAILURE; + } + + SynthRequestData *pRequestData = new SynthRequestData; + pRequestData->jniStorage = pSynthData; + pRequestData->env = env; + pRequestData->request = env->NewGlobalRef(request); + pRequestData->startCalled = false; + + const char *textNativeString = env->GetStringUTFChars(textJavaString, 0); + memset(pSynthData->mBuffer, 0, pSynthData->mBufferSize); + + int result = engine->funcs->synthesizeText(engine, textNativeString, + pSynthData->mBuffer, pSynthData->mBufferSize, static_cast(pRequestData)); + env->ReleaseStringUTFChars(textJavaString, textNativeString); + + return result; +} + +static int +com_android_tts_compat_SynthProxy_stop(JNIEnv *env, jobject thiz, jint jniData) +{ + SynthProxyJniStorage* pSynthData = getSynthData(jniData); + if (pSynthData == NULL) { + return ANDROID_TTS_FAILURE; + } + + android_tts_engine_t *engine = pSynthData->mEngine; + if (!engine) { + return ANDROID_TTS_FAILURE; + } + + return engine->funcs->stop(engine); +} + +static int +com_android_tts_compat_SynthProxy_stopSync(JNIEnv *env, jobject thiz, jint jniData) +{ + SynthProxyJniStorage* pSynthData = getSynthData(jniData); + if (pSynthData == NULL) { + return ANDROID_TTS_FAILURE; + } + + // perform a regular stop + int result = com_android_tts_compat_SynthProxy_stop(env, thiz, jniData); + // but wait on the engine having released the engine mutex which protects + // the synthesizer resources. + engineMutex.lock(); + engineMutex.unlock(); + + return result; +} + +static jobjectArray +com_android_tts_compat_SynthProxy_getLanguage(JNIEnv *env, jobject thiz, jint jniData) +{ + SynthProxyJniStorage* pSynthData = getSynthData(jniData); + if (pSynthData == NULL) { + return NULL; + } + + if (pSynthData->mEngine) { + size_t bufSize = 100; + char lang[bufSize]; + char country[bufSize]; + char variant[bufSize]; + memset(lang, 0, bufSize); + memset(country, 0, bufSize); + memset(variant, 0, bufSize); + jobjectArray retLocale = (jobjectArray)env->NewObjectArray(3, + env->FindClass("java/lang/String"), env->NewStringUTF("")); + + android_tts_engine_t *engine = pSynthData->mEngine; + engine->funcs->getLanguage(engine, lang, country, variant); + env->SetObjectArrayElement(retLocale, 0, env->NewStringUTF(lang)); + env->SetObjectArrayElement(retLocale, 1, env->NewStringUTF(country)); + env->SetObjectArrayElement(retLocale, 2, env->NewStringUTF(variant)); + return retLocale; + } else { + return NULL; + } +} + + +// Dalvik VM type signatures +static JNINativeMethod gMethods[] = { + { "native_stop", + "(I)I", + (void*)com_android_tts_compat_SynthProxy_stop + }, + { "native_stopSync", + "(I)I", + (void*)com_android_tts_compat_SynthProxy_stopSync + }, + { "native_speak", + "(ILjava/lang/String;Landroid/speech/tts/SynthesisRequest;)I", + (void*)com_android_tts_compat_SynthProxy_speak + }, + { "native_isLanguageAvailable", + "(ILjava/lang/String;Ljava/lang/String;Ljava/lang/String;)I", + (void*)com_android_tts_compat_SynthProxy_isLanguageAvailable + }, + { "native_setLanguage", + "(ILjava/lang/String;Ljava/lang/String;Ljava/lang/String;)I", + (void*)com_android_tts_compat_SynthProxy_setLanguage + }, + { "native_loadLanguage", + "(ILjava/lang/String;Ljava/lang/String;Ljava/lang/String;)I", + (void*)com_android_tts_compat_SynthProxy_loadLanguage + }, + { "native_setProperty", + "(ILjava/lang/String;Ljava/lang/String;)I", + (void*)com_android_tts_compat_SynthProxy_setProperty + }, + { "native_getLanguage", + "(I)[Ljava/lang/String;", + (void*)com_android_tts_compat_SynthProxy_getLanguage + }, + { "native_shutdown", + "(I)V", + (void*)com_android_tts_compat_SynthProxy_shutdown + }, + { "native_setup", + "(Ljava/lang/String;Ljava/lang/String;)I", + (void*)com_android_tts_compat_SynthProxy_native_setup + }, + { "native_setLowShelf", + "(ZFFFF)I", + (void*)com_android_tts_compat_SynthProxy_setLowShelf + }, + { "native_finalize", + "(I)V", + (void*)com_android_tts_compat_SynthProxy_native_finalize + } +}; + +jint JNI_OnLoad(JavaVM* vm, void* reserved) +{ + JNIEnv* env = NULL; + + if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) { + LOGE("ERROR: GetEnv failed\n"); + return -1; + } + assert(env != NULL); + + jclass classSynthesisRequest = env->FindClass( + "android/speech/tts/SynthesisRequest"); + if (classSynthesisRequest == NULL) { + return -1; + } + + synthesisRequest_start = env->GetMethodID(classSynthesisRequest, + "start", "(III)I"); + if (synthesisRequest_start == NULL) { + return -1; + } + + synthesisRequest_audioAvailable = env->GetMethodID(classSynthesisRequest, + "audioAvailable", "([BII)I"); + if (synthesisRequest_audioAvailable == NULL) { + return -1; + } + + synthesisRequest_done = env->GetMethodID(classSynthesisRequest, + "done", "()I"); + if (synthesisRequest_done == NULL) { + return -1; + } + + if (jniRegisterNativeMethods( + env, "com/android/tts/compat/SynthProxy", gMethods, NELEM(gMethods)) < 0) { + return -1; + } + + /* success -- return valid version number */ + return JNI_VERSION_1_4; +} diff --git a/pico/compat/jni/tts.h b/pico/compat/jni/tts.h new file mode 100644 index 0000000..fb15108 --- /dev/null +++ b/pico/compat/jni/tts.h @@ -0,0 +1,313 @@ +/* + * Copyright (C) 2009 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef ANDROID_TTS_H +#define ANDROID_TTS_H + +// This header defines the interface used by the Android platform +// to access Text-To-Speech functionality in shared libraries that implement +// speech synthesis and the management of resources associated with the +// synthesis. + +// The shared library must contain a function named "android_getTtsEngine" +// that returns an 'android_tts_engine_t' instance. + +#ifdef __cplusplus +extern "C" { +#endif + +#define ANDROID_TTS_ENGINE_PROPERTY_CONFIG "engineConfig" +#define ANDROID_TTS_ENGINE_PROPERTY_PITCH "pitch" +#define ANDROID_TTS_ENGINE_PROPERTY_RATE "rate" +#define ANDROID_TTS_ENGINE_PROPERTY_VOLUME "volume" + +typedef enum { + ANDROID_TTS_SUCCESS = 0, + ANDROID_TTS_FAILURE = -1, + ANDROID_TTS_FEATURE_UNSUPPORTED = -2, + ANDROID_TTS_VALUE_INVALID = -3, + ANDROID_TTS_PROPERTY_UNSUPPORTED = -4, + ANDROID_TTS_PROPERTY_SIZE_TOO_SMALL = -5, + ANDROID_TTS_MISSING_RESOURCES = -6 +} android_tts_result_t; + +typedef enum { + ANDROID_TTS_LANG_COUNTRY_VAR_AVAILABLE = 2, + ANDROID_TTS_LANG_COUNTRY_AVAILABLE = 1, + ANDROID_TTS_LANG_AVAILABLE = 0, + ANDROID_TTS_LANG_MISSING_DATA = -1, + ANDROID_TTS_LANG_NOT_SUPPORTED = -2 +} android_tts_support_result_t; + +typedef enum { + ANDROID_TTS_SYNTH_DONE = 0, + ANDROID_TTS_SYNTH_PENDING = 1 +} android_tts_synth_status_t; + +typedef enum { + ANDROID_TTS_CALLBACK_HALT = 0, + ANDROID_TTS_CALLBACK_CONTINUE = 1 +} android_tts_callback_status_t; + +// Supported audio formats +typedef enum { + ANDROID_TTS_AUDIO_FORMAT_INVALID = -1, + ANDROID_TTS_AUDIO_FORMAT_DEFAULT = 0, + ANDROID_TTS_AUDIO_FORMAT_PCM_16_BIT = 1, + ANDROID_TTS_AUDIO_FORMAT_PCM_8_BIT = 2, +} android_tts_audio_format_t; + + +/* An android_tts_engine_t object can be anything, but must have, + * as its first field, a pointer to a table of functions. + * + * See the full definition of struct android_tts_engine_t_funcs_t + * below for details. + */ +typedef struct android_tts_engine_funcs_t android_tts_engine_funcs_t; + +typedef struct { + android_tts_engine_funcs_t *funcs; +} android_tts_engine_t; + +/* This function must be located in the TTS Engine shared library + * and must return the address of an android_tts_engine_t library. + */ +extern android_tts_engine_t *android_getTtsEngine(); + +/* Including the old version for legacy support (Froyo compatibility). + * This should return the same thing as android_getTtsEngine. + */ +extern "C" android_tts_engine_t *getTtsEngine(); + +// A callback type used to notify the framework of new synthetized +// audio samples, status will be SYNTH_DONE for the last sample of +// the last request, of SYNTH_PENDING otherwise. +// +// This is passed by the framework to the engine through the +// 'engine_init' function (see below). +// +// The callback for synthesis completed takes: +// @param [inout] void *& - The userdata pointer set in the original +// synth call +// @param [in] uint32_t - Track sampling rate in Hz +// @param [in] uint32_t - The audio format +// @param [in] int - The number of channels +// @param [inout] int8_t *& - A buffer of audio data only valid during the +// execution of the callback +// @param [inout] size_t & - The size of the buffer +// @param [in] tts_synth_status - indicate whether the synthesis is done, or +// if more data is to be synthesized. +// @return TTS_CALLBACK_HALT to indicate the synthesis must stop, +// TTS_CALLBACK_CONTINUE to indicate the synthesis must continue if +// there is more data to produce. +typedef android_tts_callback_status_t (*android_tts_synth_cb_t) + (void **pUserData, + uint32_t trackSamplingHz, + android_tts_audio_format_t audioFormat, + int channelCount, + int8_t **pAudioBuffer, + size_t *pBufferSize, + android_tts_synth_status_t status); + + +// The table of function pointers that the android_tts_engine_t must point to. +// Note that each of these functions will take a handle to the engine itself +// as their first parameter. +// + +struct android_tts_engine_funcs_t { + // reserved fields, ignored by the framework + // they must be placed here to ensure binary compatibility + // of legacy binary plugins. + void *reserved[2]; + + // Initialize the TTS engine and returns whether initialization succeeded. + // @param synthDoneCBPtr synthesis callback function pointer + // @return TTS_SUCCESS, or TTS_FAILURE + android_tts_result_t (*init) + (void *engine, + android_tts_synth_cb_t synthDonePtr, + const char *engineConfig); + + // Shut down the TTS engine and releases all associated resources. + // @return TTS_SUCCESS, or TTS_FAILURE + android_tts_result_t (*shutdown) + (void *engine); + + // Interrupt synthesis and flushes any synthesized data that hasn't been + // output yet. This will block until callbacks underway are completed. + // @return TTS_SUCCESS, or TTS_FAILURE + android_tts_result_t (*stop) + (void *engine); + + // Returns the level of support for the language, country and variant. + // @return TTS_LANG_COUNTRY_VAR_AVAILABLE if the language, country and variant are supported, + // and the corresponding resources are correctly installed + // TTS_LANG_COUNTRY_AVAILABLE if the language and country are supported and the + // corresponding resources are correctly installed, but there is no match for + // the specified variant + // TTS_LANG_AVAILABLE if the language is supported and the + // corresponding resources are correctly installed, but there is no match for + // the specified country and variant + // TTS_LANG_MISSING_DATA if the required resources to provide any level of support + // for the language are not correctly installed + // TTS_LANG_NOT_SUPPORTED if the language is not supported by the TTS engine. + android_tts_support_result_t (*isLanguageAvailable) + (void *engine, + const char *lang, + const char *country, + const char *variant); + + // Load the resources associated with the specified language. The loaded + // language will only be used once a call to setLanguage() with the same + // language value is issued. Language and country values are coded according to the ISO three + // letter codes for languages and countries, as can be retrieved from a java.util.Locale + // instance. The variant value is encoded as the variant string retrieved from a + // java.util.Locale instance built with that variant data. + // @param lang pointer to the ISO three letter code for the language + // @param country pointer to the ISO three letter code for the country + // @param variant pointer to the variant code + // @return TTS_SUCCESS, or TTS_FAILURE + android_tts_result_t (*loadLanguage) + (void *engine, + const char *lang, + const char *country, + const char *variant); + + // Load the resources associated with the specified language, country and Locale variant. + // The loaded language will only be used once a call to setLanguageFromLocale() with the same + // language value is issued. Language and country values are coded according to the ISO three + // letter codes for languages and countries, as can be retrieved from a java.util.Locale + // instance. The variant value is encoded as the variant string retrieved from a + // java.util.Locale instance built with that variant data. + // @param lang pointer to the ISO three letter code for the language + // @param country pointer to the ISO three letter code for the country + // @param variant pointer to the variant code + // @return TTS_SUCCESS, or TTS_FAILURE + android_tts_result_t (*setLanguage) + (void *engine, + const char *lang, + const char *country, + const char *variant); + + // Retrieve the currently set language, country and variant, or empty strings if none of + // parameters have been set. Language and country are represented by their 3-letter ISO code + // @param[out] pointer to the retrieved 3-letter code language value + // @param[out] pointer to the retrieved 3-letter code country value + // @param[out] pointer to the retrieved variant value + // @return TTS_SUCCESS, or TTS_FAILURE + android_tts_result_t (*getLanguage) + (void *engine, + char *language, + char *country, + char *variant); + + // Notifies the engine what audio parameters should be used for the synthesis. + // This is meant to be used as a hint, the engine implementation will set the output values + // to those of the synthesis format, based on a given hint. + // @param[inout] encoding in: the desired audio sample format + // out: the format used by the TTS engine + // @param[inout] rate in: the desired audio sample rate + // out: the sample rate used by the TTS engine + // @param[inout] channels in: the desired number of audio channels + // out: the number of channels used by the TTS engine + // @return TTS_SUCCESS, or TTS_FAILURE + android_tts_result_t (*setAudioFormat) + (void *engine, + android_tts_audio_format_t* pEncoding, + uint32_t* pRate, + int* pChannels); + + // Set a property for the the TTS engine + // "size" is the maximum size of "value" for properties "property" + // @param property pointer to the property name + // @param value pointer to the property value + // @param size maximum size required to store this type of property + // @return TTS_PROPERTY_UNSUPPORTED, or TTS_SUCCESS, or TTS_FAILURE, + // or TTS_VALUE_INVALID + android_tts_result_t (*setProperty) + (void *engine, + const char *property, + const char *value, + const size_t size); + + // Retrieve a property from the TTS engine + // @param property pointer to the property name + // @param[out] value pointer to the retrieved language value + // @param[inout] iosize in: stores the size available to store the + // property value. + // out: stores the size required to hold the language + // value if getLanguage() returned + // TTS_PROPERTY_SIZE_TOO_SMALL, unchanged otherwise + // @return TTS_PROPERTY_UNSUPPORTED, or TTS_SUCCESS, + // or TTS_PROPERTY_SIZE_TOO_SMALL + android_tts_result_t (*getProperty) + (void *engine, + const char *property, + char *value, + size_t *iosize); + + // Synthesize the text. + // As the synthesis is performed, the engine invokes the callback to notify + // the TTS framework that it has filled the given buffer, and indicates how + // many bytes it wrote. The callback is called repeatedly until the engine + // has generated all the audio data corresponding to the text. + // Note about the format of the input: the text parameter may use the + // following elements + // and their respective attributes as defined in the SSML 1.0 specification: + // * lang + // * say-as: + // o interpret-as + // * phoneme + // * voice: + // o gender, + // o age, + // o variant, + // o name + // * emphasis + // * break: + // o strength, + // o time + // * prosody: + // o pitch, + // o contour, + // o range, + // o rate, + // o duration, + // o volume + // * mark + // Differences between this text format and SSML are: + // * full SSML documents are not supported + // * namespaces are not supported + // Text is coded in UTF-8. + // @param text the UTF-8 text to synthesize + // @param userdata pointer to be returned when the call is invoked + // @param buffer the location where the synthesized data must be written + // @param bufferSize the number of bytes that can be written in buffer + // @return TTS_SUCCESS or TTS_FAILURE + android_tts_result_t (*synthesizeText) + (void *engine, + const char *text, + int8_t *buffer, + size_t bufferSize, + void *userdata); +}; + +#ifdef __cplusplus +} +#endif + +#endif /* ANDROID_TTS_H */ diff --git a/pico/compat/src/com/android/tts/compat/CompatTtsService.java b/pico/compat/src/com/android/tts/compat/CompatTtsService.java new file mode 100755 index 0000000..1ec7ebf --- /dev/null +++ b/pico/compat/src/com/android/tts/compat/CompatTtsService.java @@ -0,0 +1,145 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.android.tts.compat; + +import android.database.Cursor; +import android.net.Uri; +import android.speech.tts.SynthesisRequest; +import android.speech.tts.TextToSpeech; +import android.speech.tts.TextToSpeechService; +import android.util.Log; + +import java.io.File; + +public abstract class CompatTtsService extends TextToSpeechService { + + private static final boolean DBG = false; + private static final String TAG = "CompatTtsService"; + + private SynthProxy mNativeSynth = null; + + protected abstract String getSoFilename(); + + @Override + public void onCreate() { + if (DBG) Log.d(TAG, "onCreate()"); + super.onCreate(); + + String soFilename = getSoFilename(); + + File f = new File(soFilename); + if (!f.exists()) { + Log.e(TAG, "Invalid TTS Binary: " + soFilename); + return; + } + + if (mNativeSynth != null) { + mNativeSynth.stopSync(); + mNativeSynth.shutdown(); + mNativeSynth = null; + } + + // Load the engineConfig from the plugin if it has any special configuration + // to be loaded. By convention, if an engine wants the TTS framework to pass + // in any configuration, it must put it into its content provider which has the URI: + // content://.providers.SettingsProvider + // That content provider must provide a Cursor which returns the String that + // is to be passed back to the native .so file for the plugin when getString(0) is + // called on it. + // Note that the TTS framework does not care what this String data is: it is something + // that comes from the engine plugin and is consumed only by the engine plugin itself. + String engineConfig = ""; + Cursor c = getContentResolver().query(Uri.parse("content://" + getPackageName() + + ".providers.SettingsProvider"), null, null, null, null); + if (c != null){ + c.moveToFirst(); + engineConfig = c.getString(0); + c.close(); + } + mNativeSynth = new SynthProxy(soFilename, engineConfig); + } + + @Override + public void onDestroy() { + if (DBG) Log.d(TAG, "onDestroy()"); + super.onDestroy(); + + if (mNativeSynth != null) { + mNativeSynth.shutdown(); + } + mNativeSynth = null; + } + + @Override + protected String[] onGetLanguage() { + if (mNativeSynth == null) return null; + return mNativeSynth.getLanguage(); + } + + @Override + protected int onIsLanguageAvailable(String lang, String country, String variant) { + if (DBG) Log.d(TAG, "onIsLanguageAvailable(" + lang + "," + country + "," + variant + ")"); + if (mNativeSynth == null) return TextToSpeech.ERROR; + return mNativeSynth.isLanguageAvailable(lang, country, variant); + } + + @Override + protected int onLoadLanguage(String lang, String country, String variant) { + if (DBG) Log.d(TAG, "onLoadLanguage(" + lang + "," + country + "," + variant + ")"); + int result = onIsLanguageAvailable(lang, country, variant); + // TODO: actually load language + return result; + } + + @Override + protected int onSynthesizeText(SynthesisRequest request) { + if (mNativeSynth == null) return TextToSpeech.ERROR; + + // Set language + String lang = request.getLanguage(); + String country = request.getCountry(); + String variant = request.getVariant(); + if (mNativeSynth.setLanguage(lang, country, variant) != TextToSpeech.SUCCESS) { + Log.e(TAG, "setLanguage(" + lang + "," + country + "," + variant + ") failed"); + return TextToSpeech.ERROR; + } + + // Set speech rate + int speechRate = request.getSpeechRate(); + if (mNativeSynth.setSpeechRate(speechRate) != TextToSpeech.SUCCESS) { + Log.e(TAG, "setSpeechRate(" + speechRate + ") failed"); + return TextToSpeech.ERROR; + } + + // Set speech + int pitch = request.getPitch(); + if (mNativeSynth.setPitch(pitch) != TextToSpeech.SUCCESS) { + Log.e(TAG, "setPitch(" + pitch + ") failed"); + return TextToSpeech.ERROR; + } + + // Synthesize + return mNativeSynth.speak(request); + } + + @Override + protected void onStop() { + if (DBG) Log.d(TAG, "onStop()"); + if (mNativeSynth == null) return; + mNativeSynth.stop(); + } + +} diff --git a/pico/compat/src/com/android/tts/compat/SynthProxy.java b/pico/compat/src/com/android/tts/compat/SynthProxy.java new file mode 100755 index 0000000..68d0d92 --- /dev/null +++ b/pico/compat/src/com/android/tts/compat/SynthProxy.java @@ -0,0 +1,183 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.android.tts.compat; + +import android.speech.tts.SynthesisRequest; +import android.util.Log; + +/** + * The SpeechSynthesis class provides a high-level api to create and play + * synthesized speech. This class is used internally to talk to a native + * TTS library that implements the interface defined in + * frameworks/base/include/tts/TtsEngine.h + * + */ +public class SynthProxy { + + static { + System.loadLibrary("ttscompat"); + } + + private final static String TAG = "SynthProxy"; + + // Default parameters of a filter to be applied when using the Pico engine. + // Such a huge filter gain is justified by how much energy in the low frequencies is "wasted" at + // the output of the synthesis. The low shelving filter removes it, leaving room for + // amplification. + private final static float PICO_FILTER_GAIN = 5.0f; // linear gain + private final static float PICO_FILTER_LOWSHELF_ATTENUATION = -18.0f; // in dB + private final static float PICO_FILTER_TRANSITION_FREQ = 1100.0f; // in Hz + private final static float PICO_FILTER_SHELF_SLOPE = 1.0f; // Q + + private int mJniData = 0; + + /** + * Constructor; pass the location of the native TTS .so to use. + */ + public SynthProxy(String nativeSoLib, String engineConfig) { + boolean applyFilter = shouldApplyAudioFilter(nativeSoLib); + Log.v(TAG, "About to load "+ nativeSoLib + ", applyFilter=" + applyFilter); + mJniData = native_setup(nativeSoLib, engineConfig); + if (mJniData == 0) { + throw new RuntimeException("Failed to load " + nativeSoLib); + } + native_setLowShelf(applyFilter, PICO_FILTER_GAIN, PICO_FILTER_LOWSHELF_ATTENUATION, + PICO_FILTER_TRANSITION_FREQ, PICO_FILTER_SHELF_SLOPE); + } + + // HACK: Apply audio filter if the engine is pico + private boolean shouldApplyAudioFilter(String nativeSoLib) { + return nativeSoLib.toLowerCase().contains("pico"); + } + + /** + * Stops and clears the AudioTrack. + */ + public int stop() { + return native_stop(mJniData); + } + + /** + * Synchronous stop of the synthesizer. This method returns when the synth + * has completed the stop procedure and doesn't use any of the resources it + * was using while synthesizing. + * + * @return {@link android.speech.tts.TextToSpeech#SUCCESS} or + * {@link android.speech.tts.TextToSpeech#ERROR} + */ + public int stopSync() { + return native_stopSync(mJniData); + } + + public int speak(SynthesisRequest request) { + return native_speak(mJniData, request.getText(), request); + } + + /** + * Queries for language support. + * Return codes are defined in android.speech.tts.TextToSpeech + */ + public int isLanguageAvailable(String language, String country, String variant) { + return native_isLanguageAvailable(mJniData, language, country, variant); + } + + /** + * Updates the engine configuration. + */ + public int setConfig(String engineConfig) { + return native_setProperty(mJniData, "engineConfig", engineConfig); + } + + /** + * Sets the language. + */ + public int setLanguage(String language, String country, String variant) { + return native_setLanguage(mJniData, language, country, variant); + } + + /** + * Loads the language: it's not set, but prepared for use later. + */ + public int loadLanguage(String language, String country, String variant) { + return native_loadLanguage(mJniData, language, country, variant); + } + + /** + * Sets the speech rate. + */ + public final int setSpeechRate(int speechRate) { + return native_setProperty(mJniData, "rate", String.valueOf(speechRate)); + } + + /** + * Sets the pitch of the synthesized voice. + */ + public final int setPitch(int pitch) { + return native_setProperty(mJniData, "pitch", String.valueOf(pitch)); + } + + /** + * Returns the currently set language, country and variant information. + */ + public String[] getLanguage() { + return native_getLanguage(mJniData); + } + + /** + * Shuts down the native synthesizer. + */ + public void shutdown() { + native_shutdown(mJniData); + } + + @Override + protected void finalize() { + if (mJniData != 0) { + Log.w(TAG, "SynthProxy finalized without being shutdown"); + native_finalize(mJniData); + mJniData = 0; + } + } + + private native final int native_setup(String nativeSoLib, String engineConfig); + + private native final int native_setLowShelf(boolean applyFilter, float filterGain, + float attenuationInDb, float freqInHz, float slope); + + private native final void native_finalize(int jniData); + + private native final int native_stop(int jniData); + + private native final int native_stopSync(int jniData); + + private native final int native_speak(int jniData, String text, SynthesisRequest request); + + private native final int native_isLanguageAvailable(int jniData, String language, + String country, String variant); + + private native final int native_setLanguage(int jniData, String language, String country, + String variant); + + private native final int native_loadLanguage(int jniData, String language, String country, + String variant); + + private native final int native_setProperty(int jniData, String name, String value); + + private native final String[] native_getLanguage(int jniData); + + private native final void native_shutdown(int jniData); + +} diff --git a/pico/proguard.flags b/pico/proguard.flags new file mode 100644 index 0000000..eff9d9b --- /dev/null +++ b/pico/proguard.flags @@ -0,0 +1,2 @@ +# Some methods in this class are called form native code +-keep class com.android.tts.compat.SynthProxy { *; } diff --git a/pico/src/com/svox/pico/PicoService.java b/pico/src/com/svox/pico/PicoService.java new file mode 100644 index 0000000..4c6e678 --- /dev/null +++ b/pico/src/com/svox/pico/PicoService.java @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.svox.pico; + +import com.android.tts.compat.CompatTtsService; + +public class PicoService extends CompatTtsService { + + private static final String TAG = "PicoService"; + + @Override + protected String getSoFilename() { + return "/system/lib/libttspico.so"; + } + +} diff --git a/pico/tts/com_svox_picottsengine.cpp b/pico/tts/com_svox_picottsengine.cpp index 7ac4ec7..6c05d00 100644 --- a/pico/tts/com_svox_picottsengine.cpp +++ b/pico/tts/com_svox_picottsengine.cpp @@ -40,10 +40,12 @@ #include #include /* for strlen16 */ #include -#include +#include + #include #include #include + #include "svox_ssml_parser.h" using namespace android; -- cgit v1.1