summaryrefslogtreecommitdiffstats
path: root/core/java/android/speech/tts/EventLogger.java
blob: 63b954b0b7117c30217b6859ce1c1188e67b9be6 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
/*
 * Copyright (C) 2011 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
package android.speech.tts;

import android.os.SystemClock;
import android.text.TextUtils;

/**
 * Writes data about a given speech synthesis request to the event logs.
 * The data that is logged includes the calling app, length of the utterance,
 * speech rate / pitch and the latency and overall time taken.
 *
 * Note that {@link EventLogger#onStopped()} and {@link EventLogger#onError()}
 * might be called from any thread, but on {@link EventLogger#onPlaybackStart()} and
 * {@link EventLogger#onComplete()} must be called from a single thread
 * (usually the audio playback thread}
 */
class EventLogger {
    private final SynthesisRequest mRequest;
    private final String mCallingApp;
    private final String mServiceApp;
    private final long mReceivedTime;
    private long mPlaybackStartTime = -1;
    private volatile long mRequestProcessingStartTime = -1;
    private volatile long mEngineStartTime = -1;
    private volatile long mEngineCompleteTime = -1;

    private volatile boolean mError = false;
    private volatile boolean mStopped = false;
    private boolean mLogWritten = false;

    EventLogger(SynthesisRequest request, String callingApp,
            String serviceApp) {
        mRequest = request;
        mCallingApp = callingApp;
        mServiceApp = serviceApp;
        mReceivedTime = SystemClock.elapsedRealtime();
    }

    /**
     * Notifies the logger that this request has been selected from
     * the processing queue for processing. Engine latency / total time
     * is measured from this baseline.
     */
    public void onRequestProcessingStart() {
        mRequestProcessingStartTime = SystemClock.elapsedRealtime();
    }

    /**
     * Notifies the logger that a chunk of data has been received from
     * the engine. Might be called multiple times.
     */
    public void onEngineDataReceived() {
        if (mEngineStartTime == -1) {
            mEngineStartTime = SystemClock.elapsedRealtime();
        }
    }

    /**
     * Notifies the logger that the engine has finished processing data.
     * Will be called exactly once.
     */
    public void onEngineComplete() {
        mEngineCompleteTime = SystemClock.elapsedRealtime();
    }

    /**
     * Notifies the logger that audio playback has started for some section
     * of the synthesis. This is normally some amount of time after the engine
     * has synthesized data and varides depending on utterances and
     * other audio currently in the queue.
     */
    public void onPlaybackStart() {
        // For now, keep track of only the first chunk of audio
        // that was played.
        if (mPlaybackStartTime == -1) {
            mPlaybackStartTime = SystemClock.elapsedRealtime();
        }
    }

    /**
     * Notifies the logger that the current synthesis was stopped.
     * Latency numbers are not reported for stopped syntheses.
     */
    public void onStopped() {
        mStopped = false;
    }

    /**
     * Notifies the logger that the current synthesis resulted in
     * an error. This is logged using {@link EventLogTags#writeTtsSpeakFailure}.
     */
    public void onError() {
        mError = true;
    }

    /**
     * Notifies the logger that the current synthesis has completed.
     * All available data is not logged.
     */
    public void onWriteData() {
        if (mLogWritten) {
            return;
        } else {
            mLogWritten = true;
        }

        long completionTime = SystemClock.elapsedRealtime();
        // onPlaybackStart() should normally always be called if an
        // error does not occur.
        if (mError || mPlaybackStartTime == -1 || mEngineCompleteTime == -1) {
            EventLogTags.writeTtsSpeakFailure(mServiceApp, mCallingApp,
                    getUtteranceLength(), getLocaleString(),
                    mRequest.getSpeechRate(), mRequest.getPitch());
            return;
        }

        // We don't report stopped syntheses because their overall
        // total time spent will be innacurate (will not correlate with
        // the length of the utterance).
        if (mStopped) {
            return;
        }

        final long audioLatency = mPlaybackStartTime - mReceivedTime;
        final long engineLatency = mEngineStartTime - mRequestProcessingStartTime;
        final long engineTotal = mEngineCompleteTime - mRequestProcessingStartTime;
        EventLogTags.writeTtsSpeakSuccess(mServiceApp, mCallingApp,
                getUtteranceLength(), getLocaleString(),
                mRequest.getSpeechRate(), mRequest.getPitch(),
                engineLatency, engineTotal, audioLatency);
    }

    /**
     * @return the length of the utterance for the given synthesis, 0
     *          if the utterance was {@code null}.
     */
    private int getUtteranceLength() {
        final String utterance = mRequest.getText();
        return utterance == null ? 0 : utterance.length();
    }

    /**
     * Returns a formatted locale string from the synthesis params of the
     * form lang-country-variant.
     */
    private String getLocaleString() {
        StringBuilder sb = new StringBuilder(mRequest.getLanguage());
        if (!TextUtils.isEmpty(mRequest.getCountry())) {
            sb.append('-');
            sb.append(mRequest.getCountry());

            if (!TextUtils.isEmpty(mRequest.getVariant())) {
                sb.append('-');
                sb.append(mRequest.getVariant());
            }
        }

        return sb.toString();
    }

}