1 /* 2 * Copyright 2016 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 package com.android.compatibility.common.util; 17 18 import android.media.MediaFormat; 19 import android.util.Range; 20 21 import com.android.compatibility.common.util.DeviceReportLog; 22 import com.android.compatibility.common.util.ResultType; 23 import com.android.compatibility.common.util.ResultUnit; 24 25 import java.util.Arrays; 26 import android.util.Log; 27 28 public class MediaPerfUtils { 29 private static final String TAG = "MediaPerfUtils"; 30 31 private static final int MOVING_AVERAGE_NUM_FRAMES = 10; 32 private static final int MOVING_AVERAGE_WINDOW_MS = 1000; 33 34 // allow a variance of 2x for measured frame rates (e.g. half of lower-limit to double of 35 // upper-limit of the published values). Also allow an extra 10% margin. This also acts as 36 // a limit for the size of the published rates (e.g. upper-limit / lower-limit <= tolerance). 37 private static final double FRAMERATE_TOLERANCE = 2.0 * 1.1; 38 39 /* 40 * ------------------ HELPER METHODS FOR ACHIEVABLE FRAME RATES ------------------ 41 */ 42 43 /** removes brackets from format to be included in JSON. */ formatForReport(MediaFormat format)44 private static String formatForReport(MediaFormat format) { 45 String asString = "" + format; 46 return asString.substring(1, asString.length() - 1); 47 } 48 49 /** 50 * Adds performance header info to |log| for |codecName|, |round|, |configFormat|, |inputFormat| 51 * and |outputFormat|. Also appends same to |message| and returns the resulting base message 52 * for logging purposes. 53 */ addPerformanceHeadersToLog( DeviceReportLog log, String message, int round, String codecName, MediaFormat configFormat, MediaFormat inputFormat, MediaFormat outputFormat)54 public static String addPerformanceHeadersToLog( 55 DeviceReportLog log, String message, int round, String codecName, 56 MediaFormat configFormat, MediaFormat inputFormat, MediaFormat outputFormat) { 57 String mime = configFormat.getString(MediaFormat.KEY_MIME); 58 int width = configFormat.getInteger(MediaFormat.KEY_WIDTH); 59 int height = configFormat.getInteger(MediaFormat.KEY_HEIGHT); 60 61 log.addValue("round", round, ResultType.NEUTRAL, ResultUnit.NONE); 62 log.addValue("codec_name", codecName, ResultType.NEUTRAL, ResultUnit.NONE); 63 log.addValue("mime_type", mime, ResultType.NEUTRAL, ResultUnit.NONE); 64 log.addValue("width", width, ResultType.NEUTRAL, ResultUnit.NONE); 65 log.addValue("height", height, ResultType.NEUTRAL, ResultUnit.NONE); 66 log.addValue("config_format", formatForReport(configFormat), 67 ResultType.NEUTRAL, ResultUnit.NONE); 68 log.addValue("input_format", formatForReport(inputFormat), 69 ResultType.NEUTRAL, ResultUnit.NONE); 70 log.addValue("output_format", formatForReport(outputFormat), 71 ResultType.NEUTRAL, ResultUnit.NONE); 72 73 message += " codec=" + codecName + " round=" + round + " configFormat=" + configFormat 74 + " inputFormat=" + inputFormat + " outputFormat=" + outputFormat; 75 76 Range<Double> reported = 77 MediaUtils.getVideoCapabilities(codecName, mime) 78 .getAchievableFrameRatesFor(width, height); 79 if (reported != null) { 80 log.addValue("reported_low", reported.getLower(), ResultType.NEUTRAL, ResultUnit.FPS); 81 log.addValue("reported_high", reported.getUpper(), ResultType.NEUTRAL, ResultUnit.FPS); 82 message += " reported=" + reported.getLower() + "-" + reported.getUpper(); 83 } 84 85 return message; 86 } 87 88 /** 89 * Adds performance statistics based on the raw |stats| to |log|. Also prints the same into 90 * logcat. Returns the "final fps" value. 91 */ addPerformanceStatsToLog( DeviceReportLog log, MediaUtils.Stats durationsUsStats, String message)92 public static double addPerformanceStatsToLog( 93 DeviceReportLog log, MediaUtils.Stats durationsUsStats, String message) { 94 95 MediaUtils.Stats frameAvgUsStats = 96 durationsUsStats.movingAverage(MOVING_AVERAGE_NUM_FRAMES); 97 log.addValue( 98 "window_frames", MOVING_AVERAGE_NUM_FRAMES, ResultType.NEUTRAL, ResultUnit.COUNT); 99 logPerformanceStats(log, frameAvgUsStats, "frame_avg_stats", 100 message + " window=" + MOVING_AVERAGE_NUM_FRAMES); 101 102 MediaUtils.Stats timeAvgUsStats = 103 durationsUsStats.movingAverageOverSum(MOVING_AVERAGE_WINDOW_MS * 1000); 104 log.addValue("window_time", MOVING_AVERAGE_WINDOW_MS, ResultType.NEUTRAL, ResultUnit.MS); 105 double fps = logPerformanceStats(log, timeAvgUsStats, "time_avg_stats", 106 message + " windowMs=" + MOVING_AVERAGE_WINDOW_MS); 107 108 log.setSummary("fps", fps, ResultType.HIGHER_BETTER, ResultUnit.FPS); 109 return fps; 110 } 111 112 /** 113 * Adds performance statistics based on the processed |stats| to |log| using |prefix|. 114 * Also prints the same into logcat using |message| as the base message. Returns the fps value 115 * for |stats|. |prefix| must be lowercase alphanumeric underscored format. 116 */ logPerformanceStats( DeviceReportLog log, MediaUtils.Stats statsUs, String prefix, String message)117 private static double logPerformanceStats( 118 DeviceReportLog log, MediaUtils.Stats statsUs, String prefix, String message) { 119 final String[] labels = { 120 "min", "p5", "p10", "p20", "p30", "p40", "p50", "p60", "p70", "p80", "p90", "p95", "max" 121 }; 122 final double[] points = { 123 0, 5, 10, 20, 30, 40, 50, 60, 70, 80, 90, 95, 100 124 }; 125 126 int num = statsUs.getNum(); 127 long avg = Math.round(statsUs.getAverage()); 128 long stdev = Math.round(statsUs.getStdev()); 129 log.addValue(prefix + "_num", num, ResultType.NEUTRAL, ResultUnit.COUNT); 130 log.addValue(prefix + "_avg", avg / 1000., ResultType.LOWER_BETTER, ResultUnit.MS); 131 log.addValue(prefix + "_stdev", stdev / 1000., ResultType.LOWER_BETTER, ResultUnit.MS); 132 message += " num=" + num + " avg=" + avg + " stdev=" + stdev; 133 final double[] percentiles = statsUs.getPercentiles(points); 134 for (int i = 0; i < labels.length; ++i) { 135 long p = Math.round(percentiles[i]); 136 message += " " + labels[i] + "=" + p; 137 log.addValue(prefix + "_" + labels[i], p / 1000., ResultType.NEUTRAL, ResultUnit.MS); 138 } 139 140 // print result to logcat in case test aborts before logs are written 141 Log.i(TAG, message); 142 143 return 1e6 / percentiles[points.length - 2]; 144 } 145 146 /** Verifies |measuredFps| against reported achievable rates. Returns null if at least 147 * one measurement falls within the margins of the reported range. Otherwise, returns 148 * an error message to display.*/ verifyAchievableFrameRates( String name, String mime, int w, int h, double... measuredFps)149 public static String verifyAchievableFrameRates( 150 String name, String mime, int w, int h, double... measuredFps) { 151 Range<Double> reported = 152 MediaUtils.getVideoCapabilities(name, mime).getAchievableFrameRatesFor(w, h); 153 String kind = "achievable frame rates for " + name + " " + mime + " " + w + "x" + h; 154 if (reported == null) { 155 return "Failed to get " + kind; 156 } 157 double lowerBoundary1 = reported.getLower() / FRAMERATE_TOLERANCE; 158 double upperBoundary1 = reported.getUpper() * FRAMERATE_TOLERANCE; 159 double lowerBoundary2 = reported.getUpper() / Math.pow(FRAMERATE_TOLERANCE, 2); 160 double upperBoundary2 = reported.getLower() * Math.pow(FRAMERATE_TOLERANCE, 2); 161 Log.d(TAG, name + " " + mime + " " + w + "x" + h + 162 " lowerBoundary1 " + lowerBoundary1 + " upperBoundary1 " + upperBoundary1 + 163 " lowerBoundary2 " + lowerBoundary2 + " upperBoundary2 " + upperBoundary2 + 164 " measured " + Arrays.toString(measuredFps)); 165 166 for (double measured : measuredFps) { 167 if (measured >= lowerBoundary1 && measured <= upperBoundary1 168 && measured >= lowerBoundary2 && measured <= upperBoundary2) { 169 return null; 170 } 171 } 172 173 return "Expected " + kind + ": " + reported + ".\n" 174 + "Measured frame rate: " + Arrays.toString(measuredFps) + ".\n"; 175 } 176 177 /** Verifies |requestedFps| does not exceed reported achievable rates. 178 * Returns null if *ALL* requested rates are claimed to be achievable. 179 * Otherwise, returns a diagnostic explaining why it's not achievable. 180 * (one of the rates was too fast, we don't have achievability information, etc). 181 * 182 * we're looking for 90% confidence, which is documented as being: 183 * "higher than half of the lower limit at least 90% of the time in tested configurations" 184 * 185 * NB: we only invoke this for the SW codecs; we use performance point info for the 186 * hardware codecs. 187 * */ areAchievableFrameRates( String name, String mime, int w, int h, double... requestedFps)188 public static String areAchievableFrameRates( 189 String name, String mime, int w, int h, double... requestedFps) { 190 Range<Double> reported = 191 MediaUtils.getVideoCapabilities(name, mime).getAchievableFrameRatesFor(w, h); 192 String kind = "achievable frame rates for " + name + " " + mime + " " + w + "x" + h; 193 if (reported == null) { 194 return "Failed to get " + kind; 195 } 196 197 double confidence90 = reported.getLower() / 2.0; 198 199 Log.d(TAG, name + " " + mime + " " + w + "x" + h + 200 " lower " + reported.getLower() + " 90% confidence " + confidence90 + 201 " requested " + Arrays.toString(requestedFps)); 202 203 // if *any* of them are too fast, we say no. 204 for (double requested : requestedFps) { 205 if (requested > confidence90) { 206 return "Expected " + kind + ": " + reported + ", 90% confidence: " + confidence90 207 + ".\n" 208 + "Requested frame rate: " + Arrays.toString(requestedFps) + ".\n"; 209 } 210 } 211 return null; 212 } 213 } 214