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