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.AudioOutputParams; 19import android.speech.tts.TextToSpeechService.UtteranceProgressDispatcher; 20import android.util.Log; 21 22/** 23 * Speech synthesis request that plays the audio as it is received. 24 */ 25class PlaybackSynthesisCallback extends AbstractSynthesisCallback { 26 27 private static final String TAG = "PlaybackSynthesisRequest"; 28 private static final boolean DBG = false; 29 30 private static final int MIN_AUDIO_BUFFER_SIZE = 8192; 31 32 private final AudioOutputParams mAudioParams; 33 34 /** 35 * Guards {@link #mAudioTrackHandler}, {@link #mItem} and {@link #mStopped}. 36 */ 37 private final Object mStateLock = new Object(); 38 39 // Handler associated with a thread that plays back audio requests. 40 private final AudioPlaybackHandler mAudioTrackHandler; 41 // A request "token", which will be non null after start() has been called. 42 private SynthesisPlaybackQueueItem mItem = null; 43 44 private volatile boolean mDone = false; 45 46 /** Status code of synthesis */ 47 protected int mStatusCode; 48 49 private final UtteranceProgressDispatcher mDispatcher; 50 private final Object mCallerIdentity; 51 private final AbstractEventLogger mLogger; 52 53 PlaybackSynthesisCallback(AudioOutputParams audioParams, AudioPlaybackHandler audioTrackHandler, 54 UtteranceProgressDispatcher dispatcher, Object callerIdentity, 55 AbstractEventLogger logger, boolean clientIsUsingV2) { 56 super(clientIsUsingV2); 57 mAudioParams = audioParams; 58 mAudioTrackHandler = audioTrackHandler; 59 mDispatcher = dispatcher; 60 mCallerIdentity = callerIdentity; 61 mLogger = logger; 62 mStatusCode = TextToSpeech.SUCCESS; 63 } 64 65 @Override 66 void stop() { 67 if (DBG) Log.d(TAG, "stop()"); 68 69 SynthesisPlaybackQueueItem item; 70 synchronized (mStateLock) { 71 if (mDone) { 72 return; 73 } 74 if (mStatusCode == TextToSpeech.STOPPED) { 75 Log.w(TAG, "stop() called twice"); 76 return; 77 } 78 79 item = mItem; 80 mStatusCode = TextToSpeech.STOPPED; 81 } 82 83 if (item != null) { 84 // This might result in the synthesis thread being woken up, at which 85 // point it will write an additional buffer to the item - but we 86 // won't worry about that because the audio playback queue will be cleared 87 // soon after (see SynthHandler#stop(String). 88 item.stop(TextToSpeech.STOPPED); 89 } else { 90 // This happens when stop() or error() were called before start() was. 91 92 // In all other cases, mAudioTrackHandler.stop() will 93 // result in onSynthesisDone being called, and we will 94 // write data there. 95 mLogger.onCompleted(TextToSpeech.STOPPED); 96 mDispatcher.dispatchOnStop(); 97 } 98 } 99 100 @Override 101 public int getMaxBufferSize() { 102 // The AudioTrack buffer will be at least MIN_AUDIO_BUFFER_SIZE, so that should always be 103 // a safe buffer size to pass in. 104 return MIN_AUDIO_BUFFER_SIZE; 105 } 106 107 @Override 108 public boolean hasStarted() { 109 synchronized (mStateLock) { 110 return mItem != null; 111 } 112 } 113 114 @Override 115 public boolean hasFinished() { 116 synchronized (mStateLock) { 117 return mDone; 118 } 119 } 120 121 @Override 122 public int start(int sampleRateInHz, int audioFormat, int channelCount) { 123 if (DBG) Log.d(TAG, "start(" + sampleRateInHz + "," + audioFormat + "," + channelCount 124 + ")"); 125 126 int channelConfig = BlockingAudioTrack.getChannelConfig(channelCount); 127 128 synchronized (mStateLock) { 129 if (channelConfig == 0) { 130 Log.e(TAG, "Unsupported number of channels :" + channelCount); 131 mStatusCode = TextToSpeech.ERROR_OUTPUT; 132 return TextToSpeech.ERROR; 133 } 134 if (mStatusCode == TextToSpeech.STOPPED) { 135 if (DBG) Log.d(TAG, "stop() called before start(), returning."); 136 return errorCodeOnStop(); 137 } 138 if (mStatusCode != TextToSpeech.SUCCESS) { 139 if (DBG) Log.d(TAG, "Error was raised"); 140 return TextToSpeech.ERROR; 141 } 142 if (mItem != null) { 143 Log.e(TAG, "Start called twice"); 144 return TextToSpeech.ERROR; 145 } 146 SynthesisPlaybackQueueItem item = new SynthesisPlaybackQueueItem( 147 mAudioParams, sampleRateInHz, audioFormat, channelCount, 148 mDispatcher, mCallerIdentity, mLogger); 149 mAudioTrackHandler.enqueue(item); 150 mItem = item; 151 } 152 153 return TextToSpeech.SUCCESS; 154 } 155 156 @Override 157 public int audioAvailable(byte[] buffer, int offset, int length) { 158 if (DBG) Log.d(TAG, "audioAvailable(byte[" + buffer.length + "]," + offset + "," + length 159 + ")"); 160 161 if (length > getMaxBufferSize() || length <= 0) { 162 throw new IllegalArgumentException("buffer is too large or of zero length (" + 163 + length + " bytes)"); 164 } 165 166 SynthesisPlaybackQueueItem item = null; 167 synchronized (mStateLock) { 168 if (mItem == null) { 169 mStatusCode = TextToSpeech.ERROR_OUTPUT; 170 return TextToSpeech.ERROR; 171 } 172 if (mStatusCode != TextToSpeech.SUCCESS) { 173 if (DBG) Log.d(TAG, "Error was raised"); 174 return TextToSpeech.ERROR; 175 } 176 if (mStatusCode == TextToSpeech.STOPPED) { 177 return errorCodeOnStop(); 178 } 179 item = mItem; 180 } 181 182 // Sigh, another copy. 183 final byte[] bufferCopy = new byte[length]; 184 System.arraycopy(buffer, offset, bufferCopy, 0, length); 185 186 // Might block on mItem.this, if there are too many buffers waiting to 187 // be consumed. 188 try { 189 item.put(bufferCopy); 190 } catch (InterruptedException ie) { 191 synchronized (mStateLock) { 192 mStatusCode = TextToSpeech.ERROR_OUTPUT; 193 return TextToSpeech.ERROR; 194 } 195 } 196 197 mLogger.onEngineDataReceived(); 198 return TextToSpeech.SUCCESS; 199 } 200 201 @Override 202 public int done() { 203 if (DBG) Log.d(TAG, "done()"); 204 205 int statusCode = 0; 206 SynthesisPlaybackQueueItem item = null; 207 synchronized (mStateLock) { 208 if (mDone) { 209 Log.w(TAG, "Duplicate call to done()"); 210 // Not an error that would prevent synthesis. Hence no 211 // setStatusCode 212 return TextToSpeech.ERROR; 213 } 214 if (mStatusCode == TextToSpeech.STOPPED) { 215 if (DBG) Log.d(TAG, "Request has been aborted."); 216 return errorCodeOnStop(); 217 } 218 mDone = true; 219 220 if (mItem == null) { 221 // .done() was called before .start. Treat it as successful synthesis 222 // for a client, despite service bad implementation. 223 Log.w(TAG, "done() was called before start() call"); 224 if (mStatusCode == TextToSpeech.SUCCESS) { 225 mDispatcher.dispatchOnSuccess(); 226 } else { 227 mDispatcher.dispatchOnError(mStatusCode); 228 } 229 mLogger.onEngineComplete(); 230 return TextToSpeech.ERROR; 231 } 232 233 item = mItem; 234 statusCode = mStatusCode; 235 } 236 237 // Signal done or error to item 238 if (statusCode == TextToSpeech.SUCCESS) { 239 item.done(); 240 } else { 241 item.stop(statusCode); 242 } 243 mLogger.onEngineComplete(); 244 return TextToSpeech.SUCCESS; 245 } 246 247 @Override 248 public void error() { 249 error(TextToSpeech.ERROR_SYNTHESIS); 250 } 251 252 @Override 253 public void error(int errorCode) { 254 if (DBG) Log.d(TAG, "error() [will call stop]"); 255 synchronized (mStateLock) { 256 if (mDone) { 257 return; 258 } 259 mStatusCode = errorCode; 260 } 261 } 262} 263