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         if (!mAudioTrack.init()) {
     91             dispatcher.dispatchOnError();
     92             return;
     93         }
     94 
     95         try {
     96             byte[] buffer = null;
     97 
     98             // take() will block until:
     99             //
    100             // (a) there is a buffer available to tread. In which case
    101             // a non null value is returned.
    102             // OR (b) stop() is called in which case it will return null.
    103             // OR (c) done() is called in which case it will return null.
    104             while ((buffer = take()) != null) {
    105                 mAudioTrack.write(buffer);
    106                 mLogger.onAudioDataWritten();
    107             }
    108 
    109         } catch (InterruptedException ie) {
    110             if (DBG) Log.d(TAG, "Interrupted waiting for buffers, cleaning up.");
    111         }
    112 
    113         mAudioTrack.waitAndRelease();
    114 
    115         if (mIsError) {
    116             dispatcher.dispatchOnError();
    117         } else {
    118             dispatcher.dispatchOnDone();
    119         }
    120 
    121         mLogger.onWriteData();
    122     }
    123 
    124     @Override
    125     void stop(boolean isError) {
    126         try {
    127             mListLock.lock();
    128 
    129             // Update our internal state.
    130             mStopped = true;
    131             mIsError = isError;
    132 
    133             // Wake up the audio playback thread if it was waiting on take().
    134             // take() will return null since mStopped was true, and will then
    135             // break out of the data write loop.
    136             mReadReady.signal();
    137 
    138             // Wake up the synthesis thread if it was waiting on put(). Its
    139             // buffers will no longer be copied since mStopped is true. The
    140             // PlaybackSynthesisCallback that this synthesis corresponds to
    141             // would also have been stopped, and so all calls to
    142             // Callback.onDataAvailable( ) will return errors too.
    143             mNotFull.signal();
    144         } finally {
    145             mListLock.unlock();
    146         }
    147 
    148         // Stop the underlying audio track. This will stop sending
    149         // data to the mixer and discard any pending buffers that the
    150         // track holds.
    151         mAudioTrack.stop();
    152     }
    153 
    154     void done() {
    155         try {
    156             mListLock.lock();
    157 
    158             // Update state.
    159             mDone = true;
    160 
    161             // Unblocks the audio playback thread if it was waiting on take()
    162             // after having consumed all available buffers. It will then return
    163             // null and leave the write loop.
    164             mReadReady.signal();
    165 
    166             // Just so that engines that try to queue buffers after
    167             // calling done() don't block the synthesis thread forever. Ideally
    168             // this should be called from the same thread as put() is, and hence
    169             // this call should be pointless.
    170             mNotFull.signal();
    171         } finally {
    172             mListLock.unlock();
    173         }
    174     }
    175 
    176 
    177     void put(byte[] buffer) throws InterruptedException {
    178         try {
    179             mListLock.lock();
    180             long unconsumedAudioMs = 0;
    181 
    182             while ((unconsumedAudioMs = mAudioTrack.getAudioLengthMs(mUnconsumedBytes)) >
    183                     MAX_UNCONSUMED_AUDIO_MS && !mStopped) {
    184                 mNotFull.await();
    185             }
    186 
    187             // Don't bother queueing the buffer if we've stopped. The playback thread
    188             // would have woken up when stop() is called (if it was blocked) and will
    189             // proceed to leave the write loop since take() will return null when
    190             // stopped.
    191             if (mStopped) {
    192                 return;
    193             }
    194 
    195             mDataBufferList.add(new ListEntry(buffer));
    196             mUnconsumedBytes += buffer.length;
    197             mReadReady.signal();
    198         } finally {
    199             mListLock.unlock();
    200         }
    201     }
    202 
    203     private byte[] take() throws InterruptedException {
    204         try {
    205             mListLock.lock();
    206 
    207             // Block if there are no available buffers, and stop() has not
    208             // been called and done() has not been called.
    209             while (mDataBufferList.size() == 0 && !mStopped && !mDone) {
    210                 mReadReady.await();
    211             }
    212 
    213             // If stopped, return null so that we can exit the playback loop
    214             // as soon as possible.
    215             if (mStopped) {
    216                 return null;
    217             }
    218 
    219             // Remove the first entry from the queue.
    220             ListEntry entry = mDataBufferList.poll();
    221 
    222             // This is the normal playback loop exit case, when done() was
    223             // called. (mDone will be true at this point).
    224             if (entry == null) {
    225                 return null;
    226             }
    227 
    228             mUnconsumedBytes -= entry.mBytes.length;
    229             // Unblock the waiting writer. We use signal() and not signalAll()
    230             // because there will only be one thread waiting on this (the
    231             // Synthesis thread).
    232             mNotFull.signal();
    233 
    234             return entry.mBytes;
    235         } finally {
    236             mListLock.unlock();
    237         }
    238     }
    239 
    240     static final class ListEntry {
    241         final byte[] mBytes;
    242 
    243         ListEntry(byte[] bytes) {
    244             mBytes = bytes;
    245         }
    246     }
    247 }
    248