1 /*
2 * Copyright (C) 2016 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of 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,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #define LOG_TAG "IsochronousClockModel"
18 //#define LOG_NDEBUG 0
19 #include <log/log.h>
20
21 #include <inttypes.h>
22 #include <stdint.h>
23 #include <algorithm>
24
25 #include "utility/AudioClock.h"
26 #include "utility/AAudioUtilities.h"
27 #include "IsochronousClockModel.h"
28
29 using namespace aaudio;
30
31 using namespace android::audio_utils;
32
33 #ifndef ICM_LOG_DRIFT
34 #define ICM_LOG_DRIFT 0
35 #endif // ICM_LOG_DRIFT
36
37 // To enable the timestamp histogram, enter this before opening the stream:
38 // adb root
39 // adb shell setprop aaudio.log_mask 1
40 // A histogram of the lateness of the timestamps will be cleared when the stream is started.
41 // It will be updated when the model is stable and receives a timestamp,
42 // and dumped to the log when the stream is stopped.
43
IsochronousClockModel()44 IsochronousClockModel::IsochronousClockModel()
45 {
46 if ((AAudioProperty_getLogMask() & AAUDIO_LOG_CLOCK_MODEL_HISTOGRAM) != 0) {
47 mHistogramMicros = std::make_unique<Histogram>(kHistogramBinCount,
48 kHistogramBinWidthMicros);
49 }
50 update();
51 }
52
setPositionAndTime(int64_t framePosition,int64_t nanoTime)53 void IsochronousClockModel::setPositionAndTime(int64_t framePosition, int64_t nanoTime) {
54 ALOGV("setPositionAndTime, %lld, %lld", (long long) framePosition, (long long) nanoTime);
55 mMarkerFramePosition = framePosition;
56 mMarkerNanoTime = nanoTime;
57 }
58
start(int64_t nanoTime)59 void IsochronousClockModel::start(int64_t nanoTime) {
60 ALOGV("start(nanos = %lld)\n", (long long) nanoTime);
61 mMarkerNanoTime = nanoTime;
62 mState = STATE_STARTING;
63 mConsecutiveVeryLateCount = 0;
64 mDspStallCount = 0;
65 if (mHistogramMicros) {
66 mHistogramMicros->clear();
67 }
68 }
69
stop(int64_t nanoTime)70 void IsochronousClockModel::stop(int64_t nanoTime) {
71 ALOGD("stop(nanos = %lld) max lateness = %d micros, DSP stalled %d times",
72 (long long) nanoTime,
73 (int) (mMaxMeasuredLatenessNanos / 1000),
74 mDspStallCount
75 );
76 setPositionAndTime(convertTimeToPosition(nanoTime), nanoTime);
77 // TODO should we set position?
78 mState = STATE_STOPPED;
79 if (mHistogramMicros) {
80 dumpHistogram();
81 }
82 }
83
isStarting() const84 bool IsochronousClockModel::isStarting() const {
85 return mState == STATE_STARTING;
86 }
87
isRunning() const88 bool IsochronousClockModel::isRunning() const {
89 return mState == STATE_RUNNING;
90 }
91
processTimestamp(int64_t framePosition,int64_t nanoTime)92 void IsochronousClockModel::processTimestamp(int64_t framePosition, int64_t nanoTime) {
93 mTimestampCount++;
94 // Log position and time in CSV format so we can import it easily into spreadsheets.
95 //ALOGD("%s() CSV, %d, %lld, %lld", __func__,
96 //mTimestampCount, (long long)framePosition, (long long)nanoTime);
97 int64_t framesDelta = framePosition - mMarkerFramePosition;
98 int64_t nanosDelta = nanoTime - mMarkerNanoTime;
99 if (nanosDelta < 1000) {
100 return;
101 }
102
103 // ALOGD("processTimestamp() - mMarkerFramePosition = %lld at mMarkerNanoTime %llu",
104 // (long long)mMarkerFramePosition,
105 // (long long)mMarkerNanoTime);
106
107 int64_t expectedNanosDelta = convertDeltaPositionToTime(framesDelta);
108 // ALOGD("processTimestamp() - expectedNanosDelta = %lld, nanosDelta = %llu",
109 // (long long)expectedNanosDelta,
110 // (long long)nanosDelta);
111
112 // ALOGD("processTimestamp() - mSampleRate = %d", mSampleRate);
113 // ALOGD("processTimestamp() - mState = %d", mState);
114 // Lateness relative to the start of the window.
115 int64_t latenessNanos = nanosDelta - expectedNanosDelta;
116 int32_t nextConsecutiveVeryLateCount = 0;
117 switch (mState) {
118 case STATE_STOPPED:
119 break;
120 case STATE_STARTING:
121 setPositionAndTime(framePosition, nanoTime);
122 mState = STATE_SYNCING;
123 break;
124 case STATE_SYNCING:
125 // This will handle a burst of rapid transfer at the beginning.
126 if (latenessNanos < 0) {
127 setPositionAndTime(framePosition, nanoTime);
128 } else {
129 // ALOGD("processTimestamp() - advance to STATE_RUNNING");
130 mState = STATE_RUNNING;
131 }
132 break;
133 case STATE_RUNNING:
134 if (mHistogramMicros) {
135 mHistogramMicros->add(latenessNanos / AAUDIO_NANOS_PER_MICROSECOND);
136 }
137 // Modify estimated position based on lateness.
138 // This affects the "early" side of the window, which controls output glitches.
139 if (latenessNanos < 0) {
140 // Earlier than expected timestamp.
141 // This data is probably more accurate, so use it.
142 // Or we may be drifting due to a fast HW clock.
143 setPositionAndTime(framePosition, nanoTime);
144 #if ICM_LOG_DRIFT
145 int earlyDeltaMicros = (int) ((expectedNanosDelta - nanosDelta)
146 / AAUDIO_NANOS_PER_MICROSECOND);
147 ALOGD("%s() - STATE_RUNNING - #%d, %5d micros EARLY",
148 __func__, mTimestampCount, earlyDeltaMicros);
149 #endif
150 } else if (latenessNanos > mLatenessForJumpNanos) {
151 ALOGD("%s() - STATE_RUNNING - #%d, %5d micros VERY LATE, %d times",
152 __func__,
153 mTimestampCount,
154 (int) (latenessNanos / AAUDIO_NANOS_PER_MICROSECOND),
155 mConsecutiveVeryLateCount
156 );
157 // A lateness this large is probably due to a stall in the DSP.
158 // A pause causes a persistent lateness so we can detect it by counting
159 // consecutive late timestamps.
160 if (mConsecutiveVeryLateCount >= kVeryLateCountsNeededToTriggerJump) {
161 // Assume the timestamp is valid and let subsequent EARLY timestamps
162 // move the window quickly to the correct place.
163 setPositionAndTime(framePosition, nanoTime); // JUMP!
164 mDspStallCount++;
165 // Throttle the warnings but do not silence them.
166 // They indicate a bug that needs to be fixed!
167 if ((nanoTime - mLastJumpWarningTimeNanos) > AAUDIO_NANOS_PER_SECOND) {
168 ALOGW("%s() - STATE_RUNNING - #%d, %5d micros VERY LATE! Force window jump"
169 ", mDspStallCount = %d",
170 __func__,
171 mTimestampCount,
172 (int) (latenessNanos / AAUDIO_NANOS_PER_MICROSECOND),
173 mDspStallCount
174 );
175 mLastJumpWarningTimeNanos = nanoTime;
176 }
177 } else {
178 nextConsecutiveVeryLateCount = mConsecutiveVeryLateCount + 1;
179 driftForward(latenessNanos, expectedNanosDelta, framePosition);
180 }
181 } else if (latenessNanos > mLatenessForDriftNanos) {
182 driftForward(latenessNanos, expectedNanosDelta, framePosition);
183 }
184 mConsecutiveVeryLateCount = nextConsecutiveVeryLateCount;
185
186 // Modify mMaxMeasuredLatenessNanos.
187 // This affects the "late" side of the window, which controls input glitches.
188 if (latenessNanos > mMaxMeasuredLatenessNanos) { // increase
189 #if ICM_LOG_DRIFT
190 ALOGD("%s() - STATE_RUNNING - #%d, newmax %d, oldmax %d micros LATE",
191 __func__,
192 mTimestampCount,
193 (int) (latenessNanos / AAUDIO_NANOS_PER_MICROSECOND),
194 (int) (mMaxMeasuredLatenessNanos / AAUDIO_NANOS_PER_MICROSECOND)
195 );
196 #endif
197 mMaxMeasuredLatenessNanos = (int32_t) latenessNanos;
198 }
199
200 break;
201 default:
202 break;
203 }
204 }
205
206 // When we are on the late side, it may be because of preemption in the kernel,
207 // or timing jitter caused by resampling in the DSP,
208 // or we may be drifting due to a slow HW clock.
209 // We add slight drift value just in case there is actual long term drift
210 // forward caused by a slower clock.
211 // If the clock is faster than the model will get pushed earlier
212 // by the code in the earlier branch.
213 // The two opposing forces should allow the model to track the real clock
214 // over a long time.
driftForward(int64_t latenessNanos,int64_t expectedNanosDelta,int64_t framePosition)215 void IsochronousClockModel::driftForward(int64_t latenessNanos,
216 int64_t expectedNanosDelta,
217 int64_t framePosition) {
218 const int64_t driftNanos = (latenessNanos - mLatenessForDriftNanos) >> kShifterForDrift;
219 const int64_t minDriftNanos = std::min(driftNanos, kMaxDriftNanos);
220 const int64_t expectedMarkerNanoTime = mMarkerNanoTime + expectedNanosDelta;
221 const int64_t driftedTime = expectedMarkerNanoTime + minDriftNanos;
222 setPositionAndTime(framePosition, driftedTime);
223 #if ICM_LOG_DRIFT
224 ALOGD("%s() - STATE_RUNNING - #%d, %5d micros LATE, nudge window forward by %d micros",
225 __func__,
226 mTimestampCount,
227 (int) (latenessNanos / AAUDIO_NANOS_PER_MICROSECOND),
228 (int) (minDriftNanos / AAUDIO_NANOS_PER_MICROSECOND)
229 );
230 #endif
231 }
232
setSampleRate(int32_t sampleRate)233 void IsochronousClockModel::setSampleRate(int32_t sampleRate) {
234 mSampleRate = sampleRate;
235 update();
236 }
237
setFramesPerBurst(int32_t framesPerBurst)238 void IsochronousClockModel::setFramesPerBurst(int32_t framesPerBurst) {
239 mFramesPerBurst = framesPerBurst;
240 update();
241 ALOGD("%s() - mFramesPerBurst = %d - mBurstPeriodNanos = %" PRId64,
242 __func__,
243 mFramesPerBurst,
244 mBurstPeriodNanos
245 );
246 }
247
248 // Update expected lateness based on sampleRate and framesPerBurst
update()249 void IsochronousClockModel::update() {
250 mBurstPeriodNanos = convertDeltaPositionToTime(mFramesPerBurst);
251 mLatenessForDriftNanos = mBurstPeriodNanos + kLatenessMarginForSchedulingJitter;
252 mLatenessForJumpNanos = mLatenessForDriftNanos * kScalerForJumpLateness;
253 }
254
convertDeltaPositionToTime(int64_t framesDelta) const255 int64_t IsochronousClockModel::convertDeltaPositionToTime(int64_t framesDelta) const {
256 return (AAUDIO_NANOS_PER_SECOND * framesDelta) / mSampleRate;
257 }
258
convertDeltaTimeToPosition(int64_t nanosDelta) const259 int64_t IsochronousClockModel::convertDeltaTimeToPosition(int64_t nanosDelta) const {
260 return (mSampleRate * nanosDelta) / AAUDIO_NANOS_PER_SECOND;
261 }
262
convertPositionToTime(int64_t framePosition) const263 int64_t IsochronousClockModel::convertPositionToTime(int64_t framePosition) const {
264 if (mState == STATE_STOPPED) {
265 return mMarkerNanoTime;
266 }
267 int64_t nextBurstIndex = (framePosition + mFramesPerBurst - 1) / mFramesPerBurst;
268 int64_t nextBurstPosition = mFramesPerBurst * nextBurstIndex;
269 int64_t framesDelta = nextBurstPosition - mMarkerFramePosition;
270 int64_t nanosDelta = convertDeltaPositionToTime(framesDelta);
271 int64_t time = mMarkerNanoTime + nanosDelta;
272 // ALOGD("convertPositionToTime: pos = %llu --> time = %llu",
273 // (unsigned long long)framePosition,
274 // (unsigned long long)time);
275 return time;
276 }
277
convertTimeToPosition(int64_t nanoTime) const278 int64_t IsochronousClockModel::convertTimeToPosition(int64_t nanoTime) const {
279 if (mState == STATE_STOPPED) {
280 return mMarkerFramePosition;
281 }
282 int64_t nanosDelta = nanoTime - mMarkerNanoTime;
283 int64_t framesDelta = convertDeltaTimeToPosition(nanosDelta);
284 int64_t nextBurstPosition = mMarkerFramePosition + framesDelta;
285 int64_t nextBurstIndex = nextBurstPosition / mFramesPerBurst;
286 int64_t position = nextBurstIndex * mFramesPerBurst;
287 // ALOGD("convertTimeToPosition: time = %llu --> pos = %llu",
288 // (unsigned long long)nanoTime,
289 // (unsigned long long)position);
290 // ALOGD("convertTimeToPosition: framesDelta = %llu, mFramesPerBurst = %d",
291 // (long long) framesDelta, mFramesPerBurst);
292 return position;
293 }
294
getLateTimeOffsetNanos() const295 int32_t IsochronousClockModel::getLateTimeOffsetNanos() const {
296 return mMaxMeasuredLatenessNanos + kExtraLatenessNanos;
297 }
298
convertPositionToLatestTime(int64_t framePosition) const299 int64_t IsochronousClockModel::convertPositionToLatestTime(int64_t framePosition) const {
300 return convertPositionToTime(framePosition) + getLateTimeOffsetNanos();
301 }
302
convertLatestTimeToPosition(int64_t nanoTime) const303 int64_t IsochronousClockModel::convertLatestTimeToPosition(int64_t nanoTime) const {
304 return convertTimeToPosition(nanoTime - getLateTimeOffsetNanos());
305 }
306
dump() const307 void IsochronousClockModel::dump() const {
308 ALOGD("mMarkerFramePosition = %" PRId64, mMarkerFramePosition);
309 ALOGD("mMarkerNanoTime = %" PRId64, mMarkerNanoTime);
310 ALOGD("mSampleRate = %6d", mSampleRate);
311 ALOGD("mFramesPerBurst = %6d", mFramesPerBurst);
312 ALOGD("mMaxMeasuredLatenessNanos = %6" PRId64, mMaxMeasuredLatenessNanos);
313 ALOGD("mState = %6d", mState);
314 }
315
dumpHistogram() const316 void IsochronousClockModel::dumpHistogram() const {
317 if (!mHistogramMicros) return;
318 std::istringstream istr(mHistogramMicros->dump());
319 std::string line;
320 while (std::getline(istr, line)) {
321 ALOGD("lateness, %s", line.c_str());
322 }
323 }
324