1 /* 2 * Copyright (C) 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.media.decoder.cts; 17 18 import static junit.framework.TestCase.assertTrue; 19 20 import static org.junit.Assert.fail; 21 22 import android.annotation.TargetApi; 23 import android.graphics.Bitmap; 24 import android.media.MediaFormat; 25 import android.media.cts.MediaHeavyPresubmitTest; 26 import android.media.cts.TestArgs; 27 import android.media.cts.TestUtils; 28 import android.os.Build; 29 import android.os.Environment; 30 import android.platform.test.annotations.AppModeFull; 31 import android.util.Log; 32 import android.view.View; 33 34 import com.android.compatibility.common.util.ApiLevelUtil; 35 import com.android.compatibility.common.util.MediaUtils; 36 37 import org.junit.After; 38 import org.junit.Test; 39 import org.junit.runner.RunWith; 40 import org.junit.runners.Parameterized; 41 import org.junit.runners.Parameterized.Parameters; 42 43 import java.io.File; 44 import java.io.FileOutputStream; 45 import java.io.IOException; 46 import java.lang.reflect.Field; 47 import java.util.ArrayList; 48 import java.util.Collection; 49 import java.util.List; 50 import java.util.regex.Matcher; 51 import java.util.regex.Pattern; 52 53 @TargetApi(24) 54 @RunWith(Parameterized.class) 55 @MediaHeavyPresubmitTest 56 @AppModeFull(reason = "There should be no instant apps specific behavior related to accuracy") 57 public class DecodeAccuracyTest extends DecodeAccuracyTestBase { 58 59 private static final boolean IS_AT_LEAST_U = ApiLevelUtil.isAfter(Build.VERSION_CODES.TIRAMISU) 60 || ApiLevelUtil.codenameEquals("UpsideDownCake"); 61 private static final boolean IS_BEFORE_U = !IS_AT_LEAST_U; 62 63 private static final String TAG = DecodeAccuracyTest.class.getSimpleName(); 64 private static final Field[] fields = R.raw.class.getFields(); 65 private static final int ALLOWED_GREATEST_PIXEL_DIFFERENCE = 105; 66 private static final int OFFSET = 10; 67 private static final long PER_TEST_TIMEOUT_MS = 60000; 68 private static final String[] VIDEO_FILES = { 69 // 144p 70 "video_decode_accuracy_and_capability-h264_256x108_30fps.mp4", 71 "video_decode_accuracy_and_capability-h264_256x144_30fps.mp4", 72 "video_decode_accuracy_and_capability-h264_192x144_30fps.mp4", 73 "video_decode_accuracy_and_capability-h264_82x144_30fps.mp4", 74 "video_decode_accuracy_and_capability-vp9_256x108_30fps.webm", 75 "video_decode_accuracy_and_capability-vp9_256x144_30fps.webm", 76 "video_decode_accuracy_and_capability-vp9_192x144_30fps.webm", 77 "video_decode_accuracy_and_capability-vp9_82x144_30fps.webm", 78 // 240p 79 "video_decode_accuracy_and_capability-h264_426x182_30fps.mp4", 80 "video_decode_accuracy_and_capability-h264_426x240_30fps.mp4", 81 "video_decode_accuracy_and_capability-h264_320x240_30fps.mp4", 82 "video_decode_accuracy_and_capability-h264_136x240_30fps.mp4", 83 "video_decode_accuracy_and_capability-vp9_426x182_30fps.webm", 84 "video_decode_accuracy_and_capability-vp9_426x240_30fps.webm", 85 "video_decode_accuracy_and_capability-vp9_320x240_30fps.webm", 86 "video_decode_accuracy_and_capability-vp9_136x240_30fps.webm", 87 // 360p 88 "video_decode_accuracy_and_capability-h264_640x272_30fps.mp4", 89 "video_decode_accuracy_and_capability-h264_640x360_30fps.mp4", 90 "video_decode_accuracy_and_capability-h264_480x360_30fps.mp4", 91 "video_decode_accuracy_and_capability-h264_202x360_30fps.mp4", 92 "video_decode_accuracy_and_capability-vp9_640x272_30fps.webm", 93 "video_decode_accuracy_and_capability-vp9_640x360_30fps.webm", 94 "video_decode_accuracy_and_capability-vp9_480x360_30fps.webm", 95 "video_decode_accuracy_and_capability-vp9_202x360_30fps.webm", 96 // 480p 97 "video_decode_accuracy_and_capability-h264_854x362_30fps.mp4", 98 "video_decode_accuracy_and_capability-h264_854x480_30fps.mp4", 99 "video_decode_accuracy_and_capability-h264_640x480_30fps.mp4", 100 "video_decode_accuracy_and_capability-h264_270x480_30fps.mp4", 101 "video_decode_accuracy_and_capability-vp9_854x362_30fps.webm", 102 "video_decode_accuracy_and_capability-vp9_854x480_30fps.webm", 103 "video_decode_accuracy_and_capability-vp9_640x480_30fps.webm", 104 "video_decode_accuracy_and_capability-vp9_270x480_30fps.webm", 105 // 720p 106 "video_decode_accuracy_and_capability-h264_1280x544_30fps.mp4", 107 "video_decode_accuracy_and_capability-h264_1280x720_30fps.mp4", 108 "video_decode_accuracy_and_capability-h264_960x720_30fps.mp4", 109 "video_decode_accuracy_and_capability-h264_406x720_30fps.mp4", 110 "video_decode_accuracy_and_capability-vp9_1280x544_30fps.webm", 111 "video_decode_accuracy_and_capability-vp9_1280x720_30fps.webm", 112 "video_decode_accuracy_and_capability-vp9_960x720_30fps.webm", 113 "video_decode_accuracy_and_capability-vp9_406x720_30fps.webm", 114 // 1080p 115 "video_decode_accuracy_and_capability-h264_1920x818_30fps.mp4", 116 "video_decode_accuracy_and_capability-h264_1920x1080_30fps.mp4", 117 "video_decode_accuracy_and_capability-h264_1440x1080_30fps.mp4", 118 "video_decode_accuracy_and_capability-h264_608x1080_30fps.mp4", 119 "video_decode_accuracy_and_capability-vp9_1920x818_30fps.webm", 120 "video_decode_accuracy_and_capability-vp9_1920x1080_30fps.webm", 121 "video_decode_accuracy_and_capability-vp9_1440x1080_30fps.webm", 122 "video_decode_accuracy_and_capability-vp9_608x1080_30fps.webm", 123 // 1440p 124 "video_decode_accuracy_and_capability-h264_2560x1090_30fps.mp4", 125 "video_decode_accuracy_and_capability-h264_2560x1440_30fps.mp4", 126 "video_decode_accuracy_and_capability-h264_1920x1440_30fps.mp4", 127 "video_decode_accuracy_and_capability-h264_810x1440_30fps.mp4", 128 "video_decode_accuracy_and_capability-vp9_2560x1090_30fps.webm", 129 "video_decode_accuracy_and_capability-vp9_2560x1440_30fps.webm", 130 "video_decode_accuracy_and_capability-vp9_1920x1440_30fps.webm", 131 "video_decode_accuracy_and_capability-vp9_810x1440_30fps.webm", 132 // 2160p 133 "video_decode_accuracy_and_capability-h264_3840x1634_30fps.mp4", 134 "video_decode_accuracy_and_capability-h264_3840x2160_30fps.mp4", 135 "video_decode_accuracy_and_capability-h264_2880x2160_30fps.mp4", 136 "video_decode_accuracy_and_capability-h264_1216x2160_30fps.mp4", 137 "video_decode_accuracy_and_capability-vp9_3840x1634_30fps.webm", 138 "video_decode_accuracy_and_capability-vp9_3840x2160_30fps.webm", 139 "video_decode_accuracy_and_capability-vp9_2880x2160_30fps.webm", 140 "video_decode_accuracy_and_capability-vp9_1216x2160_30fps.webm", 141 // cropped 142 "video_decode_with_cropping-h264_520x360_30fps.mp4", 143 "video_decode_with_cropping-vp9_520x360_30fps.webm" 144 }; 145 146 private static final String INP_PREFIX = WorkDir.getMediaDirString() + 147 "assets/decode_accuracy/"; 148 149 private View videoView; 150 private VideoViewFactory videoViewFactory; 151 private String testName; 152 private String fileName; 153 private String decoderName; 154 private String methodName; 155 private SimplePlayer player; 156 DecodeAccuracyTest(String decoderName, String fileName, String testName)157 public DecodeAccuracyTest(String decoderName, String fileName, String testName) { 158 this.testName = testName; 159 this.fileName = fileName; 160 this.decoderName = decoderName; 161 } 162 163 @After 164 @Override tearDown()165 public void tearDown() throws Exception { 166 if (player != null) { 167 player.release(); 168 } 169 if (videoView != null) { 170 getHelper().cleanUpView(videoView); 171 } 172 if (videoViewFactory != null) { 173 videoViewFactory.release(); 174 } 175 super.tearDown(); 176 } 177 178 @Parameters(name = "{index}({0}_{2})") input()179 public static Collection<Object[]> input() throws IOException { 180 final List<Object[]> testParams = new ArrayList<>(); 181 for (String file : VIDEO_FILES) { 182 Pattern regex = Pattern.compile("^\\w+-(\\w+)_\\d+fps\\.\\w+"); 183 Matcher matcher = regex.matcher(file); 184 String testName = ""; 185 if (matcher.matches()) { 186 testName = matcher.group(1); 187 } 188 MediaFormat mediaFormat = 189 MediaUtils.getTrackFormatForResource(INP_PREFIX + file, "video"); 190 String mediaType = mediaFormat.getString(MediaFormat.KEY_MIME); 191 if (TestArgs.shouldSkipMediaType(mediaType)) { 192 continue; 193 } 194 String[] componentNames = MediaUtils.getDecoderNamesForMime(mediaType); 195 for (String componentName : componentNames) { 196 if (!MediaUtils.supports(componentName, mediaFormat)) { 197 continue; 198 } 199 200 // we only test the first codec that supports the format. 201 // if that codec is not tested in this mode, we do NOT proceed to 202 // later codecs in the list. 203 // 204 // this means: 205 // in CTS, we'll test the HW codec. 206 // in MCTS/MTS, we'll see the HW codec, skip testing it, but NOT fall through 207 // to test any of the module-homed codecs. 208 209 if (!TestArgs.shouldSkipCodec(componentName)) { 210 testParams.add(new Object[] {componentName, file, testName}); 211 } 212 213 // ignore any further codes that might handle this mediatype, even if we 214 // chose not to test this first one. 215 // NB: remove this break to change from "test first codec" to "test all codecs" 216 break; 217 } 218 } 219 return testParams; 220 } 221 222 @Test(timeout = PER_TEST_TIMEOUT_MS) testGLViewDecodeAccuracy()223 public void testGLViewDecodeAccuracy() throws Exception { 224 this.methodName = "testGLViewDecodeAccuracy"; 225 runTest(new GLSurfaceViewFactory(), new VideoFormat(fileName), decoderName); 226 } 227 228 @Test(timeout = PER_TEST_TIMEOUT_MS) testGLViewLargerHeightDecodeAccuracy()229 public void testGLViewLargerHeightDecodeAccuracy() throws Exception { 230 this.methodName = "testGLViewLargerHeightDecodeAccuracy"; 231 runTest(new GLSurfaceViewFactory(), getLargerHeightVideoFormat(new VideoFormat(fileName)), 232 decoderName); 233 } 234 235 @Test(timeout = PER_TEST_TIMEOUT_MS) testGLViewLargerWidthDecodeAccuracy()236 public void testGLViewLargerWidthDecodeAccuracy() throws Exception { 237 this.methodName = "testGLViewLargerWidthDecodeAccuracy"; 238 runTest(new GLSurfaceViewFactory(), getLargerWidthVideoFormat(new VideoFormat(fileName)), 239 decoderName); 240 } 241 242 @Test(timeout = PER_TEST_TIMEOUT_MS) testSurfaceViewVideoDecodeAccuracy()243 public void testSurfaceViewVideoDecodeAccuracy() throws Exception { 244 this.methodName = "testSurfaceViewVideoDecodeAccuracy"; 245 runTest(new SurfaceViewFactory(), new VideoFormat(fileName), decoderName); 246 } 247 248 @Test(timeout = PER_TEST_TIMEOUT_MS) testSurfaceViewLargerHeightDecodeAccuracy()249 public void testSurfaceViewLargerHeightDecodeAccuracy() throws Exception { 250 this.methodName = "testSurfaceViewLargerHeightDecodeAccuracy"; 251 runTest(new SurfaceViewFactory(), getLargerHeightVideoFormat(new VideoFormat(fileName)), 252 decoderName); 253 } 254 255 @Test(timeout = PER_TEST_TIMEOUT_MS) testSurfaceViewLargerWidthDecodeAccuracy()256 public void testSurfaceViewLargerWidthDecodeAccuracy() throws Exception { 257 this.methodName = "testSurfaceViewLargerWidthDecodeAccuracy"; 258 runTest(new SurfaceViewFactory(), getLargerWidthVideoFormat(new VideoFormat(fileName)), 259 decoderName); 260 } 261 runTest(VideoViewFactory videoViewFactory, VideoFormat vf, String decoderName)262 private void runTest(VideoViewFactory videoViewFactory, VideoFormat vf, String decoderName) { 263 Log.i(TAG, "Running test for " + vf.toPrettyString()); 264 if (!MediaUtils.canDecodeVideo(vf.getMimeType(), vf.getWidth(), vf.getHeight(), 30)) { 265 MediaUtils.skipTest(TAG, "No supported codec is found."); 266 return; 267 } 268 this.videoViewFactory = checkNotNull(videoViewFactory); 269 this.videoView = videoViewFactory.createView(getHelper().getContext()); 270 final int maxRetries = 3; 271 for (int retry = 1; retry <= maxRetries; retry++) { 272 // If view is intended and available to display. 273 if (videoView != null) { 274 getHelper().generateView(videoView); 275 } 276 try { 277 videoViewFactory.waitForViewIsAvailable(); 278 break; 279 } catch (Exception exception) { 280 Log.e(TAG, exception.getMessage()); 281 if (retry == maxRetries) { 282 fail("Timeout waiting for a valid surface."); 283 } else { 284 Log.w(TAG, "Try again..."); 285 bringActivityToFront(); 286 } 287 } 288 } 289 final int golden = getGoldenId(vf.getDescription(), vf.getOriginalSize()); 290 assertTrue("No golden found.", golden != 0); 291 decodeVideo(vf, videoViewFactory, decoderName); 292 validateResult(vf, videoViewFactory.getVideoViewSnapshot(), golden, decoderName); 293 } 294 decodeVideo(VideoFormat videoFormat, VideoViewFactory videoViewFactory, String decoderName)295 private void decodeVideo(VideoFormat videoFormat, VideoViewFactory videoViewFactory, 296 String decoderName) { 297 this.player = new SimplePlayer(getHelper().getContext(), decoderName); 298 final SimplePlayer.PlayerResult playerResult = player.decodeVideoFrames( 299 videoViewFactory.getSurface(), videoFormat, 10); 300 assertTrue(playerResult.getFailureMessage(), playerResult.isSuccess()); 301 } 302 validateResult( VideoFormat videoFormat, VideoViewSnapshot videoViewSnapshot, int goldenId, String decoderName)303 private void validateResult( 304 VideoFormat videoFormat, VideoViewSnapshot videoViewSnapshot, int goldenId, 305 String decoderName) { 306 final Bitmap result = checkNotNull("The expected bitmap from snapshot is null", 307 getHelper().generateBitmapFromVideoViewSnapshot(videoViewSnapshot)); 308 final Bitmap golden = getHelper().generateBitmapFromImageResourceId(goldenId); 309 310 int ignorePixels = 0; 311 if (IS_BEFORE_U && TestUtils.isTestingModules()) { 312 if (TestUtils.isMainlineCodec(decoderName)) { 313 // some older systems don't give proper behavior at the edges (in system code). 314 // while we can't fix the behavior at the edges, we can verify that the rest 315 // of the image is within tolerance. b/256807044 316 ignorePixels = 1; 317 } 318 } 319 final BitmapCompare.Difference difference = BitmapCompare.computeMinimumDifference( 320 result, golden, ignorePixels, videoFormat.getOriginalWidth(), 321 videoFormat.getOriginalHeight()); 322 323 if (difference.greatestPixelDifference > ALLOWED_GREATEST_PIXEL_DIFFERENCE) { 324 /* save failing file */ 325 File failed = new File(Environment.getExternalStorageDirectory(), 326 "failed_" + methodName + "_" + testName + ".png"); 327 try (FileOutputStream fileStream = new FileOutputStream(failed)) { 328 result.compress(Bitmap.CompressFormat.PNG, 0 /* ignored for PNG */, fileStream); 329 fileStream.flush(); 330 } catch (Exception e) { 331 e.printStackTrace(); 332 } 333 Log.d(TAG, testName + " saved " + failed.getAbsolutePath()); 334 } 335 336 assertTrue("With the best matched border crop (" 337 + difference.bestMatchBorderCrop.first + ", " 338 + difference.bestMatchBorderCrop.second + "), " 339 + "greatest pixel difference is " 340 + difference.greatestPixelDifference 341 + (difference.greatestPixelDifferenceCoordinates != null 342 ? " at (" + difference.greatestPixelDifferenceCoordinates.first + ", " 343 + difference.greatestPixelDifferenceCoordinates.second + ")" : "") 344 + " which is over the allowed difference " + ALLOWED_GREATEST_PIXEL_DIFFERENCE, 345 difference.greatestPixelDifference <= ALLOWED_GREATEST_PIXEL_DIFFERENCE); 346 } 347 getLargerHeightVideoFormat(VideoFormat videoFormat)348 private static VideoFormat getLargerHeightVideoFormat(VideoFormat videoFormat) { 349 return new VideoFormat(videoFormat) { 350 @Override 351 public int getHeight() { 352 return super.getHeight() + OFFSET; 353 } 354 355 @Override 356 public boolean isAbrEnabled() { 357 return true; 358 } 359 }; 360 } 361 362 private static VideoFormat getLargerWidthVideoFormat(VideoFormat videoFormat) { 363 return new VideoFormat(videoFormat) { 364 @Override 365 public int getWidth() { 366 return super.getWidth() + OFFSET; 367 } 368 369 @Override 370 public boolean isAbrEnabled() { 371 return true; 372 } 373 }; 374 } 375 376 /** 377 * Returns the resource id by matching parts of the video and golden file name. 378 */ 379 private static int getGoldenId(String description, String size) { 380 for (Field field : fields) { 381 try { 382 final String name = field.getName(); 383 if (name.contains("golden") && name.contains(description) && name.contains(size)) { 384 int id = field.getInt(null); 385 return field.getInt(null); 386 } 387 } catch (IllegalAccessException | NullPointerException e) { 388 // No file found. 389 } 390 } 391 return 0; 392 } 393 394 } 395