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