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
52using namespace android;
53
54// ----------------------------------------------------------------------------
55// EQ data
56static double m_fa, m_fb, m_fc, m_fd, m_fe;
57static double x0;  // x[n]
58static double x1;  // x[n-1]
59static double x2;  // x[n-2]
60static double out0;// y[n]
61static double out1;// y[n-1]
62static double out2;// y[n-2]
63
64static float fFilterLowshelfAttenuation = FILTER_LOWSHELF_ATTENUATION;
65static float fFilterTransitionFreq = FILTER_TRANSITION_FREQ;
66static float fFilterShelfSlope = FILTER_SHELF_SLOPE;
67static float fFilterGain = FILTER_GAIN;
68static bool  bUseFilter = false;
69
70void 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
92void 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
101void 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
128static jmethodID synthesisRequest_start;
129static jmethodID synthesisRequest_audioAvailable;
130static jmethodID synthesisRequest_done;
131
132static Mutex engineMutex;
133
134
135
136typedef android_tts_engine_t *(*android_tts_entrypoint)();
137
138// ----------------------------------------------------------------------------
139class 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            ALOGE_IF( res != 0, "~SynthProxyJniStorage(): dlclose returned %d", res);
162        }
163        delete[] mBuffer;
164    }
165
166};
167
168// ----------------------------------------------------------------------------
169
170struct SynthRequestData {
171    SynthProxyJniStorage *jniStorage;
172    JNIEnv *env;
173    jobject request;
174    bool startCalled;
175};
176
177// ----------------------------------------------------------------------------
178
179/*
180 * Calls into Java
181 */
182
183static 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
195static 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        ALOGE("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
222static 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        ALOGE("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
247static 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 */
259extern "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        ALOGE("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// ----------------------------------------------------------------------------
311static int
312com_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            ALOGE("Invalid slope, can't be null");
326            return ANDROID_TTS_FAILURE;
327        }
328    }
329
330    return ANDROID_TTS_SUCCESS;
331}
332
333// ----------------------------------------------------------------------------
334static jint
335com_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        ALOGE("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
376static SynthProxyJniStorage *getSynthData(jint jniData)
377{
378    if (jniData == 0) {
379        ALOGE("Engine not initialized");
380        return NULL;
381    }
382    return reinterpret_cast<SynthProxyJniStorage *>(jniData);
383}
384
385static void
386com_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
398static void
399com_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
404static int
405com_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
432static int
433com_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
463static int
464com_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
491static int
492com_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
519static int
520com_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
553static int
554com_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
569static int
570com_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
587static jobjectArray
588com_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
619static 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
670jint 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        ALOGE("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