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