blob: e345e89cc476929dc67087ccb2b2ea00535fdd33 [file] [log] [blame]
Bjorn Bringert50e657b2011-03-08 16:00:40 +00001/*
2 * Copyright (C) 2011 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5 * use this file except in compliance with the License. You may obtain a copy of
6 * the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13 * License for the specific language governing permissions and limitations under
14 * the License.
15 */
16package android.speech.tts;
17
Narayan Kamath754c72e2011-11-09 14:22:32 +000018import android.speech.tts.TextToSpeechService.UtteranceProgressDispatcher;
Bjorn Bringert50e657b2011-03-08 16:00:40 +000019import android.util.Log;
20
21/**
22 * Speech synthesis request that plays the audio as it is received.
23 */
Narayan Kamathe22b69a2011-06-08 11:41:47 +010024class PlaybackSynthesisCallback extends AbstractSynthesisCallback {
Bjorn Bringert50e657b2011-03-08 16:00:40 +000025
26 private static final String TAG = "PlaybackSynthesisRequest";
27 private static final boolean DBG = false;
28
29 private static final int MIN_AUDIO_BUFFER_SIZE = 8192;
30
31 /**
32 * Audio stream type. Must be one of the STREAM_ contants defined in
33 * {@link android.media.AudioManager}.
34 */
35 private final int mStreamType;
36
37 /**
38 * Volume, in the range [0.0f, 1.0f]. The default value is
39 * {@link TextToSpeech.Engine#DEFAULT_VOLUME} (1.0f).
40 */
41 private final float mVolume;
42
43 /**
44 * Left/right position of the audio, in the range [-1.0f, 1.0f].
45 * The default value is {@link TextToSpeech.Engine#DEFAULT_PAN} (0.0f).
46 */
47 private final float mPan;
48
Narayan Kamath8d1fc242011-06-03 18:11:54 +010049 /**
Narayan Kamath67ae6bc2011-11-30 14:51:00 +000050 * Guards {@link #mAudioTrackHandler}, {@link #mItem} and {@link #mStopped}.
Narayan Kamath8d1fc242011-06-03 18:11:54 +010051 */
Bjorn Bringert50e657b2011-03-08 16:00:40 +000052 private final Object mStateLock = new Object();
Bjorn Bringert50e657b2011-03-08 16:00:40 +000053
Narayan Kamath8d1fc242011-06-03 18:11:54 +010054 // Handler associated with a thread that plays back audio requests.
55 private final AudioPlaybackHandler mAudioTrackHandler;
Narayan Kamathc3da8812011-07-01 10:13:54 +010056 // A request "token", which will be non null after start() has been called.
Narayan Kamath67ae6bc2011-11-30 14:51:00 +000057 private SynthesisPlaybackQueueItem mItem = null;
Narayan Kamath8d1fc242011-06-03 18:11:54 +010058
59 private volatile boolean mDone = false;
60
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +010061 /** Status code of synthesis */
62 protected int mStatusCode;
63
Narayan Kamath754c72e2011-11-09 14:22:32 +000064 private final UtteranceProgressDispatcher mDispatcher;
Narayan Kamath492b7f02011-11-29 17:02:06 +000065 private final Object mCallerIdentity;
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +010066 private final AbstractEventLogger mLogger;
Narayan Kamath8d1fc242011-06-03 18:11:54 +010067
Narayan Kamathe22b69a2011-06-08 11:41:47 +010068 PlaybackSynthesisCallback(int streamType, float volume, float pan,
Narayan Kamath754c72e2011-11-09 14:22:32 +000069 AudioPlaybackHandler audioTrackHandler, UtteranceProgressDispatcher dispatcher,
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +010070 Object callerIdentity, AbstractEventLogger logger, boolean clientIsUsingV2) {
71 super(clientIsUsingV2);
Bjorn Bringert50e657b2011-03-08 16:00:40 +000072 mStreamType = streamType;
73 mVolume = volume;
74 mPan = pan;
Narayan Kamathc90f1c82011-05-24 11:39:43 +010075 mAudioTrackHandler = audioTrackHandler;
Narayan Kamath8d1fc242011-06-03 18:11:54 +010076 mDispatcher = dispatcher;
Narayan Kamath492b7f02011-11-29 17:02:06 +000077 mCallerIdentity = callerIdentity;
Narayan Kamath6dabb632011-07-08 12:13:03 +010078 mLogger = logger;
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +010079 mStatusCode = TextToSpeechClient.Status.SUCCESS;
Bjorn Bringert50e657b2011-03-08 16:00:40 +000080 }
81
82 @Override
83 void stop() {
84 if (DBG) Log.d(TAG, "stop()");
Narayan Kamath8d1fc242011-06-03 18:11:54 +010085
Narayan Kamath67ae6bc2011-11-30 14:51:00 +000086 SynthesisPlaybackQueueItem item;
Bjorn Bringert50e657b2011-03-08 16:00:40 +000087 synchronized (mStateLock) {
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +010088 if (mDone) {
89 return;
90 }
91 if (mStatusCode == TextToSpeechClient.Status.STOPPED) {
Narayan Kamath6dabb632011-07-08 12:13:03 +010092 Log.w(TAG, "stop() called twice");
Narayan Kamath8d1fc242011-06-03 18:11:54 +010093 return;
Narayan Kamathc90f1c82011-05-24 11:39:43 +010094 }
Narayan Kamathbe4ad4a2011-07-15 13:01:09 +010095
Narayan Kamath67ae6bc2011-11-30 14:51:00 +000096 item = mItem;
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +010097 mStatusCode = TextToSpeechClient.Status.STOPPED;
Narayan Kamath8d1fc242011-06-03 18:11:54 +010098 }
Narayan Kamath2a0518c2011-08-26 11:43:03 +010099
Narayan Kamath67ae6bc2011-11-30 14:51:00 +0000100 if (item != null) {
Narayan Kamath2a0518c2011-08-26 11:43:03 +0100101 // This might result in the synthesis thread being woken up, at which
Narayan Kamath67ae6bc2011-11-30 14:51:00 +0000102 // point it will write an additional buffer to the item - but we
Narayan Kamath2a0518c2011-08-26 11:43:03 +0100103 // won't worry about that because the audio playback queue will be cleared
104 // soon after (see SynthHandler#stop(String).
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +0100105 item.stop(TextToSpeechClient.Status.STOPPED);
Narayan Kamath40f71f02011-11-23 16:42:53 +0000106 } else {
107 // This happens when stop() or error() were called before start() was.
108
109 // In all other cases, mAudioTrackHandler.stop() will
110 // result in onSynthesisDone being called, and we will
111 // write data there.
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +0100112 mLogger.onCompleted(TextToSpeechClient.Status.STOPPED);
113 mDispatcher.dispatchOnStop();
Narayan Kamath2a0518c2011-08-26 11:43:03 +0100114 }
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000115 }
116
Bjorn Bringert71e0b482011-04-15 14:37:05 +0100117 @Override
118 public int getMaxBufferSize() {
119 // The AudioTrack buffer will be at least MIN_AUDIO_BUFFER_SIZE, so that should always be
120 // a safe buffer size to pass in.
121 return MIN_AUDIO_BUFFER_SIZE;
122 }
123
Bjorn Bringert360eb162011-04-19 09:20:35 +0100124 @Override
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +0100125 public boolean hasStarted() {
126 synchronized (mStateLock) {
127 return mItem != null;
128 }
129 }
130
131 @Override
132 public boolean hasFinished() {
133 synchronized (mStateLock) {
134 return mDone;
135 }
Bjorn Bringert360eb162011-04-19 09:20:35 +0100136 }
137
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000138 @Override
139 public int start(int sampleRateInHz, int audioFormat, int channelCount) {
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +0100140 if (DBG) Log.d(TAG, "start(" + sampleRateInHz + "," + audioFormat + "," + channelCount
141 + ")");
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000142
Narayan Kamath67ae6bc2011-11-30 14:51:00 +0000143 int channelConfig = BlockingAudioTrack.getChannelConfig(channelCount);
Narayan Kamath8d1fc242011-06-03 18:11:54 +0100144
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000145 synchronized (mStateLock) {
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +0100146 if (channelConfig == 0) {
147 Log.e(TAG, "Unsupported number of channels :" + channelCount);
148 mStatusCode = TextToSpeechClient.Status.ERROR_OUTPUT;
149 return TextToSpeech.ERROR;
150 }
151 if (mStatusCode == TextToSpeechClient.Status.STOPPED) {
Narayan Kamath8d1fc242011-06-03 18:11:54 +0100152 if (DBG) Log.d(TAG, "stop() called before start(), returning.");
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +0100153 return errorCodeOnStop();
154 }
155 if (mStatusCode != TextToSpeechClient.Status.SUCCESS) {
156 if (DBG) Log.d(TAG, "Error was raised");
157 return TextToSpeech.ERROR;
158 }
159 if (mItem != null) {
160 Log.e(TAG, "Start called twice");
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000161 return TextToSpeech.ERROR;
162 }
Narayan Kamath67ae6bc2011-11-30 14:51:00 +0000163 SynthesisPlaybackQueueItem item = new SynthesisPlaybackQueueItem(
Narayan Kamath8d1fc242011-06-03 18:11:54 +0100164 mStreamType, sampleRateInHz, audioFormat, channelCount, mVolume, mPan,
Narayan Kamath492b7f02011-11-29 17:02:06 +0000165 mDispatcher, mCallerIdentity, mLogger);
Narayan Kamath67ae6bc2011-11-30 14:51:00 +0000166 mAudioTrackHandler.enqueue(item);
167 mItem = item;
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000168 }
169
170 return TextToSpeech.SUCCESS;
171 }
172
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000173 @Override
174 public int audioAvailable(byte[] buffer, int offset, int length) {
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +0100175 if (DBG) Log.d(TAG, "audioAvailable(byte[" + buffer.length + "]," + offset + "," + length
176 + ")");
177
Narayan Kamathc90f1c82011-05-24 11:39:43 +0100178 if (length > getMaxBufferSize() || length <= 0) {
179 throw new IllegalArgumentException("buffer is too large or of zero length (" +
180 + length + " bytes)");
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000181 }
Narayan Kamath8d1fc242011-06-03 18:11:54 +0100182
Narayan Kamath67ae6bc2011-11-30 14:51:00 +0000183 SynthesisPlaybackQueueItem item = null;
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000184 synchronized (mStateLock) {
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +0100185 if (mItem == null) {
186 mStatusCode = TextToSpeechClient.Status.ERROR_OUTPUT;
Narayan Kamathc90f1c82011-05-24 11:39:43 +0100187 return TextToSpeech.ERROR;
188 }
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +0100189 if (mStatusCode != TextToSpeechClient.Status.SUCCESS) {
190 if (DBG) Log.d(TAG, "Error was raised");
191 return TextToSpeech.ERROR;
192 }
193 if (mStatusCode == TextToSpeechClient.Status.STOPPED) {
194 return errorCodeOnStop();
195 }
Narayan Kamath67ae6bc2011-11-30 14:51:00 +0000196 item = mItem;
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000197 }
Narayan Kamath8d1fc242011-06-03 18:11:54 +0100198
Narayan Kamath2a0518c2011-08-26 11:43:03 +0100199 // Sigh, another copy.
200 final byte[] bufferCopy = new byte[length];
201 System.arraycopy(buffer, offset, bufferCopy, 0, length);
Narayan Kamath67ae6bc2011-11-30 14:51:00 +0000202
203 // Might block on mItem.this, if there are too many buffers waiting to
Narayan Kamath2a0518c2011-08-26 11:43:03 +0100204 // be consumed.
Narayan Kamath67ae6bc2011-11-30 14:51:00 +0000205 try {
206 item.put(bufferCopy);
207 } catch (InterruptedException ie) {
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +0100208 synchronized (mStateLock) {
209 mStatusCode = TextToSpeechClient.Status.ERROR_OUTPUT;
210 return TextToSpeech.ERROR;
211 }
Narayan Kamath67ae6bc2011-11-30 14:51:00 +0000212 }
Narayan Kamath2a0518c2011-08-26 11:43:03 +0100213
Narayan Kamath6dabb632011-07-08 12:13:03 +0100214 mLogger.onEngineDataReceived();
Narayan Kamath8d1fc242011-06-03 18:11:54 +0100215 return TextToSpeech.SUCCESS;
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000216 }
217
218 @Override
219 public int done() {
220 if (DBG) Log.d(TAG, "done()");
Narayan Kamath8d1fc242011-06-03 18:11:54 +0100221
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +0100222 int statusCode = 0;
Narayan Kamath67ae6bc2011-11-30 14:51:00 +0000223 SynthesisPlaybackQueueItem item = null;
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000224 synchronized (mStateLock) {
Narayan Kamath8d1fc242011-06-03 18:11:54 +0100225 if (mDone) {
226 Log.w(TAG, "Duplicate call to done()");
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +0100227 // Not an error that would prevent synthesis. Hence no
228 // setStatusCode
Narayan Kamathc90f1c82011-05-24 11:39:43 +0100229 return TextToSpeech.ERROR;
230 }
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +0100231 if (mStatusCode == TextToSpeechClient.Status.STOPPED) {
232 if (DBG) Log.d(TAG, "Request has been aborted.");
233 return errorCodeOnStop();
234 }
Bjorn Bringert360eb162011-04-19 09:20:35 +0100235 mDone = true;
Narayan Kamath8d1fc242011-06-03 18:11:54 +0100236
Narayan Kamath67ae6bc2011-11-30 14:51:00 +0000237 if (mItem == null) {
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +0100238 // .done() was called before .start. Treat it as successful synthesis
239 // for a client, despite service bad implementation.
240 Log.w(TAG, "done() was called before start() call");
241 if (mStatusCode == TextToSpeechClient.Status.SUCCESS) {
242 mDispatcher.dispatchOnSuccess();
243 } else {
244 mDispatcher.dispatchOnError(mStatusCode);
245 }
246 mLogger.onEngineComplete();
Narayan Kamath8d1fc242011-06-03 18:11:54 +0100247 return TextToSpeech.ERROR;
248 }
249
Narayan Kamath67ae6bc2011-11-30 14:51:00 +0000250 item = mItem;
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +0100251 statusCode = mStatusCode;
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000252 }
Narayan Kamath2a0518c2011-08-26 11:43:03 +0100253
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +0100254 // Signal done or error to item
255 if (statusCode == TextToSpeechClient.Status.SUCCESS) {
256 item.done();
257 } else {
258 item.stop(statusCode);
259 }
Narayan Kamath2a0518c2011-08-26 11:43:03 +0100260 mLogger.onEngineComplete();
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000261 return TextToSpeech.SUCCESS;
262 }
Bjorn Bringert71e0b482011-04-15 14:37:05 +0100263
264 @Override
Bjorn Bringert360eb162011-04-19 09:20:35 +0100265 public void error() {
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +0100266 error(TextToSpeechClient.Status.ERROR_SYNTHESIS);
Bjorn Bringert360eb162011-04-19 09:20:35 +0100267 }
268
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +0100269 @Override
270 public void error(int errorCode) {
271 if (DBG) Log.d(TAG, "error() [will call stop]");
272 synchronized (mStateLock) {
273 if (mDone) {
274 return;
275 }
276 mStatusCode = errorCode;
277 }
278 }
279
280 @Override
281 public int fallback() {
282 synchronized (mStateLock) {
283 if (hasStarted() || hasFinished()) {
284 return TextToSpeech.ERROR;
285 }
286
287 mDispatcher.dispatchOnFallback();
288 mStatusCode = TextToSpeechClient.Status.SUCCESS;
289 return TextToSpeechClient.Status.SUCCESS;
290 }
291 }
Narayan Kamath53f6f952011-04-19 16:39:20 +0100292}