SynthesisPlaybackQueueItem.java revision 67ae6bc83cf2b30b0c61b9ebba5fed7a0038549c
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
21import java.util.LinkedList;
22import java.util.concurrent.locks.Condition;
23import java.util.concurrent.locks.Lock;
24import java.util.concurrent.locks.ReentrantLock;
25
26/**
27 * Manages the playback of a list of byte arrays representing audio data
28 * that are queued by the engine to an audio track.
29 */
30final class SynthesisPlaybackQueueItem extends PlaybackQueueItem {
31    private static final String TAG = "TTS.SynthQueueItem";
32    private static final boolean DBG = false;
33
34    /**
35     * Maximum length of audio we leave unconsumed by the audio track.
36     * Calls to {@link #put(byte[])} will block until we have less than
37     * this amount of audio left to play back.
38     */
39    private static final long MAX_UNCONSUMED_AUDIO_MS = 500;
40
41    /**
42     * Guards accesses to mDataBufferList and mUnconsumedBytes.
43     */
44    private final Lock mListLock = new ReentrantLock();
45    private final Condition mReadReady = mListLock.newCondition();
46    private final Condition mNotFull = mListLock.newCondition();
47
48    // Guarded by mListLock.
49    private final LinkedList<ListEntry> mDataBufferList = new LinkedList<ListEntry>();
50    // Guarded by mListLock.
51    private int mUnconsumedBytes;
52
53    /*
54     * While mStopped and mIsError can be written from any thread, mDone is written
55     * only from the synthesis thread. All three variables are read from the
56     * audio playback thread.
57     */
58    private volatile boolean mStopped;
59    private volatile boolean mDone;
60    private volatile boolean mIsError;
61
62    private final BlockingAudioTrack mAudioTrack;
63    private final EventLogger mLogger;
64
65
66    SynthesisPlaybackQueueItem(int streamType, int sampleRate,
67            int audioFormat, int channelCount,
68            float volume, float pan, UtteranceProgressDispatcher dispatcher,
69            Object callerIdentity, EventLogger logger) {
70        super(dispatcher, callerIdentity);
71
72        mUnconsumedBytes = 0;
73
74        mStopped = false;
75        mDone = false;
76        mIsError = false;
77
78        mAudioTrack = new BlockingAudioTrack(streamType, sampleRate, audioFormat,
79                channelCount, volume, pan);
80        mLogger = logger;
81    }
82
83
84    @Override
85    public void run() {
86        final UtteranceProgressDispatcher dispatcher = getDispatcher();
87        dispatcher.dispatchOnStart();
88
89
90        mAudioTrack.init();
91
92        try {
93            byte[] buffer = null;
94
95            // take() will block until:
96            //
97            // (a) there is a buffer available to tread. In which case
98            // a non null value is returned.
99            // OR (b) stop() is called in which case it will return null.
100            // OR (c) done() is called in which case it will return null.
101            while ((buffer = take()) != null) {
102                mAudioTrack.write(buffer);
103                mLogger.onAudioDataWritten();
104            }
105
106        } catch (InterruptedException ie) {
107            if (DBG) Log.d(TAG, "Interrupted waiting for buffers, cleaning up.");
108        }
109
110        mAudioTrack.waitAndRelease();
111
112        if (mIsError) {
113            dispatcher.dispatchOnError();
114        } else {
115            dispatcher.dispatchOnDone();
116        }
117
118        mLogger.onWriteData();
119    }
120
121    @Override
122    void stop(boolean isError) {
123        try {
124            mListLock.lock();
125
126            // Update our internal state.
127            mStopped = true;
128            mIsError = isError;
129
130            // Wake up the audio playback thread if it was waiting on take().
131            // take() will return null since mStopped was true, and will then
132            // break out of the data write loop.
133            mReadReady.signal();
134
135            // Wake up the synthesis thread if it was waiting on put(). Its
136            // buffers will no longer be copied since mStopped is true. The
137            // PlaybackSynthesisCallback that this synthesis corresponds to
138            // would also have been stopped, and so all calls to
139            // Callback.onDataAvailable( ) will return errors too.
140            mNotFull.signal();
141        } finally {
142            mListLock.unlock();
143        }
144
145        // Stop the underlying audio track. This will stop sending
146        // data to the mixer and discard any pending buffers that the
147        // track holds.
148        mAudioTrack.stop();
149    }
150
151    void done() {
152        try {
153            mListLock.lock();
154
155            // Update state.
156            mDone = true;
157
158            // Unblocks the audio playback thread if it was waiting on take()
159            // after having consumed all available buffers. It will then return
160            // null and leave the write loop.
161            mReadReady.signal();
162
163            // Just so that engines that try to queue buffers after
164            // calling done() don't block the synthesis thread forever. Ideally
165            // this should be called from the same thread as put() is, and hence
166            // this call should be pointless.
167            mNotFull.signal();
168        } finally {
169            mListLock.unlock();
170        }
171    }
172
173
174    void put(byte[] buffer) throws InterruptedException {
175        try {
176            mListLock.lock();
177            long unconsumedAudioMs = 0;
178
179            while ((unconsumedAudioMs = mAudioTrack.getAudioLengthMs(mUnconsumedBytes)) >
180                    MAX_UNCONSUMED_AUDIO_MS && !mStopped) {
181                mNotFull.await();
182            }
183
184            // Don't bother queueing the buffer if we've stopped. The playback thread
185            // would have woken up when stop() is called (if it was blocked) and will
186            // proceed to leave the write loop since take() will return null when
187            // stopped.
188            if (mStopped) {
189                return;
190            }
191
192            mDataBufferList.add(new ListEntry(buffer));
193            mUnconsumedBytes += buffer.length;
194            mReadReady.signal();
195        } finally {
196            mListLock.unlock();
197        }
198    }
199
200    private byte[] take() throws InterruptedException {
201        try {
202            mListLock.lock();
203
204            // Block if there are no available buffers, and stop() has not
205            // been called and done() has not been called.
206            while (mDataBufferList.size() == 0 && !mStopped && !mDone) {
207                mReadReady.await();
208            }
209
210            // If stopped, return null so that we can exit the playback loop
211            // as soon as possible.
212            if (mStopped) {
213                return null;
214            }
215
216            // Remove the first entry from the queue.
217            ListEntry entry = mDataBufferList.poll();
218
219            // This is the normal playback loop exit case, when done() was
220            // called. (mDone will be true at this point).
221            if (entry == null) {
222                return null;
223            }
224
225            mUnconsumedBytes -= entry.mBytes.length;
226            // Unblock the waiting writer. We use signal() and not signalAll()
227            // because there will only be one thread waiting on this (the
228            // Synthesis thread).
229            mNotFull.signal();
230
231            return entry.mBytes;
232        } finally {
233            mListLock.unlock();
234        }
235    }
236
237    static final class ListEntry {
238        final byte[] mBytes;
239
240        ListEntry(byte[] bytes) {
241            mBytes = bytes;
242        }
243    }
244}
245
246