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.os.SystemClock;
     19 import android.text.TextUtils;
     20 import android.util.Log;
     21 
     22 /**
     23  * Writes data about a given speech synthesis request to the event logs.
     24  * The data that is logged includes the calling app, length of the utterance,
     25  * speech rate / pitch and the latency and overall time taken.
     26  *
     27  * Note that {@link EventLogger#onStopped()} and {@link EventLogger#onError()}
     28  * might be called from any thread, but on {@link EventLogger#onAudioDataWritten()} and
     29  * {@link EventLogger#onComplete()} must be called from a single thread
     30  * (usually the audio playback thread}
     31  */
     32 class EventLogger {
     33     private final SynthesisRequest mRequest;
     34     private final String mServiceApp;
     35     private final int mCallerUid;
     36     private final int mCallerPid;
     37     private final long mReceivedTime;
     38     private long mPlaybackStartTime = -1;
     39     private volatile long mRequestProcessingStartTime = -1;
     40     private volatile long mEngineStartTime = -1;
     41     private volatile long mEngineCompleteTime = -1;
     42 
     43     private volatile boolean mError = false;
     44     private volatile boolean mStopped = false;
     45     private boolean mLogWritten = false;
     46 
     47     EventLogger(SynthesisRequest request, int callerUid, int callerPid, String serviceApp) {
     48         mRequest = request;
     49         mCallerUid = callerUid;
     50         mCallerPid = callerPid;
     51         mServiceApp = serviceApp;
     52         mReceivedTime = SystemClock.elapsedRealtime();
     53     }
     54 
     55     /**
     56      * Notifies the logger that this request has been selected from
     57      * the processing queue for processing. Engine latency / total time
     58      * is measured from this baseline.
     59      */
     60     public void onRequestProcessingStart() {
     61         mRequestProcessingStartTime = SystemClock.elapsedRealtime();
     62     }
     63 
     64     /**
     65      * Notifies the logger that a chunk of data has been received from
     66      * the engine. Might be called multiple times.
     67      */
     68     public void onEngineDataReceived() {
     69         if (mEngineStartTime == -1) {
     70             mEngineStartTime = SystemClock.elapsedRealtime();
     71         }
     72     }
     73 
     74     /**
     75      * Notifies the logger that the engine has finished processing data.
     76      * Will be called exactly once.
     77      */
     78     public void onEngineComplete() {
     79         mEngineCompleteTime = SystemClock.elapsedRealtime();
     80     }
     81 
     82     /**
     83      * Notifies the logger that audio playback has started for some section
     84      * of the synthesis. This is normally some amount of time after the engine
     85      * has synthesized data and varies depending on utterances and
     86      * other audio currently in the queue.
     87      */
     88     public void onAudioDataWritten() {
     89         // For now, keep track of only the first chunk of audio
     90         // that was played.
     91         if (mPlaybackStartTime == -1) {
     92             mPlaybackStartTime = SystemClock.elapsedRealtime();
     93         }
     94     }
     95 
     96     /**
     97      * Notifies the logger that the current synthesis was stopped.
     98      * Latency numbers are not reported for stopped syntheses.
     99      */
    100     public void onStopped() {
    101         mStopped = false;
    102     }
    103 
    104     /**
    105      * Notifies the logger that the current synthesis resulted in
    106      * an error. This is logged using {@link EventLogTags#writeTtsSpeakFailure}.
    107      */
    108     public void onError() {
    109         mError = true;
    110     }
    111 
    112     /**
    113      * Notifies the logger that the current synthesis has completed.
    114      * All available data is not logged.
    115      */
    116     public void onWriteData() {
    117         if (mLogWritten) {
    118             return;
    119         } else {
    120             mLogWritten = true;
    121         }
    122 
    123         long completionTime = SystemClock.elapsedRealtime();
    124         // onAudioDataWritten() should normally always be called if an
    125         // error does not occur.
    126         if (mError || mPlaybackStartTime == -1 || mEngineCompleteTime == -1) {
    127             EventLogTags.writeTtsSpeakFailure(mServiceApp, mCallerUid, mCallerPid,
    128                     getUtteranceLength(), getLocaleString(),
    129                     mRequest.getSpeechRate(), mRequest.getPitch());
    130             return;
    131         }
    132 
    133         // We don't report stopped syntheses because their overall
    134         // total time spent will be innacurate (will not correlate with
    135         // the length of the utterance).
    136         if (mStopped) {
    137             return;
    138         }
    139 
    140         final long audioLatency = mPlaybackStartTime - mReceivedTime;
    141         final long engineLatency = mEngineStartTime - mRequestProcessingStartTime;
    142         final long engineTotal = mEngineCompleteTime - mRequestProcessingStartTime;
    143 
    144         EventLogTags.writeTtsSpeakSuccess(mServiceApp, mCallerUid, mCallerPid,
    145                 getUtteranceLength(), getLocaleString(),
    146                 mRequest.getSpeechRate(), mRequest.getPitch(),
    147                 engineLatency, engineTotal, audioLatency);
    148     }
    149 
    150     /**
    151      * @return the length of the utterance for the given synthesis, 0
    152      *          if the utterance was {@code null}.
    153      */
    154     private int getUtteranceLength() {
    155         final String utterance = mRequest.getText();
    156         return utterance == null ? 0 : utterance.length();
    157     }
    158 
    159     /**
    160      * Returns a formatted locale string from the synthesis params of the
    161      * form lang-country-variant.
    162      */
    163     private String getLocaleString() {
    164         StringBuilder sb = new StringBuilder(mRequest.getLanguage());
    165         if (!TextUtils.isEmpty(mRequest.getCountry())) {
    166             sb.append('-');
    167             sb.append(mRequest.getCountry());
    168 
    169             if (!TextUtils.isEmpty(mRequest.getVariant())) {
    170                 sb.append('-');
    171                 sb.append(mRequest.getVariant());
    172             }
    173         }
    174 
    175         return sb.toString();
    176     }
    177 
    178 }
    179