PlaybackSynthesisCallback.java revision 90d15d2371ad85f22254be6985455aa2baa5d15d
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
59    private volatile boolean mDone = false;
60
61    /** Status code of synthesis */
62    protected int mStatusCode;
63
64    private final UtteranceProgressDispatcher mDispatcher;
65    private final Object mCallerIdentity;
66    private final AbstractEventLogger mLogger;
67
68    PlaybackSynthesisCallback(int streamType, float volume, float pan,
69            AudioPlaybackHandler audioTrackHandler, UtteranceProgressDispatcher dispatcher,
70            Object callerIdentity, AbstractEventLogger logger, boolean clientIsUsingV2) {
71        super(clientIsUsingV2);
72        mStreamType = streamType;
73        mVolume = volume;
74        mPan = pan;
75        mAudioTrackHandler = audioTrackHandler;
76        mDispatcher = dispatcher;
77        mCallerIdentity = callerIdentity;
78        mLogger = logger;
79        mStatusCode = TextToSpeechClient.Status.SUCCESS;
80    }
81
82    @Override
83    void stop() {
84        if (DBG) Log.d(TAG, "stop()");
85
86        SynthesisPlaybackQueueItem item;
87        synchronized (mStateLock) {
88            if (mDone) {
89                return;
90            }
91            if (mStatusCode == TextToSpeechClient.Status.STOPPED) {
92                Log.w(TAG, "stop() called twice");
93                return;
94            }
95
96            item = mItem;
97            mStatusCode = TextToSpeechClient.Status.STOPPED;
98        }
99
100        if (item != null) {
101            // This might result in the synthesis thread being woken up, at which
102            // point it will write an additional buffer to the item - but we
103            // won't worry about that because the audio playback queue will be cleared
104            // soon after (see SynthHandler#stop(String).
105            item.stop(TextToSpeechClient.Status.STOPPED);
106        } else {
107            // This happens when stop() or error() were called before start() was.
108
109            // In all other cases, mAudioTrackHandler.stop() will
110            // result in onSynthesisDone being called, and we will
111            // write data there.
112            mLogger.onCompleted(TextToSpeechClient.Status.STOPPED);
113            mDispatcher.dispatchOnStop();
114        }
115    }
116
117    @Override
118    public int getMaxBufferSize() {
119        // The AudioTrack buffer will be at least MIN_AUDIO_BUFFER_SIZE, so that should always be
120        // a safe buffer size to pass in.
121        return MIN_AUDIO_BUFFER_SIZE;
122    }
123
124    @Override
125    public boolean hasStarted() {
126        synchronized (mStateLock) {
127            return mItem != null;
128        }
129    }
130
131    @Override
132    public boolean hasFinished() {
133        synchronized (mStateLock) {
134            return mDone;
135        }
136    }
137
138    @Override
139    public int start(int sampleRateInHz, int audioFormat, int channelCount) {
140        if (DBG) Log.d(TAG, "start(" + sampleRateInHz + "," + audioFormat + "," + channelCount
141                + ")");
142
143        int channelConfig = BlockingAudioTrack.getChannelConfig(channelCount);
144
145        synchronized (mStateLock) {
146            if (channelConfig == 0) {
147                Log.e(TAG, "Unsupported number of channels :" + channelCount);
148                mStatusCode = TextToSpeechClient.Status.ERROR_OUTPUT;
149                return TextToSpeech.ERROR;
150            }
151            if (mStatusCode == TextToSpeechClient.Status.STOPPED) {
152                if (DBG) Log.d(TAG, "stop() called before start(), returning.");
153                return errorCodeOnStop();
154            }
155            if (mStatusCode != TextToSpeechClient.Status.SUCCESS) {
156                if (DBG) Log.d(TAG, "Error was raised");
157                return TextToSpeech.ERROR;
158            }
159            if (mItem != null) {
160                Log.e(TAG, "Start called twice");
161                return TextToSpeech.ERROR;
162            }
163            SynthesisPlaybackQueueItem item = new SynthesisPlaybackQueueItem(
164                    mStreamType, sampleRateInHz, audioFormat, channelCount, mVolume, mPan,
165                    mDispatcher, mCallerIdentity, mLogger);
166            mAudioTrackHandler.enqueue(item);
167            mItem = item;
168        }
169
170        return TextToSpeech.SUCCESS;
171    }
172
173    @Override
174    public int audioAvailable(byte[] buffer, int offset, int length) {
175        if (DBG) Log.d(TAG, "audioAvailable(byte[" + buffer.length + "]," + offset + "," + length
176                + ")");
177
178        if (length > getMaxBufferSize() || length <= 0) {
179            throw new IllegalArgumentException("buffer is too large or of zero length (" +
180                    + length + " bytes)");
181        }
182
183        SynthesisPlaybackQueueItem item = null;
184        synchronized (mStateLock) {
185            if (mItem == null) {
186                mStatusCode = TextToSpeechClient.Status.ERROR_OUTPUT;
187                return TextToSpeech.ERROR;
188            }
189            if (mStatusCode != TextToSpeechClient.Status.SUCCESS) {
190                if (DBG) Log.d(TAG, "Error was raised");
191                return TextToSpeech.ERROR;
192            }
193            if (mStatusCode == TextToSpeechClient.Status.STOPPED) {
194                return errorCodeOnStop();
195            }
196            item = mItem;
197        }
198
199        // Sigh, another copy.
200        final byte[] bufferCopy = new byte[length];
201        System.arraycopy(buffer, offset, bufferCopy, 0, length);
202
203        // Might block on mItem.this, if there are too many buffers waiting to
204        // be consumed.
205        try {
206            item.put(bufferCopy);
207        } catch (InterruptedException ie) {
208            synchronized (mStateLock) {
209                mStatusCode = TextToSpeechClient.Status.ERROR_OUTPUT;
210                return TextToSpeech.ERROR;
211            }
212        }
213
214        mLogger.onEngineDataReceived();
215        return TextToSpeech.SUCCESS;
216    }
217
218    @Override
219    public int done() {
220        if (DBG) Log.d(TAG, "done()");
221
222        int statusCode = 0;
223        SynthesisPlaybackQueueItem item = null;
224        synchronized (mStateLock) {
225            if (mDone) {
226                Log.w(TAG, "Duplicate call to done()");
227                // Not an error that would prevent synthesis. Hence no
228                // setStatusCode
229                return TextToSpeech.ERROR;
230            }
231            if (mStatusCode == TextToSpeechClient.Status.STOPPED) {
232                if (DBG) Log.d(TAG, "Request has been aborted.");
233                return errorCodeOnStop();
234            }
235            mDone = true;
236
237            if (mItem == null) {
238                // .done() was called before .start. Treat it as successful synthesis
239                // for a client, despite service bad implementation.
240                Log.w(TAG, "done() was called before start() call");
241                if (mStatusCode == TextToSpeechClient.Status.SUCCESS) {
242                    mDispatcher.dispatchOnSuccess();
243                } else {
244                    mDispatcher.dispatchOnError(mStatusCode);
245                }
246                mLogger.onEngineComplete();
247                return TextToSpeech.ERROR;
248            }
249
250            item = mItem;
251            statusCode = mStatusCode;
252        }
253
254        // Signal done or error to item
255        if (statusCode == TextToSpeechClient.Status.SUCCESS) {
256            item.done();
257        } else {
258            item.stop(statusCode);
259        }
260        mLogger.onEngineComplete();
261        return TextToSpeech.SUCCESS;
262    }
263
264    @Override
265    public void error() {
266        error(TextToSpeechClient.Status.ERROR_SYNTHESIS);
267    }
268
269    @Override
270    public void error(int errorCode) {
271        if (DBG) Log.d(TAG, "error() [will call stop]");
272        synchronized (mStateLock) {
273            if (mDone) {
274                return;
275            }
276            mStatusCode = errorCode;
277        }
278    }
279
280    @Override
281    public int fallback() {
282        synchronized (mStateLock) {
283            if (hasStarted() || hasFinished()) {
284                return TextToSpeech.ERROR;
285            }
286
287            mDispatcher.dispatchOnFallback();
288            mStatusCode = TextToSpeechClient.Status.SUCCESS;
289            return TextToSpeechClient.Status.SUCCESS;
290        }
291    }
292}
293