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