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