1 /*
2  * Copyright (C) 2021 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 package android.mediapc.cts.common;
18 
19 import static android.util.DisplayMetrics.DENSITY_400;
20 
21 import static org.junit.Assume.assumeTrue;
22 
23 import android.app.ActivityManager;
24 import android.content.Context;
25 import android.content.pm.PackageManager;
26 import android.hardware.display.DisplayManager;
27 import android.media.MediaCodec;
28 import android.media.MediaCodecInfo;
29 import android.media.MediaCodecInfo.VideoCapabilities.PerformancePoint;
30 import android.media.MediaFormat;
31 import android.os.Build;
32 import android.os.SystemProperties;
33 import android.util.DisplayMetrics;
34 import android.util.Log;
35 import android.view.Display;
36 
37 import androidx.test.platform.app.InstrumentationRegistry;
38 
39 import com.android.compatibility.common.util.ApiLevelUtil;
40 
41 import java.io.IOException;
42 import java.util.Arrays;
43 import java.util.Comparator;
44 import java.util.List;
45 import java.util.stream.Stream;
46 
47 /**
48  * Test utilities.
49  */
50 public class Utils {
51     private static final int sPc;
52 
53     private static final String TAG = "PerformanceClassTestUtils";
54     private static final String MEDIA_PERF_CLASS_KEY = "media-performance-class";
55 
56     public static final int DISPLAY_DPI;
57     public static final int MIN_DISPLAY_CANDIDATE_DPI = DENSITY_400;
58     public static final int DISPLAY_LONG_PIXELS;
59     public static final int MIN_DISPLAY_LONG_CANDIDATE_PIXELS = 1920;
60     public static final int DISPLAY_SHORT_PIXELS;
61     public static final int MIN_DISPLAY_SHORT_CANDIDATE_PIXELS = 1080;
62     public static final boolean IS_HDR;
63     public static final float HDR_DISPLAY_AVERAGE_LUMINANCE;
64 
65     public static final long TOTAL_MEMORY_MB;
66     // Media performance requires 6 GB minimum RAM, but keeping the following to 5 GB
67     // as activityManager.getMemoryInfo() returns around 5.4 GB on a 6 GB device.
68     public static final long MIN_MEMORY_PERF_CLASS_CANDIDATE_MB = 5 * 1024;
69     // Android T Media performance requires 8 GB min RAM, so setting lower as above
70     public static final long MIN_MEMORY_PERF_CLASS_T_MB = 6800;
71 
72     private static final boolean MEETS_AVC_CODEC_PRECONDITIONS;
73     static {
74         // with a default-media-performance-class that can be configured through a command line
75         // argument.
76         android.os.Bundle args;
77         try {
78             args = InstrumentationRegistry.getArguments();
79         } catch (Exception e) {
80             args = null;
81         }
82         if (args != null) {
83             String mediaPerfClassArg = args.getString(MEDIA_PERF_CLASS_KEY);
84             if (mediaPerfClassArg != null) {
Log.d(TAG, "Running the tests with performance class set to " + mediaPerfClassArg)85                 Log.d(TAG, "Running the tests with performance class set to " + mediaPerfClassArg);
86                 sPc = Integer.parseInt(mediaPerfClassArg);
87             } else {
88                 sPc = ApiLevelUtil.isAtLeast(Build.VERSION_CODES.S)
89                         ? Build.VERSION.MEDIA_PERFORMANCE_CLASS
90                         : SystemProperties.getInt("ro.odm.build.media_performance_class", 0);
91             }
Log.d(TAG, "performance class is " + sPc)92             Log.d(TAG, "performance class is " + sPc);
93         } else {
94             sPc = 0;
95         }
96 
97         Context context;
98         try {
99             context = InstrumentationRegistry.getInstrumentation().getContext();
100         } catch (Exception e) {
101             context = null;
102         }
103         // When used from ItsService, context will be null
104         if (context != null) {
105             DisplayManager displayManager = context.getSystemService(DisplayManager.class);
106             Display defaultDisplay = displayManager.getDisplay(Display.DEFAULT_DISPLAY);
107             Display.Mode maxResolutionDisplayMode =
108                     Arrays.stream(displayManager.getDisplays())
109                             .map(Display::getSupportedModes)
110                             .flatMap(Stream::of)
111                             .max(Comparator.comparing(Display.Mode::getPhysicalHeight))
112                             .orElseThrow(
113                                     () -> new RuntimeException("Failed to determine max height"));
114             int maxWidthPixels = maxResolutionDisplayMode.getPhysicalWidth();
115             int maxHeightPixels = maxResolutionDisplayMode.getPhysicalHeight();
116             DISPLAY_LONG_PIXELS = Math.max(maxWidthPixels, maxHeightPixels);
117             DISPLAY_SHORT_PIXELS = Math.min(maxWidthPixels, maxHeightPixels);
118 
119             int widthPixels = defaultDisplay.getMode().getPhysicalWidth();
120             int heightPixels = defaultDisplay.getMode().getPhysicalHeight();
121 
122             DisplayMetrics metrics = context.getResources().getDisplayMetrics();
123             final double widthInch = (double) widthPixels / (double) metrics.xdpi;
124             final double heightInch = (double) heightPixels / (double) metrics.ydpi;
125             final double diagonalInch = Math.sqrt(widthInch * widthInch + heightInch * heightInch);
126             final double maxDiagonalPixels =
127                     Math.sqrt(maxWidthPixels * maxWidthPixels + maxHeightPixels * maxHeightPixels);
128             // Use max of computed dpi and advertised dpi as these values differ in some devices.
129             DISPLAY_DPI = Math.max((int) (maxDiagonalPixels / diagonalInch),
130                     context.getResources().getConfiguration().densityDpi);
131 
132             IS_HDR = defaultDisplay.isHdr();
133             HDR_DISPLAY_AVERAGE_LUMINANCE =
134                 defaultDisplay.getHdrCapabilities().getDesiredMaxAverageLuminance();
135 
136             ActivityManager activityManager = context.getSystemService(ActivityManager.class);
137             ActivityManager.MemoryInfo memoryInfo = new ActivityManager.MemoryInfo();
138             activityManager.getMemoryInfo(memoryInfo);
139             TOTAL_MEMORY_MB = memoryInfo.totalMem / 1024 / 1024;
140         } else {
141             DISPLAY_DPI = 0;
142             DISPLAY_LONG_PIXELS = 0;
143             DISPLAY_SHORT_PIXELS = 0;
144             TOTAL_MEMORY_MB = 0;
145             IS_HDR = false;
146             HDR_DISPLAY_AVERAGE_LUMINANCE = 0;
147         }
148         MEETS_AVC_CODEC_PRECONDITIONS = meetsAvcCodecPreconditions();
149     }
150 
151     /**
152      * First defined media performance class.
153      */
154     private static final int FIRST_PERFORMANCE_CLASS = Build.VERSION_CODES.R;
155 
isRPerfClass()156     public static boolean isRPerfClass() {
157         return sPc == Build.VERSION_CODES.R;
158     }
159 
isSPerfClass()160     public static boolean isSPerfClass() {
161         return sPc == Build.VERSION_CODES.S;
162     }
163 
isTPerfClass()164     public static boolean isTPerfClass() {
165         return sPc == Build.VERSION_CODES.TIRAMISU;
166     }
167 
isBeforeTPerfClass()168     public static boolean isBeforeTPerfClass() {
169         return sPc < Build.VERSION_CODES.TIRAMISU;
170     }
171 
isUPerfClass()172     public static boolean isUPerfClass() {
173         return sPc == Build.VERSION_CODES.UPSIDE_DOWN_CAKE;
174     }
175 
isVPerfClass()176     public static boolean isVPerfClass() {
177         return sPc == Build.VERSION_CODES.VANILLA_ICE_CREAM;
178     }
179 
180     /**
181      * Latest defined media performance class.
182      */
183     private static final int LAST_PERFORMANCE_CLASS = Build.VERSION_CODES.VANILLA_ICE_CREAM;
184 
isHandheld()185     public static boolean isHandheld() {
186         // handheld nature is not exposed to package manager, for now
187         // we check for touchscreen and NOT watch and NOT tv
188         PackageManager pm =
189                 InstrumentationRegistry.getInstrumentation().getContext().getPackageManager();
190         return pm.hasSystemFeature(pm.FEATURE_TOUCHSCREEN)
191                 && !pm.hasSystemFeature(pm.FEATURE_WATCH)
192                 && !pm.hasSystemFeature(pm.FEATURE_TELEVISION)
193                 && !pm.hasSystemFeature(pm.FEATURE_AUTOMOTIVE);
194     }
195 
meetsAvcCodecPreconditions(boolean isEncoder)196     private static boolean meetsAvcCodecPreconditions(boolean isEncoder) {
197         // Latency tests need the following instances of codecs at 30 fps
198         // 1920x1080 encoder in MediaRecorder for load conditions
199         // 1920x1080 decoder and 1920x1080 encoder for load conditions
200         // 1920x1080 encoder for initialization test
201         // Since there is no way to know if encoder and decoder are supported concurrently at their
202         // maximum load, we will test the above combined requirements are met for both encoder and
203         // decoder (so a minimum of 4 instances required for both encoder and decoder)
204         int minInstancesRequired = 4;
205         int width = 1920;
206         int height = 1080;
207         double fps = 30 /* encoder for media recorder */
208                 + 30 /* 1080p decoder for transcoder */
209                 + 30 /* 1080p encoder for transcoder */
210                 + 30 /* 1080p encoder for latency test */;
211 
212         String avcMediaType = MediaFormat.MIMETYPE_VIDEO_AVC;
213         PerformancePoint pp1080p = new PerformancePoint(width, height, (int) fps);
214         MediaCodec codec;
215         try {
216             codec = isEncoder ? MediaCodec.createEncoderByType(avcMediaType) :
217                     MediaCodec.createDecoderByType(avcMediaType);
218         } catch (IOException e) {
219             Log.d(TAG, "Unable to create codec " + e);
220             return false;
221         }
222         MediaCodecInfo info = codec.getCodecInfo();
223         MediaCodecInfo.CodecCapabilities caps = info.getCapabilitiesForType(avcMediaType);
224         List<PerformancePoint> pps =
225                 caps.getVideoCapabilities().getSupportedPerformancePoints();
226         if (pps == null || pps.size() == 0) {
227             Log.w(TAG, info.getName() + " doesn't advertise performance points. Assuming codec "
228                     + "meets the requirements");
229             codec.release();
230             return true;
231         }
232         boolean supportsRequiredRate = false;
233         for (PerformancePoint pp : pps) {
234             if (pp.covers(pp1080p)) {
235                 supportsRequiredRate = true;
236             }
237         }
238 
239         boolean supportsRequiredSize = caps.getVideoCapabilities().isSizeSupported(width, height);
240         boolean supportsRequiredInstances = caps.getMaxSupportedInstances() >= minInstancesRequired;
241         codec.release();
242         Log.d(TAG, info.getName() + " supports required FPS : " + supportsRequiredRate
243                 + ", supports required size : " + supportsRequiredSize
244                 + ", supports required instances : " + supportsRequiredInstances);
245         return supportsRequiredRate && supportsRequiredSize && supportsRequiredInstances;
246     }
247 
meetsAvcCodecPreconditions()248     private static boolean meetsAvcCodecPreconditions() {
249         return meetsAvcCodecPreconditions(/* isEncoder */ true)
250                 && meetsAvcCodecPreconditions(/* isEncoder */ false);
251     }
252 
meetsMemoryPrecondition()253     private static boolean meetsMemoryPrecondition() {
254         if (isBeforeTPerfClass()) {
255             return TOTAL_MEMORY_MB >= MIN_MEMORY_PERF_CLASS_CANDIDATE_MB;
256         } else {
257             return TOTAL_MEMORY_MB >= MIN_MEMORY_PERF_CLASS_T_MB;
258         }
259     }
260 
getPerfClass()261     public static int getPerfClass() {
262         return sPc;
263     }
264 
isPerfClass()265     public static boolean isPerfClass() {
266         return sPc >= FIRST_PERFORMANCE_CLASS &&
267                sPc <= LAST_PERFORMANCE_CLASS;
268     }
269 
meetsPerformanceClassPreconditions()270     public static boolean meetsPerformanceClassPreconditions() {
271         if (isPerfClass()) {
272             return true;
273         }
274 
275         // If device doesn't advertise performance class, check if this can be ruled out as a
276         // candidate for performance class tests.
277         if (!isHandheld()
278                 || !meetsMemoryPrecondition()
279                 || DISPLAY_DPI < MIN_DISPLAY_CANDIDATE_DPI
280                 || DISPLAY_LONG_PIXELS < MIN_DISPLAY_LONG_CANDIDATE_PIXELS
281                 || DISPLAY_SHORT_PIXELS < MIN_DISPLAY_SHORT_CANDIDATE_PIXELS
282                 || !MEETS_AVC_CODEC_PRECONDITIONS) {
283             return false;
284         }
285         return true;
286     }
287 
assumeDeviceMeetsPerformanceClassPreconditions()288     public static void assumeDeviceMeetsPerformanceClassPreconditions() {
289         assumeTrue(
290                 "Test skipped because the device does not meet the hardware requirements for "
291                         + "performance class.",
292                 meetsPerformanceClassPreconditions());
293     }
294 }
295