From e22b69a7de0349b99d3107349d1d3aa72d62c841 Mon Sep 17 00:00:00 2001 From: Narayan Kamath Date: Wed, 8 Jun 2011 11:41:47 +0100 Subject: [PATCH] Make changes to the TTS api suggested by the API review. This ended up making the implementation a lot cleaner as well. See the bug listed below for some background. bug:4553470 Change-Id: If16476a57e389c2f9b228f6548e426642d292b49 --- api/current.txt | 12 +- .../speech/tts/AbstractSynthesisCallback.java | 34 +++++ ...equest.java => FileSynthesisCallback.java} | 6 +- ...st.java => PlaybackSynthesisCallback.java} | 9 +- .../android/speech/tts/SynthesisCallback.java | 107 +++++++++++++ .../android/speech/tts/SynthesisRequest.java | 140 ++++-------------- .../speech/tts/TextToSpeechService.java | 72 ++++++--- 7 files changed, 236 insertions(+), 144 deletions(-) create mode 100644 core/java/android/speech/tts/AbstractSynthesisCallback.java rename core/java/android/speech/tts/{FileSynthesisRequest.java => FileSynthesisCallback.java} (97%) rename core/java/android/speech/tts/{PlaybackSynthesisRequest.java => PlaybackSynthesisCallback.java} (95%) create mode 100644 core/java/android/speech/tts/SynthesisCallback.java diff --git a/api/current.txt b/api/current.txt index 80d0687a88737..bc4ef0c1f5036 100644 --- a/api/current.txt +++ b/api/current.txt @@ -17446,21 +17446,23 @@ package android.speech { package android.speech.tts { - public abstract class SynthesisRequest { - ctor public SynthesisRequest(java.lang.String, android.os.Bundle); + public abstract interface SynthesisCallback { method public abstract int audioAvailable(byte[], int, int); method public abstract int completeAudioAvailable(int, int, int, byte[], int, int); method public abstract int done(); method public abstract void error(); + method public abstract int getMaxBufferSize(); + method public abstract int start(int, int, int); + } + + public final class SynthesisRequest { method public java.lang.String getCountry(); method public java.lang.String getLanguage(); - method public abstract int getMaxBufferSize(); method public android.os.Bundle getParams(); method public int getPitch(); method public int getSpeechRate(); method public java.lang.String getText(); method public java.lang.String getVariant(); - method public abstract int start(int, int, int); } public class TextToSpeech { @@ -17546,7 +17548,7 @@ package android.speech.tts { method protected abstract int onIsLanguageAvailable(java.lang.String, java.lang.String, java.lang.String); method protected abstract int onLoadLanguage(java.lang.String, java.lang.String, java.lang.String); method protected abstract void onStop(); - method protected abstract void onSynthesizeText(android.speech.tts.SynthesisRequest); + method protected abstract void onSynthesizeText(android.speech.tts.SynthesisRequest, android.speech.tts.SynthesisCallback); } } diff --git a/core/java/android/speech/tts/AbstractSynthesisCallback.java b/core/java/android/speech/tts/AbstractSynthesisCallback.java new file mode 100644 index 0000000000000..c7a4af0bf533d --- /dev/null +++ b/core/java/android/speech/tts/AbstractSynthesisCallback.java @@ -0,0 +1,34 @@ +/* + * 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; + +/** + * Defines additional methods the synthesis callback must implement that + * are private to the TTS service implementation. + */ +abstract class AbstractSynthesisCallback implements SynthesisCallback { + /** + * Checks whether the synthesis request completed successfully. + */ + abstract boolean isDone(); + + /** + * Aborts the speech request. + * + * Can be called from multiple threads. + */ + abstract void stop(); +} diff --git a/core/java/android/speech/tts/FileSynthesisRequest.java b/core/java/android/speech/tts/FileSynthesisCallback.java similarity index 97% rename from core/java/android/speech/tts/FileSynthesisRequest.java rename to core/java/android/speech/tts/FileSynthesisCallback.java index 62be2bf7ca4b4..4f4b3fb6f2038 100644 --- a/core/java/android/speech/tts/FileSynthesisRequest.java +++ b/core/java/android/speech/tts/FileSynthesisCallback.java @@ -16,7 +16,6 @@ package android.speech.tts; import android.media.AudioFormat; -import android.os.Bundle; import android.util.Log; import java.io.File; @@ -29,7 +28,7 @@ import java.nio.ByteOrder; /** * Speech synthesis request that writes the audio to a WAV file. */ -class FileSynthesisRequest extends SynthesisRequest { +class FileSynthesisCallback extends AbstractSynthesisCallback { private static final String TAG = "FileSynthesisRequest"; private static final boolean DBG = false; @@ -48,8 +47,7 @@ class FileSynthesisRequest extends SynthesisRequest { private boolean mStopped = false; private boolean mDone = false; - FileSynthesisRequest(String text, Bundle params, File fileName) { - super(text, params); + FileSynthesisCallback(File fileName) { mFileName = fileName; } diff --git a/core/java/android/speech/tts/PlaybackSynthesisRequest.java b/core/java/android/speech/tts/PlaybackSynthesisCallback.java similarity index 95% rename from core/java/android/speech/tts/PlaybackSynthesisRequest.java rename to core/java/android/speech/tts/PlaybackSynthesisCallback.java index 34b263cb74ef4..1888dda9f3a36 100644 --- a/core/java/android/speech/tts/PlaybackSynthesisRequest.java +++ b/core/java/android/speech/tts/PlaybackSynthesisCallback.java @@ -15,14 +15,13 @@ */ package android.speech.tts; -import android.os.Bundle; import android.speech.tts.TextToSpeechService.UtteranceCompletedDispatcher; import android.util.Log; /** * Speech synthesis request that plays the audio as it is received. */ -class PlaybackSynthesisRequest extends SynthesisRequest { +class PlaybackSynthesisCallback extends AbstractSynthesisCallback { private static final String TAG = "PlaybackSynthesisRequest"; private static final boolean DBG = false; @@ -66,9 +65,9 @@ class PlaybackSynthesisRequest extends SynthesisRequest { private final UtteranceCompletedDispatcher mDispatcher; - PlaybackSynthesisRequest(String text, Bundle params, int streamType, float volume, float pan, - AudioPlaybackHandler audioTrackHandler, UtteranceCompletedDispatcher dispatcher) { - super(text, params); + PlaybackSynthesisCallback(int streamType, float volume, float pan, + AudioPlaybackHandler audioTrackHandler, + UtteranceCompletedDispatcher dispatcher) { mStreamType = streamType; mVolume = volume; mPan = pan; diff --git a/core/java/android/speech/tts/SynthesisCallback.java b/core/java/android/speech/tts/SynthesisCallback.java new file mode 100644 index 0000000000000..1b80e40e2582b --- /dev/null +++ b/core/java/android/speech/tts/SynthesisCallback.java @@ -0,0 +1,107 @@ +/* + * 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 callback to return speech data synthesized by a text to speech engine. + * + * The engine can provide streaming audio by calling + * {@link #start}, then {@link #audioAvailable} until all audio has been provided, then finally + * {@link #done}. + * + * Alternatively, the engine can provide all the audio at once, by using + * {@link #completeAudioAvailable}. + * + * {@link #error} can be called at any stage in the synthesis process to + * indicate that an error has occured, but if the call is made after a call + * to {@link #done} or {@link #completeAudioAvailable} it might be discarded. + */ +public interface SynthesisCallback { + /** + * @return the maximum number of bytes that the TTS engine can pass in a single call of + * {@link #audioAvailable}. This does not apply to {@link #completeAudioAvailable}. + * Calls to {@link #audioAvailable} with data lengths larger than this + * value will not succeed. + */ + public int getMaxBufferSize(); + + /** + * 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. Must be {@code 1} or {@code 2}. + * @return {@link TextToSpeech#SUCCESS} or {@link TextToSpeech#ERROR}. + */ + public 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}. This must be + * less than or equal to the return value of {@link #getMaxBufferSize}. + * @return {@link TextToSpeech#SUCCESS} or {@link TextToSpeech#ERROR}. + */ + public int audioAvailable(byte[] buffer, int offset, int length); + + /** + * The service can call this method instead of using {@link #start}, {@link #audioAvailable} + * and {@link #done} if all the audio data is available in a single buffer. + * + * @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. Must be {@code 1} or {@code 2}. + * @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}. + * @return {@link TextToSpeech#SUCCESS} or {@link TextToSpeech#ERROR}. + */ + public int completeAudioAvailable(int sampleRateInHz, int audioFormat, + int channelCount, 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 int done(); + + /** + * The service should call this method if the speech synthesis fails. + * + * This method should only be called on the synthesis thread, + * while in {@link TextToSpeechService#onSynthesizeText}. + */ + public void error(); + +} \ No newline at end of file diff --git a/core/java/android/speech/tts/SynthesisRequest.java b/core/java/android/speech/tts/SynthesisRequest.java index 57ae10d838b41..ef1704cae0889 100644 --- a/core/java/android/speech/tts/SynthesisRequest.java +++ b/core/java/android/speech/tts/SynthesisRequest.java @@ -18,17 +18,22 @@ package android.speech.tts; import android.os.Bundle; /** - * A request for speech synthesis given to a TTS engine for processing. + * Contains data required by engines to synthesize speech. This data is : + * * - * The engine can provide streaming audio by calling - * {@link #start}, then {@link #audioAvailable} until all audio has been provided, then finally - * {@link #done}. - * - * Alternatively, the engine can provide all the audio at once, by using - * {@link #completeAudioAvailable}. + * Any additional parameters sent to the text to speech service are passed in + * uninterpreted, see the @code{params} argument in {@link TextToSpeech#speak} + * and {@link TextToSpeech#synthesizeToFile}. */ -public abstract class SynthesisRequest { - +public final class SynthesisRequest { private final String mText; private final Bundle mParams; private String mLanguage; @@ -37,34 +42,12 @@ public abstract class SynthesisRequest { private int mSpeechRate; private int mPitch; - public SynthesisRequest(String text, Bundle params) { + SynthesisRequest(String text, Bundle params) { mText = text; + // Makes a copy of params. mParams = new Bundle(params); } - /** - * 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. */ @@ -115,86 +98,25 @@ public abstract class SynthesisRequest { } /** - * Gets the maximum number of bytes that the TTS engine can pass in a single call of - * {@link #audioAvailable}. This does not apply to {@link #completeAudioAvailable}. + * Sets the locale for the request. */ - public abstract int getMaxBufferSize(); + void setLanguage(String language, String country, String variant) { + mLanguage = language; + mCountry = country; + mVariant = variant; + } /** - * Checks whether the synthesis request completed successfully. + * Sets the speech rate. */ - abstract boolean isDone(); + void setSpeechRate(int speechRate) { + mSpeechRate = speechRate; + } /** - * Aborts the speech request. - * - * Can be called from multiple threads. + * Sets the pitch. */ - 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. Must be {@code 1} or {@code 2}. - * @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}. This must be - * less than or equal to the return value of {@link #getMaxBufferSize}. - * @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(); - - /** - * The service should call this method if the speech synthesis fails. - * - * This method should only be called on the synthesis thread, - * while in {@link TextToSpeechService#onSynthesizeText}. - */ - public abstract void error(); - - /** - * The service can call this method instead of using {@link #start}, {@link #audioAvailable} - * and {@link #done} if all the audio data is available in a single buffer. - * - * @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. Must be {@code 1} or {@code 2}. - * @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}. - * @return {@link TextToSpeech#SUCCESS} or {@link TextToSpeech#ERROR}. - */ - public abstract int completeAudioAvailable(int sampleRateInHz, int audioFormat, - int channelCount, byte[] buffer, int offset, int length); -} \ No newline at end of file + void setPitch(int pitch) { + mPitch = pitch; + } +} diff --git a/core/java/android/speech/tts/TextToSpeechService.java b/core/java/android/speech/tts/TextToSpeechService.java index e553f7735b35f..55cfea31c2703 100644 --- a/core/java/android/speech/tts/TextToSpeechService.java +++ b/core/java/android/speech/tts/TextToSpeechService.java @@ -19,7 +19,6 @@ 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; @@ -40,8 +39,34 @@ import java.util.Locale; /** - * Abstract base class for TTS engine implementations. + * Abstract base class for TTS engine implementations. The following methods + * need to be implemented. + * + * + * + * The first three deal primarily with language management, and are used to + * query the engine for it's support for a given language and indicate to it + * that requests in a given language are imminent. + * + * {@link #onSynthesizeText} is central to the engine implementation. The + * implementation should synthesize text as per the request parameters and + * return synthesized data via the supplied callback. This class and its helpers + * will then consume that data, which might mean queueing it for playback or writing + * it to a file or similar. All calls to this method will be on a single + * thread, which will be different from the main thread of the service. Synthesis + * must be synchronous which means the engine must NOT hold on the callback or call + * any methods on it after the method returns + * + * {@link #onStop} tells the engine that it should stop all ongoing synthesis, if + * any. Any pending data from the current synthesis will be discarded. */ +// TODO: Add a link to the sample TTS engine once it's done. public abstract class TextToSpeechService extends Service { private static final boolean DBG = false; @@ -160,10 +185,12 @@ public abstract class TextToSpeechService extends Service { * * Called on the synthesis thread. * - * @param request The synthesis request. The method should use the methods in the request - * object to communicate the results of the synthesis. + * @param request The synthesis request. + * @param callback The callback the the engine must use to make data available for + * playback or for writing to a file. */ - protected abstract void onSynthesizeText(SynthesisRequest request); + protected abstract void onSynthesizeText(SynthesisRequest request, + SynthesisCallback callback); private boolean areDefaultsEnforced() { return getSecureSettingInt(Settings.Secure.TTS_USE_DEFAULTS, @@ -421,11 +448,16 @@ public abstract class TextToSpeechService extends Service { class SynthesisSpeechItem extends SpeechItem { private final String mText; - private SynthesisRequest mSynthesisRequest; + private final SynthesisRequest mSynthesisRequest; + // Non null after synthesis has started, and all accesses + // guarded by 'this'. + private AbstractSynthesisCallback mSynthesisCallback; public SynthesisSpeechItem(String callingApp, Bundle params, String text) { super(callingApp, params); mText = text; + mSynthesisRequest = new SynthesisRequest(mText, mParams); + setRequestParams(mSynthesisRequest); } public String getText() { @@ -447,20 +479,18 @@ public abstract class TextToSpeechService extends Service { @Override protected int playImpl() { - SynthesisRequest synthesisRequest; + AbstractSynthesisCallback synthesisCallback; synchronized (this) { - mSynthesisRequest = createSynthesisRequest(); - synthesisRequest = mSynthesisRequest; + mSynthesisCallback = createSynthesisCallback(); + synthesisCallback = mSynthesisCallback; } - setRequestParams(synthesisRequest); - TextToSpeechService.this.onSynthesizeText(synthesisRequest); - return synthesisRequest.isDone() ? TextToSpeech.SUCCESS : TextToSpeech.ERROR; + TextToSpeechService.this.onSynthesizeText(mSynthesisRequest, synthesisCallback); + return synthesisCallback.isDone() ? TextToSpeech.SUCCESS : TextToSpeech.ERROR; } - protected SynthesisRequest createSynthesisRequest() { - return new PlaybackSynthesisRequest(mText, mParams, - getStreamType(), getVolume(), getPan(), mAudioPlaybackHandler, - this); + protected AbstractSynthesisCallback createSynthesisCallback() { + return new PlaybackSynthesisCallback(getStreamType(), getVolume(), getPan(), + mAudioPlaybackHandler, this); } private void setRequestParams(SynthesisRequest request) { @@ -476,11 +506,11 @@ public abstract class TextToSpeechService extends Service { @Override protected void stopImpl() { - SynthesisRequest synthesisRequest; + AbstractSynthesisCallback synthesisCallback; synchronized (this) { - synthesisRequest = mSynthesisRequest; + synthesisCallback = mSynthesisCallback; } - synthesisRequest.stop(); + synthesisCallback.stop(); TextToSpeechService.this.onStop(); } @@ -529,8 +559,8 @@ public abstract class TextToSpeechService extends Service { } @Override - protected SynthesisRequest createSynthesisRequest() { - return new FileSynthesisRequest(getText(), mParams, mFile); + protected AbstractSynthesisCallback createSynthesisCallback() { + return new FileSynthesisCallback(mFile); } @Override