blob: 27d1fc0aeca0e2dd5b5f7f88012f180b0f6e2ae3 [file] [log] [blame]
Jean-Michel Trivi700ec652009-05-27 15:01:59 -07001/*
David 'Digit' Turner01f2f962010-05-20 16:57:34 -07002 * Copyright (C) 2009-2010 Google Inc.
Jean-Michel Trivi700ec652009-05-27 15:01:59 -07003 *
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 */
Jean-Michel Trivi700ec652009-05-27 15:01:59 -070016
17#include <stdio.h>
18#include <unistd.h>
19
Jean-Michel Trivi9d2d26a2011-01-05 16:08:21 -080020#define LOG_TAG "SynthProxyJNI"
Jean-Michel Trivi700ec652009-05-27 15:01:59 -070021
22#include <utils/Log.h>
23#include <nativehelper/jni.h>
24#include <nativehelper/JNIHelp.h>
25#include <android_runtime/AndroidRuntime.h>
David 'Digit' Turner01f2f962010-05-20 16:57:34 -070026#include <android/tts.h>
Jean-Michel Trivi700ec652009-05-27 15:01:59 -070027#include <media/AudioTrack.h>
Jean-Michel Trivi4fb7d882009-08-13 19:05:45 -070028#include <math.h>
Jean-Michel Trivi700ec652009-05-27 15:01:59 -070029
30#include <dlfcn.h>
31
32#define DEFAULT_TTS_RATE 16000
33#define DEFAULT_TTS_FORMAT AudioSystem::PCM_16_BIT
34#define DEFAULT_TTS_NB_CHANNELS 1
Jean-Michel Trivi4fb7d882009-08-13 19:05:45 -070035#define DEFAULT_TTS_BUFFERSIZE 2048
Jean-Michel Trivi9440bce2009-07-13 10:12:37 -070036#define DEFAULT_TTS_STREAM_TYPE AudioSystem::MUSIC
Jean-Michel Trivi9d2d26a2011-01-05 16:08:21 -080037#define DEFAULT_VOLUME 1.0f
Jean-Michel Trivi700ec652009-05-27 15:01:59 -070038
Jean-Michel Trivi4fb7d882009-08-13 19:05:45 -070039// EQ + BOOST parameters
40#define FILTER_LOWSHELF_ATTENUATION -18.0f // in dB
41#define FILTER_TRANSITION_FREQ 1100.0f // in Hz
42#define FILTER_SHELF_SLOPE 1.0f // Q
Jean-Michel Trivi32184712009-09-01 10:22:51 -070043#define FILTER_GAIN 5.5f // linear gain
Jean-Michel Trivi4fb7d882009-08-13 19:05:45 -070044
Jean-Michel Trivi700ec652009-05-27 15:01:59 -070045#define USAGEMODE_PLAY_IMMEDIATELY 0
46#define USAGEMODE_WRITE_TO_FILE 1
47
Jean-Michel Trivie3c18902010-02-24 18:52:39 -080048#define SYNTHPLAYSTATE_IS_STOPPED 0
49#define SYNTHPLAYSTATE_IS_PLAYING 1
50
Jean-Michel Trivi700ec652009-05-27 15:01:59 -070051using namespace android;
52
53// ----------------------------------------------------------------------------
54struct fields_t {
55 jfieldID synthProxyFieldJniData;
56 jclass synthProxyClass;
57 jmethodID synthProxyMethodPost;
58};
59
Jean-Michel Trivi9440bce2009-07-13 10:12:37 -070060// structure to hold the data that is used each time the TTS engine has synthesized more data
Jean-Michel Trivi700ec652009-05-27 15:01:59 -070061struct afterSynthData_t {
62 jint jniStorage;
63 int usageMode;
64 FILE* outputFile;
Jean-Michel Trivi9440bce2009-07-13 10:12:37 -070065 AudioSystem::stream_type streamType;
Jean-Michel Trivi700ec652009-05-27 15:01:59 -070066};
67
68// ----------------------------------------------------------------------------
Jean-Michel Trivi4fb7d882009-08-13 19:05:45 -070069// EQ data
70double amp;
71double w;
72double sinw;
73double cosw;
74double beta;
75double a0, a1, a2, b0, b1, b2;
76double m_fa, m_fb, m_fc, m_fd, m_fe;
77double x0; // x[n]
78double x1; // x[n-1]
79double x2; // x[n-2]
80double out0;// y[n]
81double out1;// y[n-1]
82double out2;// y[n-2]
83
Jean-Michel Trivi0320f8b2010-01-11 14:04:50 -080084static float fFilterLowshelfAttenuation = FILTER_LOWSHELF_ATTENUATION;
85static float fFilterTransitionFreq = FILTER_TRANSITION_FREQ;
86static float fFilterShelfSlope = FILTER_SHELF_SLOPE;
87static float fFilterGain = FILTER_GAIN;
88static bool bUseFilter = false;
89
Jean-Michel Trivi4fb7d882009-08-13 19:05:45 -070090void initializeEQ() {
91
Jean-Michel Trivi0320f8b2010-01-11 14:04:50 -080092 amp = float(pow(10.0, fFilterLowshelfAttenuation / 40.0));
93 w = 2.0 * M_PI * (fFilterTransitionFreq / DEFAULT_TTS_RATE);
Jean-Michel Trivi4fb7d882009-08-13 19:05:45 -070094 sinw = float(sin(w));
95 cosw = float(cos(w));
Jean-Michel Trivi0320f8b2010-01-11 14:04:50 -080096 beta = float(sqrt(amp)/fFilterShelfSlope);
Jean-Michel Trivi4fb7d882009-08-13 19:05:45 -070097
98 // initialize low-shelf parameters
99 b0 = amp * ((amp+1.0F) - ((amp-1.0F)*cosw) + (beta*sinw));
100 b1 = 2.0F * amp * ((amp-1.0F) - ((amp+1.0F)*cosw));
101 b2 = amp * ((amp+1.0F) - ((amp-1.0F)*cosw) - (beta*sinw));
102 a0 = (amp+1.0F) + ((amp-1.0F)*cosw) + (beta*sinw);
103 a1 = 2.0F * ((amp-1.0F) + ((amp+1.0F)*cosw));
104 a2 = -((amp+1.0F) + ((amp-1.0F)*cosw) - (beta*sinw));
105
Jean-Michel Trivi0320f8b2010-01-11 14:04:50 -0800106 m_fa = fFilterGain * b0/a0;
107 m_fb = fFilterGain * b1/a0;
108 m_fc = fFilterGain * b2/a0;
Jean-Michel Trivi4fb7d882009-08-13 19:05:45 -0700109 m_fd = a1/a0;
110 m_fe = a2/a0;
111}
112
113void initializeFilter() {
114 x0 = 0.0f;
115 x1 = 0.0f;
116 x2 = 0.0f;
117 out0 = 0.0f;
118 out1 = 0.0f;
119 out2 = 0.0f;
120}
121
122void applyFilter(int16_t* buffer, size_t sampleCount) {
123
124 for (size_t i=0 ; i<sampleCount ; i++) {
125
126 x0 = (double) buffer[i];
127
128 out0 = (m_fa*x0) + (m_fb*x1) + (m_fc*x2) + (m_fd*out1) + (m_fe*out2);
129
130 x2 = x1;
131 x1 = x0;
132
133 out2 = out1;
134 out1 = out0;
135
136 if (out0 > 32767.0f) {
137 buffer[i] = 32767;
138 } else if (out0 < -32768.0f) {
139 buffer[i] = -32768;
140 } else {
141 buffer[i] = (int16_t) out0;
142 }
143 }
144}
145
146
147// ----------------------------------------------------------------------------
Jean-Michel Trivi700ec652009-05-27 15:01:59 -0700148static fields_t javaTTSFields;
149
Jean-Michel Trivi5e11a6a2009-07-20 14:05:33 -0700150// TODO move to synth member once we have multiple simultaneous engines running
151static Mutex engineMutex;
152
Jean-Michel Trivi700ec652009-05-27 15:01:59 -0700153// ----------------------------------------------------------------------------
154class SynthProxyJniStorage {
155 public :
Jean-Michel Trivi700ec652009-05-27 15:01:59 -0700156 jobject tts_ref;
Jean-Michel Trivi9d2d26a2011-01-05 16:08:21 -0800157 android_tts_engine_t* mEngine;
Jean-Michel Trivicee3bd42009-07-21 14:12:47 -0700158 void* mEngineLibHandle;
Jean-Michel Trivi700ec652009-05-27 15:01:59 -0700159 AudioTrack* mAudioOut;
Jean-Michel Trivie3c18902010-02-24 18:52:39 -0800160 int8_t mPlayState;
161 Mutex mPlayLock;
Jean-Michel Trivi9440bce2009-07-13 10:12:37 -0700162 AudioSystem::stream_type mStreamType;
Jean-Michel Trivi700ec652009-05-27 15:01:59 -0700163 uint32_t mSampleRate;
Eric Laurenta553c252009-07-17 12:17:14 -0700164 uint32_t mAudFormat;
Jean-Michel Trivi700ec652009-05-27 15:01:59 -0700165 int mNbChannels;
Charles Chen83e712a2009-06-05 13:58:33 -0700166 int8_t * mBuffer;
167 size_t mBufferSize;
Jean-Michel Trivi9d2d26a2011-01-05 16:08:21 -0800168 float mVolume[2];
Jean-Michel Trivi700ec652009-05-27 15:01:59 -0700169
170 SynthProxyJniStorage() {
Jean-Michel Trivi700ec652009-05-27 15:01:59 -0700171 tts_ref = NULL;
David 'Digit' Turner01f2f962010-05-20 16:57:34 -0700172 mEngine = NULL;
Jean-Michel Trivicee3bd42009-07-21 14:12:47 -0700173 mEngineLibHandle = NULL;
Jean-Michel Trivi700ec652009-05-27 15:01:59 -0700174 mAudioOut = NULL;
Jean-Michel Trivie3c18902010-02-24 18:52:39 -0800175 mPlayState = SYNTHPLAYSTATE_IS_STOPPED;
Jean-Michel Trivi9440bce2009-07-13 10:12:37 -0700176 mStreamType = DEFAULT_TTS_STREAM_TYPE;
Jean-Michel Trivi700ec652009-05-27 15:01:59 -0700177 mSampleRate = DEFAULT_TTS_RATE;
178 mAudFormat = DEFAULT_TTS_FORMAT;
179 mNbChannels = DEFAULT_TTS_NB_CHANNELS;
Charles Chen83e712a2009-06-05 13:58:33 -0700180 mBufferSize = DEFAULT_TTS_BUFFERSIZE;
181 mBuffer = new int8_t[mBufferSize];
Charles Chen4a3368f2009-07-14 17:11:44 -0700182 memset(mBuffer, 0, mBufferSize);
Jean-Michel Trivi9d2d26a2011-01-05 16:08:21 -0800183 mVolume[AudioTrack::LEFT] = DEFAULT_VOLUME;
184 mVolume[AudioTrack::RIGHT] = DEFAULT_VOLUME;
Jean-Michel Trivi700ec652009-05-27 15:01:59 -0700185 }
186
187 ~SynthProxyJniStorage() {
Jean-Michel Trivicee3bd42009-07-21 14:12:47 -0700188 //LOGV("entering ~SynthProxyJniStorage()");
Jean-Michel Trivi700ec652009-05-27 15:01:59 -0700189 killAudio();
David 'Digit' Turner01f2f962010-05-20 16:57:34 -0700190 if (mEngine) {
191 mEngine->funcs->shutdown(mEngine);
192 mEngine = NULL;
Jean-Michel Trivi700ec652009-05-27 15:01:59 -0700193 }
Jean-Michel Trivicee3bd42009-07-21 14:12:47 -0700194 if (mEngineLibHandle) {
Jean-Michel Trivi9d2d26a2011-01-05 16:08:21 -0800195 //LOGV("~SynthProxyJniStorage(): before close library");
Jean-Michel Trivicee3bd42009-07-21 14:12:47 -0700196 int res = dlclose(mEngineLibHandle);
197 LOGE_IF( res != 0, "~SynthProxyJniStorage(): dlclose returned %d", res);
198 }
Charles Chen83e712a2009-06-05 13:58:33 -0700199 delete mBuffer;
Jean-Michel Trivi700ec652009-05-27 15:01:59 -0700200 }
201
202 void killAudio() {
203 if (mAudioOut) {
204 mAudioOut->stop();
205 delete mAudioOut;
206 mAudioOut = NULL;
207 }
208 }
209
Jean-Michel Trivi9440bce2009-07-13 10:12:37 -0700210 void createAudioOut(AudioSystem::stream_type streamType, uint32_t rate,
211 AudioSystem::audio_format format, int channel) {
Jean-Michel Trivi700ec652009-05-27 15:01:59 -0700212 mSampleRate = rate;
213 mAudFormat = format;
214 mNbChannels = channel;
Jean-Michel Trivi9440bce2009-07-13 10:12:37 -0700215 mStreamType = streamType;
Jean-Michel Trivi700ec652009-05-27 15:01:59 -0700216
217 // retrieve system properties to ensure successful creation of the
218 // AudioTrack object for playback
219 int afSampleRate;
Jean-Michel Trivi9440bce2009-07-13 10:12:37 -0700220 if (AudioSystem::getOutputSamplingRate(&afSampleRate, mStreamType) != NO_ERROR) {
Jean-Michel Trivi700ec652009-05-27 15:01:59 -0700221 afSampleRate = 44100;
222 }
223 int afFrameCount;
Jean-Michel Trivi9440bce2009-07-13 10:12:37 -0700224 if (AudioSystem::getOutputFrameCount(&afFrameCount, mStreamType) != NO_ERROR) {
Jean-Michel Trivi700ec652009-05-27 15:01:59 -0700225 afFrameCount = 2048;
226 }
227 uint32_t afLatency;
Jean-Michel Trivi9440bce2009-07-13 10:12:37 -0700228 if (AudioSystem::getOutputLatency(&afLatency, mStreamType) != NO_ERROR) {
Jean-Michel Trivi700ec652009-05-27 15:01:59 -0700229 afLatency = 500;
230 }
231 uint32_t minBufCount = afLatency / ((1000 * afFrameCount)/afSampleRate);
232 if (minBufCount < 2) minBufCount = 2;
233 int minFrameCount = (afFrameCount * rate * minBufCount)/afSampleRate;
234
Jean-Michel Trivie3c18902010-02-24 18:52:39 -0800235 mPlayLock.lock();
Eric Laurenta553c252009-07-17 12:17:14 -0700236 mAudioOut = new AudioTrack(mStreamType, rate, format,
237 (channel == 2) ? AudioSystem::CHANNEL_OUT_STEREO : AudioSystem::CHANNEL_OUT_MONO,
Jean-Michel Trivi700ec652009-05-27 15:01:59 -0700238 minFrameCount > 4096 ? minFrameCount : 4096,
239 0, 0, 0, 0); // not using an AudioTrack callback
240
241 if (mAudioOut->initCheck() != NO_ERROR) {
Jean-Michel Trivicee3bd42009-07-21 14:12:47 -0700242 LOGE("createAudioOut(): AudioTrack error");
Jean-Michel Trivi700ec652009-05-27 15:01:59 -0700243 delete mAudioOut;
244 mAudioOut = NULL;
245 } else {
Charles Chenb02ced72009-07-07 14:33:52 -0700246 //LOGI("AudioTrack OK");
Jean-Michel Trivi9d2d26a2011-01-05 16:08:21 -0800247 mAudioOut->setVolume(mVolume[AudioTrack::LEFT], mVolume[AudioTrack::RIGHT]);
Jean-Michel Trividdc63ad2010-01-08 14:06:21 -0800248 LOGV("AudioTrack ready");
Jean-Michel Trivi700ec652009-05-27 15:01:59 -0700249 }
Jean-Michel Trivie3c18902010-02-24 18:52:39 -0800250 mPlayLock.unlock();
Jean-Michel Trivi700ec652009-05-27 15:01:59 -0700251 }
252};
253
254
255// ----------------------------------------------------------------------------
Jean-Michel Trivi9440bce2009-07-13 10:12:37 -0700256void prepAudioTrack(SynthProxyJniStorage* pJniData, AudioSystem::stream_type streamType,
257 uint32_t rate, AudioSystem::audio_format format, int channel) {
Jean-Michel Trivi700ec652009-05-27 15:01:59 -0700258 // Don't bother creating a new audiotrack object if the current
Jean-Michel Trivi9440bce2009-07-13 10:12:37 -0700259 // object is already initialized with the same audio parameters.
Jean-Michel Trivi700ec652009-05-27 15:01:59 -0700260 if ( pJniData->mAudioOut &&
261 (rate == pJniData->mSampleRate) &&
262 (format == pJniData->mAudFormat) &&
Jean-Michel Trivi9440bce2009-07-13 10:12:37 -0700263 (channel == pJniData->mNbChannels) &&
264 (streamType == pJniData->mStreamType) ){
Jean-Michel Trivi700ec652009-05-27 15:01:59 -0700265 return;
266 }
267 if (pJniData->mAudioOut){
268 pJniData->killAudio();
269 }
Jean-Michel Trivi9440bce2009-07-13 10:12:37 -0700270 pJniData->createAudioOut(streamType, rate, format, channel);
Jean-Michel Trivi700ec652009-05-27 15:01:59 -0700271}
272
273
274// ----------------------------------------------------------------------------
275/*
276 * Callback from TTS engine.
277 * Directly speaks using AudioTrack or write to file
278 */
David 'Digit' Turner01f2f962010-05-20 16:57:34 -0700279extern "C" android_tts_callback_status_t
280__ttsSynthDoneCB(void ** pUserdata, uint32_t rate,
281 android_tts_audio_format_t format, int channel,
282 int8_t **pWav, size_t *pBufferSize,
Jean-Michel Trivi9d2d26a2011-01-05 16:08:21 -0800283 android_tts_synth_status_t status)
David 'Digit' Turner01f2f962010-05-20 16:57:34 -0700284{
Charles Chen2a8a2d72009-07-07 16:24:02 -0700285 //LOGV("ttsSynthDoneCallback: %d bytes", bufferSize);
David 'Digit' Turner01f2f962010-05-20 16:57:34 -0700286 AudioSystem::audio_format encoding;
Jean-Michel Trivi700ec652009-05-27 15:01:59 -0700287
David 'Digit' Turner01f2f962010-05-20 16:57:34 -0700288 if (*pUserdata == NULL){
Charles Chen83e712a2009-06-05 13:58:33 -0700289 LOGE("userdata == NULL");
David 'Digit' Turner01f2f962010-05-20 16:57:34 -0700290 return ANDROID_TTS_CALLBACK_HALT;
Charles Chen83e712a2009-06-05 13:58:33 -0700291 }
David 'Digit' Turner01f2f962010-05-20 16:57:34 -0700292 switch (format) {
293 case ANDROID_TTS_AUDIO_FORMAT_PCM_8_BIT:
294 encoding = AudioSystem::PCM_8_BIT;
295 break;
296 case ANDROID_TTS_AUDIO_FORMAT_PCM_16_BIT:
297 encoding = AudioSystem::PCM_16_BIT;
298 break;
299 default:
300 LOGE("Can't play, bad format");
301 return ANDROID_TTS_CALLBACK_HALT;
302 }
303 afterSynthData_t* pForAfter = (afterSynthData_t*) *pUserdata;
Charles Chen83e712a2009-06-05 13:58:33 -0700304 SynthProxyJniStorage* pJniData = (SynthProxyJniStorage*)(pForAfter->jniStorage);
Jean-Michel Trivi700ec652009-05-27 15:01:59 -0700305
306 if (pForAfter->usageMode == USAGEMODE_PLAY_IMMEDIATELY){
Charles Chenb02ced72009-07-07 14:33:52 -0700307 //LOGV("Direct speech");
Jean-Michel Trivi700ec652009-05-27 15:01:59 -0700308
David 'Digit' Turner01f2f962010-05-20 16:57:34 -0700309 if (*pWav == NULL) {
Charles Chen83e712a2009-06-05 13:58:33 -0700310 delete pForAfter;
David 'Digit' Turner01f2f962010-05-20 16:57:34 -0700311 pForAfter = NULL;
Jean-Michel Trivi4fb7d882009-08-13 19:05:45 -0700312 LOGV("Null: speech has completed");
David 'Digit' Turner01f2f962010-05-20 16:57:34 -0700313 return ANDROID_TTS_CALLBACK_HALT;
Jean-Michel Trivi700ec652009-05-27 15:01:59 -0700314 }
315
David 'Digit' Turner01f2f962010-05-20 16:57:34 -0700316 if (*pBufferSize > 0) {
317 prepAudioTrack(pJniData, pForAfter->streamType, rate, encoding, channel);
Jean-Michel Trivi700ec652009-05-27 15:01:59 -0700318 if (pJniData->mAudioOut) {
Jean-Michel Trivie3c18902010-02-24 18:52:39 -0800319 pJniData->mPlayLock.lock();
320 if(pJniData->mAudioOut->stopped()
321 && (pJniData->mPlayState == SYNTHPLAYSTATE_IS_PLAYING)) {
322 pJniData->mAudioOut->start();
323 }
324 pJniData->mPlayLock.unlock();
Jean-Michel Trivi0320f8b2010-01-11 14:04:50 -0800325 if (bUseFilter) {
David 'Digit' Turner01f2f962010-05-20 16:57:34 -0700326 applyFilter((int16_t*)*pWav, *pBufferSize/2);
Jean-Michel Trivi0320f8b2010-01-11 14:04:50 -0800327 }
David 'Digit' Turner01f2f962010-05-20 16:57:34 -0700328 pJniData->mAudioOut->write(*pWav, *pBufferSize);
329 memset(*pWav, 0, *pBufferSize);
Jean-Michel Trivi6a0e2932009-06-24 11:32:06 -0700330 //LOGV("AudioTrack wrote: %d bytes", bufferSize);
Jean-Michel Trivi700ec652009-05-27 15:01:59 -0700331 } else {
Jean-Michel Trivi6a0e2932009-06-24 11:32:06 -0700332 LOGE("Can't play, null audiotrack");
David 'Digit' Turner01f2f962010-05-20 16:57:34 -0700333 delete pForAfter;
334 pForAfter = NULL;
335 return ANDROID_TTS_CALLBACK_HALT;
Jean-Michel Trivi700ec652009-05-27 15:01:59 -0700336 }
337 }
338 } else if (pForAfter->usageMode == USAGEMODE_WRITE_TO_FILE) {
Jean-Michel Trivi4fb7d882009-08-13 19:05:45 -0700339 //LOGV("Save to file");
David 'Digit' Turner01f2f962010-05-20 16:57:34 -0700340 if (*pWav == NULL) {
Charles Chen83e712a2009-06-05 13:58:33 -0700341 delete pForAfter;
Jean-Michel Trivi6a0e2932009-06-24 11:32:06 -0700342 LOGV("Null: speech has completed");
David 'Digit' Turner01f2f962010-05-20 16:57:34 -0700343 return ANDROID_TTS_CALLBACK_HALT;
Jean-Michel Trivi700ec652009-05-27 15:01:59 -0700344 }
David 'Digit' Turner01f2f962010-05-20 16:57:34 -0700345 if (*pBufferSize > 0){
Jean-Michel Trivi0320f8b2010-01-11 14:04:50 -0800346 if (bUseFilter) {
David 'Digit' Turner01f2f962010-05-20 16:57:34 -0700347 applyFilter((int16_t*)*pWav, *pBufferSize/2);
Jean-Michel Trivi0320f8b2010-01-11 14:04:50 -0800348 }
David 'Digit' Turner01f2f962010-05-20 16:57:34 -0700349 fwrite(*pWav, 1, *pBufferSize, pForAfter->outputFile);
350 memset(*pWav, 0, *pBufferSize);
Jean-Michel Trivi700ec652009-05-27 15:01:59 -0700351 }
352 }
Jean-Michel Trivi6c24f242009-06-25 17:03:51 -0700353 // Future update:
354 // For sync points in the speech, call back into the SynthProxy class through the
Jean-Michel Trivi700ec652009-05-27 15:01:59 -0700355 // javaTTSFields.synthProxyMethodPost methode to notify
Jean-Michel Trivi6c24f242009-06-25 17:03:51 -0700356 // playback has completed if the synthesis is done or if a marker has been reached.
357
David 'Digit' Turner01f2f962010-05-20 16:57:34 -0700358 if (status == ANDROID_TTS_SYNTH_DONE) {
Jean-Michel Trivi6c24f242009-06-25 17:03:51 -0700359 // this struct was allocated in the original android_tts_SynthProxy_speak call,
360 // all processing matching this call is now done.
361 LOGV("Speech synthesis done.");
Jean-Michel Trivif07d8242009-06-30 09:36:08 -0700362 if (pForAfter->usageMode == USAGEMODE_PLAY_IMMEDIATELY) {
363 // only delete for direct playback. When writing to a file, we still have work to do
364 // in android_tts_SynthProxy_synthesizeToFile. The struct will be deleted there.
365 delete pForAfter;
366 pForAfter = NULL;
367 }
David 'Digit' Turner01f2f962010-05-20 16:57:34 -0700368 return ANDROID_TTS_CALLBACK_HALT;
Jean-Michel Trivi6c24f242009-06-25 17:03:51 -0700369 }
Jean-Michel Trivi700ec652009-05-27 15:01:59 -0700370
Charles Chen83e712a2009-06-05 13:58:33 -0700371 // we don't update the wav (output) parameter as we'll let the next callback
372 // write at the same location, we've consumed the data already, but we need
373 // to update bufferSize to let the TTS engine know how much it can write the
374 // next time it calls this function.
David 'Digit' Turner01f2f962010-05-20 16:57:34 -0700375 *pBufferSize = pJniData->mBufferSize;
Charles Chen83e712a2009-06-05 13:58:33 -0700376
David 'Digit' Turner01f2f962010-05-20 16:57:34 -0700377 return ANDROID_TTS_CALLBACK_CONTINUE;
Jean-Michel Trivi700ec652009-05-27 15:01:59 -0700378}
379
380
381// ----------------------------------------------------------------------------
Jean-Michel Trivi0320f8b2010-01-11 14:04:50 -0800382static int
383android_tts_SynthProxy_setLowShelf(JNIEnv *env, jobject thiz, jboolean applyFilter,
384 jfloat filterGain, jfloat attenuationInDb, jfloat freqInHz, jfloat slope)
385{
David 'Digit' Turner01f2f962010-05-20 16:57:34 -0700386 int result = ANDROID_TTS_SUCCESS;
Jean-Michel Trivi0320f8b2010-01-11 14:04:50 -0800387
388 bUseFilter = applyFilter;
389 if (applyFilter) {
390 fFilterLowshelfAttenuation = attenuationInDb;
391 fFilterTransitionFreq = freqInHz;
392 fFilterShelfSlope = slope;
393 fFilterGain = filterGain;
394
395 if (fFilterShelfSlope != 0.0f) {
396 initializeEQ();
397 } else {
398 LOGE("Invalid slope, can't be null");
David 'Digit' Turner01f2f962010-05-20 16:57:34 -0700399 result = ANDROID_TTS_FAILURE;
Jean-Michel Trivi0320f8b2010-01-11 14:04:50 -0800400 }
401 }
402
403 return result;
404}
405
406// ----------------------------------------------------------------------------
407static int
Jean-Michel Trivi700ec652009-05-27 15:01:59 -0700408android_tts_SynthProxy_native_setup(JNIEnv *env, jobject thiz,
Jean-Michel Trivi900e0d02010-03-18 11:07:45 -0700409 jobject weak_this, jstring nativeSoLib, jstring engConfig)
Jean-Michel Trivi700ec652009-05-27 15:01:59 -0700410{
David 'Digit' Turner01f2f962010-05-20 16:57:34 -0700411 int result = ANDROID_TTS_FAILURE;
Jean-Michel Trivi0320f8b2010-01-11 14:04:50 -0800412
413 bUseFilter = false;
414
Jean-Michel Trivi700ec652009-05-27 15:01:59 -0700415 SynthProxyJniStorage* pJniStorage = new SynthProxyJniStorage();
416
417 prepAudioTrack(pJniStorage,
Jean-Michel Trivi9440bce2009-07-13 10:12:37 -0700418 DEFAULT_TTS_STREAM_TYPE, DEFAULT_TTS_RATE, DEFAULT_TTS_FORMAT, DEFAULT_TTS_NB_CHANNELS);
Jean-Michel Trivi700ec652009-05-27 15:01:59 -0700419
Jean-Michel Trivi0320f8b2010-01-11 14:04:50 -0800420 const char *nativeSoLibNativeString = env->GetStringUTFChars(nativeSoLib, 0);
Jean-Michel Trivi900e0d02010-03-18 11:07:45 -0700421 const char *engConfigString = env->GetStringUTFChars(engConfig, 0);
Jean-Michel Trivi700ec652009-05-27 15:01:59 -0700422
423 void *engine_lib_handle = dlopen(nativeSoLibNativeString,
424 RTLD_NOW | RTLD_LOCAL);
Jean-Michel Trivicee3bd42009-07-21 14:12:47 -0700425 if (engine_lib_handle == NULL) {
426 LOGE("android_tts_SynthProxy_native_setup(): engine_lib_handle == NULL");
Jean-Michel Trivi700ec652009-05-27 15:01:59 -0700427 } else {
David 'Digit' Turner01f2f962010-05-20 16:57:34 -0700428 android_tts_engine_t * (*get_TtsEngine)() =
429 reinterpret_cast<android_tts_engine_t* (*)()>(dlsym(engine_lib_handle, "android_getTtsEngine"));
Charles Chen83e712a2009-06-05 13:58:33 -0700430
David 'Digit' Turner01f2f962010-05-20 16:57:34 -0700431 // Support obsolete/legacy binary modules
432 if (get_TtsEngine == NULL) {
433 get_TtsEngine =
434 reinterpret_cast<android_tts_engine_t* (*)()>(dlsym(engine_lib_handle, "getTtsEngine"));
Jean-Michel Trivi700ec652009-05-27 15:01:59 -0700435 }
Jean-Michel Trivi0320f8b2010-01-11 14:04:50 -0800436
David 'Digit' Turner01f2f962010-05-20 16:57:34 -0700437 pJniStorage->mEngine = (*get_TtsEngine)();
438 pJniStorage->mEngineLibHandle = engine_lib_handle;
439
440 android_tts_engine_t *engine = pJniStorage->mEngine;
441 if (engine) {
442 Mutex::Autolock l(engineMutex);
443 engine->funcs->init(
444 engine,
445 __ttsSynthDoneCB,
446 engConfigString);
447 }
448
449 result = ANDROID_TTS_SUCCESS;
Jean-Michel Trivi700ec652009-05-27 15:01:59 -0700450 }
451
452 // we use a weak reference so the SynthProxy object can be garbage collected.
453 pJniStorage->tts_ref = env->NewGlobalRef(weak_this);
454
455 // save the JNI resources so we can use them (and free them) later
Jean-Michel Trivi0320f8b2010-01-11 14:04:50 -0800456 env->SetIntField(thiz, javaTTSFields.synthProxyFieldJniData, (int)pJniStorage);
Jean-Michel Trivi4fb7d882009-08-13 19:05:45 -0700457
Jean-Michel Trivi700ec652009-05-27 15:01:59 -0700458 env->ReleaseStringUTFChars(nativeSoLib, nativeSoLibNativeString);
Jean-Michel Trivi900e0d02010-03-18 11:07:45 -0700459 env->ReleaseStringUTFChars(engConfig, engConfigString);
Jean-Michel Trivi0320f8b2010-01-11 14:04:50 -0800460
461 return result;
Jean-Michel Trivi700ec652009-05-27 15:01:59 -0700462}
463
464
465static void
466android_tts_SynthProxy_native_finalize(JNIEnv *env, jobject thiz, jint jniData)
467{
Jean-Michel Trivicee3bd42009-07-21 14:12:47 -0700468 //LOGV("entering android_tts_SynthProxy_finalize()");
469 if (jniData == 0) {
470 //LOGE("android_tts_SynthProxy_native_finalize(): invalid JNI data");
471 return;
Jean-Michel Trivi700ec652009-05-27 15:01:59 -0700472 }
Jean-Michel Trivicee3bd42009-07-21 14:12:47 -0700473
474 Mutex::Autolock l(engineMutex);
475
476 SynthProxyJniStorage* pSynthData = (SynthProxyJniStorage*)jniData;
477 env->DeleteGlobalRef(pSynthData->tts_ref);
478 delete pSynthData;
479
480 env->SetIntField(thiz, javaTTSFields.synthProxyFieldJniData, 0);
481}
482
483
484static void
485android_tts_SynthProxy_shutdown(JNIEnv *env, jobject thiz, jint jniData)
486{
487 //LOGV("entering android_tts_SynthProxy_shutdown()");
488
489 // do everything a call to finalize would
490 android_tts_SynthProxy_native_finalize(env, thiz, jniData);
Jean-Michel Trivi700ec652009-05-27 15:01:59 -0700491}
492
Jean-Michel Trivi679d7282009-06-16 15:36:28 -0700493
Jean-Michel Trivibee1c7e2009-06-29 15:55:05 -0700494static int
495android_tts_SynthProxy_isLanguageAvailable(JNIEnv *env, jobject thiz, jint jniData,
496 jstring language, jstring country, jstring variant)
497{
David 'Digit' Turner01f2f962010-05-20 16:57:34 -0700498 int result = ANDROID_TTS_LANG_NOT_SUPPORTED;
Jean-Michel Trivibee1c7e2009-06-29 15:55:05 -0700499
500 if (jniData == 0) {
501 LOGE("android_tts_SynthProxy_isLanguageAvailable(): invalid JNI data");
502 return result;
503 }
504
505 SynthProxyJniStorage* pSynthData = (SynthProxyJniStorage*)jniData;
506 const char *langNativeString = env->GetStringUTFChars(language, 0);
507 const char *countryNativeString = env->GetStringUTFChars(country, 0);
508 const char *variantNativeString = env->GetStringUTFChars(variant, 0);
Charles Chen35b86c22009-07-06 10:51:48 -0700509
David 'Digit' Turner01f2f962010-05-20 16:57:34 -0700510 android_tts_engine_t *engine = pSynthData->mEngine;
511
512 if (engine) {
513 result = engine->funcs->isLanguageAvailable(engine,langNativeString,
Jean-Michel Trivibee1c7e2009-06-29 15:55:05 -0700514 countryNativeString, variantNativeString);
515 }
516 env->ReleaseStringUTFChars(language, langNativeString);
517 env->ReleaseStringUTFChars(country, countryNativeString);
518 env->ReleaseStringUTFChars(variant, variantNativeString);
519 return result;
520}
521
Jean-Michel Trivi900e0d02010-03-18 11:07:45 -0700522static int
523android_tts_SynthProxy_setConfig(JNIEnv *env, jobject thiz, jint jniData, jstring engineConfig)
524{
David 'Digit' Turner01f2f962010-05-20 16:57:34 -0700525 int result = ANDROID_TTS_FAILURE;
Jean-Michel Trivi900e0d02010-03-18 11:07:45 -0700526
527 if (jniData == 0) {
528 LOGE("android_tts_SynthProxy_setConfig(): invalid JNI data");
529 return result;
530 }
531
532 Mutex::Autolock l(engineMutex);
533
534 SynthProxyJniStorage* pSynthData = (SynthProxyJniStorage*)jniData;
535 const char *engineConfigNativeString = env->GetStringUTFChars(engineConfig, 0);
David 'Digit' Turner01f2f962010-05-20 16:57:34 -0700536 android_tts_engine_t *engine = pSynthData->mEngine;
Jean-Michel Trivi900e0d02010-03-18 11:07:45 -0700537
David 'Digit' Turner01f2f962010-05-20 16:57:34 -0700538 if (engine) {
539 result = engine->funcs->setProperty(engine,ANDROID_TTS_ENGINE_PROPERTY_CONFIG,
Jean-Michel Trivi900e0d02010-03-18 11:07:45 -0700540 engineConfigNativeString, strlen(engineConfigNativeString));
541 }
542 env->ReleaseStringUTFChars(engineConfig, engineConfigNativeString);
543
544 return result;
545}
Jean-Michel Trivibee1c7e2009-06-29 15:55:05 -0700546
Charles Chen35b86c22009-07-06 10:51:48 -0700547static int
Jean-Michel Trivi700ec652009-05-27 15:01:59 -0700548android_tts_SynthProxy_setLanguage(JNIEnv *env, jobject thiz, jint jniData,
Jean-Michel Trivi679d7282009-06-16 15:36:28 -0700549 jstring language, jstring country, jstring variant)
Jean-Michel Trivi700ec652009-05-27 15:01:59 -0700550{
David 'Digit' Turner01f2f962010-05-20 16:57:34 -0700551 int result = ANDROID_TTS_LANG_NOT_SUPPORTED;
Charles Chen35b86c22009-07-06 10:51:48 -0700552
Jean-Michel Trivi700ec652009-05-27 15:01:59 -0700553 if (jniData == 0) {
554 LOGE("android_tts_SynthProxy_setLanguage(): invalid JNI data");
Charles Chen35b86c22009-07-06 10:51:48 -0700555 return result;
Jean-Michel Trivi700ec652009-05-27 15:01:59 -0700556 }
557
Jean-Michel Trivi5e11a6a2009-07-20 14:05:33 -0700558 Mutex::Autolock l(engineMutex);
559
Jean-Michel Trivi700ec652009-05-27 15:01:59 -0700560 SynthProxyJniStorage* pSynthData = (SynthProxyJniStorage*)jniData;
561 const char *langNativeString = env->GetStringUTFChars(language, 0);
Jean-Michel Trivi679d7282009-06-16 15:36:28 -0700562 const char *countryNativeString = env->GetStringUTFChars(country, 0);
563 const char *variantNativeString = env->GetStringUTFChars(variant, 0);
David 'Digit' Turner01f2f962010-05-20 16:57:34 -0700564 android_tts_engine_t *engine = pSynthData->mEngine;
Charles Chen35b86c22009-07-06 10:51:48 -0700565
David 'Digit' Turner01f2f962010-05-20 16:57:34 -0700566 if (engine) {
567 result = engine->funcs->setLanguage(engine, langNativeString,
Charles Chen35b86c22009-07-06 10:51:48 -0700568 countryNativeString, variantNativeString);
Jean-Michel Trivi700ec652009-05-27 15:01:59 -0700569 }
570 env->ReleaseStringUTFChars(language, langNativeString);
Jean-Michel Trivid6d03e02009-06-25 18:37:55 -0700571 env->ReleaseStringUTFChars(country, countryNativeString);
572 env->ReleaseStringUTFChars(variant, variantNativeString);
Charles Chen35b86c22009-07-06 10:51:48 -0700573 return result;
Jean-Michel Trivid6d03e02009-06-25 18:37:55 -0700574}
575
576
Charles Chen35b86c22009-07-06 10:51:48 -0700577static int
Jean-Michel Trivid6d03e02009-06-25 18:37:55 -0700578android_tts_SynthProxy_loadLanguage(JNIEnv *env, jobject thiz, jint jniData,
579 jstring language, jstring country, jstring variant)
580{
David 'Digit' Turner01f2f962010-05-20 16:57:34 -0700581 int result = ANDROID_TTS_LANG_NOT_SUPPORTED;
Charles Chen35b86c22009-07-06 10:51:48 -0700582
Jean-Michel Trivid6d03e02009-06-25 18:37:55 -0700583 if (jniData == 0) {
584 LOGE("android_tts_SynthProxy_loadLanguage(): invalid JNI data");
Charles Chen35b86c22009-07-06 10:51:48 -0700585 return result;
Jean-Michel Trivid6d03e02009-06-25 18:37:55 -0700586 }
587
588 SynthProxyJniStorage* pSynthData = (SynthProxyJniStorage*)jniData;
589 const char *langNativeString = env->GetStringUTFChars(language, 0);
590 const char *countryNativeString = env->GetStringUTFChars(country, 0);
591 const char *variantNativeString = env->GetStringUTFChars(variant, 0);
David 'Digit' Turner01f2f962010-05-20 16:57:34 -0700592 android_tts_engine_t *engine = pSynthData->mEngine;
Charles Chen35b86c22009-07-06 10:51:48 -0700593
David 'Digit' Turner01f2f962010-05-20 16:57:34 -0700594 if (engine) {
595 result = engine->funcs->loadLanguage(engine, langNativeString,
Charles Chen35b86c22009-07-06 10:51:48 -0700596 countryNativeString, variantNativeString);
Jean-Michel Trivid6d03e02009-06-25 18:37:55 -0700597 }
598 env->ReleaseStringUTFChars(language, langNativeString);
599 env->ReleaseStringUTFChars(country, countryNativeString);
600 env->ReleaseStringUTFChars(variant, variantNativeString);
Charles Chen35b86c22009-07-06 10:51:48 -0700601
602 return result;
Jean-Michel Trivi700ec652009-05-27 15:01:59 -0700603}
604
605
Charles Chen35b86c22009-07-06 10:51:48 -0700606static int
Jean-Michel Trivi700ec652009-05-27 15:01:59 -0700607android_tts_SynthProxy_setSpeechRate(JNIEnv *env, jobject thiz, jint jniData,
Jean-Michel Trivi2ea53492009-06-23 13:44:40 -0700608 jint speechRate)
Jean-Michel Trivi700ec652009-05-27 15:01:59 -0700609{
David 'Digit' Turner01f2f962010-05-20 16:57:34 -0700610 int result = ANDROID_TTS_FAILURE;
Charles Chen35b86c22009-07-06 10:51:48 -0700611
Jean-Michel Trivi700ec652009-05-27 15:01:59 -0700612 if (jniData == 0) {
613 LOGE("android_tts_SynthProxy_setSpeechRate(): invalid JNI data");
Charles Chen35b86c22009-07-06 10:51:48 -0700614 return result;
Jean-Michel Trivi700ec652009-05-27 15:01:59 -0700615 }
616
Kenny Root1ead4f02010-02-18 10:35:17 -0800617 int bufSize = 12;
Jean-Michel Trivi700ec652009-05-27 15:01:59 -0700618 char buffer [bufSize];
619 sprintf(buffer, "%d", speechRate);
620
Jean-Michel Trivi5e11a6a2009-07-20 14:05:33 -0700621 Mutex::Autolock l(engineMutex);
622
Jean-Michel Trivi700ec652009-05-27 15:01:59 -0700623 SynthProxyJniStorage* pSynthData = (SynthProxyJniStorage*)jniData;
Jean-Michel Trivi9d2d26a2011-01-05 16:08:21 -0800624 //LOGI("setting speech rate to %d", speechRate);
David 'Digit' Turner01f2f962010-05-20 16:57:34 -0700625 android_tts_engine_t *engine = pSynthData->mEngine;
Charles Chen35b86c22009-07-06 10:51:48 -0700626
David 'Digit' Turner01f2f962010-05-20 16:57:34 -0700627 if (engine) {
628 result = engine->funcs->setProperty(engine, "rate", buffer, bufSize);
Jean-Michel Trivi700ec652009-05-27 15:01:59 -0700629 }
Charles Chen35b86c22009-07-06 10:51:48 -0700630
631 return result;
Jean-Michel Trivi700ec652009-05-27 15:01:59 -0700632}
633
634
Charles Chen35b86c22009-07-06 10:51:48 -0700635static int
Jean-Michel Trivi2ea53492009-06-23 13:44:40 -0700636android_tts_SynthProxy_setPitch(JNIEnv *env, jobject thiz, jint jniData,
637 jint pitch)
638{
David 'Digit' Turner01f2f962010-05-20 16:57:34 -0700639 int result = ANDROID_TTS_FAILURE;
Charles Chen35b86c22009-07-06 10:51:48 -0700640
Jean-Michel Trivi2ea53492009-06-23 13:44:40 -0700641 if (jniData == 0) {
642 LOGE("android_tts_SynthProxy_setPitch(): invalid JNI data");
Charles Chen35b86c22009-07-06 10:51:48 -0700643 return result;
Jean-Michel Trivi2ea53492009-06-23 13:44:40 -0700644 }
645
Jean-Michel Trivi5e11a6a2009-07-20 14:05:33 -0700646 Mutex::Autolock l(engineMutex);
647
Kenny Root1ead4f02010-02-18 10:35:17 -0800648 int bufSize = 12;
Jean-Michel Trivi2ea53492009-06-23 13:44:40 -0700649 char buffer [bufSize];
650 sprintf(buffer, "%d", pitch);
651
652 SynthProxyJniStorage* pSynthData = (SynthProxyJniStorage*)jniData;
Jean-Michel Trivi9d2d26a2011-01-05 16:08:21 -0800653 //LOGI("setting pitch to %d", pitch);
David 'Digit' Turner01f2f962010-05-20 16:57:34 -0700654 android_tts_engine_t *engine = pSynthData->mEngine;
Charles Chen35b86c22009-07-06 10:51:48 -0700655
David 'Digit' Turner01f2f962010-05-20 16:57:34 -0700656 if (engine) {
657 result = engine->funcs->setProperty(engine, "pitch", buffer, bufSize);
Jean-Michel Trivi2ea53492009-06-23 13:44:40 -0700658 }
Charles Chen35b86c22009-07-06 10:51:48 -0700659
660 return result;
Jean-Michel Trivi2ea53492009-06-23 13:44:40 -0700661}
662
663
Charles Chen35b86c22009-07-06 10:51:48 -0700664static int
Jean-Michel Trivi700ec652009-05-27 15:01:59 -0700665android_tts_SynthProxy_synthesizeToFile(JNIEnv *env, jobject thiz, jint jniData,
666 jstring textJavaString, jstring filenameJavaString)
667{
David 'Digit' Turner01f2f962010-05-20 16:57:34 -0700668 int result = ANDROID_TTS_FAILURE;
Charles Chen35b86c22009-07-06 10:51:48 -0700669
Jean-Michel Trivi700ec652009-05-27 15:01:59 -0700670 if (jniData == 0) {
671 LOGE("android_tts_SynthProxy_synthesizeToFile(): invalid JNI data");
Charles Chen35b86c22009-07-06 10:51:48 -0700672 return result;
Jean-Michel Trivi700ec652009-05-27 15:01:59 -0700673 }
674
675 SynthProxyJniStorage* pSynthData = (SynthProxyJniStorage*)jniData;
David 'Digit' Turner01f2f962010-05-20 16:57:34 -0700676 if (!pSynthData->mEngine) {
Jean-Michel Trivif07d8242009-06-30 09:36:08 -0700677 LOGE("android_tts_SynthProxy_synthesizeToFile(): invalid engine handle");
Charles Chen35b86c22009-07-06 10:51:48 -0700678 return result;
Jean-Michel Trivif07d8242009-06-30 09:36:08 -0700679 }
680
Jean-Michel Trivi4fb7d882009-08-13 19:05:45 -0700681 initializeFilter();
682
Jean-Michel Trivi5e11a6a2009-07-20 14:05:33 -0700683 Mutex::Autolock l(engineMutex);
684
Jean-Michel Trivif07d8242009-06-30 09:36:08 -0700685 // Retrieve audio parameters before writing the file header
David 'Digit' Turner01f2f962010-05-20 16:57:34 -0700686 AudioSystem::audio_format encoding;
Jean-Michel Trivif07d8242009-06-30 09:36:08 -0700687 uint32_t rate = DEFAULT_TTS_RATE;
688 int channels = DEFAULT_TTS_NB_CHANNELS;
David 'Digit' Turner01f2f962010-05-20 16:57:34 -0700689 android_tts_engine_t *engine = pSynthData->mEngine;
690 android_tts_audio_format_t format = ANDROID_TTS_AUDIO_FORMAT_DEFAULT;
Jean-Michel Trivif07d8242009-06-30 09:36:08 -0700691
David 'Digit' Turner01f2f962010-05-20 16:57:34 -0700692 engine->funcs->setAudioFormat(engine, &format, &rate, &channels);
693
694 switch (format) {
695 case ANDROID_TTS_AUDIO_FORMAT_PCM_16_BIT:
696 encoding = AudioSystem::PCM_16_BIT;
697 break;
698 case ANDROID_TTS_AUDIO_FORMAT_PCM_8_BIT:
699 encoding = AudioSystem::PCM_8_BIT;
700 break;
701 default:
Jean-Michel Trivif07d8242009-06-30 09:36:08 -0700702 LOGE("android_tts_SynthProxy_synthesizeToFile(): engine uses invalid format");
Charles Chen35b86c22009-07-06 10:51:48 -0700703 return result;
Jean-Michel Trivif07d8242009-06-30 09:36:08 -0700704 }
Jean-Michel Trivi700ec652009-05-27 15:01:59 -0700705
706 const char *filenameNativeString =
707 env->GetStringUTFChars(filenameJavaString, 0);
708 const char *textNativeString = env->GetStringUTFChars(textJavaString, 0);
709
710 afterSynthData_t* pForAfter = new (afterSynthData_t);
711 pForAfter->jniStorage = jniData;
712 pForAfter->usageMode = USAGEMODE_WRITE_TO_FILE;
713
714 pForAfter->outputFile = fopen(filenameNativeString, "wb");
715
Jean-Michel Trivif07d8242009-06-30 09:36:08 -0700716 if (pForAfter->outputFile == NULL) {
717 LOGE("android_tts_SynthProxy_synthesizeToFile(): error creating output file");
718 delete pForAfter;
Charles Chen35b86c22009-07-06 10:51:48 -0700719 return result;
Jean-Michel Trivif07d8242009-06-30 09:36:08 -0700720 }
721
Jean-Michel Trivi700ec652009-05-27 15:01:59 -0700722 // Write 44 blank bytes for WAV header, then come back and fill them in
723 // after we've written the audio data
724 char header[44];
725 fwrite(header, 1, 44, pForAfter->outputFile);
726
727 unsigned int unique_identifier;
728
Charles Chen4a3368f2009-07-14 17:11:44 -0700729 memset(pSynthData->mBuffer, 0, pSynthData->mBufferSize);
David 'Digit' Turner01f2f962010-05-20 16:57:34 -0700730
731 result = engine->funcs->synthesizeText(engine, textNativeString,
Charles Chen35b86c22009-07-06 10:51:48 -0700732 pSynthData->mBuffer, pSynthData->mBufferSize, (void *)pForAfter);
Jean-Michel Trivi700ec652009-05-27 15:01:59 -0700733
734 long filelen = ftell(pForAfter->outputFile);
735
736 int samples = (((int)filelen) - 44) / 2;
737 header[0] = 'R';
738 header[1] = 'I';
739 header[2] = 'F';
740 header[3] = 'F';
741 ((uint32_t *)(&header[4]))[0] = filelen - 8;
742 header[8] = 'W';
743 header[9] = 'A';
744 header[10] = 'V';
745 header[11] = 'E';
746
747 header[12] = 'f';
748 header[13] = 'm';
749 header[14] = 't';
750 header[15] = ' ';
751
752 ((uint32_t *)(&header[16]))[0] = 16; // size of fmt
753
Jean-Michel Trivif07d8242009-06-30 09:36:08 -0700754 int sampleSizeInByte = (encoding == AudioSystem::PCM_16_BIT ? 2 : 1);
755
Jean-Michel Trivi700ec652009-05-27 15:01:59 -0700756 ((unsigned short *)(&header[20]))[0] = 1; // format
Jean-Michel Trivif07d8242009-06-30 09:36:08 -0700757 ((unsigned short *)(&header[22]))[0] = channels; // channels
758 ((uint32_t *)(&header[24]))[0] = rate; // samplerate
759 ((uint32_t *)(&header[28]))[0] = rate * sampleSizeInByte * channels;// byterate
760 ((unsigned short *)(&header[32]))[0] = sampleSizeInByte * channels; // block align
761 ((unsigned short *)(&header[34]))[0] = sampleSizeInByte * 8; // bits per sample
Jean-Michel Trivi700ec652009-05-27 15:01:59 -0700762
763 header[36] = 'd';
764 header[37] = 'a';
765 header[38] = 't';
766 header[39] = 'a';
767
768 ((uint32_t *)(&header[40]))[0] = samples * 2; // size of data
769
770 // Skip back to the beginning and rewrite the header
771 fseek(pForAfter->outputFile, 0, SEEK_SET);
772 fwrite(header, 1, 44, pForAfter->outputFile);
773
774 fflush(pForAfter->outputFile);
775 fclose(pForAfter->outputFile);
776
Jean-Michel Trivif07d8242009-06-30 09:36:08 -0700777 delete pForAfter;
778 pForAfter = NULL;
779
Jean-Michel Trivi700ec652009-05-27 15:01:59 -0700780 env->ReleaseStringUTFChars(textJavaString, textNativeString);
781 env->ReleaseStringUTFChars(filenameJavaString, filenameNativeString);
Charles Chen35b86c22009-07-06 10:51:48 -0700782
783 return result;
Jean-Michel Trivi700ec652009-05-27 15:01:59 -0700784}
785
786
Charles Chen35b86c22009-07-06 10:51:48 -0700787static int
Jean-Michel Trivi700ec652009-05-27 15:01:59 -0700788android_tts_SynthProxy_speak(JNIEnv *env, jobject thiz, jint jniData,
Jean-Michel Trivi9d2d26a2011-01-05 16:08:21 -0800789 jstring textJavaString, jint javaStreamType, jfloat volume, jfloat pan)
Jean-Michel Trivi700ec652009-05-27 15:01:59 -0700790{
David 'Digit' Turner01f2f962010-05-20 16:57:34 -0700791 int result = ANDROID_TTS_FAILURE;
Charles Chen35b86c22009-07-06 10:51:48 -0700792
Jean-Michel Trivi700ec652009-05-27 15:01:59 -0700793 if (jniData == 0) {
794 LOGE("android_tts_SynthProxy_speak(): invalid JNI data");
Charles Chen35b86c22009-07-06 10:51:48 -0700795 return result;
Jean-Michel Trivi700ec652009-05-27 15:01:59 -0700796 }
797
Jean-Michel Trivi4fb7d882009-08-13 19:05:45 -0700798 initializeFilter();
799
Jean-Michel Trivi5e11a6a2009-07-20 14:05:33 -0700800 Mutex::Autolock l(engineMutex);
801
Jean-Michel Trivi700ec652009-05-27 15:01:59 -0700802 SynthProxyJniStorage* pSynthData = (SynthProxyJniStorage*)jniData;
803
Jean-Michel Trivi9d2d26a2011-01-05 16:08:21 -0800804 {//scope for lock on mPlayLock
805 Mutex::Autolock _l(pSynthData->mPlayLock);
806
807 pSynthData->mPlayState = SYNTHPLAYSTATE_IS_PLAYING;
808
809 // clip volume and pan
810 float vol = (volume > 1.0f) ? 1.0f : (volume < 0.0f) ? 0.0f : volume;
811 float panning = (pan > 1.0f) ? 1.0f : (pan < -1.0f) ? -1.0f : pan;
812 // compute playback volume based on volume and pan, using balance rule, in order to avoid
813 // lowering volume when panning in center
814 pSynthData->mVolume[AudioTrack::LEFT] = vol;
815 pSynthData->mVolume[AudioTrack::RIGHT] = vol;
816 if (panning > 0.0f) {
817 pSynthData->mVolume[AudioTrack::LEFT] *= (1.0f - panning);
818 } else if (panning < 0.0f) {
819 pSynthData->mVolume[AudioTrack::RIGHT] *= (1.0f + panning);
820 }
821
822 // apply the volume if there is an output
823 if (NULL != pSynthData->mAudioOut) {
824 pSynthData->mAudioOut->setVolume(pSynthData->mVolume[AudioTrack::LEFT],
825 pSynthData->mVolume[AudioTrack::RIGHT]);
826 }
827
828 //LOGV("android_tts_SynthProxy_speak() vol=%.3f pan=%.3f, mVolume=[%.1f %.1f]",
829 // volume, pan,
830 // pSynthData->mVolume[AudioTrack::LEFT], pSynthData->mVolume[AudioTrack::RIGHT]);
831 }
Jean-Michel Trivi700ec652009-05-27 15:01:59 -0700832
833 afterSynthData_t* pForAfter = new (afterSynthData_t);
834 pForAfter->jniStorage = jniData;
835 pForAfter->usageMode = USAGEMODE_PLAY_IMMEDIATELY;
Jean-Michel Trivi9440bce2009-07-13 10:12:37 -0700836 pForAfter->streamType = (AudioSystem::stream_type) javaStreamType;
Jean-Michel Trivi700ec652009-05-27 15:01:59 -0700837
David 'Digit' Turner01f2f962010-05-20 16:57:34 -0700838 if (pSynthData->mEngine) {
Jean-Michel Trivi700ec652009-05-27 15:01:59 -0700839 const char *textNativeString = env->GetStringUTFChars(textJavaString, 0);
Charles Chen4a3368f2009-07-14 17:11:44 -0700840 memset(pSynthData->mBuffer, 0, pSynthData->mBufferSize);
David 'Digit' Turner01f2f962010-05-20 16:57:34 -0700841 android_tts_engine_t *engine = pSynthData->mEngine;
842
843 result = engine->funcs->synthesizeText(engine, textNativeString,
Charles Chen35b86c22009-07-06 10:51:48 -0700844 pSynthData->mBuffer, pSynthData->mBufferSize, (void *)pForAfter);
Jean-Michel Trivi700ec652009-05-27 15:01:59 -0700845 env->ReleaseStringUTFChars(textJavaString, textNativeString);
846 }
Charles Chen35b86c22009-07-06 10:51:48 -0700847
848 return result;
Jean-Michel Trivi700ec652009-05-27 15:01:59 -0700849}
850
851
Charles Chen35b86c22009-07-06 10:51:48 -0700852static int
Jean-Michel Trivi700ec652009-05-27 15:01:59 -0700853android_tts_SynthProxy_stop(JNIEnv *env, jobject thiz, jint jniData)
854{
David 'Digit' Turner01f2f962010-05-20 16:57:34 -0700855 int result = ANDROID_TTS_FAILURE;
Charles Chen35b86c22009-07-06 10:51:48 -0700856
Jean-Michel Trivi700ec652009-05-27 15:01:59 -0700857 if (jniData == 0) {
858 LOGE("android_tts_SynthProxy_stop(): invalid JNI data");
Charles Chen35b86c22009-07-06 10:51:48 -0700859 return result;
Jean-Michel Trivi700ec652009-05-27 15:01:59 -0700860 }
861
862 SynthProxyJniStorage* pSynthData = (SynthProxyJniStorage*)jniData;
863
Jean-Michel Trivie3c18902010-02-24 18:52:39 -0800864 pSynthData->mPlayLock.lock();
865 pSynthData->mPlayState = SYNTHPLAYSTATE_IS_STOPPED;
Jean-Michel Trivi700ec652009-05-27 15:01:59 -0700866 if (pSynthData->mAudioOut) {
867 pSynthData->mAudioOut->stop();
868 }
Jean-Michel Trivie3c18902010-02-24 18:52:39 -0800869 pSynthData->mPlayLock.unlock();
870
David 'Digit' Turner01f2f962010-05-20 16:57:34 -0700871 android_tts_engine_t *engine = pSynthData->mEngine;
872 if (engine) {
873 result = engine->funcs->stop(engine);
Charles Chen4a3368f2009-07-14 17:11:44 -0700874 }
Charles Chen35b86c22009-07-06 10:51:48 -0700875
876 return result;
Jean-Michel Trivi700ec652009-05-27 15:01:59 -0700877}
878
879
Jean-Michel Trivi09f8db72009-08-31 10:41:55 -0700880static int
881android_tts_SynthProxy_stopSync(JNIEnv *env, jobject thiz, jint jniData)
882{
David 'Digit' Turner01f2f962010-05-20 16:57:34 -0700883 int result = ANDROID_TTS_FAILURE;
Jean-Michel Trivi09f8db72009-08-31 10:41:55 -0700884
885 if (jniData == 0) {
886 LOGE("android_tts_SynthProxy_stop(): invalid JNI data");
887 return result;
888 }
889
890 // perform a regular stop
891 result = android_tts_SynthProxy_stop(env, thiz, jniData);
892 // but wait on the engine having released the engine mutex which protects
893 // the synthesizer resources.
894 engineMutex.lock();
895 engineMutex.unlock();
896
897 return result;
898}
899
900
Jean-Michel Trivibee1c7e2009-06-29 15:55:05 -0700901static jobjectArray
Jean-Michel Trivi700ec652009-05-27 15:01:59 -0700902android_tts_SynthProxy_getLanguage(JNIEnv *env, jobject thiz, jint jniData)
903{
904 if (jniData == 0) {
905 LOGE("android_tts_SynthProxy_getLanguage(): invalid JNI data");
Jean-Michel Trivibee1c7e2009-06-29 15:55:05 -0700906 return NULL;
Jean-Michel Trivi700ec652009-05-27 15:01:59 -0700907 }
908
909 SynthProxyJniStorage* pSynthData = (SynthProxyJniStorage*)jniData;
Jean-Michel Trivibee1c7e2009-06-29 15:55:05 -0700910
David 'Digit' Turner01f2f962010-05-20 16:57:34 -0700911 if (pSynthData->mEngine) {
Jean-Michel Trivibee1c7e2009-06-29 15:55:05 -0700912 size_t bufSize = 100;
913 char lang[bufSize];
914 char country[bufSize];
915 char variant[bufSize];
916 memset(lang, 0, bufSize);
917 memset(country, 0, bufSize);
918 memset(variant, 0, bufSize);
919 jobjectArray retLocale = (jobjectArray)env->NewObjectArray(3,
920 env->FindClass("java/lang/String"), env->NewStringUTF(""));
David 'Digit' Turner01f2f962010-05-20 16:57:34 -0700921
922 android_tts_engine_t *engine = pSynthData->mEngine;
923 engine->funcs->getLanguage(engine, lang, country, variant);
Jean-Michel Trivibee1c7e2009-06-29 15:55:05 -0700924 env->SetObjectArrayElement(retLocale, 0, env->NewStringUTF(lang));
925 env->SetObjectArrayElement(retLocale, 1, env->NewStringUTF(country));
926 env->SetObjectArrayElement(retLocale, 2, env->NewStringUTF(variant));
927 return retLocale;
928 } else {
929 return NULL;
Jean-Michel Trivi700ec652009-05-27 15:01:59 -0700930 }
Jean-Michel Trivi700ec652009-05-27 15:01:59 -0700931}
932
Jean-Michel Trivi679d7282009-06-16 15:36:28 -0700933
Jean-Michel Trivi700ec652009-05-27 15:01:59 -0700934JNIEXPORT int JNICALL
935android_tts_SynthProxy_getRate(JNIEnv *env, jobject thiz, jint jniData)
936{
937 if (jniData == 0) {
938 LOGE("android_tts_SynthProxy_getRate(): invalid JNI data");
939 return 0;
940 }
941
942 SynthProxyJniStorage* pSynthData = (SynthProxyJniStorage*)jniData;
943 size_t bufSize = 100;
944
945 char buf[bufSize];
946 memset(buf, 0, bufSize);
947 // TODO check return codes
David 'Digit' Turner01f2f962010-05-20 16:57:34 -0700948 android_tts_engine_t *engine = pSynthData->mEngine;
949 if (engine) {
950 engine->funcs->getProperty(engine,"rate", buf, &bufSize);
Jean-Michel Trivi700ec652009-05-27 15:01:59 -0700951 }
952 return atoi(buf);
953}
954
955// Dalvik VM type signatures
956static JNINativeMethod gMethods[] = {
957 { "native_stop",
Charles Chen35b86c22009-07-06 10:51:48 -0700958 "(I)I",
Jean-Michel Trivi700ec652009-05-27 15:01:59 -0700959 (void*)android_tts_SynthProxy_stop
960 },
Jean-Michel Trivi09f8db72009-08-31 10:41:55 -0700961 { "native_stopSync",
962 "(I)I",
963 (void*)android_tts_SynthProxy_stopSync
964 },
Jean-Michel Trivi700ec652009-05-27 15:01:59 -0700965 { "native_speak",
Jean-Michel Trivi9d2d26a2011-01-05 16:08:21 -0800966 "(ILjava/lang/String;IFF)I",
Jean-Michel Trivi700ec652009-05-27 15:01:59 -0700967 (void*)android_tts_SynthProxy_speak
968 },
969 { "native_synthesizeToFile",
Charles Chen35b86c22009-07-06 10:51:48 -0700970 "(ILjava/lang/String;Ljava/lang/String;)I",
Jean-Michel Trivi700ec652009-05-27 15:01:59 -0700971 (void*)android_tts_SynthProxy_synthesizeToFile
972 },
Jean-Michel Trivibee1c7e2009-06-29 15:55:05 -0700973 { "native_isLanguageAvailable",
974 "(ILjava/lang/String;Ljava/lang/String;Ljava/lang/String;)I",
975 (void*)android_tts_SynthProxy_isLanguageAvailable
976 },
Jean-Michel Trivi900e0d02010-03-18 11:07:45 -0700977 { "native_setConfig",
978 "(ILjava/lang/String;)I",
979 (void*)android_tts_SynthProxy_setConfig
980 },
Jean-Michel Trivi700ec652009-05-27 15:01:59 -0700981 { "native_setLanguage",
Charles Chen35b86c22009-07-06 10:51:48 -0700982 "(ILjava/lang/String;Ljava/lang/String;Ljava/lang/String;)I",
Jean-Michel Trivi700ec652009-05-27 15:01:59 -0700983 (void*)android_tts_SynthProxy_setLanguage
984 },
Jean-Michel Trivid6d03e02009-06-25 18:37:55 -0700985 { "native_loadLanguage",
Charles Chen35b86c22009-07-06 10:51:48 -0700986 "(ILjava/lang/String;Ljava/lang/String;Ljava/lang/String;)I",
Jean-Michel Trivid6d03e02009-06-25 18:37:55 -0700987 (void*)android_tts_SynthProxy_loadLanguage
988 },
Jean-Michel Trivi700ec652009-05-27 15:01:59 -0700989 { "native_setSpeechRate",
Charles Chen35b86c22009-07-06 10:51:48 -0700990 "(II)I",
Jean-Michel Trivi700ec652009-05-27 15:01:59 -0700991 (void*)android_tts_SynthProxy_setSpeechRate
992 },
Jean-Michel Trivi2ea53492009-06-23 13:44:40 -0700993 { "native_setPitch",
Charles Chen35b86c22009-07-06 10:51:48 -0700994 "(II)I",
Jean-Michel Trivi2ea53492009-06-23 13:44:40 -0700995 (void*)android_tts_SynthProxy_setPitch
996 },
Jean-Michel Trivi700ec652009-05-27 15:01:59 -0700997 { "native_getLanguage",
Jean-Michel Trivibee1c7e2009-06-29 15:55:05 -0700998 "(I)[Ljava/lang/String;",
Jean-Michel Trivi700ec652009-05-27 15:01:59 -0700999 (void*)android_tts_SynthProxy_getLanguage
1000 },
1001 { "native_getRate",
1002 "(I)I",
1003 (void*)android_tts_SynthProxy_getRate
1004 },
1005 { "native_shutdown",
1006 "(I)V",
1007 (void*)android_tts_SynthProxy_shutdown
1008 },
1009 { "native_setup",
Jean-Michel Trivi900e0d02010-03-18 11:07:45 -07001010 "(Ljava/lang/Object;Ljava/lang/String;Ljava/lang/String;)I",
Jean-Michel Trivi700ec652009-05-27 15:01:59 -07001011 (void*)android_tts_SynthProxy_native_setup
1012 },
Jean-Michel Trivi0320f8b2010-01-11 14:04:50 -08001013 { "native_setLowShelf",
1014 "(ZFFFF)I",
1015 (void*)android_tts_SynthProxy_setLowShelf
1016 },
Jean-Michel Trivi700ec652009-05-27 15:01:59 -07001017 { "native_finalize",
1018 "(I)V",
1019 (void*)android_tts_SynthProxy_native_finalize
1020 }
1021};
1022
1023#define SP_JNIDATA_FIELD_NAME "mJniData"
1024#define SP_POSTSPEECHSYNTHESIZED_METHOD_NAME "postNativeSpeechSynthesizedInJava"
1025
Jean-Michel Trivi700ec652009-05-27 15:01:59 -07001026static const char* const kClassPathName = "android/tts/SynthProxy";
1027
1028jint JNI_OnLoad(JavaVM* vm, void* reserved)
1029{
1030 JNIEnv* env = NULL;
1031 jint result = -1;
1032 jclass clazz;
1033
1034 if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {
1035 LOGE("ERROR: GetEnv failed\n");
1036 goto bail;
1037 }
1038 assert(env != NULL);
1039
1040 clazz = env->FindClass(kClassPathName);
1041 if (clazz == NULL) {
1042 LOGE("Can't find %s", kClassPathName);
1043 goto bail;
1044 }
1045
1046 javaTTSFields.synthProxyClass = clazz;
1047 javaTTSFields.synthProxyFieldJniData = NULL;
1048 javaTTSFields.synthProxyMethodPost = NULL;
1049
1050 javaTTSFields.synthProxyFieldJniData = env->GetFieldID(clazz,
1051 SP_JNIDATA_FIELD_NAME, "I");
1052 if (javaTTSFields.synthProxyFieldJniData == NULL) {
1053 LOGE("Can't find %s.%s field", kClassPathName, SP_JNIDATA_FIELD_NAME);
1054 goto bail;
1055 }
1056
1057 javaTTSFields.synthProxyMethodPost = env->GetStaticMethodID(clazz,
1058 SP_POSTSPEECHSYNTHESIZED_METHOD_NAME, "(Ljava/lang/Object;II)V");
1059 if (javaTTSFields.synthProxyMethodPost == NULL) {
1060 LOGE("Can't find %s.%s method", kClassPathName, SP_POSTSPEECHSYNTHESIZED_METHOD_NAME);
1061 goto bail;
1062 }
1063
1064 if (jniRegisterNativeMethods(
1065 env, kClassPathName, gMethods, NELEM(gMethods)) < 0)
1066 goto bail;
1067
1068 /* success -- return valid version number */
1069 result = JNI_VERSION_1_4;
1070
1071 bail:
1072 return result;
1073}