blob: b42435618b0f99074d81651566243b9830e46b9d [file] [log] [blame]
Narayan Kamath67ae6bc2011-11-30 14:51:00 +00001/*
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;
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +010060 private volatile int mStatusCode;
Narayan Kamath67ae6bc2011-11-30 14:51:00 +000061
62 private final BlockingAudioTrack mAudioTrack;
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +010063 private final AbstractEventLogger mLogger;
Narayan Kamath67ae6bc2011-11-30 14:51:00 +000064
65 SynthesisPlaybackQueueItem(int streamType, int sampleRate,
66 int audioFormat, int channelCount,
67 float volume, float pan, UtteranceProgressDispatcher dispatcher,
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +010068 Object callerIdentity, AbstractEventLogger logger) {
Narayan Kamath67ae6bc2011-11-30 14:51:00 +000069 super(dispatcher, callerIdentity);
70
71 mUnconsumedBytes = 0;
72
73 mStopped = false;
74 mDone = false;
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +010075 mStatusCode = TextToSpeechClient.Status.SUCCESS;
Narayan Kamath67ae6bc2011-11-30 14:51:00 +000076
77 mAudioTrack = new BlockingAudioTrack(streamType, sampleRate, audioFormat,
78 channelCount, volume, pan);
79 mLogger = logger;
80 }
81
82
83 @Override
84 public void run() {
85 final UtteranceProgressDispatcher dispatcher = getDispatcher();
86 dispatcher.dispatchOnStart();
87
Narayan Kamathed4e5412012-06-09 17:31:39 +010088 if (!mAudioTrack.init()) {
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +010089 dispatcher.dispatchOnError(TextToSpeechClient.Status.ERROR_OUTPUT);
Narayan Kamathed4e5412012-06-09 17:31:39 +010090 return;
91 }
Narayan Kamath67ae6bc2011-11-30 14:51:00 +000092
93 try {
94 byte[] buffer = null;
95
96 // take() will block until:
97 //
98 // (a) there is a buffer available to tread. In which case
99 // a non null value is returned.
100 // OR (b) stop() is called in which case it will return null.
101 // OR (c) done() is called in which case it will return null.
102 while ((buffer = take()) != null) {
103 mAudioTrack.write(buffer);
104 mLogger.onAudioDataWritten();
105 }
106
107 } catch (InterruptedException ie) {
108 if (DBG) Log.d(TAG, "Interrupted waiting for buffers, cleaning up.");
109 }
110
111 mAudioTrack.waitAndRelease();
112
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +0100113 if (mStatusCode == TextToSpeechClient.Status.SUCCESS) {
114 dispatcher.dispatchOnSuccess();
115 } else if(mStatusCode == TextToSpeechClient.Status.STOPPED) {
116 dispatcher.dispatchOnStop();
Narayan Kamath67ae6bc2011-11-30 14:51:00 +0000117 } else {
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +0100118 dispatcher.dispatchOnError(mStatusCode);
Narayan Kamath67ae6bc2011-11-30 14:51:00 +0000119 }
120
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +0100121 mLogger.onCompleted(mStatusCode);
Narayan Kamath67ae6bc2011-11-30 14:51:00 +0000122 }
123
124 @Override
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +0100125 void stop(int statusCode) {
Narayan Kamath67ae6bc2011-11-30 14:51:00 +0000126 try {
127 mListLock.lock();
128
129 // Update our internal state.
130 mStopped = true;
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +0100131 mStatusCode = statusCode;
Narayan Kamath67ae6bc2011-11-30 14:51:00 +0000132
133 // Wake up the audio playback thread if it was waiting on take().
134 // take() will return null since mStopped was true, and will then
135 // break out of the data write loop.
136 mReadReady.signal();
137
138 // Wake up the synthesis thread if it was waiting on put(). Its
139 // buffers will no longer be copied since mStopped is true. The
140 // PlaybackSynthesisCallback that this synthesis corresponds to
141 // would also have been stopped, and so all calls to
142 // Callback.onDataAvailable( ) will return errors too.
143 mNotFull.signal();
144 } finally {
145 mListLock.unlock();
146 }
147
148 // Stop the underlying audio track. This will stop sending
149 // data to the mixer and discard any pending buffers that the
150 // track holds.
151 mAudioTrack.stop();
152 }
153
154 void done() {
155 try {
156 mListLock.lock();
157
158 // Update state.
159 mDone = true;
160
161 // Unblocks the audio playback thread if it was waiting on take()
162 // after having consumed all available buffers. It will then return
163 // null and leave the write loop.
164 mReadReady.signal();
165
166 // Just so that engines that try to queue buffers after
167 // calling done() don't block the synthesis thread forever. Ideally
168 // this should be called from the same thread as put() is, and hence
169 // this call should be pointless.
170 mNotFull.signal();
171 } finally {
172 mListLock.unlock();
173 }
174 }
175
176
177 void put(byte[] buffer) throws InterruptedException {
178 try {
179 mListLock.lock();
180 long unconsumedAudioMs = 0;
181
182 while ((unconsumedAudioMs = mAudioTrack.getAudioLengthMs(mUnconsumedBytes)) >
183 MAX_UNCONSUMED_AUDIO_MS && !mStopped) {
184 mNotFull.await();
185 }
186
187 // Don't bother queueing the buffer if we've stopped. The playback thread
188 // would have woken up when stop() is called (if it was blocked) and will
189 // proceed to leave the write loop since take() will return null when
190 // stopped.
191 if (mStopped) {
192 return;
193 }
194
195 mDataBufferList.add(new ListEntry(buffer));
196 mUnconsumedBytes += buffer.length;
197 mReadReady.signal();
198 } finally {
199 mListLock.unlock();
200 }
201 }
202
203 private byte[] take() throws InterruptedException {
204 try {
205 mListLock.lock();
206
207 // Block if there are no available buffers, and stop() has not
208 // been called and done() has not been called.
209 while (mDataBufferList.size() == 0 && !mStopped && !mDone) {
210 mReadReady.await();
211 }
212
213 // If stopped, return null so that we can exit the playback loop
214 // as soon as possible.
215 if (mStopped) {
216 return null;
217 }
218
219 // Remove the first entry from the queue.
220 ListEntry entry = mDataBufferList.poll();
221
222 // This is the normal playback loop exit case, when done() was
223 // called. (mDone will be true at this point).
224 if (entry == null) {
225 return null;
226 }
227
228 mUnconsumedBytes -= entry.mBytes.length;
229 // Unblock the waiting writer. We use signal() and not signalAll()
230 // because there will only be one thread waiting on this (the
231 // Synthesis thread).
232 mNotFull.signal();
233
234 return entry.mBytes;
235 } finally {
236 mListLock.unlock();
237 }
238 }
239
240 static final class ListEntry {
241 final byte[] mBytes;
242
243 ListEntry(byte[] bytes) {
244 mBytes = bytes;
245 }
246 }
247}