AudioPlaybackHandler.java revision be4ad4ac66d6b4b878ed052975f7fb09af92c6d6
18d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath/* 28d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath * Copyright (C) 2011 The Android Open Source Project 38d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath * 48d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath * Licensed under the Apache License, Version 2.0 (the "License"); you may not 58d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath * use this file except in compliance with the License. You may obtain a copy of 68d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath * the License at 78d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath * 88d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath * http://www.apache.org/licenses/LICENSE-2.0 98d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath * 108d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath * Unless required by applicable law or agreed to in writing, software 118d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 128d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 138d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath * License for the specific language governing permissions and limitations under 148d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath * the License. 158d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath */ 168d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamathpackage android.speech.tts; 178d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath 188d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamathimport android.media.AudioFormat; 198d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamathimport android.media.AudioTrack; 20be4ad4ac66d6b4b878ed052975f7fb09af92c6d6Narayan Kamathimport android.text.TextUtils; 218d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamathimport android.util.Log; 228d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath 234924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamathimport java.util.Iterator; 244924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamathimport java.util.concurrent.PriorityBlockingQueue; 254924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamathimport java.util.concurrent.atomic.AtomicLong; 264924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath 274924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamathclass AudioPlaybackHandler { 288d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath private static final String TAG = "TTS.AudioPlaybackHandler"; 29be4ad4ac66d6b4b878ed052975f7fb09af92c6d6Narayan Kamath private static final boolean DBG_THREADING = false; 308d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath private static final boolean DBG = false; 318d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath 328d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath private static final int MIN_AUDIO_BUFFER_SIZE = 8192; 338d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath 348d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath private static final int SYNTHESIS_START = 1; 358d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath private static final int SYNTHESIS_DATA_AVAILABLE = 2; 36c3da8818f0598b3ab2cd6f4168349da6d0f72cb1Narayan Kamath private static final int SYNTHESIS_DONE = 3; 378d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath 388d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath private static final int PLAY_AUDIO = 5; 398d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath private static final int PLAY_SILENCE = 6; 408d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath 414924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath private static final int SHUTDOWN = -1; 424924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath 434924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath private static final int DEFAULT_PRIORITY = 1; 444924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath private static final int HIGH_PRIORITY = 0; 454924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath 464924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath private final PriorityBlockingQueue<ListEntry> mQueue = 474924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath new PriorityBlockingQueue<ListEntry>(); 484924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath private final Thread mHandlerThread; 494924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath 50abc63fbddab2477a2954bc804aba2826e1f11084Narayan Kamath private volatile MessageParams mCurrentParams = null; 518d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath // Used only for book keeping and error detection. 524924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath private volatile SynthesisMessageParams mLastSynthesisRequest = null; 534924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath // Used to order incoming messages in our priority queue. 544924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath private final AtomicLong mSequenceIdCtr = new AtomicLong(0); 558d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath 568d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath 574924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath AudioPlaybackHandler() { 584924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath mHandlerThread = new Thread(new MessageLoop(), "TTS.AudioPlaybackThread"); 594924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath } 608d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath 614924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath public void start() { 624924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath mHandlerThread.start(); 638d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath } 648d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath 658d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath /** 668d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath * Stops all synthesis for a given {@code token}. If the current token 678d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath * is currently being processed, an effort will be made to stop it but 688d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath * that is not guaranteed. 69be4ad4ac66d6b4b878ed052975f7fb09af92c6d6Narayan Kamath * 70be4ad4ac66d6b4b878ed052975f7fb09af92c6d6Narayan Kamath * NOTE: This assumes that all other messages in the queue with {@code token} 71be4ad4ac66d6b4b878ed052975f7fb09af92c6d6Narayan Kamath * have been removed already. 72be4ad4ac66d6b4b878ed052975f7fb09af92c6d6Narayan Kamath * 73be4ad4ac66d6b4b878ed052975f7fb09af92c6d6Narayan Kamath * NOTE: Must be called synchronized on {@code AudioPlaybackHandler.this}. 748d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath */ 75be4ad4ac66d6b4b878ed052975f7fb09af92c6d6Narayan Kamath private void stop(MessageParams token) { 764924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath if (token == null) { 774924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath return; 784924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath } 794924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath 80be4ad4ac66d6b4b878ed052975f7fb09af92c6d6Narayan Kamath if (DBG) Log.d(TAG, "Stopping token : " + token); 818d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath 828d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath if (token.getType() == MessageParams.TYPE_SYNTHESIS) { 83963719869967cc257e666809aeb9bff3f25117edNarayan Kamath AudioTrack current = ((SynthesisMessageParams) token).getAudioTrack(); 84963719869967cc257e666809aeb9bff3f25117edNarayan Kamath if (current != null) { 85963719869967cc257e666809aeb9bff3f25117edNarayan Kamath // Stop the current audio track if it's still playing. 86be4ad4ac66d6b4b878ed052975f7fb09af92c6d6Narayan Kamath // The audio track is thread safe in this regard. The current 87be4ad4ac66d6b4b878ed052975f7fb09af92c6d6Narayan Kamath // handleSynthesisDataAvailable call will return soon after this 88be4ad4ac66d6b4b878ed052975f7fb09af92c6d6Narayan Kamath // call. 89963719869967cc257e666809aeb9bff3f25117edNarayan Kamath current.stop(); 90963719869967cc257e666809aeb9bff3f25117edNarayan Kamath } 91be4ad4ac66d6b4b878ed052975f7fb09af92c6d6Narayan Kamath // This is safe because PlaybackSynthesisCallback#stop would have 92be4ad4ac66d6b4b878ed052975f7fb09af92c6d6Narayan Kamath // been called before this method, and will no longer enqueue any 93be4ad4ac66d6b4b878ed052975f7fb09af92c6d6Narayan Kamath // audio for this token. 94be4ad4ac66d6b4b878ed052975f7fb09af92c6d6Narayan Kamath // 95be4ad4ac66d6b4b878ed052975f7fb09af92c6d6Narayan Kamath // (Even if it did, all it would result in is a warning message). 964924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath mQueue.add(new ListEntry(SYNTHESIS_DONE, token, HIGH_PRIORITY)); 974924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath } else { 98be4ad4ac66d6b4b878ed052975f7fb09af92c6d6Narayan Kamath if (token != null) { 994924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath if (token.getType() == MessageParams.TYPE_AUDIO) { 100be4ad4ac66d6b4b878ed052975f7fb09af92c6d6Narayan Kamath ((AudioMessageParams) token).getPlayer().stop(); 1014924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath } else if (token.getType() == MessageParams.TYPE_SILENCE) { 102be4ad4ac66d6b4b878ed052975f7fb09af92c6d6Narayan Kamath ((SilenceMessageParams) token).getConditionVariable().open(); 1034924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath } 1048d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath } 1058d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath } 1068d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath } 1078d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath 108be4ad4ac66d6b4b878ed052975f7fb09af92c6d6Narayan Kamath // ----------------------------------------------------- 109be4ad4ac66d6b4b878ed052975f7fb09af92c6d6Narayan Kamath // Methods that add and remove elements from the queue. These do not 110be4ad4ac66d6b4b878ed052975f7fb09af92c6d6Narayan Kamath // need to be synchronized strictly speaking, but they make the behaviour 111be4ad4ac66d6b4b878ed052975f7fb09af92c6d6Narayan Kamath // a lot more predictable. (though it would still be correct without 112be4ad4ac66d6b4b878ed052975f7fb09af92c6d6Narayan Kamath // synchronization). 113be4ad4ac66d6b4b878ed052975f7fb09af92c6d6Narayan Kamath // ----------------------------------------------------- 114be4ad4ac66d6b4b878ed052975f7fb09af92c6d6Narayan Kamath 1154924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath synchronized public void removePlaybackItems(String callingApp) { 116be4ad4ac66d6b4b878ed052975f7fb09af92c6d6Narayan Kamath if (DBG_THREADING) Log.d(TAG, "Removing all callback items for : " + callingApp); 1174924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath removeMessages(callingApp); 118be4ad4ac66d6b4b878ed052975f7fb09af92c6d6Narayan Kamath 119be4ad4ac66d6b4b878ed052975f7fb09af92c6d6Narayan Kamath final MessageParams current = getCurrentParams(); 120be4ad4ac66d6b4b878ed052975f7fb09af92c6d6Narayan Kamath if (current != null && TextUtils.equals(callingApp, current.getCallingApp())) { 121be4ad4ac66d6b4b878ed052975f7fb09af92c6d6Narayan Kamath stop(current); 122be4ad4ac66d6b4b878ed052975f7fb09af92c6d6Narayan Kamath } 1234924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath } 1244924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath 1254924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath synchronized public void removeAllItems() { 126be4ad4ac66d6b4b878ed052975f7fb09af92c6d6Narayan Kamath if (DBG_THREADING) Log.d(TAG, "Removing all items"); 1274924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath removeAllMessages(); 1284924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath stop(getCurrentParams()); 1294924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath } 1304924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath 1318d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath /** 132c34f76fe89b5a31d01d63067c2f24b9a6a76df18Narayan Kamath * @return false iff the queue is empty and no queue item is currently 133c34f76fe89b5a31d01d63067c2f24b9a6a76df18Narayan Kamath * being handled, true otherwise. 134c34f76fe89b5a31d01d63067c2f24b9a6a76df18Narayan Kamath */ 135c34f76fe89b5a31d01d63067c2f24b9a6a76df18Narayan Kamath public boolean isSpeaking() { 136c34f76fe89b5a31d01d63067c2f24b9a6a76df18Narayan Kamath return (mQueue.peek() != null) || (mCurrentParams != null); 137c34f76fe89b5a31d01d63067c2f24b9a6a76df18Narayan Kamath } 138c34f76fe89b5a31d01d63067c2f24b9a6a76df18Narayan Kamath 139c34f76fe89b5a31d01d63067c2f24b9a6a76df18Narayan Kamath /** 1408d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath * Shut down the audio playback thread. 1418d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath */ 1428d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath synchronized public void quit() { 143be4ad4ac66d6b4b878ed052975f7fb09af92c6d6Narayan Kamath removeAllMessages(); 1444924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath stop(getCurrentParams()); 1454924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath mQueue.add(new ListEntry(SHUTDOWN, null, HIGH_PRIORITY)); 1468d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath } 1478d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath 148be4ad4ac66d6b4b878ed052975f7fb09af92c6d6Narayan Kamath synchronized void enqueueSynthesisStart(SynthesisMessageParams token) { 149be4ad4ac66d6b4b878ed052975f7fb09af92c6d6Narayan Kamath if (DBG_THREADING) Log.d(TAG, "Enqueuing synthesis start : " + token); 1504924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath mQueue.add(new ListEntry(SYNTHESIS_START, token)); 1518d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath } 1528d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath 153be4ad4ac66d6b4b878ed052975f7fb09af92c6d6Narayan Kamath synchronized void enqueueSynthesisDataAvailable(SynthesisMessageParams token) { 154be4ad4ac66d6b4b878ed052975f7fb09af92c6d6Narayan Kamath if (DBG_THREADING) Log.d(TAG, "Enqueuing synthesis data available : " + token); 1554924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath mQueue.add(new ListEntry(SYNTHESIS_DATA_AVAILABLE, token)); 1568d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath } 1578d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath 158be4ad4ac66d6b4b878ed052975f7fb09af92c6d6Narayan Kamath synchronized void enqueueSynthesisDone(SynthesisMessageParams token) { 159be4ad4ac66d6b4b878ed052975f7fb09af92c6d6Narayan Kamath if (DBG_THREADING) Log.d(TAG, "Enqueuing synthesis done : " + token); 1604924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath mQueue.add(new ListEntry(SYNTHESIS_DONE, token)); 1618d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath } 1628d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath 163be4ad4ac66d6b4b878ed052975f7fb09af92c6d6Narayan Kamath synchronized void enqueueAudio(AudioMessageParams token) { 164be4ad4ac66d6b4b878ed052975f7fb09af92c6d6Narayan Kamath if (DBG_THREADING) Log.d(TAG, "Enqueuing audio : " + token); 1654924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath mQueue.add(new ListEntry(PLAY_AUDIO, token)); 1668d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath } 1678d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath 168be4ad4ac66d6b4b878ed052975f7fb09af92c6d6Narayan Kamath synchronized void enqueueSilence(SilenceMessageParams token) { 169be4ad4ac66d6b4b878ed052975f7fb09af92c6d6Narayan Kamath if (DBG_THREADING) Log.d(TAG, "Enqueuing silence : " + token); 1704924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath mQueue.add(new ListEntry(PLAY_SILENCE, token)); 1718d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath } 1728d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath 1738d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath // ----------------------------------------- 1748d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath // End of public API methods. 1758d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath // ----------------------------------------- 1768d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath 1774924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath // ----------------------------------------- 1784924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath // Methods for managing the message queue. 1794924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath // ----------------------------------------- 1804924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath 1814924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath /* 1824924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath * The MessageLoop is a handler like implementation that 1834924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath * processes messages from a priority queue. 1844924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath */ 1854924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath private final class MessageLoop implements Runnable { 1864924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath @Override 1874924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath public void run() { 1884924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath while (true) { 1894924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath ListEntry entry = null; 1904924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath try { 1914924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath entry = mQueue.take(); 1924924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath } catch (InterruptedException ie) { 1934924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath return; 1944924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath } 1954924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath 1964924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath if (entry.mWhat == SHUTDOWN) { 1974924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath if (DBG) Log.d(TAG, "MessageLoop : Shutting down"); 1984924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath return; 1994924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath } 2004924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath 2014924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath if (DBG) { 2024924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath Log.d(TAG, "MessageLoop : Handling message :" + entry.mWhat 2034924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath + " ,seqId : " + entry.mSequenceId); 2044924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath } 2054924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath 2064924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath setCurrentParams(entry.mMessage); 2074924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath handleMessage(entry); 2084924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath setCurrentParams(null); 2094924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath } 2104924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath } 2114924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath } 2124924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath 2134924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath /* 2144924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath * Atomically clear the queue of all messages. 2154924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath */ 2164924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath synchronized private void removeAllMessages() { 2174924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath mQueue.clear(); 2184924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath } 2194924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath 2204924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath /* 2214924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath * Remove all messages that originate from a given calling app. 2224924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath */ 2234924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath synchronized private void removeMessages(String callingApp) { 2244924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath Iterator<ListEntry> it = mQueue.iterator(); 2254924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath 2264924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath while (it.hasNext()) { 2274924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath final ListEntry current = it.next(); 2284924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath // The null check is to prevent us from removing control messages, 2294924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath // such as a shutdown message. 2304924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath if (current.mMessage != null && 2314924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath callingApp.equals(current.mMessage.getCallingApp())) { 2324924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath it.remove(); 2334924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath } 2344924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath } 2354924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath } 2364924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath 2374924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath /* 2384924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath * An element of our priority queue of messages. Each message has a priority, 2394924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath * and a sequence id (defined by the order of enqueue calls). Among messages 2404924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath * with the same priority, messages that were received earlier win out. 2414924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath */ 2424924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath private final class ListEntry implements Comparable<ListEntry> { 2434924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath final int mWhat; 2444924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath final MessageParams mMessage; 2454924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath final int mPriority; 2464924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath final long mSequenceId; 2474924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath 2484924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath private ListEntry(int what, MessageParams message) { 2494924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath this(what, message, DEFAULT_PRIORITY); 2504924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath } 2514924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath 2524924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath private ListEntry(int what, MessageParams message, int priority) { 2534924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath mWhat = what; 2544924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath mMessage = message; 2554924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath mPriority = priority; 2564924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath mSequenceId = mSequenceIdCtr.incrementAndGet(); 2574924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath } 2584924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath 2594924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath @Override 2604924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath public int compareTo(ListEntry that) { 2614924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath if (that == this) { 2624924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath return 0; 2634924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath } 2644924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath 2654924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath // Note that this is always 0, 1 or -1. 2664924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath int priorityDiff = mPriority - that.mPriority; 2674924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath if (priorityDiff == 0) { 2684924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath // The == case cannot occur. 2694924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath return (mSequenceId < that.mSequenceId) ? -1 : 1; 2704924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath } 2714924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath 2724924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath return priorityDiff; 2734924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath } 2744924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath } 2754924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath 2764924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath private void setCurrentParams(MessageParams p) { 277be4ad4ac66d6b4b878ed052975f7fb09af92c6d6Narayan Kamath if (DBG_THREADING) { 278be4ad4ac66d6b4b878ed052975f7fb09af92c6d6Narayan Kamath if (p != null) { 279be4ad4ac66d6b4b878ed052975f7fb09af92c6d6Narayan Kamath Log.d(TAG, "Started handling :" + p); 280be4ad4ac66d6b4b878ed052975f7fb09af92c6d6Narayan Kamath } else { 281be4ad4ac66d6b4b878ed052975f7fb09af92c6d6Narayan Kamath Log.d(TAG, "End handling : " + mCurrentParams); 282be4ad4ac66d6b4b878ed052975f7fb09af92c6d6Narayan Kamath } 283be4ad4ac66d6b4b878ed052975f7fb09af92c6d6Narayan Kamath } 284abc63fbddab2477a2954bc804aba2826e1f11084Narayan Kamath mCurrentParams = p; 2854924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath } 2864924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath 2874924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath private MessageParams getCurrentParams() { 288abc63fbddab2477a2954bc804aba2826e1f11084Narayan Kamath return mCurrentParams; 2894924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath } 2904924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath 2914924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath // ----------------------------------------- 2924924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath // Methods for dealing with individual messages, the methods 2934924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath // below do the actual work. 2944924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath // ----------------------------------------- 2954924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath 2964924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath private void handleMessage(ListEntry entry) { 2974924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath final MessageParams msg = entry.mMessage; 2984924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath if (entry.mWhat == SYNTHESIS_START) { 2994924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath handleSynthesisStart(msg); 3004924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath } else if (entry.mWhat == SYNTHESIS_DATA_AVAILABLE) { 3014924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath handleSynthesisDataAvailable(msg); 3024924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath } else if (entry.mWhat == SYNTHESIS_DONE) { 3034924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath handleSynthesisDone(msg); 3044924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath } else if (entry.mWhat == PLAY_AUDIO) { 3054924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath handleAudio(msg); 3064924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath } else if (entry.mWhat == PLAY_SILENCE) { 3074924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath handleSilence(msg); 3084924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath } 3094924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath } 3104924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath 3118d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath // Currently implemented as blocking the audio playback thread for the 3128d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath // specified duration. If a call to stop() is made, the thread 3138d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath // unblocks. 3144924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath private void handleSilence(MessageParams msg) { 3158d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath if (DBG) Log.d(TAG, "handleSilence()"); 3164924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath SilenceMessageParams params = (SilenceMessageParams) msg; 3178d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath if (params.getSilenceDurationMs() > 0) { 3188d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath params.getConditionVariable().block(params.getSilenceDurationMs()); 3198d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath } 3208d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath params.getDispatcher().dispatchUtteranceCompleted(); 3218d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath if (DBG) Log.d(TAG, "handleSilence() done."); 3228d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath } 3238d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath 3248d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath // Plays back audio from a given URI. No TTS engine involvement here. 3254924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath private void handleAudio(MessageParams msg) { 3268d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath if (DBG) Log.d(TAG, "handleAudio()"); 3274924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath AudioMessageParams params = (AudioMessageParams) msg; 3288d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath // Note that the BlockingMediaPlayer spawns a separate thread. 3298d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath // 3308d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath // TODO: This can be avoided. 3318d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath params.getPlayer().startAndWait(); 3328d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath params.getDispatcher().dispatchUtteranceCompleted(); 3338d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath if (DBG) Log.d(TAG, "handleAudio() done."); 3348d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath } 3358d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath 3368d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath // Denotes the start of a new synthesis request. We create a new 3378d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath // audio track, and prepare it for incoming data. 3388d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath // 3398d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath // Note that since all TTS synthesis happens on a single thread, we 3408d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath // should ALWAYS see the following order : 3418d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath // 3428d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath // handleSynthesisStart -> handleSynthesisDataAvailable(*) -> handleSynthesisDone 3438d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath // OR 3448d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath // handleSynthesisCompleteDataAvailable. 3454924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath private void handleSynthesisStart(MessageParams msg) { 3468d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath if (DBG) Log.d(TAG, "handleSynthesisStart()"); 3474924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath final SynthesisMessageParams param = (SynthesisMessageParams) msg; 3488d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath 3498d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath // Oops, looks like the engine forgot to call done(). We go through 3508d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath // extra trouble to clean the data to prevent the AudioTrack resources 3518d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath // from being leaked. 3528d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath if (mLastSynthesisRequest != null) { 3538d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath Log.w(TAG, "Error : Missing call to done() for request : " + 3548d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath mLastSynthesisRequest); 3558d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath handleSynthesisDone(mLastSynthesisRequest); 3568d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath } 3578d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath 3588d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath mLastSynthesisRequest = param; 3598d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath 3608d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath // Create the audio track. 3618d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath final AudioTrack audioTrack = createStreamingAudioTrack( 3628d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath param.mStreamType, param.mSampleRateInHz, param.mAudioFormat, 3638d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath param.mChannelCount, param.mVolume, param.mPan); 3648d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath 3654924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath if (DBG) Log.d(TAG, "Created audio track [" + audioTrack.hashCode() + "]"); 3664924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath 3678d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath param.setAudioTrack(audioTrack); 3688d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath } 3698d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath 3708d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath // More data available to be flushed to the audio track. 3714924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath private void handleSynthesisDataAvailable(MessageParams msg) { 3724924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath final SynthesisMessageParams param = (SynthesisMessageParams) msg; 3738d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath if (param.getAudioTrack() == null) { 374be4ad4ac66d6b4b878ed052975f7fb09af92c6d6Narayan Kamath Log.w(TAG, "Error : null audio track in handleDataAvailable : " + param); 3758d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath return; 3768d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath } 3778d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath 3788d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath if (param != mLastSynthesisRequest) { 3798d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath Log.e(TAG, "Call to dataAvailable without done() / start()"); 3808d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath return; 3818d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath } 3828d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath 3838d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath final AudioTrack audioTrack = param.getAudioTrack(); 3844924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath final SynthesisMessageParams.ListEntry bufferCopy = param.getNextBuffer(); 3858d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath 3868d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath if (bufferCopy == null) { 3878d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath Log.e(TAG, "No buffers available to play."); 3888d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath return; 3898d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath } 3908d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath 3918d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath int playState = audioTrack.getPlayState(); 3928d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath if (playState == AudioTrack.PLAYSTATE_STOPPED) { 3938d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath if (DBG) Log.d(TAG, "AudioTrack stopped, restarting : " + audioTrack.hashCode()); 3948d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath audioTrack.play(); 3958d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath } 3968d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath int count = 0; 3978d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath while (count < bufferCopy.mLength) { 3988d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath // Note that we don't take bufferCopy.mOffset into account because 3998d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath // it is guaranteed to be 0. 4008d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath int written = audioTrack.write(bufferCopy.mBytes, count, bufferCopy.mLength); 4018d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath if (written <= 0) { 4028d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath break; 4038d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath } 4048d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath count += written; 4058d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath } 40647d6288541324b27c80b9949670f7b6b18d3ae4cNarayan Kamath param.mBytesWritten += count; 4076dabb63307a0b63f9386d61e8444aed29db2081eNarayan Kamath param.mLogger.onPlaybackStart(); 4088d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath } 4098d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath 4104924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath private void handleSynthesisDone(MessageParams msg) { 4114924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath final SynthesisMessageParams params = (SynthesisMessageParams) msg; 4128d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath handleSynthesisDone(params); 4136dabb63307a0b63f9386d61e8444aed29db2081eNarayan Kamath // This call is delayed more than it should be, but we are 4146dabb63307a0b63f9386d61e8444aed29db2081eNarayan Kamath // certain at this point that we have all the data we want. 4156dabb63307a0b63f9386d61e8444aed29db2081eNarayan Kamath params.mLogger.onWriteData(); 4168d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath } 4178d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath 41847d6288541324b27c80b9949670f7b6b18d3ae4cNarayan Kamath // Wait for the audio track to stop playing, and then release it's resources. 4198d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath private void handleSynthesisDone(SynthesisMessageParams params) { 4208d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath if (DBG) Log.d(TAG, "handleSynthesisDone()"); 4218d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath final AudioTrack audioTrack = params.getAudioTrack(); 4228d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath 4238d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath try { 4248d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath if (audioTrack != null) { 42547d6288541324b27c80b9949670f7b6b18d3ae4cNarayan Kamath if (DBG) Log.d(TAG, "Waiting for audio track to complete : " + 42647d6288541324b27c80b9949670f7b6b18d3ae4cNarayan Kamath audioTrack.hashCode()); 42747d6288541324b27c80b9949670f7b6b18d3ae4cNarayan Kamath blockUntilDone(params); 4284924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath if (DBG) Log.d(TAG, "Releasing audio track [" + audioTrack.hashCode() + "]"); 429963719869967cc257e666809aeb9bff3f25117edNarayan Kamath // The last call to AudioTrack.write( ) will return only after 430963719869967cc257e666809aeb9bff3f25117edNarayan Kamath // all data from the audioTrack has been sent to the mixer, so 431963719869967cc257e666809aeb9bff3f25117edNarayan Kamath // it's safe to release at this point. 4328d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath audioTrack.release(); 4338d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath } 4348d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath } finally { 4358d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath params.setAudioTrack(null); 4368d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath params.getDispatcher().dispatchUtteranceCompleted(); 4378d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath mLastSynthesisRequest = null; 4388d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath } 4398d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath } 4408d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath 44147d6288541324b27c80b9949670f7b6b18d3ae4cNarayan Kamath private static void blockUntilDone(SynthesisMessageParams params) { 44247d6288541324b27c80b9949670f7b6b18d3ae4cNarayan Kamath if (params.mAudioTrack == null || params.mBytesWritten <= 0) { 44347d6288541324b27c80b9949670f7b6b18d3ae4cNarayan Kamath return; 44447d6288541324b27c80b9949670f7b6b18d3ae4cNarayan Kamath } 44547d6288541324b27c80b9949670f7b6b18d3ae4cNarayan Kamath 446c3da8818f0598b3ab2cd6f4168349da6d0f72cb1Narayan Kamath final AudioTrack audioTrack = params.mAudioTrack; 44747d6288541324b27c80b9949670f7b6b18d3ae4cNarayan Kamath final int bytesPerFrame = getBytesPerFrame(params.mAudioFormat); 44847d6288541324b27c80b9949670f7b6b18d3ae4cNarayan Kamath final int lengthInBytes = params.mBytesWritten; 449c3da8818f0598b3ab2cd6f4168349da6d0f72cb1Narayan Kamath final int lengthInFrames = lengthInBytes / bytesPerFrame; 45047d6288541324b27c80b9949670f7b6b18d3ae4cNarayan Kamath 4518d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath int currentPosition = 0; 4528d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath while ((currentPosition = audioTrack.getPlaybackHeadPosition()) < lengthInFrames) { 45347d6288541324b27c80b9949670f7b6b18d3ae4cNarayan Kamath if (audioTrack.getPlayState() != AudioTrack.PLAYSTATE_PLAYING) { 45447d6288541324b27c80b9949670f7b6b18d3ae4cNarayan Kamath break; 45547d6288541324b27c80b9949670f7b6b18d3ae4cNarayan Kamath } 45647d6288541324b27c80b9949670f7b6b18d3ae4cNarayan Kamath 4578d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath long estimatedTimeMs = ((lengthInFrames - currentPosition) * 1000) / 4588d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath audioTrack.getSampleRate(); 45947d6288541324b27c80b9949670f7b6b18d3ae4cNarayan Kamath 4608d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath if (DBG) Log.d(TAG, "About to sleep for : " + estimatedTimeMs + " ms," + 4618d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath " Playback position : " + currentPosition); 4628d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath try { 4638d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath Thread.sleep(estimatedTimeMs); 4648d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath } catch (InterruptedException ie) { 4658d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath break; 4668d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath } 4678d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath } 4688d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath } 4698d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath 4708d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath private static AudioTrack createStreamingAudioTrack(int streamType, int sampleRateInHz, 4718d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath int audioFormat, int channelCount, float volume, float pan) { 4728d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath int channelConfig = getChannelConfig(channelCount); 4738d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath 4748d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath int minBufferSizeInBytes 4758d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath = AudioTrack.getMinBufferSize(sampleRateInHz, channelConfig, audioFormat); 4768d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath int bufferSizeInBytes = Math.max(MIN_AUDIO_BUFFER_SIZE, minBufferSizeInBytes); 4778d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath 4788d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath AudioTrack audioTrack = new AudioTrack(streamType, sampleRateInHz, channelConfig, 4798d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath audioFormat, bufferSizeInBytes, AudioTrack.MODE_STREAM); 4808d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath if (audioTrack.getState() != AudioTrack.STATE_INITIALIZED) { 4818d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath Log.w(TAG, "Unable to create audio track."); 4828d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath audioTrack.release(); 4838d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath return null; 4848d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath } 4858d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath 4868d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath setupVolume(audioTrack, volume, pan); 4878d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath return audioTrack; 4888d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath } 4898d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath 4908d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath static int getChannelConfig(int channelCount) { 4918d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath if (channelCount == 1) { 4928d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath return AudioFormat.CHANNEL_OUT_MONO; 4938d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath } else if (channelCount == 2){ 4948d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath return AudioFormat.CHANNEL_OUT_STEREO; 4958d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath } 4968d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath 4978d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath return 0; 4988d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath } 4998d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath 5008d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath static int getBytesPerFrame(int audioFormat) { 5018d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath if (audioFormat == AudioFormat.ENCODING_PCM_8BIT) { 5028d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath return 1; 5038d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath } else if (audioFormat == AudioFormat.ENCODING_PCM_16BIT) { 5048d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath return 2; 5058d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath } 5068d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath 5078d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath return -1; 5088d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath } 5098d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath 5108d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath private static void setupVolume(AudioTrack audioTrack, float volume, float pan) { 5118d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath float vol = clip(volume, 0.0f, 1.0f); 5128d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath float panning = clip(pan, -1.0f, 1.0f); 5138d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath float volLeft = vol; 5148d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath float volRight = vol; 5158d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath if (panning > 0.0f) { 5168d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath volLeft *= (1.0f - panning); 5178d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath } else if (panning < 0.0f) { 5188d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath volRight *= (1.0f + panning); 5198d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath } 5208d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath if (DBG) Log.d(TAG, "volLeft=" + volLeft + ",volRight=" + volRight); 5218d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath if (audioTrack.setStereoVolume(volLeft, volRight) != AudioTrack.SUCCESS) { 5228d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath Log.e(TAG, "Failed to set volume"); 5238d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath } 5248d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath } 5258d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath 5268d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath private static float clip(float value, float min, float max) { 5278d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath return value > max ? max : (value < min ? min : value); 5288d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath } 5298d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath 5308d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath} 531