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