• 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 <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 &current, 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 &current, 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