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.AudioOutputParams;
     19 import android.speech.tts.TextToSpeechService.UtteranceProgressDispatcher;
     20 import android.util.Log;
     21 
     22 import java.util.LinkedList;
     23 import java.util.concurrent.locks.Condition;
     24 import java.util.concurrent.locks.Lock;
     25 import java.util.concurrent.locks.ReentrantLock;
     26 
     27 /**
     28  * Manages the playback of a list of byte arrays representing audio data
     29  * that are queued by the engine to an audio track.
     30  */
     31 final class SynthesisPlaybackQueueItem extends PlaybackQueueItem {
     32     private static final String TAG = "TTS.SynthQueueItem";
     33     private static final boolean DBG = false;
     34 
     35     /**
     36      * Maximum length of audio we leave unconsumed by the audio track.
     37      * Calls to {@link #put(byte[])} will block until we have less than
     38      * this amount of audio left to play back.
     39      */
     40     private static final long MAX_UNCONSUMED_AUDIO_MS = 500;
     41 
     42     /**
     43      * Guards accesses to mDataBufferList and mUnconsumedBytes.
     44      */
     45     private final Lock mListLock = new ReentrantLock();
     46     private final Condition mReadReady = mListLock.newCondition();
     47     private final Condition mNotFull = mListLock.newCondition();
     48 
     49     // Guarded by mListLock.
     50     private final LinkedList<ListEntry> mDataBufferList = new LinkedList<ListEntry>();
     51     // Guarded by mListLock.
     52     private int mUnconsumedBytes;
     53 
     54     /*
     55      * While mStopped and mIsError can be written from any thread, mDone is written
     56      * only from the synthesis thread. All three variables are read from the
     57      * audio playback thread.
     58      */
     59     private volatile boolean mStopped;
     60     private volatile boolean mDone;
     61     private volatile int mStatusCode;
     62 
     63     private final BlockingAudioTrack mAudioTrack;
     64     private final AbstractEventLogger mLogger;
     65 
     66     SynthesisPlaybackQueueItem(AudioOutputParams audioParams, int sampleRate,
     67             int audioFormat, int channelCount, UtteranceProgressDispatcher dispatcher,
     68             Object callerIdentity, AbstractEventLogger logger) {
     69         super(dispatcher, callerIdentity);
     70 
     71         mUnconsumedBytes = 0;
     72 
     73         mStopped = false;
     74         mDone = false;
     75         mStatusCode = TextToSpeech.SUCCESS;
     76 
     77         mAudioTrack = new BlockingAudioTrack(audioParams, sampleRate, audioFormat, channelCount);
     78         mLogger = logger;
     79     }
     80 
     81 
     82     @Override
     83     public void run() {
     84         final UtteranceProgressDispatcher dispatcher = getDispatcher();
     85         dispatcher.dispatchOnStart();
     86 
     87         if (!mAudioTrack.init()) {
     88             dispatcher.dispatchOnError(TextToSpeech.ERROR_OUTPUT);
     89             return;
     90         }
     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 (mStatusCode == TextToSpeech.SUCCESS) {
    113             dispatcher.dispatchOnSuccess();
    114         } else if(mStatusCode == TextToSpeech.STOPPED) {
    115             dispatcher.dispatchOnStop();
    116         } else {
    117             dispatcher.dispatchOnError(mStatusCode);
    118         }
    119 
    120         mLogger.onCompleted(mStatusCode);
    121     }
    122 
    123     @Override
    124     void stop(int statusCode) {
    125         try {
    126             mListLock.lock();
    127 
    128             // Update our internal state.
    129             mStopped = true;
    130             mStatusCode = statusCode;
    131 
    132             // Wake up the audio playback thread if it was waiting on take().
    133             // take() will return null since mStopped was true, and will then
    134             // break out of the data write loop.
    135             mReadReady.signal();
    136 
    137             // Wake up the synthesis thread if it was waiting on put(). Its
    138             // buffers will no longer be copied since mStopped is true. The
    139             // PlaybackSynthesisCallback that this synthesis corresponds to
    140             // would also have been stopped, and so all calls to
    141             // Callback.onDataAvailable( ) will return errors too.
    142             mNotFull.signal();
    143         } finally {
    144             mListLock.unlock();
    145         }
    146 
    147         // Stop the underlying audio track. This will stop sending
    148         // data to the mixer and discard any pending buffers that the
    149         // track holds.
    150         mAudioTrack.stop();
    151     }
    152 
    153     void done() {
    154         try {
    155             mListLock.lock();
    156 
    157             // Update state.
    158             mDone = true;
    159 
    160             // Unblocks the audio playback thread if it was waiting on take()
    161             // after having consumed all available buffers. It will then return
    162             // null and leave the write loop.
    163             mReadReady.signal();
    164 
    165             // Just so that engines that try to queue buffers after
    166             // calling done() don't block the synthesis thread forever. Ideally
    167             // this should be called from the same thread as put() is, and hence
    168             // this call should be pointless.
    169             mNotFull.signal();
    170         } finally {
    171             mListLock.unlock();
    172         }
    173     }
    174 
    175 
    176     void put(byte[] buffer) throws InterruptedException {
    177         try {
    178             mListLock.lock();
    179             long unconsumedAudioMs = 0;
    180 
    181             while ((unconsumedAudioMs = mAudioTrack.getAudioLengthMs(mUnconsumedBytes)) >
    182                     MAX_UNCONSUMED_AUDIO_MS && !mStopped) {
    183                 mNotFull.await();
    184             }
    185 
    186             // Don't bother queueing the buffer if we've stopped. The playback thread
    187             // would have woken up when stop() is called (if it was blocked) and will
    188             // proceed to leave the write loop since take() will return null when
    189             // stopped.
    190             if (mStopped) {
    191                 return;
    192             }
    193 
    194             mDataBufferList.add(new ListEntry(buffer));
    195             mUnconsumedBytes += buffer.length;
    196             mReadReady.signal();
    197         } finally {
    198             mListLock.unlock();
    199         }
    200     }
    201 
    202     private byte[] take() throws InterruptedException {
    203         try {
    204             mListLock.lock();
    205 
    206             // Block if there are no available buffers, and stop() has not
    207             // been called and done() has not been called.
    208             while (mDataBufferList.size() == 0 && !mStopped && !mDone) {
    209                 mReadReady.await();
    210             }
    211 
    212             // If stopped, return null so that we can exit the playback loop
    213             // as soon as possible.
    214             if (mStopped) {
    215                 return null;
    216             }
    217 
    218             // Remove the first entry from the queue.
    219             ListEntry entry = mDataBufferList.poll();
    220 
    221             // This is the normal playback loop exit case, when done() was
    222             // called. (mDone will be true at this point).
    223             if (entry == null) {
    224                 return null;
    225             }
    226 
    227             mUnconsumedBytes -= entry.mBytes.length;
    228             // Unblock the waiting writer. We use signal() and not signalAll()
    229             // because there will only be one thread waiting on this (the
    230             // Synthesis thread).
    231             mNotFull.signal();
    232 
    233             return entry.mBytes;
    234         } finally {
    235             mListLock.unlock();
    236         }
    237     }
    238 
    239     static final class ListEntry {
    240         final byte[] mBytes;
    241 
    242         ListEntry(byte[] bytes) {
    243             mBytes = bytes;
    244         }
    245     }
    246 }
    247