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