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.cts; 17 18 import static junit.framework.TestCase.assertTrue; 19 20 import static org.junit.Assert.fail; 21 22 import android.media.cts.R; 23 24 import android.annotation.TargetApi; 25 import android.content.Context; 26 import android.graphics.Bitmap; 27 import android.media.MediaFormat; 28 import android.os.Environment; 29 import android.platform.test.annotations.AppModeFull; 30 import android.util.Log; 31 import android.view.View; 32 33 import com.android.compatibility.common.util.MediaUtils; 34 35 import java.io.File; 36 import java.io.FileOutputStream; 37 import java.lang.reflect.Field; 38 import java.util.ArrayList; 39 import java.util.Collection; 40 import java.util.List; 41 import java.util.regex.Matcher; 42 import java.util.regex.Pattern; 43 44 import org.junit.After; 45 import org.junit.Before; 46 import org.junit.Rule; 47 import org.junit.rules.Timeout; 48 import org.junit.runner.RunWith; 49 import org.junit.runners.Parameterized; 50 import org.junit.runners.Parameterized.Parameters; 51 import org.junit.Test; 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 String TAG = DecodeAccuracyTest.class.getSimpleName(); 60 private static final Field[] fields = R.raw.class.getFields(); 61 private static final int ALLOWED_GREATEST_PIXEL_DIFFERENCE = 90; 62 private static final int OFFSET = 10; 63 private static final long PER_TEST_TIMEOUT_MS = 60000; 64 private static final String[] VIDEO_FILES = { 65 // 144p 66 "video_decode_accuracy_and_capability-h264_256x108_30fps.mp4", 67 "video_decode_accuracy_and_capability-h264_256x144_30fps.mp4", 68 "video_decode_accuracy_and_capability-h264_192x144_30fps.mp4", 69 "video_decode_accuracy_and_capability-h264_82x144_30fps.mp4", 70 "video_decode_accuracy_and_capability-vp9_256x108_30fps.webm", 71 "video_decode_accuracy_and_capability-vp9_256x144_30fps.webm", 72 "video_decode_accuracy_and_capability-vp9_192x144_30fps.webm", 73 "video_decode_accuracy_and_capability-vp9_82x144_30fps.webm", 74 // 240p 75 "video_decode_accuracy_and_capability-h264_426x182_30fps.mp4", 76 "video_decode_accuracy_and_capability-h264_426x240_30fps.mp4", 77 "video_decode_accuracy_and_capability-h264_320x240_30fps.mp4", 78 "video_decode_accuracy_and_capability-h264_136x240_30fps.mp4", 79 "video_decode_accuracy_and_capability-vp9_426x182_30fps.webm", 80 "video_decode_accuracy_and_capability-vp9_426x240_30fps.webm", 81 "video_decode_accuracy_and_capability-vp9_320x240_30fps.webm", 82 "video_decode_accuracy_and_capability-vp9_136x240_30fps.webm", 83 // 360p 84 "video_decode_accuracy_and_capability-h264_640x272_30fps.mp4", 85 "video_decode_accuracy_and_capability-h264_640x360_30fps.mp4", 86 "video_decode_accuracy_and_capability-h264_480x360_30fps.mp4", 87 "video_decode_accuracy_and_capability-h264_202x360_30fps.mp4", 88 "video_decode_accuracy_and_capability-vp9_640x272_30fps.webm", 89 "video_decode_accuracy_and_capability-vp9_640x360_30fps.webm", 90 "video_decode_accuracy_and_capability-vp9_480x360_30fps.webm", 91 "video_decode_accuracy_and_capability-vp9_202x360_30fps.webm", 92 // 480p 93 "video_decode_accuracy_and_capability-h264_854x362_30fps.mp4", 94 "video_decode_accuracy_and_capability-h264_854x480_30fps.mp4", 95 "video_decode_accuracy_and_capability-h264_640x480_30fps.mp4", 96 "video_decode_accuracy_and_capability-h264_270x480_30fps.mp4", 97 "video_decode_accuracy_and_capability-vp9_854x362_30fps.webm", 98 "video_decode_accuracy_and_capability-vp9_854x480_30fps.webm", 99 "video_decode_accuracy_and_capability-vp9_640x480_30fps.webm", 100 "video_decode_accuracy_and_capability-vp9_270x480_30fps.webm", 101 // 720p 102 "video_decode_accuracy_and_capability-h264_1280x544_30fps.mp4", 103 "video_decode_accuracy_and_capability-h264_1280x720_30fps.mp4", 104 "video_decode_accuracy_and_capability-h264_960x720_30fps.mp4", 105 "video_decode_accuracy_and_capability-h264_406x720_30fps.mp4", 106 "video_decode_accuracy_and_capability-vp9_1280x544_30fps.webm", 107 "video_decode_accuracy_and_capability-vp9_1280x720_30fps.webm", 108 "video_decode_accuracy_and_capability-vp9_960x720_30fps.webm", 109 "video_decode_accuracy_and_capability-vp9_406x720_30fps.webm", 110 // 1080p 111 "video_decode_accuracy_and_capability-h264_1920x818_30fps.mp4", 112 "video_decode_accuracy_and_capability-h264_1920x1080_30fps.mp4", 113 "video_decode_accuracy_and_capability-h264_1440x1080_30fps.mp4", 114 "video_decode_accuracy_and_capability-h264_608x1080_30fps.mp4", 115 "video_decode_accuracy_and_capability-vp9_1920x818_30fps.webm", 116 "video_decode_accuracy_and_capability-vp9_1920x1080_30fps.webm", 117 "video_decode_accuracy_and_capability-vp9_1440x1080_30fps.webm", 118 "video_decode_accuracy_and_capability-vp9_608x1080_30fps.webm", 119 // 1440p 120 "video_decode_accuracy_and_capability-h264_2560x1090_30fps.mp4", 121 "video_decode_accuracy_and_capability-h264_2560x1440_30fps.mp4", 122 "video_decode_accuracy_and_capability-h264_1920x1440_30fps.mp4", 123 "video_decode_accuracy_and_capability-h264_810x1440_30fps.mp4", 124 "video_decode_accuracy_and_capability-vp9_2560x1090_30fps.webm", 125 "video_decode_accuracy_and_capability-vp9_2560x1440_30fps.webm", 126 "video_decode_accuracy_and_capability-vp9_1920x1440_30fps.webm", 127 "video_decode_accuracy_and_capability-vp9_810x1440_30fps.webm", 128 // 2160p 129 "video_decode_accuracy_and_capability-h264_3840x1634_30fps.mp4", 130 "video_decode_accuracy_and_capability-h264_3840x2160_30fps.mp4", 131 "video_decode_accuracy_and_capability-h264_2880x2160_30fps.mp4", 132 "video_decode_accuracy_and_capability-h264_1216x2160_30fps.mp4", 133 "video_decode_accuracy_and_capability-vp9_3840x1634_30fps.webm", 134 "video_decode_accuracy_and_capability-vp9_3840x2160_30fps.webm", 135 "video_decode_accuracy_and_capability-vp9_2880x2160_30fps.webm", 136 "video_decode_accuracy_and_capability-vp9_1216x2160_30fps.webm", 137 // cropped 138 "video_decode_with_cropping-h264_520x360_30fps.mp4", 139 "video_decode_with_cropping-vp9_520x360_30fps.webm" 140 }; 141 142 private View videoView; 143 private VideoViewFactory videoViewFactory; 144 private String fileName; 145 private String testName; 146 private String methodName; 147 private SimplePlayer player; 148 149 @After 150 @Override tearDown()151 public void tearDown() throws Exception { 152 if (player != null) { 153 player.release(); 154 } 155 if (videoView != null) { 156 getHelper().cleanUpView(videoView); 157 } 158 if (videoViewFactory != null) { 159 videoViewFactory.release(); 160 } 161 super.tearDown(); 162 } 163 164 @Parameters data()165 public static Collection<Object[]> data() { 166 final List<Object[]> testParams = new ArrayList<>(); 167 for (int i = 0; i < VIDEO_FILES.length; i++) { 168 final String file = VIDEO_FILES[i]; 169 Pattern regex = Pattern.compile("^\\w+-(\\w+)_\\d+fps\\.\\w+"); 170 Matcher matcher = regex.matcher(file); 171 String testName = ""; 172 if (matcher.matches()) { 173 testName = matcher.group(1); 174 } 175 testParams.add(new Object[] { testName, file }); 176 } 177 return testParams; 178 } 179 DecodeAccuracyTest(String testName, String fileName)180 public DecodeAccuracyTest(String testName, String fileName) { 181 this.fileName = fileName; 182 this.testName = testName; 183 } 184 185 @Test(timeout = PER_TEST_TIMEOUT_MS) testGLViewDecodeAccuracy()186 public void testGLViewDecodeAccuracy() throws Exception { 187 this.methodName = "testGLViewDecodeAccuracy"; 188 runTest(new GLSurfaceViewFactory(), new VideoFormat(fileName)); 189 } 190 191 @Test(timeout = PER_TEST_TIMEOUT_MS) testGLViewLargerHeightDecodeAccuracy()192 public void testGLViewLargerHeightDecodeAccuracy() throws Exception { 193 this.methodName = "testGLViewLargerHeightDecodeAccuracy"; 194 runTest(new GLSurfaceViewFactory(), getLargerHeightVideoFormat(new VideoFormat(fileName))); 195 } 196 197 @Test(timeout = PER_TEST_TIMEOUT_MS) testGLViewLargerWidthDecodeAccuracy()198 public void testGLViewLargerWidthDecodeAccuracy() throws Exception { 199 this.methodName = "testGLViewLargerWidthDecodeAccuracy"; 200 runTest(new GLSurfaceViewFactory(), getLargerWidthVideoFormat(new VideoFormat(fileName))); 201 } 202 203 @Test(timeout = PER_TEST_TIMEOUT_MS) testSurfaceViewVideoDecodeAccuracy()204 public void testSurfaceViewVideoDecodeAccuracy() throws Exception { 205 this.methodName = "testSurfaceViewVideoDecodeAccuracy"; 206 runTest(new SurfaceViewFactory(), new VideoFormat(fileName)); 207 } 208 209 @Test(timeout = PER_TEST_TIMEOUT_MS) testSurfaceViewLargerHeightDecodeAccuracy()210 public void testSurfaceViewLargerHeightDecodeAccuracy() throws Exception { 211 this.methodName = "testSurfaceViewLargerHeightDecodeAccuracy"; 212 runTest(new SurfaceViewFactory(), getLargerHeightVideoFormat(new VideoFormat(fileName))); 213 } 214 215 @Test(timeout = PER_TEST_TIMEOUT_MS) testSurfaceViewLargerWidthDecodeAccuracy()216 public void testSurfaceViewLargerWidthDecodeAccuracy() throws Exception { 217 this.methodName = "testSurfaceViewLargerWidthDecodeAccuracy"; 218 runTest(new SurfaceViewFactory(), getLargerWidthVideoFormat(new VideoFormat(fileName))); 219 } 220 runTest(VideoViewFactory videoViewFactory, VideoFormat vf)221 private void runTest(VideoViewFactory videoViewFactory, VideoFormat vf) { 222 Log.i(TAG, "Running test for " + vf.toPrettyString()); 223 if (!MediaUtils.canDecodeVideo(vf.getMimeType(), vf.getWidth(), vf.getHeight(), 30)) { 224 MediaUtils.skipTest(TAG, "No supported codec is found."); 225 return; 226 } 227 this.videoViewFactory = checkNotNull(videoViewFactory); 228 this.videoView = videoViewFactory.createView(getHelper().getContext()); 229 final int maxRetries = 3; 230 for (int retry = 1; retry <= maxRetries; retry++) { 231 // If view is intended and available to display. 232 if (videoView != null) { 233 getHelper().generateView(videoView); 234 } 235 try { 236 videoViewFactory.waitForViewIsAvailable(); 237 break; 238 } catch (Exception exception) { 239 Log.e(TAG, exception.getMessage()); 240 if (retry == maxRetries) { 241 fail("Timeout waiting for a valid surface."); 242 } else { 243 Log.w(TAG, "Try again..."); 244 bringActivityToFront(); 245 } 246 } 247 } 248 final int golden = getGoldenId(vf.getDescription(), vf.getOriginalSize()); 249 assertTrue("No golden found.", golden != 0); 250 decodeVideo(vf, videoViewFactory); 251 validateResult(vf, videoViewFactory.getVideoViewSnapshot(), golden); 252 } 253 decodeVideo(VideoFormat videoFormat, VideoViewFactory videoViewFactory)254 private void decodeVideo(VideoFormat videoFormat, VideoViewFactory videoViewFactory) { 255 this.player = new SimplePlayer(getHelper().getContext()); 256 final SimplePlayer.PlayerResult playerResult = player.decodeVideoFrames( 257 videoViewFactory.getSurface(), videoFormat, 10); 258 assertTrue(playerResult.getFailureMessage(), playerResult.isSuccess()); 259 } 260 validateResult( VideoFormat videoFormat, VideoViewSnapshot videoViewSnapshot, int goldenId)261 private void validateResult( 262 VideoFormat videoFormat, VideoViewSnapshot videoViewSnapshot, int goldenId) { 263 final Bitmap result = checkNotNull("The expected bitmap from snapshot is null", 264 getHelper().generateBitmapFromVideoViewSnapshot(videoViewSnapshot)); 265 final Bitmap golden = getHelper().generateBitmapFromImageResourceId(goldenId); 266 final BitmapCompare.Difference difference = BitmapCompare.computeMinimumDifference( 267 result, golden, videoFormat.getOriginalWidth(), videoFormat.getOriginalHeight()); 268 269 if (difference.greatestPixelDifference > ALLOWED_GREATEST_PIXEL_DIFFERENCE) { 270 /* save failing file */ 271 File failed = new File(Environment.getExternalStorageDirectory(), 272 "failed_" + methodName + "_" + testName + ".png"); 273 try (FileOutputStream fileStream = new FileOutputStream(failed)) { 274 result.compress(Bitmap.CompressFormat.PNG, 0 /* ignored for PNG */, fileStream); 275 fileStream.flush(); 276 } catch (Exception e) { 277 e.printStackTrace(); 278 } 279 Log.d(TAG, testName + " saved " + failed.getAbsolutePath()); 280 } 281 282 assertTrue("With the best matched border crop (" 283 + difference.bestMatchBorderCrop.first + ", " 284 + difference.bestMatchBorderCrop.second + "), " 285 + "greatest pixel difference is " 286 + difference.greatestPixelDifference 287 + (difference.greatestPixelDifferenceCoordinates != null 288 ? " at (" + difference.greatestPixelDifferenceCoordinates.first + ", " 289 + difference.greatestPixelDifferenceCoordinates.second + ")" : "") 290 + " which is over the allowed difference " + ALLOWED_GREATEST_PIXEL_DIFFERENCE, 291 difference.greatestPixelDifference <= ALLOWED_GREATEST_PIXEL_DIFFERENCE); 292 } 293 getLargerHeightVideoFormat(VideoFormat videoFormat)294 private static VideoFormat getLargerHeightVideoFormat(VideoFormat videoFormat) { 295 return new VideoFormat(videoFormat) { 296 @Override 297 public int getHeight() { 298 return super.getHeight() + OFFSET; 299 } 300 301 @Override 302 public boolean isAbrEnabled() { 303 return true; 304 } 305 }; 306 } 307 308 private static VideoFormat getLargerWidthVideoFormat(VideoFormat videoFormat) { 309 return new VideoFormat(videoFormat) { 310 @Override 311 public int getWidth() { 312 return super.getWidth() + OFFSET; 313 } 314 315 @Override 316 public boolean isAbrEnabled() { 317 return true; 318 } 319 }; 320 } 321 322 /** 323 * Returns the resource id by matching parts of the video and golden file name. 324 */ 325 private static int getGoldenId(String description, String size) { 326 for (Field field : fields) { 327 try { 328 final String name = field.getName(); 329 if (name.contains("golden") && name.contains(description) && name.contains(size)) { 330 int id = field.getInt(null); 331 return field.getInt(null); 332 } 333 } catch (IllegalAccessException | NullPointerException e) { 334 // No file found. 335 } 336 } 337 return 0; 338 } 339 340 } 341