PlaybackSynthesisCallback.java revision 90d15d2371ad85f22254be6985455aa2baa5d15d
1/* 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 18import android.speech.tts.TextToSpeechService.UtteranceProgressDispatcher; 19import android.util.Log; 20 21/** 22 * Speech synthesis request that plays the audio as it is received. 23 */ 24class PlaybackSynthesisCallback extends AbstractSynthesisCallback { 25 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 49 /** 50 * Guards {@link #mAudioTrackHandler}, {@link #mItem} and {@link #mStopped}. 51 */ 52 private final Object mStateLock = new Object(); 53 54 // Handler associated with a thread that plays back audio requests. 55 private final AudioPlaybackHandler mAudioTrackHandler; 56 // A request "token", which will be non null after start() has been called. 57 private SynthesisPlaybackQueueItem mItem = null; 58 59 private volatile boolean mDone = false; 60 61 /** Status code of synthesis */ 62 protected int mStatusCode; 63 64 private final UtteranceProgressDispatcher mDispatcher; 65 private final Object mCallerIdentity; 66 private final AbstractEventLogger mLogger; 67 68 PlaybackSynthesisCallback(int streamType, float volume, float pan, 69 AudioPlaybackHandler audioTrackHandler, UtteranceProgressDispatcher dispatcher, 70 Object callerIdentity, AbstractEventLogger logger, boolean clientIsUsingV2) { 71 super(clientIsUsingV2); 72 mStreamType = streamType; 73 mVolume = volume; 74 mPan = pan; 75 mAudioTrackHandler = audioTrackHandler; 76 mDispatcher = dispatcher; 77 mCallerIdentity = callerIdentity; 78 mLogger = logger; 79 mStatusCode = TextToSpeechClient.Status.SUCCESS; 80 } 81 82 @Override 83 void stop() { 84 if (DBG) Log.d(TAG, "stop()"); 85 86 SynthesisPlaybackQueueItem item; 87 synchronized (mStateLock) { 88 if (mDone) { 89 return; 90 } 91 if (mStatusCode == TextToSpeechClient.Status.STOPPED) { 92 Log.w(TAG, "stop() called twice"); 93 return; 94 } 95 96 item = mItem; 97 mStatusCode = TextToSpeechClient.Status.STOPPED; 98 } 99 100 if (item != null) { 101 // This might result in the synthesis thread being woken up, at which 102 // point it will write an additional buffer to the item - but we 103 // won't worry about that because the audio playback queue will be cleared 104 // soon after (see SynthHandler#stop(String). 105 item.stop(TextToSpeechClient.Status.STOPPED); 106 } 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. 112 mLogger.onCompleted(TextToSpeechClient.Status.STOPPED); 113 mDispatcher.dispatchOnStop(); 114 } 115 } 116 117 @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 124 @Override 125 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 } 136 } 137 138 @Override 139 public int start(int sampleRateInHz, int audioFormat, int channelCount) { 140 if (DBG) Log.d(TAG, "start(" + sampleRateInHz + "," + audioFormat + "," + channelCount 141 + ")"); 142 143 int channelConfig = BlockingAudioTrack.getChannelConfig(channelCount); 144 145 synchronized (mStateLock) { 146 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) { 152 if (DBG) Log.d(TAG, "stop() called before start(), returning."); 153 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"); 161 return TextToSpeech.ERROR; 162 } 163 SynthesisPlaybackQueueItem item = new SynthesisPlaybackQueueItem( 164 mStreamType, sampleRateInHz, audioFormat, channelCount, mVolume, mPan, 165 mDispatcher, mCallerIdentity, mLogger); 166 mAudioTrackHandler.enqueue(item); 167 mItem = item; 168 } 169 170 return TextToSpeech.SUCCESS; 171 } 172 173 @Override 174 public int audioAvailable(byte[] buffer, int offset, int length) { 175 if (DBG) Log.d(TAG, "audioAvailable(byte[" + buffer.length + "]," + offset + "," + length 176 + ")"); 177 178 if (length > getMaxBufferSize() || length <= 0) { 179 throw new IllegalArgumentException("buffer is too large or of zero length (" + 180 + length + " bytes)"); 181 } 182 183 SynthesisPlaybackQueueItem item = null; 184 synchronized (mStateLock) { 185 if (mItem == null) { 186 mStatusCode = TextToSpeechClient.Status.ERROR_OUTPUT; 187 return TextToSpeech.ERROR; 188 } 189 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 } 196 item = mItem; 197 } 198 199 // Sigh, another copy. 200 final byte[] bufferCopy = new byte[length]; 201 System.arraycopy(buffer, offset, bufferCopy, 0, length); 202 203 // Might block on mItem.this, if there are too many buffers waiting to 204 // be consumed. 205 try { 206 item.put(bufferCopy); 207 } catch (InterruptedException ie) { 208 synchronized (mStateLock) { 209 mStatusCode = TextToSpeechClient.Status.ERROR_OUTPUT; 210 return TextToSpeech.ERROR; 211 } 212 } 213 214 mLogger.onEngineDataReceived(); 215 return TextToSpeech.SUCCESS; 216 } 217 218 @Override 219 public int done() { 220 if (DBG) Log.d(TAG, "done()"); 221 222 int statusCode = 0; 223 SynthesisPlaybackQueueItem item = null; 224 synchronized (mStateLock) { 225 if (mDone) { 226 Log.w(TAG, "Duplicate call to done()"); 227 // Not an error that would prevent synthesis. Hence no 228 // setStatusCode 229 return TextToSpeech.ERROR; 230 } 231 if (mStatusCode == TextToSpeechClient.Status.STOPPED) { 232 if (DBG) Log.d(TAG, "Request has been aborted."); 233 return errorCodeOnStop(); 234 } 235 mDone = true; 236 237 if (mItem == null) { 238 // .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(); 247 return TextToSpeech.ERROR; 248 } 249 250 item = mItem; 251 statusCode = mStatusCode; 252 } 253 254 // Signal done or error to item 255 if (statusCode == TextToSpeechClient.Status.SUCCESS) { 256 item.done(); 257 } else { 258 item.stop(statusCode); 259 } 260 mLogger.onEngineComplete(); 261 return TextToSpeech.SUCCESS; 262 } 263 264 @Override 265 public void error() { 266 error(TextToSpeechClient.Status.ERROR_SYNTHESIS); 267 } 268 269 @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 } 292} 293