Merge change 2572 into donut
* changes: Adding java/jni code for the Java TTS SynthProxy class, which relays calls from the TTS service to the native TTS plugin library.
This commit is contained in:
172
tts/java/android/tts/SynthProxy.java
Executable file
172
tts/java/android/tts/SynthProxy.java
Executable file
@@ -0,0 +1,172 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2009 Google Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||||
|
* use this file except in compliance with the License. You may obtain a copy of
|
||||||
|
* the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations under
|
||||||
|
* the License.
|
||||||
|
*/
|
||||||
|
package android.tts;
|
||||||
|
|
||||||
|
import android.util.Log;
|
||||||
|
import java.lang.ref.WeakReference;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @hide
|
||||||
|
*
|
||||||
|
* The SpeechSynthesis class provides a high-level api to create and play
|
||||||
|
* synthesized speech. This class is used internally to talk to a native
|
||||||
|
* TTS library that implements the interface defined in
|
||||||
|
* frameworks/base/include/tts/TtsEngine.h
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
public class SynthProxy {
|
||||||
|
|
||||||
|
//
|
||||||
|
// External API
|
||||||
|
//
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor; pass the location of the native TTS .so to use.
|
||||||
|
*/
|
||||||
|
public SynthProxy(String nativeSoLib) {
|
||||||
|
Log.e("TTS is loading", nativeSoLib);
|
||||||
|
native_setup(new WeakReference<SynthProxy>(this), nativeSoLib);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stops and clears the AudioTrack.
|
||||||
|
*/
|
||||||
|
public void stop() {
|
||||||
|
native_stop(mJniData);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Synthesize speech and speak it directly using AudioTrack.
|
||||||
|
*/
|
||||||
|
public void speak(String text) {
|
||||||
|
native_speak(mJniData, text);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Synthesize speech to a file. The current implementation writes a valid
|
||||||
|
* WAV file to the given path, assuming it is writable. Something like
|
||||||
|
* "/sdcard/???.wav" is recommended.
|
||||||
|
*/
|
||||||
|
public void synthesizeToFile(String text, String filename) {
|
||||||
|
native_synthesizeToFile(mJniData, text, filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO add IPA methods
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the language
|
||||||
|
*/
|
||||||
|
public void setLanguage(String language) {
|
||||||
|
native_setLanguage(mJniData, language);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the speech rate
|
||||||
|
*/
|
||||||
|
public final void setSpeechRate(int speechRate) {
|
||||||
|
native_setSpeechRate(mJniData, speechRate);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Plays the given audio buffer
|
||||||
|
*/
|
||||||
|
public void playAudioBuffer(int bufferPointer, int bufferSize) {
|
||||||
|
native_playAudioBuffer(mJniData, bufferPointer, bufferSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the currently set language
|
||||||
|
*/
|
||||||
|
public String getLanguage() {
|
||||||
|
return native_getLanguage(mJniData);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the currently set rate
|
||||||
|
*/
|
||||||
|
public int getRate() {
|
||||||
|
return native_getRate(mJniData);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shuts down the native synthesizer
|
||||||
|
*/
|
||||||
|
public void shutdown() {
|
||||||
|
native_shutdown(mJniData);
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Internal
|
||||||
|
//
|
||||||
|
|
||||||
|
protected void finalize() {
|
||||||
|
native_finalize(mJniData);
|
||||||
|
mJniData = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static {
|
||||||
|
System.loadLibrary("synthproxy");
|
||||||
|
}
|
||||||
|
|
||||||
|
private final static String TAG = "SynthProxy";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Accessed by native methods
|
||||||
|
*/
|
||||||
|
private int mJniData = 0;
|
||||||
|
|
||||||
|
private native final void native_setup(Object weak_this,
|
||||||
|
String nativeSoLib);
|
||||||
|
|
||||||
|
private native final void native_finalize(int jniData);
|
||||||
|
|
||||||
|
private native final void native_stop(int jniData);
|
||||||
|
|
||||||
|
private native final void native_speak(int jniData, String text);
|
||||||
|
|
||||||
|
private native final void native_synthesizeToFile(int jniData, String text, String filename);
|
||||||
|
|
||||||
|
private native final void native_setLanguage(int jniData, String language);
|
||||||
|
|
||||||
|
private native final void native_setSpeechRate(int jniData, int speechRate);
|
||||||
|
|
||||||
|
// TODO add buffer format
|
||||||
|
private native final void native_playAudioBuffer(int jniData, int bufferPointer, int bufferSize);
|
||||||
|
|
||||||
|
private native final String native_getLanguage(int jniData);
|
||||||
|
|
||||||
|
private native final int native_getRate(int jniData);
|
||||||
|
|
||||||
|
private native final void native_shutdown(int jniData);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback from the C layer
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
private static void postNativeSpeechSynthesizedInJava(Object tts_ref,
|
||||||
|
int bufferPointer, int bufferSize) {
|
||||||
|
|
||||||
|
Log.i("TTS plugin debug", "bufferPointer: " + bufferPointer
|
||||||
|
+ " bufferSize: " + bufferSize);
|
||||||
|
|
||||||
|
SynthProxy nativeTTS = (SynthProxy)((WeakReference)tts_ref).get();
|
||||||
|
// TODO notify TTS service of synthesis/playback completion,
|
||||||
|
// method definition to be changed.
|
||||||
|
}
|
||||||
|
}
|
||||||
26
tts/jni/Android.mk
Executable file
26
tts/jni/Android.mk
Executable file
@@ -0,0 +1,26 @@
|
|||||||
|
LOCAL_PATH:= $(call my-dir)
|
||||||
|
include $(CLEAR_VARS)
|
||||||
|
|
||||||
|
LOCAL_SRC_FILES:= \
|
||||||
|
android_tts_SynthProxy.cpp
|
||||||
|
|
||||||
|
LOCAL_C_INCLUDES += \
|
||||||
|
$(JNI_H_INCLUDE)
|
||||||
|
|
||||||
|
LOCAL_SHARED_LIBRARIES := \
|
||||||
|
libandroid_runtime \
|
||||||
|
libnativehelper \
|
||||||
|
libmedia \
|
||||||
|
libutils \
|
||||||
|
libcutils \
|
||||||
|
libdl
|
||||||
|
|
||||||
|
|
||||||
|
LOCAL_MODULE:= libttssynthproxy
|
||||||
|
|
||||||
|
LOCAL_ARM_MODE := arm
|
||||||
|
|
||||||
|
LOCAL_PRELINK_MODULE := false
|
||||||
|
|
||||||
|
include $(BUILD_SHARED_LIBRARY)
|
||||||
|
|
||||||
595
tts/jni/android_tts_SynthProxy.cpp
Executable file
595
tts/jni/android_tts_SynthProxy.cpp
Executable file
@@ -0,0 +1,595 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2009 Google Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#define LOG_TAG "SynthProxy"
|
||||||
|
|
||||||
|
#include <utils/Log.h>
|
||||||
|
#include <nativehelper/jni.h>
|
||||||
|
#include <nativehelper/JNIHelp.h>
|
||||||
|
#include <android_runtime/AndroidRuntime.h>
|
||||||
|
#include <tts/TtsEngine.h>
|
||||||
|
#include <media/AudioTrack.h>
|
||||||
|
|
||||||
|
#include <dlfcn.h>
|
||||||
|
|
||||||
|
#define DEFAULT_TTS_RATE 16000
|
||||||
|
#define DEFAULT_TTS_FORMAT AudioSystem::PCM_16_BIT
|
||||||
|
#define DEFAULT_TTS_NB_CHANNELS 1
|
||||||
|
|
||||||
|
#define USAGEMODE_PLAY_IMMEDIATELY 0
|
||||||
|
#define USAGEMODE_WRITE_TO_FILE 1
|
||||||
|
|
||||||
|
using namespace android;
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
struct fields_t {
|
||||||
|
jfieldID synthProxyFieldJniData;
|
||||||
|
jclass synthProxyClass;
|
||||||
|
jmethodID synthProxyMethodPost;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct afterSynthData_t {
|
||||||
|
jint jniStorage;
|
||||||
|
int usageMode;
|
||||||
|
FILE* outputFile;
|
||||||
|
};
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
static fields_t javaTTSFields;
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
class SynthProxyJniStorage {
|
||||||
|
public :
|
||||||
|
//jclass tts_class;
|
||||||
|
jobject tts_ref;
|
||||||
|
TtsEngine* mNativeSynthInterface;
|
||||||
|
AudioTrack* mAudioOut;
|
||||||
|
uint32_t mSampleRate;
|
||||||
|
AudioSystem::audio_format mAudFormat;
|
||||||
|
int mNbChannels;
|
||||||
|
|
||||||
|
SynthProxyJniStorage() {
|
||||||
|
//tts_class = NULL;
|
||||||
|
tts_ref = NULL;
|
||||||
|
mNativeSynthInterface = NULL;
|
||||||
|
mAudioOut = NULL;
|
||||||
|
mSampleRate = DEFAULT_TTS_RATE;
|
||||||
|
mAudFormat = DEFAULT_TTS_FORMAT;
|
||||||
|
mNbChannels = DEFAULT_TTS_NB_CHANNELS;
|
||||||
|
}
|
||||||
|
|
||||||
|
~SynthProxyJniStorage() {
|
||||||
|
killAudio();
|
||||||
|
if (mNativeSynthInterface) {
|
||||||
|
mNativeSynthInterface->shutdown();
|
||||||
|
mNativeSynthInterface = NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void killAudio() {
|
||||||
|
if (mAudioOut) {
|
||||||
|
mAudioOut->stop();
|
||||||
|
delete mAudioOut;
|
||||||
|
mAudioOut = NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void createAudioOut(uint32_t rate, AudioSystem::audio_format format,
|
||||||
|
int channel) {
|
||||||
|
mSampleRate = rate;
|
||||||
|
mAudFormat = format;
|
||||||
|
mNbChannels = channel;
|
||||||
|
|
||||||
|
// TODO use the TTS stream type
|
||||||
|
int streamType = AudioSystem::MUSIC;
|
||||||
|
|
||||||
|
// retrieve system properties to ensure successful creation of the
|
||||||
|
// AudioTrack object for playback
|
||||||
|
int afSampleRate;
|
||||||
|
if (AudioSystem::getOutputSamplingRate(&afSampleRate, streamType) != NO_ERROR) {
|
||||||
|
afSampleRate = 44100;
|
||||||
|
}
|
||||||
|
int afFrameCount;
|
||||||
|
if (AudioSystem::getOutputFrameCount(&afFrameCount, streamType) != NO_ERROR) {
|
||||||
|
afFrameCount = 2048;
|
||||||
|
}
|
||||||
|
uint32_t afLatency;
|
||||||
|
if (AudioSystem::getOutputLatency(&afLatency, streamType) != NO_ERROR) {
|
||||||
|
afLatency = 500;
|
||||||
|
}
|
||||||
|
uint32_t minBufCount = afLatency / ((1000 * afFrameCount)/afSampleRate);
|
||||||
|
if (minBufCount < 2) minBufCount = 2;
|
||||||
|
int minFrameCount = (afFrameCount * rate * minBufCount)/afSampleRate;
|
||||||
|
|
||||||
|
mAudioOut = new AudioTrack(streamType, rate, format, channel,
|
||||||
|
minFrameCount > 4096 ? minFrameCount : 4096,
|
||||||
|
0, 0, 0, 0); // not using an AudioTrack callback
|
||||||
|
|
||||||
|
if (mAudioOut->initCheck() != NO_ERROR) {
|
||||||
|
LOGI("AudioTrack error");
|
||||||
|
delete mAudioOut;
|
||||||
|
mAudioOut = NULL;
|
||||||
|
} else {
|
||||||
|
LOGI("AudioTrack OK");
|
||||||
|
mAudioOut->start();
|
||||||
|
LOGI("AudioTrack started");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
void prepAudioTrack(SynthProxyJniStorage* pJniData,
|
||||||
|
uint32_t rate, AudioSystem::audio_format format, int channel)
|
||||||
|
{
|
||||||
|
// Don't bother creating a new audiotrack object if the current
|
||||||
|
// object is already set.
|
||||||
|
if ( pJniData->mAudioOut &&
|
||||||
|
(rate == pJniData->mSampleRate) &&
|
||||||
|
(format == pJniData->mAudFormat) &&
|
||||||
|
(channel == pJniData->mNbChannels) ){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (pJniData->mAudioOut){
|
||||||
|
pJniData->killAudio();
|
||||||
|
}
|
||||||
|
pJniData->createAudioOut(rate, format, channel);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
/*
|
||||||
|
* Callback from TTS engine.
|
||||||
|
* Directly speaks using AudioTrack or write to file
|
||||||
|
*/
|
||||||
|
static void ttsSynthDoneCB(void * userdata, uint32_t rate,
|
||||||
|
AudioSystem::audio_format format, int channel,
|
||||||
|
int8_t *wav, size_t bufferSize) {
|
||||||
|
LOGI("ttsSynthDoneCallback: %d bytes", bufferSize);
|
||||||
|
|
||||||
|
afterSynthData_t* pForAfter = (afterSynthData_t*)userdata;
|
||||||
|
|
||||||
|
if (pForAfter->usageMode == USAGEMODE_PLAY_IMMEDIATELY){
|
||||||
|
LOGI("Direct speech");
|
||||||
|
|
||||||
|
if (wav == NULL) {
|
||||||
|
LOGI("Null: speech has completed");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bufferSize > 0) {
|
||||||
|
SynthProxyJniStorage* pJniData =
|
||||||
|
(SynthProxyJniStorage*)(pForAfter->jniStorage);
|
||||||
|
prepAudioTrack(pJniData, rate, format, channel);
|
||||||
|
if (pJniData->mAudioOut) {
|
||||||
|
pJniData->mAudioOut->write(wav, bufferSize);
|
||||||
|
LOGI("AudioTrack wrote: %d bytes", bufferSize);
|
||||||
|
} else {
|
||||||
|
LOGI("Can't play, null audiotrack");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (pForAfter->usageMode == USAGEMODE_WRITE_TO_FILE) {
|
||||||
|
LOGI("Save to file");
|
||||||
|
if (wav == NULL) {
|
||||||
|
LOGI("Null: speech has completed");
|
||||||
|
}
|
||||||
|
if (bufferSize > 0){
|
||||||
|
fwrite(wav, 1, bufferSize, pForAfter->outputFile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// TODO update to call back into the SynthProxy class through the
|
||||||
|
// javaTTSFields.synthProxyMethodPost methode to notify
|
||||||
|
// playback has completed
|
||||||
|
|
||||||
|
delete pForAfter;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
static void
|
||||||
|
android_tts_SynthProxy_native_setup(JNIEnv *env, jobject thiz,
|
||||||
|
jobject weak_this, jstring nativeSoLib)
|
||||||
|
{
|
||||||
|
SynthProxyJniStorage* pJniStorage = new SynthProxyJniStorage();
|
||||||
|
|
||||||
|
prepAudioTrack(pJniStorage,
|
||||||
|
DEFAULT_TTS_RATE, DEFAULT_TTS_FORMAT, DEFAULT_TTS_NB_CHANNELS);
|
||||||
|
|
||||||
|
const char *nativeSoLibNativeString =
|
||||||
|
env->GetStringUTFChars(nativeSoLib, 0);
|
||||||
|
|
||||||
|
void *engine_lib_handle = dlopen(nativeSoLibNativeString,
|
||||||
|
RTLD_NOW | RTLD_LOCAL);
|
||||||
|
if (engine_lib_handle==NULL) {
|
||||||
|
LOGI("engine_lib_handle==NULL");
|
||||||
|
// TODO report error so the TTS can't be used
|
||||||
|
} else {
|
||||||
|
TtsEngine *(*get_TtsEngine)() =
|
||||||
|
reinterpret_cast<TtsEngine* (*)()>(dlsym(engine_lib_handle, "getTtsEngine"));
|
||||||
|
pJniStorage->mNativeSynthInterface = (*get_TtsEngine)();
|
||||||
|
if (pJniStorage->mNativeSynthInterface) {
|
||||||
|
pJniStorage->mNativeSynthInterface->init(ttsSynthDoneCB);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// we use a weak reference so the SynthProxy object can be garbage collected.
|
||||||
|
pJniStorage->tts_ref = env->NewGlobalRef(weak_this);
|
||||||
|
|
||||||
|
// save the JNI resources so we can use them (and free them) later
|
||||||
|
env->SetIntField(thiz, javaTTSFields.synthProxyFieldJniData,
|
||||||
|
(int)pJniStorage);
|
||||||
|
|
||||||
|
env->ReleaseStringUTFChars(nativeSoLib, nativeSoLibNativeString);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void
|
||||||
|
android_tts_SynthProxy_native_finalize(JNIEnv *env, jobject thiz, jint jniData)
|
||||||
|
{
|
||||||
|
if (jniData) {
|
||||||
|
SynthProxyJniStorage* pSynthData = (SynthProxyJniStorage*)jniData;
|
||||||
|
delete pSynthData;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void
|
||||||
|
android_tts_SynthProxy_setLanguage(JNIEnv *env, jobject thiz, jint jniData,
|
||||||
|
jstring language)
|
||||||
|
{
|
||||||
|
if (jniData == 0) {
|
||||||
|
LOGE("android_tts_SynthProxy_setLanguage(): invalid JNI data");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SynthProxyJniStorage* pSynthData = (SynthProxyJniStorage*)jniData;
|
||||||
|
const char *langNativeString = env->GetStringUTFChars(language, 0);
|
||||||
|
// TODO check return codes
|
||||||
|
if (pSynthData->mNativeSynthInterface) {
|
||||||
|
pSynthData->mNativeSynthInterface->setLanguage(langNativeString,
|
||||||
|
strlen(langNativeString));
|
||||||
|
}
|
||||||
|
env->ReleaseStringUTFChars(language, langNativeString);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void
|
||||||
|
android_tts_SynthProxy_setSpeechRate(JNIEnv *env, jobject thiz, jint jniData,
|
||||||
|
int speechRate)
|
||||||
|
{
|
||||||
|
if (jniData == 0) {
|
||||||
|
LOGE("android_tts_SynthProxy_setSpeechRate(): invalid JNI data");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int bufSize = 10;
|
||||||
|
char buffer [bufSize];
|
||||||
|
sprintf(buffer, "%d", speechRate);
|
||||||
|
|
||||||
|
SynthProxyJniStorage* pSynthData = (SynthProxyJniStorage*)jniData;
|
||||||
|
LOGI("setting speech rate to %d", speechRate);
|
||||||
|
// TODO check return codes
|
||||||
|
if (pSynthData->mNativeSynthInterface) {
|
||||||
|
pSynthData->mNativeSynthInterface->setProperty("rate", buffer, bufSize);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// TODO: Refactor this to get rid of any assumptions about sample rate, etc.
|
||||||
|
static void
|
||||||
|
android_tts_SynthProxy_synthesizeToFile(JNIEnv *env, jobject thiz, jint jniData,
|
||||||
|
jstring textJavaString, jstring filenameJavaString)
|
||||||
|
{
|
||||||
|
if (jniData == 0) {
|
||||||
|
LOGE("android_tts_SynthProxy_synthesizeToFile(): invalid JNI data");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SynthProxyJniStorage* pSynthData = (SynthProxyJniStorage*)jniData;
|
||||||
|
|
||||||
|
const char *filenameNativeString =
|
||||||
|
env->GetStringUTFChars(filenameJavaString, 0);
|
||||||
|
const char *textNativeString = env->GetStringUTFChars(textJavaString, 0);
|
||||||
|
|
||||||
|
afterSynthData_t* pForAfter = new (afterSynthData_t);
|
||||||
|
pForAfter->jniStorage = jniData;
|
||||||
|
pForAfter->usageMode = USAGEMODE_WRITE_TO_FILE;
|
||||||
|
|
||||||
|
pForAfter->outputFile = fopen(filenameNativeString, "wb");
|
||||||
|
|
||||||
|
// Write 44 blank bytes for WAV header, then come back and fill them in
|
||||||
|
// after we've written the audio data
|
||||||
|
char header[44];
|
||||||
|
fwrite(header, 1, 44, pForAfter->outputFile);
|
||||||
|
|
||||||
|
unsigned int unique_identifier;
|
||||||
|
|
||||||
|
// TODO check return codes
|
||||||
|
if (pSynthData->mNativeSynthInterface) {
|
||||||
|
pSynthData->mNativeSynthInterface->synthesizeText(textNativeString,
|
||||||
|
(void *)pForAfter);
|
||||||
|
}
|
||||||
|
|
||||||
|
long filelen = ftell(pForAfter->outputFile);
|
||||||
|
|
||||||
|
int samples = (((int)filelen) - 44) / 2;
|
||||||
|
header[0] = 'R';
|
||||||
|
header[1] = 'I';
|
||||||
|
header[2] = 'F';
|
||||||
|
header[3] = 'F';
|
||||||
|
((uint32_t *)(&header[4]))[0] = filelen - 8;
|
||||||
|
header[8] = 'W';
|
||||||
|
header[9] = 'A';
|
||||||
|
header[10] = 'V';
|
||||||
|
header[11] = 'E';
|
||||||
|
|
||||||
|
header[12] = 'f';
|
||||||
|
header[13] = 'm';
|
||||||
|
header[14] = 't';
|
||||||
|
header[15] = ' ';
|
||||||
|
|
||||||
|
((uint32_t *)(&header[16]))[0] = 16; // size of fmt
|
||||||
|
|
||||||
|
((unsigned short *)(&header[20]))[0] = 1; // format
|
||||||
|
((unsigned short *)(&header[22]))[0] = 1; // channels
|
||||||
|
((uint32_t *)(&header[24]))[0] = 22050; // samplerate
|
||||||
|
((uint32_t *)(&header[28]))[0] = 44100; // byterate
|
||||||
|
((unsigned short *)(&header[32]))[0] = 2; // block align
|
||||||
|
((unsigned short *)(&header[34]))[0] = 16; // bits per sample
|
||||||
|
|
||||||
|
header[36] = 'd';
|
||||||
|
header[37] = 'a';
|
||||||
|
header[38] = 't';
|
||||||
|
header[39] = 'a';
|
||||||
|
|
||||||
|
((uint32_t *)(&header[40]))[0] = samples * 2; // size of data
|
||||||
|
|
||||||
|
// Skip back to the beginning and rewrite the header
|
||||||
|
fseek(pForAfter->outputFile, 0, SEEK_SET);
|
||||||
|
fwrite(header, 1, 44, pForAfter->outputFile);
|
||||||
|
|
||||||
|
fflush(pForAfter->outputFile);
|
||||||
|
fclose(pForAfter->outputFile);
|
||||||
|
|
||||||
|
env->ReleaseStringUTFChars(textJavaString, textNativeString);
|
||||||
|
env->ReleaseStringUTFChars(filenameJavaString, filenameNativeString);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void
|
||||||
|
android_tts_SynthProxy_speak(JNIEnv *env, jobject thiz, jint jniData,
|
||||||
|
jstring textJavaString)
|
||||||
|
{
|
||||||
|
if (jniData == 0) {
|
||||||
|
LOGE("android_tts_SynthProxy_speak(): invalid JNI data");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SynthProxyJniStorage* pSynthData = (SynthProxyJniStorage*)jniData;
|
||||||
|
|
||||||
|
if (pSynthData->mAudioOut) {
|
||||||
|
pSynthData->mAudioOut->stop();
|
||||||
|
pSynthData->mAudioOut->start();
|
||||||
|
}
|
||||||
|
|
||||||
|
afterSynthData_t* pForAfter = new (afterSynthData_t);
|
||||||
|
pForAfter->jniStorage = jniData;
|
||||||
|
pForAfter->usageMode = USAGEMODE_PLAY_IMMEDIATELY;
|
||||||
|
|
||||||
|
if (pSynthData->mNativeSynthInterface) {
|
||||||
|
const char *textNativeString = env->GetStringUTFChars(textJavaString, 0);
|
||||||
|
pSynthData->mNativeSynthInterface->synthesizeText(textNativeString,
|
||||||
|
(void *)pForAfter);
|
||||||
|
env->ReleaseStringUTFChars(textJavaString, textNativeString);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void
|
||||||
|
android_tts_SynthProxy_stop(JNIEnv *env, jobject thiz, jint jniData)
|
||||||
|
{
|
||||||
|
if (jniData == 0) {
|
||||||
|
LOGE("android_tts_SynthProxy_stop(): invalid JNI data");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SynthProxyJniStorage* pSynthData = (SynthProxyJniStorage*)jniData;
|
||||||
|
|
||||||
|
if (pSynthData->mNativeSynthInterface) {
|
||||||
|
pSynthData->mNativeSynthInterface->stop();
|
||||||
|
}
|
||||||
|
if (pSynthData->mAudioOut) {
|
||||||
|
pSynthData->mAudioOut->stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void
|
||||||
|
android_tts_SynthProxy_shutdown(JNIEnv *env, jobject thiz, jint jniData)
|
||||||
|
{
|
||||||
|
if (jniData == 0) {
|
||||||
|
LOGE("android_tts_SynthProxy_shutdown(): invalid JNI data");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SynthProxyJniStorage* pSynthData = (SynthProxyJniStorage*)jniData;
|
||||||
|
if (pSynthData->mNativeSynthInterface) {
|
||||||
|
pSynthData->mNativeSynthInterface->shutdown();
|
||||||
|
pSynthData->mNativeSynthInterface = NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// TODO add buffer format
|
||||||
|
static void
|
||||||
|
android_tts_SynthProxy_playAudioBuffer(JNIEnv *env, jobject thiz, jint jniData,
|
||||||
|
int bufferPointer, int bufferSize)
|
||||||
|
{
|
||||||
|
if (jniData == 0) {
|
||||||
|
LOGE("android_tts_SynthProxy_playAudioBuffer(): invalid JNI data");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SynthProxyJniStorage* pSynthData = (SynthProxyJniStorage*)jniData;
|
||||||
|
short* wav = (short*) bufferPointer;
|
||||||
|
pSynthData->mAudioOut->write(wav, bufferSize);
|
||||||
|
LOGI("AudioTrack wrote: %d bytes", bufferSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
JNIEXPORT jstring JNICALL
|
||||||
|
android_tts_SynthProxy_getLanguage(JNIEnv *env, jobject thiz, jint jniData)
|
||||||
|
{
|
||||||
|
if (jniData == 0) {
|
||||||
|
LOGE("android_tts_SynthProxy_getLanguage(): invalid JNI data");
|
||||||
|
return env->NewStringUTF("");
|
||||||
|
}
|
||||||
|
|
||||||
|
SynthProxyJniStorage* pSynthData = (SynthProxyJniStorage*)jniData;
|
||||||
|
size_t bufSize = 100;
|
||||||
|
char buf[bufSize];
|
||||||
|
memset(buf, 0, bufSize);
|
||||||
|
// TODO check return codes
|
||||||
|
if (pSynthData->mNativeSynthInterface) {
|
||||||
|
pSynthData->mNativeSynthInterface->getLanguage(buf, &bufSize);
|
||||||
|
}
|
||||||
|
return env->NewStringUTF(buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
JNIEXPORT int JNICALL
|
||||||
|
android_tts_SynthProxy_getRate(JNIEnv *env, jobject thiz, jint jniData)
|
||||||
|
{
|
||||||
|
if (jniData == 0) {
|
||||||
|
LOGE("android_tts_SynthProxy_getRate(): invalid JNI data");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
SynthProxyJniStorage* pSynthData = (SynthProxyJniStorage*)jniData;
|
||||||
|
size_t bufSize = 100;
|
||||||
|
|
||||||
|
char buf[bufSize];
|
||||||
|
memset(buf, 0, bufSize);
|
||||||
|
// TODO check return codes
|
||||||
|
if (pSynthData->mNativeSynthInterface) {
|
||||||
|
pSynthData->mNativeSynthInterface->getProperty("rate", buf, &bufSize);
|
||||||
|
}
|
||||||
|
return atoi(buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dalvik VM type signatures
|
||||||
|
static JNINativeMethod gMethods[] = {
|
||||||
|
{ "native_stop",
|
||||||
|
"(I)V",
|
||||||
|
(void*)android_tts_SynthProxy_stop
|
||||||
|
},
|
||||||
|
{ "native_speak",
|
||||||
|
"(ILjava/lang/String;)V",
|
||||||
|
(void*)android_tts_SynthProxy_speak
|
||||||
|
},
|
||||||
|
{ "native_synthesizeToFile",
|
||||||
|
"(ILjava/lang/String;Ljava/lang/String;)V",
|
||||||
|
(void*)android_tts_SynthProxy_synthesizeToFile
|
||||||
|
},
|
||||||
|
{ "native_setLanguage",
|
||||||
|
"(ILjava/lang/String;)V",
|
||||||
|
(void*)android_tts_SynthProxy_setLanguage
|
||||||
|
},
|
||||||
|
{ "native_setSpeechRate",
|
||||||
|
"(II)V",
|
||||||
|
(void*)android_tts_SynthProxy_setSpeechRate
|
||||||
|
},
|
||||||
|
{ "native_playAudioBuffer",
|
||||||
|
"(III)V",
|
||||||
|
(void*)android_tts_SynthProxy_playAudioBuffer
|
||||||
|
},
|
||||||
|
{ "native_getLanguage",
|
||||||
|
"(I)Ljava/lang/String;",
|
||||||
|
(void*)android_tts_SynthProxy_getLanguage
|
||||||
|
},
|
||||||
|
{ "native_getRate",
|
||||||
|
"(I)I",
|
||||||
|
(void*)android_tts_SynthProxy_getRate
|
||||||
|
},
|
||||||
|
{ "native_shutdown",
|
||||||
|
"(I)V",
|
||||||
|
(void*)android_tts_SynthProxy_shutdown
|
||||||
|
},
|
||||||
|
{ "native_setup",
|
||||||
|
"(Ljava/lang/Object;Ljava/lang/String;)V",
|
||||||
|
(void*)android_tts_SynthProxy_native_setup
|
||||||
|
},
|
||||||
|
{ "native_finalize",
|
||||||
|
"(I)V",
|
||||||
|
(void*)android_tts_SynthProxy_native_finalize
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
#define SP_JNIDATA_FIELD_NAME "mJniData"
|
||||||
|
#define SP_POSTSPEECHSYNTHESIZED_METHOD_NAME "postNativeSpeechSynthesizedInJava"
|
||||||
|
|
||||||
|
// TODO: verify this is the correct path
|
||||||
|
static const char* const kClassPathName = "android/tts/SynthProxy";
|
||||||
|
|
||||||
|
jint JNI_OnLoad(JavaVM* vm, void* reserved)
|
||||||
|
{
|
||||||
|
JNIEnv* env = NULL;
|
||||||
|
jint result = -1;
|
||||||
|
jclass clazz;
|
||||||
|
|
||||||
|
if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {
|
||||||
|
LOGE("ERROR: GetEnv failed\n");
|
||||||
|
goto bail;
|
||||||
|
}
|
||||||
|
assert(env != NULL);
|
||||||
|
|
||||||
|
clazz = env->FindClass(kClassPathName);
|
||||||
|
if (clazz == NULL) {
|
||||||
|
LOGE("Can't find %s", kClassPathName);
|
||||||
|
goto bail;
|
||||||
|
}
|
||||||
|
|
||||||
|
javaTTSFields.synthProxyClass = clazz;
|
||||||
|
javaTTSFields.synthProxyFieldJniData = NULL;
|
||||||
|
javaTTSFields.synthProxyMethodPost = NULL;
|
||||||
|
|
||||||
|
javaTTSFields.synthProxyFieldJniData = env->GetFieldID(clazz,
|
||||||
|
SP_JNIDATA_FIELD_NAME, "I");
|
||||||
|
if (javaTTSFields.synthProxyFieldJniData == NULL) {
|
||||||
|
LOGE("Can't find %s.%s field", kClassPathName, SP_JNIDATA_FIELD_NAME);
|
||||||
|
goto bail;
|
||||||
|
}
|
||||||
|
|
||||||
|
javaTTSFields.synthProxyMethodPost = env->GetStaticMethodID(clazz,
|
||||||
|
SP_POSTSPEECHSYNTHESIZED_METHOD_NAME, "(Ljava/lang/Object;II)V");
|
||||||
|
if (javaTTSFields.synthProxyMethodPost == NULL) {
|
||||||
|
LOGE("Can't find %s.%s method", kClassPathName, SP_POSTSPEECHSYNTHESIZED_METHOD_NAME);
|
||||||
|
goto bail;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (jniRegisterNativeMethods(
|
||||||
|
env, kClassPathName, gMethods, NELEM(gMethods)) < 0)
|
||||||
|
goto bail;
|
||||||
|
|
||||||
|
/* success -- return valid version number */
|
||||||
|
result = JNI_VERSION_1_4;
|
||||||
|
|
||||||
|
bail:
|
||||||
|
return result;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user