Home | History | Annotate | Download | only in util
      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. */
     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      */
     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      */
     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      */
    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.*/
    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