1 /*
2  * Copyright 2023, 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 VIDEO_RENDER_QUALITY_TRACKER_H_
18 
19 #define VIDEO_RENDER_QUALITY_TRACKER_H_
20 
21 #include <assert.h>
22 #include <list>
23 #include <queue>
24 
25 #include <media/stagefright/MediaHistogram.h>
26 
27 namespace android {
28 
29 // A variety of video rendering quality metrics.
30 struct VideoRenderQualityMetrics {
31     static constexpr float FRAME_RATE_UNDETERMINED = -1.0f;
32     static constexpr float FRAME_RATE_24_3_2_PULLDOWN = -2.0f;
33 
34     VideoRenderQualityMetrics();
35 
36     void clear();
37 
38     // The render time of the first video frame.
39     int64_t firstRenderTimeUs;
40 
41     // The render time of the last video frame.
42     int64_t lastRenderTimeUs;
43 
44     // The number of frames released to be rendered.
45     int64_t frameReleasedCount;
46 
47     // The number of frames actually rendered.
48     int64_t frameRenderedCount;
49 
50     // The number of frames dropped - frames that were released but never rendered.
51     int64_t frameDroppedCount;
52 
53     // The number of frames that were intentionally dropped/skipped by the app.
54     int64_t frameSkippedCount;
55 
56     // The frame rate as detected by looking at the position timestamp from the content stream.
57     float contentFrameRate;
58 
59     // The frame rate as detected by looking at the desired render time passed in by the app.
60     float desiredFrameRate;
61 
62     // The frame rate as detected by looking at the actual render time, as returned by the system
63     // post-render.
64     float actualFrameRate;
65 
66     // The amount of content duration skipped by the app after a pause when video was trying to
67     // resume. This sometimes happen when catching up to the audio position which continued playing
68     // after video pauses.
69     int32_t maxContentDroppedAfterPauseMs;
70 
71     // A histogram of the durations of freezes due to dropped/skipped frames.
72     MediaHistogram<int32_t> freezeDurationMsHistogram;
73     // The computed overall freeze score using the above histogram and score conversion table. The
74     // score is based on counts in the histogram bucket, multiplied by the value in the score
75     // conversion table for that bucket. For example, the impact of a short freeze may be minimal,
76     // but the impact of long freeze may be disproportionally worse. Therefore, the score
77     // multipliers for each bucket might increase exponentially instead of linearly. A score
78     // multiplier of zero would reflect that small freeze durations have near-zero impact to the
79     // user experience.
80     int32_t freezeScore;
81     // The computed percentage of total playback duration that was frozen.
82     float freezeRate;
83     // The number of freeze events.
84     int32_t freezeEventCount;
85 
86     // A histogram of the durations between each freeze.
87     MediaHistogram<int32_t> freezeDistanceMsHistogram;
88 
89     // A histogram of the judder scores - based on the error tolerance between actual render
90     // duration of each frame and the ideal render duration.
91     MediaHistogram<int32_t> judderScoreHistogram;
92     // The computed overall judder score using the above histogram and score conversion table. The
93     // score is based on counts in the histogram bucket, multiplied by the value in the score
94     // conversion table for that bucket. For example, the impact of minimal judder may be small,
95     // but the impact of large judder may be disproportionally worse. Therefore, the score
96     // multipliers for each bucket might increase exponentially instead of linearly. A score
97     // multiplier of zero would reflect that small judder errors have near-zero impact to the user
98     // experience.
99     int32_t judderScore;
100     // The computed percentage of total frames that had judder.
101     float judderRate;
102     // The number of judder events.
103     int32_t judderEventCount;
104 };
105 
106 ///////////////////////////////////////////////////////
107 // This class analyzes various timestamps related to video rendering to compute a set of metrics
108 // that attempt to capture the quality of the user experience during video playback.
109 //
110 // The following timestamps (in microseconds) are analyzed to compute these metrics:
111 //   * The content timestamp found in the content stream, indicating the position of each video
112 //     frame.
113 //   * The desired timestamp passed in by the app, indicating at what point in time in the future
114 //     the app would like the frame to be rendered.
115 //   * The actual timestamp passed in by the display subsystem, indicating the point in time at
116 //     which the frame was actually rendered.
117 //
118 // Core to the algorithms are deriving frame durations based on these timestamps and determining
119 // the result of each video frame in the content stream:
120 //   * skipped: the app didn't want to render the frame
121 //   * dropped: the display subsystem could not render the frame in time
122 //   * rendered: the display subsystem rendered the frame
123 //
124 class VideoRenderQualityTracker {
125 public:
126     // Configurable elements of the metrics algorithms
127     class Configuration {
128     public:
129         // system/server_configurable_flags/libflags/include/get_flags.h:GetServerConfigurableFlag
130         typedef std::string (*GetServerConfigurableFlagFn)(
131                 const std::string& experiment_category_name,
132                 const std::string& experiment_flag_name,
133                 const std::string& default_value);
134 
135         static Configuration getFromServerConfigurableFlags(
136                 GetServerConfigurableFlagFn getServerConfigurableFlagFn);
137 
138         Configuration();
139 
140         // Whether or not frame render quality is tracked.
141         bool enabled;
142 
143         // Whether or not frames that are intentionally not rendered by the app should be considered
144         // as dropped.
145         bool areSkippedFramesDropped;
146 
147         // How large of a jump forward in content time is allowed before it is considered a
148         // discontinuity (seek/playlist) and various internal states are reset.
149         int32_t maxExpectedContentFrameDurationUs;
150 
151         // How much tolerance in frame duration when considering whether or not two frames have the
152         // same frame rate.
153         int32_t frameRateDetectionToleranceUs;
154 
155         // A skip forward in content time could occur during frame drops of live content. Therefore
156         // the content frame duration and the app-desired frame duration are compared using this
157         // tolerance to determine whether the app is intentionally seeking forward or whether the
158         // skip forward in content time is due to frame drops. If the app-desired frame duration is
159         // short, but the content frame duration is large, it is assumed the app is intentionally
160         // seeking forward.
161         int32_t liveContentFrameDropToleranceUs;
162 
163         // The amount of time it takes for audio to stop playback after a pause is initiated. Used
164         // for providing some allowance of dropped video frames to catch back up to the audio
165         // position when resuming playback.
166         int32_t pauseAudioLatencyUs;
167 
168         // Freeze configuration
169         //
170         // The values used to distribute freeze durations across a histogram.
171         std::vector<int32_t> freezeDurationMsHistogramBuckets;
172         //
173         // The values used to multiply the counts in the histogram buckets above to compute an
174         // overall score. This allows the score to reflect disproportionate impact as freeze
175         // durations increase.
176         std::vector<int64_t> freezeDurationMsHistogramToScore;
177         //
178         // The values used to distribute distances between freezes across a histogram.
179         std::vector<int32_t> freezeDistanceMsHistogramBuckets;
180         //
181         // The maximum number of freeze events to send back to the caller.
182         int32_t freezeEventMax;
183         //
184         // The maximum number of detail entries tracked per freeze event.
185         int32_t freezeEventDetailsMax;
186         //
187         // The maximum distance in time between two freeze occurrences such that both will be
188         // lumped into the same freeze event.
189         int32_t freezeEventDistanceToleranceMs;
190 
191         // Judder configuration
192         //
193         // A judder error lower than this value is not scored as judder.
194         int32_t judderErrorToleranceUs;
195         //
196         // The values used to distribute judder scores across a histogram.
197         std::vector<int32_t> judderScoreHistogramBuckets;
198         //
199         // The values used to multiply the counts in the histogram buckets above to compute an
200         // overall score. This allows the score to reflect disproportionate impact as judder scores
201         // increase.
202         std::vector<int64_t> judderScoreHistogramToScore;
203         //
204         // The maximum number of judder events to send back to the caller.
205         int32_t judderEventMax;
206         //
207         // The maximum number of detail entries tracked per judder event.
208         int32_t judderEventDetailsMax;
209         //
210         // The maximum distance in time between two judder occurrences such that both will be
211         // lumped into the same judder event.
212         int32_t judderEventDistanceToleranceMs;
213         //
214         // Whether or not Perfetto trace trigger is enabled.
215         bool traceTriggerEnabled;
216         //
217         // The throttle time for Perfetto trace trigger to avoid triggering multiple traces for
218         // the same event in a short time.
219         int32_t traceTriggerThrottleMs;
220         //
221         // The minimum frame render duration to recognize video freeze event to collect trace.
222         int32_t traceMinFreezeDurationMs;
223     };
224 
225     struct FreezeEvent {
226         // Details are captured for each freeze up to a limited number. The arrays are guaranteed to
227         // have the same size.
228         struct Details {
229             /// The duration of the freeze.
230             std::vector<int32_t> durationMs;
231             // The distance between the beginning of this freeze and the end of the previous freeze.
232             std::vector<int32_t> distanceMs;
233         };
234         // Whether or not the data in this structure is valid.
235         bool valid = false;
236         // The time at which the first freeze for this event was detected.
237         int64_t initialTimeUs;
238         // The total duration from the beginning of the first freeze to the end of the last freeze
239         // in this event.
240         int32_t durationMs;
241         // The number of freezes in this event.
242         int64_t count;
243         // The sum of all durations of all freezes in this event.
244         int64_t sumDurationMs;
245         // The sum of all distances between each freeze in this event.
246         int64_t sumDistanceMs;
247         // Detailed information for the first N freezes in this event.
248         Details details;
249     };
250 
251     struct JudderEvent {
252         // Details are captured for each frame judder up to a limited number. The arrays are
253         // guaranteed to have the same size.
254         struct Details {
255             // The actual render duration of the frame for this judder occurrence.
256             std::vector<int32_t> actualRenderDurationUs;
257             // The content render duration of the frame for this judder occurrence.
258             std::vector<int32_t> contentRenderDurationUs;
259             // The distance from this judder occurrence and the previous judder occurrence.
260             std::vector<int32_t> distanceMs;
261         };
262         // Whether or not the data in this structure is valid.
263         bool valid = false;
264         // The time at which the first judder occurrence for this event was detected.
265         int64_t initialTimeUs;
266         // The total duration from the first judder occurrence to the last judder occurrence in this
267         // event.
268         int32_t durationMs;
269         // The number of judder occurrences in this event.
270         int64_t count;
271         // The sum of all judder scores in this event.
272         int64_t sumScore;
273         // The sum of all distances between each judder occurrence in this event.
274         int64_t sumDistanceMs;
275         // Detailed information for the first N judder occurrences in this event.
276         Details details;
277     };
278 
279     typedef void (*TraceTriggerFn)();
280 
281     VideoRenderQualityTracker();
282     VideoRenderQualityTracker(const Configuration &configuration,
283                               const TraceTriggerFn traceTriggerFn = nullptr);
284 
285     // Called when a tunnel mode frame has been queued.
286     void onTunnelFrameQueued(int64_t contentTimeUs);
287 
288     // Called when the app has intentionally decided not to render this frame.
289     void onFrameSkipped(int64_t contentTimeUs);
290 
291     // Called when the app has requested the frame to be rendered as soon as possible.
292     void onFrameReleased(int64_t contentTimeUs);
293 
294     // Called when the app has requested the frame to be rendered at a specific point in time in the
295     // future.
296     void onFrameReleased(int64_t contentTimeUs, int64_t desiredRenderTimeNs);
297 
298     // Called when the system has detected that the frame has actually been rendered to the display.
299     // Returns any freeze events or judder events that were detected.
300     void onFrameRendered(int64_t contentTimeUs, int64_t actualRenderTimeNs,
301                          FreezeEvent *freezeEventOut = nullptr,
302                          JudderEvent *judderEventOut = nullptr);
303 
304     // Gets and resets data for the current freeze event.
305     FreezeEvent getAndResetFreezeEvent();
306 
307     // Gets and resets data for the current judder event.
308     JudderEvent getAndResetJudderEvent();
309 
310     // Retrieve the metrics.
311     const VideoRenderQualityMetrics &getMetrics();
312 
313     // Called when a change in codec state will result in a content discontinuity - e.g. flush.
314     void resetForDiscontinuity();
315 
316     // Clear out all metrics and tracking - e.g. codec reconfigured.
317     void clear();
318 
319 private:
320     // Tracking of frames that are pending to be rendered to the display.
321     struct FrameInfo {
322         int64_t contentTimeUs;
323         int64_t desiredRenderTimeUs;
324     };
325 
326     // Historic tracking of frame durations
327     struct FrameDurationUs {
328         static const int SIZE = 5;
329 
FrameDurationUsFrameDurationUs330         FrameDurationUs() {
331             for (int i = 0; i < SIZE; ++i) {
332                 durationUs[i] = -1;
333             }
334             priorTimestampUs = -1;
335         }
336 
337         int32_t &operator[](int index) {
338             assert(index < SIZE);
339             return durationUs[index];
340         }
341 
342         const int32_t &operator[](int index) const {
343             assert(index < SIZE);
344             return durationUs[index];
345         }
346 
347         // The duration of the past N frames.
348         int32_t durationUs[SIZE];
349 
350         // The timestamp of the previous frame.
351         int64_t priorTimestampUs;
352     };
353 
354     // Configure histograms for the metrics.
355     static void configureHistograms(VideoRenderQualityMetrics &m, const Configuration &c);
356 
357     // The current time in microseconds.
358     static int64_t nowUs();
359 
360     // A new frame has been processed, so update the frame durations based on the new frame
361     // timestamp.
362     static void updateFrameDurations(FrameDurationUs &durationUs, int64_t newTimestampUs);
363 
364     // Update a frame rate if, and only if, one can be detected.
365     static void updateFrameRate(float &frameRate, const FrameDurationUs &durationUs,
366                                 const Configuration &c);
367 
368     // Examine the past few frames to detect the frame rate based on each frame's render duration.
369     static float detectFrameRate(const FrameDurationUs &durationUs, const Configuration &c);
370 
371     // Determine whether or not 3:2 pulldowng for displaying 24fps content on 60Hz displays is
372     // occurring.
373     static bool is32pulldown(const FrameDurationUs &durationUs, const Configuration &c);
374 
375     // Process a frame freeze.
376     static void processFreeze(int64_t actualRenderTimeUs, int64_t lastRenderTimeUs,
377                               int64_t lastFreezeEndTimeUs, FreezeEvent &e,
378                               VideoRenderQualityMetrics &m, const Configuration &c,
379                               const TraceTriggerFn traceTriggerFn);
380 
381     // Retrieve a freeze event if an event just finished.
382     static void maybeCaptureFreezeEvent(int64_t actualRenderTimeUs, int64_t lastFreezeEndTimeUs,
383                                         FreezeEvent &e, const VideoRenderQualityMetrics & m,
384                                         const Configuration &c, FreezeEvent *freezeEventOut);
385 
386     // Compute a judder score for the previously-rendered frame.
387     static int64_t computePreviousJudderScore(const FrameDurationUs &actualRenderDurationUs,
388                                               const FrameDurationUs &contentRenderDurationUs,
389                                               const Configuration &c);
390 
391     // Process a frame judder.
392     static void processJudder(int32_t judderScore, int64_t judderTimeUs,
393                               int64_t lastJudderEndTimeUs,
394                               const FrameDurationUs &contentDurationUs,
395                               const FrameDurationUs &actualDurationUs, JudderEvent &e,
396                               VideoRenderQualityMetrics &m, const Configuration &c);
397 
398     // Retrieve a judder event if an event just finished.
399     static void maybeCaptureJudderEvent(int64_t actualRenderTimeUs, int64_t lastJudderEndTimeUs,
400                                         JudderEvent &e, const VideoRenderQualityMetrics & m,
401                                         const Configuration &c, JudderEvent *judderEventOut);
402 
403     // Trigger trace collection for video freeze.
404     static void triggerTrace();
405 
406     // Trigger collection of a Perfetto Always-On-Tracing (AOT) trace file for video freeze,
407     // triggerTimeUs is used as a throttle to avoid triggering multiple traces in a short time.
408     static void triggerTraceWithThrottle(TraceTriggerFn traceTriggerFn,
409                                          const Configuration &c, const int64_t triggerTimeUs);
410 
411     // Check to see if a discontinuity has occurred by examining the content time and the
412     // app-desired render time. If so, reset some internal state.
413     bool resetIfDiscontinuity(int64_t contentTimeUs, int64_t desiredRenderTimeUs);
414 
415     // Update the metrics because a skipped frame was detected.
416     void processMetricsForSkippedFrame(int64_t contentTimeUs);
417 
418     // Update the metrics because a dropped frame was detected.
419     void processMetricsForDroppedFrame(int64_t contentTimeUs, int64_t desiredRenderTimeUs);
420 
421     // Update the metrics because a rendered frame was detected.
422     void processMetricsForRenderedFrame(int64_t contentTimeUs, int64_t desiredRenderTimeUs,
423                                         int64_t actualRenderTimeUs,
424                                         FreezeEvent *freezeEventOut, JudderEvent *judderEventOut);
425 
426     // Configurable elements of the metrics algorithms.
427     const Configuration mConfiguration;
428 
429     // The function for triggering trace collection for video freeze.
430     const TraceTriggerFn mTraceTriggerFn;
431 
432     // Metrics are updated every time a frame event occurs - skipped, dropped, rendered.
433     VideoRenderQualityMetrics mMetrics;
434 
435     // The most recently processed timestamp referring to the position in the content stream.
436     int64_t mLastContentTimeUs;
437 
438     // The most recently processed timestamp referring to the wall clock time a frame was rendered.
439     int64_t mLastRenderTimeUs;
440 
441     // The most recent timestamp of the first frame rendered after the freeze.
442     int64_t mLastFreezeEndTimeUs;
443 
444     // The most recent timestamp of frame judder.
445     int64_t mLastJudderEndTimeUs;
446 
447     // The render duration of the playback.
448     int64_t mRenderDurationMs;
449 
450     // The duration of the content that was dropped.
451     int64_t mDroppedContentDurationUs;
452 
453     // The freeze event that's currently being tracked.
454     FreezeEvent mFreezeEvent;
455 
456     // The judder event that's currently being tracked.
457     JudderEvent mJudderEvent;
458 
459     // Frames skipped at the end of playback shouldn't really be considered skipped, therefore keep
460     // a list of the frames, and process them as skipped frames the next time a frame is rendered.
461     std::list<int64_t> mPendingSkippedFrameContentTimeUsList;
462 
463     // Since the system only signals when a frame is rendered, dropped frames are detected by
464     // checking to see if the next expected frame is rendered. If not, it is considered dropped.
465     std::queue<FrameInfo> mNextExpectedRenderedFrameQueue;
466 
467     // When B-frames are present in the stream, a P-frame will be queued before the B-frame even
468     // though it is rendered after. Therefore, the P-frame is held here and not inserted into
469     // mNextExpectedRenderedFrameQueue until it should be inserted to maintain render order.
470     int64_t mTunnelFrameQueuedContentTimeUs;
471 
472     // Frame durations derived from timestamps encoded into the content stream. These are the
473     // durations that each frame is supposed to be rendered for.
474     FrameDurationUs mContentFrameDurationUs;
475 
476     // Frame durations derived from timestamps passed in by the app, indicating the wall clock time
477     // at which the app would like to have the frame rendered.
478     FrameDurationUs mDesiredFrameDurationUs;
479 
480     // Frame durations derived from timestamps captured by the display subsystem, indicating the
481     // wall clock atime at which the frame is actually rendered.
482     FrameDurationUs mActualFrameDurationUs;
483 
484     // Token of async atrace for video frame dropped/skipped by the app.
485     int64_t mTraceFrameSkippedToken= -1;
486 };
487 
488 }  // namespace android
489 
490 #endif  // VIDEO_RENDER_QUALITY_TRACKER_H_
491