Home | History | Annotate | Download | only in tts
      1 /*
      2  * Copyright (C) 2011 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
      5  * use this file except in compliance with the License. You may obtain a copy of
      6  * the License at
      7  *
      8  * http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
     12  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
     13  * License for the specific language governing permissions and limitations under
     14  * the License.
     15  */
     16 package android.speech.tts;
     17 
     18 import android.speech.tts.TextToSpeechService.UtteranceProgressDispatcher;
     19 import android.util.Log;
     20 
     21 import java.util.LinkedList;
     22 import java.util.concurrent.locks.Condition;
     23 import java.util.concurrent.locks.Lock;
     24 import java.util.concurrent.locks.ReentrantLock;
     25 
     26 /**
     27  * Manages the playback of a list of byte arrays representing audio data
     28  * that are queued by the engine to an audio track.
     29  */
     30 final class SynthesisPlaybackQueueItem extends PlaybackQueueItem {
     31     private static final String TAG = "TTS.SynthQueueItem";
     32     private static final boolean DBG = false;
     33 
     34     /**
     35      * Maximum length of audio we leave unconsumed by the audio track.
     36      * Calls to {@link #put(byte[])} will block until we have less than
     37      * this amount of audio left to play back.
     38      */
     39     private static final long MAX_UNCONSUMED_AUDIO_MS = 500;
     40 
     41     /**
     42      * Guards accesses to mDataBufferList and mUnconsumedBytes.
     43      */
     44     private final Lock mListLock = new ReentrantLock();
     45     private final Condition mReadReady = mListLock.newCondition();
     46     private final Condition mNotFull = mListLock.newCondition();
     47 
     48     // Guarded by mListLock.
     49     private final LinkedList<ListEntry> mDataBufferList = new LinkedList<ListEntry>();
     50     // Guarded by mListLock.
     51     private int mUnconsumedBytes;
     52 
     53     /*
     54      * While mStopped and mIsError can be written from any thread, mDone is written
     55      * only from the synthesis thread. All three variables are read from the
     56      * audio playback thread.
     57      */
     58     private volatile boolean mStopped;
     59     private volatile boolean mDone;
     60     private volatile boolean mIsError;
     61 
     62     private final BlockingAudioTrack mAudioTrack;
     63     private final EventLogger mLogger;
     64 
     65 
     66     SynthesisPlaybackQueueItem(int streamType, int sampleRate,
     67             int audioFormat, int channelCount,
     68             float volume, float pan, UtteranceProgressDispatcher dispatcher,
     69             Object callerIdentity, EventLogger logger) {
     70         super(dispatcher, callerIdentity);
     71 
     72         mUnconsumedBytes = 0;
     73 
     74         mStopped = false;
     75         mDone = false;
     76         mIsError = false;
     77 
     78         mAudioTrack = new BlockingAudioTrack(streamType, sampleRate, audioFormat,
     79                 channelCount, volume, pan);
     80         mLogger = logger;
     81     }
     82 
     83 
     84     @Override
     85     public void run() {
     86         final UtteranceProgressDispatcher dispatcher = getDispatcher();
     87         dispatcher.dispatchOnStart();
     88 
     89 
     90         mAudioTrack.init();
     91 
     92         try {
     93             byte[] buffer = null;
     94 
     95             // take() will block until:
     96             //
     97             // (a) there is a buffer available to tread. In which case
     98             // a non null value is returned.
     99             // OR (b) stop() is called in which case it will return null.
    100             // OR (c) done() is called in which case it will return null.
    101             while ((buffer = take()) != null) {
    102                 mAudioTrack.write(buffer);
    103                 mLogger.onAudioDataWritten();
    104             }
    105 
    106         } catch (InterruptedException ie) {
    107             if (DBG) Log.d(TAG, "Interrupted waiting for buffers, cleaning up.");
    108         }
    109 
    110         mAudioTrack.waitAndRelease();
    111 
    112         if (mIsError) {
    113             dispatcher.dispatchOnError();
    114         } else {
    115             dispatcher.dispatchOnDone();
    116         }
    117 
    118         mLogger.onWriteData();
    119     }
    120 
    121     @Override
    122     void stop(boolean isError) {
    123         try {
    124             mListLock.lock();
    125 
    126             // Update our internal state.
    127             mStopped = true;
    128             mIsError = isError;
    129 
    130             // Wake up the audio playback thread if it was waiting on take().
    131             // take() will return null since mStopped was true, and will then
    132             // break out of the data write loop.
    133             mReadReady.signal();
    134 
    135             // Wake up the synthesis thread if it was waiting on put(). Its
    136             // buffers will no longer be copied since mStopped is true. The
    137             // PlaybackSynthesisCallback that this synthesis corresponds to
    138             // would also have been stopped, and so all calls to
    139             // Callback.onDataAvailable( ) will return errors too.
    140             mNotFull.signal();
    141         } finally {
    142             mListLock.unlock();
    143         }
    144 
    145         // Stop the underlying audio track. This will stop sending
    146         // data to the mixer and discard any pending buffers that the
    147         // track holds.
    148         mAudioTrack.stop();
    149     }
    150 
    151     void done() {
    152         try {
    153             mListLock.lock();
    154 
    155             // Update state.
    156             mDone = true;
    157 
    158             // Unblocks the audio playback thread if it was waiting on take()
    159             // after having consumed all available buffers. It will then return
    160             // null and leave the write loop.
    161             mReadReady.signal();
    162 
    163             // Just so that engines that try to queue buffers after
    164             // calling done() don't block the synthesis thread forever. Ideally
    165             // this should be called from the same thread as put() is, and hence
    166             // this call should be pointless.
    167             mNotFull.signal();
    168         } finally {
    169             mListLock.unlock();
    170         }
    171     }
    172 
    173 
    174     void put(byte[] buffer) throws InterruptedException {
    175         try {
    176             mListLock.lock();
    177             long unconsumedAudioMs = 0;
    178 
    179             while ((unconsumedAudioMs = mAudioTrack.getAudioLengthMs(mUnconsumedBytes)) >
    180                     MAX_UNCONSUMED_AUDIO_MS && !mStopped) {
    181                 mNotFull.await();
    182             }
    183 
    184             // Don't bother queueing the buffer if we've stopped. The playback thread
    185             // would have woken up when stop() is called (if it was blocked) and will
    186             // proceed to leave the write loop since take() will return null when
    187             // stopped.
    188             if (mStopped) {
    189                 return;
    190             }
    191 
    192             mDataBufferList.add(new ListEntry(buffer));
    193             mUnconsumedBytes += buffer.length;
    194             mReadReady.signal();
    195         } finally {
    196             mListLock.unlock();
    197         }
    198     }
    199 
    200     private byte[] take() throws InterruptedException {
    201         try {
    202             mListLock.lock();
    203 
    204             // Block if there are no available buffers, and stop() has not
    205             // been called and done() has not been called.
    206             while (mDataBufferList.size() == 0 && !mStopped && !mDone) {
    207                 mReadReady.await();
    208             }
    209 
    210             // If stopped, return null so that we can exit the playback loop
    211             // as soon as possible.
    212             if (mStopped) {
    213                 return null;
    214             }
    215 
    216             // Remove the first entry from the queue.
    217             ListEntry entry = mDataBufferList.poll();
    218 
    219             // This is the normal playback loop exit case, when done() was
    220             // called. (mDone will be true at this point).
    221             if (entry == null) {
    222                 return null;
    223             }
    224 
    225             mUnconsumedBytes -= entry.mBytes.length;
    226             // Unblock the waiting writer. We use signal() and not signalAll()
    227             // because there will only be one thread waiting on this (the
    228             // Synthesis thread).
    229             mNotFull.signal();
    230 
    231             return entry.mBytes;
    232         } finally {
    233             mListLock.unlock();
    234         }
    235     }
    236 
    237     static final class ListEntry {
    238         final byte[] mBytes;
    239 
    240         ListEntry(byte[] bytes) {
    241             mBytes = bytes;
    242         }
    243     }
    244 }
    245 
    246