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 android.cts.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         log.addValue("round", round, ResultType.NEUTRAL, ResultUnit.NONE);
58         log.addValue("codec_name", codecName, ResultType.NEUTRAL, ResultUnit.NONE);
59         log.addValue("mime_type", configFormat.getString(MediaFormat.KEY_MIME),
60                 ResultType.NEUTRAL, ResultUnit.NONE);
61         log.addValue("width", configFormat.getInteger(MediaFormat.KEY_WIDTH),
62                 ResultType.NEUTRAL, ResultUnit.NONE);
63         log.addValue("height", configFormat.getInteger(MediaFormat.KEY_HEIGHT),
64                 ResultType.NEUTRAL, ResultUnit.NONE);
65         log.addValue("config_format", formatForReport(configFormat),
66                 ResultType.NEUTRAL, ResultUnit.NONE);
67         log.addValue("input_format", formatForReport(inputFormat),
68                 ResultType.NEUTRAL, ResultUnit.NONE);
69         log.addValue("output_format", formatForReport(outputFormat),
70                 ResultType.NEUTRAL, ResultUnit.NONE);
71 
72         message += " codec=" + codecName + " round=" + round + " configFormat=" + configFormat
73                 + " inputFormat=" + inputFormat + " outputFormat=" + outputFormat;
74         return message;
75     }
76 
77     /**
78      * Adds performance statistics based on the raw |stats| to |log|. Also prints the same into
79      * logcat. Returns the "final fps" value.
80      */
addPerformanceStatsToLog( DeviceReportLog log, MediaUtils.Stats durationsUsStats, String message)81     public static double addPerformanceStatsToLog(
82             DeviceReportLog log, MediaUtils.Stats durationsUsStats, String message) {
83 
84         MediaUtils.Stats frameAvgUsStats =
85             durationsUsStats.movingAverage(MOVING_AVERAGE_NUM_FRAMES);
86         log.addValue(
87                 "window_frames", MOVING_AVERAGE_NUM_FRAMES, ResultType.NEUTRAL, ResultUnit.COUNT);
88         logPerformanceStats(log, frameAvgUsStats, "frame_avg_stats",
89                 message + " window=" + MOVING_AVERAGE_NUM_FRAMES);
90 
91         MediaUtils.Stats timeAvgUsStats =
92             durationsUsStats.movingAverageOverSum(MOVING_AVERAGE_WINDOW_MS * 1000);
93         log.addValue("window_time", MOVING_AVERAGE_WINDOW_MS, ResultType.NEUTRAL, ResultUnit.MS);
94         double fps = logPerformanceStats(log, timeAvgUsStats, "time_avg_stats",
95                 message + " windowMs=" + MOVING_AVERAGE_WINDOW_MS);
96 
97         log.setSummary("fps", fps, ResultType.HIGHER_BETTER, ResultUnit.FPS);
98         return fps;
99     }
100 
101     /**
102      * Adds performance statistics based on the processed |stats| to |log| using |prefix|.
103      * Also prints the same into logcat using |message| as the base message. Returns the fps value
104      * for |stats|. |prefix| must be lowercase alphanumeric underscored format.
105      */
logPerformanceStats( DeviceReportLog log, MediaUtils.Stats statsUs, String prefix, String message)106     private static double logPerformanceStats(
107             DeviceReportLog log, MediaUtils.Stats statsUs, String prefix, String message) {
108         final String[] labels = {
109             "min", "p5", "p10", "p20", "p30", "p40", "p50", "p60", "p70", "p80", "p90", "p95", "max"
110         };
111         final double[] points = {
112              0,     5,    10,    20,    30,    40,    50,    60,    70,    80,    90,    95,    100
113         };
114 
115         int num = statsUs.getNum();
116         long avg = Math.round(statsUs.getAverage());
117         long stdev = Math.round(statsUs.getStdev());
118         log.addValue(prefix + "_num", num, ResultType.NEUTRAL, ResultUnit.COUNT);
119         log.addValue(prefix + "_avg", avg / 1000., ResultType.LOWER_BETTER, ResultUnit.MS);
120         log.addValue(prefix + "_stdev", stdev / 1000., ResultType.LOWER_BETTER, ResultUnit.MS);
121         message += " num=" + num + " avg=" + avg + " stdev=" + stdev;
122         final double[] percentiles = statsUs.getPercentiles(points);
123         for (int i = 0; i < labels.length; ++i) {
124             long p = Math.round(percentiles[i]);
125             message += " " + labels[i] + "=" + p;
126             log.addValue(prefix + "_" + labels[i], p / 1000., ResultType.NEUTRAL, ResultUnit.MS);
127         }
128 
129         // print result to logcat in case test aborts before logs are written
130         Log.i(TAG, message);
131 
132         return 1e6 / percentiles[points.length - 2];
133     }
134 
135     /** Verifies |measuredFps| against reported achievable rates. Returns null if at least
136      *  one measurement falls within the margins of the reported range. Otherwise, returns
137      *  an error message to display.*/
verifyAchievableFrameRates( String name, String mime, int w, int h, double... measuredFps)138     public static String verifyAchievableFrameRates(
139             String name, String mime, int w, int h, double... measuredFps) {
140         Range<Double> reported =
141             MediaUtils.getVideoCapabilities(name, mime).getAchievableFrameRatesFor(w, h);
142         String kind = "achievable frame rates for " + name + " " + mime + " " + w + "x" + h;
143         if (reported == null) {
144             return "Failed to get " + kind;
145         }
146         double lowerBoundary1 = reported.getLower() / FRAMERATE_TOLERANCE;
147         double upperBoundary1 = reported.getUpper() * FRAMERATE_TOLERANCE;
148         double lowerBoundary2 = reported.getUpper() / Math.pow(FRAMERATE_TOLERANCE, 2);
149         double upperBoundary2 = reported.getLower() * Math.pow(FRAMERATE_TOLERANCE, 2);
150         Log.d(TAG, name + " " + mime + " " + w + "x" + h +
151                 " lowerBoundary1 " + lowerBoundary1 + " upperBoundary1 " + upperBoundary1 +
152                 " lowerBoundary2 " + lowerBoundary2 + " upperBoundary2 " + upperBoundary2 +
153                 " measured " + Arrays.toString(measuredFps));
154 
155         for (double measured : measuredFps) {
156             if (measured >= lowerBoundary1 && measured <= upperBoundary1
157                     && measured >= lowerBoundary2 && measured <= upperBoundary2) {
158                 return null;
159             }
160         }
161 
162         return "Expected " + kind + ": " + reported + ".\n"
163                 + "Measured frame rate: " + Arrays.toString(measuredFps) + ".\n";
164     }
165 }
166