PlaybackSynthesisCallback.java revision a24b50bee1aca19028477b235862bcd2c37135ed
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.media.AudioFormat;
19import android.speech.tts.TextToSpeechService.AudioOutputParams;
20import android.speech.tts.TextToSpeechService.UtteranceProgressDispatcher;
21import android.util.Log;
22
23/**
24 * Speech synthesis request that plays the audio as it is received.
25 */
26class PlaybackSynthesisCallback extends AbstractSynthesisCallback {
27
28    private static final String TAG = "PlaybackSynthesisRequest";
29    private static final boolean DBG = false;
30
31    private static final int MIN_AUDIO_BUFFER_SIZE = 8192;
32
33    private final AudioOutputParams mAudioParams;
34
35    /**
36     * Guards {@link #mAudioTrackHandler}, {@link #mItem} and {@link #mStopped}.
37     */
38    private final Object mStateLock = new Object();
39
40    // Handler associated with a thread that plays back audio requests.
41    private final AudioPlaybackHandler mAudioTrackHandler;
42    // A request "token", which will be non null after start() has been called.
43    private SynthesisPlaybackQueueItem mItem = null;
44
45    private volatile boolean mDone = false;
46
47    /** Status code of synthesis */
48    protected int mStatusCode;
49
50    private final UtteranceProgressDispatcher mDispatcher;
51    private final Object mCallerIdentity;
52    private final AbstractEventLogger mLogger;
53
54    PlaybackSynthesisCallback(AudioOutputParams audioParams, AudioPlaybackHandler audioTrackHandler,
55            UtteranceProgressDispatcher dispatcher, Object callerIdentity,
56            AbstractEventLogger logger, boolean clientIsUsingV2) {
57        super(clientIsUsingV2);
58        mAudioParams = audioParams;
59        mAudioTrackHandler = audioTrackHandler;
60        mDispatcher = dispatcher;
61        mCallerIdentity = callerIdentity;
62        mLogger = logger;
63        mStatusCode = TextToSpeech.SUCCESS;
64    }
65
66    @Override
67    void stop() {
68        if (DBG) Log.d(TAG, "stop()");
69
70        SynthesisPlaybackQueueItem item;
71        synchronized (mStateLock) {
72            if (mDone) {
73                return;
74            }
75            if (mStatusCode == TextToSpeech.STOPPED) {
76                Log.w(TAG, "stop() called twice");
77                return;
78            }
79
80            item = mItem;
81            mStatusCode = TextToSpeech.STOPPED;
82        }
83
84        if (item != null) {
85            // This might result in the synthesis thread being woken up, at which
86            // point it will write an additional buffer to the item - but we
87            // won't worry about that because the audio playback queue will be cleared
88            // soon after (see SynthHandler#stop(String).
89            item.stop(TextToSpeech.STOPPED);
90        } else {
91            // This happens when stop() or error() were called before start() was.
92
93            // In all other cases, mAudioTrackHandler.stop() will
94            // result in onSynthesisDone being called, and we will
95            // write data there.
96            mLogger.onCompleted(TextToSpeech.STOPPED);
97            mDispatcher.dispatchOnStop();
98        }
99    }
100
101    @Override
102    public int getMaxBufferSize() {
103        // The AudioTrack buffer will be at least MIN_AUDIO_BUFFER_SIZE, so that should always be
104        // a safe buffer size to pass in.
105        return MIN_AUDIO_BUFFER_SIZE;
106    }
107
108    @Override
109    public boolean hasStarted() {
110        synchronized (mStateLock) {
111            return mItem != null;
112        }
113    }
114
115    @Override
116    public boolean hasFinished() {
117        synchronized (mStateLock) {
118            return mDone;
119        }
120    }
121
122    @Override
123    public int start(int sampleRateInHz, int audioFormat, int channelCount) {
124        if (DBG) Log.d(TAG, "start(" + sampleRateInHz + "," + audioFormat + "," + channelCount
125                + ")");
126        if (audioFormat != AudioFormat.ENCODING_PCM_8BIT ||
127            audioFormat != AudioFormat.ENCODING_PCM_16BIT ||
128            audioFormat != AudioFormat.ENCODING_PCM_FLOAT) {
129            Log.w(TAG, "Audio format encoding " + audioFormat + " not supported. Please use one " +
130                       "of AudioFormat.ENCODING_PCM_8BIT, AudioFormat.ENCODING_PCM_16BIT or " +
131                       "AudioFormat.ENCODING_PCM_FLOAT");
132        }
133
134        int channelConfig = BlockingAudioTrack.getChannelConfig(channelCount);
135
136        synchronized (mStateLock) {
137            if (channelConfig == 0) {
138                Log.e(TAG, "Unsupported number of channels :" + channelCount);
139                mStatusCode = TextToSpeech.ERROR_OUTPUT;
140                return TextToSpeech.ERROR;
141            }
142            if (mStatusCode == TextToSpeech.STOPPED) {
143                if (DBG) Log.d(TAG, "stop() called before start(), returning.");
144                return errorCodeOnStop();
145            }
146            if (mStatusCode != TextToSpeech.SUCCESS) {
147                if (DBG) Log.d(TAG, "Error was raised");
148                return TextToSpeech.ERROR;
149            }
150            if (mItem != null) {
151                Log.e(TAG, "Start called twice");
152                return TextToSpeech.ERROR;
153            }
154            SynthesisPlaybackQueueItem item = new SynthesisPlaybackQueueItem(
155                    mAudioParams, sampleRateInHz, audioFormat, channelCount,
156                    mDispatcher, mCallerIdentity, mLogger);
157            mAudioTrackHandler.enqueue(item);
158            mItem = item;
159        }
160
161        return TextToSpeech.SUCCESS;
162    }
163
164    @Override
165    public int audioAvailable(byte[] buffer, int offset, int length) {
166        if (DBG) Log.d(TAG, "audioAvailable(byte[" + buffer.length + "]," + offset + "," + length
167                + ")");
168
169        if (length > getMaxBufferSize() || length <= 0) {
170            throw new IllegalArgumentException("buffer is too large or of zero length (" +
171                    + length + " bytes)");
172        }
173
174        SynthesisPlaybackQueueItem item = null;
175        synchronized (mStateLock) {
176            if (mItem == null) {
177                mStatusCode = TextToSpeech.ERROR_OUTPUT;
178                return TextToSpeech.ERROR;
179            }
180            if (mStatusCode != TextToSpeech.SUCCESS) {
181                if (DBG) Log.d(TAG, "Error was raised");
182                return TextToSpeech.ERROR;
183            }
184            if (mStatusCode == TextToSpeech.STOPPED) {
185                return errorCodeOnStop();
186            }
187            item = mItem;
188        }
189
190        // Sigh, another copy.
191        final byte[] bufferCopy = new byte[length];
192        System.arraycopy(buffer, offset, bufferCopy, 0, length);
193
194        // Might block on mItem.this, if there are too many buffers waiting to
195        // be consumed.
196        try {
197            item.put(bufferCopy);
198        } catch (InterruptedException ie) {
199            synchronized (mStateLock) {
200                mStatusCode = TextToSpeech.ERROR_OUTPUT;
201                return TextToSpeech.ERROR;
202            }
203        }
204
205        mLogger.onEngineDataReceived();
206        return TextToSpeech.SUCCESS;
207    }
208
209    @Override
210    public int done() {
211        if (DBG) Log.d(TAG, "done()");
212
213        int statusCode = 0;
214        SynthesisPlaybackQueueItem item = null;
215        synchronized (mStateLock) {
216            if (mDone) {
217                Log.w(TAG, "Duplicate call to done()");
218                // Not an error that would prevent synthesis. Hence no
219                // setStatusCode
220                return TextToSpeech.ERROR;
221            }
222            if (mStatusCode == TextToSpeech.STOPPED) {
223                if (DBG) Log.d(TAG, "Request has been aborted.");
224                return errorCodeOnStop();
225            }
226            mDone = true;
227
228            if (mItem == null) {
229                // .done() was called before .start. Treat it as successful synthesis
230                // for a client, despite service bad implementation.
231                Log.w(TAG, "done() was called before start() call");
232                if (mStatusCode == TextToSpeech.SUCCESS) {
233                    mDispatcher.dispatchOnSuccess();
234                } else {
235                    mDispatcher.dispatchOnError(mStatusCode);
236                }
237                mLogger.onEngineComplete();
238                return TextToSpeech.ERROR;
239            }
240
241            item = mItem;
242            statusCode = mStatusCode;
243        }
244
245        // Signal done or error to item
246        if (statusCode == TextToSpeech.SUCCESS) {
247            item.done();
248        } else {
249            item.stop(statusCode);
250        }
251        mLogger.onEngineComplete();
252        return TextToSpeech.SUCCESS;
253    }
254
255    @Override
256    public void error() {
257        error(TextToSpeech.ERROR_SYNTHESIS);
258    }
259
260    @Override
261    public void error(int errorCode) {
262        if (DBG) Log.d(TAG, "error() [will call stop]");
263        synchronized (mStateLock) {
264            if (mDone) {
265                return;
266            }
267            mStatusCode = errorCode;
268        }
269    }
270}
271