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 /**
     23  * Speech synthesis request that plays the audio as it is received.
     24  */
     25 class PlaybackSynthesisCallback extends AbstractSynthesisCallback {
     26 
     27     private static final String TAG = "PlaybackSynthesisRequest";
     28     private static final boolean DBG = false;
     29 
     30     private static final int MIN_AUDIO_BUFFER_SIZE = 8192;
     31 
     32     private final AudioOutputParams mAudioParams;
     33 
     34     /**
     35      * Guards {@link #mAudioTrackHandler}, {@link #mItem} and {@link #mStopped}.
     36      */
     37     private final Object mStateLock = new Object();
     38 
     39     // Handler associated with a thread that plays back audio requests.
     40     private final AudioPlaybackHandler mAudioTrackHandler;
     41     // A request "token", which will be non null after start() has been called.
     42     private SynthesisPlaybackQueueItem mItem = null;
     43 
     44     private volatile boolean mDone = false;
     45 
     46     /** Status code of synthesis */
     47     protected int mStatusCode;
     48 
     49     private final UtteranceProgressDispatcher mDispatcher;
     50     private final Object mCallerIdentity;
     51     private final AbstractEventLogger mLogger;
     52 
     53     PlaybackSynthesisCallback(AudioOutputParams audioParams, AudioPlaybackHandler audioTrackHandler,
     54             UtteranceProgressDispatcher dispatcher, Object callerIdentity,
     55             AbstractEventLogger logger, boolean clientIsUsingV2) {
     56         super(clientIsUsingV2);
     57         mAudioParams = audioParams;
     58         mAudioTrackHandler = audioTrackHandler;
     59         mDispatcher = dispatcher;
     60         mCallerIdentity = callerIdentity;
     61         mLogger = logger;
     62         mStatusCode = TextToSpeech.SUCCESS;
     63     }
     64 
     65     @Override
     66     void stop() {
     67         if (DBG) Log.d(TAG, "stop()");
     68 
     69         SynthesisPlaybackQueueItem item;
     70         synchronized (mStateLock) {
     71             if (mDone) {
     72                 return;
     73             }
     74             if (mStatusCode == TextToSpeech.STOPPED) {
     75                 Log.w(TAG, "stop() called twice");
     76                 return;
     77             }
     78 
     79             item = mItem;
     80             mStatusCode = TextToSpeech.STOPPED;
     81         }
     82 
     83         if (item != null) {
     84             // This might result in the synthesis thread being woken up, at which
     85             // point it will write an additional buffer to the item - but we
     86             // won't worry about that because the audio playback queue will be cleared
     87             // soon after (see SynthHandler#stop(String).
     88             item.stop(TextToSpeech.STOPPED);
     89         } else {
     90             // This happens when stop() or error() were called before start() was.
     91 
     92             // In all other cases, mAudioTrackHandler.stop() will
     93             // result in onSynthesisDone being called, and we will
     94             // write data there.
     95             mLogger.onCompleted(TextToSpeech.STOPPED);
     96             mDispatcher.dispatchOnStop();
     97         }
     98     }
     99 
    100     @Override
    101     public int getMaxBufferSize() {
    102         // The AudioTrack buffer will be at least MIN_AUDIO_BUFFER_SIZE, so that should always be
    103         // a safe buffer size to pass in.
    104         return MIN_AUDIO_BUFFER_SIZE;
    105     }
    106 
    107     @Override
    108     public boolean hasStarted() {
    109         synchronized (mStateLock) {
    110             return mItem != null;
    111         }
    112     }
    113 
    114     @Override
    115     public boolean hasFinished() {
    116         synchronized (mStateLock) {
    117             return mDone;
    118         }
    119     }
    120 
    121     @Override
    122     public int start(int sampleRateInHz, int audioFormat, int channelCount) {
    123         if (DBG) Log.d(TAG, "start(" + sampleRateInHz + "," + audioFormat + "," + channelCount
    124                 + ")");
    125 
    126         int channelConfig = BlockingAudioTrack.getChannelConfig(channelCount);
    127 
    128         synchronized (mStateLock) {
    129             if (channelConfig == 0) {
    130                 Log.e(TAG, "Unsupported number of channels :" + channelCount);
    131                 mStatusCode = TextToSpeech.ERROR_OUTPUT;
    132                 return TextToSpeech.ERROR;
    133             }
    134             if (mStatusCode == TextToSpeech.STOPPED) {
    135                 if (DBG) Log.d(TAG, "stop() called before start(), returning.");
    136                 return errorCodeOnStop();
    137             }
    138             if (mStatusCode != TextToSpeech.SUCCESS) {
    139                 if (DBG) Log.d(TAG, "Error was raised");
    140                 return TextToSpeech.ERROR;
    141             }
    142             if (mItem != null) {
    143                 Log.e(TAG, "Start called twice");
    144                 return TextToSpeech.ERROR;
    145             }
    146             SynthesisPlaybackQueueItem item = new SynthesisPlaybackQueueItem(
    147                     mAudioParams, sampleRateInHz, audioFormat, channelCount,
    148                     mDispatcher, mCallerIdentity, mLogger);
    149             mAudioTrackHandler.enqueue(item);
    150             mItem = item;
    151         }
    152 
    153         return TextToSpeech.SUCCESS;
    154     }
    155 
    156     @Override
    157     public int audioAvailable(byte[] buffer, int offset, int length) {
    158         if (DBG) Log.d(TAG, "audioAvailable(byte[" + buffer.length + "]," + offset + "," + length
    159                 + ")");
    160 
    161         if (length > getMaxBufferSize() || length <= 0) {
    162             throw new IllegalArgumentException("buffer is too large or of zero length (" +
    163                     + length + " bytes)");
    164         }
    165 
    166         SynthesisPlaybackQueueItem item = null;
    167         synchronized (mStateLock) {
    168             if (mItem == null) {
    169                 mStatusCode = TextToSpeech.ERROR_OUTPUT;
    170                 return TextToSpeech.ERROR;
    171             }
    172             if (mStatusCode != TextToSpeech.SUCCESS) {
    173                 if (DBG) Log.d(TAG, "Error was raised");
    174                 return TextToSpeech.ERROR;
    175             }
    176             if (mStatusCode == TextToSpeech.STOPPED) {
    177                 return errorCodeOnStop();
    178             }
    179             item = mItem;
    180         }
    181 
    182         // Sigh, another copy.
    183         final byte[] bufferCopy = new byte[length];
    184         System.arraycopy(buffer, offset, bufferCopy, 0, length);
    185 
    186         // Might block on mItem.this, if there are too many buffers waiting to
    187         // be consumed.
    188         try {
    189             item.put(bufferCopy);
    190         } catch (InterruptedException ie) {
    191             synchronized (mStateLock) {
    192                 mStatusCode = TextToSpeech.ERROR_OUTPUT;
    193                 return TextToSpeech.ERROR;
    194             }
    195         }
    196 
    197         mLogger.onEngineDataReceived();
    198         return TextToSpeech.SUCCESS;
    199     }
    200 
    201     @Override
    202     public int done() {
    203         if (DBG) Log.d(TAG, "done()");
    204 
    205         int statusCode = 0;
    206         SynthesisPlaybackQueueItem item = null;
    207         synchronized (mStateLock) {
    208             if (mDone) {
    209                 Log.w(TAG, "Duplicate call to done()");
    210                 // Not an error that would prevent synthesis. Hence no
    211                 // setStatusCode
    212                 return TextToSpeech.ERROR;
    213             }
    214             if (mStatusCode == TextToSpeech.STOPPED) {
    215                 if (DBG) Log.d(TAG, "Request has been aborted.");
    216                 return errorCodeOnStop();
    217             }
    218             mDone = true;
    219 
    220             if (mItem == null) {
    221                 // .done() was called before .start. Treat it as successful synthesis
    222                 // for a client, despite service bad implementation.
    223                 Log.w(TAG, "done() was called before start() call");
    224                 if (mStatusCode == TextToSpeech.SUCCESS) {
    225                     mDispatcher.dispatchOnSuccess();
    226                 } else {
    227                     mDispatcher.dispatchOnError(mStatusCode);
    228                 }
    229                 mLogger.onEngineComplete();
    230                 return TextToSpeech.ERROR;
    231             }
    232 
    233             item = mItem;
    234             statusCode = mStatusCode;
    235         }
    236 
    237         // Signal done or error to item
    238         if (statusCode == TextToSpeech.SUCCESS) {
    239             item.done();
    240         } else {
    241             item.stop(statusCode);
    242         }
    243         mLogger.onEngineComplete();
    244         return TextToSpeech.SUCCESS;
    245     }
    246 
    247     @Override
    248     public void error() {
    249         error(TextToSpeech.ERROR_SYNTHESIS);
    250     }
    251 
    252     @Override
    253     public void error(int errorCode) {
    254         if (DBG) Log.d(TAG, "error() [will call stop]");
    255         synchronized (mStateLock) {
    256             if (mDone) {
    257                 return;
    258             }
    259             mStatusCode = errorCode;
    260         }
    261     }
    262 }
    263