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