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