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