Propagate optional audio stream type to the creation of the audio resources
to enable the playback of TTS content on various stream types.
This commit is contained in:
@@ -33,6 +33,8 @@
|
||||
#define DEFAULT_TTS_FORMAT AudioSystem::PCM_16_BIT
|
||||
#define DEFAULT_TTS_NB_CHANNELS 1
|
||||
#define DEFAULT_TTS_BUFFERSIZE 1024
|
||||
// TODO use the TTS stream type when available
|
||||
#define DEFAULT_TTS_STREAM_TYPE AudioSystem::MUSIC
|
||||
|
||||
#define USAGEMODE_PLAY_IMMEDIATELY 0
|
||||
#define USAGEMODE_WRITE_TO_FILE 1
|
||||
@@ -46,10 +48,12 @@ struct fields_t {
|
||||
jmethodID synthProxyMethodPost;
|
||||
};
|
||||
|
||||
// structure to hold the data that is used each time the TTS engine has synthesized more data
|
||||
struct afterSynthData_t {
|
||||
jint jniStorage;
|
||||
int usageMode;
|
||||
FILE* outputFile;
|
||||
AudioSystem::stream_type streamType;
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
@@ -62,6 +66,7 @@ class SynthProxyJniStorage {
|
||||
jobject tts_ref;
|
||||
TtsEngine* mNativeSynthInterface;
|
||||
AudioTrack* mAudioOut;
|
||||
AudioSystem::stream_type mStreamType;
|
||||
uint32_t mSampleRate;
|
||||
AudioSystem::audio_format mAudFormat;
|
||||
int mNbChannels;
|
||||
@@ -73,6 +78,7 @@ class SynthProxyJniStorage {
|
||||
tts_ref = NULL;
|
||||
mNativeSynthInterface = NULL;
|
||||
mAudioOut = NULL;
|
||||
mStreamType = DEFAULT_TTS_STREAM_TYPE;
|
||||
mSampleRate = DEFAULT_TTS_RATE;
|
||||
mAudFormat = DEFAULT_TTS_FORMAT;
|
||||
mNbChannels = DEFAULT_TTS_NB_CHANNELS;
|
||||
@@ -97,34 +103,33 @@ class SynthProxyJniStorage {
|
||||
}
|
||||
}
|
||||
|
||||
void createAudioOut(uint32_t rate, AudioSystem::audio_format format,
|
||||
int channel) {
|
||||
void createAudioOut(AudioSystem::stream_type streamType, 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;
|
||||
mStreamType = streamType;
|
||||
|
||||
// retrieve system properties to ensure successful creation of the
|
||||
// AudioTrack object for playback
|
||||
int afSampleRate;
|
||||
if (AudioSystem::getOutputSamplingRate(&afSampleRate, streamType) != NO_ERROR) {
|
||||
if (AudioSystem::getOutputSamplingRate(&afSampleRate, mStreamType) != NO_ERROR) {
|
||||
afSampleRate = 44100;
|
||||
}
|
||||
int afFrameCount;
|
||||
if (AudioSystem::getOutputFrameCount(&afFrameCount, streamType) != NO_ERROR) {
|
||||
if (AudioSystem::getOutputFrameCount(&afFrameCount, mStreamType) != NO_ERROR) {
|
||||
afFrameCount = 2048;
|
||||
}
|
||||
uint32_t afLatency;
|
||||
if (AudioSystem::getOutputLatency(&afLatency, streamType) != NO_ERROR) {
|
||||
if (AudioSystem::getOutputLatency(&afLatency, mStreamType) != 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,
|
||||
mAudioOut = new AudioTrack(mStreamType, rate, format, channel,
|
||||
minFrameCount > 4096 ? minFrameCount : 4096,
|
||||
0, 0, 0, 0); // not using an AudioTrack callback
|
||||
|
||||
@@ -142,21 +147,21 @@ class SynthProxyJniStorage {
|
||||
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
void prepAudioTrack(SynthProxyJniStorage* pJniData,
|
||||
uint32_t rate, AudioSystem::audio_format format, int channel)
|
||||
{
|
||||
void prepAudioTrack(SynthProxyJniStorage* pJniData, AudioSystem::stream_type streamType,
|
||||
uint32_t rate, AudioSystem::audio_format format, int channel) {
|
||||
// Don't bother creating a new audiotrack object if the current
|
||||
// object is already set.
|
||||
// object is already initialized with the same audio parameters.
|
||||
if ( pJniData->mAudioOut &&
|
||||
(rate == pJniData->mSampleRate) &&
|
||||
(format == pJniData->mAudFormat) &&
|
||||
(channel == pJniData->mNbChannels) ){
|
||||
(channel == pJniData->mNbChannels) &&
|
||||
(streamType == pJniData->mStreamType) ){
|
||||
return;
|
||||
}
|
||||
if (pJniData->mAudioOut){
|
||||
pJniData->killAudio();
|
||||
}
|
||||
pJniData->createAudioOut(rate, format, channel);
|
||||
pJniData->createAudioOut(streamType, rate, format, channel);
|
||||
}
|
||||
|
||||
|
||||
@@ -186,7 +191,7 @@ static tts_callback_status ttsSynthDoneCB(void *& userdata, uint32_t rate,
|
||||
}
|
||||
|
||||
if (bufferSize > 0) {
|
||||
prepAudioTrack(pJniData, rate, format, channel);
|
||||
prepAudioTrack(pJniData, pForAfter->streamType, rate, format, channel);
|
||||
if (pJniData->mAudioOut) {
|
||||
pJniData->mAudioOut->write(wav, bufferSize);
|
||||
//LOGV("AudioTrack wrote: %d bytes", bufferSize);
|
||||
@@ -241,7 +246,7 @@ android_tts_SynthProxy_native_setup(JNIEnv *env, jobject thiz,
|
||||
SynthProxyJniStorage* pJniStorage = new SynthProxyJniStorage();
|
||||
|
||||
prepAudioTrack(pJniStorage,
|
||||
DEFAULT_TTS_RATE, DEFAULT_TTS_FORMAT, DEFAULT_TTS_NB_CHANNELS);
|
||||
DEFAULT_TTS_STREAM_TYPE, DEFAULT_TTS_RATE, DEFAULT_TTS_FORMAT, DEFAULT_TTS_NB_CHANNELS);
|
||||
|
||||
const char *nativeSoLibNativeString =
|
||||
env->GetStringUTFChars(nativeSoLib, 0);
|
||||
@@ -526,7 +531,7 @@ android_tts_SynthProxy_synthesizeToFile(JNIEnv *env, jobject thiz, jint jniData,
|
||||
|
||||
static int
|
||||
android_tts_SynthProxy_speak(JNIEnv *env, jobject thiz, jint jniData,
|
||||
jstring textJavaString)
|
||||
jstring textJavaString, jint javaStreamType)
|
||||
{
|
||||
int result = TTS_FAILURE;
|
||||
|
||||
@@ -545,6 +550,7 @@ android_tts_SynthProxy_speak(JNIEnv *env, jobject thiz, jint jniData,
|
||||
afterSynthData_t* pForAfter = new (afterSynthData_t);
|
||||
pForAfter->jniStorage = jniData;
|
||||
pForAfter->usageMode = USAGEMODE_PLAY_IMMEDIATELY;
|
||||
pForAfter->streamType = (AudioSystem::stream_type) javaStreamType;
|
||||
|
||||
if (pSynthData->mNativeSynthInterface) {
|
||||
const char *textNativeString = env->GetStringUTFChars(textJavaString, 0);
|
||||
@@ -672,7 +678,7 @@ static JNINativeMethod gMethods[] = {
|
||||
(void*)android_tts_SynthProxy_stop
|
||||
},
|
||||
{ "native_speak",
|
||||
"(ILjava/lang/String;)I",
|
||||
"(ILjava/lang/String;I)I",
|
||||
(void*)android_tts_SynthProxy_speak
|
||||
},
|
||||
{ "native_synthesizeToFile",
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
*/
|
||||
package android.tts;
|
||||
|
||||
import android.media.AudioManager;
|
||||
import android.media.AudioSystem;
|
||||
import android.util.Log;
|
||||
import java.lang.ref.WeakReference;
|
||||
|
||||
@@ -52,8 +54,13 @@ public class SynthProxy {
|
||||
/**
|
||||
* Synthesize speech and speak it directly using AudioTrack.
|
||||
*/
|
||||
public int speak(String text) {
|
||||
return native_speak(mJniData, text);
|
||||
public int speak(String text, int streamType) {
|
||||
if ((streamType > -1) && (streamType < AudioSystem.getNumStreamTypes())) {
|
||||
return native_speak(mJniData, text, streamType);
|
||||
} else {
|
||||
Log.e("SynthProxy", "Trying to speak with invalid stream type " + streamType);
|
||||
return native_speak(mJniData, text, AudioManager.STREAM_MUSIC);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -156,7 +163,7 @@ public class SynthProxy {
|
||||
|
||||
private native final int native_stop(int jniData);
|
||||
|
||||
private native final int native_speak(int jniData, String text);
|
||||
private native final int native_speak(int jniData, String text, int streamType);
|
||||
|
||||
private native final int native_synthesizeToFile(int jniData, String text, String filename);
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.PackageManager.NameNotFoundException;
|
||||
import android.media.AudioManager;
|
||||
import android.media.MediaPlayer;
|
||||
import android.media.MediaPlayer.OnCompletionListener;
|
||||
import android.net.Uri;
|
||||
@@ -109,6 +110,8 @@ public class TtsService extends Service implements OnCompletionListener {
|
||||
|
||||
private static final int MAX_SPEECH_ITEM_CHAR_LENGTH = 4000;
|
||||
private static final int MAX_FILENAME_LENGTH = 250;
|
||||
// TODO use the TTS stream type when available
|
||||
private static final int DEFAULT_STREAM_TYPE = AudioManager.STREAM_MUSIC;
|
||||
|
||||
private static final String ACTION = "android.intent.action.START_TTS_SERVICE";
|
||||
private static final String CATEGORY = "android.intent.category.TTS";
|
||||
@@ -450,14 +453,15 @@ public class TtsService extends Service implements OnCompletionListener {
|
||||
synth.start();
|
||||
return;
|
||||
}
|
||||
int streamType = DEFAULT_STREAM_TYPE;
|
||||
if (params != null){
|
||||
String language = "";
|
||||
String country = "";
|
||||
String variant = "";
|
||||
for (int i = 0; i < params.size() - 1; i = i + 2){
|
||||
String param = params.get(i);
|
||||
if (param != null){
|
||||
if (param.equals(TextToSpeech.Engine.TTS_KEY_PARAM_RATE)){
|
||||
if (param != null) {
|
||||
if (param.equals(TextToSpeech.Engine.TTS_KEY_PARAM_RATE)) {
|
||||
setSpeechRate("", Integer.parseInt(params.get(i+1)));
|
||||
} else if (param.equals(TextToSpeech.Engine.TTS_KEY_PARAM_LANGUAGE)){
|
||||
language = params.get(i+1);
|
||||
@@ -465,6 +469,12 @@ public class TtsService extends Service implements OnCompletionListener {
|
||||
country = params.get(i+1);
|
||||
} else if (param.equals(TextToSpeech.Engine.TTS_KEY_PARAM_VARIANT)){
|
||||
variant = params.get(i+1);
|
||||
} else if (param.equals(TextToSpeech.Engine.TTS_KEY_PARAM_STREAM)) {
|
||||
try {
|
||||
streamType = Integer.parseInt(params.get(i + 1));
|
||||
} catch (NumberFormatException e) {
|
||||
streamType = DEFAULT_STREAM_TYPE;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -472,7 +482,7 @@ public class TtsService extends Service implements OnCompletionListener {
|
||||
setLanguage("", language, country, variant);
|
||||
}
|
||||
}
|
||||
nativeSynth.speak(text);
|
||||
nativeSynth.speak(text, streamType);
|
||||
} catch (InterruptedException e) {
|
||||
Log.e("TTS speakInternalOnly", "tryLock interrupted");
|
||||
e.printStackTrace();
|
||||
@@ -651,8 +661,7 @@ public class TtsService extends Service implements OnCompletionListener {
|
||||
// Utterance is part of the app calling the library
|
||||
Context ctx;
|
||||
try {
|
||||
ctx = this.createPackageContext(sr.mSourcePackageName,
|
||||
0);
|
||||
ctx = this.createPackageContext(sr.mSourcePackageName, 0);
|
||||
} catch (NameNotFoundException e) {
|
||||
e.printStackTrace();
|
||||
mSpeechQueue.remove(0); // Remove it from the queue and
|
||||
@@ -675,6 +684,7 @@ public class TtsService extends Service implements OnCompletionListener {
|
||||
}
|
||||
mPlayer.setOnCompletionListener(this);
|
||||
try {
|
||||
mPlayer.setAudioStreamType(getStreamTypeFromParams(currentSpeechItem.mParams));
|
||||
mPlayer.start();
|
||||
} catch (IllegalStateException e) {
|
||||
mSpeechQueue.clear();
|
||||
@@ -695,6 +705,24 @@ public class TtsService extends Service implements OnCompletionListener {
|
||||
}
|
||||
}
|
||||
|
||||
private int getStreamTypeFromParams(ArrayList<String> paramList) {
|
||||
int streamType = DEFAULT_STREAM_TYPE;
|
||||
if (paramList == null) {
|
||||
return streamType;
|
||||
}
|
||||
for (int i = 0; i < paramList.size() - 1; i = i + 2) {
|
||||
String param = paramList.get(i);
|
||||
if ((param != null) && (param.equals(TextToSpeech.Engine.TTS_KEY_PARAM_STREAM))) {
|
||||
try {
|
||||
streamType = Integer.parseInt(paramList.get(i + 1));
|
||||
} catch (NumberFormatException e) {
|
||||
streamType = DEFAULT_STREAM_TYPE;
|
||||
}
|
||||
}
|
||||
}
|
||||
return streamType;
|
||||
}
|
||||
|
||||
private void cleanUpPlayer() {
|
||||
if (mPlayer != null) {
|
||||
mPlayer.release();
|
||||
|
||||
Reference in New Issue
Block a user