Home | History | Annotate | Download | only in jni
      1 /*
      2  * Copyright (C) 2009-2010 Google Inc.
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 #include <stdio.h>
     18 #include <unistd.h>
     19 
     20 #define LOG_TAG "SynthProxyJNI"
     21 
     22 #include <utils/Log.h>
     23 #include <nativehelper/jni.h>
     24 #include <nativehelper/JNIHelp.h>
     25 #include <android_runtime/AndroidRuntime.h>
     26 #include <media/AudioTrack.h>
     27 #include <math.h>
     28 
     29 #include <dlfcn.h>
     30 
     31 #include "tts.h"
     32 
     33 #define DEFAULT_TTS_RATE        16000
     34 #define DEFAULT_TTS_BUFFERSIZE  2048
     35 
     36 // EQ + BOOST parameters
     37 #define FILTER_LOWSHELF_ATTENUATION -18.0f // in dB
     38 #define FILTER_TRANSITION_FREQ 1100.0f     // in Hz
     39 #define FILTER_SHELF_SLOPE 1.0f            // Q
     40 #define FILTER_GAIN 5.5f // linear gain
     41 
     42 // android.media.AudioFormat.ENCODING_ values
     43 //
     44 // Note that these constants are different from those
     45 // defined in the native code (system/audio.h and others).
     46 // We use them because we use a Java AudioTrack to play
     47 // back our data.
     48 #define AUDIO_FORMAT_ENCODING_DEFAULT 1
     49 #define AUDIO_FORMAT_ENCODING_PCM_16_BIT 2
     50 #define AUDIO_FORMAT_ENCODING_PCM_8_BIT 3
     51 
     52 using namespace android;
     53 
     54 // ----------------------------------------------------------------------------
     55 // EQ data
     56 static double m_fa, m_fb, m_fc, m_fd, m_fe;
     57 static double x0;  // x[n]
     58 static double x1;  // x[n-1]
     59 static double x2;  // x[n-2]
     60 static double out0;// y[n]
     61 static double out1;// y[n-1]
     62 static double out2;// y[n-2]
     63 
     64 static float fFilterLowshelfAttenuation = FILTER_LOWSHELF_ATTENUATION;
     65 static float fFilterTransitionFreq = FILTER_TRANSITION_FREQ;
     66 static float fFilterShelfSlope = FILTER_SHELF_SLOPE;
     67 static float fFilterGain = FILTER_GAIN;
     68 static bool  bUseFilter = false;
     69 
     70 void initializeEQ() {
     71     double amp = float(pow(10.0, fFilterLowshelfAttenuation / 40.0));
     72     double w = 2.0 * M_PI * (fFilterTransitionFreq / DEFAULT_TTS_RATE);
     73     double sinw = float(sin(w));
     74     double cosw = float(cos(w));
     75     double beta = float(sqrt(amp)/fFilterShelfSlope);
     76 
     77     // initialize low-shelf parameters
     78     double b0 = amp * ((amp+1.0F) - ((amp-1.0F)*cosw) + (beta*sinw));
     79     double b1 = 2.0F * amp * ((amp-1.0F) - ((amp+1.0F)*cosw));
     80     double b2 = amp * ((amp+1.0F) - ((amp-1.0F)*cosw) - (beta*sinw));
     81     double a0 = (amp+1.0F) + ((amp-1.0F)*cosw) + (beta*sinw);
     82     double a1 = 2.0F * ((amp-1.0F) + ((amp+1.0F)*cosw));
     83     double a2 = -((amp+1.0F) + ((amp-1.0F)*cosw) - (beta*sinw));
     84 
     85     m_fa = fFilterGain * b0/a0;
     86     m_fb = fFilterGain * b1/a0;
     87     m_fc = fFilterGain * b2/a0;
     88     m_fd = a1/a0;
     89     m_fe = a2/a0;
     90 }
     91 
     92 void initializeFilter() {
     93     x0 = 0.0f;
     94     x1 = 0.0f;
     95     x2 = 0.0f;
     96     out0 = 0.0f;
     97     out1 = 0.0f;
     98     out2 = 0.0f;
     99 }
    100 
    101 void applyFilter(int16_t* buffer, size_t sampleCount) {
    102 
    103     for (size_t i=0 ; i<sampleCount ; i++) {
    104 
    105         x0 = (double) buffer[i];
    106 
    107         out0 = (m_fa*x0) + (m_fb*x1) + (m_fc*x2) + (m_fd*out1) + (m_fe*out2);
    108 
    109         x2 = x1;
    110         x1 = x0;
    111 
    112         out2 = out1;
    113         out1 = out0;
    114 
    115         if (out0 > 32767.0f) {
    116             buffer[i] = 32767;
    117         } else if (out0 < -32768.0f) {
    118             buffer[i] = -32768;
    119         } else {
    120             buffer[i] = (int16_t) out0;
    121         }
    122     }
    123 }
    124 
    125 
    126 // ----------------------------------------------------------------------------
    127 
    128 static jmethodID synthesisRequest_start;
    129 static jmethodID synthesisRequest_audioAvailable;
    130 static jmethodID synthesisRequest_done;
    131 
    132 static Mutex engineMutex;
    133 
    134 
    135 
    136 typedef android_tts_engine_t *(*android_tts_entrypoint)();
    137 
    138 // ----------------------------------------------------------------------------
    139 class SynthProxyJniStorage {
    140   public:
    141     android_tts_engine_t *mEngine;
    142     void *mEngineLibHandle;
    143     int8_t *mBuffer;
    144     size_t mBufferSize;
    145 
    146     SynthProxyJniStorage() {
    147         mEngine = NULL;
    148         mEngineLibHandle = NULL;
    149         mBufferSize = DEFAULT_TTS_BUFFERSIZE;
    150         mBuffer = new int8_t[mBufferSize];
    151         memset(mBuffer, 0, mBufferSize);
    152     }
    153 
    154     ~SynthProxyJniStorage() {
    155         if (mEngine) {
    156             mEngine->funcs->shutdown(mEngine);
    157             mEngine = NULL;
    158         }
    159         if (mEngineLibHandle) {
    160             int res = dlclose(mEngineLibHandle);
    161             LOGE_IF( res != 0, "~SynthProxyJniStorage(): dlclose returned %d", res);
    162         }
    163         delete[] mBuffer;
    164     }
    165 
    166 };
    167 
    168 // ----------------------------------------------------------------------------
    169 
    170 struct SynthRequestData {
    171     SynthProxyJniStorage *jniStorage;
    172     JNIEnv *env;
    173     jobject request;
    174     bool startCalled;
    175 };
    176 
    177 // ----------------------------------------------------------------------------
    178 
    179 /*
    180  * Calls into Java
    181  */
    182 
    183 static bool checkException(JNIEnv *env)
    184 {
    185     jthrowable ex = env->ExceptionOccurred();
    186     if (ex == NULL) {
    187         return false;
    188     }
    189     env->ExceptionClear();
    190     LOGE_EX(env, ex);
    191     env->DeleteLocalRef(ex);
    192     return true;
    193 }
    194 
    195 static int callRequestStart(JNIEnv *env, jobject request,
    196         uint32_t rate, android_tts_audio_format_t format, int channelCount)
    197 {
    198     int encoding;
    199 
    200     switch (format) {
    201     case ANDROID_TTS_AUDIO_FORMAT_DEFAULT:
    202         encoding = AUDIO_FORMAT_ENCODING_DEFAULT;
    203         break;
    204     case ANDROID_TTS_AUDIO_FORMAT_PCM_8_BIT:
    205         encoding = AUDIO_FORMAT_ENCODING_PCM_8_BIT;
    206         break;
    207     case ANDROID_TTS_AUDIO_FORMAT_PCM_16_BIT:
    208         encoding = AUDIO_FORMAT_ENCODING_PCM_16_BIT;
    209         break;
    210     default:
    211         LOGE("Can't play, bad format");
    212         return ANDROID_TTS_FAILURE;
    213     }
    214 
    215     int result = env->CallIntMethod(request, synthesisRequest_start, rate, encoding, channelCount);
    216     if (checkException(env)) {
    217         return ANDROID_TTS_FAILURE;
    218     }
    219     return result;
    220 }
    221 
    222 static int callRequestAudioAvailable(JNIEnv *env, jobject request, int8_t *buffer,
    223         int offset, int length)
    224 {
    225     // TODO: Not nice to have to copy the buffer. Use ByteBuffer?
    226     jbyteArray javaBuffer = env->NewByteArray(length);
    227     if (javaBuffer == NULL) {
    228         LOGE("Failed to allocate byte array");
    229         return ANDROID_TTS_FAILURE;
    230     }
    231 
    232     env->SetByteArrayRegion(javaBuffer, 0, length, static_cast<jbyte *>(buffer + offset));
    233     if (checkException(env)) {
    234         env->DeleteLocalRef(javaBuffer);
    235         return ANDROID_TTS_FAILURE;
    236     }
    237     int result = env->CallIntMethod(request, synthesisRequest_audioAvailable,
    238             javaBuffer, offset, length);
    239     if (checkException(env)) {
    240         env->DeleteLocalRef(javaBuffer);
    241         return ANDROID_TTS_FAILURE;
    242     }
    243     env->DeleteLocalRef(javaBuffer);
    244     return result;
    245 }
    246 
    247 static int callRequestDone(JNIEnv *env, jobject request)
    248 {
    249     int result = env->CallIntMethod(request, synthesisRequest_done);
    250     if (checkException(env)) {
    251         return ANDROID_TTS_FAILURE;
    252     }
    253     return result;
    254 }
    255 
    256 /*
    257  * Callback from TTS engine.
    258  */
    259 extern "C" android_tts_callback_status_t
    260 __ttsSynthDoneCB(void **pUserdata, uint32_t rate,
    261                android_tts_audio_format_t format, int channelCount,
    262                int8_t **pWav, size_t *pBufferSize,
    263                android_tts_synth_status_t status)
    264 {
    265     if (*pUserdata == NULL){
    266         LOGE("userdata == NULL");
    267         return ANDROID_TTS_CALLBACK_HALT;
    268     }
    269 
    270     SynthRequestData *pRequestData = static_cast<SynthRequestData*>(*pUserdata);
    271     SynthProxyJniStorage *pJniData = pRequestData->jniStorage;
    272     JNIEnv *env = pRequestData->env;
    273 
    274     if (*pWav != NULL && *pBufferSize > 0) {
    275         if (bUseFilter) {
    276             applyFilter(reinterpret_cast<int16_t*>(*pWav), *pBufferSize/2);
    277         }
    278 
    279         if (!pRequestData->startCalled) {
    280             // TODO: is encoding one of the AudioFormat.ENCODING_* constants?
    281             pRequestData->startCalled = true;
    282             if (callRequestStart(env, pRequestData->request, rate, format, channelCount)
    283                     != ANDROID_TTS_SUCCESS) {
    284                 return ANDROID_TTS_CALLBACK_HALT;
    285             }
    286         }
    287 
    288         if (callRequestAudioAvailable(env, pRequestData->request, *pWav, 0, *pBufferSize)
    289                 != ANDROID_TTS_SUCCESS) {
    290             return ANDROID_TTS_CALLBACK_HALT;
    291         }
    292 
    293         memset(*pWav, 0, *pBufferSize);
    294     }
    295 
    296     if (pWav == NULL || status == ANDROID_TTS_SYNTH_DONE) {
    297         callRequestDone(env, pRequestData->request);
    298         env->DeleteGlobalRef(pRequestData->request);
    299         delete pRequestData;
    300         pRequestData = NULL;
    301         return ANDROID_TTS_CALLBACK_HALT;
    302     }
    303 
    304     *pBufferSize = pJniData->mBufferSize;
    305 
    306     return ANDROID_TTS_CALLBACK_CONTINUE;
    307 }
    308 
    309 
    310 // ----------------------------------------------------------------------------
    311 static int
    312 com_android_tts_compat_SynthProxy_setLowShelf(JNIEnv *env, jobject thiz, jboolean applyFilter,
    313         jfloat filterGain, jfloat attenuationInDb, jfloat freqInHz, jfloat slope)
    314 {
    315     bUseFilter = applyFilter;
    316     if (applyFilter) {
    317         fFilterLowshelfAttenuation = attenuationInDb;
    318         fFilterTransitionFreq = freqInHz;
    319         fFilterShelfSlope = slope;
    320         fFilterGain = filterGain;
    321 
    322         if (fFilterShelfSlope != 0.0f) {
    323             initializeEQ();
    324         } else {
    325             LOGE("Invalid slope, can't be null");
    326             return ANDROID_TTS_FAILURE;
    327         }
    328     }
    329 
    330     return ANDROID_TTS_SUCCESS;
    331 }
    332 
    333 // ----------------------------------------------------------------------------
    334 static jint
    335 com_android_tts_compat_SynthProxy_native_setup(JNIEnv *env, jobject thiz,
    336         jstring nativeSoLib, jstring engConfig)
    337 {
    338     int result = 0;
    339     bUseFilter = false;
    340 
    341     const char *nativeSoLibNativeString =  env->GetStringUTFChars(nativeSoLib, 0);
    342     const char *engConfigString = env->GetStringUTFChars(engConfig, 0);
    343 
    344     void *engine_lib_handle = dlopen(nativeSoLibNativeString,
    345             RTLD_NOW | RTLD_LOCAL);
    346     if (engine_lib_handle == NULL) {
    347         LOGE("com_android_tts_compat_SynthProxy_native_setup(): engine_lib_handle == NULL");
    348     } else {
    349         android_tts_entrypoint get_TtsEngine =
    350             reinterpret_cast<android_tts_entrypoint>(dlsym(engine_lib_handle, "android_getTtsEngine"));
    351 
    352         // Support obsolete/legacy binary modules
    353         if (get_TtsEngine == NULL) {
    354             get_TtsEngine =
    355                 reinterpret_cast<android_tts_entrypoint>(dlsym(engine_lib_handle, "getTtsEngine"));
    356         }
    357 
    358         android_tts_engine_t *engine = (*get_TtsEngine)();
    359         if (engine) {
    360             Mutex::Autolock l(engineMutex);
    361             engine->funcs->init(engine, __ttsSynthDoneCB, engConfigString);
    362 
    363             SynthProxyJniStorage *pSynthData = new SynthProxyJniStorage();
    364             pSynthData->mEngine = engine;
    365             pSynthData->mEngineLibHandle = engine_lib_handle;
    366             result = reinterpret_cast<jint>(pSynthData);
    367         }
    368     }
    369 
    370     env->ReleaseStringUTFChars(nativeSoLib, nativeSoLibNativeString);
    371     env->ReleaseStringUTFChars(engConfig, engConfigString);
    372 
    373     return result;
    374 }
    375 
    376 static SynthProxyJniStorage *getSynthData(jint jniData)
    377 {
    378     if (jniData == 0) {
    379         LOGE("Engine not initialized");
    380         return NULL;
    381     }
    382     return reinterpret_cast<SynthProxyJniStorage *>(jniData);
    383 }
    384 
    385 static void
    386 com_android_tts_compat_SynthProxy_native_finalize(JNIEnv *env, jobject thiz, jint jniData)
    387 {
    388     SynthProxyJniStorage* pSynthData = getSynthData(jniData);
    389     if (pSynthData == NULL) {
    390         return;
    391     }
    392 
    393     Mutex::Autolock l(engineMutex);
    394 
    395     delete pSynthData;
    396 }
    397 
    398 static void
    399 com_android_tts_compat_SynthProxy_shutdown(JNIEnv *env, jobject thiz, jint jniData)
    400 {
    401     com_android_tts_compat_SynthProxy_native_finalize(env, thiz, jniData);
    402 }
    403 
    404 static int
    405 com_android_tts_compat_SynthProxy_isLanguageAvailable(JNIEnv *env, jobject thiz, jint jniData,
    406         jstring language, jstring country, jstring variant)
    407 {
    408     SynthProxyJniStorage* pSynthData = getSynthData(jniData);
    409     if (pSynthData == NULL) {
    410         return ANDROID_TTS_LANG_NOT_SUPPORTED;
    411     }
    412 
    413     android_tts_engine_t *engine = pSynthData->mEngine;
    414     if (!engine) {
    415         return ANDROID_TTS_LANG_NOT_SUPPORTED;
    416     }
    417 
    418     const char *langNativeString = env->GetStringUTFChars(language, 0);
    419     const char *countryNativeString = env->GetStringUTFChars(country, 0);
    420     const char *variantNativeString = env->GetStringUTFChars(variant, 0);
    421 
    422     int result = engine->funcs->isLanguageAvailable(engine, langNativeString,
    423             countryNativeString, variantNativeString);
    424 
    425     env->ReleaseStringUTFChars(language, langNativeString);
    426     env->ReleaseStringUTFChars(country, countryNativeString);
    427     env->ReleaseStringUTFChars(variant, variantNativeString);
    428 
    429     return result;
    430 }
    431 
    432 static int
    433 com_android_tts_compat_SynthProxy_setLanguage(JNIEnv *env, jobject thiz, jint jniData,
    434         jstring language, jstring country, jstring variant)
    435 {
    436     SynthProxyJniStorage* pSynthData = getSynthData(jniData);
    437     if (pSynthData == NULL) {
    438         return ANDROID_TTS_LANG_NOT_SUPPORTED;
    439     }
    440 
    441     Mutex::Autolock l(engineMutex);
    442 
    443     android_tts_engine_t *engine = pSynthData->mEngine;
    444     if (!engine) {
    445         return ANDROID_TTS_LANG_NOT_SUPPORTED;
    446     }
    447 
    448     const char *langNativeString = env->GetStringUTFChars(language, 0);
    449     const char *countryNativeString = env->GetStringUTFChars(country, 0);
    450     const char *variantNativeString = env->GetStringUTFChars(variant, 0);
    451 
    452     int result = engine->funcs->setLanguage(engine, langNativeString,
    453             countryNativeString, variantNativeString);
    454 
    455     env->ReleaseStringUTFChars(language, langNativeString);
    456     env->ReleaseStringUTFChars(country, countryNativeString);
    457     env->ReleaseStringUTFChars(variant, variantNativeString);
    458 
    459     return result;
    460 }
    461 
    462 
    463 static int
    464 com_android_tts_compat_SynthProxy_loadLanguage(JNIEnv *env, jobject thiz, jint jniData,
    465         jstring language, jstring country, jstring variant)
    466 {
    467     SynthProxyJniStorage* pSynthData = getSynthData(jniData);
    468     if (pSynthData == NULL) {
    469         return ANDROID_TTS_LANG_NOT_SUPPORTED;
    470     }
    471 
    472     android_tts_engine_t *engine = pSynthData->mEngine;
    473     if (!engine) {
    474         return ANDROID_TTS_LANG_NOT_SUPPORTED;
    475     }
    476 
    477     const char *langNativeString = env->GetStringUTFChars(language, 0);
    478     const char *countryNativeString = env->GetStringUTFChars(country, 0);
    479     const char *variantNativeString = env->GetStringUTFChars(variant, 0);
    480 
    481     int result = engine->funcs->loadLanguage(engine, langNativeString,
    482             countryNativeString, variantNativeString);
    483 
    484     env->ReleaseStringUTFChars(language, langNativeString);
    485     env->ReleaseStringUTFChars(country, countryNativeString);
    486     env->ReleaseStringUTFChars(variant, variantNativeString);
    487 
    488     return result;
    489 }
    490 
    491 static int
    492 com_android_tts_compat_SynthProxy_setProperty(JNIEnv *env, jobject thiz, jint jniData,
    493         jstring name, jstring value)
    494 {
    495     SynthProxyJniStorage* pSynthData = getSynthData(jniData);
    496     if (pSynthData == NULL) {
    497         return ANDROID_TTS_FAILURE;
    498     }
    499 
    500     Mutex::Autolock l(engineMutex);
    501 
    502     android_tts_engine_t *engine = pSynthData->mEngine;
    503     if (!engine) {
    504         return ANDROID_TTS_FAILURE;
    505     }
    506 
    507     const char *nameChars = env->GetStringUTFChars(name, 0);
    508     const char *valueChars = env->GetStringUTFChars(value, 0);
    509     size_t valueLength = env->GetStringUTFLength(value);
    510 
    511     int result = engine->funcs->setProperty(engine, nameChars, valueChars, valueLength);
    512 
    513     env->ReleaseStringUTFChars(name, nameChars);
    514     env->ReleaseStringUTFChars(name, valueChars);
    515 
    516     return result;
    517 }
    518 
    519 static int
    520 com_android_tts_compat_SynthProxy_speak(JNIEnv *env, jobject thiz, jint jniData,
    521         jstring textJavaString, jobject request)
    522 {
    523     SynthProxyJniStorage* pSynthData = getSynthData(jniData);
    524     if (pSynthData == NULL) {
    525         return ANDROID_TTS_FAILURE;
    526     }
    527 
    528     initializeFilter();
    529 
    530     Mutex::Autolock l(engineMutex);
    531 
    532     android_tts_engine_t *engine = pSynthData->mEngine;
    533     if (!engine) {
    534         return ANDROID_TTS_FAILURE;
    535     }
    536 
    537     SynthRequestData *pRequestData = new SynthRequestData;
    538     pRequestData->jniStorage = pSynthData;
    539     pRequestData->env = env;
    540     pRequestData->request = env->NewGlobalRef(request);
    541     pRequestData->startCalled = false;
    542 
    543     const char *textNativeString = env->GetStringUTFChars(textJavaString, 0);
    544     memset(pSynthData->mBuffer, 0, pSynthData->mBufferSize);
    545 
    546     int result = engine->funcs->synthesizeText(engine, textNativeString,
    547             pSynthData->mBuffer, pSynthData->mBufferSize, static_cast<void *>(pRequestData));
    548     env->ReleaseStringUTFChars(textJavaString, textNativeString);
    549 
    550     return result;
    551 }
    552 
    553 static int
    554 com_android_tts_compat_SynthProxy_stop(JNIEnv *env, jobject thiz, jint jniData)
    555 {
    556     SynthProxyJniStorage* pSynthData = getSynthData(jniData);
    557     if (pSynthData == NULL) {
    558         return ANDROID_TTS_FAILURE;
    559     }
    560 
    561     android_tts_engine_t *engine = pSynthData->mEngine;
    562     if (!engine) {
    563         return ANDROID_TTS_FAILURE;
    564     }
    565 
    566     return engine->funcs->stop(engine);
    567 }
    568 
    569 static int
    570 com_android_tts_compat_SynthProxy_stopSync(JNIEnv *env, jobject thiz, jint jniData)
    571 {
    572     SynthProxyJniStorage* pSynthData = getSynthData(jniData);
    573     if (pSynthData == NULL) {
    574         return ANDROID_TTS_FAILURE;
    575     }
    576 
    577     // perform a regular stop
    578     int result = com_android_tts_compat_SynthProxy_stop(env, thiz, jniData);
    579     // but wait on the engine having released the engine mutex which protects
    580     // the synthesizer resources.
    581     engineMutex.lock();
    582     engineMutex.unlock();
    583 
    584     return result;
    585 }
    586 
    587 static jobjectArray
    588 com_android_tts_compat_SynthProxy_getLanguage(JNIEnv *env, jobject thiz, jint jniData)
    589 {
    590     SynthProxyJniStorage* pSynthData = getSynthData(jniData);
    591     if (pSynthData == NULL) {
    592         return NULL;
    593     }
    594 
    595     if (pSynthData->mEngine) {
    596         size_t bufSize = 100;
    597         char lang[bufSize];
    598         char country[bufSize];
    599         char variant[bufSize];
    600         memset(lang, 0, bufSize);
    601         memset(country, 0, bufSize);
    602         memset(variant, 0, bufSize);
    603         jobjectArray retLocale = (jobjectArray)env->NewObjectArray(3,
    604                 env->FindClass("java/lang/String"), env->NewStringUTF(""));
    605 
    606         android_tts_engine_t *engine = pSynthData->mEngine;
    607         engine->funcs->getLanguage(engine, lang, country, variant);
    608         env->SetObjectArrayElement(retLocale, 0, env->NewStringUTF(lang));
    609         env->SetObjectArrayElement(retLocale, 1, env->NewStringUTF(country));
    610         env->SetObjectArrayElement(retLocale, 2, env->NewStringUTF(variant));
    611         return retLocale;
    612     } else {
    613         return NULL;
    614     }
    615 }
    616 
    617 
    618 // Dalvik VM type signatures
    619 static JNINativeMethod gMethods[] = {
    620     {   "native_stop",
    621         "(I)I",
    622         (void*)com_android_tts_compat_SynthProxy_stop
    623     },
    624     {   "native_stopSync",
    625         "(I)I",
    626         (void*)com_android_tts_compat_SynthProxy_stopSync
    627     },
    628     {   "native_speak",
    629         "(ILjava/lang/String;Landroid/speech/tts/SynthesisCallback;)I",
    630         (void*)com_android_tts_compat_SynthProxy_speak
    631     },
    632     {   "native_isLanguageAvailable",
    633         "(ILjava/lang/String;Ljava/lang/String;Ljava/lang/String;)I",
    634         (void*)com_android_tts_compat_SynthProxy_isLanguageAvailable
    635     },
    636     {   "native_setLanguage",
    637         "(ILjava/lang/String;Ljava/lang/String;Ljava/lang/String;)I",
    638         (void*)com_android_tts_compat_SynthProxy_setLanguage
    639     },
    640     {   "native_loadLanguage",
    641         "(ILjava/lang/String;Ljava/lang/String;Ljava/lang/String;)I",
    642         (void*)com_android_tts_compat_SynthProxy_loadLanguage
    643     },
    644     {   "native_setProperty",
    645         "(ILjava/lang/String;Ljava/lang/String;)I",
    646         (void*)com_android_tts_compat_SynthProxy_setProperty
    647     },
    648     {   "native_getLanguage",
    649         "(I)[Ljava/lang/String;",
    650         (void*)com_android_tts_compat_SynthProxy_getLanguage
    651     },
    652     {   "native_shutdown",
    653         "(I)V",
    654         (void*)com_android_tts_compat_SynthProxy_shutdown
    655     },
    656     {   "native_setup",
    657         "(Ljava/lang/String;Ljava/lang/String;)I",
    658         (void*)com_android_tts_compat_SynthProxy_native_setup
    659     },
    660     {   "native_setLowShelf",
    661         "(ZFFFF)I",
    662         (void*)com_android_tts_compat_SynthProxy_setLowShelf
    663     },
    664     {   "native_finalize",
    665         "(I)V",
    666         (void*)com_android_tts_compat_SynthProxy_native_finalize
    667     }
    668 };
    669 
    670 jint JNI_OnLoad(JavaVM* vm, void* reserved)
    671 {
    672     JNIEnv* env = NULL;
    673 
    674     if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {
    675         LOGE("ERROR: GetEnv failed\n");
    676         return -1;
    677     }
    678     assert(env != NULL);
    679 
    680     jclass classSynthesisRequest = env->FindClass(
    681             "android/speech/tts/SynthesisCallback");
    682     if (classSynthesisRequest == NULL) {
    683         return -1;
    684     }
    685 
    686     synthesisRequest_start = env->GetMethodID(classSynthesisRequest,
    687             "start", "(III)I");
    688     if (synthesisRequest_start == NULL) {
    689         return -1;
    690     }
    691 
    692     synthesisRequest_audioAvailable = env->GetMethodID(classSynthesisRequest,
    693             "audioAvailable", "([BII)I");
    694     if (synthesisRequest_audioAvailable == NULL) {
    695         return -1;
    696     }
    697 
    698     synthesisRequest_done = env->GetMethodID(classSynthesisRequest,
    699             "done", "()I");
    700     if (synthesisRequest_done == NULL) {
    701         return -1;
    702     }
    703 
    704     if (jniRegisterNativeMethods(
    705             env, "com/android/tts/compat/SynthProxy", gMethods, NELEM(gMethods)) < 0) {
    706         return -1;
    707     }
    708 
    709     /* success -- return valid version number */
    710     return JNI_VERSION_1_4;
    711 }
    712