host side MediaBitstreamsTest
Bug: 30268664
Test: run cts everything -m CtsMediaBitstreamsTestCases
Change-Id: Iae347d44a27bcc2290dd93f1db6638011b884ef9
diff --git a/hostsidetests/media/bitstreams/Android.mk b/hostsidetests/media/bitstreams/Android.mk
new file mode 100644
index 0000000..b07309c
--- /dev/null
+++ b/hostsidetests/media/bitstreams/Android.mk
@@ -0,0 +1,35 @@
+# Copyright (C) 2017 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.
+
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+LOCAL_SRC_FILES += $(call all-java-files-under, common/src)
+
+LOCAL_JAVA_LIBRARIES := compatibility-host-util cts-tradefed tradefed
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_MODULE := CtsMediaBitstreamsTestCases
+
+# Tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts
+
+LOCAL_SDK_VERSION := current
+
+include $(BUILD_HOST_JAVA_LIBRARY)
+
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/hostsidetests/media/bitstreams/AndroidTest.xml b/hostsidetests/media/bitstreams/AndroidTest.xml
new file mode 100644
index 0000000..fe06a09
--- /dev/null
+++ b/hostsidetests/media/bitstreams/AndroidTest.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 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.
+-->
+<configuration description="Config for CTS Sample host test cases">
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="cleanup-apks" value="true" />
+ <option name="test-file-name" value="CtsMediaBitstreamsDeviceSideTestApp.apk" />
+ </target_preparer>
+ <target_preparer class="com.android.compatibility.common.tradefed.targetprep.DynamicConfigPusher">
+ <option name="target" value="device" />
+ <option name="config-filename" value="CtsMediaBitstreamsTestCases" />
+ </target_preparer>
+ <test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
+ <option name="jar" value="CtsMediaBitstreamsTestCases.jar" />
+ <option name="runtime-hint" value="6m" />
+ </test>
+</configuration>
diff --git a/hostsidetests/media/bitstreams/app/Android.mk b/hostsidetests/media/bitstreams/app/Android.mk
new file mode 100644
index 0000000..df8e6a4
--- /dev/null
+++ b/hostsidetests/media/bitstreams/app/Android.mk
@@ -0,0 +1,39 @@
+# Copyright (C) 2017 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.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+# Don't include this package in any target
+LOCAL_MODULE_TAGS := tests
+# When built, explicitly put it in the data partition.
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+LOCAL_DEX_PREOPT := false
+
+LOCAL_PROGUARD_ENABLED := disabled
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-test compatibility-device-util media-bitstreams-common-devicesidelib
+
+# tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts
+
+LOCAL_PACKAGE_NAME := CtsMediaBitstreamsDeviceSideTestApp
+
+LOCAL_SDK_VERSION := current
+
+include $(BUILD_CTS_PACKAGE)
diff --git a/hostsidetests/media/bitstreams/app/AndroidManifest.xml b/hostsidetests/media/bitstreams/app/AndroidManifest.xml
new file mode 100644
index 0000000..d5565fd
--- /dev/null
+++ b/hostsidetests/media/bitstreams/app/AndroidManifest.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2017 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.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.media.cts.bitstreams.app">
+
+ <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
+ <application>
+ <uses-library android:name="android.test.runner" />
+ </application>
+
+ <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
+ android:targetPackage="android.media.cts.bitstreams.app"
+ android:label="Device-side CTS media bitstreams preparation" />
+</manifest>
diff --git a/hostsidetests/media/bitstreams/app/src/android/media/cts/bitstreams/app/MediaBitstreamsDeviceSideTest.java b/hostsidetests/media/bitstreams/app/src/android/media/cts/bitstreams/app/MediaBitstreamsDeviceSideTest.java
new file mode 100644
index 0000000..d7965b7
--- /dev/null
+++ b/hostsidetests/media/bitstreams/app/src/android/media/cts/bitstreams/app/MediaBitstreamsDeviceSideTest.java
@@ -0,0 +1,379 @@
+/*
+ * Copyright (C) 2017 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.cts.bitstreams.app;
+
+import android.app.Instrumentation;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.content.SharedPreferences.Editor;
+import android.media.MediaCodec;
+import android.media.MediaExtractor;
+import android.media.MediaFormat;
+import android.media.cts.bitstreams.MediaBitstreams;
+import android.os.Bundle;
+import android.os.Debug;
+import android.support.test.InstrumentationRegistry;
+import android.util.Xml;
+import com.android.compatibility.common.util.DynamicConfigDeviceSide;
+import com.android.compatibility.common.util.MediaUtils;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.PrintStream;
+import java.nio.file.Files;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Scanner;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.xmlpull.v1.XmlSerializer;
+
+/**
+ * Test class that uses device-side media APIs to determine up to which resolution MediaPreparer
+ * should copy media files for CtsMediaStressTestCases.
+ */
+@RunWith(JUnit4.class)
+public class MediaBitstreamsDeviceSideTest {
+
+ private static final String KEY_SIZE = "size";
+ private static final String UTF_8 = "utf-8";
+ private static final String DYNAMIC_CONFIG = "dynamicConfig";
+ private static final String DYNAMIC_CONFIG_ENTRY = "entry";
+ private static final String DYNAMIC_CONFIG_KEY = "key";
+ private static final String DYNAMIC_CONFIG_VALUE = "value";
+
+ /** Instrumentation status code used to write resolution to metrics */
+ private static final int INST_STATUS_IN_PROGRESS = 2;
+
+ private static File mAppCache = InstrumentationRegistry.getContext().getExternalCacheDir();
+ private static String mDeviceBitstreamsPath = InstrumentationRegistry.getArguments().getString(
+ MediaBitstreams.OPT_DEVICE_BITSTEAMS_PATH,
+ MediaBitstreams.DEFAULT_DEVICE_BITSTEAMS_PATH);
+
+ @BeforeClass
+ public static void setUp() {
+ Bundle args = InstrumentationRegistry.getArguments();
+ String debugStr = args.getString(MediaBitstreams.OPT_DEBUG_TARGET_DEVICE, "false");
+ boolean debug = Boolean.parseBoolean(debugStr);
+ if (debug && !Debug.isDebuggerConnected()) {
+ Debug.waitForDebugger();
+ }
+ }
+
+ static interface ReportCallback {
+ void run(OutputStream out) throws Exception;
+ }
+
+ static class GenerateBitstreamsFormatsXml implements ReportCallback {
+ @Override
+ public void run(OutputStream out) throws Exception {
+
+ String[] keys = new String[] {
+ MediaFormat.KEY_WIDTH,
+ MediaFormat.KEY_HEIGHT,
+ MediaFormat.KEY_FRAME_RATE,
+ MediaFormat.KEY_PROFILE,
+ MediaFormat.KEY_LEVEL,
+ MediaFormat.KEY_BIT_RATE};
+
+ XmlSerializer formats = Xml.newSerializer();
+ formats.setOutput(out, UTF_8);
+ formats.startDocument(UTF_8, true);
+ formats.startTag(null, DYNAMIC_CONFIG);
+
+ DynamicConfigDeviceSide config = new DynamicConfigDeviceSide(MediaBitstreams.K_MODULE);
+ for (String path : config.keySet()) {
+
+ formats.startTag(null, DYNAMIC_CONFIG_ENTRY);
+ formats.attribute(null, DYNAMIC_CONFIG_KEY, path);
+ formats.startTag(null, DYNAMIC_CONFIG_VALUE);
+
+ String formatStr = config.getValue(path);
+ if (formatStr != null && !formatStr.isEmpty()) {
+ formats.text(formatStr);
+ } else {
+ File media = new File(mDeviceBitstreamsPath, path);
+ String fullPath = media.getPath();
+ MediaFormat format = MediaUtils.getTrackFormatForPath(null, fullPath, "video");
+ StringBuilder formatStringBuilder = new StringBuilder(MediaFormat.KEY_MIME);
+ formatStringBuilder.append('=').append(format.getString(MediaFormat.KEY_MIME));
+ formatStringBuilder.append(',').append(KEY_SIZE)
+ .append('=').append(media.length());
+ for (String key : keys) {
+ formatStringBuilder.append(',').append(key).append('=');
+ if (format.containsKey(key)) {
+ formatStringBuilder.append(format.getInteger(key));
+ }
+ }
+ formats.text(formatStringBuilder.toString());
+ }
+
+ formats.endTag(null, DYNAMIC_CONFIG_VALUE);
+ formats.endTag(null, DYNAMIC_CONFIG_ENTRY);
+
+ }
+
+ formats.endTag(null, DYNAMIC_CONFIG);
+ formats.endDocument();
+
+ }
+ }
+
+ static class GenerateSupportedBitstreamsFormatsTxt implements ReportCallback {
+
+ @Override
+ public void run(OutputStream out) throws Exception {
+
+ PrintStream ps = new PrintStream(out);
+ Bundle args = InstrumentationRegistry.getArguments();
+ String prefix = args.getString(MediaBitstreams.OPT_BITSTREAMS_PREFIX, "");
+ DynamicConfigDeviceSide config = new DynamicConfigDeviceSide(MediaBitstreams.K_MODULE);
+
+ for (String path : config.keySet()) {
+
+ if (!path.startsWith(prefix)) {
+ continue;
+ }
+
+ String formatStr = config.getValue(path);
+ if (formatStr == null || formatStr.isEmpty()) {
+ continue;
+ }
+
+ MediaFormat format = parseTrackFormat(formatStr);
+ if (!MediaUtils.checkDecoderForFormat(format)) {
+ continue;
+ }
+
+ ps.println(path);
+ }
+ ps.flush();
+ }
+ }
+
+ static class TestBitstreamsConformance implements ReportCallback {
+
+ ExecutorService mExecutorService;
+
+ private SharedPreferences getSettings() {
+ Context ctx = InstrumentationRegistry.getContext();
+ SharedPreferences settings = ctx.getSharedPreferences(MediaBitstreams.K_MODULE, 0);
+ return settings;
+ }
+
+ private void setup() {
+ Bundle args = InstrumentationRegistry.getArguments();
+ String lastCrash = args.getString(MediaBitstreams.OPT_LAST_CRASH);
+ if (lastCrash != null) {
+ SharedPreferences settings = getSettings();
+ int n = settings.getInt(lastCrash, 0);
+ Editor editor = settings.edit();
+ editor.putInt(lastCrash, n + 1);
+ editor.commit();
+ }
+ }
+
+ @Override
+ public void run(OutputStream out) throws Exception {
+ setup();
+ mExecutorService = Executors.newFixedThreadPool(3);
+ try (
+ Scanner sc = new Scanner(
+ new File(mDeviceBitstreamsPath, MediaBitstreams.K_BITSTREAMS_LIST_TXT));
+ PrintStream ps = new PrintStream(out, true)
+ ) {
+ while (sc.hasNextLine()) {
+ verifyBitstream(ps, sc.nextLine());
+ }
+ } finally {
+ mExecutorService.shutdown();
+ }
+ }
+
+ private List<String> getDecodersForPath(String path) throws IOException {
+ List<String> decoders = new ArrayList<>();
+ MediaExtractor ex = new MediaExtractor();
+ try {
+ ex.setDataSource(path);
+ MediaFormat format = ex.getTrackFormat(0);
+ boolean[] vendors = new boolean[] {false, true};
+ for (boolean v : vendors) {
+ for (String name : MediaUtils.getDecoderNames(v, format)) {
+ decoders.add(name);
+ }
+ }
+ } finally {
+ ex.release();
+ }
+ return decoders;
+ }
+
+ private List<String> getFrameChecksumsForPath(String path) throws IOException {
+ String md5Path = MediaBitstreams.getMd5Path(path);
+ List<String> frameMD5Sums = Files.readAllLines(
+ new File(mDeviceBitstreamsPath, md5Path).toPath());
+ for (int i = 0; i < frameMD5Sums.size(); i++) {
+ String line = frameMD5Sums.get(i);
+ frameMD5Sums.set(i, line.split(" ")[0]);
+ }
+ return frameMD5Sums;
+ }
+
+ private void verifyBitstream(PrintStream ps, String relativePath) {
+ ps.println(relativePath);
+
+ List<String> decoders = new ArrayList<>();
+ List<String> frameChecksums = new ArrayList<>();
+ SharedPreferences settings = getSettings();
+ String fullPath = new File(mDeviceBitstreamsPath, relativePath).toString();
+ try {
+ String lastCrash = MediaBitstreams.generateCrashSignature(relativePath, "");
+ if (settings.getInt(lastCrash, 0) >= 3) {
+ ps.println(MediaBitstreams.K_NATIVE_CRASH);
+ return;
+ }
+ decoders = getDecodersForPath(fullPath);
+ frameChecksums = getFrameChecksumsForPath(relativePath);
+ ps.println(false);
+ } catch (Exception e) {
+ ps.println(true);
+ ps.println(e.toString());
+ return;
+ }
+
+ ps.println(decoders.size());
+ for (String name : decoders) {
+ ps.println(name);
+ String lastCrash = MediaBitstreams.generateCrashSignature(relativePath, name);
+ if (settings.getInt(lastCrash, 0) >= 3) {
+ ps.println(MediaBitstreams.K_NATIVE_CRASH);
+ } else {
+ ps.println(verifyBitstream(fullPath, name, frameChecksums));
+ }
+ }
+
+ }
+
+ private String verifyBitstream(String path, String name, List<String> frameChecksums) {
+ MediaExtractor ex = new MediaExtractor();
+ MediaCodec d = null;
+ try {
+ MediaCodec decoder = d = MediaCodec.createByCodecName(name);
+ ex.setDataSource(path);
+ ex.selectTrack(0);
+ ex.seekTo(0, MediaExtractor.SEEK_TO_NEXT_SYNC);
+ Future<Boolean> conform = mExecutorService.submit(new Callable<Boolean>() {
+ @Override
+ public Boolean call() throws Exception {
+ return MediaUtils.verifyDecoder(decoder, ex, frameChecksums);
+ }
+ });
+ return conform.get(15, TimeUnit.SECONDS).toString();
+ } catch (Exception e) {
+ return e.toString();
+ } finally {
+ ex.release();
+ if (d != null) {
+ d.release();
+ }
+ }
+ }
+
+ }
+
+ private void generateReportFile(String suffix, String reportKey, ReportCallback callback)
+ throws IOException, FileNotFoundException, Exception {
+
+ OutputStream out = new ByteArrayOutputStream(0);
+
+ try {
+
+ File tmpf = File.createTempFile(getClass().getSimpleName(), suffix, mAppCache);
+ Instrumentation inst = InstrumentationRegistry.getInstrumentation();
+ Bundle bundle = new Bundle();
+ bundle.putString(MediaBitstreams.KEY_APP_CACHE_DIR, mAppCache.getCanonicalPath());
+ bundle.putString(reportKey, tmpf.getCanonicalPath());
+ inst.sendStatus(INST_STATUS_IN_PROGRESS, bundle);
+
+ out = new FileOutputStream(tmpf);
+ callback.run(out);
+ out.flush();
+
+ } finally {
+
+ out.close();
+
+ }
+ }
+
+ @Test
+ public void testGetBitstreamsFormats() throws Exception {
+ generateReportFile(".xml",
+ MediaBitstreams.KEY_BITSTREAMS_FORMATS_XML,
+ new GenerateBitstreamsFormatsXml());
+ }
+
+ @Test
+ public void testGetSupportedBitstreams() throws Exception {
+ generateReportFile(".txt",
+ MediaBitstreams.KEY_SUPPORTED_BITSTREAMS_TXT,
+ new GenerateSupportedBitstreamsFormatsTxt());
+ }
+
+ @Test
+ public void testBitstreamsConformance() throws Exception {
+ generateReportFile(".txt",
+ MediaBitstreams.KEY_BITSTREAMS_VALIDATION_TXT,
+ new TestBitstreamsConformance());
+ }
+
+ /**
+ * Converts a single media track format string into a MediaFormat object
+ *
+ * @param trackFormatString a string representation of the format of one media track
+ * @return a MediaFormat
+ */
+ private static MediaFormat parseTrackFormat(String trackFormatString) {
+ MediaFormat format = new MediaFormat();
+ format.setString(MediaFormat.KEY_MIME, "");
+ for (String entry : trackFormatString.split(",")) {
+ String[] kv = entry.split("=");
+ if (kv.length < 2 || kv[1].isEmpty()) {
+ continue;
+ }
+ String k = kv[0];
+ String v = kv[1];
+ try {
+ format.setInteger(k, Integer.parseInt(v));
+ } catch (NumberFormatException e) {
+ format.setString(k, v);
+ }
+ }
+ return format;
+ }
+}
diff --git a/hostsidetests/media/bitstreams/common/Android.mk b/hostsidetests/media/bitstreams/common/Android.mk
new file mode 100644
index 0000000..1691257
--- /dev/null
+++ b/hostsidetests/media/bitstreams/common/Android.mk
@@ -0,0 +1,31 @@
+# Copyright (C) 2017 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.
+
+###############################################################################
+# Build the common library for use device-side
+###############################################################################
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_MODULE := media-bitstreams-common-devicesidelib
+
+LOCAL_SDK_VERSION := current
+
+include $(BUILD_STATIC_JAVA_LIBRARY)
diff --git a/hostsidetests/media/bitstreams/common/src/android/media/cts/bitstreams/MediaBitstreams.java b/hostsidetests/media/bitstreams/common/src/android/media/cts/bitstreams/MediaBitstreams.java
new file mode 100644
index 0000000..2266254
--- /dev/null
+++ b/hostsidetests/media/bitstreams/common/src/android/media/cts/bitstreams/MediaBitstreams.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2017 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.cts.bitstreams;
+
+/**
+ * This class provides constants and utilities shared between the host-side and device-side test
+ * components.
+ */
+public class MediaBitstreams {
+
+ /* options */
+ public static final String OPT_HOST_BITSTEAMS_PATH = "host-bitsteams-path";
+ public static final String OPT_DEVICE_BITSTEAMS_PATH = "device-bitsteams-path";
+ public static final String OPT_DOWNLOAD_BITSTREAMS = "download-bitstreams";
+ public static final String OPT_DEBUG_TARGET_DEVICE = "debug-target-device";
+ public static final String OPT_BITSTREAMS_TO_TEST_TXT = "bitstreams-to-test-txt";
+ public static final String OPT_UTILIZATION_RATE = "utilization-rate";
+ public static final String OPT_NUM_BATCHES = "num-batches";
+ public static final String OPT_LAST_CRASH = "last-crash";
+ public static final String OPT_BITSTREAMS_PREFIX = "prefix";
+
+ /* defaults */
+ public static final String DEFAULT_HOST_BITSTREAMS_PATH = "TestVectorsIttiam";
+ public static final String DEFAULT_DEVICE_BITSTEAMS_PATH = "/data/local/tmp/TestVectorsIttiam";
+
+ /* metric keys */
+ public static final String KEY_BITSTREAMS_FORMATS_XML = "bitstreams-formats-xml";
+ public static final String KEY_SUPPORTED_BITSTREAMS_TXT = "supported-bitstreams-txt";
+ public static final String KEY_BITSTREAMS_VALIDATION_TXT = "bitstreams-validation-txt";
+ public static final String KEY_APP_CACHE_DIR = "app-cache-dir";
+ public static final String KEY_ERR_MSG = "err-msg";
+
+ /* constants */
+ public static final String K_MODULE = "CtsMediaBitstreamsTestCases";
+ public static final String K_BITSTREAMS_LIST_TXT = "bitstreamsFile.txt";
+ public static final String K_TEST_GET_SUPPORTED_BITSTREAMS = "testGetSupportedBitstreams";
+ public static final String K_NATIVE_CRASH = "native crash";
+
+ /* utilities */
+ /**
+ * @param bitstreamPath path of individual bitstream relative to bitstreams root,
+ * e.g. {@code h264/../../../../*.mp4}
+ * @return checksum file path for {@code bitstreamPath}, e.g. {@code h264/../../../../*_md5}.
+ */
+ public static String getMd5Path(String bitstreamPath) {
+ String base = bitstreamPath.split("\\.")[0];
+ String codec = bitstreamPath.split("/", 2)[0];
+ String md5Path = String.format("%s_%s_md5", base, codec);
+ return md5Path;
+ }
+
+ /**
+ * @param path relative bitstream path, e.g. {@code h264/../../../../*.mp4}
+ * @param name codec name, e.g. {@code OMX.google.h264.decoder}
+ * @return crash signature for a crashed device decoding session,
+ * in the form of {@code <bitstream path>:<codec name>}
+ */
+ public static String generateCrashSignature(String path, String name) {
+ return String.format("%s:%s", path, name);
+ }
+
+
+}
diff --git a/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/MediaBitstreamsTest.java b/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/MediaBitstreamsTest.java
new file mode 100644
index 0000000..5f5e4cd
--- /dev/null
+++ b/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/MediaBitstreamsTest.java
@@ -0,0 +1,690 @@
+/*
+ * Copyright (C) 2017 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.cts.bitstreams;
+
+import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
+import com.android.compatibility.common.util.MetricsReportLog;
+import com.android.compatibility.common.util.ResultType;
+import com.android.compatibility.common.util.ResultUnit;
+import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.config.Option;
+import com.android.tradefed.config.OptionClass;
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.log.LogUtil.CLog;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+import com.android.tradefed.testtype.IAbi;
+import com.android.tradefed.testtype.IAbiReceiver;
+import com.android.tradefed.testtype.IBuildReceiver;
+import com.android.tradefed.testtype.IDeviceTest;
+import com.android.tradefed.util.FileUtil;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.PrintStream;
+import java.nio.file.Files;
+import java.util.ArrayDeque;
+import java.util.Deque;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedHashSet;
+import java.util.Map;
+import java.util.Set;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Test that verifies video bitstreams decode pixel perfectly
+ */
+@OptionClass(alias="media-bitstreams-test")
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class MediaBitstreamsTest implements IDeviceTest, IBuildReceiver, IAbiReceiver {
+
+ @Option(name = MediaBitstreams.OPT_HOST_BITSTEAMS_PATH,
+ description = "Absolute path of Ittiam bitstreams (host)",
+ mandatory = true)
+ private File mHostBitstreamsPath = new File(MediaBitstreams.DEFAULT_HOST_BITSTREAMS_PATH);
+
+ @Option(name = MediaBitstreams.OPT_DEVICE_BITSTEAMS_PATH,
+ description = "Absolute path of Ittiam bitstreams (device)")
+ private String mDeviceBitstreamsPath = MediaBitstreams.DEFAULT_DEVICE_BITSTEAMS_PATH;
+
+ @Option(name = MediaBitstreams.OPT_DOWNLOAD_BITSTREAMS,
+ description = "Whether to download the bitstreams files")
+ private boolean mDownloadBitstreams = false;
+
+ @Option(name = MediaBitstreams.OPT_UTILIZATION_RATE,
+ description = "Percentage of external storage space used for test")
+ private int mUtilizationRate = 80;
+
+ @Option(name = MediaBitstreams.OPT_NUM_BATCHES,
+ description = "Number of batches to test;"
+ + " each batch uses external storage up to utilization rate")
+ private int mNumBatches = Integer.MAX_VALUE;
+
+ @Option(name = MediaBitstreams.OPT_DEBUG_TARGET_DEVICE,
+ description = "Whether to debug target device under test")
+ private boolean mDebugTargetDevice = false;
+
+ @Option(name = MediaBitstreams.OPT_BITSTREAMS_PREFIX,
+ description = "Only test bitstreams in this sub-directory")
+ private String mPrefix = "";
+
+ /**
+ * A helper to access resources in the build.
+ */
+ private CompatibilityBuildHelper mBuildHelper;
+
+ private IAbi mAbi;
+ private ITestDevice mDevice;
+
+ private MediaBitstreamsTest(String prefix) {
+ mPrefix = prefix;
+ }
+
+ public MediaBitstreamsTest() {
+ this("");
+ }
+
+ @Override
+ public void setBuild(IBuildInfo buildInfo) {
+ // Get the build, this is used to access the APK.
+ mBuildHelper = new CompatibilityBuildHelper(buildInfo);
+ }
+
+ @Override
+ public void setAbi(IAbi abi) {
+ mAbi = abi;
+ }
+
+ @Override
+ public void setDevice(ITestDevice device) {
+ mDevice = device;
+ }
+
+ @Override
+ public ITestDevice getDevice() {
+ return mDevice;
+ }
+
+ /*
+ * Returns true if all necessary media files exist on the device, and false otherwise.
+ *
+ * This method is exposed for unit testing.
+ */
+ private boolean bitstreamsExistOnDevice(ITestDevice device)
+ throws DeviceNotAvailableException {
+ return device.doesFileExist(mDeviceBitstreamsPath)
+ && device.isDirectory(mDeviceBitstreamsPath);
+ }
+
+ private String getCurrentMethod() {
+ return Thread.currentThread().getStackTrace()[2].getMethodName();
+ }
+
+ Map<String, String> getArgs() {
+ Map<String, String> args = new HashMap<>();
+ args.put(MediaBitstreams.OPT_DEBUG_TARGET_DEVICE, Boolean.toString(mDebugTargetDevice));
+ args.put(MediaBitstreams.OPT_DEVICE_BITSTEAMS_PATH, mDeviceBitstreamsPath);
+ return args;
+ }
+
+ private class ProcessBitstreamsFormats extends ReportProcessor {
+
+ @Override
+ void setUp(ITestDevice device) throws DeviceNotAvailableException {
+ if (mDownloadBitstreams || !bitstreamsExistOnDevice(device)) {
+ device.pushDir(mHostBitstreamsPath, mDeviceBitstreamsPath);
+ }
+ }
+
+ @Override
+ Map<String, String> getArgs() {
+ return MediaBitstreamsTest.this.getArgs();
+ }
+
+ @Override
+ void process(ITestDevice device, String reportPath)
+ throws DeviceNotAvailableException, IOException {
+ File testDir = mBuildHelper.getTestsDir();
+ File dynamicConfigFile = new File(testDir, MediaBitstreams.K_MODULE + ".dynamic");
+ device.pullFile(reportPath, dynamicConfigFile);
+ CLog.i("Pulled bitstreams formats to %s", dynamicConfigFile.getPath());
+ }
+
+ }
+
+ private class ProcessBitstreamsValidation extends ReportProcessor {
+
+ Set<String> mBitstreams;
+ Deque<String> mProcessedBitstreams = new ArrayDeque<>();
+ private final String mMethodName;
+ private final String mBitstreamsListTxt = new File(
+ mDeviceBitstreamsPath,
+ MediaBitstreams.K_BITSTREAMS_LIST_TXT).toString();
+ private String mLastCrash;
+
+ ProcessBitstreamsValidation(Set<String> bitstreams, String methodName) {
+ mBitstreams = bitstreams;
+ mMethodName = methodName;
+ }
+
+ private String getBitstreamsListString() {
+ OutputStream baos = new ByteArrayOutputStream();
+ PrintStream ps = new PrintStream(baos, true);
+ try {
+ for (String b : mBitstreams) {
+ ps.println(b);
+ }
+ return baos.toString();
+ } finally {
+ ps.close();
+ }
+ }
+
+ private void pushBitstreams(ITestDevice device)
+ throws IOException, DeviceNotAvailableException {
+ File tmp = null;
+ try {
+ CLog.i("Pushing %d bitstream(s) from %s to %s",
+ mBitstreams.size(),
+ mHostBitstreamsPath,
+ mDeviceBitstreamsPath);
+ tmp = Files.createTempDirectory(null).toFile();
+ for (String b : mBitstreams) {
+ String m = MediaBitstreams.getMd5Path(b);
+ for (String f : new String[] {m, b}) {
+ File tmpf = new File(tmp, f);
+ new File(tmpf.getParent()).mkdirs();
+ FileUtil.copyFile(new File(mHostBitstreamsPath, f), tmpf);
+ }
+ }
+ device.executeShellCommand(String.format("rm -rf %s", mDeviceBitstreamsPath));
+ device.pushDir(tmp, mDeviceBitstreamsPath);
+ device.pushString(getBitstreamsListString(), mBitstreamsListTxt);
+ } finally {
+ FileUtil.recursiveDelete(tmp);
+ }
+ }
+
+ @Override
+ void setUp(ITestDevice device) throws DeviceNotAvailableException, IOException {
+ pushBitstreams(device);
+ }
+
+ @Override
+ Map<String, String> getArgs() {
+ Map<String, String> args = MediaBitstreamsTest.this.getArgs();
+ if (mLastCrash != null) {
+ args.put(MediaBitstreams.OPT_LAST_CRASH, mLastCrash);
+ }
+ return args;
+ }
+
+ private void parse(ITestDevice device, String reportPath)
+ throws DeviceNotAvailableException {
+ String[] lines = getReportLines(device, reportPath);
+ mProcessedBitstreams.clear();
+ for (int i = 0; i < lines.length;) {
+
+ String path = lines[i++];
+ mProcessedBitstreams.add(path);
+ String className = MediaBitstreamsTest.class.getCanonicalName();
+ MetricsReportLog report = new MetricsReportLog(
+ mBuildHelper.getBuildInfo(), mAbi.getName(),
+ String.format("%s#%s", className, mMethodName),
+ getClass().getSimpleName(), path);
+
+ boolean failedEarly;
+ String errMsg;
+ if (i < lines.length) {
+ failedEarly = Boolean.parseBoolean(lines[i++]);
+ errMsg = failedEarly ? lines[i++] : "";
+ } else {
+ failedEarly = true;
+ errMsg = MediaBitstreams.K_NATIVE_CRASH;
+ mLastCrash = MediaBitstreams.generateCrashSignature(path, "");
+ mProcessedBitstreams.removeLast();
+ }
+ if (failedEarly) {
+ String keyErrMsg = MediaBitstreams.KEY_ERR_MSG;
+ report.addValue(keyErrMsg, errMsg, ResultType.NEUTRAL, ResultUnit.NONE);
+ report.submit();
+ continue;
+ }
+
+ int n = Integer.parseInt(lines[i++]);
+ for (int j = 0; j < n && i < lines.length; j++) {
+ String name = lines[i++];
+ String result;
+ if (i < lines.length) {
+ result = lines[i++];
+ } else {
+ result = MediaBitstreams.K_NATIVE_CRASH;
+ mLastCrash = MediaBitstreams.generateCrashSignature(path, name);
+ mProcessedBitstreams.removeLast();
+ }
+ report.addValue(name, result, ResultType.NEUTRAL, ResultUnit.NONE);
+ }
+ report.submit();
+
+ }
+ }
+
+ @Override
+ void process(ITestDevice device, String reportPath)
+ throws DeviceNotAvailableException, IOException {
+ parse(device, reportPath);
+ }
+
+ @Override
+ boolean recover(ITestDevice device, String reportPath)
+ throws DeviceNotAvailableException, IOException {
+ try {
+ parse(device, reportPath);
+ mBitstreams.removeAll(mProcessedBitstreams);
+ device.pushString(getBitstreamsListString(), mBitstreamsListTxt);
+ return true;
+ } catch (RuntimeException e) {
+ CLog.e("Error parsing report; saving report to %s", device.pullFile(reportPath));
+ CLog.e(e);
+ return false;
+ }
+ }
+
+ }
+
+ @Ignore
+ @Test
+ public void testGetBitstreamsFormats() throws DeviceNotAvailableException, IOException {
+ ReportProcessor processor = new ProcessBitstreamsFormats();
+ processor.processDeviceReport(
+ getDevice(),
+ mBuildHelper.getTestsDir(),
+ getCurrentMethod(), MediaBitstreams.KEY_BITSTREAMS_FORMATS_XML);
+ }
+
+ @Test
+ public void testH264Yuv420_8bitBpBitrate() throws Exception {
+ testBitstreamsConformance("h264/yuv420/8bit/bp/bitrate");
+ }
+
+ @Test
+ public void testH264Yuv420_8bitBpLevels() throws Exception {
+ testBitstreamsConformance("h264/yuv420/8bit/bp/levels");
+ }
+
+ @Test
+ public void testH264Yuv420_8bitBpParamsCrowd_640x360p50f32() throws Exception {
+ testBitstreamsConformance("h264/yuv420/8bit/bp/params/crowd_640x360p50f32");
+ }
+
+ @Test
+ public void testH264Yuv420_8bitBpParamsCrowd_854x480p50f32() throws Exception {
+ testBitstreamsConformance("h264/yuv420/8bit/bp/params/crowd_854x480p50f32");
+ }
+
+ @Test
+ public void testH264Yuv420_8bitBpParamsCrowd_1280x720p50f32() throws Exception {
+ testBitstreamsConformance("h264/yuv420/8bit/bp/params/crowd_1280x720p50f32");
+ }
+
+ @Test
+ public void testH264Yuv420_8bitBpParamsCrowd_1920x1080p50f32() throws Exception {
+ testBitstreamsConformance("h264/yuv420/8bit/bp/params/crowd_1920x1080p50f32");
+ }
+
+ @Test
+ public void testH264Yuv420_8bitBpParamsCrowd_3840x2160p50f32() throws Exception {
+ testBitstreamsConformance("h264/yuv420/8bit/bp/params/crowd_3840x2160p50f32");
+ }
+
+ @Test
+ public void testH264Yuv420_8bitBpResolutions() throws Exception {
+ testBitstreamsConformance("h264/yuv420/8bit/bp/resolutions");
+ }
+
+ @Test
+ public void testH264Yuv420_8bitBpSlicesCrowd_640x360p50f32() throws Exception {
+ testBitstreamsConformance("h264/yuv420/8bit/bp/slices/crowd_640x360p50f32");
+ }
+
+ @Test
+ public void testH264Yuv420_8bitBpSlicesCrowd_854x480p50f32() throws Exception {
+ testBitstreamsConformance("h264/yuv420/8bit/bp/slices/crowd_854x480p50f32");
+ }
+
+ @Test
+ public void testH264Yuv420_8bitBpSlicesCrowd_1280x720p50f32() throws Exception {
+ testBitstreamsConformance("h264/yuv420/8bit/bp/slices/crowd_1280x720p50f32");
+ }
+
+ @Test
+ public void testH264Yuv420_8bitBpSlicesCrowd_1920x1080p50f32() throws Exception {
+ testBitstreamsConformance("h264/yuv420/8bit/bp/slices/crowd_1920x1080p50f32");
+ }
+
+ @Test
+ public void testH264Yuv420_8bitBpSlicesCrowd_3840x2160p50f32() throws Exception {
+ testBitstreamsConformance("h264/yuv420/8bit/bp/slices/crowd_3840x2160p50f32");
+ }
+
+ @Test
+ public void testH264Yuv420_8bitMpBitrate() throws Exception {
+ testBitstreamsConformance("h264/yuv420/8bit/mp/bitrate");
+ }
+
+ @Test
+ public void testH264Yuv420_8bitMpGopCrowd_640x360p50() throws Exception {
+ testBitstreamsConformance("h264/yuv420/8bit/mp/gop/crowd_640x360p50");
+ }
+
+ @Test
+ public void testH264Yuv420_8bitMpGopCrowd_854x480p50() throws Exception {
+ testBitstreamsConformance("h264/yuv420/8bit/mp/gop/crowd_854x480p50");
+ }
+
+ @Test
+ public void testH264Yuv420_8bitMpGopCrowd_1280x720p50() throws Exception {
+ testBitstreamsConformance("h264/yuv420/8bit/mp/gop/crowd_1280x720p50");
+ }
+
+ @Test
+ public void testH264Yuv420_8bitMpGopCrowd_1920x1080p50() throws Exception {
+ testBitstreamsConformance("h264/yuv420/8bit/mp/gop/crowd_1920x1080p50");
+ }
+
+ @Test
+ public void testH264Yuv420_8bitMpGopCrowd_3840x2160p50() throws Exception {
+ testBitstreamsConformance("h264/yuv420/8bit/mp/gop/crowd_3840x2160p50");
+ }
+
+ @Test
+ public void testH264Yuv420_8bitMpLevels() throws Exception {
+ testBitstreamsConformance("h264/yuv420/8bit/mp/levels");
+ }
+
+ @Test
+ public void testH264Yuv420_8bitMpParamsCrowd_640x360p50f32() throws Exception {
+ testBitstreamsConformance("h264/yuv420/8bit/mp/params/crowd_640x360p50f32");
+ }
+
+ @Test
+ public void testH264Yuv420_8bitMpParamsCrowd_854x480p50f32() throws Exception {
+ testBitstreamsConformance("h264/yuv420/8bit/mp/params/crowd_854x480p50f32");
+ }
+
+ @Test
+ public void testH264Yuv420_8bitMpParamsCrowd_1280x720p50f32() throws Exception {
+ testBitstreamsConformance("h264/yuv420/8bit/mp/params/crowd_1280x720p50f32");
+ }
+
+ @Test
+ public void testH264Yuv420_8bitMpParamsCrowd_1920x1080p50f32() throws Exception {
+ testBitstreamsConformance("h264/yuv420/8bit/mp/params/crowd_1920x1080p50f32");
+ }
+
+ @Test
+ public void testH264Yuv420_8bitMpParamsCrowd_3840x2160p50f32() throws Exception {
+ testBitstreamsConformance("h264/yuv420/8bit/mp/params/crowd_3840x2160p50f32");
+ }
+
+ @Test
+ public void testH264Yuv420_8bitMpResolutions() throws Exception {
+ testBitstreamsConformance("h264/yuv420/8bit/mp/resolutions");
+ }
+
+ @Test
+ public void testH264Yuv420_8bitMpSlicesCrowd_640x360p50f32() throws Exception {
+ testBitstreamsConformance("h264/yuv420/8bit/mp/slices/crowd_640x360p50f32");
+ }
+
+ @Test
+ public void testH264Yuv420_8bitMpSlicesCrowd_854x480p50f32() throws Exception {
+ testBitstreamsConformance("h264/yuv420/8bit/mp/slices/crowd_854x480p50f32");
+ }
+
+ @Test
+ public void testH264Yuv420_8bitMpSlicesCrowd_1280x720p50f32() throws Exception {
+ testBitstreamsConformance("h264/yuv420/8bit/mp/slices/crowd_1280x720p50f32");
+ }
+
+ @Test
+ public void testH264Yuv420_8bitMpSlicesCrowd_1920x1080p50f32() throws Exception {
+ testBitstreamsConformance("h264/yuv420/8bit/mp/slices/crowd_1920x1080p50f32");
+ }
+
+ @Test
+ public void testH264Yuv420_8bitMpSlicesCrowd_3840x2160p50f32() throws Exception {
+ testBitstreamsConformance("h264/yuv420/8bit/mp/slices/crowd_3840x2160p50f32");
+ }
+
+ @Test
+ public void testH264Yuv420_8bitHpBitrate() throws Exception {
+ testBitstreamsConformance("h264/yuv420/8bit/hp/bitrate");
+ }
+
+ @Test
+ public void testH264Yuv420_8bitHpGopCrowd_640x360p50() throws Exception {
+ testBitstreamsConformance("h264/yuv420/8bit/hp/gop/crowd_640x360p50");
+ }
+
+ @Test
+ public void testH264Yuv420_8bitHpGopCrowd_854x480p50() throws Exception {
+ testBitstreamsConformance("h264/yuv420/8bit/hp/gop/crowd_854x480p50");
+ }
+
+ @Test
+ public void testH264Yuv420_8bitHpGopCrowd_1280x720p50() throws Exception {
+ testBitstreamsConformance("h264/yuv420/8bit/hp/gop/crowd_1280x720p50");
+ }
+
+ @Test
+ public void testH264Yuv420_8bitHpGopCrowd_1920x1080p50() throws Exception {
+ testBitstreamsConformance("h264/yuv420/8bit/hp/gop/crowd_1920x1080p50");
+ }
+
+ @Test
+ public void testH264Yuv420_8bitHpGopCrowd_3840x2160p50() throws Exception {
+ testBitstreamsConformance("h264/yuv420/8bit/hp/gop/crowd_3840x2160p50");
+ }
+
+ @Test
+ public void testH264Yuv420_8bitHpLevels() throws Exception {
+ testBitstreamsConformance("h264/yuv420/8bit/hp/levels");
+ }
+
+ @Test
+ public void testH264Yuv420_8bitHpParamsCrowd_640x360p50f32() throws Exception {
+ testBitstreamsConformance("h264/yuv420/8bit/hp/params/crowd_640x360p50f32");
+ }
+
+ @Test
+ public void testH264Yuv420_8bitHpParamsCrowd_854x480p50f32() throws Exception {
+ testBitstreamsConformance("h264/yuv420/8bit/hp/params/crowd_854x480p50f32");
+ }
+
+ @Test
+ public void testH264Yuv420_8bitHpParamsCrowd_1280x720p50f32() throws Exception {
+ testBitstreamsConformance("h264/yuv420/8bit/hp/params/crowd_1280x720p50f32");
+ }
+
+ @Test
+ public void testH264Yuv420_8bitHpParamsCrowd_1920x1080p50f32() throws Exception {
+ testBitstreamsConformance("h264/yuv420/8bit/hp/params/crowd_1920x1080p50f32");
+ }
+
+ @Test
+ public void testH264Yuv420_8bitHpParamsCrowd_3840x2160p50f32() throws Exception {
+ testBitstreamsConformance("h264/yuv420/8bit/hp/params/crowd_3840x2160p50f32");
+ }
+
+ @Test
+ public void testH264Yuv420_8bitHpResolutions() throws Exception {
+ testBitstreamsConformance("h264/yuv420/8bit/hp/resolutions");
+ }
+
+ @Test
+ public void testH264Yuv420_8bitHpScalingmatrixCrowd_640x360p50f32() throws Exception {
+ testBitstreamsConformance("h264/yuv420/8bit/hp/scalingmatrix/crowd_640x360p50f32");
+ }
+
+ @Test
+ public void testH264Yuv420_8bitHpScalingmatrixCrowd_854x480p50f32() throws Exception {
+ testBitstreamsConformance("h264/yuv420/8bit/hp/scalingmatrix/crowd_854x480p50f32");
+ }
+
+ @Test
+ public void testH264Yuv420_8bitHpScalingmatrixCrowd_1280x720p50f32() throws Exception {
+ testBitstreamsConformance("h264/yuv420/8bit/hp/scalingmatrix/crowd_1280x720p50f32");
+ }
+
+ @Test
+ public void testH264Yuv420_8bitHpScalingmatrixCrowd_1920x1080p50f32() throws Exception {
+ testBitstreamsConformance("h264/yuv420/8bit/hp/scalingmatrix/crowd_1920x1080p50f32");
+ }
+
+ @Test
+ public void testH264Yuv420_8bitHpScalingmatrixCrowd_3840x2160p50f32() throws Exception {
+ testBitstreamsConformance("h264/yuv420/8bit/hp/scalingmatrix/crowd_3840x2160p50f32");
+ }
+
+ @Test
+ public void testH264Yuv420_8bitHpSlicesCrowd_640x360p50f32() throws Exception {
+ testBitstreamsConformance("h264/yuv420/8bit/hp/slices/crowd_640x360p50f32");
+ }
+
+ @Test
+ public void testH264Yuv420_8bitHpSlicesCrowd_854x480p50f32() throws Exception {
+ testBitstreamsConformance("h264/yuv420/8bit/hp/slices/crowd_854x480p50f32");
+ }
+
+ @Test
+ public void testH264Yuv420_8bitHpSlicesCrowd_1280x720p50f32() throws Exception {
+ testBitstreamsConformance("h264/yuv420/8bit/hp/slices/crowd_1280x720p50f32");
+ }
+
+ @Test
+ public void testH264Yuv420_8bitHpSlicesCrowd_1920x1080p50f32() throws Exception {
+ testBitstreamsConformance("h264/yuv420/8bit/hp/slices/crowd_1920x1080p50f32");
+ }
+
+ @Test
+ public void testH264Yuv420_8bitHpSlicesCrowd_3840x2160p50f32() throws Exception {
+ testBitstreamsConformance("h264/yuv420/8bit/hp/slices/crowd_3840x2160p50f32");
+ }
+
+ @Test
+ public void testVp8Yuv420_8bitBitrate() throws Exception {
+ testBitstreamsConformance("vp8/yuv420/8bit/bitrate");
+ }
+
+ @Test
+ public void testVp8Yuv420_8bitParamsCrowd_640x360p50f32() throws Exception {
+ testBitstreamsConformance("vp8/yuv420/8bit/params/crowd_640x360p50f32");
+ }
+
+ @Test
+ public void testVp8Yuv420_8bitParamsCrowd_854x480p50f32() throws Exception {
+ testBitstreamsConformance("vp8/yuv420/8bit/params/crowd_854x480p50f32");
+ }
+
+ @Test
+ public void testVp8Yuv420_8bitParamsCrowd_1280x720p50f32() throws Exception {
+ testBitstreamsConformance("vp8/yuv420/8bit/params/crowd_1280x720p50f32");
+ }
+
+ @Test
+ public void testVp8Yuv420_8bitParamsCrowd_1920x1080p50f32() throws Exception {
+ testBitstreamsConformance("vp8/yuv420/8bit/params/crowd_1920x1080p50f32");
+ }
+
+ @Test
+ public void testVp8Yuv420_8bitParamsCrowd_3840x2160p50f32() throws Exception {
+ testBitstreamsConformance("vp8/yuv420/8bit/params/crowd_3840x2160p50f32");
+ }
+
+ @Test
+ public void testVp8Yuv420_8bitResolution() throws Exception {
+ testBitstreamsConformance("vp8/yuv420/8bit/resolution");
+ }
+
+ @Ignore
+ @Test
+ public void testBitstreamsConformance()
+ throws DeviceNotAvailableException, IOException {
+ testBitstreamsConformance(mPrefix);
+ }
+
+ private void testBitstreamsConformance(String prefix)
+ throws DeviceNotAvailableException, IOException {
+
+ ITestDevice device = getDevice();
+ SupportedBitstreamsProcessor preparer;
+ preparer = new SupportedBitstreamsProcessor(prefix, mDebugTargetDevice);
+ preparer.processDeviceReport(
+ device,
+ mBuildHelper.getTestsDir(),
+ MediaBitstreams.K_TEST_GET_SUPPORTED_BITSTREAMS,
+ MediaBitstreams.KEY_SUPPORTED_BITSTREAMS_TXT);
+ Set<String> supportedBitstreams = preparer.getSupportedBitstreams();
+ CLog.i("%d supported bitstreams under %s", supportedBitstreams.size(), prefix);
+
+ int n = 0;
+ long size = 0;
+ long limit = device.getExternalStoreFreeSpace() * mUtilizationRate * 1024 / 100;
+
+ String currentMethod = getCurrentMethod();
+ Set<String> bitstreams = new LinkedHashSet<>();
+ Iterator<String> iter = supportedBitstreams.iterator();
+
+ for (int i = 0; i < supportedBitstreams.size(); i++) {
+
+ if (n >= mNumBatches) {
+ break;
+ }
+
+ String bitstreamPath = iter.next();
+ File bitstreamFile = new File(mHostBitstreamsPath, bitstreamPath);
+ String md5Path = MediaBitstreams.getMd5Path(bitstreamPath);
+ File md5File = new File(mHostBitstreamsPath, md5Path);
+
+ if (md5File.exists() && bitstreamFile.exists()) {
+ size += md5File.length();
+ size += bitstreamFile.length();
+ bitstreams.add(bitstreamPath);
+ }
+
+ if (size > limit || i + 1 == supportedBitstreams.size()) {
+ ReportProcessor processor;
+ processor = new ProcessBitstreamsValidation(bitstreams, currentMethod);
+ processor.processDeviceReport(
+ device,
+ mBuildHelper.getTestsDir(),
+ currentMethod, MediaBitstreams.KEY_BITSTREAMS_VALIDATION_TXT);
+ bitstreams.clear();
+ size = 0;
+ n++;
+ }
+
+ }
+
+ }
+
+
+}
diff --git a/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/ReportProcessor.java b/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/ReportProcessor.java
new file mode 100644
index 0000000..8c41347
--- /dev/null
+++ b/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/ReportProcessor.java
@@ -0,0 +1,177 @@
+/*
+ * Copyright (C) 2017 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.cts.bitstreams;
+
+import com.android.ddmlib.testrunner.TestIdentifier;
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.log.LogUtil.CLog;
+import com.android.tradefed.result.ITestInvocationListener;
+import com.android.tradefed.testtype.AndroidJUnitTest;
+import java.io.File;
+import java.io.IOException;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Map.Entry;
+
+/**
+ * A {@link ReportProcessor} installs {@code CtsMediaBitstreamsDeviceSideTestApp.apk}
+ * onto a test device, invokes a report-generating test, then pulls and processes
+ * the generated report.
+ */
+abstract class ReportProcessor {
+
+ private final Map<String, String> mMetrics = new HashMap<>();
+ private String mFailureStackTrace = null;
+
+ private static final String APP_APK = "CtsMediaBitstreamsDeviceSideTestApp.apk";
+ private static final String APP_CLS_NAME = "MediaBitstreamsDeviceSideTest";
+ private static final String APP_PKG_NAME = "android.media.cts.bitstreams.app";
+
+ /**
+ * Setup {@code device} before test.
+ *
+ * @param device device under test
+ * @throws DeviceNotAvailableException
+ * @throws IOException
+ */
+ void setUp(ITestDevice device) throws DeviceNotAvailableException, IOException {}
+
+ Map<String, String> getArgs() {
+ return Collections.emptyMap();
+ }
+
+ /**
+ * Process test report.
+ *
+ * @param device device under test
+ * @param reportPath path to test report on {@code device}
+ * @throws DeviceNotAvailableException
+ * @throws IOException
+ */
+ void process(ITestDevice device, String reportPath)
+ throws DeviceNotAvailableException, IOException {}
+
+ /**
+ * Attempt to recover from a crash during test on {@code device}.
+ *
+ * @param device device under test
+ * @param reportPath path to test report on {@code device}
+ * @throws DeviceNotAvailableException
+ * @throws IOException
+ * @return true if successfully recovered from test crash, false otherwise
+ */
+ boolean recover(ITestDevice device, String reportPath)
+ throws DeviceNotAvailableException, IOException {
+ return false;
+ }
+
+ /**
+ * Cleanup {@code device} after test
+ * @param device device under test
+ * @param reportPath path to test report on {@code device}
+ */
+ void cleanup(ITestDevice device, String reportPath) {
+ try {
+ device.executeShellCommand(String.format("rm %s", reportPath));
+ } catch (DeviceNotAvailableException e) {
+ CLog.e(e);
+ }
+ }
+
+ /**
+ * @param device device under test
+ * @param reportPath path to test report on {@code device}
+ * @return array of lines in report, sans newline
+ * @throws DeviceNotAvailableException
+ */
+ static String[] getReportLines(ITestDevice device, String reportPath)
+ throws DeviceNotAvailableException {
+ String cat = String.format("cat %s", reportPath);
+ String output = device.executeShellCommand(cat);
+ return output.isEmpty() ? new String[0] : output.split("\n");
+ }
+
+ /* Special listener for setting MediaPreparer instance variable values */
+ private class MediaBitstreamsListener implements ITestInvocationListener {
+
+ @Override
+ public void testEnded(TestIdentifier test, Map<String, String> metrics) {
+ mMetrics.putAll(metrics);
+ }
+
+ @Override
+ public void testFailed(TestIdentifier test, String trace) {
+ mFailureStackTrace = trace;
+ }
+
+ }
+
+ private boolean runDeviceTest(
+ ITestDevice device, File testDir, String method, String reportKey,
+ int testTimeout, long shellTimeout)
+ throws DeviceNotAvailableException {
+
+ File apkFile = new File(testDir, APP_APK);
+ device.installPackage(apkFile, true, true);
+
+ String fullTestName = String.format("%s.%s#%s", APP_PKG_NAME, APP_CLS_NAME, method);
+ AndroidJUnitTest instrTest = new AndroidJUnitTest();
+ instrTest.setDevice(device);
+ instrTest.setPackageName(APP_PKG_NAME);
+ instrTest.addIncludeFilter(fullTestName);
+ instrTest.setTestTimeout(testTimeout);
+ instrTest.setShellTimeout(shellTimeout);
+ for (Entry<String, String> e : getArgs().entrySet()) {
+ instrTest.addInstrumentationArg(e.getKey(), e.getValue());
+ }
+ instrTest.run(new MediaBitstreamsListener());
+
+ return checkFile(reportKey);
+
+ }
+
+ private boolean checkFile(String reportKey) {
+ if (mFailureStackTrace != null) {
+ CLog.w("Retrieving bitstreams formats failed with trace:\n%s", mFailureStackTrace);
+ mFailureStackTrace = null;
+ return false;
+ } else if (!mMetrics.containsKey(reportKey)) {
+ CLog.w("Failed to generate file key=%s on device", reportKey);
+ return false;
+ }
+ return true;
+ }
+
+ void processDeviceReport(
+ ITestDevice device, File testDir, String method, String reportKey)
+ throws DeviceNotAvailableException, IOException {
+ try {
+ setUp(device);
+ while (!runDeviceTest(device, testDir, method, reportKey, 0, 0)) {
+ if (!recover(device, mMetrics.get(reportKey))) {
+ return;
+ }
+ }
+ process(device, mMetrics.get(reportKey));
+ } finally {
+ cleanup(device, mMetrics.get(reportKey));
+ }
+ }
+
+}
diff --git a/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/SupportedBitstreamsProcessor.java b/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/SupportedBitstreamsProcessor.java
new file mode 100644
index 0000000..8a6c74e
--- /dev/null
+++ b/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/SupportedBitstreamsProcessor.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2017 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.cts.bitstreams;
+
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.ITestDevice;
+import java.util.HashMap;
+import java.util.LinkedHashSet;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Runs a test on target device to query support for bitstreams listed in device side
+ * dynamic configuration file.
+ */
+public class SupportedBitstreamsProcessor extends ReportProcessor {
+
+ private final String mPrefix;
+ private final boolean mDebugTargetDevice;
+ private final Set<String> mSupportedBitstreams = new LinkedHashSet<>();
+
+ public SupportedBitstreamsProcessor() {
+ this("",false);
+ }
+
+ /**
+ * @param prefix only bitstreams whose relative path starts with {@code prefix}
+ * would be processed
+ * @param debugTargetDevice whether to pause {@code device} for debugging
+ */
+ public SupportedBitstreamsProcessor(String prefix, boolean debugTargetDevice) {
+ mPrefix = prefix;
+ mDebugTargetDevice = debugTargetDevice;
+ }
+
+ /**
+ * @return paths of supported devices on device
+ */
+ public Set<String> getSupportedBitstreams() {
+ return mSupportedBitstreams;
+ }
+
+ @Override
+ Map<String, String> getArgs() {
+ Map<String, String> args = new HashMap<>();
+ args.put(MediaBitstreams.OPT_BITSTREAMS_PREFIX, mPrefix);
+ args.put(MediaBitstreams.OPT_DEBUG_TARGET_DEVICE, Boolean.toString(mDebugTargetDevice));
+ return args;
+ }
+
+ @Override
+ void process(ITestDevice device, String reportPath) throws DeviceNotAvailableException {
+ mSupportedBitstreams.clear();
+ for (String path: getReportLines(device, reportPath)) {
+ if (path.isEmpty()) {
+ continue;
+ }
+ mSupportedBitstreams.add(path);
+ }
+ }
+
+}