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)); 9790e5650f96dabadaaf141beae20a646855073ae1Narayan Kamath } else if (token.getType() == MessageParams.TYPE_AUDIO) { 9890e5650f96dabadaaf141beae20a646855073ae1Narayan Kamath ((AudioMessageParams) token).getPlayer().stop(); 9990e5650f96dabadaaf141beae20a646855073ae1Narayan Kamath // No cleanup required for audio messages. 10090e5650f96dabadaaf141beae20a646855073ae1Narayan Kamath } else if (token.getType() == MessageParams.TYPE_SILENCE) { 10190e5650f96dabadaaf141beae20a646855073ae1Narayan Kamath ((SilenceMessageParams) token).getConditionVariable().open(); 10290e5650f96dabadaaf141beae20a646855073ae1Narayan Kamath // No cleanup required for silence messages. 1038d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath } 1048d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath } 1058d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath 106be4ad4ac66d6b4b878ed052975f7fb09af92c6d6Narayan Kamath // ----------------------------------------------------- 107be4ad4ac66d6b4b878ed052975f7fb09af92c6d6Narayan Kamath // Methods that add and remove elements from the queue. These do not 108be4ad4ac66d6b4b878ed052975f7fb09af92c6d6Narayan Kamath // need to be synchronized strictly speaking, but they make the behaviour 109be4ad4ac66d6b4b878ed052975f7fb09af92c6d6Narayan Kamath // a lot more predictable. (though it would still be correct without 110be4ad4ac66d6b4b878ed052975f7fb09af92c6d6Narayan Kamath // synchronization). 111be4ad4ac66d6b4b878ed052975f7fb09af92c6d6Narayan Kamath // ----------------------------------------------------- 112be4ad4ac66d6b4b878ed052975f7fb09af92c6d6Narayan Kamath 1134924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath synchronized public void removePlaybackItems(String callingApp) { 114be4ad4ac66d6b4b878ed052975f7fb09af92c6d6Narayan Kamath if (DBG_THREADING) Log.d(TAG, "Removing all callback items for : " + callingApp); 1154924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath removeMessages(callingApp); 116be4ad4ac66d6b4b878ed052975f7fb09af92c6d6Narayan Kamath 117be4ad4ac66d6b4b878ed052975f7fb09af92c6d6Narayan Kamath final MessageParams current = getCurrentParams(); 118be4ad4ac66d6b4b878ed052975f7fb09af92c6d6Narayan Kamath if (current != null && TextUtils.equals(callingApp, current.getCallingApp())) { 119be4ad4ac66d6b4b878ed052975f7fb09af92c6d6Narayan Kamath stop(current); 120be4ad4ac66d6b4b878ed052975f7fb09af92c6d6Narayan Kamath } 12140f71f0be3cefabde9dc066d7707a1e5ebaec820Narayan Kamath 12240f71f0be3cefabde9dc066d7707a1e5ebaec820Narayan Kamath final MessageParams lastSynthesis = mLastSynthesisRequest; 12340f71f0be3cefabde9dc066d7707a1e5ebaec820Narayan Kamath 12440f71f0be3cefabde9dc066d7707a1e5ebaec820Narayan Kamath if (lastSynthesis != null && lastSynthesis != current && 12540f71f0be3cefabde9dc066d7707a1e5ebaec820Narayan Kamath TextUtils.equals(callingApp, lastSynthesis.getCallingApp())) { 12640f71f0be3cefabde9dc066d7707a1e5ebaec820Narayan Kamath stop(lastSynthesis); 12740f71f0be3cefabde9dc066d7707a1e5ebaec820Narayan Kamath } 1284924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath } 1294924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath 1304924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath synchronized public void removeAllItems() { 131be4ad4ac66d6b4b878ed052975f7fb09af92c6d6Narayan Kamath if (DBG_THREADING) Log.d(TAG, "Removing all items"); 1324924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath removeAllMessages(); 13340f71f0be3cefabde9dc066d7707a1e5ebaec820Narayan Kamath 13440f71f0be3cefabde9dc066d7707a1e5ebaec820Narayan Kamath final MessageParams current = getCurrentParams(); 13540f71f0be3cefabde9dc066d7707a1e5ebaec820Narayan Kamath final MessageParams lastSynthesis = mLastSynthesisRequest; 13640f71f0be3cefabde9dc066d7707a1e5ebaec820Narayan Kamath stop(current); 13740f71f0be3cefabde9dc066d7707a1e5ebaec820Narayan Kamath 13840f71f0be3cefabde9dc066d7707a1e5ebaec820Narayan Kamath if (lastSynthesis != null && lastSynthesis != current) { 13940f71f0be3cefabde9dc066d7707a1e5ebaec820Narayan Kamath stop(lastSynthesis); 14040f71f0be3cefabde9dc066d7707a1e5ebaec820Narayan Kamath } 1414924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath } 1424924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath 1438d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath /** 144c34f76fe89b5a31d01d63067c2f24b9a6a76df18Narayan Kamath * @return false iff the queue is empty and no queue item is currently 145c34f76fe89b5a31d01d63067c2f24b9a6a76df18Narayan Kamath * being handled, true otherwise. 146c34f76fe89b5a31d01d63067c2f24b9a6a76df18Narayan Kamath */ 147c34f76fe89b5a31d01d63067c2f24b9a6a76df18Narayan Kamath public boolean isSpeaking() { 148c34f76fe89b5a31d01d63067c2f24b9a6a76df18Narayan Kamath return (mQueue.peek() != null) || (mCurrentParams != null); 149c34f76fe89b5a31d01d63067c2f24b9a6a76df18Narayan Kamath } 150c34f76fe89b5a31d01d63067c2f24b9a6a76df18Narayan Kamath 151c34f76fe89b5a31d01d63067c2f24b9a6a76df18Narayan Kamath /** 1528d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath * Shut down the audio playback thread. 1538d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath */ 1548d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath synchronized public void quit() { 155be4ad4ac66d6b4b878ed052975f7fb09af92c6d6Narayan Kamath removeAllMessages(); 1564924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath stop(getCurrentParams()); 1574924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath mQueue.add(new ListEntry(SHUTDOWN, null, HIGH_PRIORITY)); 1588d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath } 1598d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath 160be4ad4ac66d6b4b878ed052975f7fb09af92c6d6Narayan Kamath synchronized void enqueueSynthesisStart(SynthesisMessageParams token) { 161be4ad4ac66d6b4b878ed052975f7fb09af92c6d6Narayan Kamath if (DBG_THREADING) Log.d(TAG, "Enqueuing synthesis start : " + token); 1624924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath mQueue.add(new ListEntry(SYNTHESIS_START, token)); 1638d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath } 1648d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath 165be4ad4ac66d6b4b878ed052975f7fb09af92c6d6Narayan Kamath synchronized void enqueueSynthesisDataAvailable(SynthesisMessageParams token) { 166be4ad4ac66d6b4b878ed052975f7fb09af92c6d6Narayan Kamath if (DBG_THREADING) Log.d(TAG, "Enqueuing synthesis data available : " + token); 1674924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath mQueue.add(new ListEntry(SYNTHESIS_DATA_AVAILABLE, token)); 1688d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath } 1698d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath 170be4ad4ac66d6b4b878ed052975f7fb09af92c6d6Narayan Kamath synchronized void enqueueSynthesisDone(SynthesisMessageParams token) { 171be4ad4ac66d6b4b878ed052975f7fb09af92c6d6Narayan Kamath if (DBG_THREADING) Log.d(TAG, "Enqueuing synthesis done : " + token); 1724924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath mQueue.add(new ListEntry(SYNTHESIS_DONE, token)); 1738d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath } 1748d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath 175be4ad4ac66d6b4b878ed052975f7fb09af92c6d6Narayan Kamath synchronized void enqueueAudio(AudioMessageParams token) { 176be4ad4ac66d6b4b878ed052975f7fb09af92c6d6Narayan Kamath if (DBG_THREADING) Log.d(TAG, "Enqueuing audio : " + token); 1774924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath mQueue.add(new ListEntry(PLAY_AUDIO, token)); 1788d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath } 1798d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath 180be4ad4ac66d6b4b878ed052975f7fb09af92c6d6Narayan Kamath synchronized void enqueueSilence(SilenceMessageParams token) { 181be4ad4ac66d6b4b878ed052975f7fb09af92c6d6Narayan Kamath if (DBG_THREADING) Log.d(TAG, "Enqueuing silence : " + token); 1824924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath mQueue.add(new ListEntry(PLAY_SILENCE, token)); 1838d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath } 1848d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath 1858d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath // ----------------------------------------- 1868d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath // End of public API methods. 1878d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath // ----------------------------------------- 1888d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath 1894924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath // ----------------------------------------- 1904924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath // Methods for managing the message queue. 1914924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath // ----------------------------------------- 1924924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath 1934924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath /* 1944924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath * The MessageLoop is a handler like implementation that 1954924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath * processes messages from a priority queue. 1964924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath */ 1974924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath private final class MessageLoop implements Runnable { 1984924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath @Override 1994924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath public void run() { 2004924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath while (true) { 2014924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath ListEntry entry = null; 2024924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath try { 2034924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath entry = mQueue.take(); 2044924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath } catch (InterruptedException ie) { 2054924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath return; 2064924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath } 2074924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath 2084924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath if (entry.mWhat == SHUTDOWN) { 2094924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath if (DBG) Log.d(TAG, "MessageLoop : Shutting down"); 2104924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath return; 2114924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath } 2124924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath 2134924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath if (DBG) { 2144924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath Log.d(TAG, "MessageLoop : Handling message :" + entry.mWhat 2154924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath + " ,seqId : " + entry.mSequenceId); 2164924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath } 2174924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath 2184924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath setCurrentParams(entry.mMessage); 2194924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath handleMessage(entry); 2204924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath setCurrentParams(null); 2214924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath } 2224924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath } 2234924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath } 2244924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath 2254924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath /* 2264924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath * Atomically clear the queue of all messages. 2274924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath */ 2284924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath synchronized private void removeAllMessages() { 2294924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath mQueue.clear(); 2304924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath } 2314924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath 2324924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath /* 2334924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath * Remove all messages that originate from a given calling app. 2344924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath */ 2354924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath synchronized private void removeMessages(String callingApp) { 2364924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath Iterator<ListEntry> it = mQueue.iterator(); 2374924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath 2384924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath while (it.hasNext()) { 2394924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath final ListEntry current = it.next(); 2404924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath // The null check is to prevent us from removing control messages, 2414924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath // such as a shutdown message. 2424924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath if (current.mMessage != null && 2434924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath callingApp.equals(current.mMessage.getCallingApp())) { 2444924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath it.remove(); 2454924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath } 2464924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath } 2474924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath } 2484924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath 2494924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath /* 2504924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath * An element of our priority queue of messages. Each message has a priority, 2514924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath * and a sequence id (defined by the order of enqueue calls). Among messages 2524924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath * with the same priority, messages that were received earlier win out. 2534924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath */ 2544924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath private final class ListEntry implements Comparable<ListEntry> { 2554924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath final int mWhat; 2564924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath final MessageParams mMessage; 2574924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath final int mPriority; 2584924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath final long mSequenceId; 2594924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath 2604924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath private ListEntry(int what, MessageParams message) { 2614924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath this(what, message, DEFAULT_PRIORITY); 2624924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath } 2634924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath 2644924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath private ListEntry(int what, MessageParams message, int priority) { 2654924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath mWhat = what; 2664924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath mMessage = message; 2674924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath mPriority = priority; 2684924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath mSequenceId = mSequenceIdCtr.incrementAndGet(); 2694924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath } 2704924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath 2714924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath @Override 2724924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath public int compareTo(ListEntry that) { 2734924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath if (that == this) { 2744924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath return 0; 2754924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath } 2764924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath 2774924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath // Note that this is always 0, 1 or -1. 2784924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath int priorityDiff = mPriority - that.mPriority; 2794924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath if (priorityDiff == 0) { 2804924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath // The == case cannot occur. 2814924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath return (mSequenceId < that.mSequenceId) ? -1 : 1; 2824924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath } 2834924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath 2844924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath return priorityDiff; 2854924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath } 2864924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath } 2874924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath 2884924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath private void setCurrentParams(MessageParams p) { 289be4ad4ac66d6b4b878ed052975f7fb09af92c6d6Narayan Kamath if (DBG_THREADING) { 290be4ad4ac66d6b4b878ed052975f7fb09af92c6d6Narayan Kamath if (p != null) { 291be4ad4ac66d6b4b878ed052975f7fb09af92c6d6Narayan Kamath Log.d(TAG, "Started handling :" + p); 292be4ad4ac66d6b4b878ed052975f7fb09af92c6d6Narayan Kamath } else { 293be4ad4ac66d6b4b878ed052975f7fb09af92c6d6Narayan Kamath Log.d(TAG, "End handling : " + mCurrentParams); 294be4ad4ac66d6b4b878ed052975f7fb09af92c6d6Narayan Kamath } 295be4ad4ac66d6b4b878ed052975f7fb09af92c6d6Narayan Kamath } 296abc63fbddab2477a2954bc804aba2826e1f11084Narayan Kamath mCurrentParams = p; 2974924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath } 2984924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath 2994924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath private MessageParams getCurrentParams() { 300abc63fbddab2477a2954bc804aba2826e1f11084Narayan Kamath return mCurrentParams; 3014924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath } 3024924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath 3034924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath // ----------------------------------------- 3044924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath // Methods for dealing with individual messages, the methods 3054924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath // below do the actual work. 3064924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath // ----------------------------------------- 3074924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath 3084924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath private void handleMessage(ListEntry entry) { 3094924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath final MessageParams msg = entry.mMessage; 3104924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath if (entry.mWhat == SYNTHESIS_START) { 3114924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath handleSynthesisStart(msg); 3124924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath } else if (entry.mWhat == SYNTHESIS_DATA_AVAILABLE) { 3134924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath handleSynthesisDataAvailable(msg); 3144924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath } else if (entry.mWhat == SYNTHESIS_DONE) { 3154924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath handleSynthesisDone(msg); 3164924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath } else if (entry.mWhat == PLAY_AUDIO) { 3174924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath handleAudio(msg); 3184924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath } else if (entry.mWhat == PLAY_SILENCE) { 3194924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath handleSilence(msg); 3204924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath } 3214924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath } 3224924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath 3238d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath // Currently implemented as blocking the audio playback thread for the 3248d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath // specified duration. If a call to stop() is made, the thread 3258d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath // unblocks. 3264924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath private void handleSilence(MessageParams msg) { 3278d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath if (DBG) Log.d(TAG, "handleSilence()"); 3284924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath SilenceMessageParams params = (SilenceMessageParams) msg; 329754c72ed9e8e83e5a913aa7552fc2e1b1b5277e0Narayan Kamath params.getDispatcher().dispatchOnStart(); 3308d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath if (params.getSilenceDurationMs() > 0) { 3318d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath params.getConditionVariable().block(params.getSilenceDurationMs()); 3328d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath } 333754c72ed9e8e83e5a913aa7552fc2e1b1b5277e0Narayan Kamath params.getDispatcher().dispatchOnDone(); 3348d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath if (DBG) Log.d(TAG, "handleSilence() done."); 3358d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath } 3368d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath 3378d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath // Plays back audio from a given URI. No TTS engine involvement here. 3384924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath private void handleAudio(MessageParams msg) { 3398d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath if (DBG) Log.d(TAG, "handleAudio()"); 3404924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath AudioMessageParams params = (AudioMessageParams) msg; 341754c72ed9e8e83e5a913aa7552fc2e1b1b5277e0Narayan Kamath params.getDispatcher().dispatchOnStart(); 3428d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath // Note that the BlockingMediaPlayer spawns a separate thread. 3438d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath // 3448d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath // TODO: This can be avoided. 3458d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath params.getPlayer().startAndWait(); 346754c72ed9e8e83e5a913aa7552fc2e1b1b5277e0Narayan Kamath params.getDispatcher().dispatchOnDone(); 3478d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath if (DBG) Log.d(TAG, "handleAudio() done."); 3488d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath } 3498d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath 3508d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath // Denotes the start of a new synthesis request. We create a new 3518d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath // audio track, and prepare it for incoming data. 3528d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath // 3538d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath // Note that since all TTS synthesis happens on a single thread, we 3548d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath // should ALWAYS see the following order : 3558d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath // 3568d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath // handleSynthesisStart -> handleSynthesisDataAvailable(*) -> handleSynthesisDone 3578d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath // OR 3588d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath // handleSynthesisCompleteDataAvailable. 3594924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath private void handleSynthesisStart(MessageParams msg) { 3608d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath if (DBG) Log.d(TAG, "handleSynthesisStart()"); 3614924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath final SynthesisMessageParams param = (SynthesisMessageParams) msg; 3628d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath 3638d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath // Oops, looks like the engine forgot to call done(). We go through 3648d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath // extra trouble to clean the data to prevent the AudioTrack resources 3658d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath // from being leaked. 3668d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath if (mLastSynthesisRequest != null) { 36740f71f0be3cefabde9dc066d7707a1e5ebaec820Narayan Kamath Log.e(TAG, "Error : Missing call to done() for request : " + 3688d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath mLastSynthesisRequest); 3698d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath handleSynthesisDone(mLastSynthesisRequest); 3708d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath } 3718d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath 3728d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath mLastSynthesisRequest = param; 3738d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath 3748d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath // Create the audio track. 375673f360b0e22a8591f515cba7a90d5cfcfad81a7Narayan Kamath final AudioTrack audioTrack = createStreamingAudioTrack(param); 3768d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath 3774924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath if (DBG) Log.d(TAG, "Created audio track [" + audioTrack.hashCode() + "]"); 3784924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath 3798d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath param.setAudioTrack(audioTrack); 380754c72ed9e8e83e5a913aa7552fc2e1b1b5277e0Narayan Kamath msg.getDispatcher().dispatchOnStart(); 3818d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath } 3828d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath 3838d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath // More data available to be flushed to the audio track. 3844924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath private void handleSynthesisDataAvailable(MessageParams msg) { 3854924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath final SynthesisMessageParams param = (SynthesisMessageParams) msg; 3868d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath if (param.getAudioTrack() == null) { 387be4ad4ac66d6b4b878ed052975f7fb09af92c6d6Narayan Kamath Log.w(TAG, "Error : null audio track in handleDataAvailable : " + param); 3888d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath return; 3898d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath } 3908d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath 3918d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath if (param != mLastSynthesisRequest) { 3928d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath Log.e(TAG, "Call to dataAvailable without done() / start()"); 3938d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath return; 3948d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath } 3958d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath 3968d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath final AudioTrack audioTrack = param.getAudioTrack(); 3974924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath final SynthesisMessageParams.ListEntry bufferCopy = param.getNextBuffer(); 3988d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath 3998d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath if (bufferCopy == null) { 4008d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath Log.e(TAG, "No buffers available to play."); 4018d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath return; 4028d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath } 4038d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath 4048d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath int playState = audioTrack.getPlayState(); 4058d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath if (playState == AudioTrack.PLAYSTATE_STOPPED) { 4068d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath if (DBG) Log.d(TAG, "AudioTrack stopped, restarting : " + audioTrack.hashCode()); 4078d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath audioTrack.play(); 4088d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath } 4098d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath int count = 0; 4102a0518cd1d18dc50ffa063a71133e69de2e866abNarayan Kamath while (count < bufferCopy.mBytes.length) { 4118d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath // Note that we don't take bufferCopy.mOffset into account because 4128d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath // it is guaranteed to be 0. 4132a0518cd1d18dc50ffa063a71133e69de2e866abNarayan Kamath int written = audioTrack.write(bufferCopy.mBytes, count, bufferCopy.mBytes.length); 4148d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath if (written <= 0) { 4158d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath break; 4168d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath } 4178d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath count += written; 4188d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath } 41947d6288541324b27c80b9949670f7b6b18d3ae4cNarayan Kamath param.mBytesWritten += count; 4206dabb63307a0b63f9386d61e8444aed29db2081eNarayan Kamath param.mLogger.onPlaybackStart(); 4218d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath } 4228d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath 423673f360b0e22a8591f515cba7a90d5cfcfad81a7Narayan Kamath // Wait for the audio track to stop playing, and then release its resources. 4244924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath private void handleSynthesisDone(MessageParams msg) { 4254924fe38675f0bf69bb0c16fc059ffa1606807ceNarayan Kamath final SynthesisMessageParams params = (SynthesisMessageParams) msg; 4268d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath 4278d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath if (DBG) Log.d(TAG, "handleSynthesisDone()"); 4288d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath final AudioTrack audioTrack = params.getAudioTrack(); 4298d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath 430a36cce06a63ff0eb1c3a765ceb3863195732b4bfNarayan Kamath if (audioTrack == null) { 431cc084f1d734c1e332d6acc1f89204036ee82bd0cNarayan Kamath // There was already a call to handleSynthesisDone for 432cc084f1d734c1e332d6acc1f89204036ee82bd0cNarayan Kamath // this token. 433a36cce06a63ff0eb1c3a765ceb3863195732b4bfNarayan Kamath return; 434a36cce06a63ff0eb1c3a765ceb3863195732b4bfNarayan Kamath } 435a36cce06a63ff0eb1c3a765ceb3863195732b4bfNarayan Kamath 436673f360b0e22a8591f515cba7a90d5cfcfad81a7Narayan Kamath if (params.mBytesWritten < params.mAudioBufferSize) { 43797171243f82532398a96f7f7adcceb61722519deNarayan Kamath if (DBG) Log.d(TAG, "Stopping audio track to flush audio, state was : " + 43897171243f82532398a96f7f7adcceb61722519deNarayan Kamath audioTrack.getPlayState()); 43997171243f82532398a96f7f7adcceb61722519deNarayan Kamath params.mIsShortUtterance = true; 440673f360b0e22a8591f515cba7a90d5cfcfad81a7Narayan Kamath audioTrack.stop(); 441673f360b0e22a8591f515cba7a90d5cfcfad81a7Narayan Kamath } 442673f360b0e22a8591f515cba7a90d5cfcfad81a7Narayan Kamath 443a36cce06a63ff0eb1c3a765ceb3863195732b4bfNarayan Kamath if (DBG) Log.d(TAG, "Waiting for audio track to complete : " + 444a36cce06a63ff0eb1c3a765ceb3863195732b4bfNarayan Kamath audioTrack.hashCode()); 445a36cce06a63ff0eb1c3a765ceb3863195732b4bfNarayan Kamath blockUntilDone(params); 446a36cce06a63ff0eb1c3a765ceb3863195732b4bfNarayan Kamath if (DBG) Log.d(TAG, "Releasing audio track [" + audioTrack.hashCode() + "]"); 447a36cce06a63ff0eb1c3a765ceb3863195732b4bfNarayan Kamath 448a36cce06a63ff0eb1c3a765ceb3863195732b4bfNarayan Kamath // The last call to AudioTrack.write( ) will return only after 449a36cce06a63ff0eb1c3a765ceb3863195732b4bfNarayan Kamath // all data from the audioTrack has been sent to the mixer, so 450a36cce06a63ff0eb1c3a765ceb3863195732b4bfNarayan Kamath // it's safe to release at this point. Make sure release() and the call 451a36cce06a63ff0eb1c3a765ceb3863195732b4bfNarayan Kamath // that set the audio track to null are performed atomically. 452a36cce06a63ff0eb1c3a765ceb3863195732b4bfNarayan Kamath synchronized (this) { 453a36cce06a63ff0eb1c3a765ceb3863195732b4bfNarayan Kamath // Never allow the audioTrack to be observed in a state where 454a36cce06a63ff0eb1c3a765ceb3863195732b4bfNarayan Kamath // it is released but non null. The only case this might happen 455a36cce06a63ff0eb1c3a765ceb3863195732b4bfNarayan Kamath // is in the various stopFoo methods that call AudioTrack#stop from 456a36cce06a63ff0eb1c3a765ceb3863195732b4bfNarayan Kamath // different threads, but they are synchronized on AudioPlayBackHandler#this 457a36cce06a63ff0eb1c3a765ceb3863195732b4bfNarayan Kamath // too. 458a36cce06a63ff0eb1c3a765ceb3863195732b4bfNarayan Kamath audioTrack.release(); 4598d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath params.setAudioTrack(null); 4608d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath } 46140f71f0be3cefabde9dc066d7707a1e5ebaec820Narayan Kamath if (params.isError()) { 46240f71f0be3cefabde9dc066d7707a1e5ebaec820Narayan Kamath params.getDispatcher().dispatchOnError(); 46340f71f0be3cefabde9dc066d7707a1e5ebaec820Narayan Kamath } else { 46440f71f0be3cefabde9dc066d7707a1e5ebaec820Narayan Kamath params.getDispatcher().dispatchOnDone(); 46540f71f0be3cefabde9dc066d7707a1e5ebaec820Narayan Kamath } 466a36cce06a63ff0eb1c3a765ceb3863195732b4bfNarayan Kamath mLastSynthesisRequest = null; 467673f360b0e22a8591f515cba7a90d5cfcfad81a7Narayan Kamath params.mLogger.onWriteData(); 4688d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath } 4698d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath 470673f360b0e22a8591f515cba7a90d5cfcfad81a7Narayan Kamath /** 471673f360b0e22a8591f515cba7a90d5cfcfad81a7Narayan Kamath * The minimum increment of time to wait for an audiotrack to finish 472673f360b0e22a8591f515cba7a90d5cfcfad81a7Narayan Kamath * playing. 473673f360b0e22a8591f515cba7a90d5cfcfad81a7Narayan Kamath */ 474673f360b0e22a8591f515cba7a90d5cfcfad81a7Narayan Kamath private static final long MIN_SLEEP_TIME_MS = 20; 475673f360b0e22a8591f515cba7a90d5cfcfad81a7Narayan Kamath 47669bc1b2696dde849102f0ac8071999843d01b8d1Narayan Kamath /** 47769bc1b2696dde849102f0ac8071999843d01b8d1Narayan Kamath * The maximum increment of time to sleep while waiting for an audiotrack 47869bc1b2696dde849102f0ac8071999843d01b8d1Narayan Kamath * to finish playing. 47969bc1b2696dde849102f0ac8071999843d01b8d1Narayan Kamath */ 48069bc1b2696dde849102f0ac8071999843d01b8d1Narayan Kamath private static final long MAX_SLEEP_TIME_MS = 2500; 48169bc1b2696dde849102f0ac8071999843d01b8d1Narayan Kamath 48269bc1b2696dde849102f0ac8071999843d01b8d1Narayan Kamath /** 48369bc1b2696dde849102f0ac8071999843d01b8d1Narayan Kamath * The maximum amount of time to wait for an audio track to make progress while 48469bc1b2696dde849102f0ac8071999843d01b8d1Narayan Kamath * it remains in PLAYSTATE_PLAYING. This should never happen in normal usage, but 48569bc1b2696dde849102f0ac8071999843d01b8d1Narayan Kamath * could happen in exceptional circumstances like a media_server crash. 48669bc1b2696dde849102f0ac8071999843d01b8d1Narayan Kamath */ 48769bc1b2696dde849102f0ac8071999843d01b8d1Narayan Kamath private static final long MAX_PROGRESS_WAIT_MS = MAX_SLEEP_TIME_MS; 48869bc1b2696dde849102f0ac8071999843d01b8d1Narayan Kamath 48947d6288541324b27c80b9949670f7b6b18d3ae4cNarayan Kamath private static void blockUntilDone(SynthesisMessageParams params) { 49047d6288541324b27c80b9949670f7b6b18d3ae4cNarayan Kamath if (params.mAudioTrack == null || params.mBytesWritten <= 0) { 49147d6288541324b27c80b9949670f7b6b18d3ae4cNarayan Kamath return; 49247d6288541324b27c80b9949670f7b6b18d3ae4cNarayan Kamath } 49347d6288541324b27c80b9949670f7b6b18d3ae4cNarayan Kamath 49497171243f82532398a96f7f7adcceb61722519deNarayan Kamath if (params.mIsShortUtterance) { 49597171243f82532398a96f7f7adcceb61722519deNarayan Kamath // In this case we would have called AudioTrack#stop() to flush 49697171243f82532398a96f7f7adcceb61722519deNarayan Kamath // buffers to the mixer. This makes the playback head position 49797171243f82532398a96f7f7adcceb61722519deNarayan Kamath // unobservable and notification markers do not work reliably. We 49897171243f82532398a96f7f7adcceb61722519deNarayan Kamath // have no option but to wait until we think the track would finish 49997171243f82532398a96f7f7adcceb61722519deNarayan Kamath // playing and release it after. 50097171243f82532398a96f7f7adcceb61722519deNarayan Kamath // 50197171243f82532398a96f7f7adcceb61722519deNarayan Kamath // This isn't as bad as it looks because (a) We won't end up waiting 50297171243f82532398a96f7f7adcceb61722519deNarayan Kamath // for much longer than we should because even at 4khz mono, a short 50397171243f82532398a96f7f7adcceb61722519deNarayan Kamath // utterance weighs in at about 2 seconds, and (b) such short utterances 50497171243f82532398a96f7f7adcceb61722519deNarayan Kamath // are expected to be relatively infrequent and in a stream of utterances 50597171243f82532398a96f7f7adcceb61722519deNarayan Kamath // this shows up as a slightly longer pause. 50697171243f82532398a96f7f7adcceb61722519deNarayan Kamath blockUntilEstimatedCompletion(params); 50797171243f82532398a96f7f7adcceb61722519deNarayan Kamath } else { 50897171243f82532398a96f7f7adcceb61722519deNarayan Kamath blockUntilCompletion(params); 50997171243f82532398a96f7f7adcceb61722519deNarayan Kamath } 51097171243f82532398a96f7f7adcceb61722519deNarayan Kamath } 51197171243f82532398a96f7f7adcceb61722519deNarayan Kamath 51297171243f82532398a96f7f7adcceb61722519deNarayan Kamath private static void blockUntilEstimatedCompletion(SynthesisMessageParams params) { 51397171243f82532398a96f7f7adcceb61722519deNarayan Kamath final int lengthInFrames = params.mBytesWritten / params.mBytesPerFrame; 51497171243f82532398a96f7f7adcceb61722519deNarayan Kamath final long estimatedTimeMs = (lengthInFrames * 1000 / params.mSampleRateInHz); 51597171243f82532398a96f7f7adcceb61722519deNarayan Kamath 51697171243f82532398a96f7f7adcceb61722519deNarayan Kamath if (DBG) Log.d(TAG, "About to sleep for: " + estimatedTimeMs + "ms for a short utterance"); 51797171243f82532398a96f7f7adcceb61722519deNarayan Kamath 51897171243f82532398a96f7f7adcceb61722519deNarayan Kamath try { 51997171243f82532398a96f7f7adcceb61722519deNarayan Kamath Thread.sleep(estimatedTimeMs); 52097171243f82532398a96f7f7adcceb61722519deNarayan Kamath } catch (InterruptedException ie) { 52197171243f82532398a96f7f7adcceb61722519deNarayan Kamath // Do nothing. 52297171243f82532398a96f7f7adcceb61722519deNarayan Kamath } 52397171243f82532398a96f7f7adcceb61722519deNarayan Kamath } 52497171243f82532398a96f7f7adcceb61722519deNarayan Kamath 52597171243f82532398a96f7f7adcceb61722519deNarayan Kamath private static void blockUntilCompletion(SynthesisMessageParams params) { 526c3da8818f0598b3ab2cd6f4168349da6d0f72cb1Narayan Kamath final AudioTrack audioTrack = params.mAudioTrack; 52797171243f82532398a96f7f7adcceb61722519deNarayan Kamath final int lengthInFrames = params.mBytesWritten / params.mBytesPerFrame; 52847d6288541324b27c80b9949670f7b6b18d3ae4cNarayan Kamath 52969bc1b2696dde849102f0ac8071999843d01b8d1Narayan Kamath int previousPosition = -1; 5308d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath int currentPosition = 0; 53169bc1b2696dde849102f0ac8071999843d01b8d1Narayan Kamath long blockedTimeMs = 0; 53269bc1b2696dde849102f0ac8071999843d01b8d1Narayan Kamath 53369bc1b2696dde849102f0ac8071999843d01b8d1Narayan Kamath while ((currentPosition = audioTrack.getPlaybackHeadPosition()) < lengthInFrames && 53469bc1b2696dde849102f0ac8071999843d01b8d1Narayan Kamath audioTrack.getPlayState() == AudioTrack.PLAYSTATE_PLAYING) { 53547d6288541324b27c80b9949670f7b6b18d3ae4cNarayan Kamath 536673f360b0e22a8591f515cba7a90d5cfcfad81a7Narayan Kamath final long estimatedTimeMs = ((lengthInFrames - currentPosition) * 1000) / 5378d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath audioTrack.getSampleRate(); 53869bc1b2696dde849102f0ac8071999843d01b8d1Narayan Kamath final long sleepTimeMs = clip(estimatedTimeMs, MIN_SLEEP_TIME_MS, MAX_SLEEP_TIME_MS); 53969bc1b2696dde849102f0ac8071999843d01b8d1Narayan Kamath 54069bc1b2696dde849102f0ac8071999843d01b8d1Narayan Kamath // Check if the audio track has made progress since the last loop 54169bc1b2696dde849102f0ac8071999843d01b8d1Narayan Kamath // iteration. We should then add in the amount of time that was 54269bc1b2696dde849102f0ac8071999843d01b8d1Narayan Kamath // spent sleeping in the last iteration. 54369bc1b2696dde849102f0ac8071999843d01b8d1Narayan Kamath if (currentPosition == previousPosition) { 54469bc1b2696dde849102f0ac8071999843d01b8d1Narayan Kamath // This works only because the sleep time that would have been calculated 54569bc1b2696dde849102f0ac8071999843d01b8d1Narayan Kamath // would be the same in the previous iteration too. 54669bc1b2696dde849102f0ac8071999843d01b8d1Narayan Kamath blockedTimeMs += sleepTimeMs; 54769bc1b2696dde849102f0ac8071999843d01b8d1Narayan Kamath // If we've taken too long to make progress, bail. 54869bc1b2696dde849102f0ac8071999843d01b8d1Narayan Kamath if (blockedTimeMs > MAX_PROGRESS_WAIT_MS) { 54969bc1b2696dde849102f0ac8071999843d01b8d1Narayan Kamath Log.w(TAG, "Waited unsuccessfully for " + MAX_PROGRESS_WAIT_MS + "ms " + 55069bc1b2696dde849102f0ac8071999843d01b8d1Narayan Kamath "for AudioTrack to make progress, Aborting"); 55169bc1b2696dde849102f0ac8071999843d01b8d1Narayan Kamath break; 55269bc1b2696dde849102f0ac8071999843d01b8d1Narayan Kamath } 55369bc1b2696dde849102f0ac8071999843d01b8d1Narayan Kamath } else { 55469bc1b2696dde849102f0ac8071999843d01b8d1Narayan Kamath blockedTimeMs = 0; 55569bc1b2696dde849102f0ac8071999843d01b8d1Narayan Kamath } 55669bc1b2696dde849102f0ac8071999843d01b8d1Narayan Kamath previousPosition = currentPosition; 557673f360b0e22a8591f515cba7a90d5cfcfad81a7Narayan Kamath 558673f360b0e22a8591f515cba7a90d5cfcfad81a7Narayan Kamath if (DBG) Log.d(TAG, "About to sleep for : " + sleepTimeMs + " ms," + 559673f360b0e22a8591f515cba7a90d5cfcfad81a7Narayan Kamath " Playback position : " + currentPosition + ", Length in frames : " 560673f360b0e22a8591f515cba7a90d5cfcfad81a7Narayan Kamath + lengthInFrames); 5618d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath try { 562673f360b0e22a8591f515cba7a90d5cfcfad81a7Narayan Kamath Thread.sleep(sleepTimeMs); 5638d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath } catch (InterruptedException ie) { 5648d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath break; 5658d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath } 5668d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath } 5678d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath } 5688d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath 56969bc1b2696dde849102f0ac8071999843d01b8d1Narayan Kamath private static final long clip(long value, long min, long max) { 57069bc1b2696dde849102f0ac8071999843d01b8d1Narayan Kamath if (value < min) { 57169bc1b2696dde849102f0ac8071999843d01b8d1Narayan Kamath return min; 57269bc1b2696dde849102f0ac8071999843d01b8d1Narayan Kamath } 57369bc1b2696dde849102f0ac8071999843d01b8d1Narayan Kamath 57469bc1b2696dde849102f0ac8071999843d01b8d1Narayan Kamath if (value > max) { 57569bc1b2696dde849102f0ac8071999843d01b8d1Narayan Kamath return max; 57669bc1b2696dde849102f0ac8071999843d01b8d1Narayan Kamath } 57769bc1b2696dde849102f0ac8071999843d01b8d1Narayan Kamath 57869bc1b2696dde849102f0ac8071999843d01b8d1Narayan Kamath return value; 57969bc1b2696dde849102f0ac8071999843d01b8d1Narayan Kamath } 58069bc1b2696dde849102f0ac8071999843d01b8d1Narayan Kamath 581673f360b0e22a8591f515cba7a90d5cfcfad81a7Narayan Kamath private static AudioTrack createStreamingAudioTrack(SynthesisMessageParams params) { 582673f360b0e22a8591f515cba7a90d5cfcfad81a7Narayan Kamath final int channelConfig = getChannelConfig(params.mChannelCount); 583673f360b0e22a8591f515cba7a90d5cfcfad81a7Narayan Kamath final int sampleRateInHz = params.mSampleRateInHz; 584673f360b0e22a8591f515cba7a90d5cfcfad81a7Narayan Kamath final int audioFormat = params.mAudioFormat; 5858d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath 5868d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath int minBufferSizeInBytes 5878d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath = AudioTrack.getMinBufferSize(sampleRateInHz, channelConfig, audioFormat); 5888d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath int bufferSizeInBytes = Math.max(MIN_AUDIO_BUFFER_SIZE, minBufferSizeInBytes); 5898d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath 590673f360b0e22a8591f515cba7a90d5cfcfad81a7Narayan Kamath AudioTrack audioTrack = new AudioTrack(params.mStreamType, sampleRateInHz, channelConfig, 5918d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath audioFormat, bufferSizeInBytes, AudioTrack.MODE_STREAM); 5928d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath if (audioTrack.getState() != AudioTrack.STATE_INITIALIZED) { 5938d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath Log.w(TAG, "Unable to create audio track."); 5948d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath audioTrack.release(); 5958d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath return null; 5968d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath } 597673f360b0e22a8591f515cba7a90d5cfcfad81a7Narayan Kamath params.mAudioBufferSize = bufferSizeInBytes; 5988d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath 599673f360b0e22a8591f515cba7a90d5cfcfad81a7Narayan Kamath setupVolume(audioTrack, params.mVolume, params.mPan); 6008d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath return audioTrack; 6018d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath } 6028d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath 6038d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath static int getChannelConfig(int channelCount) { 6048d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath if (channelCount == 1) { 6058d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath return AudioFormat.CHANNEL_OUT_MONO; 6068d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath } else if (channelCount == 2){ 6078d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath return AudioFormat.CHANNEL_OUT_STEREO; 6088d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath } 6098d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath 6108d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath return 0; 6118d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath } 6128d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath 6138d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath private static void setupVolume(AudioTrack audioTrack, float volume, float pan) { 6148d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath float vol = clip(volume, 0.0f, 1.0f); 6158d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath float panning = clip(pan, -1.0f, 1.0f); 6168d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath float volLeft = vol; 6178d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath float volRight = vol; 6188d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath if (panning > 0.0f) { 6198d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath volLeft *= (1.0f - panning); 6208d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath } else if (panning < 0.0f) { 6218d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath volRight *= (1.0f + panning); 6228d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath } 6238d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath if (DBG) Log.d(TAG, "volLeft=" + volLeft + ",volRight=" + volRight); 6248d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath if (audioTrack.setStereoVolume(volLeft, volRight) != AudioTrack.SUCCESS) { 6258d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath Log.e(TAG, "Failed to set volume"); 6268d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath } 6278d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath } 6288d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath 6298d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath private static float clip(float value, float min, float max) { 6308d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath return value > max ? max : (value < min ? min : value); 6318d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath } 6328d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath 6338d1fc2403b8277e68d7816b2bbf05464a4c7a58aNarayan Kamath} 634