PlaybackSynthesisCallback.java revision 492b7f0d51f53164aa6eb974cd7ab6a7889af677
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 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        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, mCallerIdentity, 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