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