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