PlaybackSynthesisCallback.java revision c3da8818f0598b3ab2cd6f4168349da6d0f72cb1
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.UtteranceCompletedDispatcher; 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 UtteranceCompletedDispatcher mDispatcher; 66 private final String mCallingApp; 67 private final EventLogger mLogger; 68 69 PlaybackSynthesisCallback(int streamType, float volume, float pan, 70 AudioPlaybackHandler audioTrackHandler, UtteranceCompletedDispatcher 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 synchronized (mStateLock) { 89 if (mStopped) { 90 Log.w(TAG, "stop() called twice"); 91 return; 92 } 93 // mToken will be null if the engine encounters 94 // an error before it called start(). 95 if (mToken != null) { 96 mAudioTrackHandler.stop(mToken); 97 mToken = null; 98 } else { 99 // In all other cases, mAudioTrackHandler.stop() will 100 // result in onComplete being called. 101 mLogger.onWriteData(); 102 } 103 mStopped = true; 104 } 105 } 106 107 @Override 108 public int getMaxBufferSize() { 109 // The AudioTrack buffer will be at least MIN_AUDIO_BUFFER_SIZE, so that should always be 110 // a safe buffer size to pass in. 111 return MIN_AUDIO_BUFFER_SIZE; 112 } 113 114 @Override 115 boolean isDone() { 116 return mDone; 117 } 118 119 @Override 120 public int start(int sampleRateInHz, int audioFormat, int channelCount) { 121 if (DBG) { 122 Log.d(TAG, "start(" + sampleRateInHz + "," + audioFormat 123 + "," + channelCount + ")"); 124 } 125 126 int channelConfig = AudioPlaybackHandler.getChannelConfig(channelCount); 127 if (channelConfig == 0) { 128 Log.e(TAG, "Unsupported number of channels :" + channelCount); 129 return TextToSpeech.ERROR; 130 } 131 132 synchronized (mStateLock) { 133 if (mStopped) { 134 if (DBG) Log.d(TAG, "stop() called before start(), returning."); 135 return TextToSpeech.ERROR; 136 } 137 SynthesisMessageParams params = new SynthesisMessageParams( 138 mStreamType, sampleRateInHz, audioFormat, channelCount, mVolume, mPan, 139 mDispatcher, mCallingApp, mLogger); 140 mAudioTrackHandler.enqueueSynthesisStart(params); 141 142 mToken = params; 143 } 144 145 return TextToSpeech.SUCCESS; 146 } 147 148 149 @Override 150 public int audioAvailable(byte[] buffer, int offset, int length) { 151 if (DBG) { 152 Log.d(TAG, "audioAvailable(byte[" + buffer.length + "]," 153 + offset + "," + length + ")"); 154 } 155 if (length > getMaxBufferSize() || length <= 0) { 156 throw new IllegalArgumentException("buffer is too large or of zero length (" + 157 + length + " bytes)"); 158 } 159 160 synchronized (mStateLock) { 161 if (mToken == null) { 162 return TextToSpeech.ERROR; 163 } 164 165 // Sigh, another copy. 166 final byte[] bufferCopy = new byte[length]; 167 System.arraycopy(buffer, offset, bufferCopy, 0, length); 168 mToken.addBuffer(bufferCopy); 169 mAudioTrackHandler.enqueueSynthesisDataAvailable(mToken); 170 } 171 172 mLogger.onEngineDataReceived(); 173 174 return TextToSpeech.SUCCESS; 175 } 176 177 @Override 178 public int done() { 179 if (DBG) Log.d(TAG, "done()"); 180 181 synchronized (mStateLock) { 182 if (mDone) { 183 Log.w(TAG, "Duplicate call to done()"); 184 return TextToSpeech.ERROR; 185 } 186 187 mDone = true; 188 189 if (mToken == null) { 190 return TextToSpeech.ERROR; 191 } 192 193 mAudioTrackHandler.enqueueSynthesisDone(mToken); 194 mLogger.onEngineComplete(); 195 } 196 return TextToSpeech.SUCCESS; 197 } 198 199 @Override 200 public void error() { 201 if (DBG) Log.d(TAG, "error() [will call stop]"); 202 // Currently, this call will not be logged if error( ) is called 203 // before start. 204 mLogger.onError(); 205 stop(); 206 } 207 208} 209