blob: 145cfaf96fdff2ed8855c988d9b8305aae807608 [file] [log] [blame]
/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.media.decoder.cts;
import static junit.framework.TestCase.assertTrue;
import static org.junit.Assert.fail;
import android.media.decoder.cts.R;
import android.annotation.TargetApi;
import android.content.Context;
import android.graphics.Bitmap;
import android.media.MediaFormat;
import android.media.cts.MediaCodecTunneledPlayer;
import android.media.cts.MediaHeavyPresubmitTest;
import android.media.cts.TestArgs;
import android.os.Environment;
import android.platform.test.annotations.AppModeFull;
import android.util.Log;
import android.view.View;
import androidx.test.platform.app.InstrumentationRegistry;
import com.android.compatibility.common.util.MediaUtils;
import java.io.File;
import java.io.IOException;
import java.io.FileOutputStream;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.rules.Timeout;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
import org.junit.Test;
@TargetApi(24)
@RunWith(Parameterized.class)
@MediaHeavyPresubmitTest
@AppModeFull(reason = "There should be no instant apps specific behavior related to accuracy")
public class DecodeAccuracyTest extends DecodeAccuracyTestBase {
private static final String TAG = DecodeAccuracyTest.class.getSimpleName();
private static final Field[] fields = R.raw.class.getFields();
private static final int ALLOWED_GREATEST_PIXEL_DIFFERENCE = 90;
private static final int OFFSET = 10;
private static final long PER_TEST_TIMEOUT_MS = 60000;
private static final String[] VIDEO_FILES = {
// 144p
"video_decode_accuracy_and_capability-h264_256x108_30fps.mp4",
"video_decode_accuracy_and_capability-h264_256x144_30fps.mp4",
"video_decode_accuracy_and_capability-h264_192x144_30fps.mp4",
"video_decode_accuracy_and_capability-h264_82x144_30fps.mp4",
"video_decode_accuracy_and_capability-vp9_256x108_30fps.webm",
"video_decode_accuracy_and_capability-vp9_256x144_30fps.webm",
"video_decode_accuracy_and_capability-vp9_192x144_30fps.webm",
"video_decode_accuracy_and_capability-vp9_82x144_30fps.webm",
// 240p
"video_decode_accuracy_and_capability-h264_426x182_30fps.mp4",
"video_decode_accuracy_and_capability-h264_426x240_30fps.mp4",
"video_decode_accuracy_and_capability-h264_320x240_30fps.mp4",
"video_decode_accuracy_and_capability-h264_136x240_30fps.mp4",
"video_decode_accuracy_and_capability-vp9_426x182_30fps.webm",
"video_decode_accuracy_and_capability-vp9_426x240_30fps.webm",
"video_decode_accuracy_and_capability-vp9_320x240_30fps.webm",
"video_decode_accuracy_and_capability-vp9_136x240_30fps.webm",
// 360p
"video_decode_accuracy_and_capability-h264_640x272_30fps.mp4",
"video_decode_accuracy_and_capability-h264_640x360_30fps.mp4",
"video_decode_accuracy_and_capability-h264_480x360_30fps.mp4",
"video_decode_accuracy_and_capability-h264_202x360_30fps.mp4",
"video_decode_accuracy_and_capability-vp9_640x272_30fps.webm",
"video_decode_accuracy_and_capability-vp9_640x360_30fps.webm",
"video_decode_accuracy_and_capability-vp9_480x360_30fps.webm",
"video_decode_accuracy_and_capability-vp9_202x360_30fps.webm",
// 480p
"video_decode_accuracy_and_capability-h264_854x362_30fps.mp4",
"video_decode_accuracy_and_capability-h264_854x480_30fps.mp4",
"video_decode_accuracy_and_capability-h264_640x480_30fps.mp4",
"video_decode_accuracy_and_capability-h264_270x480_30fps.mp4",
"video_decode_accuracy_and_capability-vp9_854x362_30fps.webm",
"video_decode_accuracy_and_capability-vp9_854x480_30fps.webm",
"video_decode_accuracy_and_capability-vp9_640x480_30fps.webm",
"video_decode_accuracy_and_capability-vp9_270x480_30fps.webm",
// 720p
"video_decode_accuracy_and_capability-h264_1280x544_30fps.mp4",
"video_decode_accuracy_and_capability-h264_1280x720_30fps.mp4",
"video_decode_accuracy_and_capability-h264_960x720_30fps.mp4",
"video_decode_accuracy_and_capability-h264_406x720_30fps.mp4",
"video_decode_accuracy_and_capability-vp9_1280x544_30fps.webm",
"video_decode_accuracy_and_capability-vp9_1280x720_30fps.webm",
"video_decode_accuracy_and_capability-vp9_960x720_30fps.webm",
"video_decode_accuracy_and_capability-vp9_406x720_30fps.webm",
// 1080p
"video_decode_accuracy_and_capability-h264_1920x818_30fps.mp4",
"video_decode_accuracy_and_capability-h264_1920x1080_30fps.mp4",
"video_decode_accuracy_and_capability-h264_1440x1080_30fps.mp4",
"video_decode_accuracy_and_capability-h264_608x1080_30fps.mp4",
"video_decode_accuracy_and_capability-vp9_1920x818_30fps.webm",
"video_decode_accuracy_and_capability-vp9_1920x1080_30fps.webm",
"video_decode_accuracy_and_capability-vp9_1440x1080_30fps.webm",
"video_decode_accuracy_and_capability-vp9_608x1080_30fps.webm",
// 1440p
"video_decode_accuracy_and_capability-h264_2560x1090_30fps.mp4",
"video_decode_accuracy_and_capability-h264_2560x1440_30fps.mp4",
"video_decode_accuracy_and_capability-h264_1920x1440_30fps.mp4",
"video_decode_accuracy_and_capability-h264_810x1440_30fps.mp4",
"video_decode_accuracy_and_capability-vp9_2560x1090_30fps.webm",
"video_decode_accuracy_and_capability-vp9_2560x1440_30fps.webm",
"video_decode_accuracy_and_capability-vp9_1920x1440_30fps.webm",
"video_decode_accuracy_and_capability-vp9_810x1440_30fps.webm",
// 2160p
"video_decode_accuracy_and_capability-h264_3840x1634_30fps.mp4",
"video_decode_accuracy_and_capability-h264_3840x2160_30fps.mp4",
"video_decode_accuracy_and_capability-h264_2880x2160_30fps.mp4",
"video_decode_accuracy_and_capability-h264_1216x2160_30fps.mp4",
"video_decode_accuracy_and_capability-vp9_3840x1634_30fps.webm",
"video_decode_accuracy_and_capability-vp9_3840x2160_30fps.webm",
"video_decode_accuracy_and_capability-vp9_2880x2160_30fps.webm",
"video_decode_accuracy_and_capability-vp9_1216x2160_30fps.webm",
// cropped
"video_decode_with_cropping-h264_520x360_30fps.mp4",
"video_decode_with_cropping-vp9_520x360_30fps.webm"
};
private static final String INP_PREFIX = WorkDir.getMediaDirString() +
"assets/decode_accuracy/";
private View videoView;
private VideoViewFactory videoViewFactory;
private String testName;
private String fileName;
private String decoderName;
private String methodName;
private SimplePlayer player;
public DecodeAccuracyTest(String decoderName, String fileName, String testName) {
this.testName = testName;
this.fileName = fileName;
this.decoderName = decoderName;
}
@After
@Override
public void tearDown() throws Exception {
if (player != null) {
player.release();
}
if (videoView != null) {
getHelper().cleanUpView(videoView);
}
if (videoViewFactory != null) {
videoViewFactory.release();
}
super.tearDown();
}
@Parameters(name = "{index}({0}_{2})")
public static Collection<Object[]> input() throws IOException {
final List<Object[]> testParams = new ArrayList<>();
for (String file : VIDEO_FILES) {
Pattern regex = Pattern.compile("^\\w+-(\\w+)_\\d+fps\\.\\w+");
Matcher matcher = regex.matcher(file);
String testName = "";
if (matcher.matches()) {
testName = matcher.group(1);
}
MediaFormat mediaFormat =
MediaUtils.getTrackFormatForResource(INP_PREFIX + file, "video");
String mediaType = mediaFormat.getString(MediaFormat.KEY_MIME);
if (TestArgs.shouldSkipMediaType(mediaType)) {
continue;
}
String[] componentNames = MediaUtils.getDecoderNamesForMime(mediaType);
for (String componentName : componentNames) {
if (TestArgs.shouldSkipCodec(componentName)) {
continue;
}
if (MediaUtils.supports(componentName, mediaFormat)) {
testParams.add(new Object[] {componentName, file, testName});
// Test only the first decoder that supports given format.
// Remove the following break statement to test all decoders on the device.
break;
}
}
}
return testParams;
}
@Test(timeout = PER_TEST_TIMEOUT_MS)
public void testGLViewDecodeAccuracy() throws Exception {
this.methodName = "testGLViewDecodeAccuracy";
runTest(new GLSurfaceViewFactory(), new VideoFormat(fileName), decoderName);
}
@Test(timeout = PER_TEST_TIMEOUT_MS)
public void testGLViewLargerHeightDecodeAccuracy() throws Exception {
this.methodName = "testGLViewLargerHeightDecodeAccuracy";
runTest(new GLSurfaceViewFactory(), getLargerHeightVideoFormat(new VideoFormat(fileName)),
decoderName);
}
@Test(timeout = PER_TEST_TIMEOUT_MS)
public void testGLViewLargerWidthDecodeAccuracy() throws Exception {
this.methodName = "testGLViewLargerWidthDecodeAccuracy";
runTest(new GLSurfaceViewFactory(), getLargerWidthVideoFormat(new VideoFormat(fileName)),
decoderName);
}
@Test(timeout = PER_TEST_TIMEOUT_MS)
public void testSurfaceViewVideoDecodeAccuracy() throws Exception {
this.methodName = "testSurfaceViewVideoDecodeAccuracy";
runTest(new SurfaceViewFactory(), new VideoFormat(fileName), decoderName);
}
@Test(timeout = PER_TEST_TIMEOUT_MS)
public void testSurfaceViewLargerHeightDecodeAccuracy() throws Exception {
this.methodName = "testSurfaceViewLargerHeightDecodeAccuracy";
runTest(new SurfaceViewFactory(), getLargerHeightVideoFormat(new VideoFormat(fileName)),
decoderName);
}
@Test(timeout = PER_TEST_TIMEOUT_MS)
public void testSurfaceViewLargerWidthDecodeAccuracy() throws Exception {
this.methodName = "testSurfaceViewLargerWidthDecodeAccuracy";
runTest(new SurfaceViewFactory(), getLargerWidthVideoFormat(new VideoFormat(fileName)),
decoderName);
}
private void runTest(VideoViewFactory videoViewFactory, VideoFormat vf, String decoderName) {
Log.i(TAG, "Running test for " + vf.toPrettyString());
if (!MediaUtils.canDecodeVideo(vf.getMimeType(), vf.getWidth(), vf.getHeight(), 30)) {
MediaUtils.skipTest(TAG, "No supported codec is found.");
return;
}
this.videoViewFactory = checkNotNull(videoViewFactory);
this.videoView = videoViewFactory.createView(getHelper().getContext());
final int maxRetries = 3;
for (int retry = 1; retry <= maxRetries; retry++) {
// If view is intended and available to display.
if (videoView != null) {
getHelper().generateView(videoView);
}
try {
videoViewFactory.waitForViewIsAvailable();
break;
} catch (Exception exception) {
Log.e(TAG, exception.getMessage());
if (retry == maxRetries) {
fail("Timeout waiting for a valid surface.");
} else {
Log.w(TAG, "Try again...");
bringActivityToFront();
}
}
}
final int golden = getGoldenId(vf.getDescription(), vf.getOriginalSize());
assertTrue("No golden found.", golden != 0);
decodeVideo(vf, videoViewFactory, decoderName);
validateResult(vf, videoViewFactory.getVideoViewSnapshot(), golden);
}
private void decodeVideo(VideoFormat videoFormat, VideoViewFactory videoViewFactory,
String decoderName) {
this.player = new SimplePlayer(getHelper().getContext(), decoderName);
final SimplePlayer.PlayerResult playerResult = player.decodeVideoFrames(
videoViewFactory.getSurface(), videoFormat, 10);
assertTrue(playerResult.getFailureMessage(), playerResult.isSuccess());
}
private void validateResult(
VideoFormat videoFormat, VideoViewSnapshot videoViewSnapshot, int goldenId) {
final Bitmap result = checkNotNull("The expected bitmap from snapshot is null",
getHelper().generateBitmapFromVideoViewSnapshot(videoViewSnapshot));
final Bitmap golden = getHelper().generateBitmapFromImageResourceId(goldenId);
final BitmapCompare.Difference difference = BitmapCompare.computeMinimumDifference(
result, golden, videoFormat.getOriginalWidth(), videoFormat.getOriginalHeight());
if (difference.greatestPixelDifference > ALLOWED_GREATEST_PIXEL_DIFFERENCE) {
/* save failing file */
File failed = new File(Environment.getExternalStorageDirectory(),
"failed_" + methodName + "_" + testName + ".png");
try (FileOutputStream fileStream = new FileOutputStream(failed)) {
result.compress(Bitmap.CompressFormat.PNG, 0 /* ignored for PNG */, fileStream);
fileStream.flush();
} catch (Exception e) {
e.printStackTrace();
}
Log.d(TAG, testName + " saved " + failed.getAbsolutePath());
}
assertTrue("With the best matched border crop ("
+ difference.bestMatchBorderCrop.first + ", "
+ difference.bestMatchBorderCrop.second + "), "
+ "greatest pixel difference is "
+ difference.greatestPixelDifference
+ (difference.greatestPixelDifferenceCoordinates != null
? " at (" + difference.greatestPixelDifferenceCoordinates.first + ", "
+ difference.greatestPixelDifferenceCoordinates.second + ")" : "")
+ " which is over the allowed difference " + ALLOWED_GREATEST_PIXEL_DIFFERENCE,
difference.greatestPixelDifference <= ALLOWED_GREATEST_PIXEL_DIFFERENCE);
}
private static VideoFormat getLargerHeightVideoFormat(VideoFormat videoFormat) {
return new VideoFormat(videoFormat) {
@Override
public int getHeight() {
return super.getHeight() + OFFSET;
}
@Override
public boolean isAbrEnabled() {
return true;
}
};
}
private static VideoFormat getLargerWidthVideoFormat(VideoFormat videoFormat) {
return new VideoFormat(videoFormat) {
@Override
public int getWidth() {
return super.getWidth() + OFFSET;
}
@Override
public boolean isAbrEnabled() {
return true;
}
};
}
/**
* Returns the resource id by matching parts of the video and golden file name.
*/
private static int getGoldenId(String description, String size) {
for (Field field : fields) {
try {
final String name = field.getName();
if (name.contains("golden") && name.contains(description) && name.contains(size)) {
int id = field.getInt(null);
return field.getInt(null);
}
} catch (IllegalAccessException | NullPointerException e) {
// No file found.
}
}
return 0;
}
}