diff options
author | Bjorn Bringert <bringert@android.com> | 2011-03-08 16:00:40 +0000 |
---|---|---|
committer | Bjorn Bringert <bringert@android.com> | 2011-04-15 10:04:31 +0100 |
commit | 50e657bb2d005568f5dd8bc1d904d07b0d94018f (patch) | |
tree | bcf319e64984c80f4793602aeb366e7f6b33afdd | |
parent | 720dd9a89857512d14adabea4efbb542d25fbb02 (diff) | |
download | frameworks_base-50e657bb2d005568f5dd8bc1d904d07b0d94018f.zip frameworks_base-50e657bb2d005568f5dd8bc1d904d07b0d94018f.tar.gz frameworks_base-50e657bb2d005568f5dd8bc1d904d07b0d94018f.tar.bz2 |
Add Java API for writing TTS engines
This removes the old non-public C++ API for TTS
engines and replaces it with a Java API.
The new API is still @hidden, until it has been approved.
Bug: 4148636
Change-Id: I7614ff788e11f897e87052f684f1b4938d539fb7
24 files changed, 2089 insertions, 4352 deletions
@@ -137,8 +137,8 @@ LOCAL_SRC_FILES += \ core/java/android/view/IWindowSession.aidl \ core/java/android/speech/IRecognitionListener.aidl \ core/java/android/speech/IRecognitionService.aidl \ - core/java/android/speech/tts/ITts.aidl \ - core/java/android/speech/tts/ITtsCallback.aidl \ + core/java/android/speech/tts/ITextToSpeechCallback.aidl \ + core/java/android/speech/tts/ITextToSpeechService.aidl \ core/java/com/android/internal/app/IBatteryStats.aidl \ core/java/com/android/internal/app/IUsageStats.aidl \ core/java/com/android/internal/app/IMediaContainerService.aidl \ diff --git a/CleanSpec.mk b/CleanSpec.mk index 023ce59..50292e4 100644 --- a/CleanSpec.mk +++ b/CleanSpec.mk @@ -96,6 +96,7 @@ $(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/APPS/PerfTest_interme $(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/APPS/RSTest_intermediates/) $(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/framework_intermediates/src/core/java/android/hardware/IUsbManager.java) $(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/framework_intermediates/src/core/java/android/nfc) +$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/framework_intermediates) # ************************************************ # NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST diff --git a/core/java/android/speech/tts/BlockingMediaPlayer.java b/core/java/android/speech/tts/BlockingMediaPlayer.java new file mode 100644 index 0000000..3cf60dd --- /dev/null +++ b/core/java/android/speech/tts/BlockingMediaPlayer.java @@ -0,0 +1,146 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * 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.speech.tts; + +import android.content.Context; +import android.media.MediaPlayer; +import android.net.Uri; +import android.os.ConditionVariable; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.Looper; +import android.util.Log; + +/** + * A media player that allows blocking to wait for it to finish. + */ +class BlockingMediaPlayer { + + private static final String TAG = "BlockMediaPlayer"; + + private static final String MEDIA_PLAYER_THREAD_NAME = "TTS-MediaPlayer"; + + private final Context mContext; + private final Uri mUri; + private final int mStreamType; + private final ConditionVariable mDone; + // Only accessed on the Handler thread + private MediaPlayer mPlayer; + private volatile boolean mFinished; + + /** + * Creates a new blocking media player. + * Creating a blocking media player is a cheap operation. + * + * @param context + * @param uri + * @param streamType + */ + public BlockingMediaPlayer(Context context, Uri uri, int streamType) { + mContext = context; + mUri = uri; + mStreamType = streamType; + mDone = new ConditionVariable(); + + } + + /** + * Starts playback and waits for it to finish. + * Can be called from any thread. + * + * @return {@code true} if the playback finished normally, {@code false} if the playback + * failed or {@link #stop} was called before the playback finished. + */ + public boolean startAndWait() { + HandlerThread thread = new HandlerThread(MEDIA_PLAYER_THREAD_NAME); + thread.start(); + Handler handler = new Handler(thread.getLooper()); + mFinished = false; + handler.post(new Runnable() { + @Override + public void run() { + startPlaying(); + } + }); + mDone.block(); + handler.post(new Runnable() { + @Override + public void run() { + finish(); + // No new messages should get posted to the handler thread after this + Looper.myLooper().quit(); + } + }); + return mFinished; + } + + /** + * Stops playback. Can be called multiple times. + * Can be called from any thread. + */ + public void stop() { + mDone.open(); + } + + /** + * Starts playback. + * Called on the handler thread. + */ + private void startPlaying() { + mPlayer = MediaPlayer.create(mContext, mUri); + if (mPlayer == null) { + Log.w(TAG, "Failed to play " + mUri); + mDone.open(); + return; + } + try { + mPlayer.setOnErrorListener(new MediaPlayer.OnErrorListener() { + @Override + public boolean onError(MediaPlayer mp, int what, int extra) { + Log.w(TAG, "Audio playback error: " + what + ", " + extra); + mDone.open(); + return true; + } + }); + mPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() { + @Override + public void onCompletion(MediaPlayer mp) { + mFinished = true; + mDone.open(); + } + }); + mPlayer.setAudioStreamType(mStreamType); + mPlayer.start(); + } catch (IllegalArgumentException ex) { + Log.w(TAG, "MediaPlayer failed", ex); + mDone.open(); + } + } + + /** + * Stops playback and release the media player. + * Called on the handler thread. + */ + private void finish() { + try { + mPlayer.stop(); + } catch (IllegalStateException ex) { + // Do nothing, the player is already stopped + } + mPlayer.release(); + } + +}
\ No newline at end of file diff --git a/core/java/android/speech/tts/FileSynthesisRequest.java b/core/java/android/speech/tts/FileSynthesisRequest.java new file mode 100644 index 0000000..370ad53 --- /dev/null +++ b/core/java/android/speech/tts/FileSynthesisRequest.java @@ -0,0 +1,197 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * 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.speech.tts; + +import android.media.AudioFormat; +import android.util.Log; + +import java.io.File; +import java.io.IOException; +import java.io.RandomAccessFile; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +/** + * Speech synthesis request that writes the audio to a WAV file. + */ +class FileSynthesisRequest extends SynthesisRequest { + + private static final String TAG = "FileSynthesisRequest"; + private static final boolean DBG = false; + + private static final int WAV_HEADER_LENGTH = 44; + private static final short WAV_FORMAT_PCM = 0x0001; + + private final Object mStateLock = new Object(); + private final File mFileName; + private int mSampleRateInHz; + private int mAudioFormat; + private int mChannelCount; + private RandomAccessFile mFile; + private boolean mStopped = false; + + FileSynthesisRequest(String text, File fileName) { + super(text); + mFileName = fileName; + } + + @Override + void stop() { + synchronized (mStateLock) { + mStopped = true; + cleanUp(); + } + } + + /** + * Must be called while holding the monitor on {@link #mStateLock}. + */ + private void cleanUp() { + closeFile(); + if (mFile != null) { + mFileName.delete(); + } + } + + /** + * Must be called while holding the monitor on {@link #mStateLock}. + */ + private void closeFile() { + try { + if (mFile != null) { + mFile.close(); + mFile = null; + } + } catch (IOException ex) { + Log.e(TAG, "Failed to close " + mFileName + ": " + ex); + } + } + + @Override + public int start(int sampleRateInHz, int audioFormat, int channelCount) { + if (DBG) { + Log.d(TAG, "FileSynthesisRequest.start(" + sampleRateInHz + "," + audioFormat + + "," + channelCount + ")"); + } + synchronized (mStateLock) { + if (mStopped) { + if (DBG) Log.d(TAG, "Request has been aborted."); + return TextToSpeech.ERROR; + } + if (mFile != null) { + cleanUp(); + throw new IllegalArgumentException("FileSynthesisRequest.start() called twice"); + } + mSampleRateInHz = sampleRateInHz; + mAudioFormat = audioFormat; + mChannelCount = channelCount; + try { + mFile = new RandomAccessFile(mFileName, "rw"); + // Reserve space for WAV header + mFile.write(new byte[WAV_HEADER_LENGTH]); + return TextToSpeech.SUCCESS; + } catch (IOException ex) { + Log.e(TAG, "Failed to open " + mFileName + ": " + ex); + cleanUp(); + return TextToSpeech.ERROR; + } + } + } + + @Override + public int audioAvailable(byte[] buffer, int offset, int length) { + if (DBG) { + Log.d(TAG, "FileSynthesisRequest.audioAvailable(" + buffer + "," + offset + + "," + length + ")"); + } + synchronized (mStateLock) { + if (mStopped) { + if (DBG) Log.d(TAG, "Request has been aborted."); + return TextToSpeech.ERROR; + } + if (mFile == null) { + Log.e(TAG, "File not open"); + return TextToSpeech.ERROR; + } + try { + mFile.write(buffer, offset, length); + return TextToSpeech.SUCCESS; + } catch (IOException ex) { + Log.e(TAG, "Failed to write to " + mFileName + ": " + ex); + cleanUp(); + return TextToSpeech.ERROR; + } + } + } + + @Override + public int done() { + if (DBG) Log.d(TAG, "FileSynthesisRequest.done()"); + synchronized (mStateLock) { + if (mStopped) { + if (DBG) Log.d(TAG, "Request has been aborted."); + return TextToSpeech.ERROR; + } + if (mFile == null) { + Log.e(TAG, "File not open"); + return TextToSpeech.ERROR; + } + try { + // Write WAV header at start of file + mFile.seek(0); + int fileLen = (int) mFile.length(); + mFile.write(makeWavHeader(mSampleRateInHz, mAudioFormat, mChannelCount, fileLen)); + closeFile(); + return TextToSpeech.SUCCESS; + } catch (IOException ex) { + Log.e(TAG, "Failed to write to " + mFileName + ": " + ex); + cleanUp(); + return TextToSpeech.ERROR; + } + } + } + + private byte[] makeWavHeader(int sampleRateInHz, int audioFormat, int channelCount, + int fileLength) { + // TODO: is AudioFormat.ENCODING_DEFAULT always the same as ENCODING_PCM_16BIT? + int sampleSizeInBytes = (audioFormat == AudioFormat.ENCODING_PCM_8BIT ? 1 : 2); + int byteRate = sampleRateInHz * sampleSizeInBytes * channelCount; + short blockAlign = (short) (sampleSizeInBytes * channelCount); + short bitsPerSample = (short) (sampleSizeInBytes * 8); + + byte[] headerBuf = new byte[WAV_HEADER_LENGTH]; + ByteBuffer header = ByteBuffer.wrap(headerBuf); + header.order(ByteOrder.LITTLE_ENDIAN); + + header.put(new byte[]{ 'R', 'I', 'F', 'F' }); + header.putInt(fileLength - 8); // RIFF chunk size + header.put(new byte[]{ 'W', 'A', 'V', 'E' }); + header.put(new byte[]{ 'f', 'm', 't', ' ' }); + header.putInt(16); // size of fmt chunk + header.putShort(WAV_FORMAT_PCM); + header.putShort((short) channelCount); + header.putInt(sampleRateInHz); + header.putInt(byteRate); + header.putShort(blockAlign); + header.putShort(bitsPerSample); + header.put(new byte[]{ 'd', 'a', 't', 'a' }); + int dataLength = fileLength - WAV_HEADER_LENGTH; + header.putInt(dataLength); + + return headerBuf; + } + +} diff --git a/core/java/android/speech/tts/ITtsCallback.aidl b/core/java/android/speech/tts/ITextToSpeechCallback.aidl index c9898eb..40902ae 100755 --- a/core/java/android/speech/tts/ITtsCallback.aidl +++ b/core/java/android/speech/tts/ITextToSpeechCallback.aidl @@ -1,5 +1,5 @@ /* - * Copyright (C) 2009 The Android Open Source Project + * Copyright (C) 2011 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,15 +13,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package android.speech.tts; /** - * AIDL for the callback from the TTS Service - * ITtsCallback.java is autogenerated from this. + * Interface for callbacks from TextToSpeechService * * {@hide} */ -oneway interface ITtsCallback { +oneway interface ITextToSpeechCallback { void utteranceCompleted(String utteranceId); } diff --git a/core/java/android/speech/tts/ITextToSpeechService.aidl b/core/java/android/speech/tts/ITextToSpeechService.aidl new file mode 100644 index 0000000..ff3fa11 --- /dev/null +++ b/core/java/android/speech/tts/ITextToSpeechService.aidl @@ -0,0 +1,140 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * 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.speech.tts; + +import android.net.Uri; +import android.os.Bundle; +import android.speech.tts.ITextToSpeechCallback; + +/** + * Interface for TextToSpeech to talk to TextToSpeechService. + * + * {@hide} + */ +interface ITextToSpeechService { + + /** + * Tells the engine to synthesize some speech and play it back. + * + * @param callingApp The package name of the calling app. Used to connect requests + * callbacks and to clear requests when the calling app is stopping. + * @param text The text to synthesize. + * @param queueMode Determines what to do to requests already in the queue. + * @param param Request parameters. + */ + int speak(in String callingApp, in String text, in int queueMode, in Bundle params); + + /** + * Tells the engine to synthesize some speech and write it to a file. + * + * @param callingApp The package name of the calling app. Used to connect requests + * callbacks and to clear requests when the calling app is stopping. + * @param text The text to synthesize. + * @param filename The file to write the synthesized audio to. + * @param param Request parameters. + */ + int synthesizeToFile(in String callingApp, in String text, + in String filename, in Bundle params); + + /** + * Plays an existing audio resource. + * + * @param callingApp The package name of the calling app. Used to connect requests + * callbacks and to clear requests when the calling app is stopping. + * @param audioUri URI for the audio resource (a file or android.resource URI) + * @param queueMode Determines what to do to requests already in the queue. + * @param param Request parameters. + */ + int playAudio(in String callingApp, in Uri audioUri, in int queueMode, in Bundle params); + + /** + * Plays silence. + * + * @param callingApp The package name of the calling app. Used to connect requests + * callbacks and to clear requests when the calling app is stopping. + * @param duration Number of milliseconds of silence to play. + * @param queueMode Determines what to do to requests already in the queue. + * @param param Request parameters. + */ + int playSilence(in String callingApp, in long duration, in int queueMode, in Bundle params); + + /** + * Checks whether the service is currently playing some audio. + */ + boolean isSpeaking(); + + /** + * Interrupts the current utterance (if from the given app) and removes any utterances + * in the queue that are from the given app. + * + * @param callingApp Package name of the app whose utterances + * should be interrupted and cleared. + */ + int stop(in String callingApp); + + /** + * Returns the language, country and variant currently being used by the TTS engine. + * + * Can be called from multiple threads. + * + * @return A 3-element array, containing language (ISO 3-letter code), + * country (ISO 3-letter code) and variant used by the engine. + * The country and variant may be {@code ""}. If country is empty, then variant must + * be empty too. + */ + String[] getLanguage(); + + /** + * Checks whether the engine supports a given language. + * + * @param lang ISO-3 language code. + * @param country ISO-3 country code. May be empty or null. + * @param variant Language variant. May be empty or null. + * @return Code indicating the support status for the locale. + * One of {@link TextToSpeech#LANG_AVAILABLE}, + * {@link TextToSpeech#LANG_COUNTRY_AVAILABLE}, + * {@link TextToSpeech#LANG_COUNTRY_VAR_AVAILABLE}, + * {@link TextToSpeech#LANG_MISSING_DATA} + * {@link TextToSpeech#LANG_NOT_SUPPORTED}. + */ + int isLanguageAvailable(in String lang, in String country, in String variant); + + /** + * Notifies the engine that it should load a speech synthesis language. + * + * @param lang ISO-3 language code. + * @param country ISO-3 country code. May be empty or null. + * @param variant Language variant. May be empty or null. + * @return Code indicating the support status for the locale. + * One of {@link TextToSpeech#LANG_AVAILABLE}, + * {@link TextToSpeech#LANG_COUNTRY_AVAILABLE}, + * {@link TextToSpeech#LANG_COUNTRY_VAR_AVAILABLE}, + * {@link TextToSpeech#LANG_MISSING_DATA} + * {@link TextToSpeech#LANG_NOT_SUPPORTED}. + */ + int loadLanguage(in String lang, in String country, in String variant); + + /** + * Sets the callback that will be notified when playback of utterance from the + * given app are completed. + * + * @param callingApp Package name for the app whose utterance the callback will handle. + * @param cb The callback. + */ + void setCallback(in String callingApp, ITextToSpeechCallback cb); + +} diff --git a/core/java/android/speech/tts/ITts.aidl b/core/java/android/speech/tts/ITts.aidl deleted file mode 100755 index c1051c4..0000000 --- a/core/java/android/speech/tts/ITts.aidl +++ /dev/null @@ -1,69 +0,0 @@ -/*
- * Copyright (C) 2009 The Android Open Source Project
- *
- * 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.speech.tts;
-
-import android.speech.tts.ITtsCallback;
-
-import android.content.Intent;
-
-/**
- * AIDL for the TTS Service
- * ITts.java is autogenerated from this.
- *
- * {@hide}
- */
-interface ITts {
- int setSpeechRate(in String callingApp, in int speechRate);
-
- int setPitch(in String callingApp, in int pitch);
-
- int speak(in String callingApp, in String text, in int queueMode, in String[] params);
-
- boolean isSpeaking();
-
- int stop(in String callingApp);
-
- void addSpeech(in String callingApp, in String text, in String packageName, in int resId);
-
- void addSpeechFile(in String callingApp, in String text, in String filename);
-
- String[] getLanguage();
-
- int isLanguageAvailable(in String language, in String country, in String variant, in String[] params);
-
- int setLanguage(in String callingApp, in String language, in String country, in String variant);
-
- boolean synthesizeToFile(in String callingApp, in String text, in String[] params, in String outputDirectory);
-
- int playEarcon(in String callingApp, in String earcon, in int queueMode, in String[] params);
-
- void addEarcon(in String callingApp, in String earcon, in String packageName, in int resId);
-
- void addEarconFile(in String callingApp, in String earcon, in String filename);
-
- int registerCallback(in String callingApp, ITtsCallback cb);
-
- int unregisterCallback(in String callingApp, ITtsCallback cb);
-
- int playSilence(in String callingApp, in long duration, in int queueMode, in String[] params); -
- int setEngineByPackageName(in String enginePackageName); - - String getDefaultEngine(); - - boolean areDefaultsEnforced();
-}
diff --git a/core/java/android/speech/tts/PlaybackSynthesisRequest.java b/core/java/android/speech/tts/PlaybackSynthesisRequest.java new file mode 100644 index 0000000..15a4ee9 --- /dev/null +++ b/core/java/android/speech/tts/PlaybackSynthesisRequest.java @@ -0,0 +1,197 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * 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.speech.tts; + +import android.media.AudioFormat; +import android.media.AudioTrack; +import android.util.Log; + +/** + * Speech synthesis request that plays the audio as it is received. + */ +class PlaybackSynthesisRequest extends SynthesisRequest { + + private static final String TAG = "PlaybackSynthesisRequest"; + private static final boolean DBG = false; + + private static final int MIN_AUDIO_BUFFER_SIZE = 8192; + + /** + * Audio stream type. Must be one of the STREAM_ contants defined in + * {@link android.media.AudioManager}. + */ + private final int mStreamType; + + /** + * Volume, in the range [0.0f, 1.0f]. The default value is + * {@link TextToSpeech.Engine#DEFAULT_VOLUME} (1.0f). + */ + private final float mVolume; + + /** + * Left/right position of the audio, in the range [-1.0f, 1.0f]. + * The default value is {@link TextToSpeech.Engine#DEFAULT_PAN} (0.0f). + */ + private final float mPan; + + private final Object mStateLock = new Object(); + private AudioTrack mAudioTrack = null; + private boolean mStopped = false; + + PlaybackSynthesisRequest(String text, int streamType, float volume, float pan) { + super(text); + mStreamType = streamType; + mVolume = volume; + mPan = pan; + } + + @Override + void stop() { + if (DBG) Log.d(TAG, "stop()"); + synchronized (mStateLock) { + mStopped = true; + cleanUp(); + } + } + + private void cleanUp() { + if (DBG) Log.d(TAG, "cleanUp()"); + if (mAudioTrack != null) { + mAudioTrack.flush(); + mAudioTrack.stop(); + // TODO: do we need to wait for playback to finish before releasing? + mAudioTrack.release(); + mAudioTrack = null; + } + } + + // TODO: add a thread that writes to the AudioTrack? + @Override + public int start(int sampleRateInHz, int audioFormat, int channelCount) { + if (DBG) { + Log.d(TAG, "start(" + sampleRateInHz + "," + audioFormat + + "," + channelCount + ")"); + } + + int channelConfig; + if (channelCount == 1) { + channelConfig = AudioFormat.CHANNEL_OUT_MONO; + } else if (channelCount == 2){ + channelConfig = AudioFormat.CHANNEL_OUT_STEREO; + } else { + Log.e(TAG, "Unsupported number of channels: " + channelCount); + return TextToSpeech.ERROR; + } + + int minBufferSizeInBytes + = AudioTrack.getMinBufferSize(sampleRateInHz, channelConfig, audioFormat); + int bufferSizeInBytes = Math.max(MIN_AUDIO_BUFFER_SIZE, minBufferSizeInBytes); + + synchronized (mStateLock) { + if (mStopped) { + if (DBG) Log.d(TAG, "Request has been aborted."); + return TextToSpeech.ERROR; + } + if (mAudioTrack != null) { + Log.e(TAG, "start() called twice"); + cleanUp(); + return TextToSpeech.ERROR; + } + + mAudioTrack = new AudioTrack(mStreamType, sampleRateInHz, channelConfig, audioFormat, + bufferSizeInBytes, AudioTrack.MODE_STREAM); + if (mAudioTrack.getState() != AudioTrack.STATE_INITIALIZED) { + cleanUp(); + return TextToSpeech.ERROR; + } + + setupVolume(); + } + + return TextToSpeech.SUCCESS; + } + + private void setupVolume() { + float vol = clip(mVolume, 0.0f, 1.0f); + float panning = clip(mPan, -1.0f, 1.0f); + float volLeft = vol; + float volRight = vol; + if (panning > 0.0f) { + volLeft *= (1.0f - panning); + } else if (panning < 0.0f) { + volRight *= (1.0f + panning); + } + if (DBG) Log.d(TAG, "volLeft=" + volLeft + ",volRight=" + volRight); + if (mAudioTrack.setStereoVolume(volLeft, volRight) != AudioTrack.SUCCESS) { + Log.e(TAG, "Failed to set volume"); + } + } + + private float clip(float value, float min, float max) { + return value > max ? max : (value < min ? min : value); + } + + @Override + public int audioAvailable(byte[] buffer, int offset, int length) { + if (DBG) { + Log.d(TAG, "audioAvailable(byte[" + buffer.length + "]," + + offset + "," + length + "), thread ID=" + android.os.Process.myTid()); + } + synchronized (mStateLock) { + if (mStopped) { + if (DBG) Log.d(TAG, "Request has been aborted."); + return TextToSpeech.ERROR; + } + if (mAudioTrack == null) { + Log.e(TAG, "audioAvailable(): Not started"); + return TextToSpeech.ERROR; + } + int playState = mAudioTrack.getPlayState(); + if (playState == AudioTrack.PLAYSTATE_STOPPED) { + if (DBG) Log.d(TAG, "AudioTrack stopped, restarting"); + mAudioTrack.play(); + } + // TODO: loop until all data is written? + if (DBG) Log.d(TAG, "AudioTrack.write()"); + int count = mAudioTrack.write(buffer, offset, length); + if (DBG) Log.d(TAG, "AudioTrack.write() returned " + count); + if (count < 0) { + Log.e(TAG, "Writing to AudioTrack failed: " + count); + cleanUp(); + return TextToSpeech.ERROR; + } else { + return TextToSpeech.SUCCESS; + } + } + } + + @Override + public int done() { + if (DBG) Log.d(TAG, "done()"); + synchronized (mStateLock) { + if (mStopped) { + if (DBG) Log.d(TAG, "Request has been aborted."); + return TextToSpeech.ERROR; + } + if (mAudioTrack == null) { + Log.e(TAG, "done(): Not started"); + return TextToSpeech.ERROR; + } + cleanUp(); + } + return TextToSpeech.SUCCESS; + } +}
\ No newline at end of file diff --git a/core/java/android/speech/tts/SynthesisRequest.java b/core/java/android/speech/tts/SynthesisRequest.java new file mode 100644 index 0000000..3f2ec5d --- /dev/null +++ b/core/java/android/speech/tts/SynthesisRequest.java @@ -0,0 +1,151 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * 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.speech.tts; + +/** + * A request for speech synthesis given to a TTS engine for processing. + * + * @hide Pending approval + */ +public abstract class SynthesisRequest { + + private final String mText; + private String mLanguage; + private String mCountry; + private String mVariant; + private int mSpeechRate; + private int mPitch; + + public SynthesisRequest(String text) { + mText = text; + } + + /** + * Sets the locale for the request. + */ + void setLanguage(String language, String country, String variant) { + mLanguage = language; + mCountry = country; + mVariant = variant; + } + + /** + * Sets the speech rate. + */ + void setSpeechRate(int speechRate) { + mSpeechRate = speechRate; + } + + /** + * Sets the pitch. + */ + void setPitch(int pitch) { + mPitch = pitch; + } + + /** + * Gets the text which should be synthesized. + */ + public String getText() { + return mText; + } + + /** + * Gets the ISO 3-letter language code for the language to use. + */ + public String getLanguage() { + return mLanguage; + } + + /** + * Gets the ISO 3-letter country code for the language to use. + */ + public String getCountry() { + return mCountry; + } + + /** + * Gets the language variant to use. + */ + public String getVariant() { + return mVariant; + } + + /** + * Gets the speech rate to use. {@link TextToSpeech.Engine#DEFAULT_RATE} (100) + * is the normal rate. + */ + public int getSpeechRate() { + return mSpeechRate; + } + + /** + * Gets the pitch to use. {@link TextToSpeech.Engine#DEFAULT_PITCH} (100) + * is the normal pitch. + */ + public int getPitch() { + return mPitch; + } + + /** + * Aborts the speech request. + * + * Can be called from multiple threads. + */ + abstract void stop(); + + /** + * The service should call this when it starts to synthesize audio for this + * request. + * + * This method should only be called on the synthesis thread, + * while in {@link TextToSpeechService#onSynthesizeText}. + * + * @param sampleRateInHz Sample rate in HZ of the generated audio. + * @param audioFormat Audio format of the generated audio. Must be one of + * the ENCODING_ constants defined in {@link android.media.AudioFormat}. + * @param channelCount The number of channels + * @return {@link TextToSpeech#SUCCESS} or {@link TextToSpeech#ERROR}. + */ + public abstract int start(int sampleRateInHz, int audioFormat, int channelCount); + + /** + * The service should call this method when synthesized audio is ready for consumption. + * + * This method should only be called on the synthesis thread, + * while in {@link TextToSpeechService#onSynthesizeText}. + * + * @param buffer The generated audio data. This method will not hold on to {@code buffer}, + * so the caller is free to modify it after this method returns. + * @param offset The offset into {@code buffer} where the audio data starts. + * @param length The number of bytes of audio data in {@code buffer}. + * Must be less than or equal to {@code buffer.length - offset}. + * @return {@link TextToSpeech#SUCCESS} or {@link TextToSpeech#ERROR}. + */ + public abstract int audioAvailable(byte[] buffer, int offset, int length); + + /** + * The service should call this method when all the synthesized audio for a request has + * been passed to {@link #audioAvailable}. + * + * This method should only be called on the synthesis thread, + * while in {@link TextToSpeechService#onSynthesizeText}. + * + * @return {@link TextToSpeech#SUCCESS} or {@link TextToSpeech#ERROR}. + */ + public abstract int done(); + +}
\ No newline at end of file diff --git a/core/java/android/speech/tts/TextToSpeech.java b/core/java/android/speech/tts/TextToSpeech.java index 95830ec..2add7b5 100755 --- a/core/java/android/speech/tts/TextToSpeech.java +++ b/core/java/android/speech/tts/TextToSpeech.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2009 Google Inc. + * Copyright (C) 2009 The Android Open Source Project * * 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 @@ -15,22 +15,31 @@ */ package android.speech.tts; -import android.speech.tts.ITts; -import android.speech.tts.ITtsCallback; - import android.annotation.SdkConstant; import android.annotation.SdkConstant.SdkConstantType; import android.content.ComponentName; +import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.content.pm.ServiceInfo; import android.media.AudioManager; +import android.net.Uri; +import android.os.Bundle; import android.os.IBinder; import android.os.RemoteException; +import android.provider.Settings; +import android.text.TextUtils; import android.util.Log; +import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; +import java.util.List; import java.util.Locale; +import java.util.Map; /** * @@ -49,11 +58,11 @@ public class TextToSpeech { /** * Denotes a successful operation. */ - public static final int SUCCESS = 0; + public static final int SUCCESS = 0; /** * Denotes a generic operation failure. */ - public static final int ERROR = -1; + public static final int ERROR = -1; /** * Queue mode where all entries in the playback queue (media to be played @@ -65,20 +74,17 @@ public class TextToSpeech { */ public static final int QUEUE_ADD = 1; - /** * Denotes the language is available exactly as specified by the locale. */ public static final int LANG_COUNTRY_VAR_AVAILABLE = 2; - /** * Denotes the language is available for the language and country specified * by the locale, but not the variant. */ public static final int LANG_COUNTRY_AVAILABLE = 1; - /** * Denotes the language is available for the language by the locale, * but not the country and variant. @@ -95,7 +101,6 @@ public class TextToSpeech { */ public static final int LANG_NOT_SUPPORTED = -2; - /** * Broadcast Action: The TextToSpeech synthesizer has completed processing * of all the text in the speech queue. @@ -104,7 +109,6 @@ public class TextToSpeech { public static final String ACTION_TTS_QUEUE_PROCESSING_COMPLETED = "android.speech.tts.TTS_QUEUE_PROCESSING_COMPLETED"; - /** * Interface definition of a callback to be invoked indicating the completion of the * TextToSpeech engine initialization. @@ -112,103 +116,117 @@ public class TextToSpeech { public interface OnInitListener { /** * Called to signal the completion of the TextToSpeech engine initialization. + * * @param status {@link TextToSpeech#SUCCESS} or {@link TextToSpeech#ERROR}. */ public void onInit(int status); } /** - * Interface definition of a callback to be invoked indicating the TextToSpeech engine has - * completed synthesizing an utterance with an utterance ID set. - * + * Listener that will be called when the TTS service has + * completed synthesizing an utterance. This is only called if the utterance + * has an utterance ID (see {@link TextToSpeech.Engine#KEY_PARAM_UTTERANCE_ID}). */ public interface OnUtteranceCompletedListener { /** - * Called to signal the completion of the synthesis of the utterance that was identified - * with the string parameter. This identifier is the one originally passed in the - * parameter hashmap of the synthesis request in - * {@link TextToSpeech#speak(String, int, HashMap)} or - * {@link TextToSpeech#synthesizeToFile(String, HashMap, String)} with the - * {@link TextToSpeech.Engine#KEY_PARAM_UTTERANCE_ID} key. + * Called when an utterance has been synthesized. + * * @param utteranceId the identifier of the utterance. */ public void onUtteranceCompleted(String utteranceId); } - /** - * Internal constants for the TextToSpeech functionality - * + * Constants and parameter names for controlling text-to-speech. */ public class Engine { - // default values for a TTS engine when settings are not found in the provider + /** - * {@hide} + * Default speech rate. + * @hide */ - public static final int DEFAULT_RATE = 100; // 1x + public static final int DEFAULT_RATE = 100; + /** - * {@hide} + * Default pitch. + * @hide */ - public static final int DEFAULT_PITCH = 100;// 1x + public static final int DEFAULT_PITCH = 100; + /** - * {@hide} + * Default volume. + * @hide */ public static final float DEFAULT_VOLUME = 1.0f; + /** - * {@hide} - */ - protected static final String DEFAULT_VOLUME_STRING = "1.0"; - /** - * {@hide} + * Default pan (centered). + * @hide */ public static final float DEFAULT_PAN = 0.0f; - /** - * {@hide} - */ - protected static final String DEFAULT_PAN_STRING = "0.0"; /** - * {@hide} + * Default value for {@link Settings.Secure#TTS_USE_DEFAULTS}. + * @hide */ public static final int USE_DEFAULTS = 0; // false + /** - * {@hide} + * Package name of the default TTS engine. + * + * TODO: This should come from a system property + * + * @hide */ - public static final String DEFAULT_SYNTH = "com.svox.pico"; + public static final String DEFAULT_ENGINE = "com.svox.pico"; - // default values for rendering /** * Default audio stream used when playing synthesized speech. */ public static final int DEFAULT_STREAM = AudioManager.STREAM_MUSIC; - // return codes for a TTS engine's check data activity /** * Indicates success when checking the installation status of the resources used by the * TextToSpeech engine with the {@link #ACTION_CHECK_TTS_DATA} intent. */ public static final int CHECK_VOICE_DATA_PASS = 1; + /** * Indicates failure when checking the installation status of the resources used by the * TextToSpeech engine with the {@link #ACTION_CHECK_TTS_DATA} intent. */ public static final int CHECK_VOICE_DATA_FAIL = 0; + /** * Indicates erroneous data when checking the installation status of the resources used by * the TextToSpeech engine with the {@link #ACTION_CHECK_TTS_DATA} intent. */ public static final int CHECK_VOICE_DATA_BAD_DATA = -1; + /** * Indicates missing resources when checking the installation status of the resources used * by the TextToSpeech engine with the {@link #ACTION_CHECK_TTS_DATA} intent. */ public static final int CHECK_VOICE_DATA_MISSING_DATA = -2; + /** * Indicates missing storage volume when checking the installation status of the resources * used by the TextToSpeech engine with the {@link #ACTION_CHECK_TTS_DATA} intent. */ public static final int CHECK_VOICE_DATA_MISSING_VOLUME = -3; + /** + * Intent for starting a TTS service. Services that handle this intent must + * extend {@link TextToSpeechService}. Normal applications should not use this intent + * directly, instead they should talk to the TTS service using the the methods in this + * class. + * + * @hide Pending API council approval + */ + @SdkConstant(SdkConstantType.SERVICE_ACTION) + public static final String INTENT_ACTION_TTS_SERVICE = + "android.intent.action.TTS_SERVICE"; + // intents to ask engine to install data or check its data /** * Activity Action: Triggers the platform TextToSpeech engine to @@ -231,6 +249,7 @@ public class TextToSpeech { @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) public static final String ACTION_TTS_DATA_INSTALLED = "android.speech.tts.engine.TTS_DATA_INSTALLED"; + /** * Activity Action: Starts the activity from the platform TextToSpeech * engine to verify the proper installation and availability of the @@ -258,23 +277,36 @@ public class TextToSpeech { public static final String ACTION_CHECK_TTS_DATA = "android.speech.tts.engine.CHECK_TTS_DATA"; + /** + * Activity intent for getting some sample text to use for demonstrating TTS. + * + * @hide This intent was used by engines written against the old API. + * Not sure if it should be exposed. + */ + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_GET_SAMPLE_TEXT = + "android.speech.tts.engine.GET_SAMPLE_TEXT"; + // extras for a TTS engine's check data activity /** * Extra information received with the {@link #ACTION_CHECK_TTS_DATA} intent where * the TextToSpeech engine specifies the path to its resources. */ public static final String EXTRA_VOICE_DATA_ROOT_DIRECTORY = "dataRoot"; + /** * Extra information received with the {@link #ACTION_CHECK_TTS_DATA} intent where * the TextToSpeech engine specifies the file names of its resources under the * resource path. */ public static final String EXTRA_VOICE_DATA_FILES = "dataFiles"; + /** * Extra information received with the {@link #ACTION_CHECK_TTS_DATA} intent where * the TextToSpeech engine specifies the locale associated with each resource file. */ public static final String EXTRA_VOICE_DATA_FILES_INFO = "dataFilesInfo"; + /** * Extra information received with the {@link #ACTION_CHECK_TTS_DATA} intent where * the TextToSpeech engine returns an ArrayList<String> of all the available voices. @@ -282,6 +314,7 @@ public class TextToSpeech { * optional (ie, "eng" or "eng-USA" or "eng-USA-FEMALE"). */ public static final String EXTRA_AVAILABLE_VOICES = "availableVoices"; + /** * Extra information received with the {@link #ACTION_CHECK_TTS_DATA} intent where * the TextToSpeech engine returns an ArrayList<String> of all the unavailable voices. @@ -289,6 +322,7 @@ public class TextToSpeech { * optional (ie, "eng" or "eng-USA" or "eng-USA-FEMALE"). */ public static final String EXTRA_UNAVAILABLE_VOICES = "unavailableVoices"; + /** * Extra information sent with the {@link #ACTION_CHECK_TTS_DATA} intent where the * caller indicates to the TextToSpeech engine which specific sets of voice data to @@ -311,134 +345,87 @@ public class TextToSpeech { // keys for the parameters passed with speak commands. Hidden keys are used internally // to maintain engine state for each TextToSpeech instance. /** - * {@hide} + * @hide */ public static final String KEY_PARAM_RATE = "rate"; + /** - * {@hide} + * @hide */ public static final String KEY_PARAM_LANGUAGE = "language"; + /** - * {@hide} + * @hide */ public static final String KEY_PARAM_COUNTRY = "country"; + /** - * {@hide} + * @hide */ public static final String KEY_PARAM_VARIANT = "variant"; + /** - * {@hide} + * @hide */ public static final String KEY_PARAM_ENGINE = "engine"; + /** - * {@hide} + * @hide */ public static final String KEY_PARAM_PITCH = "pitch"; + /** * Parameter key to specify the audio stream type to be used when speaking text - * or playing back a file. + * or playing back a file. The value should be one of the STREAM_ constants + * defined in {@link AudioManager}. + * * @see TextToSpeech#speak(String, int, HashMap) * @see TextToSpeech#playEarcon(String, int, HashMap) */ public static final String KEY_PARAM_STREAM = "streamType"; + /** * Parameter key to identify an utterance in the * {@link TextToSpeech.OnUtteranceCompletedListener} after text has been * spoken, a file has been played back or a silence duration has elapsed. + * * @see TextToSpeech#speak(String, int, HashMap) * @see TextToSpeech#playEarcon(String, int, HashMap) * @see TextToSpeech#synthesizeToFile(String, HashMap, String) */ public static final String KEY_PARAM_UTTERANCE_ID = "utteranceId"; + /** * Parameter key to specify the speech volume relative to the current stream type * volume used when speaking text. Volume is specified as a float ranging from 0 to 1 * where 0 is silence, and 1 is the maximum volume (the default behavior). + * * @see TextToSpeech#speak(String, int, HashMap) * @see TextToSpeech#playEarcon(String, int, HashMap) */ public static final String KEY_PARAM_VOLUME = "volume"; + /** * Parameter key to specify how the speech is panned from left to right when speaking text. * Pan is specified as a float ranging from -1 to +1 where -1 maps to a hard-left pan, * 0 to center (the default behavior), and +1 to hard-right. + * * @see TextToSpeech#speak(String, int, HashMap) * @see TextToSpeech#playEarcon(String, int, HashMap) */ public static final String KEY_PARAM_PAN = "pan"; - // key positions in the array of cached parameters - /** - * {@hide} - */ - protected static final int PARAM_POSITION_RATE = 0; - /** - * {@hide} - */ - protected static final int PARAM_POSITION_LANGUAGE = 2; - /** - * {@hide} - */ - protected static final int PARAM_POSITION_COUNTRY = 4; - /** - * {@hide} - */ - protected static final int PARAM_POSITION_VARIANT = 6; - /** - * {@hide} - */ - protected static final int PARAM_POSITION_STREAM = 8; - /** - * {@hide} - */ - protected static final int PARAM_POSITION_UTTERANCE_ID = 10; - - /** - * {@hide} - */ - protected static final int PARAM_POSITION_ENGINE = 12; - - /** - * {@hide} - */ - protected static final int PARAM_POSITION_PITCH = 14; - - /** - * {@hide} - */ - protected static final int PARAM_POSITION_VOLUME = 16; - - /** - * {@hide} - */ - protected static final int PARAM_POSITION_PAN = 18; - - - /** - * {@hide} - * Total number of cached speech parameters. - * This number should be equal to (max param position/2) + 1. - */ - protected static final int NB_CACHED_PARAMS = 10; } - /** - * Connection needed for the TTS. - */ - private ServiceConnection mServiceConnection; - - private ITts mITts = null; - private ITtsCallback mITtscallback = null; - private Context mContext = null; - private String mPackageName = ""; - private OnInitListener mInitListener = null; - private boolean mStarted = false; + private final Context mContext; + private Connection mServiceConnection; + private OnInitListener mInitListener; private final Object mStartLock = new Object(); - /** - * Used to store the cached parameters sent along with each synthesis request to the - * TTS service. - */ - private String[] mCachedParams; + + private String mRequestedEngine; + private final Map<String, Uri> mEarcons; + private final Map<String, Uri> mUtterances; + private final Bundle mParams = new Bundle(); /** * The constructor for the TextToSpeech class. @@ -451,84 +438,98 @@ public class TextToSpeech { * TextToSpeech engine has initialized. */ public TextToSpeech(Context context, OnInitListener listener) { + this(context, listener, null); + } + + /** + * @hide pending approval + */ + public TextToSpeech(Context context, OnInitListener listener, String engine) { mContext = context; - mPackageName = mContext.getPackageName(); mInitListener = listener; + mRequestedEngine = engine; - mCachedParams = new String[2*Engine.NB_CACHED_PARAMS]; // store key and value - mCachedParams[Engine.PARAM_POSITION_RATE] = Engine.KEY_PARAM_RATE; - mCachedParams[Engine.PARAM_POSITION_LANGUAGE] = Engine.KEY_PARAM_LANGUAGE; - mCachedParams[Engine.PARAM_POSITION_COUNTRY] = Engine.KEY_PARAM_COUNTRY; - mCachedParams[Engine.PARAM_POSITION_VARIANT] = Engine.KEY_PARAM_VARIANT; - mCachedParams[Engine.PARAM_POSITION_STREAM] = Engine.KEY_PARAM_STREAM; - mCachedParams[Engine.PARAM_POSITION_UTTERANCE_ID] = Engine.KEY_PARAM_UTTERANCE_ID; - mCachedParams[Engine.PARAM_POSITION_ENGINE] = Engine.KEY_PARAM_ENGINE; - mCachedParams[Engine.PARAM_POSITION_PITCH] = Engine.KEY_PARAM_PITCH; - mCachedParams[Engine.PARAM_POSITION_VOLUME] = Engine.KEY_PARAM_VOLUME; - mCachedParams[Engine.PARAM_POSITION_PAN] = Engine.KEY_PARAM_PAN; - - // Leave all defaults that are shown in Settings uninitialized/at the default - // so that the values set in Settings will take effect if the application does - // not try to change these settings itself. - mCachedParams[Engine.PARAM_POSITION_RATE + 1] = ""; - mCachedParams[Engine.PARAM_POSITION_LANGUAGE + 1] = ""; - mCachedParams[Engine.PARAM_POSITION_COUNTRY + 1] = ""; - mCachedParams[Engine.PARAM_POSITION_VARIANT + 1] = ""; - mCachedParams[Engine.PARAM_POSITION_STREAM + 1] = - String.valueOf(Engine.DEFAULT_STREAM); - mCachedParams[Engine.PARAM_POSITION_UTTERANCE_ID + 1] = ""; - mCachedParams[Engine.PARAM_POSITION_ENGINE + 1] = ""; - mCachedParams[Engine.PARAM_POSITION_PITCH + 1] = "100"; - mCachedParams[Engine.PARAM_POSITION_VOLUME + 1] = Engine.DEFAULT_VOLUME_STRING; - mCachedParams[Engine.PARAM_POSITION_PAN + 1] = Engine.DEFAULT_PAN_STRING; + mEarcons = new HashMap<String, Uri>(); + mUtterances = new HashMap<String, Uri>(); initTts(); } + private String getPackageName() { + return mContext.getPackageName(); + } - private void initTts() { - mStarted = false; - - // Initialize the TTS, run the callback after the binding is successful - mServiceConnection = new ServiceConnection() { - public void onServiceConnected(ComponentName name, IBinder service) { - synchronized(mStartLock) { - mITts = ITts.Stub.asInterface(service); - mStarted = true; - // Cache the default engine and current language - setEngineByPackageName(getDefaultEngine()); - setLanguage(getLanguage()); - if (mInitListener != null) { - // TODO manage failures and missing resources - mInitListener.onInit(SUCCESS); - } - } + private <R> R runActionNoReconnect(Action<R> action, R errorResult, String method) { + return runAction(action, errorResult, method, false); + } + + private <R> R runAction(Action<R> action, R errorResult, String method) { + return runAction(action, errorResult, method, true); + } + + private <R> R runAction(Action<R> action, R errorResult, String method, boolean reconnect) { + synchronized (mStartLock) { + if (mServiceConnection == null) { + Log.w(TAG, method + " failed: not bound to TTS engine"); + return errorResult; } + return mServiceConnection.runAction(action, errorResult, method, reconnect); + } + } - public void onServiceDisconnected(ComponentName name) { - synchronized(mStartLock) { - mITts = null; - mInitListener = null; - mStarted = false; - } + private int initTts() { + String defaultEngine = getDefaultEngine(); + String engine = defaultEngine; + if (!areDefaultsEnforced() && !TextUtils.isEmpty(mRequestedEngine) + && isEngineEnabled(engine)) { + engine = mRequestedEngine; + } + + // Try requested engine + if (connectToEngine(engine)) { + return SUCCESS; + } + + // Fall back to user's default engine if different from the already tested one + if (!engine.equals(defaultEngine)) { + if (connectToEngine(defaultEngine)) { + return SUCCESS; } - }; + } - Intent intent = new Intent("android.intent.action.START_TTS_SERVICE"); - intent.addCategory("android.intent.category.TTS"); - boolean bound = mContext.bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE); - if (!bound) { - Log.e("TextToSpeech.java", "initTts() failed to bind to service"); - if (mInitListener != null) { - mInitListener.onInit(ERROR); + // Fall back to the hardcoded default if different from the two above + if (!defaultEngine.equals(Engine.DEFAULT_ENGINE) + && !engine.equals(Engine.DEFAULT_ENGINE)) { + if (connectToEngine(Engine.DEFAULT_ENGINE)) { + return SUCCESS; } + } + + return ERROR; + } + + private boolean connectToEngine(String engine) { + Connection connection = new Connection(); + Intent intent = new Intent(Engine.INTENT_ACTION_TTS_SERVICE); + intent.setPackage(engine); + boolean bound = mContext.bindService(intent, connection, Context.BIND_AUTO_CREATE); + if (!bound) { + Log.e(TAG, "Failed to bind to " + engine); + dispatchOnInit(ERROR); + return false; } else { - // initialization listener will be called inside ServiceConnection - Log.i("TextToSpeech.java", "initTts() successfully bound to service"); + return true; } - // TODO handle plugin failures } + private void dispatchOnInit(int result) { + synchronized (mStartLock) { + if (mInitListener != null) { + mInitListener.onInit(result); + mInitListener = null; + } + } + } /** * Releases the resources used by the TextToSpeech engine. @@ -536,15 +537,17 @@ public class TextToSpeech { * so the TextToSpeech engine can be cleanly stopped. */ public void shutdown() { - try { - mContext.unbindService(mServiceConnection); - } catch (IllegalArgumentException e) { - // Do nothing and fail silently since an error here indicates that - // binding never succeeded in the first place. - } + runActionNoReconnect(new Action<Void>() { + @Override + public Void run(ITextToSpeechService service) throws RemoteException { + service.setCallback(getPackageName(), null); + service.stop(getPackageName()); + mServiceConnection.disconnect(); + return null; + } + }, null, "shutdown"); } - /** * Adds a mapping between a string of text and a sound resource in a * package. After a call to this method, subsequent calls to @@ -573,21 +576,9 @@ public class TextToSpeech { * @return Code indicating success or failure. See {@link #ERROR} and {@link #SUCCESS}. */ public int addSpeech(String text, String packagename, int resourceId) { - synchronized(mStartLock) { - if (!mStarted) { - return ERROR; - } - try { - mITts.addSpeech(mPackageName, text, packagename, resourceId); - return SUCCESS; - } catch (RemoteException e) { - restart("addSpeech", e); - } catch (NullPointerException e) { - restart("addSpeech", e); - } catch (IllegalStateException e) { - restart("addSpeech", e); - } - return ERROR; + synchronized (mStartLock) { + mUtterances.put(text, makeResourceUri(packagename, resourceId)); + return SUCCESS; } } @@ -608,20 +599,8 @@ public class TextToSpeech { */ public int addSpeech(String text, String filename) { synchronized (mStartLock) { - if (!mStarted) { - return ERROR; - } - try { - mITts.addSpeechFile(mPackageName, text, filename); - return SUCCESS; - } catch (RemoteException e) { - restart("addSpeech", e); - } catch (NullPointerException e) { - restart("addSpeech", e); - } catch (IllegalStateException e) { - restart("addSpeech", e); - } - return ERROR; + mUtterances.put(text, Uri.parse(filename)); + return SUCCESS; } } @@ -653,24 +632,11 @@ public class TextToSpeech { */ public int addEarcon(String earcon, String packagename, int resourceId) { synchronized(mStartLock) { - if (!mStarted) { - return ERROR; - } - try { - mITts.addEarcon(mPackageName, earcon, packagename, resourceId); - return SUCCESS; - } catch (RemoteException e) { - restart("addEarcon", e); - } catch (NullPointerException e) { - restart("addEarcon", e); - } catch (IllegalStateException e) { - restart("addEarcon", e); - } - return ERROR; + mEarcons.put(earcon, makeResourceUri(packagename, resourceId)); + return SUCCESS; } } - /** * Adds a mapping between a string of text and a sound file. * Use this to add custom earcons. @@ -687,312 +653,199 @@ public class TextToSpeech { * @return Code indicating success or failure. See {@link #ERROR} and {@link #SUCCESS}. */ public int addEarcon(String earcon, String filename) { - synchronized (mStartLock) { - if (!mStarted) { - return ERROR; - } - try { - mITts.addEarconFile(mPackageName, earcon, filename); - return SUCCESS; - } catch (RemoteException e) { - restart("addEarcon", e); - } catch (NullPointerException e) { - restart("addEarcon", e); - } catch (IllegalStateException e) { - restart("addEarcon", e); - } - return ERROR; + synchronized(mStartLock) { + mEarcons.put(earcon, Uri.parse(filename)); + return SUCCESS; } } + private Uri makeResourceUri(String packageName, int resourceId) { + return new Uri.Builder() + .scheme(ContentResolver.SCHEME_ANDROID_RESOURCE) + .encodedAuthority(packageName) + .appendEncodedPath(String.valueOf(resourceId)) + .build(); + } /** * Speaks the string using the specified queuing strategy and speech * parameters. * - * @param text - * The string of text to be spoken. - * @param queueMode - * The queuing strategy to use. - * {@link #QUEUE_ADD} or {@link #QUEUE_FLUSH}. - * @param params - * The list of parameters to be used. Can be null if no parameters are given. - * They are specified using a (key, value) pair, where the key can be - * {@link Engine#KEY_PARAM_STREAM} or - * {@link Engine#KEY_PARAM_UTTERANCE_ID}. + * @param text The string of text to be spoken. + * @param queueMode The queuing strategy to use, {@link #QUEUE_ADD} or {@link #QUEUE_FLUSH}. + * @param params Parameters for the request. Can be null. + * Supported parameter names: + * {@link Engine#KEY_PARAM_STREAM}, + * {@link Engine#KEY_PARAM_UTTERANCE_ID}, + * {@link Engine#KEY_PARAM_VOLUME}, + * {@link Engine#KEY_PARAM_PAN}. * - * @return Code indicating success or failure. See {@link #ERROR} and {@link #SUCCESS}. + * @return {@link #ERROR} or {@link #SUCCESS}. */ - public int speak(String text, int queueMode, HashMap<String,String> params) - { - synchronized (mStartLock) { - int result = ERROR; - Log.i("TextToSpeech.java - speak", "speak text of length " + text.length()); - if (!mStarted) { - Log.e("TextToSpeech.java - speak", "service isn't started"); - return result; - } - try { - if ((params != null) && (!params.isEmpty())) { - setCachedParam(params, Engine.KEY_PARAM_STREAM, Engine.PARAM_POSITION_STREAM); - setCachedParam(params, Engine.KEY_PARAM_UTTERANCE_ID, - Engine.PARAM_POSITION_UTTERANCE_ID); - setCachedParam(params, Engine.KEY_PARAM_ENGINE, Engine.PARAM_POSITION_ENGINE); - setCachedParam(params, Engine.KEY_PARAM_VOLUME, Engine.PARAM_POSITION_VOLUME); - setCachedParam(params, Engine.KEY_PARAM_PAN, Engine.PARAM_POSITION_PAN); + public int speak(final String text, final int queueMode, final HashMap<String, String> params) { + return runAction(new Action<Integer>() { + @Override + public Integer run(ITextToSpeechService service) throws RemoteException { + Uri utteranceUri = mUtterances.get(text); + if (utteranceUri != null) { + return service.playAudio(getPackageName(), utteranceUri, queueMode, + getParams(params)); + } else { + return service.speak(getPackageName(), text, queueMode, getParams(params)); } - result = mITts.speak(mPackageName, text, queueMode, mCachedParams); - } catch (RemoteException e) { - restart("speak", e); - } catch (NullPointerException e) { - restart("speak", e); - } catch (IllegalStateException e) { - restart("speak", e); - } finally { - resetCachedParams(); } - return result; - } + }, ERROR, "speak"); } - /** * Plays the earcon using the specified queueing mode and parameters. + * The earcon must already have been added with {@link #addEarcon(String, String)} or + * {@link #addEarcon(String, String, int)}. * - * @param earcon - * The earcon that should be played - * @param queueMode - * {@link #QUEUE_ADD} or {@link #QUEUE_FLUSH}. - * @param params - * The list of parameters to be used. Can be null if no parameters are given. - * They are specified using a (key, value) pair, where the key can be - * {@link Engine#KEY_PARAM_STREAM} or + * @param earcon The earcon that should be played + * @param queueMode {@link #QUEUE_ADD} or {@link #QUEUE_FLUSH}. + * @param params Parameters for the request. Can be null. + * Supported parameter names: + * {@link Engine#KEY_PARAM_STREAM}, * {@link Engine#KEY_PARAM_UTTERANCE_ID}. * - * @return Code indicating success or failure. See {@link #ERROR} and {@link #SUCCESS}. + * @return {@link #ERROR} or {@link #SUCCESS}. */ - public int playEarcon(String earcon, int queueMode, - HashMap<String,String> params) { - synchronized (mStartLock) { - int result = ERROR; - if (!mStarted) { - return result; - } - try { - if ((params != null) && (!params.isEmpty())) { - String extra = params.get(Engine.KEY_PARAM_STREAM); - if (extra != null) { - mCachedParams[Engine.PARAM_POSITION_STREAM + 1] = extra; - } - setCachedParam(params, Engine.KEY_PARAM_STREAM, Engine.PARAM_POSITION_STREAM); - setCachedParam(params, Engine.KEY_PARAM_UTTERANCE_ID, - Engine.PARAM_POSITION_UTTERANCE_ID); + public int playEarcon(final String earcon, final int queueMode, + final HashMap<String, String> params) { + return runAction(new Action<Integer>() { + @Override + public Integer run(ITextToSpeechService service) throws RemoteException { + Uri earconUri = mEarcons.get(earcon); + if (earconUri == null) { + return ERROR; } - result = mITts.playEarcon(mPackageName, earcon, queueMode, null); - } catch (RemoteException e) { - restart("playEarcon", e); - } catch (NullPointerException e) { - restart("playEarcon", e); - } catch (IllegalStateException e) { - restart("playEarcon", e); - } finally { - resetCachedParams(); + return service.playAudio(getPackageName(), earconUri, queueMode, + getParams(params)); } - return result; - } + }, ERROR, "playEarcon"); } /** * Plays silence for the specified amount of time using the specified * queue mode. * - * @param durationInMs - * A long that indicates how long the silence should last. - * @param queueMode - * {@link #QUEUE_ADD} or {@link #QUEUE_FLUSH}. - * @param params - * The list of parameters to be used. Can be null if no parameters are given. - * They are specified using a (key, value) pair, where the key can be + * @param durationInMs The duration of the silence. + * @param queueMode {@link #QUEUE_ADD} or {@link #QUEUE_FLUSH}. + * @param params Parameters for the request. Can be null. + * Supported parameter names: * {@link Engine#KEY_PARAM_UTTERANCE_ID}. * - * @return Code indicating success or failure. See {@link #ERROR} and {@link #SUCCESS}. + * @return {@link #ERROR} or {@link #SUCCESS}. */ - public int playSilence(long durationInMs, int queueMode, HashMap<String,String> params) { - synchronized (mStartLock) { - int result = ERROR; - if (!mStarted) { - return result; - } - try { - if ((params != null) && (!params.isEmpty())) { - setCachedParam(params, Engine.KEY_PARAM_UTTERANCE_ID, - Engine.PARAM_POSITION_UTTERANCE_ID); - } - result = mITts.playSilence(mPackageName, durationInMs, queueMode, mCachedParams); - } catch (RemoteException e) { - restart("playSilence", e); - } catch (NullPointerException e) { - restart("playSilence", e); - } catch (IllegalStateException e) { - restart("playSilence", e); - } finally { - resetCachedParams(); + public int playSilence(final long durationInMs, final int queueMode, + final HashMap<String, String> params) { + return runAction(new Action<Integer>() { + @Override + public Integer run(ITextToSpeechService service) throws RemoteException { + return service.playSilence(getPackageName(), durationInMs, queueMode, + getParams(params)); } - return result; - } + }, ERROR, "playSilence"); } - /** - * Returns whether or not the TextToSpeech engine is busy speaking. + * Checks whether the TTS engine is busy speaking. * - * @return Whether or not the TextToSpeech engine is busy speaking. + * @return {@code true} if the TTS engine is speaking. */ public boolean isSpeaking() { - synchronized (mStartLock) { - if (!mStarted) { - return false; - } - try { - return mITts.isSpeaking(); - } catch (RemoteException e) { - restart("isSpeaking", e); - } catch (NullPointerException e) { - restart("isSpeaking", e); - } catch (IllegalStateException e) { - restart("isSpeaking", e); + return runAction(new Action<Boolean>() { + @Override + public Boolean run(ITextToSpeechService service) throws RemoteException { + return service.isSpeaking(); } - return false; - } + }, false, "isSpeaking"); } - /** * Interrupts the current utterance (whether played or rendered to file) and discards other * utterances in the queue. * - * @return Code indicating success or failure. See {@link #ERROR} and {@link #SUCCESS}. + * @return {@link #ERROR} or {@link #SUCCESS}. */ public int stop() { - synchronized (mStartLock) { - int result = ERROR; - if (!mStarted) { - return result; + return runAction(new Action<Integer>() { + @Override + public Integer run(ITextToSpeechService service) throws RemoteException { + return service.stop(getPackageName()); } - try { - result = mITts.stop(mPackageName); - } catch (RemoteException e) { - restart("stop", e); - } catch (NullPointerException e) { - restart("stop", e); - } catch (IllegalStateException e) { - restart("stop", e); - } - return result; - } + }, ERROR, "stop"); } - /** - * Sets the speech rate for the TextToSpeech engine. + * Sets the speech rate. * * This has no effect on any pre-recorded speech. * - * @param speechRate - * The speech rate for the TextToSpeech engine. 1 is the normal speed, - * lower values slow down the speech (0.5 is half the normal speech rate), - * greater values accelerate it (2 is twice the normal speech rate). + * @param speechRate Speech rate. {@code 1.0} is the normal speech rate, + * lower values slow down the speech ({@code 0.5} is half the normal speech rate), + * greater values accelerate it ({@code 2.0} is twice the normal speech rate). * - * @return Code indicating success or failure. See {@link #ERROR} and {@link #SUCCESS}. + * @return {@link #ERROR} or {@link #SUCCESS}. */ public int setSpeechRate(float speechRate) { - synchronized (mStartLock) { - int result = ERROR; - if (!mStarted) { - return result; - } - try { - if (speechRate > 0) { - int rate = (int)(speechRate*100); - mCachedParams[Engine.PARAM_POSITION_RATE + 1] = String.valueOf(rate); - // the rate is not set here, instead it is cached so it will be associated - // with all upcoming utterances. - if (speechRate > 0.0f) { - result = SUCCESS; - } else { - result = ERROR; - } + if (speechRate > 0.0f) { + int intRate = (int)(speechRate * 100); + if (intRate > 0) { + synchronized (mStartLock) { + mParams.putInt(Engine.KEY_PARAM_RATE, intRate); } - } catch (NullPointerException e) { - restart("setSpeechRate", e); - } catch (IllegalStateException e) { - restart("setSpeechRate", e); + return SUCCESS; } - return result; } + return ERROR; } - /** * Sets the speech pitch for the TextToSpeech engine. * * This has no effect on any pre-recorded speech. * - * @param pitch - * The pitch for the TextToSpeech engine. 1 is the normal pitch, + * @param pitch Speech pitch. {@code 1.0} is the normal pitch, * lower values lower the tone of the synthesized voice, * greater values increase it. * - * @return Code indicating success or failure. See {@link #ERROR} and {@link #SUCCESS}. + * @return {@link #ERROR} or {@link #SUCCESS}. */ public int setPitch(float pitch) { - synchronized (mStartLock) { - int result = ERROR; - if (!mStarted) { - return result; - } - try { - // the pitch is not set here, instead it is cached so it will be associated - // with all upcoming utterances. - if (pitch > 0) { - int p = (int)(pitch*100); - mCachedParams[Engine.PARAM_POSITION_PITCH + 1] = String.valueOf(p); - result = SUCCESS; + if (pitch > 0.0f) { + int intPitch = (int)(pitch * 100); + if (intPitch > 0) { + synchronized (mStartLock) { + mParams.putInt(Engine.KEY_PARAM_PITCH, intPitch); } - } catch (NullPointerException e) { - restart("setPitch", e); - } catch (IllegalStateException e) { - restart("setPitch", e); + return SUCCESS; } - return result; } + return ERROR; } - /** - * Sets the language for the TextToSpeech engine. - * The TextToSpeech engine will try to use the closest match to the specified + * Sets the text-to-speech language. + * The TTS engine will try to use the closest match to the specified * language as represented by the Locale, but there is no guarantee that the exact same Locale * will be used. Use {@link #isLanguageAvailable(Locale)} to check the level of support * before choosing the language to use for the next utterances. * - * @param loc - * The locale describing the language to be used. + * @param loc The locale describing the language to be used. * - * @return code indicating the support status for the locale. See {@link #LANG_AVAILABLE}, + * @return Code indicating the support status for the locale. See {@link #LANG_AVAILABLE}, * {@link #LANG_COUNTRY_AVAILABLE}, {@link #LANG_COUNTRY_VAR_AVAILABLE}, * {@link #LANG_MISSING_DATA} and {@link #LANG_NOT_SUPPORTED}. */ - public int setLanguage(Locale loc) { - synchronized (mStartLock) { - int result = LANG_NOT_SUPPORTED; - if (!mStarted) { - return result; - } - if (loc == null) { - return result; - } - try { + public int setLanguage(final Locale loc) { + return runAction(new Action<Integer>() { + @Override + public Integer run(ITextToSpeechService service) throws RemoteException { + if (loc == null) { + return LANG_NOT_SUPPORTED; + } String language = loc.getISO3Language(); String country = loc.getISO3Country(); String variant = loc.getVariant(); @@ -1000,294 +853,316 @@ public class TextToSpeech { // the available parts. // Note that the language is not actually set here, instead it is cached so it // will be associated with all upcoming utterances. - result = mITts.isLanguageAvailable(language, country, variant, mCachedParams); + int result = service.loadLanguage(language, country, variant); if (result >= LANG_AVAILABLE){ - mCachedParams[Engine.PARAM_POSITION_LANGUAGE + 1] = language; - if (result >= LANG_COUNTRY_AVAILABLE){ - mCachedParams[Engine.PARAM_POSITION_COUNTRY + 1] = country; - } else { - mCachedParams[Engine.PARAM_POSITION_COUNTRY + 1] = ""; - } - if (result >= LANG_COUNTRY_VAR_AVAILABLE){ - mCachedParams[Engine.PARAM_POSITION_VARIANT + 1] = variant; - } else { - mCachedParams[Engine.PARAM_POSITION_VARIANT + 1] = ""; + if (result < LANG_COUNTRY_VAR_AVAILABLE) { + variant = ""; + if (result < LANG_COUNTRY_AVAILABLE) { + country = ""; + } } + mParams.putString(Engine.KEY_PARAM_LANGUAGE, language); + mParams.putString(Engine.KEY_PARAM_COUNTRY, country); + mParams.putString(Engine.KEY_PARAM_VARIANT, variant); } - } catch (RemoteException e) { - restart("setLanguage", e); - } catch (NullPointerException e) { - restart("setLanguage", e); - } catch (IllegalStateException e) { - restart("setLanguage", e); + return result; } - return result; - } + }, LANG_NOT_SUPPORTED, "setLanguage"); } - /** * Returns a Locale instance describing the language currently being used by the TextToSpeech * engine. + * * @return language, country (if any) and variant (if any) used by the engine stored in a Locale - * instance, or null is the TextToSpeech engine has failed. + * instance, or {@code null} on error. */ public Locale getLanguage() { - synchronized (mStartLock) { - if (!mStarted) { - return null; - } - try { - // Only do a call to the native synth if there is nothing in the cached params - if (mCachedParams[Engine.PARAM_POSITION_LANGUAGE + 1].length() < 1){ - String[] locStrings = mITts.getLanguage(); - if ((locStrings != null) && (locStrings.length == 3)) { - return new Locale(locStrings[0], locStrings[1], locStrings[2]); - } else { - return null; - } - } else { - return new Locale(mCachedParams[Engine.PARAM_POSITION_LANGUAGE + 1], - mCachedParams[Engine.PARAM_POSITION_COUNTRY + 1], - mCachedParams[Engine.PARAM_POSITION_VARIANT + 1]); + return runAction(new Action<Locale>() { + @Override + public Locale run(ITextToSpeechService service) throws RemoteException { + String[] locStrings = service.getLanguage(); + if (locStrings != null && locStrings.length == 3) { + return new Locale(locStrings[0], locStrings[1], locStrings[2]); } - } catch (RemoteException e) { - restart("getLanguage", e); - } catch (NullPointerException e) { - restart("getLanguage", e); - } catch (IllegalStateException e) { - restart("getLanguage", e); + return null; } - return null; - } + }, null, "getLanguage"); } /** * Checks if the specified language as represented by the Locale is available and supported. * - * @param loc - * The Locale describing the language to be used. + * @param loc The Locale describing the language to be used. * - * @return code indicating the support status for the locale. See {@link #LANG_AVAILABLE}, + * @return Code indicating the support status for the locale. See {@link #LANG_AVAILABLE}, * {@link #LANG_COUNTRY_AVAILABLE}, {@link #LANG_COUNTRY_VAR_AVAILABLE}, * {@link #LANG_MISSING_DATA} and {@link #LANG_NOT_SUPPORTED}. */ - public int isLanguageAvailable(Locale loc) { - synchronized (mStartLock) { - int result = LANG_NOT_SUPPORTED; - if (!mStarted) { - return result; + public int isLanguageAvailable(final Locale loc) { + return runAction(new Action<Integer>() { + @Override + public Integer run(ITextToSpeechService service) throws RemoteException { + return service.isLanguageAvailable(loc.getISO3Language(), + loc.getISO3Country(), loc.getVariant()); } - try { - result = mITts.isLanguageAvailable(loc.getISO3Language(), - loc.getISO3Country(), loc.getVariant(), mCachedParams); - } catch (RemoteException e) { - restart("isLanguageAvailable", e); - } catch (NullPointerException e) { - restart("isLanguageAvailable", e); - } catch (IllegalStateException e) { - restart("isLanguageAvailable", e); - } - return result; - } + }, LANG_NOT_SUPPORTED, "isLanguageAvailable"); } - /** * Synthesizes the given text to a file using the specified parameters. * - * @param text - * The String of text that should be synthesized - * @param params - * The list of parameters to be used. Can be null if no parameters are given. - * They are specified using a (key, value) pair, where the key can be + * @param text Thetext that should be synthesized + * @param params Parameters for the request. Can be null. + * Supported parameter names: * {@link Engine#KEY_PARAM_UTTERANCE_ID}. - * @param filename - * The string that gives the full output filename; it should be + * @param filename Absolute file filename to write the generated audio data to.It should be * something like "/sdcard/myappsounds/mysound.wav". * - * @return Code indicating success or failure. See {@link #ERROR} and {@link #SUCCESS}. + * @return {@link #ERROR} or {@link #SUCCESS}. */ - public int synthesizeToFile(String text, HashMap<String,String> params, - String filename) { - Log.i("TextToSpeech.java", "synthesizeToFile()"); - synchronized (mStartLock) { - int result = ERROR; - Log.i("TextToSpeech.java - synthesizeToFile", "synthesizeToFile text of length " - + text.length()); - if (!mStarted) { - Log.e("TextToSpeech.java - synthesizeToFile", "service isn't started"); - return result; + public int synthesizeToFile(final String text, final HashMap<String, String> params, + final String filename) { + return runAction(new Action<Integer>() { + @Override + public Integer run(ITextToSpeechService service) throws RemoteException { + return service.synthesizeToFile(getPackageName(), text, filename, + getParams(params)); } - try { - if ((params != null) && (!params.isEmpty())) { - // no need to read the stream type here - setCachedParam(params, Engine.KEY_PARAM_UTTERANCE_ID, - Engine.PARAM_POSITION_UTTERANCE_ID); - setCachedParam(params, Engine.KEY_PARAM_ENGINE, Engine.PARAM_POSITION_ENGINE); - } - result = mITts.synthesizeToFile(mPackageName, text, mCachedParams, filename) ? - SUCCESS : ERROR; - } catch (RemoteException e) { - restart("synthesizeToFile", e); - } catch (NullPointerException e) { - restart("synthesizeToFile", e); - } catch (IllegalStateException e) { - restart("synthesizeToFile", e); - } finally { - resetCachedParams(); - } - return result; + }, ERROR, "synthesizeToFile"); + } + + private Bundle getParams(HashMap<String, String> params) { + if (params != null && !params.isEmpty()) { + Bundle bundle = new Bundle(mParams); + copyIntParam(bundle, params, Engine.KEY_PARAM_STREAM); + copyStringParam(bundle, params, Engine.KEY_PARAM_UTTERANCE_ID); + copyFloatParam(bundle, params, Engine.KEY_PARAM_VOLUME); + copyFloatParam(bundle, params, Engine.KEY_PARAM_PAN); + return bundle; + } else { + return mParams; } } + private void copyStringParam(Bundle bundle, HashMap<String, String> params, String key) { + String value = params.get(key); + if (value != null) { + bundle.putString(key, value); + } + } - /** - * Convenience method to reset the cached parameters to the current default values - * if they are not persistent between calls to the service. - */ - private void resetCachedParams() { - mCachedParams[Engine.PARAM_POSITION_STREAM + 1] = - String.valueOf(Engine.DEFAULT_STREAM); - mCachedParams[Engine.PARAM_POSITION_UTTERANCE_ID+ 1] = ""; - mCachedParams[Engine.PARAM_POSITION_VOLUME + 1] = Engine.DEFAULT_VOLUME_STRING; - mCachedParams[Engine.PARAM_POSITION_PAN + 1] = Engine.DEFAULT_PAN_STRING; + private void copyIntParam(Bundle bundle, HashMap<String, String> params, String key) { + String valueString = params.get(key); + if (!TextUtils.isEmpty(valueString)) { + try { + int value = Integer.parseInt(valueString); + bundle.putInt(key, value); + } catch (NumberFormatException ex) { + // don't set the value in the bundle + } + } } - /** - * Convenience method to save a parameter in the cached parameter array, at the given index, - * for a property saved in the given hashmap. - */ - private void setCachedParam(HashMap<String,String> params, String key, int keyIndex) { - String extra = params.get(key); - if (extra != null) { - mCachedParams[keyIndex+1] = extra; + private void copyFloatParam(Bundle bundle, HashMap<String, String> params, String key) { + String valueString = params.get(key); + if (!TextUtils.isEmpty(valueString)) { + try { + float value = Float.parseFloat(valueString); + bundle.putFloat(key, value); + } catch (NumberFormatException ex) { + // don't set the value in the bundle + } } } /** - * Sets the OnUtteranceCompletedListener that will fire when an utterance completes. + * Sets the listener that will be notified when synthesis of an utterance completes. * - * @param listener - * The OnUtteranceCompletedListener + * @param listener The listener to use. * - * @return Code indicating success or failure. See {@link #ERROR} and {@link #SUCCESS}. + * @return {@link #ERROR} or {@link #SUCCESS}. */ - public int setOnUtteranceCompletedListener( - final OnUtteranceCompletedListener listener) { - synchronized (mStartLock) { - int result = ERROR; - if (!mStarted) { - return result; - } - mITtscallback = new ITtsCallback.Stub() { - public void utteranceCompleted(String utteranceId) throws RemoteException { - if (listener != null) { - listener.onUtteranceCompleted(utteranceId); + public int setOnUtteranceCompletedListener(final OnUtteranceCompletedListener listener) { + return runAction(new Action<Integer>() { + @Override + public Integer run(ITextToSpeechService service) throws RemoteException { + ITextToSpeechCallback.Stub callback = new ITextToSpeechCallback.Stub() { + public void utteranceCompleted(String utteranceId) { + if (listener != null) { + listener.onUtteranceCompleted(utteranceId); + } } - } - }; - try { - result = mITts.registerCallback(mPackageName, mITtscallback); - } catch (RemoteException e) { - restart("registerCallback", e); - } catch (NullPointerException e) { - restart("registerCallback", e); - } catch (IllegalStateException e) { - restart("registerCallback", e); + }; + service.setCallback(getPackageName(), callback); + return SUCCESS; } - return result; - } + }, ERROR, "setOnUtteranceCompletedListener"); } /** - * Sets the speech synthesis engine to be used by its packagename. + * Sets the TTS engine to use. * - * @param enginePackageName - * The packagename for the synthesis engine (ie, "com.svox.pico") + * @param enginePackageName The package name for the synthesis engine (e.g. "com.svox.pico") * - * @return Code indicating success or failure. See {@link #ERROR} and {@link #SUCCESS}. + * @return {@link #ERROR} or {@link #SUCCESS}. */ + // TODO: add @Deprecated{This method does not tell the caller when the new engine + // has been initialized. You should create a new TextToSpeech object with the new + // engine instead.} public int setEngineByPackageName(String enginePackageName) { - synchronized (mStartLock) { - int result = TextToSpeech.ERROR; - if (!mStarted) { - return result; - } - try { - result = mITts.setEngineByPackageName(enginePackageName); - if (result == TextToSpeech.SUCCESS){ - mCachedParams[Engine.PARAM_POSITION_ENGINE + 1] = enginePackageName; - } - } catch (RemoteException e) { - restart("setEngineByPackageName", e); - } catch (NullPointerException e) { - restart("setEngineByPackageName", e); - } catch (IllegalStateException e) { - restart("setEngineByPackageName", e); - } - return result; - } + mRequestedEngine = enginePackageName; + return initTts(); } - /** - * Gets the packagename of the default speech synthesis engine. + * Gets the package name of the default speech synthesis engine. * - * @return Packagename of the TTS engine that the user has chosen as their default. + * @return Package name of the TTS engine that the user has chosen as their default. */ public String getDefaultEngine() { - synchronized (mStartLock) { - String engineName = ""; - if (!mStarted) { - return engineName; - } - try { - engineName = mITts.getDefaultEngine(); - } catch (RemoteException e) { - restart("getDefaultEngine", e); - } catch (NullPointerException e) { - restart("getDefaultEngine", e); - } catch (IllegalStateException e) { - restart("getDefaultEngine", e); + String engine = Settings.Secure.getString(mContext.getContentResolver(), + Settings.Secure.TTS_DEFAULT_SYNTH); + return engine != null ? engine : Engine.DEFAULT_ENGINE; + } + + /** + * Checks whether the user's settings should override settings requested by the calling + * application. + */ + public boolean areDefaultsEnforced() { + return Settings.Secure.getInt(mContext.getContentResolver(), + Settings.Secure.TTS_USE_DEFAULTS, Engine.USE_DEFAULTS) == 1; + } + + private boolean isEngineEnabled(String engine) { + if (Engine.DEFAULT_ENGINE.equals(engine)) { + return true; + } + for (String enabled : getEnabledEngines()) { + if (engine.equals(enabled)) { + return true; } - return engineName; } + return false; } + private String[] getEnabledEngines() { + String str = Settings.Secure.getString(mContext.getContentResolver(), + Settings.Secure.TTS_ENABLED_PLUGINS); + if (TextUtils.isEmpty(str)) { + return new String[0]; + } + return str.split(" "); + } /** - * Returns whether or not the user is forcing their defaults to override the - * Text-To-Speech settings set by applications. + * Gets a list of all installed TTS engines. * - * @return Whether or not defaults are enforced. + * @return A list of engine info objects. The list can be empty, but will never by {@code null}. + * + * @hide Pending approval */ - public boolean areDefaultsEnforced() { - synchronized (mStartLock) { - boolean defaultsEnforced = false; - if (!mStarted) { - return defaultsEnforced; + public List<EngineInfo> getEngines() { + PackageManager pm = mContext.getPackageManager(); + Intent intent = new Intent(Engine.INTENT_ACTION_TTS_SERVICE); + List<ResolveInfo> resolveInfos = pm.queryIntentServices(intent, 0); + if (resolveInfos == null) return Collections.emptyList(); + List<EngineInfo> engines = new ArrayList<EngineInfo>(resolveInfos.size()); + for (ResolveInfo resolveInfo : resolveInfos) { + ServiceInfo service = resolveInfo.serviceInfo; + if (service != null) { + EngineInfo engine = new EngineInfo(); + // Using just the package name isn't great, since it disallows having + // multiple engines in the same package, but that's what the existing API does. + engine.name = service.packageName; + CharSequence label = service.loadLabel(pm); + engine.label = TextUtils.isEmpty(label) ? engine.name : label.toString(); + engine.icon = service.getIconResource(); + engines.add(engine); } + } + return engines; + } + + private class Connection implements ServiceConnection { + private ITextToSpeechService mService; + + public void onServiceConnected(ComponentName name, IBinder service) { + Log.i(TAG, "Connected to " + name); + synchronized(mStartLock) { + if (mServiceConnection != null) { + // Disconnect any previous service connection + mServiceConnection.disconnect(); + } + mServiceConnection = this; + mService = ITextToSpeechService.Stub.asInterface(service); + dispatchOnInit(SUCCESS); + } + } + + public void onServiceDisconnected(ComponentName name) { + synchronized(mStartLock) { + mService = null; + // If this is the active connection, clear it + if (mServiceConnection == this) { + mServiceConnection = null; + } + } + } + + public void disconnect() { + mContext.unbindService(this); + } + + public <R> R runAction(Action<R> action, R errorResult, String method, boolean reconnect) { try { - defaultsEnforced = mITts.areDefaultsEnforced(); - } catch (RemoteException e) { - restart("areDefaultsEnforced", e); - } catch (NullPointerException e) { - restart("areDefaultsEnforced", e); - } catch (IllegalStateException e) { - restart("areDefaultsEnforced", e); + synchronized (mStartLock) { + if (mService == null) { + Log.w(TAG, method + " failed: not connected to TTS engine"); + return errorResult; + } + return action.run(mService); + } + } catch (RemoteException ex) { + Log.e(TAG, method + " failed", ex); + if (reconnect) { + disconnect(); + initTts(); + } + return errorResult; } - return defaultsEnforced; } } + private interface Action<R> { + R run(ITextToSpeechService service) throws RemoteException; + } + /** - * Restarts the TTS after a failure. + * Information about an installed text-to-speech engine. + * + * @see TextToSpeech#getEngines + * @hide Pending approval */ - private void restart(String method, Exception e) { - // TTS died; restart it. - Log.e(TAG, method, e); - mStarted = false; - initTts(); + public static class EngineInfo { + /** + * Engine package name.. + */ + public String name; + /** + * Localized label for the engine. + */ + public String label; + /** + * Icon for the engine. + */ + public int icon; + + @Override + public String toString() { + return "EngineInfo{name=" + name + "}"; + } + } } diff --git a/core/java/android/speech/tts/TextToSpeechService.java b/core/java/android/speech/tts/TextToSpeechService.java new file mode 100644 index 0000000..a408ea2 --- /dev/null +++ b/core/java/android/speech/tts/TextToSpeechService.java @@ -0,0 +1,715 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * 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.speech.tts; + +import android.app.Service; +import android.content.Intent; +import android.net.Uri; +import android.os.Bundle; +import android.os.ConditionVariable; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.IBinder; +import android.os.Looper; +import android.os.Message; +import android.os.MessageQueue; +import android.os.RemoteCallbackList; +import android.os.RemoteException; +import android.provider.Settings; +import android.speech.tts.TextToSpeech.Engine; +import android.text.TextUtils; +import android.util.Log; + +import java.io.File; +import java.io.IOException; +import java.util.HashMap; +import java.util.Locale; + + +/** + * Abstract base class for TTS engine implementations. + * + * @hide Pending approval + */ +public abstract class TextToSpeechService extends Service { + + private static final boolean DBG = false; + private static final String TAG = "TextToSpeechService"; + + private static final int MAX_SPEECH_ITEM_CHAR_LENGTH = 4000; + private static final String SYNTH_THREAD_NAME = "SynthThread"; + + private SynthHandler mSynthHandler; + + private CallbackMap mCallbacks; + + @Override + public void onCreate() { + if (DBG) Log.d(TAG, "onCreate()"); + super.onCreate(); + + SynthThread synthThread = new SynthThread(); + synthThread.start(); + mSynthHandler = new SynthHandler(synthThread.getLooper()); + + mCallbacks = new CallbackMap(); + + // Load default language + onLoadLanguage(getDefaultLanguage(), getDefaultCountry(), getDefaultVariant()); + } + + @Override + public void onDestroy() { + if (DBG) Log.d(TAG, "onDestroy()"); + + // Tell the synthesizer to stop + mSynthHandler.quit(); + + // Unregister all callbacks. + mCallbacks.kill(); + + super.onDestroy(); + } + + /** + * Checks whether the engine supports a given language. + * + * Can be called on multiple threads. + * + * @param lang ISO-3 language code. + * @param country ISO-3 country code. May be empty or null. + * @param variant Language variant. May be empty or null. + * @return Code indicating the support status for the locale. + * One of {@link TextToSpeech#LANG_AVAILABLE}, + * {@link TextToSpeech#LANG_COUNTRY_AVAILABLE}, + * {@link TextToSpeech#LANG_COUNTRY_VAR_AVAILABLE}, + * {@link TextToSpeech#LANG_MISSING_DATA} + * {@link TextToSpeech#LANG_NOT_SUPPORTED}. + */ + protected abstract int onIsLanguageAvailable(String lang, String country, String variant); + + /** + * Returns the language, country and variant currently being used by the TTS engine. + * + * Can be called on multiple threads. + * + * @return A 3-element array, containing language (ISO 3-letter code), + * country (ISO 3-letter code) and variant used by the engine. + * The country and variant may be {@code ""}. If country is empty, then variant must + * be empty too. + * @see Locale#getISO3Language() + * @see Locale#getISO3Country() + * @see Locale#getVariant() + */ + protected abstract String[] onGetLanguage(); + + /** + * Notifies the engine that it should load a speech synthesis language. There is no guarantee + * that this method is always called before the language is used for synthesis. It is merely + * a hint to the engine that it will probably get some synthesis requests for this language + * at some point in the future. + * + * Can be called on multiple threads. + * + * @param lang ISO-3 language code. + * @param country ISO-3 country code. May be empty or null. + * @param variant Language variant. May be empty or null. + * @return Code indicating the support status for the locale. + * One of {@link TextToSpeech#LANG_AVAILABLE}, + * {@link TextToSpeech#LANG_COUNTRY_AVAILABLE}, + * {@link TextToSpeech#LANG_COUNTRY_VAR_AVAILABLE}, + * {@link TextToSpeech#LANG_MISSING_DATA} + * {@link TextToSpeech#LANG_NOT_SUPPORTED}. + */ + protected abstract int onLoadLanguage(String lang, String country, String variant); + + /** + * Notifies the service that it should stop any in-progress speech synthesis. + * This method can be called even if no speech synthesis is currently in progress. + * + * Can be called on multiple threads, but not on the synthesis thread. + */ + protected abstract void onStop(); + + /** + * Tells the service to synthesize speech from the given text. This method should + * block until the synthesis is finished. + * + * Called on the synthesis thread. + * + * @param request The synthesis request. The method should + * call {@link SynthesisRequest#start}, {@link SynthesisRequest#audioAvailable}, + * and {@link SynthesisRequest#done} on this request. + * @return {@link TextToSpeech#SUCCESS} or {@link TextToSpeech#ERROR}. + */ + protected abstract int onSynthesizeText(SynthesisRequest request); + + private boolean areDefaultsEnforced() { + return getSecureSettingInt(Settings.Secure.TTS_USE_DEFAULTS, + TextToSpeech.Engine.USE_DEFAULTS) == 1; + } + + private int getDefaultSpeechRate() { + return getSecureSettingInt(Settings.Secure.TTS_DEFAULT_RATE, Engine.DEFAULT_RATE); + } + + private String getDefaultLanguage() { + return getSecureSettingString(Settings.Secure.TTS_DEFAULT_LANG, + Locale.getDefault().getISO3Language()); + } + + private String getDefaultCountry() { + return getSecureSettingString(Settings.Secure.TTS_DEFAULT_COUNTRY, + Locale.getDefault().getISO3Country()); + } + + private String getDefaultVariant() { + return getSecureSettingString(Settings.Secure.TTS_DEFAULT_VARIANT, + Locale.getDefault().getVariant()); + } + + private int getSecureSettingInt(String name, int defaultValue) { + return Settings.Secure.getInt(getContentResolver(), name, defaultValue); + } + + private String getSecureSettingString(String name, String defaultValue) { + String value = Settings.Secure.getString(getContentResolver(), name); + return value != null ? value : defaultValue; + } + + /** + * Synthesizer thread. This thread is used to run {@link SynthHandler}. + */ + private class SynthThread extends HandlerThread implements MessageQueue.IdleHandler { + + private boolean mFirstIdle = true; + + public SynthThread() { + super(SYNTH_THREAD_NAME, android.os.Process.THREAD_PRIORITY_AUDIO); + } + + @Override + protected void onLooperPrepared() { + getLooper().getQueue().addIdleHandler(this); + } + + @Override + public boolean queueIdle() { + if (mFirstIdle) { + mFirstIdle = false; + } else { + broadcastTtsQueueProcessingCompleted(); + } + return true; + } + + private void broadcastTtsQueueProcessingCompleted() { + Intent i = new Intent(TextToSpeech.ACTION_TTS_QUEUE_PROCESSING_COMPLETED); + if (DBG) Log.d(TAG, "Broadcasting: " + i); + sendBroadcast(i); + } + } + + private class SynthHandler extends Handler { + + private SpeechItem mCurrentSpeechItem = null; + + public SynthHandler(Looper looper) { + super(looper); + } + + private void dispatchUtteranceCompleted(SpeechItem item) { + String utteranceId = item.getUtteranceId(); + if (!TextUtils.isEmpty(utteranceId)) { + mCallbacks.dispatchUtteranceCompleted(item.getCallingApp(), utteranceId); + } + } + + private synchronized SpeechItem getCurrentSpeechItem() { + return mCurrentSpeechItem; + } + + private synchronized SpeechItem setCurrentSpeechItem(SpeechItem speechItem) { + SpeechItem old = mCurrentSpeechItem; + mCurrentSpeechItem = speechItem; + return old; + } + + public boolean isSpeaking() { + return getCurrentSpeechItem() != null; + } + + public void quit() { + // Don't process any more speech items + getLooper().quit(); + // Stop the current speech item + SpeechItem current = setCurrentSpeechItem(null); + if (current != null) { + current.stop(); + } + } + + /** + * Adds a speech item to the queue. + * + * Called on a service binder thread. + */ + public int enqueueSpeechItem(int queueMode, final SpeechItem speechItem) { + if (!speechItem.isValid()) { + return TextToSpeech.ERROR; + } + // TODO: The old code also supported the undocumented queueMode == 2, + // which clears out all pending items from the calling app, as well as all + // non-file items from other apps. + if (queueMode == TextToSpeech.QUEUE_FLUSH) { + stop(speechItem.getCallingApp()); + } + Runnable runnable = new Runnable() { + @Override + public void run() { + setCurrentSpeechItem(speechItem); + if (speechItem.play() == TextToSpeech.SUCCESS) { + dispatchUtteranceCompleted(speechItem); + } + setCurrentSpeechItem(null); + } + }; + Message msg = Message.obtain(this, runnable); + // The obj is used to remove all callbacks from the given app in stop(String). + msg.obj = speechItem.getCallingApp(); + if (sendMessage(msg)) { + return TextToSpeech.SUCCESS; + } else { + Log.w(TAG, "SynthThread has quit"); + return TextToSpeech.ERROR; + } + } + + /** + * Stops all speech output and removes any utterances still in the queue for + * the calling app. + * + * Called on a service binder thread. + */ + public int stop(String callingApp) { + if (TextUtils.isEmpty(callingApp)) { + return TextToSpeech.ERROR; + } + removeCallbacksAndMessages(callingApp); + SpeechItem current = setCurrentSpeechItem(null); + if (current != null && TextUtils.equals(callingApp, current.getCallingApp())) { + current.stop(); + } + return TextToSpeech.SUCCESS; + } + } + + /** + * An item in the synth thread queue. + */ + private static abstract class SpeechItem { + private final String mCallingApp; + private final Bundle mParams; + private boolean mStarted = false; + private boolean mStopped = false; + + public SpeechItem(String callingApp, Bundle params) { + mCallingApp = callingApp; + mParams = params; + } + + public String getCallingApp() { + return mCallingApp; + } + + /** + * Checker whether the item is valid. If this method returns false, the item should not + * be played. + */ + public abstract boolean isValid(); + + /** + * Plays the speech item. Blocks until playback is finished. + * Must not be called more than once. + * + * Only called on the synthesis thread. + * + * @return {@link TextToSpeech#SUCCESS} or {@link TextToSpeech#ERROR}. + */ + public int play() { + synchronized (this) { + if (mStarted) { + throw new IllegalStateException("play() called twice"); + } + mStarted = true; + } + return playImpl(); + } + + /** + * Stops the speech item. + * Must not be called more than once. + * + * Can be called on multiple threads, but not on the synthesis thread. + */ + public void stop() { + synchronized (this) { + if (mStopped) { + throw new IllegalStateException("stop() called twice"); + } + mStopped = true; + } + stopImpl(); + } + + protected abstract int playImpl(); + + protected abstract void stopImpl(); + + public int getStreamType() { + return getIntParam(Engine.KEY_PARAM_STREAM, Engine.DEFAULT_STREAM); + } + + public float getVolume() { + return getFloatParam(Engine.KEY_PARAM_VOLUME, Engine.DEFAULT_VOLUME); + } + + public float getPan() { + return getFloatParam(Engine.KEY_PARAM_PAN, Engine.DEFAULT_PAN); + } + + public String getUtteranceId() { + return getStringParam(Engine.KEY_PARAM_UTTERANCE_ID, null); + } + + protected String getStringParam(String key, String defaultValue) { + return mParams == null ? defaultValue : mParams.getString(key, defaultValue); + } + + protected int getIntParam(String key, int defaultValue) { + return mParams == null ? defaultValue : mParams.getInt(key, defaultValue); + } + + protected float getFloatParam(String key, float defaultValue) { + return mParams == null ? defaultValue : mParams.getFloat(key, defaultValue); + } + } + + private class SynthesisSpeechItem extends SpeechItem { + private final String mText; + private SynthesisRequest mSynthesisRequest; + + public SynthesisSpeechItem(String callingApp, Bundle params, String text) { + super(callingApp, params); + mText = text; + } + + public String getText() { + return mText; + } + + @Override + public boolean isValid() { + if (TextUtils.isEmpty(mText)) { + Log.w(TAG, "Got empty text"); + return false; + } + if (mText.length() >= MAX_SPEECH_ITEM_CHAR_LENGTH){ + Log.w(TAG, "Text too long: " + mText.length() + " chars"); + return false; + } + return true; + } + + @Override + protected int playImpl() { + SynthesisRequest synthesisRequest; + synchronized (this) { + mSynthesisRequest = createSynthesisRequest(); + synthesisRequest = mSynthesisRequest; + } + setRequestParams(synthesisRequest); + return TextToSpeechService.this.onSynthesizeText(synthesisRequest); + } + + protected SynthesisRequest createSynthesisRequest() { + return new PlaybackSynthesisRequest(mText, getStreamType(), getVolume(), getPan()); + } + + private void setRequestParams(SynthesisRequest request) { + if (areDefaultsEnforced()) { + request.setLanguage(getDefaultLanguage(), getDefaultCountry(), getDefaultVariant()); + request.setSpeechRate(getDefaultSpeechRate()); + } else { + request.setLanguage(getLanguage(), getCountry(), getVariant()); + request.setSpeechRate(getSpeechRate()); + } + request.setPitch(getPitch()); + } + + @Override + protected void stopImpl() { + SynthesisRequest synthesisRequest; + synchronized (this) { + synthesisRequest = mSynthesisRequest; + } + synthesisRequest.stop(); + TextToSpeechService.this.onStop(); + } + + public String getLanguage() { + return getStringParam(Engine.KEY_PARAM_LANGUAGE, getDefaultLanguage()); + } + + private boolean hasLanguage() { + return !TextUtils.isEmpty(getStringParam(Engine.KEY_PARAM_LANGUAGE, null)); + } + + private String getCountry() { + if (!hasLanguage()) return getDefaultCountry(); + return getStringParam(Engine.KEY_PARAM_COUNTRY, ""); + } + + private String getVariant() { + if (!hasLanguage()) return getDefaultVariant(); + return getStringParam(Engine.KEY_PARAM_VARIANT, ""); + } + + private int getSpeechRate() { + return getIntParam(Engine.KEY_PARAM_RATE, getDefaultSpeechRate()); + } + + private int getPitch() { + return getIntParam(Engine.KEY_PARAM_PITCH, Engine.DEFAULT_PITCH); + } + } + + private class SynthesisToFileSpeechItem extends SynthesisSpeechItem { + private final File mFile; + + public SynthesisToFileSpeechItem(String callingApp, Bundle params, String text, + File file) { + super(callingApp, params, text); + mFile = file; + } + + @Override + public boolean isValid() { + if (!super.isValid()) { + return false; + } + return checkFile(mFile); + } + + @Override + protected SynthesisRequest createSynthesisRequest() { + return new FileSynthesisRequest(getText(), mFile); + } + + /** + * Checks that the given file can be used for synthesis output. + */ + private boolean checkFile(File file) { + try { + if (file.exists()) { + Log.v(TAG, "File " + file + " exists, deleting."); + if (!file.delete()) { + Log.e(TAG, "Failed to delete " + file); + return false; + } + } + if (!file.createNewFile()) { + Log.e(TAG, "Can't create file " + file); + return false; + } + if (!file.delete()) { + Log.e(TAG, "Failed to delete " + file); + return false; + } + return true; + } catch (IOException e) { + Log.e(TAG, "Can't use " + file + " due to exception " + e); + return false; + } + } + } + + private class AudioSpeechItem extends SpeechItem { + + private final BlockingMediaPlayer mPlayer; + + public AudioSpeechItem(String callingApp, Bundle params, Uri uri) { + super(callingApp, params); + mPlayer = new BlockingMediaPlayer(TextToSpeechService.this, uri, getStreamType()); + } + + @Override + public boolean isValid() { + return true; + } + + @Override + protected int playImpl() { + return mPlayer.startAndWait() ? TextToSpeech.SUCCESS : TextToSpeech.ERROR; + } + + @Override + protected void stopImpl() { + mPlayer.stop(); + } + } + + private class SilenceSpeechItem extends SpeechItem { + private final long mDuration; + private final ConditionVariable mDone; + + public SilenceSpeechItem(String callingApp, Bundle params, long duration) { + super(callingApp, params); + mDuration = duration; + mDone = new ConditionVariable(); + } + + @Override + public boolean isValid() { + return true; + } + + @Override + protected int playImpl() { + boolean aborted = mDone.block(mDuration); + return aborted ? TextToSpeech.ERROR : TextToSpeech.SUCCESS; + } + + @Override + protected void stopImpl() { + mDone.open(); + } + } + + @Override + public IBinder onBind(Intent intent) { + if (TextToSpeech.Engine.INTENT_ACTION_TTS_SERVICE.equals(intent.getAction())) { + return mBinder; + } + return null; + } + + /** + * Binder returned from {@code #onBind(Intent)}. The methods in this class can be + * called called from several different threads. + */ + private final ITextToSpeechService.Stub mBinder = new ITextToSpeechService.Stub() { + + public int speak(String callingApp, String text, int queueMode, Bundle params) { + SpeechItem item = new SynthesisSpeechItem(callingApp, params, text); + return mSynthHandler.enqueueSpeechItem(queueMode, item); + } + + public int synthesizeToFile(String callingApp, String text, String filename, + Bundle params) { + File file = new File(filename); + SpeechItem item = new SynthesisToFileSpeechItem(callingApp, params, text, file); + return mSynthHandler.enqueueSpeechItem(TextToSpeech.QUEUE_ADD, item); + } + + public int playAudio(String callingApp, Uri audioUri, int queueMode, Bundle params) { + SpeechItem item = new AudioSpeechItem(callingApp, params, audioUri); + return mSynthHandler.enqueueSpeechItem(queueMode, item); + } + + public int playSilence(String callingApp, long duration, int queueMode, Bundle params) { + SpeechItem item = new SilenceSpeechItem(callingApp, params, duration); + return mSynthHandler.enqueueSpeechItem(queueMode, item); + } + + public boolean isSpeaking() { + return mSynthHandler.isSpeaking(); + } + + public int stop(String callingApp) { + return mSynthHandler.stop(callingApp); + } + + public String[] getLanguage() { + return onGetLanguage(); + } + + public int isLanguageAvailable(String lang, String country, String variant) { + return onIsLanguageAvailable(lang, country, variant); + } + + public int loadLanguage(String lang, String country, String variant) { + return onLoadLanguage(lang, country, variant); + } + + public void setCallback(String packageName, ITextToSpeechCallback cb) { + mCallbacks.setCallback(packageName, cb); + } + }; + + private class CallbackMap extends RemoteCallbackList<ITextToSpeechCallback> { + + private final HashMap<String, ITextToSpeechCallback> mAppToCallback + = new HashMap<String, ITextToSpeechCallback>(); + + public void setCallback(String packageName, ITextToSpeechCallback cb) { + synchronized (mAppToCallback) { + ITextToSpeechCallback old; + if (cb != null) { + register(cb, packageName); + old = mAppToCallback.put(packageName, cb); + } else { + old = mAppToCallback.remove(packageName); + } + if (old != null && old != cb) { + unregister(old); + } + } + } + + public void dispatchUtteranceCompleted(String packageName, String utteranceId) { + ITextToSpeechCallback cb; + synchronized (mAppToCallback) { + cb = mAppToCallback.get(packageName); + } + if (cb == null) return; + try { + cb.utteranceCompleted(utteranceId); + } catch (RemoteException e) { + Log.e(TAG, "Callback failed: " + e); + } + } + + @Override + public void onCallbackDied(ITextToSpeechCallback callback, Object cookie) { + String packageName = (String) cookie; + synchronized (mAppToCallback) { + mAppToCallback.remove(packageName); + } + mSynthHandler.stop(packageName); + } + + @Override + public void kill() { + synchronized (mAppToCallback) { + mAppToCallback.clear(); + super.kill(); + } + } + + } + +} diff --git a/include/tts/TtsEngine.h b/include/tts/TtsEngine.h deleted file mode 100644 index 998e353..0000000 --- a/include/tts/TtsEngine.h +++ /dev/null @@ -1,232 +0,0 @@ -/* - * 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 <media/AudioSystem.h> - -// This header defines the interface used by the Android platform -// to access Text-To-Speech functionality in shared libraries that implement -// speech synthesis and the management of resources associated with the -// synthesis. -// An example of the implementation of this interface can be found in -// FIXME: add path+name to implementation of default TTS engine -// Libraries implementing this interface are used in: -// frameworks/base/tts/jni/android_tts_SpeechSynthesis.cpp - -namespace android { - -#define ANDROID_TTS_ENGINE_PROPERTY_CONFIG "engineConfig" -#define ANDROID_TTS_ENGINE_PROPERTY_PITCH "pitch" -#define ANDROID_TTS_ENGINE_PROPERTY_RATE "rate" -#define ANDROID_TTS_ENGINE_PROPERTY_VOLUME "volume" - - -enum tts_synth_status { - TTS_SYNTH_DONE = 0, - TTS_SYNTH_PENDING = 1 -}; - -enum tts_callback_status { - TTS_CALLBACK_HALT = 0, - TTS_CALLBACK_CONTINUE = 1 -}; - -// The callback is used by the implementation of this interface to notify its -// client, the Android TTS service, that the last requested synthesis has been -// completed. // TODO reword -// The callback for synthesis completed takes: -// @param [inout] void *& - The userdata pointer set in the original -// synth call -// @param [in] uint32_t - Track sampling rate in Hz -// @param [in] uint32_t - The audio format -// @param [in] int - The number of channels -// @param [inout] int8_t *& - A buffer of audio data only valid during the -// execution of the callback -// @param [inout] size_t & - The size of the buffer -// @param [in] tts_synth_status - indicate whether the synthesis is done, or -// if more data is to be synthesized. -// @return TTS_CALLBACK_HALT to indicate the synthesis must stop, -// TTS_CALLBACK_CONTINUE to indicate the synthesis must continue if -// there is more data to produce. -typedef tts_callback_status (synthDoneCB_t)(void *&, uint32_t, - uint32_t, int, int8_t *&, size_t&, tts_synth_status); - -class TtsEngine; -extern "C" TtsEngine* getTtsEngine(); - -enum tts_result { - TTS_SUCCESS = 0, - TTS_FAILURE = -1, - TTS_FEATURE_UNSUPPORTED = -2, - TTS_VALUE_INVALID = -3, - TTS_PROPERTY_UNSUPPORTED = -4, - TTS_PROPERTY_SIZE_TOO_SMALL = -5, - TTS_MISSING_RESOURCES = -6 -}; - -enum tts_support_result { - TTS_LANG_COUNTRY_VAR_AVAILABLE = 2, - TTS_LANG_COUNTRY_AVAILABLE = 1, - TTS_LANG_AVAILABLE = 0, - TTS_LANG_MISSING_DATA = -1, - TTS_LANG_NOT_SUPPORTED = -2 -}; - -class TtsEngine -{ -public: - virtual ~TtsEngine() {} - - // Initialize the TTS engine and returns whether initialization succeeded. - // @param synthDoneCBPtr synthesis callback function pointer - // @return TTS_SUCCESS, or TTS_FAILURE - virtual tts_result init(synthDoneCB_t synthDoneCBPtr, const char *engineConfig); - - // Shut down the TTS engine and releases all associated resources. - // @return TTS_SUCCESS, or TTS_FAILURE - virtual tts_result shutdown(); - - // Interrupt synthesis and flushes any synthesized data that hasn't been - // output yet. This will block until callbacks underway are completed. - // @return TTS_SUCCESS, or TTS_FAILURE - virtual tts_result stop(); - - // Returns the level of support for the language, country and variant. - // @return TTS_LANG_COUNTRY_VAR_AVAILABLE if the language, country and variant are supported, - // and the corresponding resources are correctly installed - // TTS_LANG_COUNTRY_AVAILABLE if the language and country are supported and the - // corresponding resources are correctly installed, but there is no match for - // the specified variant - // TTS_LANG_AVAILABLE if the language is supported and the - // corresponding resources are correctly installed, but there is no match for - // the specified country and variant - // TTS_LANG_MISSING_DATA if the required resources to provide any level of support - // for the language are not correctly installed - // TTS_LANG_NOT_SUPPORTED if the language is not supported by the TTS engine. - virtual tts_support_result isLanguageAvailable(const char *lang, const char *country, - const char *variant); - - // Load the resources associated with the specified language. The loaded - // language will only be used once a call to setLanguage() with the same - // language value is issued. Language and country values are coded according to the ISO three - // letter codes for languages and countries, as can be retrieved from a java.util.Locale - // instance. The variant value is encoded as the variant string retrieved from a - // java.util.Locale instance built with that variant data. - // @param lang pointer to the ISO three letter code for the language - // @param country pointer to the ISO three letter code for the country - // @param variant pointer to the variant code - // @return TTS_SUCCESS, or TTS_FAILURE - virtual tts_result loadLanguage(const char *lang, const char *country, const char *variant); - - // Load the resources associated with the specified language, country and Locale variant. - // The loaded language will only be used once a call to setLanguageFromLocale() with the same - // language value is issued. Language and country values are coded according to the ISO three - // letter codes for languages and countries, as can be retrieved from a java.util.Locale - // instance. The variant value is encoded as the variant string retrieved from a - // java.util.Locale instance built with that variant data. - // @param lang pointer to the ISO three letter code for the language - // @param country pointer to the ISO three letter code for the country - // @param variant pointer to the variant code - // @return TTS_SUCCESS, or TTS_FAILURE - virtual tts_result setLanguage(const char *lang, const char *country, const char *variant); - - // Retrieve the currently set language, country and variant, or empty strings if none of - // parameters have been set. Language and country are represented by their 3-letter ISO code - // @param[out] pointer to the retrieved 3-letter code language value - // @param[out] pointer to the retrieved 3-letter code country value - // @param[out] pointer to the retrieved variant value - // @return TTS_SUCCESS, or TTS_FAILURE - virtual tts_result getLanguage(char *language, char *country, char *variant); - - // Notifies the engine what audio parameters should be used for the synthesis. - // This is meant to be used as a hint, the engine implementation will set the output values - // to those of the synthesis format, based on a given hint. - // @param[inout] encoding in: the desired audio sample format - // out: the format used by the TTS engine - // @param[inout] rate in: the desired audio sample rate - // out: the sample rate used by the TTS engine - // @param[inout] channels in: the desired number of audio channels - // out: the number of channels used by the TTS engine - // @return TTS_SUCCESS, or TTS_FAILURE - virtual tts_result setAudioFormat(AudioSystem::audio_format& encoding, uint32_t& rate, - int& channels); - - // Set a property for the the TTS engine - // "size" is the maximum size of "value" for properties "property" - // @param property pointer to the property name - // @param value pointer to the property value - // @param size maximum size required to store this type of property - // @return TTS_PROPERTY_UNSUPPORTED, or TTS_SUCCESS, or TTS_FAILURE, - // or TTS_VALUE_INVALID - virtual tts_result setProperty(const char *property, const char *value, - const size_t size); - - // Retrieve a property from the TTS engine - // @param property pointer to the property name - // @param[out] value pointer to the retrieved language value - // @param[inout] iosize in: stores the size available to store the - // property value. - // out: stores the size required to hold the language - // value if getLanguage() returned - // TTS_PROPERTY_SIZE_TOO_SMALL, unchanged otherwise - // @return TTS_PROPERTY_UNSUPPORTED, or TTS_SUCCESS, - // or TTS_PROPERTY_SIZE_TOO_SMALL - virtual tts_result getProperty(const char *property, char *value, - size_t *iosize); - - // Synthesize the text. - // As the synthesis is performed, the engine invokes the callback to notify - // the TTS framework that it has filled the given buffer, and indicates how - // many bytes it wrote. The callback is called repeatedly until the engine - // has generated all the audio data corresponding to the text. - // Note about the format of the input: the text parameter may use the - // following elements - // and their respective attributes as defined in the SSML 1.0 specification: - // * lang - // * say-as: - // o interpret-as - // * phoneme - // * voice: - // o gender, - // o age, - // o variant, - // o name - // * emphasis - // * break: - // o strength, - // o time - // * prosody: - // o pitch, - // o contour, - // o range, - // o rate, - // o duration, - // o volume - // * mark - // Differences between this text format and SSML are: - // * full SSML documents are not supported - // * namespaces are not supported - // Text is coded in UTF-8. - // @param text the UTF-8 text to synthesize - // @param userdata pointer to be returned when the call is invoked - // @param buffer the location where the synthesized data must be written - // @param bufferSize the number of bytes that can be written in buffer - // @return TTS_SUCCESS or TTS_FAILURE - virtual tts_result synthesizeText(const char *text, int8_t *buffer, - size_t bufferSize, void *userdata); - -}; - -} // namespace android - diff --git a/native/include/android/tts.h b/native/include/android/tts.h deleted file mode 100644 index fb15108..0000000 --- a/native/include/android/tts.h +++ /dev/null @@ -1,313 +0,0 @@ -/* - * 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. - */ -#ifndef ANDROID_TTS_H -#define ANDROID_TTS_H - -// This header defines the interface used by the Android platform -// to access Text-To-Speech functionality in shared libraries that implement -// speech synthesis and the management of resources associated with the -// synthesis. - -// The shared library must contain a function named "android_getTtsEngine" -// that returns an 'android_tts_engine_t' instance. - -#ifdef __cplusplus -extern "C" { -#endif - -#define ANDROID_TTS_ENGINE_PROPERTY_CONFIG "engineConfig" -#define ANDROID_TTS_ENGINE_PROPERTY_PITCH "pitch" -#define ANDROID_TTS_ENGINE_PROPERTY_RATE "rate" -#define ANDROID_TTS_ENGINE_PROPERTY_VOLUME "volume" - -typedef enum { - ANDROID_TTS_SUCCESS = 0, - ANDROID_TTS_FAILURE = -1, - ANDROID_TTS_FEATURE_UNSUPPORTED = -2, - ANDROID_TTS_VALUE_INVALID = -3, - ANDROID_TTS_PROPERTY_UNSUPPORTED = -4, - ANDROID_TTS_PROPERTY_SIZE_TOO_SMALL = -5, - ANDROID_TTS_MISSING_RESOURCES = -6 -} android_tts_result_t; - -typedef enum { - ANDROID_TTS_LANG_COUNTRY_VAR_AVAILABLE = 2, - ANDROID_TTS_LANG_COUNTRY_AVAILABLE = 1, - ANDROID_TTS_LANG_AVAILABLE = 0, - ANDROID_TTS_LANG_MISSING_DATA = -1, - ANDROID_TTS_LANG_NOT_SUPPORTED = -2 -} android_tts_support_result_t; - -typedef enum { - ANDROID_TTS_SYNTH_DONE = 0, - ANDROID_TTS_SYNTH_PENDING = 1 -} android_tts_synth_status_t; - -typedef enum { - ANDROID_TTS_CALLBACK_HALT = 0, - ANDROID_TTS_CALLBACK_CONTINUE = 1 -} android_tts_callback_status_t; - -// Supported audio formats -typedef enum { - ANDROID_TTS_AUDIO_FORMAT_INVALID = -1, - ANDROID_TTS_AUDIO_FORMAT_DEFAULT = 0, - ANDROID_TTS_AUDIO_FORMAT_PCM_16_BIT = 1, - ANDROID_TTS_AUDIO_FORMAT_PCM_8_BIT = 2, -} android_tts_audio_format_t; - - -/* An android_tts_engine_t object can be anything, but must have, - * as its first field, a pointer to a table of functions. - * - * See the full definition of struct android_tts_engine_t_funcs_t - * below for details. - */ -typedef struct android_tts_engine_funcs_t android_tts_engine_funcs_t; - -typedef struct { - android_tts_engine_funcs_t *funcs; -} android_tts_engine_t; - -/* This function must be located in the TTS Engine shared library - * and must return the address of an android_tts_engine_t library. - */ -extern android_tts_engine_t *android_getTtsEngine(); - -/* Including the old version for legacy support (Froyo compatibility). - * This should return the same thing as android_getTtsEngine. - */ -extern "C" android_tts_engine_t *getTtsEngine(); - -// A callback type used to notify the framework of new synthetized -// audio samples, status will be SYNTH_DONE for the last sample of -// the last request, of SYNTH_PENDING otherwise. -// -// This is passed by the framework to the engine through the -// 'engine_init' function (see below). -// -// The callback for synthesis completed takes: -// @param [inout] void *& - The userdata pointer set in the original -// synth call -// @param [in] uint32_t - Track sampling rate in Hz -// @param [in] uint32_t - The audio format -// @param [in] int - The number of channels -// @param [inout] int8_t *& - A buffer of audio data only valid during the -// execution of the callback -// @param [inout] size_t & - The size of the buffer -// @param [in] tts_synth_status - indicate whether the synthesis is done, or -// if more data is to be synthesized. -// @return TTS_CALLBACK_HALT to indicate the synthesis must stop, -// TTS_CALLBACK_CONTINUE to indicate the synthesis must continue if -// there is more data to produce. -typedef android_tts_callback_status_t (*android_tts_synth_cb_t) - (void **pUserData, - uint32_t trackSamplingHz, - android_tts_audio_format_t audioFormat, - int channelCount, - int8_t **pAudioBuffer, - size_t *pBufferSize, - android_tts_synth_status_t status); - - -// The table of function pointers that the android_tts_engine_t must point to. -// Note that each of these functions will take a handle to the engine itself -// as their first parameter. -// - -struct android_tts_engine_funcs_t { - // reserved fields, ignored by the framework - // they must be placed here to ensure binary compatibility - // of legacy binary plugins. - void *reserved[2]; - - // Initialize the TTS engine and returns whether initialization succeeded. - // @param synthDoneCBPtr synthesis callback function pointer - // @return TTS_SUCCESS, or TTS_FAILURE - android_tts_result_t (*init) - (void *engine, - android_tts_synth_cb_t synthDonePtr, - const char *engineConfig); - - // Shut down the TTS engine and releases all associated resources. - // @return TTS_SUCCESS, or TTS_FAILURE - android_tts_result_t (*shutdown) - (void *engine); - - // Interrupt synthesis and flushes any synthesized data that hasn't been - // output yet. This will block until callbacks underway are completed. - // @return TTS_SUCCESS, or TTS_FAILURE - android_tts_result_t (*stop) - (void *engine); - - // Returns the level of support for the language, country and variant. - // @return TTS_LANG_COUNTRY_VAR_AVAILABLE if the language, country and variant are supported, - // and the corresponding resources are correctly installed - // TTS_LANG_COUNTRY_AVAILABLE if the language and country are supported and the - // corresponding resources are correctly installed, but there is no match for - // the specified variant - // TTS_LANG_AVAILABLE if the language is supported and the - // corresponding resources are correctly installed, but there is no match for - // the specified country and variant - // TTS_LANG_MISSING_DATA if the required resources to provide any level of support - // for the language are not correctly installed - // TTS_LANG_NOT_SUPPORTED if the language is not supported by the TTS engine. - android_tts_support_result_t (*isLanguageAvailable) - (void *engine, - const char *lang, - const char *country, - const char *variant); - - // Load the resources associated with the specified language. The loaded - // language will only be used once a call to setLanguage() with the same - // language value is issued. Language and country values are coded according to the ISO three - // letter codes for languages and countries, as can be retrieved from a java.util.Locale - // instance. The variant value is encoded as the variant string retrieved from a - // java.util.Locale instance built with that variant data. - // @param lang pointer to the ISO three letter code for the language - // @param country pointer to the ISO three letter code for the country - // @param variant pointer to the variant code - // @return TTS_SUCCESS, or TTS_FAILURE - android_tts_result_t (*loadLanguage) - (void *engine, - const char *lang, - const char *country, - const char *variant); - - // Load the resources associated with the specified language, country and Locale variant. - // The loaded language will only be used once a call to setLanguageFromLocale() with the same - // language value is issued. Language and country values are coded according to the ISO three - // letter codes for languages and countries, as can be retrieved from a java.util.Locale - // instance. The variant value is encoded as the variant string retrieved from a - // java.util.Locale instance built with that variant data. - // @param lang pointer to the ISO three letter code for the language - // @param country pointer to the ISO three letter code for the country - // @param variant pointer to the variant code - // @return TTS_SUCCESS, or TTS_FAILURE - android_tts_result_t (*setLanguage) - (void *engine, - const char *lang, - const char *country, - const char *variant); - - // Retrieve the currently set language, country and variant, or empty strings if none of - // parameters have been set. Language and country are represented by their 3-letter ISO code - // @param[out] pointer to the retrieved 3-letter code language value - // @param[out] pointer to the retrieved 3-letter code country value - // @param[out] pointer to the retrieved variant value - // @return TTS_SUCCESS, or TTS_FAILURE - android_tts_result_t (*getLanguage) - (void *engine, - char *language, - char *country, - char *variant); - - // Notifies the engine what audio parameters should be used for the synthesis. - // This is meant to be used as a hint, the engine implementation will set the output values - // to those of the synthesis format, based on a given hint. - // @param[inout] encoding in: the desired audio sample format - // out: the format used by the TTS engine - // @param[inout] rate in: the desired audio sample rate - // out: the sample rate used by the TTS engine - // @param[inout] channels in: the desired number of audio channels - // out: the number of channels used by the TTS engine - // @return TTS_SUCCESS, or TTS_FAILURE - android_tts_result_t (*setAudioFormat) - (void *engine, - android_tts_audio_format_t* pEncoding, - uint32_t* pRate, - int* pChannels); - - // Set a property for the the TTS engine - // "size" is the maximum size of "value" for properties "property" - // @param property pointer to the property name - // @param value pointer to the property value - // @param size maximum size required to store this type of property - // @return TTS_PROPERTY_UNSUPPORTED, or TTS_SUCCESS, or TTS_FAILURE, - // or TTS_VALUE_INVALID - android_tts_result_t (*setProperty) - (void *engine, - const char *property, - const char *value, - const size_t size); - - // Retrieve a property from the TTS engine - // @param property pointer to the property name - // @param[out] value pointer to the retrieved language value - // @param[inout] iosize in: stores the size available to store the - // property value. - // out: stores the size required to hold the language - // value if getLanguage() returned - // TTS_PROPERTY_SIZE_TOO_SMALL, unchanged otherwise - // @return TTS_PROPERTY_UNSUPPORTED, or TTS_SUCCESS, - // or TTS_PROPERTY_SIZE_TOO_SMALL - android_tts_result_t (*getProperty) - (void *engine, - const char *property, - char *value, - size_t *iosize); - - // Synthesize the text. - // As the synthesis is performed, the engine invokes the callback to notify - // the TTS framework that it has filled the given buffer, and indicates how - // many bytes it wrote. The callback is called repeatedly until the engine - // has generated all the audio data corresponding to the text. - // Note about the format of the input: the text parameter may use the - // following elements - // and their respective attributes as defined in the SSML 1.0 specification: - // * lang - // * say-as: - // o interpret-as - // * phoneme - // * voice: - // o gender, - // o age, - // o variant, - // o name - // * emphasis - // * break: - // o strength, - // o time - // * prosody: - // o pitch, - // o contour, - // o range, - // o rate, - // o duration, - // o volume - // * mark - // Differences between this text format and SSML are: - // * full SSML documents are not supported - // * namespaces are not supported - // Text is coded in UTF-8. - // @param text the UTF-8 text to synthesize - // @param userdata pointer to be returned when the call is invoked - // @param buffer the location where the synthesized data must be written - // @param bufferSize the number of bytes that can be written in buffer - // @return TTS_SUCCESS or TTS_FAILURE - android_tts_result_t (*synthesizeText) - (void *engine, - const char *text, - int8_t *buffer, - size_t bufferSize, - void *userdata); -}; - -#ifdef __cplusplus -} -#endif - -#endif /* ANDROID_TTS_H */ diff --git a/packages/TtsService/Android.mk b/packages/TtsService/Android.mk deleted file mode 100644 index a1a3b9f..0000000 --- a/packages/TtsService/Android.mk +++ /dev/null @@ -1,15 +0,0 @@ -LOCAL_PATH:= $(call my-dir) -include $(CLEAR_VARS) - -LOCAL_MODULE_TAGS := optional - -LOCAL_SRC_FILES := $(call all-subdir-java-files) \ - -LOCAL_PACKAGE_NAME := TtsService -LOCAL_CERTIFICATE := platform - -LOCAL_PROGUARD_FLAG_FILES := proguard.flags - -include $(BUILD_PACKAGE) - -include $(call all-makefiles-under,$(LOCAL_PATH)) diff --git a/packages/TtsService/AndroidManifest.xml b/packages/TtsService/AndroidManifest.xml deleted file mode 100755 index 46e0ad1..0000000 --- a/packages/TtsService/AndroidManifest.xml +++ /dev/null @@ -1,17 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="android.tts"> - <application android:label="TTS Service" - android:icon="@drawable/ic_launcher_text_to_speech"> - <service android:enabled="true" - android:name=".TtsService" - android:label="TTS Service"> - <intent-filter> - <action android:name="android.intent.action.START_TTS_SERVICE"/> - <category android:name="android.intent.category.TTS"/> - </intent-filter> - </service> - </application> - <uses-permission android:name="android.permission.INTERNET"/> - <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> -</manifest> diff --git a/packages/TtsService/MODULE_LICENSE_APACHE2 b/packages/TtsService/MODULE_LICENSE_APACHE2 deleted file mode 100644 index e69de29..0000000 --- a/packages/TtsService/MODULE_LICENSE_APACHE2 +++ /dev/null diff --git a/packages/TtsService/NOTICE b/packages/TtsService/NOTICE deleted file mode 100644 index 64aaa8d..0000000 --- a/packages/TtsService/NOTICE +++ /dev/null @@ -1,190 +0,0 @@ - - Copyright (c) 2009, The Android Open Source Project - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - - 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. - - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - diff --git a/packages/TtsService/jni/Android.mk b/packages/TtsService/jni/Android.mk deleted file mode 100755 index 5dc0c30..0000000 --- a/packages/TtsService/jni/Android.mk +++ /dev/null @@ -1,30 +0,0 @@ -LOCAL_PATH:= $(call my-dir) -include $(CLEAR_VARS) - -LOCAL_SRC_FILES:= \ - android_tts_SynthProxy.cpp - -LOCAL_C_INCLUDES += \ - frameworks/base/native/include \ - $(JNI_H_INCLUDE) - -LOCAL_SHARED_LIBRARIES := \ - libandroid_runtime \ - libnativehelper \ - libmedia \ - libutils \ - libcutils - -ifeq ($(TARGET_SIMULATOR),true) - LOCAL_LDLIBS += -ldl -else - LOCAL_SHARED_LIBRARIES += libdl -endif - - -LOCAL_MODULE:= libttssynthproxy - -LOCAL_ARM_MODE := arm - -include $(BUILD_SHARED_LIBRARY) - diff --git a/packages/TtsService/jni/android_tts_SynthProxy.cpp b/packages/TtsService/jni/android_tts_SynthProxy.cpp deleted file mode 100644 index e00fa85..0000000 --- a/packages/TtsService/jni/android_tts_SynthProxy.cpp +++ /dev/null @@ -1,1071 +0,0 @@ -/* - * Copyright (C) 2009-2010 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 "SynthProxyJNI" - -#include <utils/Log.h> -#include <nativehelper/jni.h> -#include <nativehelper/JNIHelp.h> -#include <android_runtime/AndroidRuntime.h> -#include <android/tts.h> -#include <media/AudioTrack.h> -#include <math.h> - -#include <dlfcn.h> - -#define DEFAULT_TTS_RATE 16000 -#define DEFAULT_TTS_FORMAT AudioSystem::PCM_16_BIT -#define DEFAULT_TTS_NB_CHANNELS 1 -#define DEFAULT_TTS_BUFFERSIZE 2048 -#define DEFAULT_TTS_STREAM_TYPE AudioSystem::MUSIC -#define DEFAULT_VOLUME 1.0f - -// EQ + BOOST parameters -#define FILTER_LOWSHELF_ATTENUATION -18.0f // in dB -#define FILTER_TRANSITION_FREQ 1100.0f // in Hz -#define FILTER_SHELF_SLOPE 1.0f // Q -#define FILTER_GAIN 5.5f // linear gain - -#define USAGEMODE_PLAY_IMMEDIATELY 0 -#define USAGEMODE_WRITE_TO_FILE 1 - -#define SYNTHPLAYSTATE_IS_STOPPED 0 -#define SYNTHPLAYSTATE_IS_PLAYING 1 - -using namespace android; - -// ---------------------------------------------------------------------------- -struct fields_t { - jfieldID synthProxyFieldJniData; - 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; -}; - -// ---------------------------------------------------------------------------- -// EQ data -double amp; -double w; -double sinw; -double cosw; -double beta; -double a0, a1, a2, b0, b1, b2; -double m_fa, m_fb, m_fc, m_fd, m_fe; -double x0; // x[n] -double x1; // x[n-1] -double x2; // x[n-2] -double out0;// y[n] -double out1;// y[n-1] -double out2;// y[n-2] - -static float fFilterLowshelfAttenuation = FILTER_LOWSHELF_ATTENUATION; -static float fFilterTransitionFreq = FILTER_TRANSITION_FREQ; -static float fFilterShelfSlope = FILTER_SHELF_SLOPE; -static float fFilterGain = FILTER_GAIN; -static bool bUseFilter = false; - -void initializeEQ() { - - amp = float(pow(10.0, fFilterLowshelfAttenuation / 40.0)); - w = 2.0 * M_PI * (fFilterTransitionFreq / DEFAULT_TTS_RATE); - sinw = float(sin(w)); - cosw = float(cos(w)); - beta = float(sqrt(amp)/fFilterShelfSlope); - - // initialize low-shelf parameters - b0 = amp * ((amp+1.0F) - ((amp-1.0F)*cosw) + (beta*sinw)); - b1 = 2.0F * amp * ((amp-1.0F) - ((amp+1.0F)*cosw)); - b2 = amp * ((amp+1.0F) - ((amp-1.0F)*cosw) - (beta*sinw)); - a0 = (amp+1.0F) + ((amp-1.0F)*cosw) + (beta*sinw); - a1 = 2.0F * ((amp-1.0F) + ((amp+1.0F)*cosw)); - a2 = -((amp+1.0F) + ((amp-1.0F)*cosw) - (beta*sinw)); - - m_fa = fFilterGain * b0/a0; - m_fb = fFilterGain * b1/a0; - m_fc = fFilterGain * b2/a0; - m_fd = a1/a0; - m_fe = a2/a0; -} - -void initializeFilter() { - x0 = 0.0f; - x1 = 0.0f; - x2 = 0.0f; - out0 = 0.0f; - out1 = 0.0f; - out2 = 0.0f; -} - -void applyFilter(int16_t* buffer, size_t sampleCount) { - - for (size_t i=0 ; i<sampleCount ; i++) { - - x0 = (double) buffer[i]; - - out0 = (m_fa*x0) + (m_fb*x1) + (m_fc*x2) + (m_fd*out1) + (m_fe*out2); - - x2 = x1; - x1 = x0; - - out2 = out1; - out1 = out0; - - if (out0 > 32767.0f) { - buffer[i] = 32767; - } else if (out0 < -32768.0f) { - buffer[i] = -32768; - } else { - buffer[i] = (int16_t) out0; - } - } -} - - -// ---------------------------------------------------------------------------- -static fields_t javaTTSFields; - -// TODO move to synth member once we have multiple simultaneous engines running -static Mutex engineMutex; - -// ---------------------------------------------------------------------------- -class SynthProxyJniStorage { - public : - jobject tts_ref; - android_tts_engine_t* mEngine; - void* mEngineLibHandle; - AudioTrack* mAudioOut; - int8_t mPlayState; - Mutex mPlayLock; - AudioSystem::stream_type mStreamType; - uint32_t mSampleRate; - uint32_t mAudFormat; - int mNbChannels; - int8_t * mBuffer; - size_t mBufferSize; - float mVolume[2]; - - SynthProxyJniStorage() { - tts_ref = NULL; - mEngine = NULL; - mEngineLibHandle = NULL; - mAudioOut = NULL; - mPlayState = SYNTHPLAYSTATE_IS_STOPPED; - mStreamType = DEFAULT_TTS_STREAM_TYPE; - mSampleRate = DEFAULT_TTS_RATE; - mAudFormat = DEFAULT_TTS_FORMAT; - mNbChannels = DEFAULT_TTS_NB_CHANNELS; - mBufferSize = DEFAULT_TTS_BUFFERSIZE; - mBuffer = new int8_t[mBufferSize]; - memset(mBuffer, 0, mBufferSize); - mVolume[AudioTrack::LEFT] = DEFAULT_VOLUME; - mVolume[AudioTrack::RIGHT] = DEFAULT_VOLUME; - } - - ~SynthProxyJniStorage() { - //LOGV("entering ~SynthProxyJniStorage()"); - killAudio(); - if (mEngine) { - mEngine->funcs->shutdown(mEngine); - mEngine = NULL; - } - if (mEngineLibHandle) { - //LOGV("~SynthProxyJniStorage(): before close library"); - int res = dlclose(mEngineLibHandle); - LOGE_IF( res != 0, "~SynthProxyJniStorage(): dlclose returned %d", res); - } - delete mBuffer; - } - - void killAudio() { - if (mAudioOut) { - mAudioOut->stop(); - delete mAudioOut; - mAudioOut = NULL; - } - } - - void createAudioOut(AudioSystem::stream_type streamType, uint32_t rate, - AudioSystem::audio_format format, int channel) { - mSampleRate = rate; - mAudFormat = format; - mNbChannels = channel; - mStreamType = streamType; - - // retrieve system properties to ensure successful creation of the - // AudioTrack object for playback - int afSampleRate; - if (AudioSystem::getOutputSamplingRate(&afSampleRate, mStreamType) != NO_ERROR) { - afSampleRate = 44100; - } - int afFrameCount; - if (AudioSystem::getOutputFrameCount(&afFrameCount, mStreamType) != NO_ERROR) { - afFrameCount = 2048; - } - uint32_t afLatency; - 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; - - mPlayLock.lock(); - mAudioOut = new AudioTrack(mStreamType, rate, format, - (channel == 2) ? AudioSystem::CHANNEL_OUT_STEREO : AudioSystem::CHANNEL_OUT_MONO, - minFrameCount > 4096 ? minFrameCount : 4096, - 0, 0, 0, 0); // not using an AudioTrack callback - - if (mAudioOut->initCheck() != NO_ERROR) { - LOGE("createAudioOut(): AudioTrack error"); - delete mAudioOut; - mAudioOut = NULL; - } else { - //LOGI("AudioTrack OK"); - mAudioOut->setVolume(mVolume[AudioTrack::LEFT], mVolume[AudioTrack::RIGHT]); - LOGV("AudioTrack ready"); - } - mPlayLock.unlock(); - } -}; - - -// ---------------------------------------------------------------------------- -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 initialized with the same audio parameters. - if ( pJniData->mAudioOut && - (rate == pJniData->mSampleRate) && - (format == pJniData->mAudFormat) && - (channel == pJniData->mNbChannels) && - (streamType == pJniData->mStreamType) ){ - return; - } - if (pJniData->mAudioOut){ - pJniData->killAudio(); - } - pJniData->createAudioOut(streamType, rate, format, channel); -} - - -// ---------------------------------------------------------------------------- -/* - * Callback from TTS engine. - * Directly speaks using AudioTrack or write to file - */ -extern "C" android_tts_callback_status_t -__ttsSynthDoneCB(void ** pUserdata, uint32_t rate, - android_tts_audio_format_t format, int channel, - int8_t **pWav, size_t *pBufferSize, - android_tts_synth_status_t status) -{ - //LOGV("ttsSynthDoneCallback: %d bytes", bufferSize); - AudioSystem::audio_format encoding; - - if (*pUserdata == NULL){ - LOGE("userdata == NULL"); - return ANDROID_TTS_CALLBACK_HALT; - } - switch (format) { - case ANDROID_TTS_AUDIO_FORMAT_PCM_8_BIT: - encoding = AudioSystem::PCM_8_BIT; - break; - case ANDROID_TTS_AUDIO_FORMAT_PCM_16_BIT: - encoding = AudioSystem::PCM_16_BIT; - break; - default: - LOGE("Can't play, bad format"); - return ANDROID_TTS_CALLBACK_HALT; - } - afterSynthData_t* pForAfter = (afterSynthData_t*) *pUserdata; - SynthProxyJniStorage* pJniData = (SynthProxyJniStorage*)(pForAfter->jniStorage); - - if (pForAfter->usageMode == USAGEMODE_PLAY_IMMEDIATELY){ - //LOGV("Direct speech"); - - if (*pWav == NULL) { - delete pForAfter; - pForAfter = NULL; - LOGV("Null: speech has completed"); - return ANDROID_TTS_CALLBACK_HALT; - } - - if (*pBufferSize > 0) { - prepAudioTrack(pJniData, pForAfter->streamType, rate, encoding, channel); - if (pJniData->mAudioOut) { - pJniData->mPlayLock.lock(); - if(pJniData->mAudioOut->stopped() - && (pJniData->mPlayState == SYNTHPLAYSTATE_IS_PLAYING)) { - pJniData->mAudioOut->start(); - } - pJniData->mPlayLock.unlock(); - if (bUseFilter) { - applyFilter((int16_t*)*pWav, *pBufferSize/2); - } - pJniData->mAudioOut->write(*pWav, *pBufferSize); - memset(*pWav, 0, *pBufferSize); - //LOGV("AudioTrack wrote: %d bytes", bufferSize); - } else { - LOGE("Can't play, null audiotrack"); - delete pForAfter; - pForAfter = NULL; - return ANDROID_TTS_CALLBACK_HALT; - } - } - } else if (pForAfter->usageMode == USAGEMODE_WRITE_TO_FILE) { - //LOGV("Save to file"); - if (*pWav == NULL) { - delete pForAfter; - LOGV("Null: speech has completed"); - return ANDROID_TTS_CALLBACK_HALT; - } - if (*pBufferSize > 0){ - if (bUseFilter) { - applyFilter((int16_t*)*pWav, *pBufferSize/2); - } - fwrite(*pWav, 1, *pBufferSize, pForAfter->outputFile); - memset(*pWav, 0, *pBufferSize); - } - } - // Future update: - // For sync points in the speech, call back into the SynthProxy class through the - // javaTTSFields.synthProxyMethodPost methode to notify - // playback has completed if the synthesis is done or if a marker has been reached. - - if (status == ANDROID_TTS_SYNTH_DONE) { - // this struct was allocated in the original android_tts_SynthProxy_speak call, - // all processing matching this call is now done. - LOGV("Speech synthesis done."); - if (pForAfter->usageMode == USAGEMODE_PLAY_IMMEDIATELY) { - // only delete for direct playback. When writing to a file, we still have work to do - // in android_tts_SynthProxy_synthesizeToFile. The struct will be deleted there. - delete pForAfter; - pForAfter = NULL; - } - return ANDROID_TTS_CALLBACK_HALT; - } - - // we don't update the wav (output) parameter as we'll let the next callback - // write at the same location, we've consumed the data already, but we need - // to update bufferSize to let the TTS engine know how much it can write the - // next time it calls this function. - *pBufferSize = pJniData->mBufferSize; - - return ANDROID_TTS_CALLBACK_CONTINUE; -} - - -// ---------------------------------------------------------------------------- -static int -android_tts_SynthProxy_setLowShelf(JNIEnv *env, jobject thiz, jboolean applyFilter, - jfloat filterGain, jfloat attenuationInDb, jfloat freqInHz, jfloat slope) -{ - int result = ANDROID_TTS_SUCCESS; - - bUseFilter = applyFilter; - if (applyFilter) { - fFilterLowshelfAttenuation = attenuationInDb; - fFilterTransitionFreq = freqInHz; - fFilterShelfSlope = slope; - fFilterGain = filterGain; - - if (fFilterShelfSlope != 0.0f) { - initializeEQ(); - } else { - LOGE("Invalid slope, can't be null"); - result = ANDROID_TTS_FAILURE; - } - } - - return result; -} - -// ---------------------------------------------------------------------------- -static int -android_tts_SynthProxy_native_setup(JNIEnv *env, jobject thiz, - jobject weak_this, jstring nativeSoLib, jstring engConfig) -{ - int result = ANDROID_TTS_FAILURE; - - bUseFilter = false; - - SynthProxyJniStorage* pJniStorage = new SynthProxyJniStorage(); - - prepAudioTrack(pJniStorage, - DEFAULT_TTS_STREAM_TYPE, DEFAULT_TTS_RATE, DEFAULT_TTS_FORMAT, DEFAULT_TTS_NB_CHANNELS); - - const char *nativeSoLibNativeString = env->GetStringUTFChars(nativeSoLib, 0); - const char *engConfigString = env->GetStringUTFChars(engConfig, 0); - - void *engine_lib_handle = dlopen(nativeSoLibNativeString, - RTLD_NOW | RTLD_LOCAL); - if (engine_lib_handle == NULL) { - LOGE("android_tts_SynthProxy_native_setup(): engine_lib_handle == NULL"); - } else { - android_tts_engine_t * (*get_TtsEngine)() = - reinterpret_cast<android_tts_engine_t* (*)()>(dlsym(engine_lib_handle, "android_getTtsEngine")); - - // Support obsolete/legacy binary modules - if (get_TtsEngine == NULL) { - get_TtsEngine = - reinterpret_cast<android_tts_engine_t* (*)()>(dlsym(engine_lib_handle, "getTtsEngine")); - } - - pJniStorage->mEngine = (*get_TtsEngine)(); - pJniStorage->mEngineLibHandle = engine_lib_handle; - - android_tts_engine_t *engine = pJniStorage->mEngine; - if (engine) { - Mutex::Autolock l(engineMutex); - engine->funcs->init( - engine, - __ttsSynthDoneCB, - engConfigString); - } - - result = ANDROID_TTS_SUCCESS; - } - - // 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); - env->ReleaseStringUTFChars(engConfig, engConfigString); - - return result; -} - - -static void -android_tts_SynthProxy_native_finalize(JNIEnv *env, jobject thiz, jint jniData) -{ - //LOGV("entering android_tts_SynthProxy_finalize()"); - if (jniData == 0) { - //LOGE("android_tts_SynthProxy_native_finalize(): invalid JNI data"); - return; - } - - Mutex::Autolock l(engineMutex); - - SynthProxyJniStorage* pSynthData = (SynthProxyJniStorage*)jniData; - env->DeleteGlobalRef(pSynthData->tts_ref); - delete pSynthData; - - env->SetIntField(thiz, javaTTSFields.synthProxyFieldJniData, 0); -} - - -static void -android_tts_SynthProxy_shutdown(JNIEnv *env, jobject thiz, jint jniData) -{ - //LOGV("entering android_tts_SynthProxy_shutdown()"); - - // do everything a call to finalize would - android_tts_SynthProxy_native_finalize(env, thiz, jniData); -} - - -static int -android_tts_SynthProxy_isLanguageAvailable(JNIEnv *env, jobject thiz, jint jniData, - jstring language, jstring country, jstring variant) -{ - int result = ANDROID_TTS_LANG_NOT_SUPPORTED; - - if (jniData == 0) { - LOGE("android_tts_SynthProxy_isLanguageAvailable(): invalid JNI data"); - return result; - } - - SynthProxyJniStorage* pSynthData = (SynthProxyJniStorage*)jniData; - const char *langNativeString = env->GetStringUTFChars(language, 0); - const char *countryNativeString = env->GetStringUTFChars(country, 0); - const char *variantNativeString = env->GetStringUTFChars(variant, 0); - - android_tts_engine_t *engine = pSynthData->mEngine; - - if (engine) { - result = engine->funcs->isLanguageAvailable(engine,langNativeString, - countryNativeString, variantNativeString); - } - env->ReleaseStringUTFChars(language, langNativeString); - env->ReleaseStringUTFChars(country, countryNativeString); - env->ReleaseStringUTFChars(variant, variantNativeString); - return result; -} - -static int -android_tts_SynthProxy_setConfig(JNIEnv *env, jobject thiz, jint jniData, jstring engineConfig) -{ - int result = ANDROID_TTS_FAILURE; - - if (jniData == 0) { - LOGE("android_tts_SynthProxy_setConfig(): invalid JNI data"); - return result; - } - - Mutex::Autolock l(engineMutex); - - SynthProxyJniStorage* pSynthData = (SynthProxyJniStorage*)jniData; - const char *engineConfigNativeString = env->GetStringUTFChars(engineConfig, 0); - android_tts_engine_t *engine = pSynthData->mEngine; - - if (engine) { - result = engine->funcs->setProperty(engine,ANDROID_TTS_ENGINE_PROPERTY_CONFIG, - engineConfigNativeString, strlen(engineConfigNativeString)); - } - env->ReleaseStringUTFChars(engineConfig, engineConfigNativeString); - - return result; -} - -static int -android_tts_SynthProxy_setLanguage(JNIEnv *env, jobject thiz, jint jniData, - jstring language, jstring country, jstring variant) -{ - int result = ANDROID_TTS_LANG_NOT_SUPPORTED; - - if (jniData == 0) { - LOGE("android_tts_SynthProxy_setLanguage(): invalid JNI data"); - return result; - } - - Mutex::Autolock l(engineMutex); - - SynthProxyJniStorage* pSynthData = (SynthProxyJniStorage*)jniData; - const char *langNativeString = env->GetStringUTFChars(language, 0); - const char *countryNativeString = env->GetStringUTFChars(country, 0); - const char *variantNativeString = env->GetStringUTFChars(variant, 0); - android_tts_engine_t *engine = pSynthData->mEngine; - - if (engine) { - result = engine->funcs->setLanguage(engine, langNativeString, - countryNativeString, variantNativeString); - } - env->ReleaseStringUTFChars(language, langNativeString); - env->ReleaseStringUTFChars(country, countryNativeString); - env->ReleaseStringUTFChars(variant, variantNativeString); - return result; -} - - -static int -android_tts_SynthProxy_loadLanguage(JNIEnv *env, jobject thiz, jint jniData, - jstring language, jstring country, jstring variant) -{ - int result = ANDROID_TTS_LANG_NOT_SUPPORTED; - - if (jniData == 0) { - LOGE("android_tts_SynthProxy_loadLanguage(): invalid JNI data"); - return result; - } - - SynthProxyJniStorage* pSynthData = (SynthProxyJniStorage*)jniData; - const char *langNativeString = env->GetStringUTFChars(language, 0); - const char *countryNativeString = env->GetStringUTFChars(country, 0); - const char *variantNativeString = env->GetStringUTFChars(variant, 0); - android_tts_engine_t *engine = pSynthData->mEngine; - - if (engine) { - result = engine->funcs->loadLanguage(engine, langNativeString, - countryNativeString, variantNativeString); - } - env->ReleaseStringUTFChars(language, langNativeString); - env->ReleaseStringUTFChars(country, countryNativeString); - env->ReleaseStringUTFChars(variant, variantNativeString); - - return result; -} - - -static int -android_tts_SynthProxy_setSpeechRate(JNIEnv *env, jobject thiz, jint jniData, - jint speechRate) -{ - int result = ANDROID_TTS_FAILURE; - - if (jniData == 0) { - LOGE("android_tts_SynthProxy_setSpeechRate(): invalid JNI data"); - return result; - } - - int bufSize = 12; - char buffer [bufSize]; - sprintf(buffer, "%d", speechRate); - - Mutex::Autolock l(engineMutex); - - SynthProxyJniStorage* pSynthData = (SynthProxyJniStorage*)jniData; - //LOGI("setting speech rate to %d", speechRate); - android_tts_engine_t *engine = pSynthData->mEngine; - - if (engine) { - result = engine->funcs->setProperty(engine, "rate", buffer, bufSize); - } - - return result; -} - - -static int -android_tts_SynthProxy_setPitch(JNIEnv *env, jobject thiz, jint jniData, - jint pitch) -{ - int result = ANDROID_TTS_FAILURE; - - if (jniData == 0) { - LOGE("android_tts_SynthProxy_setPitch(): invalid JNI data"); - return result; - } - - Mutex::Autolock l(engineMutex); - - int bufSize = 12; - char buffer [bufSize]; - sprintf(buffer, "%d", pitch); - - SynthProxyJniStorage* pSynthData = (SynthProxyJniStorage*)jniData; - //LOGI("setting pitch to %d", pitch); - android_tts_engine_t *engine = pSynthData->mEngine; - - if (engine) { - result = engine->funcs->setProperty(engine, "pitch", buffer, bufSize); - } - - return result; -} - - -static int -android_tts_SynthProxy_synthesizeToFile(JNIEnv *env, jobject thiz, jint jniData, - jstring textJavaString, jstring filenameJavaString) -{ - int result = ANDROID_TTS_FAILURE; - - if (jniData == 0) { - LOGE("android_tts_SynthProxy_synthesizeToFile(): invalid JNI data"); - return result; - } - - SynthProxyJniStorage* pSynthData = (SynthProxyJniStorage*)jniData; - if (!pSynthData->mEngine) { - LOGE("android_tts_SynthProxy_synthesizeToFile(): invalid engine handle"); - return result; - } - - initializeFilter(); - - Mutex::Autolock l(engineMutex); - - // Retrieve audio parameters before writing the file header - AudioSystem::audio_format encoding; - uint32_t rate = DEFAULT_TTS_RATE; - int channels = DEFAULT_TTS_NB_CHANNELS; - android_tts_engine_t *engine = pSynthData->mEngine; - android_tts_audio_format_t format = ANDROID_TTS_AUDIO_FORMAT_DEFAULT; - - engine->funcs->setAudioFormat(engine, &format, &rate, &channels); - - switch (format) { - case ANDROID_TTS_AUDIO_FORMAT_PCM_16_BIT: - encoding = AudioSystem::PCM_16_BIT; - break; - case ANDROID_TTS_AUDIO_FORMAT_PCM_8_BIT: - encoding = AudioSystem::PCM_8_BIT; - break; - default: - LOGE("android_tts_SynthProxy_synthesizeToFile(): engine uses invalid format"); - return result; - } - - 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"); - - if (pForAfter->outputFile == NULL) { - LOGE("android_tts_SynthProxy_synthesizeToFile(): error creating output file"); - delete pForAfter; - return result; - } - - // 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; - - memset(pSynthData->mBuffer, 0, pSynthData->mBufferSize); - - result = engine->funcs->synthesizeText(engine, textNativeString, - pSynthData->mBuffer, pSynthData->mBufferSize, (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 - - int sampleSizeInByte = (encoding == AudioSystem::PCM_16_BIT ? 2 : 1); - - ((unsigned short *)(&header[20]))[0] = 1; // format - ((unsigned short *)(&header[22]))[0] = channels; // channels - ((uint32_t *)(&header[24]))[0] = rate; // samplerate - ((uint32_t *)(&header[28]))[0] = rate * sampleSizeInByte * channels;// byterate - ((unsigned short *)(&header[32]))[0] = sampleSizeInByte * channels; // block align - ((unsigned short *)(&header[34]))[0] = sampleSizeInByte * 8; // 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); - - delete pForAfter; - pForAfter = NULL; - - env->ReleaseStringUTFChars(textJavaString, textNativeString); - env->ReleaseStringUTFChars(filenameJavaString, filenameNativeString); - - return result; -} - - -static int -android_tts_SynthProxy_speak(JNIEnv *env, jobject thiz, jint jniData, - jstring textJavaString, jint javaStreamType, jfloat volume, jfloat pan) -{ - int result = ANDROID_TTS_FAILURE; - - if (jniData == 0) { - LOGE("android_tts_SynthProxy_speak(): invalid JNI data"); - return result; - } - - initializeFilter(); - - Mutex::Autolock l(engineMutex); - - SynthProxyJniStorage* pSynthData = (SynthProxyJniStorage*)jniData; - - {//scope for lock on mPlayLock - Mutex::Autolock _l(pSynthData->mPlayLock); - - pSynthData->mPlayState = SYNTHPLAYSTATE_IS_PLAYING; - - // clip volume and pan - float vol = (volume > 1.0f) ? 1.0f : (volume < 0.0f) ? 0.0f : volume; - float panning = (pan > 1.0f) ? 1.0f : (pan < -1.0f) ? -1.0f : pan; - // compute playback volume based on volume and pan, using balance rule, in order to avoid - // lowering volume when panning in center - pSynthData->mVolume[AudioTrack::LEFT] = vol; - pSynthData->mVolume[AudioTrack::RIGHT] = vol; - if (panning > 0.0f) { - pSynthData->mVolume[AudioTrack::LEFT] *= (1.0f - panning); - } else if (panning < 0.0f) { - pSynthData->mVolume[AudioTrack::RIGHT] *= (1.0f + panning); - } - - // apply the volume if there is an output - if (NULL != pSynthData->mAudioOut) { - pSynthData->mAudioOut->setVolume(pSynthData->mVolume[AudioTrack::LEFT], - pSynthData->mVolume[AudioTrack::RIGHT]); - } - - //LOGV("android_tts_SynthProxy_speak() vol=%.3f pan=%.3f, mVolume=[%.1f %.1f]", - // volume, pan, - // pSynthData->mVolume[AudioTrack::LEFT], pSynthData->mVolume[AudioTrack::RIGHT]); - } - - afterSynthData_t* pForAfter = new (afterSynthData_t); - pForAfter->jniStorage = jniData; - pForAfter->usageMode = USAGEMODE_PLAY_IMMEDIATELY; - pForAfter->streamType = (AudioSystem::stream_type) javaStreamType; - - if (pSynthData->mEngine) { - const char *textNativeString = env->GetStringUTFChars(textJavaString, 0); - memset(pSynthData->mBuffer, 0, pSynthData->mBufferSize); - android_tts_engine_t *engine = pSynthData->mEngine; - - result = engine->funcs->synthesizeText(engine, textNativeString, - pSynthData->mBuffer, pSynthData->mBufferSize, (void *)pForAfter); - env->ReleaseStringUTFChars(textJavaString, textNativeString); - } - - return result; -} - - -static int -android_tts_SynthProxy_stop(JNIEnv *env, jobject thiz, jint jniData) -{ - int result = ANDROID_TTS_FAILURE; - - if (jniData == 0) { - LOGE("android_tts_SynthProxy_stop(): invalid JNI data"); - return result; - } - - SynthProxyJniStorage* pSynthData = (SynthProxyJniStorage*)jniData; - - pSynthData->mPlayLock.lock(); - pSynthData->mPlayState = SYNTHPLAYSTATE_IS_STOPPED; - if (pSynthData->mAudioOut) { - pSynthData->mAudioOut->stop(); - } - pSynthData->mPlayLock.unlock(); - - android_tts_engine_t *engine = pSynthData->mEngine; - if (engine) { - result = engine->funcs->stop(engine); - } - - return result; -} - - -static int -android_tts_SynthProxy_stopSync(JNIEnv *env, jobject thiz, jint jniData) -{ - int result = ANDROID_TTS_FAILURE; - - if (jniData == 0) { - LOGE("android_tts_SynthProxy_stop(): invalid JNI data"); - return result; - } - - // perform a regular stop - result = android_tts_SynthProxy_stop(env, thiz, jniData); - // but wait on the engine having released the engine mutex which protects - // the synthesizer resources. - engineMutex.lock(); - engineMutex.unlock(); - - return result; -} - - -static jobjectArray -android_tts_SynthProxy_getLanguage(JNIEnv *env, jobject thiz, jint jniData) -{ - if (jniData == 0) { - LOGE("android_tts_SynthProxy_getLanguage(): invalid JNI data"); - return NULL; - } - - SynthProxyJniStorage* pSynthData = (SynthProxyJniStorage*)jniData; - - if (pSynthData->mEngine) { - size_t bufSize = 100; - char lang[bufSize]; - char country[bufSize]; - char variant[bufSize]; - memset(lang, 0, bufSize); - memset(country, 0, bufSize); - memset(variant, 0, bufSize); - jobjectArray retLocale = (jobjectArray)env->NewObjectArray(3, - env->FindClass("java/lang/String"), env->NewStringUTF("")); - - android_tts_engine_t *engine = pSynthData->mEngine; - engine->funcs->getLanguage(engine, lang, country, variant); - env->SetObjectArrayElement(retLocale, 0, env->NewStringUTF(lang)); - env->SetObjectArrayElement(retLocale, 1, env->NewStringUTF(country)); - env->SetObjectArrayElement(retLocale, 2, env->NewStringUTF(variant)); - return retLocale; - } else { - return NULL; - } -} - - -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 - android_tts_engine_t *engine = pSynthData->mEngine; - if (engine) { - engine->funcs->getProperty(engine,"rate", buf, &bufSize); - } - return atoi(buf); -} - -// Dalvik VM type signatures -static JNINativeMethod gMethods[] = { - { "native_stop", - "(I)I", - (void*)android_tts_SynthProxy_stop - }, - { "native_stopSync", - "(I)I", - (void*)android_tts_SynthProxy_stopSync - }, - { "native_speak", - "(ILjava/lang/String;IFF)I", - (void*)android_tts_SynthProxy_speak - }, - { "native_synthesizeToFile", - "(ILjava/lang/String;Ljava/lang/String;)I", - (void*)android_tts_SynthProxy_synthesizeToFile - }, - { "native_isLanguageAvailable", - "(ILjava/lang/String;Ljava/lang/String;Ljava/lang/String;)I", - (void*)android_tts_SynthProxy_isLanguageAvailable - }, - { "native_setConfig", - "(ILjava/lang/String;)I", - (void*)android_tts_SynthProxy_setConfig - }, - { "native_setLanguage", - "(ILjava/lang/String;Ljava/lang/String;Ljava/lang/String;)I", - (void*)android_tts_SynthProxy_setLanguage - }, - { "native_loadLanguage", - "(ILjava/lang/String;Ljava/lang/String;Ljava/lang/String;)I", - (void*)android_tts_SynthProxy_loadLanguage - }, - { "native_setSpeechRate", - "(II)I", - (void*)android_tts_SynthProxy_setSpeechRate - }, - { "native_setPitch", - "(II)I", - (void*)android_tts_SynthProxy_setPitch - }, - { "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;Ljava/lang/String;)I", - (void*)android_tts_SynthProxy_native_setup - }, - { "native_setLowShelf", - "(ZFFFF)I", - (void*)android_tts_SynthProxy_setLowShelf - }, - { "native_finalize", - "(I)V", - (void*)android_tts_SynthProxy_native_finalize - } -}; - -#define SP_JNIDATA_FIELD_NAME "mJniData" -#define SP_POSTSPEECHSYNTHESIZED_METHOD_NAME "postNativeSpeechSynthesizedInJava" - -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.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; -} diff --git a/packages/TtsService/proguard.flags b/packages/TtsService/proguard.flags deleted file mode 100644 index e8bee6b..0000000 --- a/packages/TtsService/proguard.flags +++ /dev/null @@ -1,5 +0,0 @@ --keep class android.tts.SynthProxy { - int mJniData; - # keep all declarations for native methods - <methods>; -} diff --git a/packages/TtsService/res/drawable-hdpi/ic_launcher_text_to_speech.png b/packages/TtsService/res/drawable-hdpi/ic_launcher_text_to_speech.png Binary files differdeleted file mode 100644 index f075e0f..0000000 --- a/packages/TtsService/res/drawable-hdpi/ic_launcher_text_to_speech.png +++ /dev/null diff --git a/packages/TtsService/res/drawable-mdpi/ic_launcher_text_to_speech.png b/packages/TtsService/res/drawable-mdpi/ic_launcher_text_to_speech.png Binary files differdeleted file mode 100644 index cbae7de..0000000 --- a/packages/TtsService/res/drawable-mdpi/ic_launcher_text_to_speech.png +++ /dev/null diff --git a/packages/TtsService/src/android/tts/SynthProxy.java b/packages/TtsService/src/android/tts/SynthProxy.java deleted file mode 100755 index f5f5fcf..0000000 --- a/packages/TtsService/src/android/tts/SynthProxy.java +++ /dev/null @@ -1,238 +0,0 @@ -/* - * 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.media.AudioManager; -import android.media.AudioSystem; -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 { - - // Default parameters of a filter to be applied when using the Pico engine. - // Such a huge filter gain is justified by how much energy in the low frequencies is "wasted" at - // the output of the synthesis. The low shelving filter removes it, leaving room for - // amplification. - private final static float PICO_FILTER_GAIN = 5.0f; // linear gain - private final static float PICO_FILTER_LOWSHELF_ATTENUATION = -18.0f; // in dB - private final static float PICO_FILTER_TRANSITION_FREQ = 1100.0f; // in Hz - private final static float PICO_FILTER_SHELF_SLOPE = 1.0f; // Q - - // - // External API - // - - /** - * Constructor; pass the location of the native TTS .so to use. - */ - public SynthProxy(String nativeSoLib, String engineConfig) { - boolean applyFilter = nativeSoLib.toLowerCase().contains("pico"); - Log.v(TtsService.SERVICE_TAG, "About to load "+ nativeSoLib + ", applyFilter="+applyFilter); - native_setup(new WeakReference<SynthProxy>(this), nativeSoLib, engineConfig); - native_setLowShelf(applyFilter, PICO_FILTER_GAIN, PICO_FILTER_LOWSHELF_ATTENUATION, - PICO_FILTER_TRANSITION_FREQ, PICO_FILTER_SHELF_SLOPE); - } - - /** - * Stops and clears the AudioTrack. - */ - public int stop() { - return native_stop(mJniData); - } - - /** - * Synchronous stop of the synthesizer. This method returns when the synth - * has completed the stop procedure and doesn't use any of the resources it - * was using while synthesizing. - * - * @return {@link android.speech.tts.TextToSpeech.SUCCESS} or - * {@link android.speech.tts.TextToSpeech.ERROR} - */ - public int stopSync() { - return native_stopSync(mJniData); - } - - /** - * Synthesize speech and speak it directly using AudioTrack. - */ - public int speak(String text, int streamType, float volume, float pan) { - Log.i(TAG, "speak() on stream "+ streamType); - if ((streamType > -1) && (streamType < AudioSystem.getNumStreamTypes())) { - return native_speak(mJniData, text, streamType, volume, pan); - } else { - Log.e("SynthProxy", "Trying to speak with invalid stream type " + streamType); - return native_speak(mJniData, text, AudioManager.STREAM_MUSIC, volume, pan); - } - } - - /** - * 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 int synthesizeToFile(String text, String filename) { - Log.i(TAG, "synthesizeToFile() to file "+ filename); - return native_synthesizeToFile(mJniData, text, filename); - } - - /** - * Queries for language support. - * Return codes are defined in android.speech.tts.TextToSpeech - */ - public int isLanguageAvailable(String language, String country, String variant) { - return native_isLanguageAvailable(mJniData, language, country, variant); - } - - /** - * Updates the engine configuration. - */ - public int setConfig(String engineConfig) { - return native_setConfig(mJniData, engineConfig); - } - - /** - * Sets the language. - */ - public int setLanguage(String language, String country, String variant) { - return native_setLanguage(mJniData, language, country, variant); - } - - /** - * Loads the language: it's not set, but prepared for use later. - */ - public int loadLanguage(String language, String country, String variant) { - return native_loadLanguage(mJniData, language, country, variant); - } - - /** - * Sets the speech rate. - */ - public final int setSpeechRate(int speechRate) { - return native_setSpeechRate(mJniData, speechRate); - } - - /** - * Sets the pitch of the synthesized voice. - */ - public final int setPitch(int pitch) { - return native_setPitch(mJniData, pitch); - } - - /** - * Returns the currently set language, country and variant information. - */ - 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("ttssynthproxy"); - } - - private final static String TAG = "SynthProxy"; - - /** - * Accessed by native methods - */ - private int mJniData = 0; - - private native final int native_setup(Object weak_this, String nativeSoLib, - String engineConfig); - - private native final int native_setLowShelf(boolean applyFilter, float filterGain, - float attenuationInDb, float freqInHz, float slope); - - private native final void native_finalize(int jniData); - - private native final int native_stop(int jniData); - - private native final int native_stopSync(int jniData); - - private native final int native_speak(int jniData, String text, int streamType, float volume, - float pan); - - private native final int native_synthesizeToFile(int jniData, String text, String filename); - - private native final int native_isLanguageAvailable(int jniData, String language, - String country, String variant); - - private native final int native_setLanguage(int jniData, String language, String country, - String variant); - - private native final int native_loadLanguage(int jniData, String language, String country, - String variant); - - private native final int native_setConfig(int jniData, String engineConfig); - - private native final int native_setSpeechRate(int jniData, int speechRate); - - private native final int native_setPitch(int jniData, int speechRate); - - 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. - } -} diff --git a/packages/TtsService/src/android/tts/TtsService.java b/packages/TtsService/src/android/tts/TtsService.java deleted file mode 100755 index c562327..0000000 --- a/packages/TtsService/src/android/tts/TtsService.java +++ /dev/null @@ -1,1503 +0,0 @@ -/* - * 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.app.Service; -import android.content.ContentResolver; -import android.content.Context; -import android.content.Intent; -import android.content.SharedPreferences; -import android.content.pm.ActivityInfo; -import android.content.pm.PackageManager; -import android.content.pm.PackageManager.NameNotFoundException; -import android.content.pm.ResolveInfo; -import android.database.Cursor; -import android.media.AudioManager; -import android.media.MediaPlayer; -import android.media.MediaPlayer.OnCompletionListener; -import android.net.Uri; -import android.os.IBinder; -import android.os.RemoteCallbackList; -import android.os.RemoteException; -import android.preference.PreferenceManager; -import android.speech.tts.ITts.Stub; -import android.speech.tts.ITtsCallback; -import android.speech.tts.TextToSpeech; -import android.util.Log; - -import java.io.File; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Locale; -import java.util.concurrent.locks.ReentrantLock; -import java.util.concurrent.TimeUnit; - - -/** - * @hide Synthesizes speech from text. This is implemented as a service so that - * other applications can call the TTS without needing to bundle the TTS - * in the build. - * - */ -public class TtsService extends Service implements OnCompletionListener { - - private static class SpeechItem { - public static final int TEXT = 0; - public static final int EARCON = 1; - public static final int SILENCE = 2; - public static final int TEXT_TO_FILE = 3; - public String mText = ""; - public ArrayList<String> mParams = null; - public int mType = TEXT; - public long mDuration = 0; - public String mFilename = null; - public String mCallingApp = ""; - - public SpeechItem(String source, String text, ArrayList<String> params, int itemType) { - mText = text; - mParams = params; - mType = itemType; - mCallingApp = source; - } - - public SpeechItem(String source, long silenceTime, ArrayList<String> params) { - mDuration = silenceTime; - mParams = params; - mType = SILENCE; - mCallingApp = source; - } - - public SpeechItem(String source, String text, ArrayList<String> params, - int itemType, String filename) { - mText = text; - mParams = params; - mType = itemType; - mFilename = filename; - mCallingApp = source; - } - - } - - /** - * Contains the information needed to access a sound resource; the name of - * the package that contains the resource and the resID of the resource - * within that package. - */ - private static class SoundResource { - public String mSourcePackageName = null; - public int mResId = -1; - public String mFilename = null; - - public SoundResource(String packageName, int id) { - mSourcePackageName = packageName; - mResId = id; - mFilename = null; - } - - public SoundResource(String file) { - mSourcePackageName = null; - mResId = -1; - mFilename = file; - } - } - // If the speech queue is locked for more than 5 seconds, something has gone - // very wrong with processSpeechQueue. - private static final int SPEECHQUEUELOCK_TIMEOUT = 5000; - private static final int MAX_SPEECH_ITEM_CHAR_LENGTH = 4000; - private static final int MAX_FILENAME_LENGTH = 250; - private static final int DEFAULT_STREAM_TYPE = AudioManager.STREAM_MUSIC; - // TODO use TextToSpeech.DEFAULT_SYNTH once it is unhidden - private static final String DEFAULT_SYNTH = "com.svox.pico"; - private static final String ACTION = "android.intent.action.START_TTS_SERVICE"; - private static final String CATEGORY = "android.intent.category.TTS"; - private static final String PKGNAME = "android.tts"; - protected static final String SERVICE_TAG = "TtsService"; - - private final RemoteCallbackList<ITtsCallback> mCallbacks - = new RemoteCallbackList<ITtsCallback>(); - - private HashMap<String, ITtsCallback> mCallbacksMap; - - private Boolean mIsSpeaking; - private Boolean mSynthBusy; - private ArrayList<SpeechItem> mSpeechQueue; - private HashMap<String, SoundResource> mEarcons; - private HashMap<String, SoundResource> mUtterances; - private MediaPlayer mPlayer; - private SpeechItem mCurrentSpeechItem; - private HashMap<SpeechItem, Boolean> mKillList; // Used to ensure that in-flight synth calls - // are killed when stop is used. - private TtsService mSelf; - - private ContentResolver mResolver; - - // lock for the speech queue (mSpeechQueue) and the current speech item (mCurrentSpeechItem) - private final ReentrantLock speechQueueLock = new ReentrantLock(); - private final ReentrantLock synthesizerLock = new ReentrantLock(); - - private static SynthProxy sNativeSynth = null; - private String currentSpeechEngineSOFile = ""; - - @Override - public void onCreate() { - super.onCreate(); - Log.v("TtsService", "TtsService.onCreate()"); - - mResolver = getContentResolver(); - - currentSpeechEngineSOFile = ""; - setEngine(getDefaultEngine()); - - mSelf = this; - mIsSpeaking = false; - mSynthBusy = false; - - mEarcons = new HashMap<String, SoundResource>(); - mUtterances = new HashMap<String, SoundResource>(); - mCallbacksMap = new HashMap<String, android.speech.tts.ITtsCallback>(); - - mSpeechQueue = new ArrayList<SpeechItem>(); - mPlayer = null; - mCurrentSpeechItem = null; - mKillList = new HashMap<SpeechItem, Boolean>(); - - setDefaultSettings(); - } - - @Override - public void onDestroy() { - super.onDestroy(); - - killAllUtterances(); - - // Don't hog the media player - cleanUpPlayer(); - - if (sNativeSynth != null) { - sNativeSynth.shutdown(); - } - sNativeSynth = null; - - // Unregister all callbacks. - mCallbacks.kill(); - - Log.v(SERVICE_TAG, "onDestroy() completed"); - } - - - private int setEngine(String enginePackageName) { - String soFilename = ""; - if (isDefaultEnforced()) { - enginePackageName = getDefaultEngine(); - } - - // Make sure that the engine has been allowed by the user - if (!enginePackageName.equals(DEFAULT_SYNTH)) { - String[] enabledEngines = android.provider.Settings.Secure.getString(mResolver, - android.provider.Settings.Secure.TTS_ENABLED_PLUGINS).split(" "); - boolean isEnabled = false; - for (int i=0; i<enabledEngines.length; i++) { - if (enabledEngines[i].equals(enginePackageName)) { - isEnabled = true; - break; - } - } - if (!isEnabled) { - // Do not use an engine that the user has not enabled; fall back - // to using the default synthesizer. - enginePackageName = DEFAULT_SYNTH; - } - } - - // The SVOX TTS is an exception to how the TTS packaging scheme works - // because it is part of the system and not a 3rd party add-on; thus - // its binary is actually located under /system/lib/ - if (enginePackageName.equals(DEFAULT_SYNTH)) { - soFilename = "/system/lib/libttspico.so"; - } else { - // Find the package - Intent intent = new Intent("android.intent.action.START_TTS_ENGINE"); - intent.setPackage(enginePackageName); - ResolveInfo[] enginesArray = new ResolveInfo[0]; - PackageManager pm = getPackageManager(); - List <ResolveInfo> resolveInfos = pm.queryIntentActivities(intent, 0); - if ((resolveInfos == null) || resolveInfos.isEmpty()) { - Log.e(SERVICE_TAG, "Invalid TTS Engine Package: " + enginePackageName); - return TextToSpeech.ERROR; - } - enginesArray = resolveInfos.toArray(enginesArray); - // Generate the TTS .so filename from the package - ActivityInfo aInfo = enginesArray[0].activityInfo; - soFilename = aInfo.name.replace(aInfo.packageName + ".", "") + ".so"; - soFilename = soFilename.toLowerCase(); - soFilename = "/data/data/" + aInfo.packageName + "/lib/libtts" + soFilename; - } - - if (currentSpeechEngineSOFile.equals(soFilename)) { - return TextToSpeech.SUCCESS; - } - - File f = new File(soFilename); - if (!f.exists()) { - Log.e(SERVICE_TAG, "Invalid TTS Binary: " + soFilename); - return TextToSpeech.ERROR; - } - - if (sNativeSynth != null) { - sNativeSynth.stopSync(); - sNativeSynth.shutdown(); - sNativeSynth = null; - } - - // Load the engineConfig from the plugin if it has any special configuration - // to be loaded. By convention, if an engine wants the TTS framework to pass - // in any configuration, it must put it into its content provider which has the URI: - // content://<packageName>.providers.SettingsProvider - // That content provider must provide a Cursor which returns the String that - // is to be passed back to the native .so file for the plugin when getString(0) is - // called on it. - // Note that the TTS framework does not care what this String data is: it is something - // that comes from the engine plugin and is consumed only by the engine plugin itself. - String engineConfig = ""; - Cursor c = getContentResolver().query(Uri.parse("content://" + enginePackageName - + ".providers.SettingsProvider"), null, null, null, null); - if (c != null){ - c.moveToFirst(); - engineConfig = c.getString(0); - c.close(); - } - sNativeSynth = new SynthProxy(soFilename, engineConfig); - currentSpeechEngineSOFile = soFilename; - return TextToSpeech.SUCCESS; - } - - - - private void setDefaultSettings() { - setLanguage("", this.getDefaultLanguage(), getDefaultCountry(), getDefaultLocVariant()); - - // speech rate - setSpeechRate("", getDefaultRate()); - } - - - private boolean isDefaultEnforced() { - return (android.provider.Settings.Secure.getInt(mResolver, - android.provider.Settings.Secure.TTS_USE_DEFAULTS, - TextToSpeech.Engine.USE_DEFAULTS) - == 1 ); - } - - private String getDefaultEngine() { - String defaultEngine = android.provider.Settings.Secure.getString(mResolver, - android.provider.Settings.Secure.TTS_DEFAULT_SYNTH); - if (defaultEngine == null) { - return TextToSpeech.Engine.DEFAULT_SYNTH; - } else { - return defaultEngine; - } - } - - private int getDefaultRate() { - return android.provider.Settings.Secure.getInt(mResolver, - android.provider.Settings.Secure.TTS_DEFAULT_RATE, - TextToSpeech.Engine.DEFAULT_RATE); - } - - private int getDefaultPitch() { - // Pitch is not user settable; the default pitch is always 100. - return 100; - } - - private String getDefaultLanguage() { - String defaultLang = android.provider.Settings.Secure.getString(mResolver, - android.provider.Settings.Secure.TTS_DEFAULT_LANG); - if (defaultLang == null) { - // no setting found, use the current Locale to determine the default language - return Locale.getDefault().getISO3Language(); - } else { - return defaultLang; - } - } - - - private String getDefaultCountry() { - String defaultCountry = android.provider.Settings.Secure.getString(mResolver, - android.provider.Settings.Secure.TTS_DEFAULT_COUNTRY); - if (defaultCountry == null) { - // no setting found, use the current Locale to determine the default country - return Locale.getDefault().getISO3Country(); - } else { - return defaultCountry; - } - } - - - private String getDefaultLocVariant() { - String defaultVar = android.provider.Settings.Secure.getString(mResolver, - android.provider.Settings.Secure.TTS_DEFAULT_VARIANT); - if (defaultVar == null) { - // no setting found, use the current Locale to determine the default variant - return Locale.getDefault().getVariant(); - } else { - return defaultVar; - } - } - - - private int setSpeechRate(String callingApp, int rate) { - int res = TextToSpeech.ERROR; - try { - if (isDefaultEnforced()) { - res = sNativeSynth.setSpeechRate(getDefaultRate()); - } else { - res = sNativeSynth.setSpeechRate(rate); - } - } catch (NullPointerException e) { - // synth will become null during onDestroy() - res = TextToSpeech.ERROR; - } - return res; - } - - - private int setPitch(String callingApp, int pitch) { - int res = TextToSpeech.ERROR; - try { - res = sNativeSynth.setPitch(pitch); - } catch (NullPointerException e) { - // synth will become null during onDestroy() - res = TextToSpeech.ERROR; - } - return res; - } - - - private int isLanguageAvailable(String lang, String country, String variant) { - int res = TextToSpeech.LANG_NOT_SUPPORTED; - try { - res = sNativeSynth.isLanguageAvailable(lang, country, variant); - } catch (NullPointerException e) { - // synth will become null during onDestroy() - res = TextToSpeech.LANG_NOT_SUPPORTED; - } - return res; - } - - - private String[] getLanguage() { - try { - return sNativeSynth.getLanguage(); - } catch (Exception e) { - return null; - } - } - - - private int setLanguage(String callingApp, String lang, String country, String variant) { - Log.v(SERVICE_TAG, "TtsService.setLanguage(" + lang + ", " + country + ", " + variant + ")"); - int res = TextToSpeech.ERROR; - try { - if (isDefaultEnforced()) { - res = sNativeSynth.setLanguage(getDefaultLanguage(), getDefaultCountry(), - getDefaultLocVariant()); - } else { - res = sNativeSynth.setLanguage(lang, country, variant); - } - } catch (NullPointerException e) { - // synth will become null during onDestroy() - res = TextToSpeech.ERROR; - } - return res; - } - - - /** - * Adds a sound resource to the TTS. - * - * @param text - * The text that should be associated with the sound resource - * @param packageName - * The name of the package which has the sound resource - * @param resId - * The resource ID of the sound within its package - */ - private void addSpeech(String callingApp, String text, String packageName, int resId) { - mUtterances.put(text, new SoundResource(packageName, resId)); - } - - /** - * Adds a sound resource to the TTS. - * - * @param text - * The text that should be associated with the sound resource - * @param filename - * The filename of the sound resource. This must be a complete - * path like: (/sdcard/mysounds/mysoundbite.mp3). - */ - private void addSpeech(String callingApp, String text, String filename) { - mUtterances.put(text, new SoundResource(filename)); - } - - /** - * Adds a sound resource to the TTS as an earcon. - * - * @param earcon - * The text that should be associated with the sound resource - * @param packageName - * The name of the package which has the sound resource - * @param resId - * The resource ID of the sound within its package - */ - private void addEarcon(String callingApp, String earcon, String packageName, int resId) { - mEarcons.put(earcon, new SoundResource(packageName, resId)); - } - - /** - * Adds a sound resource to the TTS as an earcon. - * - * @param earcon - * The text that should be associated with the sound resource - * @param filename - * The filename of the sound resource. This must be a complete - * path like: (/sdcard/mysounds/mysoundbite.mp3). - */ - private void addEarcon(String callingApp, String earcon, String filename) { - mEarcons.put(earcon, new SoundResource(filename)); - } - - /** - * Speaks the given text using the specified queueing mode and parameters. - * - * @param text - * The text that should be spoken - * @param queueMode - * TextToSpeech.TTS_QUEUE_FLUSH for no queue (interrupts all previous utterances), - * TextToSpeech.TTS_QUEUE_ADD for queued - * @param params - * An ArrayList of parameters. This is not implemented for all - * engines. - */ - private int speak(String callingApp, String text, int queueMode, ArrayList<String> params) { - // Log.v(SERVICE_TAG, "TTS service received " + text); - if (queueMode == TextToSpeech.QUEUE_FLUSH) { - stop(callingApp); - } else if (queueMode == 2) { - stopAll(callingApp); - } - mSpeechQueue.add(new SpeechItem(callingApp, text, params, SpeechItem.TEXT)); - if (!mIsSpeaking) { - processSpeechQueue(); - } - return TextToSpeech.SUCCESS; - } - - /** - * Plays the earcon using the specified queueing mode and parameters. - * - * @param earcon - * The earcon that should be played - * @param queueMode - * TextToSpeech.TTS_QUEUE_FLUSH for no queue (interrupts all previous utterances), - * TextToSpeech.TTS_QUEUE_ADD for queued - * @param params - * An ArrayList of parameters. This is not implemented for all - * engines. - */ - private int playEarcon(String callingApp, String earcon, int queueMode, - ArrayList<String> params) { - if (queueMode == TextToSpeech.QUEUE_FLUSH) { - stop(callingApp); - } else if (queueMode == 2) { - stopAll(callingApp); - } - mSpeechQueue.add(new SpeechItem(callingApp, earcon, params, SpeechItem.EARCON)); - if (!mIsSpeaking) { - processSpeechQueue(); - } - return TextToSpeech.SUCCESS; - } - - /** - * Stops all speech output and removes any utterances still in the queue for the calling app. - */ - private int stop(String callingApp) { - int result = TextToSpeech.ERROR; - boolean speechQueueAvailable = false; - try{ - speechQueueAvailable = - speechQueueLock.tryLock(SPEECHQUEUELOCK_TIMEOUT, TimeUnit.MILLISECONDS); - if (speechQueueAvailable) { - Log.i(SERVICE_TAG, "Stopping"); - for (int i = mSpeechQueue.size() - 1; i > -1; i--){ - if (mSpeechQueue.get(i).mCallingApp.equals(callingApp)){ - mSpeechQueue.remove(i); - } - } - if ((mCurrentSpeechItem != null) && - mCurrentSpeechItem.mCallingApp.equals(callingApp)) { - try { - result = sNativeSynth.stop(); - } catch (NullPointerException e1) { - // synth will become null during onDestroy() - result = TextToSpeech.ERROR; - } - mKillList.put(mCurrentSpeechItem, true); - if (mPlayer != null) { - try { - mPlayer.stop(); - } catch (IllegalStateException e) { - // Do nothing, the player is already stopped. - } - } - mIsSpeaking = false; - mCurrentSpeechItem = null; - } else { - result = TextToSpeech.SUCCESS; - } - Log.i(SERVICE_TAG, "Stopped"); - } else { - Log.e(SERVICE_TAG, "TTS stop(): queue locked longer than expected"); - result = TextToSpeech.ERROR; - } - } catch (InterruptedException e) { - Log.e(SERVICE_TAG, "TTS stop: tryLock interrupted"); - e.printStackTrace(); - } finally { - // This check is needed because finally will always run; even if the - // method returns somewhere in the try block. - if (speechQueueAvailable) { - speechQueueLock.unlock(); - } - return result; - } - } - - - /** - * Stops all speech output, both rendered to a file and directly spoken, and removes any - * utterances still in the queue globally. Files that were being written are deleted. - */ - @SuppressWarnings("finally") - private int killAllUtterances() { - int result = TextToSpeech.ERROR; - boolean speechQueueAvailable = false; - - try { - speechQueueAvailable = speechQueueLock.tryLock(SPEECHQUEUELOCK_TIMEOUT, - TimeUnit.MILLISECONDS); - if (speechQueueAvailable) { - // remove every single entry in the speech queue - mSpeechQueue.clear(); - - // clear the current speech item - if (mCurrentSpeechItem != null) { - result = sNativeSynth.stopSync(); - mKillList.put(mCurrentSpeechItem, true); - mIsSpeaking = false; - - // was the engine writing to a file? - if (mCurrentSpeechItem.mType == SpeechItem.TEXT_TO_FILE) { - // delete the file that was being written - if (mCurrentSpeechItem.mFilename != null) { - File tempFile = new File(mCurrentSpeechItem.mFilename); - Log.v(SERVICE_TAG, "Leaving behind " + mCurrentSpeechItem.mFilename); - if (tempFile.exists()) { - Log.v(SERVICE_TAG, "About to delete " - + mCurrentSpeechItem.mFilename); - if (tempFile.delete()) { - Log.v(SERVICE_TAG, "file successfully deleted"); - } - } - } - } - - mCurrentSpeechItem = null; - } - } else { - Log.e(SERVICE_TAG, "TTS killAllUtterances(): queue locked longer than expected"); - result = TextToSpeech.ERROR; - } - } catch (InterruptedException e) { - Log.e(SERVICE_TAG, "TTS killAllUtterances(): tryLock interrupted"); - result = TextToSpeech.ERROR; - } finally { - // This check is needed because finally will always run, even if the - // method returns somewhere in the try block. - if (speechQueueAvailable) { - speechQueueLock.unlock(); - } - return result; - } - } - - - /** - * Stops all speech output and removes any utterances still in the queue globally, except - * those intended to be synthesized to file. - */ - private int stopAll(String callingApp) { - int result = TextToSpeech.ERROR; - boolean speechQueueAvailable = false; - try{ - speechQueueAvailable = - speechQueueLock.tryLock(SPEECHQUEUELOCK_TIMEOUT, TimeUnit.MILLISECONDS); - if (speechQueueAvailable) { - for (int i = mSpeechQueue.size() - 1; i > -1; i--){ - if (mSpeechQueue.get(i).mType != SpeechItem.TEXT_TO_FILE){ - mSpeechQueue.remove(i); - } - } - if ((mCurrentSpeechItem != null) && - ((mCurrentSpeechItem.mType != SpeechItem.TEXT_TO_FILE) || - mCurrentSpeechItem.mCallingApp.equals(callingApp))) { - try { - result = sNativeSynth.stop(); - } catch (NullPointerException e1) { - // synth will become null during onDestroy() - result = TextToSpeech.ERROR; - } - mKillList.put(mCurrentSpeechItem, true); - if (mPlayer != null) { - try { - mPlayer.stop(); - } catch (IllegalStateException e) { - // Do nothing, the player is already stopped. - } - } - mIsSpeaking = false; - mCurrentSpeechItem = null; - } else { - result = TextToSpeech.SUCCESS; - } - Log.i(SERVICE_TAG, "Stopped all"); - } else { - Log.e(SERVICE_TAG, "TTS stopAll(): queue locked longer than expected"); - result = TextToSpeech.ERROR; - } - } catch (InterruptedException e) { - Log.e(SERVICE_TAG, "TTS stopAll: tryLock interrupted"); - e.printStackTrace(); - } finally { - // This check is needed because finally will always run; even if the - // method returns somewhere in the try block. - if (speechQueueAvailable) { - speechQueueLock.unlock(); - } - return result; - } - } - - public void onCompletion(MediaPlayer arg0) { - // mCurrentSpeechItem may become null if it is stopped at the same - // time it completes. - SpeechItem currentSpeechItemCopy = mCurrentSpeechItem; - if (currentSpeechItemCopy != null) { - String callingApp = currentSpeechItemCopy.mCallingApp; - ArrayList<String> params = currentSpeechItemCopy.mParams; - String utteranceId = ""; - if (params != null) { - for (int i = 0; i < params.size() - 1; i = i + 2) { - String param = params.get(i); - if (param.equals(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID)) { - utteranceId = params.get(i + 1); - } - } - } - if (utteranceId.length() > 0) { - dispatchUtteranceCompletedCallback(utteranceId, callingApp); - } - } - processSpeechQueue(); - } - - private int playSilence(String callingApp, long duration, int queueMode, - ArrayList<String> params) { - if (queueMode == TextToSpeech.QUEUE_FLUSH) { - stop(callingApp); - } - mSpeechQueue.add(new SpeechItem(callingApp, duration, params)); - if (!mIsSpeaking) { - processSpeechQueue(); - } - return TextToSpeech.SUCCESS; - } - - private void silence(final SpeechItem speechItem) { - class SilenceThread implements Runnable { - public void run() { - String utteranceId = ""; - if (speechItem.mParams != null){ - for (int i = 0; i < speechItem.mParams.size() - 1; i = i + 2){ - String param = speechItem.mParams.get(i); - if (param.equals(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID)){ - utteranceId = speechItem.mParams.get(i+1); - } - } - } - try { - Thread.sleep(speechItem.mDuration); - } catch (InterruptedException e) { - e.printStackTrace(); - } finally { - if (utteranceId.length() > 0){ - dispatchUtteranceCompletedCallback(utteranceId, speechItem.mCallingApp); - } - processSpeechQueue(); - } - } - } - Thread slnc = (new Thread(new SilenceThread())); - slnc.setPriority(Thread.MIN_PRIORITY); - slnc.start(); - } - - private void speakInternalOnly(final SpeechItem speechItem) { - class SynthThread implements Runnable { - public void run() { - boolean synthAvailable = false; - String utteranceId = ""; - try { - synthAvailable = synthesizerLock.tryLock(); - if (!synthAvailable) { - mSynthBusy = true; - Thread.sleep(100); - Thread synth = (new Thread(new SynthThread())); - synth.start(); - mSynthBusy = false; - return; - } - int streamType = DEFAULT_STREAM_TYPE; - String language = ""; - String country = ""; - String variant = ""; - String speechRate = ""; - String engine = ""; - String pitch = ""; - float volume = TextToSpeech.Engine.DEFAULT_VOLUME; - float pan = TextToSpeech.Engine.DEFAULT_PAN; - if (speechItem.mParams != null){ - for (int i = 0; i < speechItem.mParams.size() - 1; i = i + 2){ - String param = speechItem.mParams.get(i); - if (param != null) { - if (param.equals(TextToSpeech.Engine.KEY_PARAM_RATE)) { - speechRate = speechItem.mParams.get(i+1); - } else if (param.equals(TextToSpeech.Engine.KEY_PARAM_LANGUAGE)){ - language = speechItem.mParams.get(i+1); - } else if (param.equals(TextToSpeech.Engine.KEY_PARAM_COUNTRY)){ - country = speechItem.mParams.get(i+1); - } else if (param.equals(TextToSpeech.Engine.KEY_PARAM_VARIANT)){ - variant = speechItem.mParams.get(i+1); - } else if (param.equals(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID)){ - utteranceId = speechItem.mParams.get(i+1); - } else if (param.equals(TextToSpeech.Engine.KEY_PARAM_STREAM)) { - try { - streamType - = Integer.parseInt(speechItem.mParams.get(i + 1)); - } catch (NumberFormatException e) { - streamType = DEFAULT_STREAM_TYPE; - } - } else if (param.equals(TextToSpeech.Engine.KEY_PARAM_ENGINE)) { - engine = speechItem.mParams.get(i + 1); - } else if (param.equals(TextToSpeech.Engine.KEY_PARAM_PITCH)) { - pitch = speechItem.mParams.get(i + 1); - } else if (param.equals(TextToSpeech.Engine.KEY_PARAM_VOLUME)) { - try { - volume = Float.parseFloat(speechItem.mParams.get(i + 1)); - } catch (NumberFormatException e) { - volume = TextToSpeech.Engine.DEFAULT_VOLUME; - } - } else if (param.equals(TextToSpeech.Engine.KEY_PARAM_PAN)) { - try { - pan = Float.parseFloat(speechItem.mParams.get(i + 1)); - } catch (NumberFormatException e) { - pan = TextToSpeech.Engine.DEFAULT_PAN; - } - } - } - } - } - // Only do the synthesis if it has not been killed by a subsequent utterance. - if (mKillList.get(speechItem) == null) { - if (engine.length() > 0) { - setEngine(engine); - } else { - setEngine(getDefaultEngine()); - } - if (language.length() > 0){ - setLanguage("", language, country, variant); - } else { - setLanguage("", getDefaultLanguage(), getDefaultCountry(), - getDefaultLocVariant()); - } - if (speechRate.length() > 0){ - setSpeechRate("", Integer.parseInt(speechRate)); - } else { - setSpeechRate("", getDefaultRate()); - } - if (pitch.length() > 0){ - setPitch("", Integer.parseInt(pitch)); - } else { - setPitch("", getDefaultPitch()); - } - try { - sNativeSynth.speak(speechItem.mText, streamType, volume, pan); - } catch (NullPointerException e) { - // synth will become null during onDestroy() - Log.v(SERVICE_TAG, " null synth, can't speak"); - } - } - } catch (InterruptedException e) { - Log.e(SERVICE_TAG, "TTS speakInternalOnly(): tryLock interrupted"); - e.printStackTrace(); - } finally { - // This check is needed because finally will always run; - // even if the - // method returns somewhere in the try block. - if (utteranceId.length() > 0){ - dispatchUtteranceCompletedCallback(utteranceId, speechItem.mCallingApp); - } - if (synthAvailable) { - synthesizerLock.unlock(); - processSpeechQueue(); - } - } - } - } - Thread synth = (new Thread(new SynthThread())); - synth.setPriority(Thread.MAX_PRIORITY); - synth.start(); - } - - private void synthToFileInternalOnly(final SpeechItem speechItem) { - class SynthThread implements Runnable { - public void run() { - boolean synthAvailable = false; - String utteranceId = ""; - Log.i(SERVICE_TAG, "Synthesizing to " + speechItem.mFilename); - try { - synthAvailable = synthesizerLock.tryLock(); - if (!synthAvailable) { - synchronized (this) { - mSynthBusy = true; - } - Thread.sleep(100); - Thread synth = (new Thread(new SynthThread())); - synth.start(); - synchronized (this) { - mSynthBusy = false; - } - return; - } - String language = ""; - String country = ""; - String variant = ""; - String speechRate = ""; - String engine = ""; - String pitch = ""; - if (speechItem.mParams != null){ - for (int i = 0; i < speechItem.mParams.size() - 1; i = i + 2){ - String param = speechItem.mParams.get(i); - if (param != null) { - if (param.equals(TextToSpeech.Engine.KEY_PARAM_RATE)) { - speechRate = speechItem.mParams.get(i+1); - } else if (param.equals(TextToSpeech.Engine.KEY_PARAM_LANGUAGE)){ - language = speechItem.mParams.get(i+1); - } else if (param.equals(TextToSpeech.Engine.KEY_PARAM_COUNTRY)){ - country = speechItem.mParams.get(i+1); - } else if (param.equals(TextToSpeech.Engine.KEY_PARAM_VARIANT)){ - variant = speechItem.mParams.get(i+1); - } else if (param.equals(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID)){ - utteranceId = speechItem.mParams.get(i+1); - } else if (param.equals(TextToSpeech.Engine.KEY_PARAM_ENGINE)) { - engine = speechItem.mParams.get(i + 1); - } else if (param.equals(TextToSpeech.Engine.KEY_PARAM_PITCH)) { - pitch = speechItem.mParams.get(i + 1); - } - } - } - } - // Only do the synthesis if it has not been killed by a subsequent utterance. - if (mKillList.get(speechItem) == null){ - if (engine.length() > 0) { - setEngine(engine); - } else { - setEngine(getDefaultEngine()); - } - if (language.length() > 0){ - setLanguage("", language, country, variant); - } else { - setLanguage("", getDefaultLanguage(), getDefaultCountry(), - getDefaultLocVariant()); - } - if (speechRate.length() > 0){ - setSpeechRate("", Integer.parseInt(speechRate)); - } else { - setSpeechRate("", getDefaultRate()); - } - if (pitch.length() > 0){ - setPitch("", Integer.parseInt(pitch)); - } else { - setPitch("", getDefaultPitch()); - } - try { - sNativeSynth.synthesizeToFile(speechItem.mText, speechItem.mFilename); - } catch (NullPointerException e) { - // synth will become null during onDestroy() - Log.v(SERVICE_TAG, " null synth, can't synthesize to file"); - } - } - } catch (InterruptedException e) { - Log.e(SERVICE_TAG, "TTS synthToFileInternalOnly(): tryLock interrupted"); - e.printStackTrace(); - } finally { - // This check is needed because finally will always run; - // even if the - // method returns somewhere in the try block. - if (utteranceId.length() > 0){ - dispatchUtteranceCompletedCallback(utteranceId, speechItem.mCallingApp); - } - if (synthAvailable) { - synthesizerLock.unlock(); - processSpeechQueue(); - } - } - } - } - Thread synth = (new Thread(new SynthThread())); - synth.setPriority(Thread.MAX_PRIORITY); - synth.start(); - } - - private SoundResource getSoundResource(SpeechItem speechItem) { - SoundResource sr = null; - String text = speechItem.mText; - if (speechItem.mType == SpeechItem.SILENCE) { - // Do nothing if this is just silence - } else if (speechItem.mType == SpeechItem.EARCON) { - sr = mEarcons.get(text); - } else { - sr = mUtterances.get(text); - } - return sr; - } - - private void broadcastTtsQueueProcessingCompleted(){ - Intent i = new Intent(TextToSpeech.ACTION_TTS_QUEUE_PROCESSING_COMPLETED); - sendBroadcast(i); - } - - - private void dispatchUtteranceCompletedCallback(String utteranceId, String packageName) { - ITtsCallback cb = mCallbacksMap.get(packageName); - if (cb == null){ - return; - } - Log.v(SERVICE_TAG, "TTS callback: dispatch started"); - // Broadcast to all clients the new value. - final int N = mCallbacks.beginBroadcast(); - try { - cb.utteranceCompleted(utteranceId); - } catch (RemoteException e) { - // The RemoteCallbackList will take care of removing - // the dead object for us. - } - mCallbacks.finishBroadcast(); - Log.v(SERVICE_TAG, "TTS callback: dispatch completed to " + N); - } - - private SpeechItem splitCurrentTextIfNeeded(SpeechItem currentSpeechItem){ - if (currentSpeechItem.mText.length() < MAX_SPEECH_ITEM_CHAR_LENGTH){ - return currentSpeechItem; - } else { - String callingApp = currentSpeechItem.mCallingApp; - ArrayList<SpeechItem> splitItems = new ArrayList<SpeechItem>(); - int start = 0; - int end = start + MAX_SPEECH_ITEM_CHAR_LENGTH - 1; - String splitText; - SpeechItem splitItem; - while (end < currentSpeechItem.mText.length()){ - splitText = currentSpeechItem.mText.substring(start, end); - splitItem = new SpeechItem(callingApp, splitText, null, SpeechItem.TEXT); - splitItems.add(splitItem); - start = end; - end = start + MAX_SPEECH_ITEM_CHAR_LENGTH - 1; - } - splitText = currentSpeechItem.mText.substring(start); - splitItem = new SpeechItem(callingApp, splitText, null, SpeechItem.TEXT); - splitItems.add(splitItem); - mSpeechQueue.remove(0); - for (int i = splitItems.size() - 1; i >= 0; i--){ - mSpeechQueue.add(0, splitItems.get(i)); - } - return mSpeechQueue.get(0); - } - } - - private void processSpeechQueue() { - boolean speechQueueAvailable = false; - synchronized (this) { - if (mSynthBusy){ - // There is already a synth thread waiting to run. - return; - } - } - try { - speechQueueAvailable = - speechQueueLock.tryLock(SPEECHQUEUELOCK_TIMEOUT, TimeUnit.MILLISECONDS); - if (!speechQueueAvailable) { - Log.e(SERVICE_TAG, "processSpeechQueue - Speech queue is unavailable."); - return; - } - if (mSpeechQueue.size() < 1) { - mIsSpeaking = false; - mKillList.clear(); - broadcastTtsQueueProcessingCompleted(); - return; - } - - mCurrentSpeechItem = mSpeechQueue.get(0); - mIsSpeaking = true; - SoundResource sr = getSoundResource(mCurrentSpeechItem); - // Synth speech as needed - synthesizer should call - // processSpeechQueue to continue running the queue - // Log.v(SERVICE_TAG, "TTS processing: " + mCurrentSpeechItem.mText); - if (sr == null) { - if (mCurrentSpeechItem.mType == SpeechItem.TEXT) { - mCurrentSpeechItem = splitCurrentTextIfNeeded(mCurrentSpeechItem); - speakInternalOnly(mCurrentSpeechItem); - } else if (mCurrentSpeechItem.mType == SpeechItem.TEXT_TO_FILE) { - synthToFileInternalOnly(mCurrentSpeechItem); - } else { - // This is either silence or an earcon that was missing - silence(mCurrentSpeechItem); - } - } else { - cleanUpPlayer(); - if (sr.mSourcePackageName == PKGNAME) { - // Utterance is part of the TTS library - mPlayer = MediaPlayer.create(this, sr.mResId); - } else if (sr.mSourcePackageName != null) { - // Utterance is part of the app calling the library - Context ctx; - try { - ctx = this.createPackageContext(sr.mSourcePackageName, 0); - } catch (NameNotFoundException e) { - e.printStackTrace(); - mSpeechQueue.remove(0); // Remove it from the queue and - // move on - mIsSpeaking = false; - return; - } - mPlayer = MediaPlayer.create(ctx, sr.mResId); - } else { - // Utterance is coming from a file - mPlayer = MediaPlayer.create(this, Uri.parse(sr.mFilename)); - } - - // Check if Media Server is dead; if it is, clear the queue and - // give up for now - hopefully, it will recover itself. - if (mPlayer == null) { - mSpeechQueue.clear(); - mIsSpeaking = false; - return; - } - mPlayer.setOnCompletionListener(this); - try { - mPlayer.setAudioStreamType(getStreamTypeFromParams(mCurrentSpeechItem.mParams)); - mPlayer.start(); - } catch (IllegalStateException e) { - mSpeechQueue.clear(); - mIsSpeaking = false; - cleanUpPlayer(); - return; - } - } - if (mSpeechQueue.size() > 0) { - mSpeechQueue.remove(0); - } - } catch (InterruptedException e) { - Log.e(SERVICE_TAG, "TTS processSpeechQueue: tryLock interrupted"); - e.printStackTrace(); - } finally { - // This check is needed because finally will always run; even if the - // method returns somewhere in the try block. - if (speechQueueAvailable) { - speechQueueLock.unlock(); - } - } - } - - 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.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(); - mPlayer = null; - } - } - - /** - * Synthesizes the given text to a file using the specified parameters. - * - * @param text - * The String of text that should be synthesized - * @param params - * An ArrayList of parameters. The first element of this array - * controls the type of voice to use. - * @param filename - * The string that gives the full output filename; it should be - * something like "/sdcard/myappsounds/mysound.wav". - * @return A boolean that indicates if the synthesis can be started - */ - private boolean synthesizeToFile(String callingApp, String text, ArrayList<String> params, - String filename) { - // Don't allow a filename that is too long - if (filename.length() > MAX_FILENAME_LENGTH) { - return false; - } - // Don't allow anything longer than the max text length; since this - // is synthing to a file, don't even bother splitting it. - if (text.length() >= MAX_SPEECH_ITEM_CHAR_LENGTH){ - return false; - } - // Check that the output file can be created - try { - File tempFile = new File(filename); - if (tempFile.exists()) { - Log.v("TtsService", "File " + filename + " exists, deleting."); - tempFile.delete(); - } - if (!tempFile.createNewFile()) { - Log.e("TtsService", "Unable to synthesize to file: can't create " + filename); - return false; - } - tempFile.delete(); - } catch (IOException e) { - Log.e("TtsService", "Can't create " + filename + " due to exception " + e); - return false; - } - mSpeechQueue.add(new SpeechItem(callingApp, text, params, SpeechItem.TEXT_TO_FILE, filename)); - if (!mIsSpeaking) { - processSpeechQueue(); - } - return true; - } - - @Override - public IBinder onBind(Intent intent) { - if (ACTION.equals(intent.getAction())) { - for (String category : intent.getCategories()) { - if (category.equals(CATEGORY)) { - return mBinder; - } - } - } - return null; - } - - private final android.speech.tts.ITts.Stub mBinder = new Stub() { - - public int registerCallback(String packageName, ITtsCallback cb) { - if (cb != null) { - mCallbacks.register(cb); - mCallbacksMap.put(packageName, cb); - return TextToSpeech.SUCCESS; - } - return TextToSpeech.ERROR; - } - - public int unregisterCallback(String packageName, ITtsCallback cb) { - if (cb != null) { - mCallbacksMap.remove(packageName); - mCallbacks.unregister(cb); - return TextToSpeech.SUCCESS; - } - return TextToSpeech.ERROR; - } - - /** - * Speaks the given text using the specified queueing mode and - * parameters. - * - * @param text - * The text that should be spoken - * @param queueMode - * TextToSpeech.TTS_QUEUE_FLUSH for no queue (interrupts all previous utterances) - * TextToSpeech.TTS_QUEUE_ADD for queued - * @param params - * An ArrayList of parameters. The first element of this - * array controls the type of voice to use. - */ - public int speak(String callingApp, String text, int queueMode, String[] params) { - ArrayList<String> speakingParams = new ArrayList<String>(); - if (params != null) { - speakingParams = new ArrayList<String>(Arrays.asList(params)); - } - return mSelf.speak(callingApp, text, queueMode, speakingParams); - } - - /** - * Plays the earcon using the specified queueing mode and parameters. - * - * @param earcon - * The earcon that should be played - * @param queueMode - * TextToSpeech.TTS_QUEUE_FLUSH for no queue (interrupts all previous utterances) - * TextToSpeech.TTS_QUEUE_ADD for queued - * @param params - * An ArrayList of parameters. - */ - public int playEarcon(String callingApp, String earcon, int queueMode, String[] params) { - ArrayList<String> speakingParams = new ArrayList<String>(); - if (params != null) { - speakingParams = new ArrayList<String>(Arrays.asList(params)); - } - return mSelf.playEarcon(callingApp, earcon, queueMode, speakingParams); - } - - /** - * Plays the silence using the specified queueing mode and parameters. - * - * @param duration - * The duration of the silence that should be played - * @param queueMode - * TextToSpeech.TTS_QUEUE_FLUSH for no queue (interrupts all previous utterances) - * TextToSpeech.TTS_QUEUE_ADD for queued - * @param params - * An ArrayList of parameters. - */ - public int playSilence(String callingApp, long duration, int queueMode, String[] params) { - ArrayList<String> speakingParams = new ArrayList<String>(); - if (params != null) { - speakingParams = new ArrayList<String>(Arrays.asList(params)); - } - return mSelf.playSilence(callingApp, duration, queueMode, speakingParams); - } - - /** - * Stops all speech output and removes any utterances still in the - * queue. - */ - public int stop(String callingApp) { - return mSelf.stop(callingApp); - } - - /** - * Returns whether or not the TTS is speaking. - * - * @return Boolean to indicate whether or not the TTS is speaking - */ - public boolean isSpeaking() { - return (mSelf.mIsSpeaking && (mSpeechQueue.size() < 1)); - } - - /** - * Adds a sound resource to the TTS. - * - * @param text - * The text that should be associated with the sound resource - * @param packageName - * The name of the package which has the sound resource - * @param resId - * The resource ID of the sound within its package - */ - public void addSpeech(String callingApp, String text, String packageName, int resId) { - mSelf.addSpeech(callingApp, text, packageName, resId); - } - - /** - * Adds a sound resource to the TTS. - * - * @param text - * The text that should be associated with the sound resource - * @param filename - * The filename of the sound resource. This must be a - * complete path like: (/sdcard/mysounds/mysoundbite.mp3). - */ - public void addSpeechFile(String callingApp, String text, String filename) { - mSelf.addSpeech(callingApp, text, filename); - } - - /** - * Adds a sound resource to the TTS as an earcon. - * - * @param earcon - * The text that should be associated with the sound resource - * @param packageName - * The name of the package which has the sound resource - * @param resId - * The resource ID of the sound within its package - */ - public void addEarcon(String callingApp, String earcon, String packageName, int resId) { - mSelf.addEarcon(callingApp, earcon, packageName, resId); - } - - /** - * Adds a sound resource to the TTS as an earcon. - * - * @param earcon - * The text that should be associated with the sound resource - * @param filename - * The filename of the sound resource. This must be a - * complete path like: (/sdcard/mysounds/mysoundbite.mp3). - */ - public void addEarconFile(String callingApp, String earcon, String filename) { - mSelf.addEarcon(callingApp, earcon, filename); - } - - /** - * Sets the speech rate for the TTS. Note that this will only have an - * effect on synthesized speech; it will not affect pre-recorded speech. - * - * @param speechRate - * The speech rate that should be used - */ - public int setSpeechRate(String callingApp, int speechRate) { - return mSelf.setSpeechRate(callingApp, speechRate); - } - - /** - * Sets the pitch for the TTS. Note that this will only have an - * effect on synthesized speech; it will not affect pre-recorded speech. - * - * @param pitch - * The pitch that should be used for the synthesized voice - */ - public int setPitch(String callingApp, int pitch) { - return mSelf.setPitch(callingApp, pitch); - } - - /** - * Returns the level of support for the specified language. - * - * @param lang the three letter ISO language code. - * @param country the three letter ISO country code. - * @param variant the variant code associated with the country and language pair. - * @return one of TTS_LANG_NOT_SUPPORTED, TTS_LANG_MISSING_DATA, TTS_LANG_AVAILABLE, - * TTS_LANG_COUNTRY_AVAILABLE, TTS_LANG_COUNTRY_VAR_AVAILABLE as defined in - * android.speech.tts.TextToSpeech. - */ - public int isLanguageAvailable(String lang, String country, String variant, - String[] params) { - for (int i = 0; i < params.length - 1; i = i + 2){ - String param = params[i]; - if (param != null) { - if (param.equals(TextToSpeech.Engine.KEY_PARAM_ENGINE)) { - mSelf.setEngine(params[i + 1]); - break; - } - } - } - return mSelf.isLanguageAvailable(lang, country, variant); - } - - /** - * Returns the currently set language / country / variant strings representing the - * language used by the TTS engine. - * @return null is no language is set, or an array of 3 string containing respectively - * the language, country and variant. - */ - public String[] getLanguage() { - return mSelf.getLanguage(); - } - - /** - * Sets the speech rate for the TTS, which affects the synthesized voice. - * - * @param lang the three letter ISO language code. - * @param country the three letter ISO country code. - * @param variant the variant code associated with the country and language pair. - */ - public int setLanguage(String callingApp, String lang, String country, String variant) { - return mSelf.setLanguage(callingApp, lang, country, variant); - } - - /** - * Synthesizes the given text to a file using the specified - * parameters. - * - * @param text - * The String of text that should be synthesized - * @param params - * An ArrayList of parameters. The first element of this - * array controls the type of voice to use. - * @param filename - * The string that gives the full output filename; it should - * be something like "/sdcard/myappsounds/mysound.wav". - * @return A boolean that indicates if the synthesis succeeded - */ - public boolean synthesizeToFile(String callingApp, String text, String[] params, - String filename) { - ArrayList<String> speakingParams = new ArrayList<String>(); - if (params != null) { - speakingParams = new ArrayList<String>(Arrays.asList(params)); - } - return mSelf.synthesizeToFile(callingApp, text, speakingParams, filename); - } - - /** - * Sets the speech synthesis engine for the TTS by specifying its packagename - * - * @param packageName the packageName of the speech synthesis engine (ie, "com.svox.pico") - * - * @return SUCCESS or ERROR as defined in android.speech.tts.TextToSpeech. - */ - public int setEngineByPackageName(String packageName) { - return mSelf.setEngine(packageName); - } - - /** - * Returns the packagename of the default speech synthesis engine. - * - * @return Packagename of the TTS engine that the user has chosen as their default. - */ - public String getDefaultEngine() { - return mSelf.getDefaultEngine(); - } - - /** - * Returns whether or not the user is forcing their defaults to override the - * Text-To-Speech settings set by applications. - * - * @return Whether or not defaults are enforced. - */ - public boolean areDefaultsEnforced() { - return mSelf.isDefaultEnforced(); - } - - }; - -} |