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 // Whether this request has been stopped. This is useful for keeping 59 // track whether stop() has been called before start(). In all other cases, 60 // a non-null value of mItem will provide the same information. 61 private boolean mStopped = false; 62 63 private volatile boolean mDone = false; 64 65 private final UtteranceProgressDispatcher mDispatcher; 66 private final Object mCallerIdentity; 67 private final EventLogger mLogger; 68 69 PlaybackSynthesisCallback(int streamType, float volume, float pan, 70 AudioPlaybackHandler audioTrackHandler, UtteranceProgressDispatcher dispatcher, 71 Object callerIdentity, EventLogger logger) { 72 mStreamType = streamType; 73 mVolume = volume; 74 mPan = pan; 75 mAudioTrackHandler = audioTrackHandler; 76 mDispatcher = dispatcher; 77 mCallerIdentity = callerIdentity; 78 mLogger = logger; 79 } 80 81 @Override 82 void stop() { 83 stopImpl(false); 84 } 85 86 void stopImpl(boolean wasError) { 87 if (DBG) Log.d(TAG, "stop()"); 88 89 // Note that mLogger.mError might be true too at this point. 90 mLogger.onStopped(); 91 92 SynthesisPlaybackQueueItem item; 93 synchronized (mStateLock) { 94 if (mStopped) { 95 Log.w(TAG, "stop() called twice"); 96 return; 97 } 98 99 item = mItem; 100 mStopped = true; 101 } 102 103 if (item != null) { 104 // This might result in the synthesis thread being woken up, at which 105 // point it will write an additional buffer to the item - but we 106 // won't worry about that because the audio playback queue will be cleared 107 // soon after (see SynthHandler#stop(String). 108 item.stop(wasError); 109 } else { 110 // This happens when stop() or error() were called before start() was. 111 112 // In all other cases, mAudioTrackHandler.stop() will 113 // result in onSynthesisDone being called, and we will 114 // write data there. 115 mLogger.onWriteData(); 116 117 if (wasError) { 118 // We have to dispatch the error ourselves. 119 mDispatcher.dispatchOnError(); 120 } 121 } 122 } 123 124 @Override 125 public int getMaxBufferSize() { 126 // The AudioTrack buffer will be at least MIN_AUDIO_BUFFER_SIZE, so that should always be 127 // a safe buffer size to pass in. 128 return MIN_AUDIO_BUFFER_SIZE; 129 } 130 131 @Override 132 boolean isDone() { 133 return mDone; 134 } 135 136 @Override 137 public int start(int sampleRateInHz, int audioFormat, int channelCount) { 138 if (DBG) { 139 Log.d(TAG, "start(" + sampleRateInHz + "," + audioFormat 140 + "," + channelCount + ")"); 141 } 142 143 int channelConfig = BlockingAudioTrack.getChannelConfig(channelCount); 144 if (channelConfig == 0) { 145 Log.e(TAG, "Unsupported number of channels :" + channelCount); 146 return TextToSpeech.ERROR; 147 } 148 149 synchronized (mStateLock) { 150 if (mStopped) { 151 if (DBG) Log.d(TAG, "stop() called before start(), returning."); 152 return TextToSpeech.ERROR; 153 } 154 SynthesisPlaybackQueueItem item = new SynthesisPlaybackQueueItem( 155 mStreamType, sampleRateInHz, audioFormat, channelCount, mVolume, mPan, 156 mDispatcher, mCallerIdentity, mLogger); 157 mAudioTrackHandler.enqueue(item); 158 mItem = item; 159 } 160 161 return TextToSpeech.SUCCESS; 162 } 163 164 165 @Override 166 public int audioAvailable(byte[] buffer, int offset, int length) { 167 if (DBG) { 168 Log.d(TAG, "audioAvailable(byte[" + buffer.length + "]," 169 + offset + "," + length + ")"); 170 } 171 if (length > getMaxBufferSize() || length <= 0) { 172 throw new IllegalArgumentException("buffer is too large or of zero length (" + 173 + length + " bytes)"); 174 } 175 176 SynthesisPlaybackQueueItem item = null; 177 synchronized (mStateLock) { 178 if (mItem == null || mStopped) { 179 return TextToSpeech.ERROR; 180 } 181 item = mItem; 182 } 183 184 // Sigh, another copy. 185 final byte[] bufferCopy = new byte[length]; 186 System.arraycopy(buffer, offset, bufferCopy, 0, length); 187 188 // Might block on mItem.this, if there are too many buffers waiting to 189 // be consumed. 190 try { 191 item.put(bufferCopy); 192 } catch (InterruptedException ie) { 193 return TextToSpeech.ERROR; 194 } 195 196 mLogger.onEngineDataReceived(); 197 198 return TextToSpeech.SUCCESS; 199 } 200 201 @Override 202 public int done() { 203 if (DBG) Log.d(TAG, "done()"); 204 205 SynthesisPlaybackQueueItem item = null; 206 synchronized (mStateLock) { 207 if (mDone) { 208 Log.w(TAG, "Duplicate call to done()"); 209 return TextToSpeech.ERROR; 210 } 211 212 mDone = true; 213 214 if (mItem == null) { 215 return TextToSpeech.ERROR; 216 } 217 218 item = mItem; 219 } 220 221 item.done(); 222 mLogger.onEngineComplete(); 223 224 return TextToSpeech.SUCCESS; 225 } 226 227 @Override 228 public void error() { 229 if (DBG) Log.d(TAG, "error() [will call stop]"); 230 // Currently, this call will not be logged if error( ) is called 231 // before start. 232 mLogger.onError(); 233 stopImpl(true); 234 } 235 236} 237