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 /**
     22  * Speech synthesis request that plays the audio as it is received.
     23  */
     24 class PlaybackSynthesisCallback extends AbstractSynthesisCallback {
     25 
     26     private static final String TAG = "PlaybackSynthesisRequest";
     27     private static final boolean DBG = false;
     28 
     29     private static final int MIN_AUDIO_BUFFER_SIZE = 8192;
     30 
     31     /**
     32      * Audio stream type. Must be one of the STREAM_ contants defined in
     33      * {@link android.media.AudioManager}.
     34      */
     35     private final int mStreamType;
     36 
     37     /**
     38      * Volume, in the range [0.0f, 1.0f]. The default value is
     39      * {@link TextToSpeech.Engine#DEFAULT_VOLUME} (1.0f).
     40      */
     41     private final float mVolume;
     42 
     43     /**
     44      * Left/right position of the audio, in the range [-1.0f, 1.0f].
     45      * The default value is {@link TextToSpeech.Engine#DEFAULT_PAN} (0.0f).
     46      */
     47     private final float mPan;
     48 
     49     /**
     50      * Guards {@link #mAudioTrackHandler}, {@link #mItem} and {@link #mStopped}.
     51      */
     52     private final Object mStateLock = new Object();
     53 
     54     // Handler associated with a thread that plays back audio requests.
     55     private final AudioPlaybackHandler mAudioTrackHandler;
     56     // A request "token", which will be non null after start() has been called.
     57     private SynthesisPlaybackQueueItem mItem = null;
     58     // Whether this request has been stopped. This is useful for keeping
     59     // track whether stop() has been called before start(). In all other cases,
     60     // a non-null value of mItem will provide the same information.
     61     private boolean mStopped = false;
     62 
     63     private volatile boolean mDone = false;
     64 
     65     private final UtteranceProgressDispatcher mDispatcher;
     66     private final Object mCallerIdentity;
     67     private final EventLogger mLogger;
     68 
     69     PlaybackSynthesisCallback(int streamType, float volume, float pan,
     70             AudioPlaybackHandler audioTrackHandler, UtteranceProgressDispatcher dispatcher,
     71             Object callerIdentity, EventLogger logger) {
     72         mStreamType = streamType;
     73         mVolume = volume;
     74         mPan = pan;
     75         mAudioTrackHandler = audioTrackHandler;
     76         mDispatcher = dispatcher;
     77         mCallerIdentity = callerIdentity;
     78         mLogger = logger;
     79     }
     80 
     81     @Override
     82     void stop() {
     83         stopImpl(false);
     84     }
     85 
     86     void stopImpl(boolean wasError) {
     87         if (DBG) Log.d(TAG, "stop()");
     88 
     89         // Note that mLogger.mError might be true too at this point.
     90         mLogger.onStopped();
     91 
     92         SynthesisPlaybackQueueItem item;
     93         synchronized (mStateLock) {
     94             if (mStopped) {
     95                 Log.w(TAG, "stop() called twice");
     96                 return;
     97             }
     98 
     99             item = mItem;
    100             mStopped = true;
    101         }
    102 
    103         if (item != null) {
    104             // This might result in the synthesis thread being woken up, at which
    105             // point it will write an additional buffer to the item - but we
    106             // won't worry about that because the audio playback queue will be cleared
    107             // soon after (see SynthHandler#stop(String).
    108             item.stop(wasError);
    109         } else {
    110             // This happens when stop() or error() were called before start() was.
    111 
    112             // In all other cases, mAudioTrackHandler.stop() will
    113             // result in onSynthesisDone being called, and we will
    114             // write data there.
    115             mLogger.onWriteData();
    116 
    117             if (wasError) {
    118                 // We have to dispatch the error ourselves.
    119                 mDispatcher.dispatchOnError();
    120             }
    121         }
    122     }
    123 
    124     @Override
    125     public int getMaxBufferSize() {
    126         // The AudioTrack buffer will be at least MIN_AUDIO_BUFFER_SIZE, so that should always be
    127         // a safe buffer size to pass in.
    128         return MIN_AUDIO_BUFFER_SIZE;
    129     }
    130 
    131     @Override
    132     boolean isDone() {
    133         return mDone;
    134     }
    135 
    136     @Override
    137     public int start(int sampleRateInHz, int audioFormat, int channelCount) {
    138         if (DBG) {
    139             Log.d(TAG, "start(" + sampleRateInHz + "," + audioFormat
    140                     + "," + channelCount + ")");
    141         }
    142 
    143         int channelConfig = BlockingAudioTrack.getChannelConfig(channelCount);
    144         if (channelConfig == 0) {
    145             Log.e(TAG, "Unsupported number of channels :" + channelCount);
    146             return TextToSpeech.ERROR;
    147         }
    148 
    149         synchronized (mStateLock) {
    150             if (mStopped) {
    151                 if (DBG) Log.d(TAG, "stop() called before start(), returning.");
    152                 return TextToSpeech.ERROR;
    153             }
    154             SynthesisPlaybackQueueItem item = new SynthesisPlaybackQueueItem(
    155                     mStreamType, sampleRateInHz, audioFormat, channelCount, mVolume, mPan,
    156                     mDispatcher, mCallerIdentity, mLogger);
    157             mAudioTrackHandler.enqueue(item);
    158             mItem = item;
    159         }
    160 
    161         return TextToSpeech.SUCCESS;
    162     }
    163 
    164 
    165     @Override
    166     public int audioAvailable(byte[] buffer, int offset, int length) {
    167         if (DBG) {
    168             Log.d(TAG, "audioAvailable(byte[" + buffer.length + "],"
    169                     + offset + "," + length + ")");
    170         }
    171         if (length > getMaxBufferSize() || length <= 0) {
    172             throw new IllegalArgumentException("buffer is too large or of zero length (" +
    173                     + length + " bytes)");
    174         }
    175 
    176         SynthesisPlaybackQueueItem item = null;
    177         synchronized (mStateLock) {
    178             if (mItem == null || mStopped) {
    179                 return TextToSpeech.ERROR;
    180             }
    181             item = mItem;
    182         }
    183 
    184         // Sigh, another copy.
    185         final byte[] bufferCopy = new byte[length];
    186         System.arraycopy(buffer, offset, bufferCopy, 0, length);
    187 
    188         // Might block on mItem.this, if there are too many buffers waiting to
    189         // be consumed.
    190         try {
    191             item.put(bufferCopy);
    192         } catch (InterruptedException ie) {
    193             return TextToSpeech.ERROR;
    194         }
    195 
    196         mLogger.onEngineDataReceived();
    197 
    198         return TextToSpeech.SUCCESS;
    199     }
    200 
    201     @Override
    202     public int done() {
    203         if (DBG) Log.d(TAG, "done()");
    204 
    205         SynthesisPlaybackQueueItem item = null;
    206         synchronized (mStateLock) {
    207             if (mDone) {
    208                 Log.w(TAG, "Duplicate call to done()");
    209                 return TextToSpeech.ERROR;
    210             }
    211 
    212             mDone = true;
    213 
    214             if (mItem == null) {
    215                 return TextToSpeech.ERROR;
    216             }
    217 
    218             item = mItem;
    219         }
    220 
    221         item.done();
    222         mLogger.onEngineComplete();
    223 
    224         return TextToSpeech.SUCCESS;
    225     }
    226 
    227     @Override
    228     public void error() {
    229         if (DBG) Log.d(TAG, "error() [will call stop]");
    230         // Currently, this call will not be logged if error( ) is called
    231         // before start.
    232         mLogger.onError();
    233         stopImpl(true);
    234     }
    235 
    236 }
    237