1 /* 2 * Copyright 2018 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 #ifndef ANDROID_AUDIO_UTILS_TIMESTAMP_VERIFIER_H 18 #define ANDROID_AUDIO_UTILS_TIMESTAMP_VERIFIER_H 19 20 #include <assert.h> 21 #include <audio_utils/clock.h> 22 #include <audio_utils/Statistics.h> 23 24 namespace android { 25 26 /** Verifies that a sequence of timestamps (a frame count, time pair) 27 * is consistent based on sample rate. 28 * 29 * F is the type of frame counts (for example int64_t) 30 * T is the type of time in Ns (for example int64_t ns) 31 * 32 * All methods except for toString() are safe to call from a SCHED_FIFO thread. 33 */ 34 template <typename F /* frame count */, typename T /* time units */> 35 class TimestampVerifier { 36 public: 37 explicit constexpr TimestampVerifier( 38 double alphaJitter = kDefaultAlphaJitter, 39 double alphaEstimator = kDefaultAlphaEstimator) 40 : mJitterMs{alphaJitter} 41 , mTimestampEstimator{alphaEstimator} 42 , mCorrectedJitterMs{alphaJitter} 43 { } 44 45 // construct from static arrays. 46 template <size_t N> TimestampVerifier(const F (& frames)[N],const T (& timeNs)[N],uint32_t sampleRate)47 constexpr TimestampVerifier(const F (&frames)[N], const T (&timeNs)[N], uint32_t sampleRate) { 48 for (size_t i = 0; i < N; ++i) { 49 add(frames[i], timeNs[i], sampleRate); 50 } 51 } 52 53 /** adds a timestamp, represented by a (frames, timeNs) pair and the 54 * sample rate to the TimestampVerifier. 55 * 56 * The frames and timeNs should be monotonic increasing 57 * (from the previous discontinuity, or the start of adding). 58 * 59 * A sample rate change triggers a discontinuity automatically. 60 */ add(F frames,T timeNs,uint32_t sampleRate)61 constexpr void add(F frames, T timeNs, uint32_t sampleRate) { 62 // We consider negative time as "not ready". 63 // TODO: do we need to consider an explicit epoch start time? 64 if (timeNs < 0) { 65 ++mNotReady; 66 return; 67 } 68 69 // Reject timestamp if identical to last 70 if (mLastTimestamp.mFrames == frames && mLastTimestamp.mTimeNs == timeNs 71 && mSampleRate == sampleRate) { 72 return; 73 } 74 75 if (mDiscontinuity || mSampleRate != sampleRate) { 76 // ALOGD("disc:%d frames:%lld timeNs:%lld", 77 // mDiscontinuity, (long long)frames, (long long)timeNs); 78 switch (mDiscontinuityMode) { 79 case DISCONTINUITY_MODE_CONTINUOUS: 80 break; 81 case DISCONTINUITY_MODE_ZERO: 82 // frame position reset to 0 on discontinuity - detect this. 83 if (mLastTimestamp.mFrames 84 > kDiscontinuityZeroStartThresholdMs * sampleRate / MILLIS_PER_SECOND 85 && frames >= mLastTimestamp.mFrames) { 86 return; 87 } 88 break; 89 default: 90 assert(false); // never here. 91 break; 92 } 93 mDiscontinuity = false; 94 mFirstTimestamp = {frames, timeNs}; 95 mLastTimestamp = mFirstTimestamp; 96 mLastCorrectedTimestamp = mFirstTimestamp; 97 mSampleRate = sampleRate; 98 } else { 99 assert(sampleRate != 0); 100 const FrameTime timestamp{frames, timeNs}; 101 if (mCold && (timestamp.mTimeNs == mLastTimestamp.mTimeNs 102 || computeRatio(timestamp, mLastTimestamp, sampleRate) 103 < kMinimumSpeedToStartVerification)) { 104 // cold is when the timestamp may take some time to start advancing at normal rate. 105 ++mColds; 106 mFirstTimestamp = timestamp; 107 mLastCorrectedTimestamp = mFirstTimestamp; 108 // ALOGD("colds:%lld frames:%lld timeNs:%lld", 109 // (long long)mColds, (long long)frames, (long long)timeNs); 110 } else { 111 const double jitterMs = computeJitterMs(timestamp, mLastTimestamp, sampleRate); 112 mJitterMs.add(jitterMs); 113 // ALOGD("frames:%lld timeNs:%lld jitterMs:%lf", 114 // (long long)frames, (long long)timeNs, jitterMs); 115 116 // Handle timestamp estimation 117 if (mCold) { 118 mCold = false; 119 mTimestampEstimator.reset(); 120 mTimestampEstimator.add( 121 {(double)mFirstTimestamp.mTimeNs * 1e-9, (double)mFirstTimestamp.mFrames}); 122 mFirstCorrectedTimestamp = mFirstTimestamp; 123 mLastCorrectedTimestamp = mFirstCorrectedTimestamp; 124 } 125 mTimestampEstimator.add({(double)timeNs * 1e-9, (double)frames}); 126 127 // Find the corrected timestamp, a posteriori estimate. 128 FrameTime correctedTimestamp = timestamp; 129 130 // The estimator is valid after 2 timestamps; we choose correlation 131 // of kEstimatorR2Lock to signal locked. 132 if (mTimestampEstimator.getN() > 2 133 && mTimestampEstimator.getR2() >= kEstimatorR2Lock) { 134 #if 1 135 // We choose frame correction over time correction. 136 // TODO: analyze preference. 137 138 // figure out frames based on time. 139 const F newFrames = mTimestampEstimator.getYFromX((double)timeNs * 1e-9); 140 // prevent retrograde correction. 141 correctedTimestamp.mFrames = std::max( 142 newFrames, mLastCorrectedTimestamp.mFrames); 143 #else 144 // figure out time based on frames 145 const T newTimeNs = mTimestampEstimator.getXFromY((double)frames) * 1e9; 146 // prevent retrograde correction. 147 correctedTimestamp.mTimeNs = std::max( 148 newTimeNs, mLastCorrectedTimestamp.mTimeNs); 149 #endif 150 } 151 152 // Compute the jitter if the corrected timestamp is used. 153 const double correctedJitterMs = computeJitterMs( 154 correctedTimestamp, mLastCorrectedTimestamp, sampleRate); 155 mCorrectedJitterMs.add(correctedJitterMs); 156 mLastCorrectedTimestamp = correctedTimestamp; 157 } 158 mLastTimestamp = timestamp; 159 } 160 ++mTimestamps; 161 } 162 163 // How a discontinuity affects frame position. 164 enum DiscontinuityMode : int32_t { 165 DISCONTINUITY_MODE_CONTINUOUS, // frame position is unaffected. 166 DISCONTINUITY_MODE_ZERO, // frame position resets to zero. 167 }; 168 169 /** registers a discontinuity. 170 * 171 * The next timestamp added does not participate in any statistics with the last 172 * timestamp, but rather anchors following timestamp sequence verification. 173 * 174 * Consecutive discontinuities are treated as one for the purposes of counting. 175 */ discontinuity(DiscontinuityMode mode)176 constexpr void discontinuity(DiscontinuityMode mode) { 177 assert(mode == DISCONTINUITY_MODE_CONTINUOUS || mode == DISCONTINUITY_MODE_ZERO); 178 179 // If there is a pending ZERO discontinuity, do not override with CONTINUOUS 180 if (mode == DISCONTINUITY_MODE_CONTINUOUS && mDiscontinuityMode == DISCONTINUITY_MODE_ZERO 181 && mDiscontinuity) { 182 return; 183 } 184 185 if (mode != mDiscontinuityMode || !mDiscontinuity) { 186 // ALOGD("discontinuity"); 187 mDiscontinuity = true; 188 mDiscontinuityMode = mode; 189 mCold = true; 190 ++mDiscontinuities; 191 } 192 } 193 194 /** registers an error. 195 * 196 * The timestamp sequence is still assumed continuous after error. Use discontinuity() 197 * if it is not. 198 */ error()199 constexpr void error() { 200 ++mErrors; 201 } 202 getDiscontinuityMode()203 constexpr DiscontinuityMode getDiscontinuityMode() const { 204 return mDiscontinuityMode; 205 } 206 207 /** returns a string with relevant statistics. 208 * 209 * Should not be called from a SCHED_FIFO thread since it uses std::string. 210 */ toString()211 std::string toString() const { 212 std::stringstream ss; 213 214 ss << "n=" << mTimestamps; // number of timestamps added with valid times. 215 ss << " disc=" << mDiscontinuities; // discontinuities encountered (dups ignored). 216 ss << " cold=" << mColds; // timestamps not progressing after discontinuity. 217 ss << " nRdy=" << mNotReady; // timestamps not ready (time negative). 218 ss << " err=" << mErrors; // errors encountered. 219 if (mSampleRate != 0) { // ratio of time-by-frames / time 220 ss << " rate=" << computeRatio( // (since last discontinuity). 221 mLastTimestamp, mFirstTimestamp, mSampleRate); 222 } 223 ss << " jitterMs(" << mJitterMs.toString() << ")"; // timestamp jitter statistics. 224 225 double a, b, r2; // sample rate is the slope b. 226 estimateSampleRate(a, b, r2); 227 228 // r2 is correlation coefficient (where 1 is best and 0 is worst), 229 // so for better printed resolution, we print from 1 - r2. 230 ss << " localSR(" << b << ", " << (1. - r2) << ")"; 231 ss << " correctedJitterMs(" << mCorrectedJitterMs.toString() << ")"; 232 return ss.str(); 233 } 234 235 // general counters getN()236 constexpr int64_t getN() const { return mTimestamps; } getDiscontinuities()237 constexpr int64_t getDiscontinuities() const { return mDiscontinuities; } getNotReady()238 constexpr int64_t getNotReady() const { return mNotReady; } getColds()239 constexpr int64_t getColds() const { return mColds; } getErrors()240 constexpr int64_t getErrors() const { return mErrors; } getJitterMs()241 constexpr const audio_utils::Statistics<double> & getJitterMs() const { 242 return mJitterMs; 243 } 244 // estimate local sample rate (dframes / dtime) which is the slope b from: 245 // y = a + bx estimateSampleRate(double & a,double & b,double & r2)246 constexpr void estimateSampleRate(double &a, double &b, double &r2) const { 247 mTimestampEstimator.computeYLine(a, b, r2); 248 } 249 250 // timestamp anchor info 251 using FrameTime = struct { F mFrames; T mTimeNs; }; // a "constexpr" pair getFirstTimestamp()252 constexpr FrameTime getFirstTimestamp() const { return mFirstTimestamp; } getLastTimestamp()253 constexpr FrameTime getLastTimestamp() const { return mLastTimestamp; } getSampleRate()254 constexpr uint32_t getSampleRate() const { return mSampleRate; } 255 getLastCorrectedTimestamp()256 constexpr FrameTime getLastCorrectedTimestamp() const { return mLastCorrectedTimestamp; } 257 258 // Inf+-, NaN is possible only if sampleRate is 0 (should not happen) computeJitterMs(const FrameTime & current,const FrameTime & last,uint32_t sampleRate)259 static constexpr double computeJitterMs( 260 const FrameTime ¤t, const FrameTime &last, uint32_t sampleRate) { 261 const auto diff = sub(current, last); 262 const double frameDifferenceNs = diff.first * 1e9 / sampleRate; 263 const double jitterNs = diff.second - frameDifferenceNs; // actual - expected 264 return jitterNs * 1e-6; 265 } 266 267 private: 268 // our statistics have exponentially weighted history. 269 // the defaults are here. 270 static constexpr double kDefaultAlphaJitter = 0.999; 271 static constexpr double kDefaultAlphaEstimator = 0.99; 272 static constexpr double kEstimatorR2Lock = 0.95; 273 274 // general counters 275 int64_t mTimestamps = 0; 276 int64_t mDiscontinuities = 0; 277 int64_t mNotReady = 0; 278 int64_t mColds = 0; 279 int64_t mErrors = 0; 280 audio_utils::Statistics<double> mJitterMs{kDefaultAlphaJitter}; 281 282 // timestamp anchor info 283 bool mDiscontinuity = true; 284 bool mCold = true; 285 FrameTime mFirstTimestamp{}; 286 FrameTime mLastTimestamp{}; 287 uint32_t mSampleRate = 0; 288 289 // timestamp estimation and correction 290 audio_utils::LinearLeastSquaresFit<double> mTimestampEstimator{kDefaultAlphaEstimator}; 291 FrameTime mFirstCorrectedTimestamp{}; 292 FrameTime mLastCorrectedTimestamp{}; 293 audio_utils::Statistics<double> mCorrectedJitterMs{kDefaultAlphaJitter}; 294 295 // configuration 296 DiscontinuityMode mDiscontinuityMode = DISCONTINUITY_MODE_CONTINUOUS; 297 298 static constexpr double kMinimumSpeedToStartVerification = 0.1; 299 // Number of ms so small that initial jitter is OK for DISCONTINUITY_MODE_ZERO. 300 static constexpr int64_t kDiscontinuityZeroStartThresholdMs = 5; 301 302 // sub returns the signed type of the difference between left and right. 303 // This is only important if F or T are unsigned int types. 304 __attribute__((no_sanitize("integer"))) sub(const FrameTime & left,const FrameTime & right)305 static constexpr auto sub(const FrameTime &left, const FrameTime &right) { 306 return std::make_pair< 307 typename std::make_signed<F>::type, typename std::make_signed<T>::type>( 308 left.mFrames - right.mFrames, left.mTimeNs - right.mTimeNs); 309 } 310 311 // Inf+-, Nan possible depending on differences between current and last. computeRatio(const FrameTime & current,const FrameTime & last,uint32_t sampleRate)312 static constexpr double computeRatio( 313 const FrameTime ¤t, const FrameTime &last, uint32_t sampleRate) { 314 const auto diff = sub(current, last); 315 const double frameDifferenceNs = diff.first * 1e9 / sampleRate; 316 return frameDifferenceNs / diff.second; 317 } 318 }; 319 320 } // namespace android 321 322 #endif // !ANDROID_AUDIO_UTILS_TIMESTAMP_VERIFIER_H 323