• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 &current, 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 &current, 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