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