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